一、对象的初始化和清理
-
C++中的面向对象来源于生活,每个对象也都会有初始设置以及对象销毁前的清理数据的设置,对象的初始化和清理也是两个非常重要的安全问题
- 一个对象或者变量没有初始状态,对其使用后果是未知的
- 使用完一个对象或变量,没有及时清理,也会造成一定的安全问题
- 例如:生活中我们买的电子产品基本都会有出厂设置,在某一天我们不用时也会删除掉自己的信息数据以保证隐私
-
每个对象与其他对象的区别:
- 外在区别即对象名称
- 内在区别则是对象自身属性值,即数据成员的值
-
C++利用构造函数和析构函数解决上述问题,这两个函数会被编译器自动调用,完成对象初始化和清理工作
- 对象的初始化和清理工作是编译器强制要我们做的事情,如果没有提供构造和析构,编译器会提供该函数的空实现
- 构造函数:创建对象时,为对象的成员属性赋值
- 析构函数:对象销毁前,系统自动调用,执行一些清理工作
二、构造函数(constructor)
2.1 构造函数的定义形式
类名(参数说明){
初始化代码
}
2.2 构造函数的特点
- 构造函数的名字必须与类的名字相同;
- 定义构造函数时不能指定返回类型;
- 构造函数的代码与其他函数一样,但一般不直接调用(不显示调用);
- 创建类的一个新对象时,会隐式地自动调用构造函数。即当程序中声明一个新对象时,程序会自动调用该对象所属类中定义的构造函数来初始化这个对象的状态;
- 若在定义类时没有定义构造函数,C++会自动为该类创建一个缺省(default)的构造函数。该缺省构造函数没有任何形式参数,且函数体为空。
例子:
# include <iostream>
using namespace std;
// 对象的初始化和清理
// 构造函数 进行初始化操作
// 析构函数,进行清理操作
class Person {
public:
//定义构造函数,编译器自动调用
Person() {
cout << "Person 构造函数调用" << endl;
}
//定义析构函数,自动调用
~Person() {
cout << "Person 析构函数调用" << endl;
}
};
int main() {
Person p;
}
2.3 有参构造函数和无参构造函数
- 构造函数定义中,在冒号之后、函数体之前是初始化列表
- 有参构造函数:定义构造函数时带有形参,那么建立对象时必须要给出初始值,用于作为调用构造函数时的实参
- 调用时可以不需要参数的构造函数都是无参构造函数,也称为默认构造函数
- 当不定义构造函数时,编译器自动产生默认构造函数
- 在类中可以自定义无参数的构造函数,也是默认构造函数
- 全部参数都有默认形参值的构造函数也是默认构造函数
- 一个系统只能有一个默认构造函数
示例:
#include <iostream>
using namespace std;
class Clock{
public:
// 定义了两个Clock构造函数,即为构造函数的重载
Clock(int newH, int newM, int newS); // 定义有参构造函数
Clock(); // 定义无参构造函数
/*
// 每个参数都有默认值,属于默认构造参数
Clock(int newH = 0, int newM = 0, int newS = 0);
调用方式:
Clock c1(8, 30, 30);
Clock c2(8);
Clock c3; // 这种方式也可以使用Clock()定义无参构造函数,因此类中不可同时存在这两种构造函数的定义方式
*/
void setTime(int newH, int newM, int newS);
void showTime();
private:
int hour, minute, second;
};
// 有参构造函数的实现
// 冒号之后,函数体之前部分称为 初始化成员列表
// 将初始化传入的三个数据newH、newM、newS的值分别给hour、minute、second
Clock::Clock(int newH, int newM, int newS):hour(newH), minute(newM), second(newS){
}
/* 等价于
Clock::Clock(int newH, int newM, int newS){
hour = newH;
minute = newM;
second = newS;
}
*/
// 无参构造函数的实现
Clock::Clock(): hour(1), minute(1), second(1){
}
/* 等价于
Clock::Clock(){
hour = 1;
minute = 1;
second = 1;
}
*/
// 成员函数
void Clock::setTime(int newH, int newM, int newS){
hour = newH;
minute = newM;
second = newS;
}
void Clock::showTime(){
cout << hour << ":" << minute << ":" << second << endl;
}
int main(){
Clock myClock1; // 定义对象,此时自动调用无参构造函数
myClock1.showTime();
Clock myClock2(8, 3, 30); // 定义对象,此时自动调用有参构造函数
myClock2.showTime();
return 0;
}
2.4 复制构造函数
-
具有一般构造函数的所有特性,其形参是本类的对象的引用
-
作用:使用一个已经存在的对象,该对象由复制构造函数的形参指定,去初始化 同类 的一个新对象
-
语法:
class 类名 { public: 类名(形参表); // 构造函数 类名(类名 &对象名); // 复制构造函数 ... }; // 复制构造函数的定义 类名::类名(类名 &对象名){ }
-
类内声明,类外实现
2.4.1 复制构造函数的三种使用情形
-
定义一个对象时,以本类另一个对象作为初始值
Point a(10, 20); Point b = a;
-
传参
void fun1(Point p){ cout << p.getX() << endl; } // 调用语句 fun1(b);
-
函数值返回
Point fun2(){ return Point(1, 2); } // 调用语句 b = fun2();
三、析构函数(destructor)
3.1 析构函数的定义形式
~类名() {
}
3.2 析构函数的特点
- 析构函数的名字必须是在类名前加上一个波纹号“~”,以区别于构造函数;
- 定义析构函数时也不能指定返回类型;
- 在对象消亡时,隐式地自动调用析构函数。即当程序中一个对象作用结束时,程序会自动调用该对象所属类中定义的析构函数来清除这个对象所占的存储空间;
- 若在定义类时没有定义析构函数,C++会自动为该类创建一个缺省的析构函数。这个缺省析构函数没有任何形式参数,且函数体为空。
3.3 析构函数的实现
#include <iostream>
using namespace std;
class DemoClass{
public:
DemoClass(int i); // 函数名与类名一样,因此是构造函数
~DemoClass(); // 函数名与类名一样,且开头有~,因此是析构函数
};
// 构造函数的实现
DemoClass::DemoClass(int i){
cout << "Initial value is" << i << endl;
}
// 析构函数的实现
DemoClass::~DemoClass(){
cout << "destructor" << endl;
}
void main(){
DemoClass obj(30); // 创建对象,同时自动调用构造函数DemoClass()
cout << "This is the end main()" << endl;
return; // 函数执行结束,创建的对象生命结束,自动调用析构函数~DemoClass()
}
四、类对象作为类成员
- C++类中的成员可以是另一个类的对象,称该成员为对象成员
- 例如:B类中有对象A作为成员,A为对象成员
class A{}
class B{
A a;
}
- 创建B对象时,A与B的构造和析构的顺序:
- 构造顺序:先调用对象成员的构造,再调用本类的构造
- 析构顺序:与构造顺序相反
# include <iostream>
# include <string>
using namespace std;
// 手机类
class Phone {
public:
Phone(string name) {
m_PhoneName = name;
cout << "Phone构造" << endl;
}
~Phone() {
cout << "Phone析构" << endl;
}
string m_PhoneName;
};
// 人类
class Person {
public:
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;
}
string m_Name; // 姓名
Phone m_Phone; // 手机,手机类对象
};
void test01() {
// 当类中成员是其他类对象时,我们称该成员为 对象成员
// 构造的顺序是:先调用对象成员的构造,再调用本类构造
// 析构的顺序与构造相反
Person p("张三", "苹果X");
p.playGame();
}
int main() {
test01();
}