文章目录
- 一.类的默认6个成员函数
- 二.构造函数
- 1.引例
- 2.构造函数的概念及特性
- 三.析构函数
- 😋析构函数的特性
前言:
上篇文章初步认识了类以及类的相关知识,本篇将继续深入学习类与对象——类的默认6个成员函数:
一.类的默认6个成员函数
上一章我们说到,如果一个类中什么成员都没有,那么这个类就叫空类。这么说其实不是很严谨,因为当我们定义好一个,不做任何处理时,编译器会自动生成以下6个默认成员函数:
- 默认成员函数:如果用户没有手动实现,则编译器会自动生成的成员函数:
- 构造函数:主要完成初始化工作
- 析构函数:主要完成清理工作
- 拷贝构造:使用已经存在的一个对象初始化创建另一个对象
- 复制重载:把一个对象赋值给另一个对象
- 取地址重载:普通对象取地址操作
- 取地址重载(
cons
t):const
对象取地址操作
二.构造函数
1.引例
在C语言阶段,我们在使用栈的数据结构的时候,每当创建一个Stack
对象,首先得调用它的初始化函数StackInit
来初始化对象。
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}St;
void StackInit(St* ps);
//……
int main()
{
St s;
StackInit(&s);
return 0;
}
在每次使用前,都要先初始化,这样显得太麻烦而且容易遗忘。在C++
中,构造函数为我们很好的解决了这一问题。
2.构造函数的概念及特性
构造函数是一个特殊的成员函数。构造函数虽然叫构造,但是其主要作用并不是开辟空间创建对象,而是初始化对象。
构造函数具有以下特性:
1. 函数名与类名相同
2. 无返回值
3. 对象实例化时,编译器自动调用对应的构造函数
4. 构造函数可以重载
举例:
class Data
{
public:
// 无参的构造函数
Data()
{}
// 带参的构造函数
Data(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void TestData()
{
// 调用无参构造函数
Data d1;
// 调用带参的构造函数
Data d2(2023, 5, 3);
}
注意:
创建对象时编译器会自动调用构造函数,若是调用无参的构造函数,则无需在对象后加
()
,否则会产生歧义:编译器无法确定是在声明函数还是在创建对象。
错误示例:
// 错误示例
Data d3();
5. 如果类中没有显示定义的构造函数,则C++
编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生成
class Data
{
public:
// 若用户没有显示定义,则编译器自动生成
//Data(int year, int month, int day)
//{
// _year = year;
// _month = month;
// _day = day;
//}
private:
int _year;
int _month;
int _day;
};
6. 默认生成构造函数,对内置成员不作处理,对自定义类型成员,会调用它的默认构造函数
C++
把类型分为内置类型(基本类型)和自定义类型。内置类型就是语言提供的基本数据类型,如:int
,char
,double
……,自定义类型就是我们使用的class
,struct
,union
等自己定义的类型
举例:
默认构造函数对内置类型:
class Data
{
public:
void print()
{
cout << _year << '-' << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestData1()
{
Data d1;
d1.print();
}
int main()
{
TestData1();
return 0;
}
运行结果:
如图所示,默认构造函数未对内置类型做处理。
默认构造函数对自定义类型:
class stack
{
public:
// stack的默认构造函数
stack()
{
cout << "stack" << endl;
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class queue
{
public:
// 不对queue的构造函数做显示定义,测试构造函数
private:
stack _s;
};
void TestQueue()
{
queue q;
}
int main()
{
TestQueue();
return 0;
}
运行结果:
如图所示,在创建queue
对象时,默认构造函数对自定义成员_s
做了处理,调用了它的默认构造函数stack()
- 但是这种容易混淆的使用方式让很多人感到不满,为什么要针对内置类型和自定义类型做不同的处理呢?终于,在
C++11
中针对内置类型成员变量不初始化的缺陷,又打了补丁,即:
7. 内置类型成员变量在类中声明时可以给默认值
示例:
class Date
{
public:
void print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
void TestDate2()
{
Date d2;
d2.print();
}
int main()
{
TestDate2();
return 0;
}
运行结果:
默认值:若不对成员变量作处理,则使用默认值
8. 无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只有一个
示例:
class Date
{
public:
// 1.无参的默认构造函数
// Date()
// {
// ……
// }
// 2.全缺省的默认构造函数
Date(int year = 0, int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
void print()
{
cout << _year << '-' << _month << '-' << _day << endl;
}
private:
int _year = 0;
int _month = 0;
int _day = 0;
};
三.析构函数
析构函数与构造函数的特性相似,但功能恰好相反。构造函数是用来初始化对象的,析构函数是用来销毁对象的。
需要注意的是,析构函数并不是对对象本身进行销毁(因为局部对象出了作用域会自行销毁,由编译器来完成),而是在对象销毁时会自动调用析构函数,对对象内部的资源做清理(如对象
stack
中的int *a
)。
同样,有了析构函数,我们就不用担心创建对象后由于忘记释放内存而造成内存泄漏了。
示例:
class Stack
{
public:
// 构造函数
Stack()
{
}
void push(int x);
bool empty();
int Top();
// 手动释放
void Destory();
private:
// 成员变量
int* _a;
int _top;
int _capacity;
};
void TestStack()
{
Stack s;
s.push(1);
s.push(2);
// 手动释放
s.Destory();
}
😋析构函数的特性
- 析构函数名是在类名前加~
- 无参数
- 无返回值
- 一个类只能有一个析构函数。若未显示定义,系统会自动生成默认的析构函数
- 析构函数不能重载
示例:
class Date
{
public:
// 构造函数
Date()
{
cout << "Date()" << endl;
}
// 析构函数
~Date()
{
cout << "~Date()" << endl;
}
private:
int _year;
int _month;
int _day;
};
void TestDate3()
{
// d3生命周期结束时自动调用构造函数
Date d3;
}
int main()
{
TestDate3();
}
运行结果:
6.编译器生成的默认析构函数,对自定义类型调用它的析构函数
示例:
class Stack
{
public:
// 构造函数
Stack()
{
cout << "stack()" << endl;
_a = nullptr;
_top = _capacity = 0;
}
// 析构函数
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
class queue
{
public:
// 不对queue的构造函数和析构函数做显示定义
// 测试默认构造函数和默认析构函数
private:
Stack _s;
};
void TestQueue1()
{
queue q;
}
int main()
{
TestQueue1();
return 0;
}
运行结果:
- 这里与构造函数的区别是:编译器默认生成的析构函数不像构造函数那样区分内置类型与自定义类型
这是因为:内置类型在生命周期结束时会自动销毁。而自定义类类型里可能有动态开辟的空间,因此要进行清理工作。
因此:
7.如果类中没有申请资源时,析构函数可以不写,直接用编译器生成的默认析构函数,比如Date
类;有资源申请时,一定要写,否则会造成资源泄露,比如Stack
类
本文到此结束,码文不易,还请多多支持哦!!