C++的参数传递
通过上篇C语言中的参数传递的讨论,我们知道,C语言中只有值传递。C++作为C的超集,出现了引用(reference),因而在参数传递中除了像C那样的值传递,还有引用传递的方式。
值传递
对于基本数据类型,C和C++是一样的。而C++是面向对象的,有了类(class),不仅如此,C++中的结构体(struct)、共同体(union)以及枚举(enum)相比C中的也有了很大不同。但是,在C++中,这些数据类型作为参数传递时都是也都是值传递的。下面是C++中的对象按值传递的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
#include <iostream> using namespace std; class Person{ public: string name; int age; }; void change(Person p){ cout<<"name:"<<p.name<<", age:"<<p.age<<", &p:"<<&p<<endl; p.age++; p.name = "another " + p.name; cout<<"name:"<<p.name<<", age:"<<p.age<<", &p:"<<&p<<endl; } int main(){ Person person; person.age=20; person.name="Bob"; cout<<"name:"<<person.name<<", age:"<<person.age<<", &p:"<<&person<<endl; change(person); cout<<"name:"<<person.name<<", age:"<<person.age<<", &p:"<<&person<<endl; return 0; } |
输出:
1 2 3 4 5 |
name:Bob, age:20, &p:0x5fcae0 name:Bob, age:20, &p:0x5fcaf0 name:another Bob, age:21, &p:0x5fcaf0 name:Bob, age:20, &p:0x5fcae0 |
引用
C语言中并没有引用,下面这段代码使用gcc是无法编译通过的,而使用g++却可以,也就是对 .c 文件而言,下列代码存在语法错误,而在 .cpp 文件中却是正确的写法。
1 2 3 4 5 6 |
int main(){ int a = 1; int & b = a;// 引用在C中不被支持 return 0; } |
引用是C++中的语言特性,并不是一种数据类型。C++中的引用就是某一变量的一个别名,对引用的操作与对变量直接操作完全一样。
声明一个引用,并不意味着新定义了一个变量,而仅仅表示为某个已声明的变量定义了一个别名。因此,引用不会被分配内存空间。对引用求地址,就是对目标变量求地址:
1 2 3 4 5 6 7 8 9 10 11 12 |
#include <iostream> using namespace std; int main(){ int a=0; int & b = a; cout<<"a:"<<a<<", &a:"<<&a<<", sizeof(a):"<< sizeof(a)<<endl; cout<<"b:"<<b<<", &b:"<<&b<<", sizeof(b):"<< sizeof(b)<<endl; return 0; } |
编译后运行,输出:
1 2 3 |
a:0, &a:0x5fcaf4, sizeof(a):4 b:0, &b:0x5fcaf4, sizeof(b):4 |
因为并不是一种数据类型,引用本身是不占存储单元的,上例中的sizeof得到的是引用所指代的变量的空间大小。
引用的关键点在于“别名”,我们可以把它和被引变量看成是同一的。
引用传递
引用不是类型,而是别名,因此当函数的形参是一个引用时,意味着形参是实参的一个别名。在函数体内对形参的任何操作都是在直接操作实参变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
#include <iostream> using namespace std; void change(int & i){ cout<<"i:"<<i<<", &i:"<<&i<<endl; i++; cout<<"自增操作后"<<endl; cout<<"i:"<<i<<", &i:"<<&i<<endl; } int main(){ int a=0; cout<<"a:"<<a<<", &a:"<<&a<<endl; change(a); cout<<"函数调用后"<<endl; cout<<"a:"<<a<<", &a:"<<&a<<endl; return 0; } |
上面代码编译后运行,输出:
1 2 3 4 5 6 7 |
a:0, &a:0x5fcafc i:0, &i:0x5fcafc 自增操作后 i:1, &i:0x5fcafc 函数调用后 a:1, &a:0x5fcafc |
因为形参i是一个引用,在函数调用时,它并没有分配到内存空间,而是作为实参a的一个别名,与a有相同的值和地址,对i的操作就是对a的操作。
这种形参作为实参的引用的参数传递方式,就是引用传递。与值传递最根本的区别在于,引用传递不会在内存中产生实参的副本,对形参的操作就是直接对实参操作。
而值传递方式中,当发生函数调用时,需要在内存中给形参分配空间,并以实参变量的值来初始化形参,形参变量是实参变量的副本。
因为引用传递并不会为形参分配空间并初始化,所以使用引用传递会有比值传递更高的效率。
常引用
使用 const 关键字修饰的引用是常引用,常引用虽然仍是一个别名,但无法通过这种引用来修改被引变量的值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
#include <iostream> using namespace std; int main(){ int a=0; const int & b = a; cout<<"a:"<<a<<", &a:"<<&a<<endl; cout<<"b:"<<b<<", &b:"<<&b<<endl; a++; //b++; //错误!b是const的,不允许修改 cout<<"a:"<<a<<", &a:"<<&a<<endl; cout<<"b:"<<b<<", &b:"<<&b<<endl; return 0; } |
使用常引用可以阻止通过引用修改被引变量而不影响被引变量的访问性。如果需要保护传递给函数的数据不在函数中被改变,即需要形参是只读的,就应使用常引用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#include <iostream> using namespace std; void change(const int & i){ i++; //错误!read-only reference } int main(){ int a=0; const int b =0; change(a); change(b); // b是const的,所以只能传递给常引用 return 0; } |
需要注意的是,如果想要以引用传递方式将一个常量传递给函数,函数的形参必须是常引用。
小结
C++中,参数传递方式分为值传递和引用传递,其中值传递中的通过指针传递因为实际传递的是地址,属于一种较为特殊的值传递。
C++中,函数形参被声明为引用的,属于引用传递,除此之外都是值传递。
值传递和引用传递的区别是:
- 形参是否有单独的内存空间,是实参的副本还是别名
- 对形参的修改是否会使得实参发生变化(注意:修改形参指针本身是并不会让实参发生变化的,修改形参指针的所指内容才会)
- 实参是否向形参传递了具体的值(指针的值既是地址)