写在前面:内容主为分享,非教学,以下内容如有不当之处,恳请批评指正。
今天在学习c++的时候,突然想到一个问题,引用既然是指针的语法糖,那么理应是等同于C++为我们包装了一个方便的用法来使用指针,为什么传指针的方式会导致在函数栈内创建一个副本,而传引用却不会呢?相信有人与我有同样的疑惑,下面让我解释一下两种方式的不同。
先说一下值传递,下面是一个对传入的x执行x++操作的函数,但当在main函数中调用该函数时,却并不会导致实参的值发生改变。因为值传递传入的为实参的副本,下面的例子中,main函数中的局部变量num作为实参传递给increment函数,而在调用函数时,increment栈桢中存入的其实为num的副本,也就是说虽然x的值与实参相同,但是地址确实发生了变化,我们修改的只是副本,也就是说原本地址上内存的内容并没有被修改,而在increment函数结束时,函数栈销毁,一切好像是“从未发生过”。因为x的修改并没有影响到实参。就好比“你找的是周树人,和我鲁迅有什么关系?”,哈哈,当然这可能不太一样,因为鲁迅和周树人本来就是一个人,他们在“内存上的地址”是一摸一样的,这句话更适合描述我们下面将会提到的另外一种参数传递方式,传引用。因为周树人是鲁迅的别名,这恰恰符合c++中引用的定义。
void increment(int x) {
x++; // 只修改了副本
cout << "Inside increment function: " << x << endl;
}
int main() {
int num = 5;
increment(num); // 传递 num 的副本
cout << "After increment function: " << num << endl; // num 未被改变
return 0;
}
引用,我们常常称之为“指针的语法糖”,即C++语言为我们提供的一种更加方便的使用指针的方式,引用在声明时,必须进行初始化,并且当你绑定为某个对象的时候,便不能再让引用指向其他的对象。下面是一个引用的应用实例,我们传引用的时候,传入了一个别名,就像鲁迅和周树人,是同一个人,不同的是一个是笔名,一个是真名。引用也是如此,因此下面的例子中,我们修改x的引用,自然会影响到原来的实参num。但这种行为为什么不会造成拷贝呢?这个问题我们留到下面讲完传指针再来解决。
void increment(int& x) {
x++; // 直接修改原始数据
cout << "Inside increment function: " << x << endl;
}
int main() {
int num = 5;
increment(num); // 传递 num 的引用
cout << "After increment function: " << num << endl; // num 被修改
return 0;
}
指针,在我认为,是编程语言最伟大的发明,很多问题解决不了的时候,不妨试试用指针。要说传指针,首先我们需要搞明白,什么是指针,指针其实也是内存上的一块空间,只是这块空间里存放的是另一个变量的地址。而常规变量的内存空间中存放的是该变量的值。指针的中存储的地址,其实就是“指针的值”,当然这么说不是很好,但通俗理解是这样的。理解了这一点,下面的问题就可以更好的理解了。
下面的例子中,我们传入了一个int型的指针x,也即,我们传入的实际上是实参“地址的副本”,因为我们提到过,指针的值,其实就是一个地址,一般是其他变量的地址。传值的时候,我们还记得传入的是x值的副本,这里其实同样也是,只不过这里面x的值是一个地址,而并非一个数值,当我们对一个地址进行操作时,也就是说我们真正的修改了对应内存地址的内容,这是真实发生了的,所以传指针的会影响到main函数中局部变量num的值。
void increment(int* x) {
(*x)++; // 通过指针修改原始数据
cout << "Inside increment function: " << *x << endl;
}
int main() {
int num = 5;
increment(&num); // 传递 num 的地址(指针)
cout << "After increment function: " << num << endl; // num 被修改
return 0;
}
说到这里应该很清晰了,我们传入的是“地址的副本”,也就是说我复制了两个一模一样的地址,虽然指针自身的地址不同,但是他们指向的内存空间是相同的。同时切记不要在函数中使用形参进行内存分配,比如在increment中,我们给他new一块内存出来,这是很危险的!!!因为我们说到过,传入的其实是地址的副本,这也代表着,如果我们给x分配一块内存,即改变了x指向的地址,并不是对x指向内存空间的值进行操作,而是修改了x的指向,也就是说我们并没有按照原来的地址寻址,找到相应的内存空间,然后进行操作,而是直接修改了内存的指向,这当然不会对我原来地址内存空间的内容造成影响,也就是说,原来的内存空间存储的内容没有变化,你只是给形参x分配了内存,如果此时你没有进行delete操作,那么恭喜你,打开了潘多拉的盒子,因为每当有人调用一次这个函数,就会发生一次内存泄漏。最终导致程序崩溃。如果你想要给传入的指针分配内存,下面是正确的做法,就是传入指针的指针。即传入的是指向了一个指针的指针,这段代码中,ptr自身的地址没有被改变,改变的是ptr指向的地址,这听起来很绕,但是如果你能明白其中的道理,我相信从此指针不会再困扰你。
void increment(int** x) {
*x = new int; //为ptr分配内存
}
int main() {
int num = 5;
int* ptr = # //指向num的指针
increment(&ptr); // 传递 num 的地址的指针
cout << "After increment function: " << num << endl; // num 被修改
return 0;
}
最后我们说一说传引用和传指针的区别,传值和传指针,其实都发生了拷贝的行为,在上面我们已经详细说过。而引用的行为由于是一种别名,即编译器再看到传入的为一个引用时,会直接到这段地址上对这个变量进行直接操作。并不进行复制,你可以理解为直接寻址的行为,并不复制地址,而是直接操作原来的变量。说到这里我想大家已经明白了三种参数传递的方式,并且对指针有了一定了解。
写在最后,如有不当之处,恳请指正!!!