知识屋:更实用的电脑技术知识网站
所在位置:首页 > 教育

C++自学笔记

发表时间:2022-03-25来源:网络

C++自学笔记(14 类和对象_对象的初始化和清理)

14 类和对象_对象的初始化和清理构造函数和析构函数构造函数的分类及调用拷贝构造函数的调用时机构造函数调用规则深拷贝与浅拷贝初始化列表类对象作为类成员静态成员1、静态成员变量2、静态成员函数

14 类和对象_对象的初始化和清理

本次记录对象的初始化和清理,还请各位大佬批评指正!

生活中我们买的电子产品基本都有出场配置,在某一天我们不用的时候也会删除一些自己信息数据保证安全。
在C++中的面向对象来源于生活,每个对象也都会有初始设置以及 对象销毁前的清理数据的设置。

构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题。
(1)一个对象或者变量没有初始状态,对其使用后果是未知;
(2)同样的,使用完一个对象或者变量,没有及时清理,也会造成某些安全问题。
C++利用构造函数和析构函数解决上述问题,这两个函数将会被编译器自动调用,完成对象初始化和清理工作。
对象的初始化和清理工作是编译器强制要我们做的事情,因此如果我们不提供构造和析构,编译器会提供。编译器提供的构造函数和析构函数是空实现。
1、构造函数:
主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
语法:
类名(){ }
1、构造函数没有返回值,也不写void。
2、函数名于类名称相同。
3、构造函数可以有参数,因此可以重载。
4、程序在调用对象的时候会自动调用构造,无需手动调用,且只会调用一次

2、析构函数:
主要作用在于对象销毁前系统自动调用,执行一些清理工作。
语法:
~类名(){ }
1、析构函数没有返回值,也不写void。
2、函数名于类名称相同,在名称前加上符号 ~ 。
3、析构函数不可以有参数,因此不可以重载。
4、程序在调用对象的时候会自动调用析构,无需手动调用,且只会调用一次。

#include #include #include using namespace std; //对象的初始化和清理 class person { public: //class 默认权限为私有 //1、构造函数 进行初始化操作 person()//构造函数没有返回值,也不写void ; 函数名于类名称相同 { cout person p; //局部变量 在栈上的数据,test01执行完毕后,释放这个对象 } int main() { test01(); person p;//对象不会释放,程序结束的时候才会调用析构函数 system("pause"); return 0; }

运行结果如下:
其中前两行是调用 test01( ) 的结果,test01( )运行结束后,p就被释放掉了,会有构造函数和析构函数的调用。
第三行是 person p; 代码运行的结果,执行完该行代码,对象不会释放,程序结束的时候对象释放,才会调用析构函数,也就是最下边一行的结果。

构造函数的分类及调用

两种分类方式:
按参数分为:有参构造和无参(默认)构造。
按类型分为:普通构造和拷贝构造。
三种调用方式:
括号法。
显示法。
隐式转换法。

#include #include #include using namespace std; //构造函数对的分类及调用 class person { public: //class 默认权限为私有 //构造函数 //前俩为普通构造函数,第三个为拷贝构造函数;第一个为无参构造函数,后边俩为有参构造函数 person() { cout age = p.age;//将传入的人身上的所有属性,拷贝到我身上 cout //1、括号法 //person p1; //默认构造函数的调用 //person p2(10);//有参构造函数的调用 //person p3(p2);//拷贝构造函数 //注意事项1 //调用默认构造函数的时候不要加() //person p1();//这行代码,编译器会认为是函数的声明,不会认为在创建对象 /*cout public: //class 默认权限为私有 //构造函数 person() { cout m_age = p.m_age;//将传入的人身上的所有属性,拷贝到我身上 cout person p1(20); person p2(p1); cout person p; dowork01(p); } //3、以值的方式返回局部对象 person dowork02() { person p1; cout test01(); //test02(); //test03(); system("pause"); return 0; }

test01();调用结果如下:
代码person p1(20);调用有参构造函数。代码person p2(p1);调用拷贝构造函数。输出p2的年龄后,test01程序结束,p1和p2被释放,调用两次析构函数。

test02();调用结果如下:
代码person p;调用默认构造函数。代码dowork01( p );以值传递(拷贝副本)的方式给参数传值是调用拷贝构造函数。test02程序结束,对象被释放,调用两次析构函数。

test03();调用结果如下:
首先看dowork02(),代码person p1;调用默认构造函数。然后输出p1的地址。代码return p1;会根据p1返回一个新的对象,给到对象p,此时调用拷贝构造函数。dowork02()结束后p1被释放,调用析构函数。然后输出对象p的地址。test03()结束后p被释放,调用析构函数。

构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数:
1、默认构造函数(无参,函数体为空)。
2、默认析构函数(无参,函数体为空)。
3、默认拷贝构造函数,对属性进行值拷贝。
构造函数调用规则如下:
1、如果用户定义有参构造函数,C++不再提供默认无参构造函数,但是会提供默认拷贝构造函数。调用 test02 时注释掉 test01 以及定义的拷贝构造函数,程序正常运行。此时定义了有参构造函数,C++提供默认拷贝构造函数,p1 能拷贝到 p2。
2、如果用户定义拷贝构造函数,C++不会再提供其他构造函数。调用 test01 时,若注释掉默认构造函数和有参构造函数时出错。此时定义了拷贝构造函数,C++不会再提供其他构造函数。

#include #include #include using namespace std; //构造函数调用规则 class person { public: //无参(默认)构造函数 person() { cout // m_age = p.m_age;//将传入的人身上的所有属性,拷贝到我身上 // cout // person p; // person p1; // p1.m_age = 18; // // person p2(p1); //值拷贝 // cout //test01(); test02(); system("pause"); return 0; }

深拷贝与浅拷贝

深浅拷贝是面试经典问题,也是常见的一个坑。
浅拷贝:简单的赋值拷贝操作。存在的问题是,会导致堆区的内存重复释放。
深拷贝:在堆区重新申请空间,进行拷贝操作。

运行如下代码时,如果注释掉自己定义的拷贝构造函数,会出错。执行person p2(p1); 时会使用编译器提供的拷贝构造函数,会做浅拷贝操作。调用析构函数时,先进后出,即p2先释放,p1后释放。p2 释放调用析构函数时已经将身高的内存释放掉了,p1 释放调用析构函数会重复释放同一内存,重复释放是非法操作。浅拷贝的问题,要利用深拷贝进行解决。

注意事项:
如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题。

#include #include #include using namespace std; //深拷贝与浅拷贝 class person { public: //无参(默认)构造函数 person() { cout m_age = p.m_age; //m_h = p.m_h;//编译器默认实现的就是这行代码 //深拷贝操作 m_h = new int(*p.m_h); cout delete m_h; m_h = NULL; } cout test01(); system("pause"); return 0; }

初始化列表

作用:C++提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2)…{ }

#include #include #include using namespace std; //初始化列表 class person { public: 传统初始化操作 //person(int a, int b, int c) //{ // m_A = a; // m_B = b; // m_C = c; //} //初始化列表初始化属性 person(int a, int b, int c) :m_A(a), m_B(b), m_C(c){ } int m_A; int m_B; int m_C; }; void test01() { person p(10, 20, 30); cout
A a;
};
B类中有对象A作为成员,A为对象成员。

#include #include #include using namespace std; //类对象作为类成员 class phone { public: phone(string name) { cout public: //phone m_phone = pname //隐式转换法 person(string name, string pname) : m_name(name), m_phone(pname) { cout person p("张三", "苹果MAX"); cout public: //所有对象共享同一份数据;类内声明,类外初始化 static int m_A; //静态成员变量也是有访问权限的 private: static int m_B; }; int person :: m_A = 100;//类外初始化 int person :: m_B = 200; void test01() { person p1; cout test01(); test02(); system("pause"); return 0; }

2、静态成员函数

特点如下:
所有对象共享一个函数;
静态成员函数只能访问静态成员变量。

#include #include #include using namespace std; //静态成员函数 class person { public: //静态成员函数 static void func() { m_A = 100;//静态成员函数能够访问静态成员变量,所有对象都共享一份数据 //m_B = 100;//静态成员函数 不能访问 非静态成员变量,无法区分到底是哪个对象的m_B属性 cout //通过对象访问 person p; p.func(); //通过类名访问 person::func(); //person::func2();//私有静态成员函数类外访问不到 } int main() { test03(); system("pause"); return 0; }
收藏
  • 人气文章
  • 最新文章
  • 下载排行榜
  • 热门排行榜