C++知识点更多,文章更用来查缺补漏更好,希望对你们有帮助
如果一个类中什么成员都没有,简称为空类。
但是类里面并不是什么都没有,实际上,在什么都不写的情况下,编译器会产生6个默认成员函数
默认成员函数:默认成员函数可以是自己创建的,也可以是编译器默认生成的成员函数
举例(以 构造函数 为例)
class A { A() //不带参数 { ; } A(int x = 4) //全缺省参数 { ; } };
注意事项:
类在实例化的时候一定会调用默认成员函数
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用(即实例化的时候会调用),以保证每个数据成员都有 一个合适的初始值,并且在对象整个生命周期内只调用一次。
1. 函数名与类名相同。
2. 无返回值。 (void 也不用写)
3. 对象实例化时编译器自动调用对应的构造函数。
4. 构造函数可以重载。
代码举例
#include
using namespace std; class Data { public: Data(int year = 2, int month = 2, int day = 2) { _year = year; _month = month; _day = day; cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Data t; return 0; } 运行结果:
分析:
我们可以看到在实例化的时候确实调用了默认成员函数
疑问:
为什么不可以写成 Data t() ?
因为这样编译器无法辨别它是构造函数还是一个函数的声明(返回类型:Data ,函数名:t , 参数:无参)
#include
using namespace std; class Data { public: Data(int year = 2, int month = 2, int day = 2) { _year = year; _month = month; _day = day; cout << _year << "年" << _month << "月" << _day << "日" << endl; } Data() { ; } private: int _year; int _month; int _day; }; int main() { Data t; return 0; } 这两个成员函数不构成重载函数,编译器无法确定初始化的时候调用哪个函数
#include
using namespace std; class Data { public: Data(int year = 2, int month = 2, int day = 2) { _year = year; _month = month; _day = day; cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Data t(2024,1,5); return 0; } 运行结果:
默认构造函数:
如果类中没有构造函数,则C++编译器会自动生成一个无参的默认构造函数
编译器生成的默认构造函数对内置类型(如 int , char ,int * ... ...)不做处理(即得到的还是随机值), 对自定类型(如 calss, union ,struct ......)会自动调用它的构造函数
代码举例
#include
using namespace std; class Data { public: void Print() { cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; int main() { Data t; t.Print(); return 0; } 运行结果:
这里可以看出对 内置类型 没有做任何处理
#include
using namespace std; class Data { public: Data(int year = 0, int month = 0, int day = 0) { _year = year; _month = month; _day = day; cout << _year << "年" << _month << "月" << _day << "日" << endl; } private: int _year; int _month; int _day; }; class A { Data t1; Data t2; }; int main() { A t3; return 0; } 运行结果:
很明显:实例化对象 t1 , t2 的时候,同时也调用了 它的默认构造函数
注意事项:
C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值。
代码举例
#include
using namespace std; class Data { public: Data(int year = 0, int month = 0, int day = 0) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; class A { public: Data t1; Data t2; int size = 0; }; int main() { A t3; cout << t3.size << endl; return 0; } 运行结果:
析构函数不是完成对对象本身的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作。
1. 析构函数名是在类名前加上字符 ~。
2. 无参数无返回值类型。 (void 也不用写)
3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
4. 对象生命周期结束时,C++编译系统系统自动调用析构函数
代码举例
using namespace std; class stack { public: stack(int n = 4) { int* pa = (int*)malloc(sizeof(int) * n); if (pa == NULL) { perror("malloc fail"); return; } _a = pa; _top = 0; _capacity = 4; cout << "已开辟4字节空间大小" << endl; } ~stack() { free(_a); _a = NULL; cout << "数组已经销毁了" << endl; } private: int* _a; int _top; int _capacity; }; int main() { stack st; return 0; }
运行结果:
编译器生成的默认析构函数对内置类型(如 int , char ,int * ... ...)不做处理, 对自定义类型(如 class, union ,struct ......)会自动调用它的析构函数
代码举例
#include
using namespace std; class stack { public: stack(int n = 4) { int* pa = (int*)malloc(sizeof(int) * n); if (pa == NULL) { perror("malloc fail"); return; } _a = pa; _top = 0; _capacity = 4; cout << "已开辟4字节空间大小" << endl; } ~stack() { free(_a); _a = NULL; cout << "数组已经销毁了" << endl; } private: int* _a; int _top; int _capacity; }; class ST { stack t1; stack t2; }; int main() { ST t3; return 0; } 运行结果:
注意事项:
举例
#include
using namespace std; class stack { public: ~stack() { free(a); a = nullptr; } private: int* a = (int*)malloc(sizeof(int) * 4); }; int main() { stack t1; stack t2; return 0; } 销毁时,先调用的是后面的析构函数(先t2 , 再t1)
拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰,是为了防止改变形参的值),在用已存在的类类型对象创建新对象时由编译器自动调用。
举例
#include
using namespace std; class Data { public: Data(int year = 0,int month = 0,int day = 0) { _year = year; _month = month; _day = day; } Data(Data& d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year; int _month; int _day; }; int main() { Data t1; Data t2(t1); return 0; }
1. 拷贝构造函数是构造函数的一个重载形式。
2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,(使用传值方式编译器直接报错,因为会引发无穷递归调用)
注意事项:
传值传参时,如果参数是内置类型,如果直接拷贝给形参,但是如果是自定义类型,需要先调用形参的拷贝构造函数(编译器无法承担直接拷贝自定义类型变量的后果)
原因:
对于栈,如果直接拷贝,就会是这样:
如果调用默认析构函数,同一块空间就会被释放两次,所以直接拷贝自定义函数是行不通的
分析(对于参数不是类对象的引用 错误在哪)
class Data
{
pubilc:
Data(int year = 0 , int month = 0, int day = 0)
{
_year = year;
_month = month;
_day = day;
}
Data(Data d)
{
_year = d.year;
_month= d.month;
_day = d.day;
}
private:
int _year;
int _month;
int _day;
}
int mian()
{
Data d1;
Data d2(d1);
return 0;
}
如果没有自己写的拷贝函数,调用拷贝构造函数调用的是 编译器生成的默认拷贝构造函数 (对传值会浅拷贝)
代码举例
#include
using namespace std; class Data { public: Data(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; int _month; int _day; }; int main() { Data t1(2023,1,8); Data t2(t1); t2.Print(); return 0; } 运行结果:
调用的是编译器自己生成的默认拷贝构造函数(即浅拷贝)
但某些情况下,我们需要自己去写拷贝构造函数,完成深拷贝(如:写栈的时候,不能浅拷贝,因为 定义的 int *a 如果是浅拷贝,则类定义的两个对象的成员变量a都是指向同一块空间)
运算符重载是具有特殊函数名的函数 ,可以帮助实现自定义类型的大小比较
函数名字为:关键字operator后面接需要重载的运算符符号。(如:operator+)
注意事项:
代码举例
#include
using namespace std; class Data { public: Data(int year = 0,int month = 0,int day = 0) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } bool operator ==(Data& d) { return _year == d._year && _month == d._month && _day == d._day; } bool operator >(Data& d) { return _year > d._year || ((_year == d._year) && (_month > d._month)) || ((_year == d._year) && (_month == d._month) && _day > d._day); } bool operator >=(Data& d) { return *this == d || *this > d; } bool operator <(Data& d) { return !(*this >= d); } bool operator <=(Data& d) { return *this == d || *this < d; } private: int _year; int _month; int _day; }; int main() { Data t1(2023,1,8); Data t2(2023,1,8); if (t1 == t2) { printf("相等\n"); } return 0; } 运行结果:
将一个类定义的对象的值赋给另一个对象
代码举例
#include
using namespace std; class Data { public: Data(int year = 0,int month = 0,int day = 0) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } Data& operator=(const Data& d) { _year = d._year; _month = d._month; _day = d._day; return *this; } private: int _year; int _month; int _day; }; int main() { Data t1(2023,1,8); Data t2(2023,1,9); Data t3; t3 = t1 = t2; t1.Print(); t3.Print(); return 0; } 运行结果:
分析:
为了能够连续赋值(参考内置类型的连续赋值:int i = j = k ,从右往左运行,把 k 赋值给了 j ,返回 j 的值,再把 j 赋值给 i ,返回 i 的值)
注意事项:
和编译器默认生成的拷贝构造函数一样,都是浅拷贝(逐字节的拷贝)
代码举例
#include
using namespace std; struct Data { public: Data(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Data t1(2024, 1, 12); Data t3; t3 = t1; t3.Print(); return 0; } 运行结果:
前置-- 和 后置--实现类似
代码举例
#include
#include using namespace std; struct Data { public: Data(int year = 1, int month = 1, int day = 1) { if (year > 0 && month > 0 && month < 13 && day > 0 && day <= GetMonthDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "日期错误" << endl; exit(0); } } Data& operator+=(int day) { _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); _month++; if (_month == 13) { _year++; _month = 1; } } return *this; } int GetMonthDay(int year, int month) { int MonthDay[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 }; if (((year % 4 == 0) && (year % 100 != 0)) || year % 400 == 0) { MonthDay[2]++; } return MonthDay[month]; } Data& operator++() { *this += 1; return *this; } Data operator++(int) { Data tmp = *this; *this += 1; return tmp; } private: int _year; int _month; int _day; }; Data& Data ::operator++() { *this += 1; return *this; } Data Data :: operator++(int) { Data tmp = *this; *this += 1; return tmp; } int main() { Data t1(2024,2 ,23); t1++; //调用 t1.operator++() ++t1; //调用 t1.operator++(0) 参数也可能是其它 int 类型的数字 return 0; } 分析:
为了区分前置加加和后置加加,且能构成重载函数,参数一定要有一个 int 类型(无实际意义)
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改
代码举例
struct A { public: void fun() const { _a = 5; } private: int _a = 4; }; int main() { A aa; aa.fun(); return 0; }
分析:
// 相当于 const A * this
所以 this 的成员的值不可以修改
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
代码举例
#include
using namespace std; struct A { private: int _a = 4; }; int main() { A aa; cout << &aa << endl; return 0; } include
using namespace std; struct A { A* operator&() { return this; } const A* operator&() const { return this; } private: int _a = 4; }; int main() { A aa; cout << &aa << endl; return 0; }