C++中的构造函数/拷贝构造函数

本文详细介绍了C++构造函数的两阶段执行过程,包括成员初始化和普通计算阶段,并强调了在初始化列表中初始化成员的重要性。此外,讨论了拷贝构造函数的功能、调用场景以及系统默认的浅拷贝问题,强调了深拷贝的必要性。同时,通过示例代码展示了如何实现深拷贝,以避免共享内存导致的问题。最后,提到了禁止拷贝的实现方式。
摘要由CSDN通过智能技术生成

构造函数

构造函数的两阶段执行

1、初始化阶段;
所有类型的成员都会在初始化阶段被初始化,即使该成员没有出现在构造函数的初始化列表中。

(1)、初始化列表中显示初始化的成员,按照列表中圆括号内的值初始化。

(2)、初始化列表中没有显式列出的成员,若是类成员,则调用该类型的默认构造函数初始化,若是内置类型或者复合类型,则按照变量初始化的原则,在局部作用域中的不做初始化,全局作用于中的初始化为0。

注意:成员变量的初始化顺序,成员是按他们在类中出现的顺序初始化的.而不是按初始化列表的顺序.

2、普通的计算阶段。
一般是指在构造函数的函数体内对数据成员做赋值工作,在函数体内进行赋值操作之前,数据成员的初始化已经完成。

推荐在构造函数初始化列表中进行初始化

基于一个性能的问题,对于内置类型(int,float,double…),使用初始化列表和在构造函数体内初始化,差别不大。但是存在以下情况,最好使用初始化列表:
1、类存在继承关系
类中的数据成员含有一下内容:
2、没有默认构造函数的类类型成员,因为使用初始化列表可以不必调用默认的构造函数来初始化,而是直接调用拷贝构造函数来初始化。
3、需要初始化的数据成员是对象
4、const数据成员,即常量成员,因为常量只能初始化而不能赋值,所以必须使用初始化列表
5、引用成员,必须在定义的时候初始化,并且不能重新赋值,所以要写在初始化列表里面.

以下代码示例展示为什么推荐在构造函数初始化列表中进行初始化:

#include<iostream>
using namespace std;

class Test1{
public:
    Test1() {
        cout << "Construct Test1" << endl;
    }

    Test1(const Test1& t1) {
        cout << "Copy Construct for Test1" << endl;
        this->a = t1.a;
    }

    Test1& operator = (const Test1& t1) {
        cout << "assignment for Test1" << endl;
	    this->a = t1.a;
	    return *this;
    }

private:
    int a;
};

class Test2 {
public:
    Test2(Test1& t1) : test1(t1) {
    }
private:
    Test1 test1;
};

class Test3 {
public:
    Test3(Test1& t1) {
        test1 = t1;
    }
private:
    Test1 test1;
};

int main() {
    Test1 t1;      // 调用Test1的默认构造函数
    cout << "000000" << endl;
    Test2 t2(t1);  // 根据Test2构造函数的初始化列表,调用Test1的拷贝构造函数
    cout << "000000" << endl;
    Test2 t22 = t1;  // 等价于Test2 t2(t1);
    cout << "000000" << endl;
    Test3 t3(t1); 
    // Test3的构造函数没有初始化列表,用默认的构造函数初始化对象test1,这就是所谓的初始化阶段。然后根据Test1的赋值运算符中代码,执行赋值操作,这就是所谓的计算阶段。
    cout << "000000" << endl;
    Test3 t33 = t1;   // 等价于Test3 t3(t1);
    return 0;
}

程序输出如下

Construct Test1
000000
Copy Construct for Test1
000000
Copy Construct for Test1
000000
Construct Test1
assignment for Test1
000000
Construct Test1
assignment for Test1

拷贝构造函数

功能:使用一个已经存在的对象来初始化一个新的同一类型的对象

声明:只有一个参数并且参数为该类对象的引用

如果类中没有说明拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员。

拷贝构造函数调用的几种情况

1、当函数的形参是类的对象,调用函数时,进行形参与实参结合时使用。
这时要在内存新建立一个局部对象,并把实参拷贝到新的对象中。理所当然也调用拷贝构造函数。

2、当函数的返回值是类对象,函数执行完成返回调用者时使用。
理由也是要建立一个临时对象中,再返回调用者。为什么不直接用要返回的局部对象呢?因为局部对象在离开建立它的函数时就消亡了,不可能在返回调用函数后继续生存,所以在处理这种情况时,编译系统会在调用函数的表达式中创建一个无名临时对象,该临时对象的生存周期只在函数调用处的表达式中。所谓return 对象,实际上是调用拷贝构造函数把该对象的值拷入临时对象。如果返回的是变量,处理过程类似,只是不调用构造函数。

深拷贝与浅拷贝

在没有提供拷贝构造函数时,系统会提供默认的拷贝函数,系统提供的默认拷贝构造函数是浅拷贝,各个成员进行赋值,但是当成员含义指针时涉及到动态内存的分配,如果涉及浅拷贝就会共享同一块内存,但是当一个对象释放时,另一个对象也会出现无效的状态,有可能出现同一块内存释放两次的情况。因此就要实施深拷贝,将指针指向的对象的值进行拷贝。

赋值操作
系统默认提供的赋值操作也是浅拷贝,各个成员进行赋值。因此也要实施深拷贝

禁止拷贝
将赋值与拷贝构造函数声明为私有,并不实现。

空类默认产生的成员:

class Empty {};
Empty(); 			// 默认构造函数
Empty( const Empty& );	// 默认拷贝构造函数
~Empty(); 			// 默认析构函数
Empty& operator=( const Empty& );  // 默认赋值运算符
Empty* operator&();		               // 取址运算符
const Empty* operator&() const;	    // 取址运算符 const

字符串深拷贝实现代码:

#include<iostream>
using namespace std;

class String {
private:
    char * _str;
public:
    String(char * str) {
        _str = AllocCopy(str);
    }

    ~String() {
        delete[] _str;
    }

    String(const String & other) {
       _str = AllocCopy(other._str);
    }

    String & operator = (const String & other) {
        if (this == &other) {
	        return *this;
	    }
	    delete[] _str;
	    _str = AllocCopy(other._str);
	    return *this;
    }

    char * AllocCopy(char * str) {
        int len = strlen(str) + 1;
	    char * tmp = new char[len];
	    memset(tmp, 0, len);
	    strcpy(tmp, str);
	    return tmp;
    }
    void Display() {
        cout << _str << endl;
	    cout << this << endl;
    }
};

int main() {
   String a("aaaaaaa");
   a.Display();
   String b(a);
   b.Display();
}

代码输出结果如下:

aaaaaaa
0x7ffee3a39ad8
aaaaaaa
0x7ffee3a39ac0

参考:
https://blog.csdn.net/hailong0715/article/details/54018002
https://blog.csdn.net/Alatebloomer/article/details/81299331

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值