构造函数和析构函数会被编译器自动调用,完成对象初始化和对象清理工作。即使自己不提供初始化操作和清理操作,编译器也会增加默认的操作,所以编写类就应该顺便提供初始化函数。
构造函数和析构函数必须写在public下才可以调用到。
1)构造函数和析构函数的概念
构造函数主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用;析构函数主要用于对象销毁前系统自动调用,执行一些清理工作。
2)构造函数和析构函数的语法
构造函数与类名相同,没有返回值,不写void,可以发生重载(可以有参数),由编译器自动调用而不是手动,而且只会调用一次。
析构函数与类名相同,但类名前面需要加"~",没有返回值,不写void,不可以有参数(不能发生重载)。
- class Person{
- public:
- Person(){
- cout << "构造函数调用" << endl; //自动调用
- }
-
- ~Person(){
- cout << "构造函数调用" << endl; //自动调用,在函数结束后
- }
-
- }
3)构造函数的分类及调用
构造函数按照参数进行分类,分为无参构造函数(默认构造函数)和有参构造函数;
- class Person{
- public:
- //默认构造函数调用时不要加小括号
- //加上括号编译器认为是函数声明,不会调用构造和析构
- Person() {
- cout << "默认构造函数" << endl;
- }
-
- Person(int a) {
- cout << "有参构造函数" << endl;
- }
- };
按照类型进行分类,可分为普通构造函数和拷贝构造函数。
- class Person{
- public:
- //拷贝构造函数
- Person(const Person& p) {
- cout << "拷贝构造函数" << endl;
- }
-
- Person(int a) {
- cout << "有参构造函数" << endl;
- }
- };
构造函数的调用方式分为括号法调用、显示法调用以及隐式类型转换。
使用括号法调用时,默认构造函数不要加(),加括号编译器会认为是函数声明;
Person(100)叫做匿名对象,匿名对象特定,如果编译器发现对象是匿名的,那么在这行代码结束就释放这个对象;
不能用拷贝构造函数初始化匿名对象,如果写成Person(p5)编译器会认为写的是Person p5,按照对象的声明处理,写成右值才会按拷贝构造进行处理。
- void test01(){
- //括号法调用
- Person p1(1); //有参
- p1.m_age = 10;
- Person p2(p1); //拷贝
-
- Person p3; //默认构造函数,不要加(),加括号编译器会认为是函数声明
-
- //显示法调用
- Person p4 = Person(100); //有参函数调用
- Person p5 = Person(p4); //拷贝函数调用
- Person(100); //叫做匿名对象
-
- //隐式类型转换
- Person p = 100; //会使用隐式类型转换,转换成Person p = Person(100);
- Person p6 = p5; //会转换成拷贝函数
-
- }
4)拷贝构造函数调用时机
1.用已经创建好的对象来初始化新的对象
- void test01(){
- Person p1;
- p1.m_age = 10;
- Person p2(p1);
- }
2.以值传递的方式给函数参数传值
- //值传递会调用拷贝构造,引用传递不会调用拷贝构造
- void doWork(Person p1) { //Person p1 = Person(p)
-
- }
-
- void test02() {
- Person p;
- p.m_age = 10;
-
- doWork(p);
- }
3.以值的方式返回局部对象
- Person doWork2(){
- Person p1;
- return p1;
- }
-
- void test03() {
- Person p = doWork2();
- }
5)构造函数的调用规则
系统默认会给一个类提供三个函数:默认构造,拷贝构造,析构函数。
1.当提供了有参构造函数,系统就不会再提供默认构造函数,但是系统还会提供默认的拷贝构造函数;
2.当提供了拷贝构造,系统就不会提供其他构造了。
6)深拷贝和浅拷贝
有参构造函数就是为了初始化属性。
- class Person {
- public:
- Person() {}
-
- //初始化属性
- Person(char * name, int age) {
- m_Name = (char *)malloc(strlen(name) + 1);
- strcpy(m_Name, name);
-
- m_age = age;
- }
-
- //拷贝函数系统会默认提供,并且是简单的值拷贝
-
-
- char * m_Name;
- int m_age;
- };
-
- void test01() {
- Person p1("老王", 10);
- Person p2(p1); //调用了拷贝构造
- }
当上述代码加上拷贝函数后,无法运行成功,因为当p1调用析构函数之后指针指向的地址里的内容已经被释放,当p2再调用析构时,m_Name也不为空,进行释地址内容时会发生异常。
- //析构函数
- ~Person() {
- cout << "析构函数调用" <
- if(m_Name != NULL) { //m_Name是一个指针,需要释放
- free(m_Name);
- m_Name = NULL;
- }
- }
上述拷贝方式叫做浅拷贝,浅拷贝直接将p1的地址拷贝给p2,导致析构时释放堆区空间两次发生异常,解决方法是使用深拷贝。深拷贝不会直接拷贝地址,而是开辟一块新的空间,此时都调用析构函数时不会再发生错误。
- class Person {
- public:
- Person() {}
-
- //初始化属性
- Person(char * name, int age) {
- m_Name = (char *)malloc(strlen(name) + 1);
- strcpy(m_Name, name);
-
- m_age = age;
- }
-
- //拷贝函数系统会默认提供,并且是简单的值拷贝
- //深拷贝写法
- Person(const Person&p) {
- m_age = p.m_age;
- m_Name = (char *)malloc(strlen(p.m_Name) + 1);
- strcpy(m_Name, p.m_Name);
- }
-
- //析构函数
- ~Person() {
- cout << "析构函数调用" <
- if(m_Name != NULL) { //m_Name是一个指针,需要释放
- free(m_Name);
- m_Name = NULL;
- }
- }
-
- char * m_Name;
- int m_age;
- };
-
- void test01() {
- Person p1("老王", 10);
- Person p2(p1); //调用了拷贝构造
- }
7)初始化列表
数据的初始化可以使用有参构造函数以及初始化列表。
利用构造函数初始化数据:
- class Person {
- public:
- //有参构造函数进行数据初始化
- Person(int a, int b, int c) {
- ma = a;
- mb = b;
- mc = c;
- }
-
- int ma;
- int mb;
- int mc;
- };
-
- void test() {
- Person p1(10, 20, 30);
- }
利用初始化列表初始化数据:构造函数后面 + : 属性(参数),属性(参数)...
- class Person {
- public:
- //有参构造函数进行数据初始化
- Person(int a, int b, int c) : ma(a), mb(b), mc(c) {}
-
- int ma;
- int mb;
- int mc;
- };
-
- void test() {
- Person p1(10, 20, 30);
- }
8)类对象作为类成员的实例
- class Phone {
- public:
- Phone(){
- cout << "手机的默认构造函数调用" << endl;
- }
-
- Phone(string name){
- Phone_Name = name;
- }
-
- ~Phone(){
- cout << "手机的析构函数调用" << endl;
- }
-
- string Phone_Name;
- };
-
-
- class Game {
- public:
- Game(){
- cout << "游戏的默认构造函数调用" << endl;
- }
-
- Game(string name){
- Game_Name = name;
- }
-
- ~Game(){
- cout << "游戏的析构函数调用" << endl;
- }
-
- string Game_Name;
- };
-
-
- class Person{
- public:
-
- Person(){
- cout << "Person的默认构造函数调用" << endl;
- }
-
- Person(string name, string phoneName, string gameName){
- m_name = name;
- m_phone = phoneName;
- m_game = gameName;
- }
-
- ~Person(){
- cout << "Person的析构函数调用" << endl;
- }
-
- string m_name;
- Phone m_phone;
- Game m_game;
- };
-
- void test() {
- Person p("老王", "华为", "斗地主");
- }
类对象作为类成员的时候,构造顺序先将类对象一一构造,然后构造自己,析构的顺序相反。
9)explicit关键字作用
使用了explicit关键字就不能用隐式类型转换来构造对象,explicit用来防止隐式类型转换。
10)动态对象创建
C语言提供了动态内存分配函数malloc和free,用于从堆中分配和释放存储单元,然而这些函数在C++中不能很好的运行,因为它不能很好完成对象的初始化工作。
1.使用malloc存在的问题:
程序员必须确定对象的长度;malloc返回一个void*指针,C++不允许将void*赋值给其他指针,必须强制类型转换;malloc可能申请内存失败,所以必须判断返回值确保内存分配成功;用户在使用对象之前必须对他初始化,构造函数不能显示调用初始化,用户可能会忘记调用。
2.可以使用new来开辟空间,new的对象会默认调用构造函数,使用方法如下:
Person * p2 = new Person;
3.new和malloc的区别:
new在堆区开辟空间,所有new出来的对象都会返回该类型的指针;
malloc不会调用构造,new会调用构造;
new是一个运算符,malloc是一个函数。
释放堆区的空间用delete,delete也是一个运算符,要配合new使用;malloc配合free使用。
void * p = new Person;
void*接收new的对象会造成无法释放,所以要避免这种写法。
4.通过new开辟数组
- void test(){
- Person * array = new Person[10]; //会调用十次默认构造函数
-
- delete [] array; //释放数组对象必须要delete[]
- }
-
相关阅读:
vue-element-admin+springboot登录功能实现
python自动化测试(二):获取元素定位的基本方式
Java基础 - 模拟医院挂号系统
PyQt5 QLabel标签
详解API接口如何安全的传输数据
神经网络反向传播的数学原理
letcode 2171. 拿出最少数目的魔法豆
QT day3
java基于springboot+vue的疫情网课管理系统 elementui
JVM类加载机制详解
-
原文地址:https://blog.csdn.net/qq_43697646/article/details/126979519