c++关于值传递,引用传递和指针传递以及深拷贝和浅拷贝的理解
1. 背景
由于之前一直使用c#进行软件开发,所以习惯于c#语言的特性。现在公司技术栈调整,转成c++开发,在开发的工程中经常会串到c#语言特性中。特别是形参遇到对象,数组等大量数据,总是疑神疑鬼,考虑用引用传递还是值传递。因为c#将内置的数据类型和对象分为值类型和引用类型。一般对象,数值等都会自动使用引用类型传递,完全不用担心数据量比较大,实参到形参复制浪费性能的问题,简直是yyds。但是现在使用了c++,于是把c++里面的传参撸一撸。
2. c++三大传参方式
- 值传递:
实参向函数形参所属的栈空间拷贝数据的过程。如果传递的值数据量比较大,将消耗一定的时间和空间。 - 指针传递:
同值传递,只不过拷贝的值是4字节的地址。 - 引用传递:
同值传递,只不过拷贝的值是数据所在地址的一个别名。
注意:一般推荐使用指针或者引用传递,效率高。
3. c++是否会自动根据数据类型选用不同的传参方式。
c++是否有类似c#的机制,参数分为值类型和引用类型。并根据不同的类型参数,自动选用值传参还是引用传参。说实话这个问题我也不清楚,但是没关系,可以写个代码测试。看看实参和形参地址是否相同,便可以知晓答案。
class school {
public:
school() {}
private :
int id;
char name[32];
};
typedef struct {
int id;
char name[32];
}student;
void func_string(string str) {
cout << "func string addr = " << &str << endl;
}
void func_char(char ch) {
printf("func char addr = %d\n", &ch);
//cout << "func char addr = " << &ch << endl;
}
void func_array(int array[]) {
cout << "func array addr = " << array << endl;
}
void func_struct(student stu) {
cout << "func struct addr = " << &stu << endl;
}
void func_class(school sch) {
cout << "func class addr = " << &sch << endl;
}
void main(){
string str = "hello world";
cout << "test string addr = " << &str << endl;
func_string(str);
cout << "--------------------------------------------" << endl;
char ch = 'A';
printf("test char addr = %d\n", &ch);
//cout << "test char addr = " << &ch << endl;
func_char(ch);
cout << "--------------------------------------------" << endl;
int array[] = { 1,2,3,4,5 };
func_array(array);
cout << "test array addr = " << array << endl;
cout << "--------------------------------------------" << endl;
student stu = { 1,"ccc" };
func_struct(stu);
cout << "test struct addr = " << &stu << endl;
cout << "--------------------------------------------" << endl;
school sch;
func_class(sch);
cout << "test class addr = " << &sch << endl;
}
上面测试实例中,分别设置了string,char ,array,struct, class等变量进行测试。得出如下所示的结果。
test string addr = 003BF8D0
func string addr = 003BF820
--------------------------------------------
test char addr = 3930311
func char addr = 3930168
--------------------------------------------
func array addr = 003BF8A8
test array addr = 003BF8A8
--------------------------------------------
func struct addr = 003BF818
test struct addr = 003BF87C
--------------------------------------------
func class addr = 003BF818
test class addr = 003BF850
从结果中看,除了array类型的数据实参和形参一致,其他的都不同。由于数组传参本身就是传递数据的地址,所以理论上数组传参也是值传递,只是恰好这个值是地址,导致数组传参的时候实参和形参地址一致。
综上所述,c++中所有类型的值传参,都直接采用值传递参数,并没有自动根据数据类型转换成引用传参。所以遇到数据量比较大的时候还是需要开发人员手动设置参数为引用类型。虽然c++没有这么智能,有点小小的失望,但是这也正符合c++的特性,一切依靠开发者自己维护。
4. 值传递引发的对象拷贝问题。
前面知道,指针传参和引用传参,分别是传递实参的地址和实参的别名,实际上对形参的操作就是在操作实参。这不存在问题。值传递的时候,如果只是单纯的值类型数据,形参拷贝实参数据也不存在任何问题。但是当实参是对象,且对象中存在指针的数据。形参拷贝实参的时候,复制实参指针,但是二者指针所指向的空间是共同的。这导致形参和实参相互影响,这违背了值传递的目的(实参形参相互独立,不影响)。这个问题就是对象拷贝的问题。
- 拷贝构造函数:用一个对象去构造另一个相同类的对象时候,会调用拷贝构造函数
- 如果类的开发者没有写拷贝构造函数,编译器会自动生成拷贝构造函数,实现从源对象到目标对象逐个字节的复制。使得二者一致。
- 拷贝构造函数只拷贝数据成员,不拷贝函数成员,所有对象中函数成员只有一份。
- 拷贝构造函数必须是引用类型,否者会引起无穷递归。
- 拷贝构造函数被调用的三种情况
- 用一个对象初始化另外一个对象
A a(b);
A a = b;
a = b;//该情况不调用拷贝构造函数,属于赋值。 - 作为形参的对象,在函数被调用时,使用拷贝构造函数初始化。
- 作为函数返回值的对象用拷贝构造函数初始化。
- 用一个对象初始化另外一个对象
- 浅拷贝和深拷贝:
- 浅拷贝:默认的拷贝构造函数是按位拷贝,实现复制成员。若成员中没有指针,该方式能实现对象数据的拷贝。
- 深拷贝:当拷贝对象中有对其他资源(如堆、文件、系统等)的引用时(引用可以是指针或引用)时,对象重新开辟一份空间,将引用的内容拷贝到新的空间,而不是单纯复制其他支援引用的地址。
- 注意:浅拷贝存在引用类型成员的对象时候,会存在多次释放引用问题。虽然拷贝构造函数只是对对象而言,但是同样的结果,在拷贝结构体,数组等存在引用成员的时候也要注意。