环境:
编译器:CLion2021.3;操作系统:macOS Ventura 13.0.1

文章目录

  • 一、构造函数和析构函数
    • 1.1 构造函数
    • 1.2 析构函数
    • 1.3 示例
  • 二、构造函数的分类及调用
    • 1.1 构造函数的分类
    • 1.2 构造函数的调用
  • 三、拷贝构造函数调用时机
    • 3.1 调用时机
    • 3.2 返回值优化
  • 四、构造函数调用规则
  • 五、深拷贝与浅拷贝
    • 5.1 浅拷贝
    • 5.2 深拷贝
  • 六、初始化列表
  • 七、类对象作为类成员
  • 八、静态成员
    • 8.1 静态成员变量
    • 8.2 静态成员函数

地表最强C++系列传送门:
「地表最强」C++核心编程(一)内存分区模型
「地表最强」C++核心编程(二)引用
「地表最强」C++核心编程(三)函数提高
「地表最强」C++核心编程(四)类和对象----封装
「地表最强」C++核心编程(五)类和对象----对象初始化和清理
「地表最强」C++核心编程(六)类和对象----对象模型和this指针
「地表最强」C++核心编程(七)类和对象----友元
「地表最强」C++核心编程(八)类和对象----运算符重载
「地表最强」C++核心编程(九)类和对象----继承
「地表最强」C++核心编程(十)类和对象----多态
「地表最强」C++核心编程(十一)文件操作

一、构造函数和析构函数

构造函数和析构函数是必须实现的两个函数,即使自己没有写,编译器也会默认调用默认的,空的构造和析构函数。

1.1 构造函数

构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无须手动调用。

语法: 类名(){}

构造函数的特点:

  • 没有返回值,不用写void
  • 函数名与类名相同
  • 构造函数可以有参数,可以发生重载
  • 创建对象时自动调用,且只调用一次

1.2 析构函数

析构函数主要作用在于对象销毁前系统自动调用,执行一些清理工作。

语法: ~类名(){}

析构函数的特点:

  • 没有返回值,不用写void
  • 函数名与类名相同,前面加个~
  • 析构函数不可以有参数,不可以发生重载
  • 销毁对象时自动调用,且只调用一次

1.3 示例

class Person {public:Person() {cout << "Person的构造函数" << endl;//自己写了就自动调用自己写的。否则就调用默认的空的构造函数}~Person() {cout << "Person的析构函数" << endl;}
};//构造和析构函数都必须实现,自己不提供编译器会提供一个空的构造和析构函数
void test01(){Person p;
}int main() {test01();return 0;
}

二、构造函数的分类及调用

1.1 构造函数的分类

构造函数按照不同的标准可以分为不同的类型:
按参数分类可分为无参构造函数(也叫做默认构造函数)和有参构造函数;
按类型分类可分为普通构造函数拷贝构造函数
⚠️由于创建对象需要调用构造函数,因此构造函数的权限应该是public,否则类外无法调用构造函数,也就无法实例化对象。
⚠️拷贝构造函数的参数需要是常量对象

class Person {public://要加public作用域,否则默认是private,就无法调用构造析构函数,也就无法创建对象int age;Person() {cout << "Person的无参构造函数" << endl;}Person(int a) {age = a;cout << "Person的有参构造函数" << endl;}Person(const Person &p) {//拷贝构造函数,要用const和&的方式传参age = p.age;//将传入的人身上的所有属性,拷贝到自己身上cout << "Person的拷贝构造函数" << endl;}~Person() {cout << "Person的析构函数" << endl;}
};

1.2 构造函数的调用

构造函数的调用有三种方法,分别是括号法、显示法和隐式转换法

void test() {//括号法Person p1;//默认构造函数调用Person p2(10);//有参构造函数调用Person p3(p2);//拷贝构造函数调用//⚠️括号法调用默认构造函数的时候不要加(),否则编译器会认为这是一个函数的声明
//    Person p1();//虽然不报错,但是逻辑错误,编译器会认为有一个返回值类型是Person,名为p1的函数cout<<"p2的年龄是:"<<p2.age<<endl;//10cout<<"p3的年龄是:"<<p3.age<<endl;//10//显示法Person p1;Person p2 = Person(10);//有参构造,实际上是将匿名对象赋值给p2Person p3 = Person(p2);//拷贝构造
//    Person(10);//这是一个创建匿名对象,但没有办法使用        特点:当前行执行结束后,系统会立即回收掉匿名对象
//    cout<<"匿名对象已经没了"<<endl;//⚠️不要用拷贝构造函数初始化匿名对象,编译器会认为这是重定义。   Person(p3); 会被转换为 Person p3;
//    Person(p3);//err,Redefinition of 'p3'//隐式转换法Person p4 = 10;//相当于Person p4 = Person(10);     有参构造Person p5 = p4;//相当于Person P5 = Person(p4);     拷贝构造
}

三、拷贝构造函数调用时机

3.1 调用时机

C++中拷贝构造函数调用时机通常有三种情况:

  • 1使用一个已经创建完毕的对象来初始化一个新对象
  • 2值传递的方式给函数参数传值
  • 3以值方式返回局部对象
//1使用一个已经创建完毕的对象来初始化一个新对象
void test01() {Person p1(20);//括号法调用构造函数Person p2(p1);//用对象p1来初始化p2,实际上就是调用了拷贝构造函数cout << "p2的年龄:" << p1.mAge << endl;
}//2值传递的方式给函数参数传值
void doWork1(Person p) {}
void test02() {Person p;//调用默认构造函数doWork1(p);//值传递,doWork函数会调用拷贝构造函数给形参临时开辟空间
}//3以值方式返回局部对象
Person doWork2() {Person p;//调用默认构造函数return p;//返回的不是上边的p,因为已经被释放掉了。这里返回的是另一个创建的对象,这个对象是用拷贝构造函数创建的。
}
void test03() {Person p = doWork2();//CLion测试下不会调用析构函数,这是因为返回值优化技术。cout << &p << endl;
}

3.2 返回值优化

这里解释一下第3点以值的方式返回对象:doWork2函数中首先定义了一个Person类型变量p,我们假设这个对象的地址是add1,然后该函数返回了对象p。这一步的操作实际上是用拷贝构造函数的方法又创建了一个对象p’,我们假设p’的地址是add2,然后把p作为拷贝构造函数的参数传给p’。而实际返回的是p’而不是p,这两个对象的地址是不同的,是两个对象
返回值优化是一项编译优化技术,使得返回对象时不必调用拷贝构造函数,经过测试后返回的就是已经创建好的p的地址而不会产生p’。
关于返回值优化,更多的可以参考一下这里:Return value optimization-Wikipedia

四、构造函数调用规则

默认情况下,c++编译器至少给一个类添加3个函数:

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无参,函数体为空)
  • 默认拷贝构造函数,对属性进行值拷贝

构造函数调用规则如下:

  • 如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,c++不会再提供其他构造函数
class Person {public:int age;//无参(默认)构造函数Person() {cout << "无参构造函数!" << endl;}//有参构造函数Person(int a) {age = a;cout << "有参构造函数!" << endl;}//拷贝构造函数Person(const Person &p) {age = p.age;cout << "拷贝构造函数!" << endl;}//析构函数~Person() {cout << "析构函数!" << endl;}
};void test(){Person p;//若只写有参构造或者拷贝构造,不写无参,此处会报错。因为此行代码会调用无参,但是我们写了有参,编译器就不会提供无参Person p(28);//若没写无参和有参,只写了拷贝构造,此处会报错。因为写了拷贝构造,编译器就不会提供其他无参和有参Person p2(p);cout << "p2的年龄:" << p2.age << endl;
}

五、深拷贝与浅拷贝

5.1 浅拷贝

浅拷贝就是简单的赋值操作,默认的拷贝构造函数进行的就是浅拷贝。

class Person{private:string m_name;int m_age;
public:Person(const Person& p){//这里其实就是浅拷贝m_name = p.m_name;m_age = p.m_age;}

5.2 深拷贝

从下边这个实例说起,注意这里的身高我定义成了指针,指向的内容是身高

class Person {public:int m_age;int *m_height;//指针,指向的内容是身高public://有参构造函数Person(int age, int height) {m_age = age;m_height = new int(height);//用new创建在堆区}//拷贝构造函数Person(const Person &p) {m_age = p.m_age;m_height = p.m_height;//默认的浅拷贝}//析构函数~Person() {//堆区开辟的数据在此时可以释放了if (m_height != NULL) {delete m_height;m_height = NULL;}}
};void test01() {Person p1(18, 180);//调用有参构造Person p2(p1);//调用拷贝构造
}


如图,通过有参构造创建了p1,然后通过浅拷贝的拷贝构造创建了p2。而浅拷贝的只是在机械的赋值,因此p2的所有属性都和p1一样,此时他们的身高的指针都是指向同一堆空间的,那么此时他们中任何一个对这块空间的操作会直接影响到另一个人。在test()调用结束后,这两个对象也会调用自己的析构函数来销毁空间。p2调用了自己的析构函数,将0x003这块儿空间释放掉,然后p1才调用自己的析构函数(这里涉及到析构函数的调用时间,简单来说就是先构造的后析构,想想栈的特性就明白了),但此时此空间已经被释放掉了,还要释放那就是非法的。
想解决这个问题,只需要在构造p2的时候,让他的身高指针指向与p1不同的空间即可。因此只需重写拷贝构造函数即可:

    Person(const Person &p) {//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题。若不自己重写拷贝构造函数,拷贝构造出来的对象和被拷贝的对象指向同一块儿空间。m_age = p.m_age;m_height = new int(*p.m_height);//重新申请一块堆区,属于深拷贝}


这样就不会有重复释放的问题,这就是深拷贝。

六、初始化列表

初始化列表是一种利用构造函数初始化属性的另一种方式。

语法: 构造函数():属性1(值1),属性2(值2)… {}

class Person {public:int m_A;int m_B;int m_C;public://传统方式初始化Person(int a, int b, int c) {m_A = a;m_B = b;m_C = c;}//初始化列表方式初始化Person() : m_A(10), m_B(20), m_C(30) {}Person(int a, int b, int c) : m_A(a), m_B(b), m_C(c) {}
};

七、类对象作为类成员

假设一个类的对象B是另一个类A的成员,那么在调用构造函数的时候,会先调用B的构造函数:

class Phone {public:string m_PhoneName;Phone(string name) {m_PhoneName = name;cout << "Phone构造" << endl;}~Phone() {cout << "Phone析构" << endl;}
};class Person {public:string m_Name;Phone m_Phone;//初始化列表可以告诉编译器调用哪一个构造函数             Phone m_Phone = pName;其实是隐式转换法Person(string name, string pName) : m_Name(name), m_Phone(pName) {cout << "Person构造" << endl;}~Person() {cout << "Person析构" << endl;}void playGame() {cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 手机! " << endl;}
};void test01() {//当类中成员是其他类对象时,我们称该成员为对象成员//构造的顺序是 :先调用对象成员的构造,再调用本类构造,析构顺序与构造相反Person p("张三", "苹果1024");//先构造phonep.playGame();
}

八、静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称为静态成员。包括静态成员变量静态成员函数

8.1 静态成员变量

特点:
1.所有对象共享同一份数据,因此不属于某个对象
2.在编译阶段分配内存,全局区
3.类内声明,类外初始化

class Person {public:static int m_A; //静态成员变量
private:static int m_B; //静态成员变量也是有访问权限的
};//类外初始化,否则无法正常访问
int Person::m_A = 10;
int Person::m_B = 10;//private也需要类外初始化,这是可以的void test01() {//静态成员变量两种访问方式,这是由于静态成员变量被所有对象共享//1、通过对象        非静态成员变量只能这么访问Person p1;p1.m_A = 100;cout << "p1.m_A = " << p1.m_A << endl;//100Person p2;p2.m_A = 200;cout << "p1.m_A = " << p1.m_A << endl; //200 共享同一份数据,不属于某个对象cout << "p2.m_A = " << p2.m_A << endl; //200//2、通过类名        非静态成员变量不能这么访问cout << "m_A = " << Person::m_A << endl;//cout << "m_B = " << Person::m_B << endl; //err,私有权限访问不到
}

8.2 静态成员函数

特点:
1.所有对象共享同一个函数
2.静态成员函数只能访问静态成员变量

class Person
{public:static int m_A; //静态成员变量int m_B; //只属于对象本身,不能被对象共享//静态成员函数static void func(){cout << "func调用" << endl;m_A = 100;
//        m_B = 100; //err,不可以访问非静态成员变量,无法区分到底是哪个对象的属性}private://静态成员函数也是有访问权限的static void func2(){cout << "func2调用" << endl;}
};int Person::m_A = 10;void test01()
{//静态成员变量两种访问方式//1、通过对象Person p1;p1.func();//2、通过类名Person::func();
//    Person::func2(); //err,私有权限访问不到
}

「地表最强」C++核心编程(五)类和对象--对象初始化和清理相关推荐

  1. 「地表最强」C++核心编程(七)类和对象--友元

    环境: 编译器:CLion2021.3:操作系统:macOS Ventura 13.0.1 文章目录 一.全局函数做友元 二.类做友元 三.成员函数做友元 地表最强C++系列传送门: 「地表最强」C+ ...

  2. 苹果M1「徒有其表」?「地表最强」芯只能剪视频引知乎热议

    来源:新智元 [导读]5nm工艺,570亿晶体管,70%CPU性能提升,4倍GPU性能提升.号称史上最强芯片的M1 Max,只能「剪剪视频」? 最近,苹果开了一个芯片新品发布会. 光看参数,M1 Pr ...

  3. 第四部分—C++核心编程_4. 类和对象

    4.1 类和对象的基本概念 4.1.1 C和C++中struct区别 c语言struct中只有变量 c++语言struct中既有变量,也有函数 4.1.2 类的封装 我们编写程序的目的是为了解决现实中 ...

  4. C++核心编程4— 类和对象

    C++面向对象的三大特性为:封装.继承.多态 C++认为万事万物都皆为对象,对象上有其属性和行为 例如: ​ 人可以作为对象,属性有姓名.年龄.身高.体重-,行为有走.跑.跳.吃饭.唱歌- ​ 车也可 ...

  5. C++核心编程<类和对象>(4)

    C++核心编程<类和对象> 4.类和对象 4.1封装 4.1.1封装的意义 封装的意义1 封装的意义2 4.1.2struct和class区别 4.1.3成员属性设置为私有 4.2对象的初 ...

  6. 【黑马程序员 C++教程从0到1入门编程】【笔记4】C++核心编程(类和对象——封装、权限、对象的初始化和清理、构造函数、析构函数、深拷贝、浅拷贝、初始化列表、友元friend、运算符重载)

    黑马程序员C++教程 文章目录 4 类和对象(类属性[成员属性],类函数[成员函数]) 4.1 封装 4.1.1 封装的意义(三种权限:public公共.protected保护.private私有)( ...

  7. 「星火计划沙龙视频」Angel核心推荐算法及其应用探秘

    腾讯大数据"星火计划"系列技术沙龙第4期,<Angel核心推荐算法及其应用探秘>直播专场回顾. 作为当前AI技术最常见的应用场景之一,推荐系统能够帮助用户更加高效地获取 ...

  8. 前端与移动开发----JS高级----面向对象编程,类与实例对象,继承,严格模式,模板字符串,class封装tab栏

    JS高级01 回顾上阶段 Javascript组成 ECMAScript: 基础语法 (变量, 表达式, 循环, 判断, 函数, 对象等) DOM: document 操作标签(获取, 增加, 插入, ...

  9. 「科技与安全」RK3568J核心板让隔离网闸更强大

    一句"科技与狠活"让食品安全的话题冲上了热搜,其实除了食品安全,生活中还有很多安全问题值得我们注意.在信息数字化给我们的生活带来种种便利的同时,信息泄露的风险也大大增加.如何有效地 ...

最新文章

  1. PCL两种方式的点云读写
  2. 收藏!超全机器学习资料合集!(附下载)
  3. Python中is和==有什么区别?
  4. dae怎么用草图大师打开_当 to C市场饱和,该怎么用场景化打开新市场?
  5. vue计算多列和_解决vue 表格table列求和的问题
  6. Educational Codeforces Round 60 D. Magic Gems
  7. oracle 全局搜索字符串,oracle操作字符串:拼接、替换、截取、查找 _ 学编程-免费技术教程分享平台...
  8. Eclipse-习惯设置/快捷键/插件
  9. 有人说智能制造装备前景大好,那么智能制造装备产业园的潜力如何?
  10. 笔记本重置找不到恢复环境_Win10 自带的疑问解答、备份、恢复还原、重置系统怎么使用?...
  11. 中小制造型企业如何成功实施5S管理?
  12. FAT文件系统详解(一)
  13. 在n*n方阵里填入1,2,...n*n,要求填成蛇形
  14. pearson相关性
  15. 薛定谔的猫、量子纠缠、和量子计算机
  16. SharePoint传出电子邮件配置
  17. 如何用ChemDraw Prime 绘制任意弧线箭头
  18. HTML hr 标签的用法
  19. 咸鱼软件应用—Cura3D切片
  20. 这七种职业男人让多少日本女性着迷

热门文章

  1. 前端JS面试题简约版
  2. 《千山暮雪》落下帷幕 张然因悦莹获封最佳闺蜜_0
  3. python编译安装(centos、uos\ubuntu)
  4. 利用循环嵌套,输出九九乘法表。c语言+注释
  5. 相册照片不小心删了怎么恢复?恢复秘诀
  6. 好看的抖音小视频不小心手滑刷过去了怎么办?Python帮你解决烦恼
  7. PHP面向对象的三大特性
  8. 匹配中文字符正则表达式
  9. java stream Map用法
  10. GitHub SSH免密登录