总结6:构造/析构 函数


本文PDF下载站点: https://github.com/MrWang522/Private-Document.git


1. 概念

1.1 构造函数
  • 定义:与类名相同的特殊成员函数

  • 语法: ClassName();

  • 作用:完成对属性的初始化

  • 特点: ①. 在定义时可以有参数,也可没有参数         ②. 没有任何返回类型的声明

  • 调用方式: 一般情况下C++编译器会自动调用构造函数, 在一些情况下则需要手工调用构造函数

class MyTest{
public:
	MyTest() {}   // 构造函数: 定义对象时,自动调用该函数,完成对属性的初始化
	~MyTest() {}  // 析构函数:对象声明周期结束时,自动调用该函数,释放对象占用的空间
};

1.2 析构函数
  • 定义:在构造函数名前加 ~ 的特殊成员函数

  • 语法: ~ClassName();

  • 作用:对象销毁时,自动被调用,用来释放对象占用的空间

  • 特点: ①. 声明的析构函数 没有参数 没有任何返回类型         ②. 在对象销毁时自动被调用

  • 调用方式: 被C++编译器自动调用

  • 注意(重点!!! ):先定义的对象 后析构

2. 构造函数分类

1. 默认构造函数
2. 无参数构造函数
3. 带参数构造函数
4. 拷贝构造函数

class Test{
public:
	Test(){ 		// 1. 无参数构造函数
		m_a = 0;	m_b = 0;	m_c = 0;
	}
	Test(int a){ 	// 2. 有参数构造函数 1个参数
		m_a = a;
	}
	Test(int a, int b, int c){ 	// 2. 有参数构造函数  3个参数
		m_a = a;	m_b = b;	m_c = c
	}

	Test(const Test& obj ){ 	// 3 . 拷贝构造函数
		/* ... ... */
	}
private:
	int m_a, m_b, m_c;
};
2.1 默认构造函数
  • 默认 无参 构造函数: 当类中没有定义构造函数时,编译器默认提供一个无参构造函数,并且其函数体为空
  • 默认 拷贝 构造函数: 当类中没有定义拷贝构造函数时,编译器默认提供一个默认拷贝构造函数,简单的进行成员变量的值复制(浅拷贝)

2.2 无参构造函数
Test t1;   // 直接调用 无参数构造函数  --> 属性初始化值为: m_a = 0;  m_b = 0; m_c = 0;

2.3 有参构造函数

注意:对象初始化 和 对象赋值是两个不同的概念 !!!

/* 1. 括号法 自动调用 */
Test t2(1, 2, 3);    // 直接调用三个参数的构造函数: --> 属性初始化值为: m_a = 1;  m_b = 2; m_c = 3;

/* 2. 等号法 自动调用 */
Test t3 = (1, 2);    // 直接调用一个参数的构造函数: --> 属性初始化值为: m_a = 2;  注意:C++等号符功能加强!!!直接取最后一个参数
Test t4 = (1, 2, 3); // 直接调用三个参数的构造函数: --> 属性初始化值为: m_a = 1;  m_b = 2; m_c = 3;

/* 3. 手动调用 初始化操作 */
Test t5 = Test(1, 2, 3);  // 产生匿名对象, 赋给t5, 但只调用一次构造函数, 原因是把匿名对象那块地址 直接命名为t5 !!!

2.4 拷贝构造函数

作用:用一个对象 去 初始化另外一个对象

class Test{
public:
	Test(int a, int b){     // 1. 有参数构造函数  2个参数
		m_a = a;	m_b = b;
	}

	Test(const Test& obj ){ // 2 . 拷贝构造函数
    	m_a = obj.m_a;	m_b = obj.m_b;    
	}
private:
	int m_a, m_b;
};

void MyTest_03(Test mp){ 	// 第三种调用测试
    /* ... ... */
}

Test MyTest_04(){ 			// 第四种调用测试
    Test tmp(1, 2);
    return tmp;    // *******重点!!! 返回一个匿名对象 返回时调用 一次拷贝构造函数(构造的是匿名对象) 在进行一次析构函数(析构的是tmp)
}

void main(){
    Test a(1, 2);
    
    /* 第一种调用:直接调用 */
    Test b = a;   	// 1. 不会调用普通构造含糊 直接调用拷贝构造函数!!!
    // t1 = t2; --> 不会调用拷贝构造函数 这种写法是操作符重载
    
    /* 第二种调用:直接调用 */
    Test c(a); 		// 2. 与上边一样 只是写法不同 
    
    /* 第三种调用:类的形参初始化 */ 
	MyTest_03(a);	// 3. 此时会调用拷贝构造函数 该函数执行完毕后 调用形参参数的析构函数
    
    /* 第四种调用:函数返回类的匿名对象 */
    Test d = MyTest_04(); 	//  4. 该函数返回了一个匿名对象 使用了匿名对象时 直接把d 的名字给匿名对象(地址不会改变)
    a = MyTest_04();        //     因为 a 已经被定义,此时匿名对象未使用,则直接调用匿名对象的析构函数
}

3. 构造函数的规则

  • 当类中定义了 有参/无参构造 函数时, C++编译器 不会提供 默认的构造函数
  • 当类中定义了 拷贝构造 函数时,C++编译器 不会提供 默认的构造函数
  • 默认拷贝构造函数成员变量简单赋值(浅拷贝)

4. 浅/深 拷贝

  • 浅拷贝: 默认拷贝构造函数 可以完成 对象的数据成员值 简单地复制
  • 深拷贝: 对象的属性 有指针 指示的堆时,需要 显式定义 拷贝构造函数(自定义)
4.1 浅拷贝
class MyClass{
public:    
    MyClass(const char *sp){
        m_cp = (char *)malloc(strlen(sp) + 1);  // 需要末尾添加 \n ,所以此处 +1
        strcpy(m_cp, sp);
    }
    
    /* 编译器 自动提供一个默认的拷贝构造函数 */
    
 	~MyClass(){ 
        if(m_cp != NULL){
            free(m_cp);
            m_cp = NULL;
        }
    }  
private:
	char *m_cp;   
}

void Test(){
    MyClass p1("home"), p3;
	MyClass p2 = p1;    // 1. 此处进行默认的拷贝 但是 运行时候会造成 断错误, 原因如下图
    p3 = p1;            // 2. 操作运算符重载 使用默认的运算符重载 也会 存在这样的问题 造成段错误
}

 浅拷贝

注意(重点!!! ):操作运算符重载 使用 默认的运算符重载 (浅拷贝) 也会段错误


4.2 深拷贝
class MyClass{
public:    
    MyClass(const char *sp){
        m_cp = (char *)malloc(strlen(sp) + 1);  // 需要末尾添加 \n ,所以此处 +1
        strcpy(m_cp, sp);
    }
    
 	MyClass(const Name& obj1){ 					// 自定义拷贝构造函数完成深拷贝
		m_cp = (char *)malloc(strlen(bj1.m_cp) + 1); 
		strcpy(m_cp, obj1.m_cp);
	}   
    
 	~MyClass(){ 
        if(m_cp != NULL){
            free(m_cp);
            m_cp = NULL;
        }
    }  
private:
	char *m_cp;   
}

void Test(){
    MyClass p1("home"), p3;
	MyClass p2 = p1;    	// 这样就解决了这个问题
}


5. 构造 初始化列表

5.1 语法规则

构造函数初始化列表以一个冒号开始, 以逗号分隔的数据成员列表, 每个数据成员后面跟一个放在括号中的初始化式(所赋的参数)

class CExample {
public:
    CExample(): a(0),b(8.8) {} /* 构造函数初始化列表 */
    CExample(){ a=0; b=8.8; }  /* 构造函数内部赋值 */
private:
    int a; float b;
};

5.2 必须 使用初始化列表 情景
  • 情景1: 成员类型是 没有默认构造函数的类,若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败。
class A {
public:
    A(int a) {m_a = a}
private:
    int m_a; 
};

class B {
public:     /* 编译器提供默认构造函数 */
private:
    A m_b; 
};

void Test(){
    B obj;      // 编译出错!!! 原因:没有机会初始化 B 类 
}
  • 情景2: const 成员 引用类型 的成员。因为 const 对象或引用类型只能初始化,不能对他们赋值。
class CExample {
public:
    CExample(): a(0),b(8.8) {} 
private:
    int m_a; const float m_fb;
};

5.3 成员变量的初始化顺序
  • 成员变量的初始化顺序 与 声明的顺序 相关,与在 初始化列表中的顺序 无关(重点!!!)
class CExample {
public:
    CExample(): a(0),b(8.8) {} /* 构造函数初始化列表 与初始化列表顺序无关 */
private:
    float b; int a;    /* 先初始化b 再初始化a 与声明顺序有关 */
};

6. 构造函数中调用构造函数

说明: 构造函数 中 调用构造函数 是 不可取 ( 危险 ) 的!!!

class Test{
public:
	Test(int a, int b, int c){
		this->a = a;	this->b = b;	this->c = c;
	}
	Test(int a, int b){
		this->a = a;	this->b = b;
		Test(a, b, 100);  // 产生新的匿名对象, 对原有的 属性并无影响,并且执行完此函数后 匿名对象被析构
	}
private:
    int a, b, c; 
}

void MyTest(){
    Test x(1, 2);
    cout << "c的值为?" << endl;		// C的值是 随机值!! 
}
  • 运行结果: C的值是 随机值 (没有赋初值)
  • 原因: 构造 调用 构造, 产生了新的匿名对象, 对原有的 属性并无影响

7. 匿名对象的生命周期

class Test{
public:
	Test(int a, int b, int c){
		this->a = a;	this->b = b;	this->c = c;
	}
	~Test(){}
private:
    int a, b, c; 
}

void MyTest(){
    Test(1, 2, 3); 			 	// 直产生匿名对象  --> 先调用一次 构造函数   紧接着在调用析构函数
    Test aa = Test(1, 2, 3);  	// 先调用一次 构造函数 然后将aa名字赋给匿名对象地址(编译器自动优化) aa生命周期结束后 在调用析构
}

写文不易 且行且珍惜
MrWang
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

盗骊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值