目录
类的6个默认成员函数
构造函数
析构函数
拷贝构造函数
运算符重载
赋值运算符重载
const成员函数
取地址及const取地址操作符重载
如果一个类中什么成员都没有,简称空类
空类并不是什么都没有,任何类什么都不写时,编译器会自动生成以下6个默认成员函数
默认成员函数:用户没有显式实现,编译器会自动生成的成员函数称为默认成员函数
C++的很多函数是为了填C语言的坑,比如用C语言写的栈和队列,定义的变量经常忘记初始化就拿来用,导致程序崩溃;甚至调用未初始化的函数使得变量被制成随机值
于是有了构造函数,保证对象一定被初始化
特性:
构造函数是特殊的成员函数,虽然名字叫构造,但主要任务并不是开空间创造对象,而是
初始化对象
无参的构造函数和全缺省的构造函数都称为默认构造函数, 并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数
构造函数的定义
class Date
{
public:
Date(int year, int month, int day) //带参构造函数
{
_year = year;
_month = month;
_day = day;
}
Date() //无参构造函数
{
_year = 1994;
_month = 8;
_day = 8;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 7, 28); //调含参构造函数
Date d2; //调无参构造函数
//无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
}
如果传一个或两个参数,就要重新定义一个构造函数
这时定义一个全缺省构造函数就会比较方便
如果类中没有定义构造函数, 则C++编译器会自动生成一个无参的默认构造函数, 一旦用户显式定义编译器将不再生成
C++类型分类:
内置类型 / 基本类型: int / double / char/指针等等
自定义类型: struct / class
默认生成构造函数:1.内置类型成员不做处理
2.自定义类型成员会去调用他的默认构造函数
默认生成构造函数内置类型成员不做处理
但你可以给定一个值
这并不是初始化而是缺省值
概念
通过前面构造函数的学习, 我们知道一个对象是怎么来的, 那一个对象又是怎么没呢的?
析构函数: 与构造函数功能相反, 析构函数不是完成对对象本身的销毁, 局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数, 完成对象中资源的清理工作
析构函数是特殊的成员函数, 其特征如下:
对于日期类我们没有什么资源需要清理,可以不用显示写的,默认生成的析构函数就够用,现在实现一个栈的析构函数;
因为栈我们是用数组实现的,我们先需要向堆申请一块空间,最后对象销毁时,堆上的空间并没有释放,如果没有释放会造成内存泄漏,所以需要我们实现一个析构函数去完成这块的清理工作:
//析构函数先定义的后析构,后定义的先析构
class Stack
{
public:
// 栈的构造函数
Stack(int capacity = 4)
{
cout << "Stack(int capacity = 4)" << endl;
_a = (int*)malloc(sizeof(int) * capacity);
assert(_a);
_top = 0;
_capacity = capacity;
}
// 栈的析构函数
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
private:
int* _a;
int _top;
int _capacity;
};
默认生成的析构函数的特点:
跟构造函数类似:a、内置类型不处理 b、自定义类型成员会去调用它的析构
总结,如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如日期类;有资源申请时,一定要写,否则会造成资源泄漏,比如Stack类
C c;
int main()
{
A a;
B b;
static D d;
}
程序析构顺序:B A D C
分析:1、类的析构函数调用一般按照构造函数调用的相反顺序进行调用,但是要注意static对象的存在,因为static改变了对象的生存作用域,需要等待程序结束时才会析构释放对象
2、全局对象先于局部对象进行构造
3、局部对象按出现顺序构造,无论是否为static
4、所以构造顺序为C A B D
在创建对象时,我们可以创建一个与已存在的对象一模一样的新对象
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用 const 修饰),在用已存在的类类型对象创建新对象时由编译器自动调用
拷贝构造函数也是特殊的成员函数,其特征如下:
class Date
{
public:
// 构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
Date(Date d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 8, 3);
Date d2(d1);
}
上面代码的拷贝构造函数,如果使用传值的方式,
调用拷贝构造要先传参,而传参又是一个拷贝构造,层层递归,无穷无尽
解决方式:加引用
// 拷贝构造函数
Date(const Date& d) //注意加const防止被修改
{
_year = d._year;
_month = d._month;
_day = d._day;
}
若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝
注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的
浅拷贝的两大问题
- 一个对象修改会影响另一个对象
- 会析构两次,程序崩溃
解决方式:自己实现深拷贝
C++为了增强代码的可读性引入了运算符重载, 运算符重载是具有特殊函数名的函数
也具有其返回值类型,函数名字以及参数列表, 其返回值类型与参数列表与普通的函数类似
函数名字为: 关键字operator后面接需要重载的运算符符号
函数原型: 返回值类型 operator操作符(参数列表)
注意:
int main()
{
Date d1(2023, 8, 3);
Date d2(2023, 8, 4);
d1 == d2;
d1 < d2;
d1++;
d1 + 100;
}
定义两个日期类对象,但是现在的日期类对象并不好用
比如判断两个日期是否相等,谁大谁小,日期自增,计算100天后是哪天
它并不支持==,<,++,+等运算符的运算
这是因为内置类型可以直接使用运算符运算,编译器知道如何运算
而自定义类型则无法直接使用运算符,因为编译器也不知道如何运算
想支持可以自己实现一个运算符重载
实现一个日期类比较相等和相减
class Date
{
public:
// 构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& x) //因为有隐藏指针,为了参数匹配,所以只能传一个参数
{
return _year == x._year
&& _month == x._month
&& _day == x._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 8, 3);
Date d2(2023, 8, 4);
cout << d1.operator == (d2) << endl;
}
private里面的成员是私有的,在外面不能访问
可以直接把函数写到类里面
1.赋值运算符重载格式
注意: 赋值运算符只能重载成类的成员函数不能重载成全局函数,因为重载成全局的,类里面又会自动生成一个,这样编译器就无法分辨
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
注意:内置类型成员变量是直接赋值的, 而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值
下面是日期类赋值运算符重载:
Date& Date::operator=(const Date& d)
{
if (this != &d) //this是第一个操作数的地址,如果两个操作数地址一样就跳过赋值过程
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this; //返回值为了支持连续赋值
}
Date d1(2023, 8, 10); // 构造函数
Date d2=d1; // 拷贝构造函数,并不是调用的赋值运算符重载函数
Date d3; // 构造函数
d3=d2; // 赋值运算符重载函数
拷贝构造和赋值运算符重载的区别
用一个已存在的对象去初始化另一个对象时,即使写的是 = ,此时也是调用它的拷贝构造函数
已经存在的两个对象之间赋值拷贝,此时是赋值运算符重载
int main()
{
Date d1(23, 8, 10);
// 拷贝构造
Date d2 = d1;
Date d2(d1)
// 赋值运算符重载
d2 = d1;
return 0;
}
// ++d1;
// d1++;
// 直接按特性重载,无法区分
// 特殊处理,使用重载区分,后置++重载增加一个int参数跟前置构成函数重载进行区分
Date& operator++(); // 前置
Date operator++(int); // 后置
Date& operator--(); // 前置
Date operator--(int); // 后置
Date operator++(int),这里的int只起到占位的作用,不会作为参数使用
const修饰的类成员函数称为const成员函数,实际修饰的是该成员函数隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改
// void Date::Print(const Date* const this)
void Date::Print() const
{
cout << _year << "/" << _month << "/" << _day << endl;
}
这两个默认成员函数大多数情况下都不需要自己写,默认生成的就够用