构造函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有初始值,并且在对象的生命周期内只调用一次。它并不开空间创建对象,而是初始化对象。
- 特性:
#include<iostream>
using namespace std;
class Date {
private:
int _years, _month, _day;
public:
Date(int year, int month, int day) { //构造函数,创建类类型对象时由编译器自动调用,不能和全缺省三个参数共存
_years = year;
_month = month;
_day = day; //没有返回值
}
Date() { //可重载,但调用上有歧义且不能和全缺省三个参数共存
_years = 2020;
_month = 2;
_day = 2;
}
Date(int year) { //可缺省
_years = year;
_month = 3;
_day = 3;
}
void Init(int year, int month, int day) { //有构造函数就无需再初始化了
_years = year;
_month = month;
_day = day;
}
void Print() {
cout << _years << "-" << _month << "-" << _day << endl;
}
};
int main() {
Date d1(2010, 10, 10); //并不是开空间创建对象,而是初始化对象
d1.Print();
//Date d1(2010, 10, 10); 无法二次调用
Date d2; //可重载,没有参数不能加"()"
d2.Print();
Date d3(2020); //缺参数
d3.Print();
d2.Init(2010, 12, 06);
d2.Print();
system("pause");
return 0;
}
2. 全缺省构造:(最常用)
#include<iostream>
using namespace std;
class Date {
private:
int _years, _month, _day;
public:
Date(int year = 2019, int month = 9, int day = 9) {
_years = year;
_month = month;
_day = day;
};
void Print() {
cout << _years << "-" << _month << "-" << _day << endl;
};
};
int main() {
Date d1;
Date d2(2020);
Date d3(2020,2);
Date d4(2020, 2, 2);
d1.Print();
d2.Print();
d3.Print();
d4.Print();
return 0;
}
3. 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数。
析构函数
与构造函数功能相反,析构函数负责清理而不是完成对象的销毁,对象销毁工作是由系统完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。
- 特性:
#include <iostream>
using namespace std;
class Date {
public:
Date(int year = 2020, int month = 2, int day = 2) {
_year = year;
_month = month;
_day = day;
}
~Date() {} //Date类没有资源需要清理,所以析构函数类什么都不用写
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
class Stack {
public:
Stack(int n = 10) {
_a = (int*)malloc(sizeof(int)*n);
_size = 0;
_capacity = n;
}
~Stack() { //Stack不写析构,会内存泄漏
free(_a);
_size = _capacity = 0;
_a = nullptr;
}
private:
int* _a;
int _size;
int _capacity;
};
int main() {
Date d1;
d1.Print();
Stack st(100); //C++类使用构造函数和析构函数来解决忽视初始化和清理问题
return 0;
}
- 默认析构:
#include <iostream>
using namespace std;
class Time {
public:
~Time() {
cout << "~Time()" << endl;
}
};
class Date {
public:
Date(int year = 2020, int month = 2, int day = 2) {
_year = year;
_month = month;
_day = day;
}
void Print() { //内置类型不处理,但要处理自定义类型,调用它Time的析构函数
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
int main() {
Date d;
d.Print();
system("pause");
return 0;
}
注: 先构造的后析构(栈帧),先局部后全局。
拷贝构造
拷贝构造函数是构造函数的一个重载形式。拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。若未显示定义,系统生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 2020, int month =2, int day = 2) {
_year = year;
_month = month;
_day = day;
}
//Date copy(d1); 无穷递归
Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1); //这里d2调用的默认拷贝构造完成拷贝,d2和d1的值也是一样的。
return 0;
}
赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:operator[操作符];
函数原型:返回值类型 operator[操作符] (参数列表)
5个不能重载的运算符: .* 、:: 、sizeof 、?: 、. 。
#include <iostream>
using namespace std;
class Date {
public:
int _year;
int _month;
int _day;
Date(int year = 2020, int month = 2, int day = 2) {
_year = year;
_month = month;
_day = day;
}
void Print() {
cout << _year << "-" << _month << "-" << _day << endl;
}
};
//参数为自定义类型的对象传参尽量用引用,提高效率
bool CompareEquelDate(const Date& d1, const Date& d2) { //bool型,真则返回1否则为0
return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
bool operator>(const Date& d1, const Date& d2) { //C++解决方式:运算符重载,可读性比较强
return d1._year > d2._year || d1._month > d2._month || d1._day > d2._day;
}
bool operator==(const Date& d1, const Date& d2) {
return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day;
}
bool operator<(const Date& d1, const Date& d2) {
return d1._year < d2._year || d1._month < d2._month || d1._day < d2._day;
}
int main() {
Date d1(2020, 3, 13);
Date d2(2020, 3, 12);
Date d3(2020, 3, 14);
cout << CompareEquelDate(d1, d2) << endl;
cout << operator==(d1, d2) << endl; //一般不这么用,没有可读性
cout << (d1 == d2) << endl; //0,编译遇到d1==d2会转化成operator==(d1,d2)
cout << (d1 > d2) << endl; //1
cout << (d3 < d2) << endl; //0
//运算符重载的意义是,可以让自定义类型像内置类型一样去使用我们的运算符,增强程序的可读性
return 0;
}
赋值运算符
与处理拷贝构造函数一样,如果一个类没有定义自己了拷贝赋值运算符,则编译器会为它生成一个合成拷贝赋值运算符。类似拷贝构造函数,对于某些类,合成拷贝赋值运算符用来禁止该类型对象的赋值。如果拷贝赋值运算符并非出于此目的,它会将右侧运算对象的每个非static成员赋予左侧运算对象的对应成员,这一工作是通过成员类型的拷贝赋值运算符来完成的。对于数组类型的成员,逐个赋值数组元素。合成拷贝赋值运算符返回一个指向其左侧运算对象的引用。
特点: 参数类型,返回值,检查自身赋值,返回*this
#include<iostream>
using namespace std;
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d) {
if (this != &d){
_year = d._year;
_month = d._month;
_day = d._day;
}
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1;
Date d2(2019, 2, 20); //这里d1调用的编译器生成operator=完成拷贝,d2和d1的值也是一样的。
Date d3 = d2; //注意:直接调用拷贝构造而不是调用operator
d1 = d2;
return 0;
}
const成员
将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。
#include<iostream>
using namespace std;
class Date {
public:
/*void Display() {
cout << "Display ()" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}*/
void Display() const { //在成员函数内保护调用对象不被修改
cout << "Display () const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main() {
Date d1;
d1.Display();
const Date d2;
d2.Display();
}
- const对象和成员函数都不可以调用非const成员函数吗;
- 非const对象和成员函数都可以调用const成员函数吗。
const Date* -->const Date*
Date* -->Date*
Date* -->const Date*
const Date* -X>Date*
取地址及const取地址操作符重载: 这两个默认成员函数一般不用重新定义 ,编译器默认会生成
只有在想让他人获取到指定的内容时才使用。