对象的初始化和清除也是非常重要的安全问题
一个对象或者变量没有初始化,对其使用后果是未知
同样的使用完一个对象或变量,没有及时清除,也会造成一定的安全问题。
C 利用了构造函数和析构函数解决上述问题,这个函数将会被编译器自动调用,完成对象初始化和清除工作。对象的初始化和清除工作是编译器强制我们做的事情,因此如果我们不提供构造和析构函数,编译器会提供编译器创造的构造函数和析构函数是空实现。
构造函数:主要作用于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用。
析构函数:主要作用于对象销毁前系统自动调用,执行一些清理工作。
构造函数语法:类名(){}
1.构造函数,没有返回值也不写void
2.函数名称与类名相同
3.构造函数可以有参数,因此可以发生重载
4.程序在调用对象时候会自动调用构造,无需手动调用,而且只调用一次。
析构函数语法 :~类名(){}
1.析构函数,没有返回值也不写void
2.函数名称与类名相同,在名称前面加上符号~
3.析构函数不可以有参数,因此不可以发生重载
4.程序在对象销毁前会自动调用函数,需手动调用,而且只调用一次。
//对象的初始化和清理
//1.构造函数,进行初始化操作
class Person{
public:
Person(){
cout <<"Person的构造函数"<<endl;
}
~Person(){
cout <<"Person的析构函数"<<endl;
}
}
void test01(){
Person p;
}
int main(){
test01();
//输出:Person的构造函数 Person的析构函数
//test01函数的数据存放在栈区,内存在函数调用完释放,所有对象的析构函数也调用了。
Person p;//输出:Person的构造函数
//对象创建在主函数中,不会立即释放
}
2.构造函数的分类及调用
两种分类方式:
按参数分类:有参构造和无参构造(默认构造)
按类型分类:普通类型和拷贝类型
三种调用方式:
括号法,显示法,隐式转换法
class Perosn{
public:
//无参构造,普通类
Person(){
cout<<"Person的无参构造"<<endl;
}
//有参构造,普通类
Person(int a){
int age = a;
cout<<"Person的有参构造"<<endl;
}
//拷贝构造函数
Person(const Person &p){
int age = p.a;//将传去的人身上的所有属性,拷贝到我身上
cout<<"Person的拷贝构造"<<endl;
}
}
void main(){
//1.括号法
Person p1;//调用默认构造函数
//注意:调用默认构造函数时不要加(),如果加了Person p1(); 编译器会认为是一个函数声明,不会创建对象
Person p2(10);//调用有参构造函数
Person p3(p1)//调用拷贝构造函数
//2.显示法
Person p4;//调用默认构造函数
Person p5 = Person(10);//调用有参构造函数
Person p6 = Person(p5);//调用拷贝构造函数
//Person(10);单独拿出,是一个匿名对象。特点:当前行执行结束后,系统会立即回收掉匿名对象
Person(p4);//是错误的,会报p4重定义
//注意:不要利用拷贝函数初始化匿名对象 编译器会认为Person(p4);等价于 Person p4;所以重定义p4了
//3.隐式转换法
Person p7= 10;//相当于 Person p7 = Person(10);
Person p8= p7;//拷贝构造
}
3.拷贝构造函数调用时机
C 中拷贝构造函数调用时机通常有三种情况
1.使用一个已经创建完毕的对象来初始化一个新对象。
2.值传递的方式给函数参数传值
3.以值方式返回局部对象
class Person{
public:
int m_Age;
Person(){
cout<<"Person的无参构造"<<endl;
}
Person(int age){
m_Age = age;
cout<<"Person的有参构造"<<endl;
}
Person(const Person &p){
cout<<"Person的拷贝构造"<<endl;
}
~Person(){
cout<<"Person的析构构造"<<endl;
}
}
//2.值传递的方式给函数参数传值
void doWork(Person p){
}
//3.以值方式返回局部对象
Person doWork2(){
Person p;
return p;//此时的p是一个局部对象,当次函数调用结束就会销毁,所以要把值拷贝给p3。
}
void main(){
//1.使用一个已经创建完毕的对象来初始化一个新对象。
Person p1(20);
Person p2(p1);
cout << p2.m_Age<<emdl;//20
//2.值传递的方式给函数参数传值
Person p;
doWork(p);//会调用拷贝构造函数,因为当把实参的值传给函数的形参时实际上是把实参的值复制给形参,也就是拷贝
//3.以值方式返回局部对象
Person p3 = doWork2();
}
4.构造函数的调用规则
默认情况下,C 编译器至少给一个类添加三个函数
1.默认构造函数(无参,函数体空)
2.默认析构函数(无参,函数体空)
3.默认拷贝构造函数,对属性进行值拷贝(值拷贝)
构造函数调用规则:
1.如果用户定义有参构造函数,c 不提供默认无参构造,但会提供默认拷贝构造
2.如果用户定义拷贝构造函数,c 不会提供其他普通构造函数。
5.深拷贝和浅拷贝深浅拷贝是面试经典问题,也是常见的一个坑。
浅拷贝:简单的赋值拷贝操作。
深拷贝:在堆区重新申请空间,进行拷贝操作。
class Person{
public:
int m_Age;
int *m_Height;
Person(int age){
m_Age = age;
}
Person(int age,int height){
m_Age = age;
m_Height = new int(height);//把数据存放到堆区
}
~Person(){
//析构函数,将堆区开辟堆区数据做释放操作
if(m_Height!=NULL){
delete m_Height;
m_Height = NULL;
}
cout << "析构 "<<endl;
}
//自己实现拷贝函数,解决浅拷贝带来的问题
Person(const Person &p){
m_Age = p.m_Age;
m_Height = new int(*p.m_Height);
}
};
void main(){
Person p1(18,160);
cout << "p1的年龄为:"<< p1.m_Age<<endl;
//调用系统提供的拷贝函数,浅拷贝
Person p2(p1);
//当数据写在堆区用浅拷贝会带来一种问题是,堆区的数据会重复释放以此报错。
Person p3(18,182);
cout << p3.m_Age<< *p3.m_Height <<endl;
//自己重写拷贝函数,把堆区的数据换地存放
Person p4(p3);
}
浅拷贝造成错误的原因如下图所示:当对象p2释放内存是会把堆区的内存也一起释放,但是当轮到对象p1是否内存是,堆区的内存已经释放了,以此造成错误。
6.初始化列表(不重要)作用:C 提供了初始化列表语法,用来初始化属性
语法:构造函数():属性1(值1),属性2(值2).....{}
class Person{
public:
int m_A;
int m_B;
int m_C;
//传统初始化操作
Person(int a, int b, int c){
m_A = a;
m_B = b;
m_C = c;
}
//初始化列表
Person():m_A(10),m_A(20),m_A(30){
}
//两者结合
Person(int a, int b, int c):m_A(a),m_A(b),m_A(c){
}
}
void main(){
//传统初始化操作
Person p(10,20,30);
//初始化列表
Person p;
//两者结合初始化列表
Person p(10,20,30);
}
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved