目录
1. 类的默认成员函数
2. 构造函数
3. 析构函数
4. 拷贝构造函数
5. 赋值运算符重载
5.1 运算符重载
5.2 赋值运算符重载
5.3 日期类实现
6. 取地址运算符重载
6.1 const成员函数
6.2 取地址运算符重载
1. 类的默认成员函数
•默认成员函数:类中没有显示实现,编译器自动生成的函数叫做默认成员函数。默认成员函数包含:构造函数,析构函数,拷贝构造函数,赋值重载函数,普通对象取地址运算符重载函数,const对象取地址运算符重载函数。
2. 构造函数
•构造函数是特殊的成员函数,主要任务是在对象实例化时对它进行初始化,构造函数会被自动调用。
构造函数特点:
•构造函数函数名与类名相同,函数无返回值,对象实例化会自动调用对应构造函数,构造函数可以重载。
•类中没有显示实现构造函数(包含构造函数和拷贝构造函数)时,编译器才会自动生成一个无参的默认构造函数。编译器自动生成的构造函数对内置类型(int/char/double/指针等)成员变量初始化没有要求,是否初始化由编译器决定。对于自定义类型(用class/struct等关键字自己定义的类型)成员变量,编译器会调用它的默认构造函数进行初始化,没有默认构造函数则会报错。
#include<iostream> using namespace std; class Date { public: //不写构造,编译器生成默认构造 void Print() { cout << this->_year << " " << this->_month << " " << this->_day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; d1.Print(); return 0; }vs编译器下自动生成的默认构造函数将成员变量初始化为随机值。
•无参构造函数、全缺省构造函数与编译器自动生成的构造函数都叫做默认构造函数。但是这三个函数在一个类中只有一个存在,不能同时存在,无参构造函数与全缺省构造函数虽然构成重载,但是在函数调用时会存在歧义。即:可以不传实参就可以调用的构造函数就是默认构造函数。
#include<iostream> using namespace std; class Date { public: // 1.⽆参构造函数 Date() { _year = 1; _month = 1; _day = 1; cout << "无参构造函数" << endl; } // 2.带参构造函数 ,不是默认构造函数 Date(int year, int month, int day) { _year = year; _month = month; _day = day; cout << "带参构造函数" << endl; } // 3.全缺省构造函数 ,这里全缺省函数与带参构造函数形参类型/数量/顺序均相同,不构成重载函数 /*Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; cout << "全缺省构造函数" << endl; }*/ void Print() { cout << _year << "/" << _month << "/" << _day << endl; } private: int _year; int _month; int _day; }; int main() { Date d1; // 调⽤默认构造函数 Date d2(2025, 1, 1); // 调⽤带参的构造函数 // 注意:如果通过⽆参构造函数创建对象时,对象后⾯不⽤跟括号,否则编译器⽆法 // 区分这⾥是函数声明还是实例化对象 Date d3();//C++中 类型 对象() 会被识别为函数声明 d1.Print(); d2.Print(); return 0; }3. 析构函数
析构函数:在对象被销毁时完成资源的清理释放,C++规定规定对象在销毁时自动调用析构函数。
析构函数特点:
•析构函数函数名是类名前加上“~”,没有参数和返回值,不需要加void,对象生命周期结束会自动调用析构函数。
•类中没有显示实现析构函数时,编译器会自动生成默认的析构函数。编译器自动生成的默认析构函数对内置类型成员不做处理,自定义类型成员会调用它的析构函数。
•类中没有申请资源或默认生成的析构函数可以使用时,可以不写析构函数,使用编译器自动生成的默认析构函数,类中有资源申请需要自己写析构函数,防止出现资源泄露。
•C++规定一个局部域中的多个对象,后定义的先析构。
#include<iostream> using namespace std; typedef int STDataType; class Stack { public: Stack(int n = 4) { _a = (STDataType*)malloc(sizeof(STDataType) * n); if (nullptr == _a) { perror("malloc申请空间失败"); return; } _capacity = n; _top = 0; } //不显示写析构函数时,编译器默认生成析构函数,对于需要释放资源的类型,需要显示实现析构函数 ~Stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } private: STDataType* _a; size_t _capacity; size_t _top; }; int main() { Stack s; return 0; } //局部对象s在main函数结束自动调用析构函数,自己实现的析构函数会free掉malloc出来的资源 //不显示写析构函数时,编译器调用默认生成析构函数,仅会完成对象的销毁,不会对malloc出来的资源释放,引发内存泄漏4. 拷贝构造函数
•拷贝构造函数是一个特殊的构造函数。
拷贝构造函数特点:
•拷贝构造函数是构造函数的重载。
•拷贝构造函数的第一个参数必须是类类型的对象的引用,多个参数时,第一个参数必须是类类型的对象的引用,其他参数都需要缺省值。
#include<iostream> using namespace std; class Date { public: //默认构造函数(全缺省构造函数) Date(int year = 1, 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*类型) Date(Date* d) { _year = d->_year; _month = d->_month; _day = d->_day; } private: int _year; int _month; int _day; }; void func(Date d) { } int main() { Date d1(2005, 10, 5);//调用默认构造函数 Date d2(&d1);//调用带参构造函数 Date d3 = &d1;//调用带参构造函数 Date d4(d1);//调用拷贝构造函数 Date d5 = d1;//调用拷贝构造函数 func(d1);//函数传参调用拷贝构造 return 0; }注意:拷贝构造函数的第一个参数必须是类类型的对象的引用,如果不是,调用拷贝构造函数时,先进行函数传参,函数传参会调用拷贝构造函数,调用拷贝构造函数又会先传参,传参过程又调用拷贝构造函数……最终会无穷调用拷贝构造函数,加引用后就解决了这个问题。
•C++规定自定义类型对象进行拷贝行为时需要调用拷贝构造,自定义类型传值传参和传值返回都会调用拷贝构造。
•若未显式定义拷贝构造函数,编译器会自动生成拷贝构造函数,自动生成的拷贝构造函数对内置类型完成浅拷贝(按字节直接复制成员的值),自定义类型需要调用它的拷贝构造。
•类中如果申请了资源,就需要自己显示实现拷贝构造函数,同时也需要显示实现析构函数释放资源。
#include<iostream> using namespace std; typedef int STdatetype; class Stack { public: //构造函数 Stack(int n = 4) { _a = (STdatetype*)malloc(sizeof(STdatetype) * n); if (_a == nullptr) { perror("malloc申请空间失败"); return; } capacity = n; top = 0; } //拷贝构造函数 Stack(const Stack& st) { _a = (STdatetype*)malloc(sizeof(STdatetype) * st.capacity); if (_a == nullptr) { perror("malloc申请空间失败"); return; } memcpy(_a,st._a,sizeof(STdatetype)*st.top); capacity = st.capacity; top = st.top; } //插入元素 void Push(STdatetype s) { //扩容 if (top == capacity) { size_t newcapacity = capacity*2; STdatetype* tmp= (STdatetype*)realloc(_a,sizeof(STdatetype) * newcapacity); if (tmp== nullptr) { perror("realloc申请空间失败"); return; } _a = tmp; capacity = newcapacity; } _a[top++] = s; } //析构函数 ~Stack() { cout << "~Stack" << endl; free(_a); _a = nullptr; top = capacity = 0; } private: STdatetype* _a; size_t capacity;//元素个数 size_t top;//插入元素位置 }; class Myqueen { public: private: Stack pushst; Stack popst; }; int main() { Stack st1;//调用构造函数 st1.Push(1); st1.Push(2); Stack st2 = st1;//调用拷贝构造函数 //如果没有显示实现拷贝构造函数,编译器自动生成的拷贝构造函数只会完成浅拷贝, //st2与st1指向同一块空间,析构函数会对这块空间析构两次,程序崩溃 Myqueen qu1; Myqueen qu2 = qu1; // Myqueen⾃动⽣成的拷⻉构造,会⾃动调⽤Stack拷⻉构造完成qu1,qu2 // 的拷⻉,只要Stack拷⻉构造⾃⼰实现了深拷⻉,就没有问题 return 0; }注意:自己显示实现构造函数,编译器会自动生成默认拷贝构造函数,不会生成无参构造函数;
自己显示实现拷贝构造函数,编译器不会自动生成无参构造函数与拷贝构造函数;
•传值返回会产生临时对象调用拷贝构造,使用传引用返回返回的是返回对象的引用,不会产生临时对象,没有拷贝,可以提高效率。注意:如果返回对象是局部域的局部对象,函数结束就销毁了,此时传引用返回指向已销毁的对象,属于未定义行为bug,需避免这种使用。
5. 赋值运算符重载
5.1 运算符重载
C++自定义类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。因此,当自定义类型对象使用运算符时,需要对运算符进行重载赋予它新的含义。
运算符重载特点:
•运算符重载函数是具有特殊名字的函数,结构与函数相似,名字是operator和你需要定义的运算符共同构成。
•运算符重载函数的参数个数与运算符作用的对象个数一致,一元运算符有一个参数,二元运算符有两个参数(左侧运算符对象传给第一个参数,右侧运算符对象传给第二个参数,)。
•若运算符重载函数为成员函数,则运算符的第一个对象默认传给隐式this指针,所以,当运算符重载函数作为成员函数时,函数参数比运算对象少一个。
•语法中没有的符号无法进行重载,如:@, .*, ::, sizeof, ?:无法进行运算符重载。重载操作符必须有一个类类型参数,不能通过运算符重载改变类类型对象含义。
非法重载:
#include<iostream> using namespace std; class A { int x; }; //int operator +(int x, int y) //非法重载,参数必须要有一个自定义类型 //{ // return x + y; //} int main() { return 0; }•重载++运算符时,分为前置++和后置++,运算符重载函数名都是operator++,无法进行区分,所以后置++重载时增加一个int形参,与前置++构成重载。
++重载:
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } void Print() { cout << _year << "-" << _month << "-" << _day<<endl; } bool operator==( const Date& d1) { return _year == d1._year && _month == d1._month && _day == d1._day; } Date& operator++()//前置++,返回实现++功能后的自己,可以传引用返回 { //先++,再返回 _year++; _month++; _day++; return *this; } Date operator++(int)//后置++,返回实现++功能前的自己,也就是tmp,这里tmp是局部变量,无法传引用返回 { Date tmp = *this; //先返回再++ _year++; _month++; _day++; return tmp; } private: int _year; int _month; int _day; }; int main() { Date d1(2026, 3, 5); Date d2(2026, 3, 6); Date d3(2026, 3, 5); cout << d1.operator==( d2) << endl;//operator==(d1, d2)运算符重载函数可以显示调用 cout << (d1 == d3)<< endl;//d1 == d3 ,编译器会转换为operator==d1,d3) Date d4 = ++d1; d4.Print(); Date d5 =d3++; d5.Print(); return 0; }前置++与后置++都会使对象本身++,区别在返回值不同:前置++返回++后的对象,后置++返回++前的对象。
•重载<<与>>时需要重载为全局函数,因为重载为成员函数时,this指针会默认抢占第一个形参位置,与运算符左侧对象对应,实际运算符左侧对象为ostream/istream,与使用习惯不符。
成员函数指针使用:
#include<iostream> using namespace std; class A { public: void func() { cout << "func()" << endl; } }; typedef void (A:: *FF)();//FF为指向A类域中、返回值为void 、无参成员函数的指针类型 int main() { FF pf = &A::func;//成员函数名必须加&取到函数指针,pf为一个成员函数指针,指向func函数 A aa; (aa.*pf)();//对象调用成员函数使用.*运算符 return 0; }==运算符重载:
#include<iostream> using namespace std; class Date { public: Date(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; }; 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(2026, 3, 5); Date d2(2026, 3, 6); Date d3(2026, 3, 5); cout << operator==(d1, d2) << endl;//operator==(d1, d2)运算符重载函数可以显示调用 cout << (d1 == d3)<< endl;//d1 == d3 ,编译器会转换为operator==d1,d3) return 0; }重载为全局的面临对象访问私有变量时的方法:1.成员放公有;2.通过成员函数简接访问;3.友元函数;4.将该函数重载为全局函数。
5.2 赋值运算符重载
赋值运算符重载函数是一个默认成员函数,用于完成两个已经存在的对象的直接拷贝赋值。
与拷贝构造区分:拷贝构造用于完成一个已存在对象拷贝初始化给一个要创建的对象。
赋值运算符重载特点:
•赋值运算符需要重载为成员函数,参数可以写成const当前类类型引用,返回值可以写成类类型引用,减少传值传参的拷贝,同时支持连续赋值的场景。
•没有显示实现时,编译器会生成默认运算符重载函数,默认运算符重载函数对内置类型只进行浅拷贝,对自定义类型成员变量会调用它的运算符重载函数。
赋值运算符重载:
#include<iostream> using namespace std; class Date { public: Date(int year = 1, 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; } void Print() { cout << _year << "-" << _month << "-" << _day << endl; } Date& operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this; } private: int _year; int _month; int _day; }; int main() { Date d1(2026, 9, 4);//构造 Date d2(2026,9,5);//构造 Date d3(d1);//拷贝构造 d3.Print(); d1 = d2;//赋值 d1.Print(); Date d4 = d1;//拷贝构造 d4.Print(); return 0; }拷贝构造用于一个已存在对象拷贝初始化给另一个要创建对象;赋值重载完成两个已存在对象直接的拷贝赋值。
5.3 日期类实现
Date.h文件:
#pragma once #include<iostream> #include<assert.h> using namespace std; class Date { public: //友元函数声明 friend ostream& operator<<(ostream& out,const Date& d); friend istream& operator>>(istream& in, Date& d); int GetMonthDay(int year, int month) const { assert(month >= 1 && month <=12); //将数组MonthArr设为静态数组可以避免每次函数调用都重新生成一个数组,提高效率 static int MonthArr[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 }; if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))) return 29; else return MonthArr[month]; } //只要不改变调用对象本身的函数都可以加const void Print() const; bool CheckDate() const; Date(int year = 1, int month = 1, int day = 1); Date& operator +=(int day); Date operator +(int day) const; Date& operator -=(int day); Date operator -(int day) const; Date& operator ++(); Date operator ++(int); Date& operator --(); Date operator --(int); bool operator==(const Date& d) const; bool operator!=(const Date& d) const; bool operator>(const Date& d) const; bool operator>=(const Date& d) const; bool operator<(const Date& d) const; bool operator<=(const Date& d) const; int operator -(const Date& d) const; private: int _year; int _month; int _day; }; ostream& operator<<(ostream& out, const Date& d); istream& operator>>(istream& in, Date& d);Date.cpp文件
#define _CRT_SECURE_NO_WARNINGS #include"Date.h" void Date::Print() const { cout << _year << " " << _month << "-" << _day << endl; } bool Date::CheckDate() const { if ((_month >= 1 && _month <= 12) && (_day >= 1 && _day <= Date::GetMonthDay(_year,_month))) return true; else return false; } Date::Date(int year , int month , int day ) { _year = year; _month = month; _day = day; if (!CheckDate()) { cout << "非法日期->" << *this; } } Date& Date::operator+=(int day) { if (day < 0) return *this -= -day; _day += day; while (_day > GetMonthDay(_year, _month)) { _day -= GetMonthDay(_year, _month); ++_month; if (_month == 13) { _month = 1; _year++; } } return *this; } Date Date::operator +(int day) const { Date tmp(*this); tmp += day; return tmp; } Date& Date::operator -=(int day) { if (day < 0) return *this += -day; _day -= day; while (_day <= 0) { //需要先对month进行处理,方便后面向前一月借天数 --_month; if (_month == 0) { _month = 12; _year--; } _day += GetMonthDay(_year, _month); } return *this; } Date Date::operator -(int day) const { Date tmp(*this); tmp -= day; return tmp; } Date& Date::operator ++() { *this += 1; return *this; } Date Date::operator ++(int) { Date tmp(*this); *this += 1; return tmp; } Date& Date::operator --() { *this -= 1; return *this; } Date Date::operator --(int) { Date tmp(*this); *this -= 1; return tmp; } bool Date::operator==(const Date& d) const { return _year == d._year && _month == d._month && _day == d._day; } bool Date::operator!=(const Date& d) const { return !(*this == d); } bool Date::operator>(const Date& d) const { if (_year > d._year) return true; else if (_year == d._year && _month > d._month) return true; else if (_year == d._year && _month == d._month && _day > d._day) return true; return false; } bool Date::operator>=(const Date& d) const { return *this > d || *this == d; } bool Date::operator<(const Date& d) const { return !(*this >= d); } bool Date::operator<=(const Date& d) const { return !(*this > d); } int Date::operator -(const Date& d) const { Date max = *this; Date min = d; int flag = 1; if (*this < d) { max = d; min = *this; flag = -1; } int n = 0; while (min != max) { ++min; ++n; } return n * flag; } ostream& operator<<(ostream& out, const Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } istream& operator>>(istream& in, Date& d) { while (1) { cout << "请输入年月日:" << endl; in >> d._year >> d._month >> d._day; if (d.CheckDate()) { break; } cout << "非法日期,重新输入!" << endl; } return in; }Test.cpp文件:
#define _CRT_SECURE_NO_WARNINGS #include"Date.h" #include<iostream> using namespace std; void TestDate1() { Date d1(2025, 4, 24); //Date d2(d1 + 100); Date d2 = d1 + 100; d1.Print(); d2.Print(); Date d3 = d1 += 100; d1.Print(); d3.Print(); } void TestDate2() { Date d1(2025, 4, 26); Date d2 = d1 -= 10000; d1.Print(); d2.Print(); Date d3(2025, 4, 26); Date d4 = d3 - 10000; d3.Print(); d4.Print(); Date d5(2025, 4, 26); d5 += -100; d5.Print(); } void TestDate3() { Date d1(2025, 4, 26); Date d2 = d1++; //Date d2 = d1.operator++(0); d1.Print(); d2.Print(); Date d3(2025, 4, 26); Date d4 = ++d3; d3.Print(); d4.Print(); } void TestDate4() { Date d1(2025, 4, 26); Date d2(2025, 7, 1); cout << (d1 < d2) << endl; cout << (d2 < d1) << endl; cout << (d1 == d2) << endl; cout << d1 - d2 << endl; Date d3(2005, 4, 26); Date d4(2100, 1, 1); cout << d3 - d4 << endl; } void TestDate5() { Date d1(2100, 2, 29); Date d2(2000, 2, 29); // 流插入 cout << d1 << d2; //operator<<(cout, d1); cin >> d1 >> d2; cout << d1 << d2; cout << d1 - d2 << endl; } void TestDate6() { Date d1(2100, 2, 28); d1.Print(); d1 + 100; d1 += 100; const Date d2(2100, 2, 28); d2.Print(); d2 + 100; //d2 += 100; d2为const Date类型不支持 cout << &d1 << endl; cout << &d2 << endl; } int main() { TestDate1(); cout << endl; TestDate2(); cout << endl; TestDate3(); cout << endl; TestDate4(); cout << endl; TestDate5(); cout << endl; TestDate6(); cout << endl; return 0; }6. 取地址运算符重载
6.1 const成员函数
将const修饰的成员函数称为const成员函数,const修饰成员函数时放到函数参数列表后面。
•普通成员函数this指针类型:Date* const this,this指针不能指向其他对象,但可以通过this指针修改对象的成员。
•const实际修饰成员函数中隐含的this指针,表明在该成语函数中对类的任何成员都不能进行修改。const修饰Date类的Print函数时,隐含的this指针由Date* const this转变为const Date* const this,无法再通过this指针修改对象的成员。
权限可以缩小,权限不能放大:普通对象可以调用const成员函数和普通成员函数;const对象不能调用普通成员函数,只能调用const成员函数。
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } // void Print(const Date* const this) const void Print() const { cout << _year << "-" << _month << "-" << _day << endl; } private: int _year; int _month; int _day; }; int main() { // 这⾥⾮const对象d1也可以调⽤const成员函数Print,是⼀种权限的缩⼩ Date d1(2026, 8, 5); d1.Print(); const Date d2(2026, 9, 5); d2.Print(); return 0; }6.2 取地址运算符重载
取地址运算符重载分为普通运算符重载和const运算符重载。一般不需要显示实现,编译器原生存在的取地址足够我们使用。
自己显示实现可以按需求返回需要的地址:
class Date { public : Date* operator&()//非const版本,this为Date* const 类型 { return this; // return nullptr; } const Date* operator&()const//const版本,this为const Date* const 类型 { return this; // return nullptr; } private : int _year ; // 年 int _month ; // ⽉ int _day ; // ⽇ };