此篇继续讲解类和对象的提高知识,包括默认成员函数、运算符重载等相关内容,没有看过 C++基础知识
和类和对象基础知识
(可翻阅我的文章)的小伙伴,可以先收藏,之后学习完再看效果会更好哦。
默认成员函数
在一个空类中并不是什么都没有,编译器会调用六个默认成员函数
1)构造函数
我们常常会忘记调用Init函数
,导致没有初始化,而如果使用构造函数初始化
就不会出现这种现象,因为编译器会自动调用。怎么样,体会到 C++ 的快乐了吧。
注意:构造函数不是开空间创建
对象,而是初始化
对象
【特性】
- 函数名和类名相同,且无返回值
- 对象实例化时编译器自动调用
- 可以重载
【实例对比】
①老版本
class Date
{
public:void Init(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << _year << "-" << _month << "-" << _day << endl;}private:int _year;int _month;int _day;
};int main()
{Date d1;d1.Init(2024, 2, 26);d1.Print();// 弊端就是会忘记Initreturn 0;
}
②新版本
class Date
{
public:// 1.无参构造函数Date(){}// 2.带参构造函数Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1; // 调用无参构造函数Date d2(2024, 2, 26); // 调用带参的构造函数//Date d3(); //错误,此处是为了避免跟函数声明混淆
}
【注意】:如果显式调用,编译器就不会生成,否则会生成无参的默认构造函数
(注意和默认成员函数
区分)
PS:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数,并且默认构造函数只能有一个。
Q:有小伙伴觉得这个系统调用的默认构造有什么用?对象的成员变量不还是随机值吗?
对于内置类型
(语言提供的数据类型,eg. int/char)不作处理,在C++11中可以在类声明时给默认值;而对于自定义类型
(eg. class/struct/union),会去调用各自的构造函数,如果不具备对应的默认构造函数则会报错。
2)析构函数
同样的,Destroy也是经常容易忘记的操作,而在C++中,引入析构函数
来实现自动清理对象中的资源(妈妈再也不用担心我会忘记Destroy了)
注意:不是对对象本身的销毁,销毁
工作是由编译器完成的,析构的作用是在销毁时完成对象中资源的清理
工作。
跟构造函数类似,对于每个成员变量,内置类型
最后系统会将其内存回收,而自定义类型
需要调用它的析构函数。
【示例】
#include<iostream>
using namespace std;typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 3){// 有资源申请_array = (DataType*)malloc(sizeof(DataType) * capacity);if (NULL == _array){perror("malloc申请空间失败!");return; }_capacity = capacity;_size = 0; }void Push(DataType data){_array[_size] = data;_size++; }// ~类名() 这种表达方式为析构函数~Stack() {if (_array) {free(_array);_array = NULL;_capacity = 0;_size = 0;} }
private:DataType* _array;int _capacity;int _size;
};void TestStack()
{Stack s;s.Push(1);s.Push(2);
}
【注意】
(1) 如果类中没有申请资源时,析构函数可以不写,直接使用编译器生成的默认析构函数,比如 Date类;
有资源申请时,一定要写,否则会造成资源泄漏,比如上面的Stack类。
(2)后定义的先析构;
析构顺序:局部对象(后定义先析构) → 局部静态 → 全局变量(后定义先析构)
(3)析构函数不能重载
3)拷贝构造函数
它的作用就如名字一样,用来拷贝的,只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
【示例】
class Date
{
public:// 拷贝构造,是构造函数的一个重载形式Date(const Date& d){_year = d._year;_month = d._month;_day = d._day;}Date(int year, int month, int day){_year = year;_month = month;_day = day;}private:int _year;int _month;int _day;
};void TestDate()
{Date d1(2024, 2, 26); Date d2(d1); // 拷贝构造
}
【注意】
(1)拷贝构造的参数只能是类对象的引用
,否则会引发无穷递归。因为在传值传参的时候,会形成一个新的拷贝构造,然后就再调用拷贝构造,无穷递归下去了。
(2)并且是const类的,避免对原先的对象作出修改。
(3)如果没有显式定义,编译器会生成默认的拷贝构造函数,其中的内置类型
成员按内存存储的字节序拷贝,自定义类型
成员会调用它的拷贝构造,这个过程称为浅拷贝
或值拷贝。
【重点】
一般来说,如果没有涉及资源申请(动态内存开辟),拷贝构造可写可不写,但是一旦涉及资源申请就必须写了。
如果不写拷贝构造,会调用默认的拷贝构造,则会将原对象的内容按值拷贝,其中的指针会指向同一块空间。
而在销毁时,其中一个对象已经释放对应地址的空间,而后释放的对象会对相同的位置进行释放,导致程序崩溃。
4)赋值运算符重载
引入:C++为了自定义类型也可以像内置类型那样使用运算操作符,增强代码的可读性,引入了运算符重载,运算符重载是具有特殊函数名的函数。
使用方式:返回值类型 operator操作符(参数列表)
【注意事项】
- 不能创造新的操作符,例如operator@
- 内置类型的运算符不可改变含义
- .* :: sizeof ?: . 这五个运算符不可重载
【示例】以日期类的小于和等于符号作例子
bool Date::operator==(const Date& d)
{return _year == d._year&& _month == d._month&& _day == d._day;
}bool Date::operator<(const Date& d)
{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month)return true;else if (_month == d._month){if (_day < d._day)return true;}}return false;
}
其实在实现了这两个运算符重载之后,其他的就可以复用已经重载过的了
bool Date::operator<=(const Date& d)
{return *this < d || * this == d;
}
// 是不是很爽bool Date::operator>(const Date& d)
{return !(*this <= d);
}bool Date::operator>=(const Date& d)
{return !(*this < d);
}bool Date::operator!=(const Date& d)
{return !(*this == d);
}
赋值运算符重载
是一种特殊的运算符重载,属于默认成员函数,也就是说不显式写,编译器也会自动调用。与拷贝构造类似,当没有涉及资源配置时,可写可不写,但如果涉及资源配置必须写。
【示例】
Date& operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
【注意点】
- 参数是本类对象的引用
- 赋值运算符只能重载成类的成员函数不能重载成全局函数,因为赋值运算符如果不显式实现,编译器会生成一个默认的,全局的赋值重载和编译器在类中生成的默认赋值重载冲突了。
拷贝构造
是用一个已经存在的对象去初始化一个新的对象,而赋值运算符重载是两个已经存在的对象进行赋值操作。
关于日期类的运算符重载我会单独写一篇博客详解,敬请期待!
5)取地址操作符重载
class Date
{
public :Date* operator&(){return this;}const Date* operator&() const{return this;}
private :int _year ; int _month ;int _day ;
}
这两个一般不需要重载,用编译器自动调用的就好,所以不用深入学习。
如果对你有帮助的话,不妨点个赞鼓励一下博主~