没做题,真的以为自己对指针算是掌握了,刷面试题时才发现自己对指针参数传递问题并不是真正搞透彻了,所以参考了很多其他博客写一篇指针参数传递的文章,加深一下自己的理解,水平有限,如有错误,欢迎指正,谢谢。
大家先看一下经常出现的笔试题:
程序1:
void myMalloc(char *s) //想在函数中分配内存,再返回
{
s=(char *)malloc(100);
}
void main()
{
char *p=NULL;
myMalloc(p); //这里的p实际还是NULL,p的值没有改变,为什么?
if(p)
free(p);
}
程序2:
void myMalloc(char **s)
{
*s=(char *)malloc(100);
}
void main()
{
char *p=NULL;
myMalloc(&p);//这里的p可以得到正确的值了
if(p)
free(p);
}
程序1中被分配内存的是形参s,p没有分配内存;
程序2中被分配内存的是形参s指向的指针p,所以分配了内存。
深入理解后,才发现自己不是指针没明白,是函数调用的问题。函数调用必须提到的就是参数传递的问题,参数传递分为两种:一种是值传递,另一种是引用;这里说的主要是
值传递,暂时不说引用传递。值传递又分为两种:一种是实际的值传递,如int类型的参数传递属于实际值传递;另一种就是地址值传递,实参把实际地址传递给形参,如指针就是地址值传递。
参数传递的重点:
当实参把实际值或地址值传递给形参时,实际上不是直接使用实参,而是在栈去开辟内存空间copy一个副本,int a的副本是_a,(_a=a),char *p的副本是_p(_p=p),所以函数内的操作都是对副本进行操作,改变形参的值不会影响实参的值,函数执行完就释放副本开辟的空间。
我们还得清楚一个概念就是指针的概念,
指针其实也是一个参数,和int及char类似,int参数存放整数,char参数存放字符,指针存放的是一个地址而已;指针就是保存一片内存的起始地址,知道这个指针就可以对这个指针指向的内存进行操作;指向指针的指针即二级指针,保存是一级指针的地址。
下面结合具体例子真正了解一下指针参数是如何传递内存的?
有这么一句话:如果函数的参数是一级指针,不要指望用该指针去申请动态内存。如下面的程序3:
void GetMemory(char *p, int num)
{
p = (char *)malloc(sizeof(char) * num);
}
void Test(void)
{
char *str = NULL;
GetMemory(str, 100); // str 仍然为 NULL
strcpy(str, "hello"); // 运行错误
}
程序3:试图用指针参数申请动态内存
程序3中Test函数的语句GetMemory(str, 100);并没有使str获得期望的内存,str依旧是NULL,为什么?
我们先看一下malloc之前和malloc之后的指针p和指针str的地址以及指向情况:
malloc之前的指向情况:
malloc之后的指向情况:
问题出在GetMemory函数中,原因在于前面提到的函数传递参数时编译器会为函数的每个参数在栈区开辟内存空间copy一个临时副本_str(_str=str)。如果GetMemory函数体内的程序修改了_str的内容,就导致参数str的内容作相应的修改,这就是指针可以用作输出参数的原因。但是在程序3中,_str申请了新的内存,只是把_str所指向的内存地址改变了,但是str指向的内存地址丝毫没有改变,所以GetMemory函数调用结束后,形参被释放掉,并不输出任何东西,反而会泄漏一块内存,因为没有用free释放内存。
如果还是考虑要用指针参数来申请内存,应该改用“指向指针的指针”,如程序4:
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(sizeof(char) * num);
}
void Test2(void)
{
char *str = NULL;
GetMemory2(&str, 100); // 注意参数是 &str,而不是str
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
程序4:用指向指针的指针申请动态内存
我们看一下malloc之前和malloc之后的指针p和指针str的地址以及指向情况:
malloc之前的指向情况:
malloc之后的指向情况:
除了使用“指向指针的指针”的方法,我们可以用函数返回值来传递动态内存,这种方法简单些,如程序5:
char *GetMemory3(int num)
{
char *p = (char *)malloc(sizeof(char) * num);
return p;
}
void Test3(void)
{
char *str = NULL;
str = GetMemory3(100);
strcpy(str, "hello");
cout<< str << endl;
free(str);
}
程序5:用函数返回值来传递动态内存
利用程序5:函数返回值来传递动态内存这种方法虽然好用,但是很多人会把return语句用错。很多人会不经意地用return语句返回指向“栈内存”的指针,会出现野指针现象,因为该内存在函数调用结束时自动消亡,如程序6:
char *GetString(void)
{
char p[] = "hello world";
return p; // 编译器将提出警告
}
void Test4(void)
{
char *str = NULL;
str = GetString(); // str 的内容是垃圾
cout<< str << endl;
}
程序6:return语句返回指向“栈内存”的指针
用调试器逐步跟踪程序6,发现执行str = GetString();语句后str不再是NULL指针,但是str的内容不是“hello word”而是野指针。
如果把程序6改成下面程序7,会怎么样?
程序7:
char *GetString2(void)
{
char *p = "hello world";
return p;
}
void Test5(void)
{
char *str = NULL;
str = GetString2();
cout<< str << endl;
}
程序7:return语句返回常量字符串
函数Test5运行虽然不会出错,但是函数GetString2的设计概念却是错误的。因为GetString2内的“hello world”是常量字符串,位于静态存储区,它在程序生命周期内恒定不变。无论什么时候调用GetString2,它返回的始终是同一个“只读”的内存块。
上面提到出现野指针的现象,可能大家对野指针的概念也有点模糊,在这简单说一下
:“野指针”不是NULL指针,是指向“垃圾”内存的指针。我们一般不会错用NULL指针,因为用if语句很容易判断,if语句对“野指针”不起作用。
野指针的成因主要有三种:
①指针没有初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如:
char *p = NULL;
char *str = (char *) malloc(100);
②指针p被free或者delete之后,没有置为NULL,让人误以为p是个合法的指针。
③指针操作超越了变量的作用范围,这种情况让人防不胜防,如上面所讲的程序6。
阅读(2427) | 评论(0) | 转发(0) |