对象的初始化和处理也是两个非常重要的安全问题,C++利用构造函数和析构函数解决上述问题。这两个函数会被编译器自动调用,如果我们不提供构造和析构,编译器会提供编译器提供的构造函数和析构函数是空实现。
- 构造函数:主要作用域创建对象时为对象成员属性赋值,构造函数由编译器自动调用,无须手动调用
- 析构函数:主要作用于销毁前系统自动调用,执行一些清理工作
一、构造函数和析构函数
1. 构造函数语法:类名(){}
- 构造函数,没有返回值也不写void
- 函数名称于类名相同
- 构造函数可以由参数,因此可以发生重载
- 程序在调用对象时候会自动调用构造,无须手动调用,而且只会调用一次
2. 析构函数语法:~类名(){}
- 析构函数,没有返回值也不写void
- 函数名称于类名相同,在名称前加上符号~
- 析构函数不可以由参数,因此不可以发生重载
- 程序在对象**销毁前*会自动调用析构,无须手动调用,而且只会调用一次
#include <iostream>
using namespace std;
//对象的初始化和处理
class Person
{
public:
//1.构造函数,初始化操作
Person()
{
cout <<"Person 构造函数的调用" <<endl; //如果我们不写,此函数就为空
}
//2.析构函数,进行清理操作
~Person()
{
cout <<"Person的析构函数调用" <<endl;//如果我们不写,此函数就为空
}
};
//小结:构造和析构都是==必须有的实现==,如果我们自己不提供,编译器会提供一个空实现的构造和析构
void test01()
{
Person p; //局部变量,在栈上的数据,test01执行完毕后,释放这个对象
}
int main()
{
test01(); //析构、构造都调用
Person p;// 在全部执行完后,调用析构函数
system("pause");
return 0;
}
二、构造函数的分类及调用
两种分类方式:
- 按参数分为:有参构造和无参构造
- 按类型分为:普通构造和拷贝构造
三种调用方式:
- 括号法
- 显示法
- 隐式转换法
#include <iostream>
using namespace std;
//1. 构造函数的分类及调用
class Person
{
public:
//构造函数,类型有参与无参(默认构造)
Person()
{
cout <<"Person 无参(默认)构造函数的调用" <<endl;
}
Person(int a)
{
age = a;
cout <<"Person 有参构造函数的调用" <<endl;
}
//拷贝构造函数
Person(const Person &p)
{
age = p.age; //将传入人身上的所有属性,拷贝到我自己身上
cout <<"Person 拷贝构造函数的调用" <<endl;
}
~Person()
{
cout <<"Person的析构函数调用" <<endl;//如果我们不写,此函数就为空
}
int age;
};
//调用
void test01()
{
//1.括号发
Person p; //默认构造函数调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
cout << "p2的年龄" << p2.age << endl;
cout << "p3的年龄" << p3.age << endl; //拷贝构造函数复制全部属性
//2.显示法
Person p1;
Person p2 = Person(10); //有参构造
Person p3 = Person(p2); //拷贝构造
//3.隐式转换法
Person p4=10; //相当于写了 Person p4 = Person(10);有参构造
Person p5=p4;//拷贝构造
}
int main()
{
test01();
system("pause");
return 0;
}
注意事项:
- 调用默认构造函数时,不要加(),因为
Person p();
,编译器会认为这是一个函数的声明 - 显示法中,单独拿出右侧对象(eg:Person(10))是匿名对象。特点:当前执行结束后,系统会立刻回收匿名对象
- 不要利用拷贝构造函数 初始化匿名对象,例如编译器会认为Person (p3)等价于Person p3;
1. 拷贝构造函数
C++中拷贝构造函数的调用时机通常由三种情况
- 使用一个已经创建完毕的调用对象来初始化一个新对象
- 值传递的方式给函数参数传值
- 以值方式返回局部对象
- 由于编辑器和版本的不同,地址返回可能会有所不同
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "person 默认构造函数调用"<<endl;
}
Person(int age)
{
cout << "person 有参构造函数调用"<<endl;
m_age=age;
}
Person(const Person &p)
{
cout << "person 拷贝构造函数调用"<<endl;
m_age = p.m_age;
}
~Person()
{
cout << "person 析构函数调用"<<endl;
}
int m_age;
};
//1. 使用一个已经创建完毕的调用对象来初始化一个新对象
void test01()
{
Person p1(20);
Person p2(p1);
cout << "p2的年龄" << p2.m_age << endl;
}
//2. 值传递的方式给函数参数传值
void dowork(Person p)
{
}
void test02()
{
Person p;
dowork(p); //值传递方式,形参不影响实参
}
//3. 以值方式返回局部对象
Person dowork2() //返回值为p1的拷贝对象
{
Person p1;
cout << (int*)&p1 << endl;
return p1;
}
void test03()
{
Person p = dowork2();
cout << (int*)&p << endl;
}
int main()
{
test01();
test02();
test03();
system("pause");
return 0;
}
2. 构造函数的调用规则
默认情况下,C++编译器至少给一个类添加3个函数
- 默认构造函数(无参,函数体为空)
- 默认析构函数(无参,函数体为空)
- 默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下: - 如果用户定义有参构造函数,C++不再提供默认无参构造,但会提供拷贝构造;
- 如果用户定义拷贝构造函数,对属性进行值拷贝,C++不会提供其他构造函数;
3. 深拷贝与浅拷贝
浅拷贝:简单的赋值拷贝操作 ————> 问题:由于栈的先进后出,堆区的重复释放
深拷贝:在堆区重新申请空间,进行拷贝操作 ————> 解决浅拷贝带来的问题
小结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "person 默认构造函数调用"<<endl;
}
Person(int age,int height)
{
m_age=age;
m_height=new int (height)
cout << "person 有参构造函数调用"<<endl;
}
Person(const Person &p)
{
cout << "person 拷贝构造函数调用"<<endl;
m_age = p.m_age;
m_height = p.m_height; //编译器默认实现就是这行代码
//深拷贝操作
m_height=new int (*p.m_height); //重新开辟一块堆区内存
}
~Person()
{
//析构代码,将堆区开辟数据做释放操作
if (m_height != NULL)
{
delete m_height;
m_height = NULL; //防止野指针
}
cout << "person 析构函数调用"<<endl;
}
int m_age; //年龄
int *m_height; //身高,堆区数据
};
void test01()
{
Person p1(18);
cout << "p1的年龄:" << p1.m_age <<"身高为:"<<*p1.m_height<< endl;
Person p2(p1);
cout << "p2的年龄:" << p2.m_age <<"身高为:"<<*p2.m_height<< endl;
}
int main()
{
test01();
system("pause");
return 0;
}