0.概述
如果你能很清楚的理解下面这个程序,那么本篇文章就不用看了。班门弄斧而已。
void GetMemory(char* p){
p = new char[100];
}
void main(){
char *str = "1234";
GetMemory(str);
strcpy(str, "hi"); //出错! str = NULL!
printf("%s\n", str);
}
看不懂的话,可以好好学习一下。对于初学者来说,C/C++的参数传递的确让人感觉非常困惑。“传地址”,“传值”,“传引用”,“传指针”等等,功能又似乎很相似,实在难以区分。本文就C/C++参数传递问题做以总结。
一.错误认识:
很多人把C/C++中函数的调用方式分为两种:一种是传值的,一种传地址的。传值比较常见,形参变实参不变。 而传地址形参实参都变,而且有两种实现方式:指针和引用。除了大众意义上的指针和引用的区别(一个可变一个不可变)外,其他视为一样。
如果你也有这上面的错误认识,那你可要认真看下面的文章了。上面这段程序,主函数中调用GetMemory(str),只是把一个str指向的地址传递到子函数的参数p,即p指向str所指向的地址,你可以修改p指向值的内容,但你不可以让p指向别的地方。比如你让p指向了一个新的char数组。那么主函数中的实参str指向的那个地址的值是不会改变的。如图所示:
如果你写成以下这样就可以了:
void GetMemory(char* &p){
p = new char[100];
}
void main(){
char *str;
GetMemory(str);
strcpy(str, "hi"); //OK!
printf("%s\n", str);
}
因为p是主函数str的一个引用,可以说就是str,所以你改变p的指向,也就是改变了str的指向。
所以我之前最大的错误就是对调用方式头脑中的分类不正确。我所谓的指针传地址仍旧属于传值调用。
二:总结
1:类型
变量里面存取的东西称为值,我们可以存普通数值123,也可以123所在内存中的地址;可以存对象A,也可以存对象A在内存中的地址。所以值有两种:普通值和地址值。用于存取普通值的变量就是我们经常说的变量,用于存取地址值的变量就是指针变量(地址值变量)。
C语言:有普通值变量,也有指针变量。
C++语言:有普通值变量,指针变量,还有引用。所谓引用就是给一个变量起个别名。同理,可以给普通值变量起别名,也可以给指针变量起别名。
JAVA语言:只有基本类型可以显示的使用普通值变量,对象只能用引用(JAVA里面的引用是指针变量)来使用。
例如:
C++:Mytype A;定义了一个Mytype对象,A就是一个普通变量。
Java:Mytype A;定义了一个指向Mytype对象的引用(指针)。
注意JAVA和C++的不同。
(1): 传值调用:分为传值和传地址。注意这句话中第一个“值”有两种意思。
(2): 引用调用:同理,你引用的可以是值,可以是地址,可以使指针。
下面再检验一下是否过关吧!看下面代码:
void Change(char *p)
{
*p = 'b';
p = NULL;
}
main()
{
char a = 'a';
char* p = &a;
Change(p);
printf("%c\n", a); //值a改变!
}
三:代码
/*
* 对于所有的参数传递你都可以理解为把实参赋给形参。代码中将以此理念讲解。
* 首先C++的参数传递包括两类:传"值"和传"引用"。C语言只有传值,因为C语言没有引用这个东西。
* 值有两种:A.普通的值 B.地址
* 引用也有两种:A.引用一个普通值 B.引用一个指针
* 注意区分以下测试
*/
//测试1:传普通值
void fun1(int value){
value = 10;
}
//测试2:传地址值(俗称指针传递方式,指针不就是一个地址么,指针变量不就是一个可变地址么)
void fun2(int* value){
*value = 10;
}
//测试3:传地址值
void fun3(int* value){
value = new int(10);
}
//测试4: 传地址值
void fun4(int* value){
int a = 10;
value = &a;
}
//测试5: 传引用
void fun5(int &value){
value = 10;
}
//测试6: 传引用
void fun6(int* &value){
value = new int(10);
}
int main(){
//测试1调用
int v1 = 2;
fun1(v1); //相当于int value(形参:基本类型) = v(实参:基本类型);换句话说把数字2交给函数fun的形参,让fun处理一些工作。当然不会造成value的改变了。
cout << v1 << endl;
//测试2调用
int v2 = 2;
fun2(&v2); //相当于int* value(形参:指针类型) = &v2(实参:一个地址);
cout << v2 << endl; //实参把他的地址交给形参value后,此时value为实参v2的地址。那么对*value(value指向的内容)的修改,当然会导致实参的改变。
//测试3调用
int v3 = 2;
fun3(&v3); //相当于int* value(形参:指针类型) = &v3(实参:一个地址)
cout << v3 << endl; //此时value为实参v3的地址。可是函数fun3又让value指向了一块新开辟的地方(value = new int(10)); 那么对这块新开辟地方的修改 //当然不会影响到实参了。
//测试4调用
int v4 = 2;
fun4(&v4); //同测试3
cout << v4 << endl;
//测试5调用
int v5 = 2;
fun5(v5); //相当于int &value(形参:引用类型) = v5(实参:基本类型); 一个引用一旦被赋值就完全代表了他引用的变量。
cout << v5 << endl; //value此时就成了v5的另一个名字。对value的任何修改都会引起value的改变。
//测试6调用
int a = 2;
int *v6 = &a;
fun6(v6); //这个有难度啊。是引用调用,但是是一个指针类型的引用。上面是基本类型的引用。理解理解。
cout << *v6 << endl;
return 0;
}
四. 疑惑
指针的用法非常灵活,引用其实就是一个常量指针,如int &p其实就是int* const p,一旦确定指向就不能再指向别的了。那么完全可以不需要引用这种方式吗?这就要从历史的角度理解了。指针是C语言的一个十分重要的概念,而C++语言是C语言的的超集,当然可以使用指针了。而C++语言虽然可以使用指针,但是它并没有像C语言中那样重要,因为在C++语言中又引进了引用的概念,引用占据了指针的一些作用,而使指针的功能相对地减弱了。
引用这个概念看起来更加简单,使用起来不像指针那么难,容易出错。因此这个概念变得重要且被广泛使用。到Java语言的时,引用被使用进来,可是Java中的引用变得更加灵活了,灵活的又像当年的指针一样,可以指来指去。而且作为Java语言传参的主体。如果希望只指向一次,那就加上const就行了。
这应该就是参数传递的发展渊源,似乎又绕回去了,不过使用起来更加简化了。