首先先来个长篇幅度的前言,想看结果的同学请直接看分割线以下的内容。
通常情况下,编译器会自动为我们的类创建"拷贝构造函数"和"赋值运算符"。这里的拷贝构造函数和赋值运算符就是所谓的copying函数。
拷贝构造:
用一个已有的对象,构造和它同类型的副本对象——克隆。
形如
class X {
X(const X &that) {
...
}
};
的构造函数称为拷贝构造函数。如果一个类没有定义拷贝构造函数,系统会提供一个缺省拷贝构造函数。缺省拷贝构造函数对于基本类型的成员变量,按字节复制,对于class类型的成员变量,调用相应类型的拷贝构造函数。
在某些情况下,缺省拷贝构造函数只能实现浅拷贝,如果需要获得深拷贝的复制效果,就需要自己定义拷贝构造函数。
例如:
-
Integer::Integer(const Integer &that):m_data(new int(*that.m_data)){}
重载赋值运算符函数:
形如
class X {
X& operator=(const X &that) {
...
}
};
的构造函数称为重载赋值运算符函数。如果一个类没有定义拷贝赋值运算符函数,系统会提供一个缺省赋值运算符函数。缺省拷贝赋值运算符函数对于基本类型的成员变量,按字节复制,对于class类型的成员变量,调用相应类型的拷贝赋值运算符函数。
在某些情况下,缺省拷贝赋值运算符函数只能实现浅拷贝,如果需要获得深拷贝的复制效果,就需要自己定义拷贝赋值运算符函数。
例如:
-
Integer & Integer::operator=(const Integer &that)
-
{
-
// 防止自赋值
-
if ( this != &that) {
-
// 释放旧资源
-
delete m_data;
-
#if 0
-
// 分配新资源 并拷贝新数据
-
m_data = new int(*(that.m_data));
-
cout << "11111111111" << endl;
-
#else
-
// 或者分开两步:
-
// 分配新资源
-
m_data = new int;
-
// 拷贝新数据
-
*m_data = *(that.m_data);
-
cout << "222222222" << endl;
-
#endif
-
}
-
return *this;
-
}
关于拷贝构造函数的参数:
例如:
-
X(X that): m_data(new int(*that.m_data)){}
-
-
X x1(10);
-
x1.print();
-
X x2(x1);
-
x2.print();
编译器是不允许非引用的参数行为,因为函数如果以值的方式传参,会调用拷贝构造,然后为了让x1拷贝到x2,就要向拷贝构造函数传参,然后传参的过程中又要用拷贝构造的方式来构造that,就会又调用一次拷贝构造,就会陷入无限递归的情况……也就是说,本来这个函数就是在做拷贝构造,可是在把x1传给that的时候,因为是以值的方式传递的,在传递的过程当中,它又要调用that的拷贝构造函数,来把x1构造进来,又要调用拷贝构造,在这一次拷贝构造的时候,又要传个参数,这就没完没了了……所以说拷贝构造函数的参数只能是引用而不能是值。
这个其实很多人都理解,但是因为本人是一直做C的,所以我要说一下从C的角度怎么去理解这事。
===========================分割线===============================
首先因为C里面是没用引用的,所以程序文件后缀还得是CPP然后用g++去编译……
因为一直在想,普通的值传递,所谓的指针传递和引用传递,三种的区别。
关于这个问题思考的有2点:1.引用传递、指针传递从开销上讲都比普通的值传递要小很多。2.那么指针传递对比引用传递又有什么区别呢
上代码:
-
#include<iostream>
-
using namespace std;
-
-
//值传递
-
void value(int n)
-
{
-
cout<<"\n值传递--函数操作地址"<<&n<<endl; // 显示的是拷贝(形参)的地址而不是源地址
-
n++;
-
}
-
//引用传递
-
void reference(int & n)
-
{
-
cout<<"\n引用传递--函数操作地址"<<&n<<endl;
-
n++;
-
}
-
//指针传递
-
void point(int *n)
-
{
-
cout<<"\n指针传递--函数操作地址 "<<&n<<endl;
-
*n=*n+1;
-
}
-
-
int main(void)
-
{
-
int n=10;
-
cout<<"实参的地址"<<&n<<endl;
-
cout<<"Initial value: n="<<n<<endl;
-
value(n);
-
cout<<"after value() n="<<n<<endl;
-
reference(n);
-
cout<<"after reference() n="<<n<<endl;
-
point(&n);
-
cout<<"after point() n="<<n<<endl;
-
-
return 0;
-
}
然后在Ubuntu9.10下g++ (Ubuntu 4.4.1-4ubuntu8) 4.4.1下编译:
-
$ ./a.out
-
实参的地址0xbfd108ac
-
Initial value: n=10
-
-
值传递--函数操作地址0xbfd10890
-
after value() n=10
-
-
引用传递--函数操作地址0xbfd108ac
-
after reference() n=11
-
-
指针传递--函数操作地址 0xbfd10890
-
after point() n=12
其实不用做解释了,已经很清楚不过了,引用的形参地址值与实参一致。
指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。
而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
用稍微专业的话就是:
程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。
指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。
符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。
好了,话来回换着方的解释说了这么多,能懂的相信早就懂了而且还会觉得好多废话,不能懂的相信在下点功夫也能懂的,快夜里2点了,赶紧睡觉了~
阅读(815) | 评论(0) | 转发(0) |