Chinaunix首页 | 论坛 | 博客
  • 博客访问: 7832
  • 博文数量: 15
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 95
  • 用 户 组: 普通用户
  • 注册时间: 2015-07-15 14:37
文章分类

全部博文(15)

文章存档

2016年(2)

2015年(13)

我的朋友

分类: C/C++

2016-08-30 01:41:41

首先先来个长篇幅度的前言,想看结果的同学请直接看分割线以下的内容。

通常情况下,编译器会自动为我们的类创建"拷贝构造函数"和"赋值运算符"。这里的拷贝构造函数和赋值运算符就是所谓的copying函数。
拷贝构造:
    用一个已有的对象,构造和它同类型的副本对象——克隆。
    形如
    class X {
        X(const X &that) {
            ...
        }
    };
    的构造函数称为拷贝构造函数。如果一个类没有定义拷贝构造函数,系统会提供一个缺省拷贝构造函数。缺省拷贝构造函数对于基本类型的成员变量,按字节复制,对于class类型的成员变量,调用相应类型的拷贝构造函数。
    在某些情况下,缺省拷贝构造函数只能实现浅拷贝,如果需要获得深拷贝的复制效果,就需要自己定义拷贝构造函数。
    例如:

点击(此处)折叠或打开

  1. Integer::Integer(const Integer &that):m_data(new int(*that.m_data)){}

重载赋值运算符函数:
    形如
    class X {
        X& operator=(const X &that) {
            ...
        }
    };
    的构造函数称为重载赋值运算符函数。如果一个类没有定义拷贝赋值运算符函数,系统会提供一个缺省赋值运算符函数。缺省拷贝赋值运算符函数对于基本类型的成员变量,按字节复制,对于class类型的成员变量,调用相应类型的拷贝赋值运算符函数。
    在某些情况下,缺省拷贝赋值运算符函数只能实现浅拷贝,如果需要获得深拷贝的复制效果,就需要自己定义拷贝赋值运算符函数。
    例如:

点击(此处)折叠或打开

  1. Integer & Integer::operator=(const Integer &that)
  2. {
  3.     // 防止自赋值
  4.     if ( this != &that) {
  5.         // 释放旧资源
  6.         delete m_data;
  7. #if 0
  8.         // 分配新资源 并拷贝新数据
  9.         m_data = new int(*(that.m_data));
  10.         cout << "11111111111" << endl;
  11. #else
  12.         // 或者分开两步:
  13.         // 分配新资源
  14.         m_data = new int;
  15.         // 拷贝新数据
  16.         *m_data = *(that.m_data);
  17.         cout << "222222222" << endl;
  18. #endif
  19.     }
  20.     return *this;
  21. }

关于拷贝构造函数的参数:
例如:

点击(此处)折叠或打开

  1. X(X that): m_data(new int(*that.m_data)){}

  2. X x1(10);
  3. x1.print();
  4. X x2(x1);
  5. x2.print();
    编译器是不允许非引用的参数行为,因为函数如果以值的方式传参,会调用拷贝构造,然后为了让x1拷贝到x2,就要向拷贝构造函数传参,然后传参的过程中又要用拷贝构造的方式来构造that,就会又调用一次拷贝构造,就会陷入无限递归的情况……也就是说,本来这个函数就是在做拷贝构造,可是在把x1传给that的时候,因为是以值的方式传递的,在传递的过程当中,它又要调用that的拷贝构造函数,来把x1构造进来,又要调用拷贝构造,在这一次拷贝构造的时候,又要传个参数,这就没完没了了……所以说拷贝构造函数的参数只能是引用而不能是值。
    这个其实很多人都理解,但是因为本人是一直做C的,所以我要说一下从C的角度怎么去理解这事。
===========================分割线===============================
首先因为C里面是没用引用的,所以程序文件后缀还得是CPP然后用g++去编译……
因为一直在想,普通的值传递,所谓的指针传递和引用传递,三种的区别。
关于这个问题思考的有2点:1.引用传递、指针传递从开销上讲都比普通的值传递要小很多。2.那么指针传递对比引用传递又有什么区别呢
上代码:

点击(此处)折叠或打开

  1. #include<iostream>
  2. using namespace std;

  3. //值传递
  4.  void value(int n)
  5. {
  6.     cout<<"\n值传递--函数操作地址"<<&n<<endl; // 显示的是拷贝(形参)的地址而不是源地址
  7.     n++;
  8. }
  9. //引用传递
  10. void reference(int & n)
  11. {
  12.     cout<<"\n引用传递--函数操作地址"<<&n<<endl;
  13.     n++;
  14. }
  15.  //指针传递
  16. void point(int *n)
  17. {
  18.      cout<<"\n指针传递--函数操作地址 "<<&n<<endl;
  19.     *n=*n+1;
  20. }

  21. int main(void)
  22. {
  23.     int n=10;
  24.     cout<<"实参的地址"<<&n<<endl;
  25.     cout<<"Initial value: n="<<n<<endl;
  26.     value(n);
  27.     cout<<"after value() n="<<n<<endl;
  28.     reference(n);
  29.     cout<<"after reference() n="<<n<<endl;
  30.     point(&n);
  31.     cout<<"after point() n="<<n<<endl;

  32.     return 0;
  33. }
然后在Ubuntu9.10下g++ (Ubuntu 4.4.1-4ubuntu8) 4.4.1下编译:

点击(此处)折叠或打开

  1. $ ./a.out
  2. 实参的地址0xbfd108ac
  3. Initial value: n=10

  4. 值传递--函数操作地址0xbfd10890
  5. after value() n=10

  6. 引用传递--函数操作地址0xbfd108ac
  7. after reference() n=11

  8. 指针传递--函数操作地址 0xbfd10890
  9. after point() n=12
其实不用做解释了,已经很清楚不过了,引用的形参地址值与实参一致。
    指针传递参数本质上是值传递的方式,它所传递的是一个地址值。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,即在栈中开辟了内存空间以存放由主调函数放进来的实参的值,从而成为了实参的一个副本。
    而在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。
用稍微专业的话就是:
    程序在编译时分别将指针和引用添加到符号表上,符号表上记录的是变量名及变量所对应地址。
    指针变量在符号表上对应的地址值为指针变量的地址值,而引用在符号表上对应的地址值为引用对象的地址值。
    符号表生成后就不会再改,因此指针可以改变其指向的对象(指针变量中的值可以改),而引用对象则不能修改。

好了,话来回换着方的解释说了这么多,能懂的相信早就懂了而且还会觉得好多废话,不能懂的相信在下点功夫也能懂的,快夜里2点了,赶紧睡觉了~
阅读(815) | 评论(0) | 转发(0) |
0

上一篇:APUE——fork与I/O函数之间的交互关系

下一篇:没有了

给主人留下些什么吧!~~