目录

如果一个类中什么成员都没有,简称为空类。空类中真的什么都没有吗?并不是,任何类在什么都不写时,编译器会自动生成以下 6 个默认成员函数。默认成员函数:用户没有显式实现,编译器会生成的成员函数称为默认成员函数。
- 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(2022, 7, 5);
- d1.Print();
- Date d2;
- d2.Init(2022, 7, 6);
- d2.Print();
- return 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(2015, 1, 1); // 调用带参的构造函数
-
- // 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明
- // 以下代码的函数:声明了d3函数,该函数无参,返回一个日期类型的对象
- // warning C4930: “Date d3(void)”: 未调用原型函数(是否是有意用变量定义的?)
- Date d3();
- }
- class Date
- {
- public:
- /*
- // 如果用户显式定义了构造函数,编译器将不再生成
- Date(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类中构造函数屏蔽后,代码可以通过编译,因为编译器生成了一个无参的默认构造函
- 数
- // 将Date类中构造函数放开,代码编译失败,因为一旦显式定义任何构造函数,编译器将不再
- 生成
- // 无参构造函数,放开后报错:error C2512: “Date”: 没有合适的默认构造函数可用
- Date d1;
- return 0;
- }
解答: C++ 把类型分成内置类型 ( 基本类型 ) 和自定义类型。内置类型就是语言提供的数据类型,如: int/char... ,自定义类型就是我们使用 class/struct/union 等自己定义的类型,看看下面的程序,就会发现编译器生成默认的构造函数会对自定类型成员 _t 调用的它的默认成员函数。 (需要注意的是,该种情况下,需要自定义成员变量的构造函数是默认构造函数,如果该自定义成员变量无默认构造函数,则编译器报错)
- class Time
- {
- public:
- Time()
- {
- cout << "Time()" << endl;
- _hour = 0;
- _minute = 0;
- _second = 0;
- }
- private:
- int _hour;
- int _minute;
- int _second;
- };
- class Date
- {
- private:
- // 基本类型(内置类型)
- int _year;
- int _month;
- int _day;
- // 自定义类型
- Time _t;
- };
- int main()
- {
- Date d;
- return 0;
- }
- class Time
- {
- public:
- Time()
- {
- cout << "Time()" << endl;
- _hour = 0;
- _minute = 0;
- _second = 0;
- }
- private:
- int _hour;
- int _minute;
- int _second;
- };
- class Date
- {
- private:
- // 基本类型(内置类型)
- int _year = 1970;
- int _month = 1;
- int _day = 1;
- // 自定义类型
- Time _t;
- };
- int main()
- {
- Date d;
- return 0;
- }
- 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)
- {
- // CheckCapacity();
- _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);
- }
关于编译器自动生成的析构函数,是否会完成一些事情呢?下面的程序我们会看到,编译器
- class Time
- {
- public:
- ~Time()
- {
- cout << "~Time()" << endl;
- }
- private:
- int _hour;
- int _minute;
- int _second;
- };
- class Date
- {
- private:
- // 基本类型(内置类型)
- int _year = 1970;
- int _month = 1;
- int _day = 1;
- // 自定义类型
- Time _t;
- };
- int main()
- {
- Date d;
- return 0;
- }
- // 程序运行结束后输出:~Time()
- // 在main方法中根本没有直接创建Time类的对象,为什么最后会调用Time类的析构函数?
- // 因为:main方法中创建了Date对象d,而d中包含4个成员变量,其中_year, _month,
- _day三个是
- // 内置类型成员,销毁时不需要资源清理,最后系统直接将其内存回收即可;而_t是Time类对
- 象,所以在
- // d销毁时,要将其内部包含的Time类的_t对象销毁,所以要调用Time类的析构函数。但是:
- main函数
- // 中不能直接调用Time类的析构函数,实际要释放的是Date类对象,所以编译器会调用Date
- 类的析构函
- // 数,而Date没有显式提供,则编译器会给Date类生成一个默认的析构函数,目的是在其内部
- 调用Time
- // 类的析构函数,即当Date对象销毁时,要保证其内部每个自定义对象都可以正确销毁
- // main函数中并没有直接调用Time类析构函数,而是显式调用编译器为Date类生成的默认析
- 构函数
- // 注意:创建哪个类的对象则调用该类的析构函数,销毁那个类的对象则调用该类的析构函数
- class Date
- {
- public:
- Date(int year = 1900, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- // Date(const Date& d) // 正确写法
- 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);
- return 0;
- }

- class Time
- {
- public:
- Time()
- {
- _hour = 1;
- _minute = 1;
- _second = 1;
- }
- Time(const Time& t)
- {
- _hour = t._hour;
- _minute = t._minute;
- _second = t._second;
- cout << "Time::Time(const Time&)" << endl;
- }
- private:
- int _hour;
- int _minute;
- int _second;
- };
- class Date
- {
- private:
- // 基本类型(内置类型)
- int _year = 1970;
- int _month = 1;
- int _day = 1;
- // 自定义类型
- Time _t;
- };
- int main()
- {
- Date d1;
-
- // 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
- // 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构
- 造函数
- Date d2(d1);
- return 0;
- }
- // 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
- typedef int DataType;
- class Stack
- {
- public:
- Stack(size_t capacity = 10)
- {
- _array = (DataType*)malloc(capacity * sizeof(DataType));
- if (nullptr == _array)
- {
- perror("malloc申请空间失败");
- return;
- }
- _size = 0;
- _capacity = capacity;
- }
- void Push(const DataType& data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
- ~Stack()
- {
- if (_array)
- {
- free(_array);
- _array = nullptr;
- _capacity = 0;
- _size = 0;
- }
- }
- private:
- DataType *_array;
- size_t _size;
- size_t _capacity;
- };
- int main()
- {
- Stack s1;
- s1.Push(1);
- s1.Push(2);
- s1.Push(3);
- s1.Push(4);
- Stack s2(s1);
- return 0;
- }

- class Date
- {
- public:
- Date(int year, int minute, int day)
- {
- cout << "Date(int,int,int):" << this << endl;
- }
- Date(const Date& d)
- {
- cout << "Date(const Date& d):" << this << endl;
- }
- ~Date()
- {
- cout << "~Date():" << this << endl;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
- Date Test(Date d)
- {
- Date temp(d);
- return temp;
- }
- int main()
- {
- Date d1(2022,1,13);
- Test(d1);
- return 0;
- }
- // 全局的operator==
- class Date
- {
- public:
- Date(int year = 1900, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- //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;
- }
- void Test ()
- {
- Date d1(2018, 9, 26);
- Date d2(2018, 9, 27);
- cout<<(d1 == d2)<
- }
- class Date
- {
- public:
- Date(int year = 1900, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
-
- // bool operator==(Date* this, const Date& d2)
- // 这里需要注意的是,左操作数是this,指向调用函数的对象
- bool operator==(const Date& d2)
- {
- return _year == d2._year;
- && _month == d2._month
- && _day == d2._day;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
5.2 赋值运算符重载
1.
赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
- 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;
- }
-
- return *this;
- }
- private:
- int _year ;
- int _month ;
- int _day ;
- };
2.
赋值运算符只能重载成类的成员函数不能重载成全局函数
- class Date
- {
- public:
- Date(int year = 1900, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- int _year;
- int _month;
- int _day;
- };
- // 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
- Date& operator=(Date& left, const Date& right)
- {
- if (&left != &right)
- {
- left._year = right._year;
- left._month = right._month;
- left._day = right._day;
- }
- return left;
- }
- // 编译失败:
- // error C2801: “operator =”必须是非静态成员
因为赋值重载是类的六大默认成员函数之一,如果不显示在类中声明(定义),编译器就会自动生成,此时就会与定义在全局的赋值重载函数发生冲突

3.
用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝
。注
意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。
- class Time
- {
- public:
- Time()
- {
- _hour = 1;
- _minute = 1;
- _second = 1;
- }
- Time& operator=(const Time& t)
- {
- if (this != &t)
- {
- _hour = t._hour;
- _minute = t._minute;
- _second = t._second;
- }
- return *this;
- }
- private:
- int _hour;
- int _minute;
- int _second;
- };
- class Date
- {
- private:
- // 基本类型(内置类型)
- int _year = 1970;
- int _month = 1;
- int _day = 1;
- // 自定义类型
- Time _t;
- };
- int main()
- {
- Date d1;
- Date d2;
- d1 = d2;
- return 0;
- }
既然
编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了
,还需要自己实
现吗?当然像日期类这样的类是没必要的。那么下面的类呢?
- // 这里会发现下面的程序会崩溃掉?这里就需要我们以后讲的深拷贝去解决。
- typedef int DataType;
- class Stack
- {
- public:
- Stack(size_t capacity = 10)
- {
- _array = (DataType*)malloc(capacity * sizeof(DataType));
- if (nullptr == _array)
- {
- perror("malloc申请空间失败");
- return;
- }
- _size = 0;
- _capacity = capacity;
- }
- void Push(const DataType& data)
- {
- // CheckCapacity();
- _array[_size] = data;
- _size++;
- }
- ~Stack()
- {
- if (_array)
- {
- free(_array);
- _array = nullptr;
- _capacity = 0;
- _size = 0;
- }
- }
- private:
- DataType *_array;
- size_t _size;
- size_t _capacity;
- };
- int main()
- {
- Stack s1;
- s1.Push(1);
- s1.Push(2);
- s1.Push(3);
- s1.Push(4);
- Stack s2;
- s2 = s1;
- return 0;
- }
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
须要实现。
5.3 前置++和后置++重载
- class Date
- {
- public:
- Date(int year = 1900, int month = 1, int day = 1)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- // 前置++:返回+1之后的结果
- // 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
- Date& operator++()
- {
- _day += 1;
- return *this;
- }
- // 后置++:
- // 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
- // C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器
- 自动传递
- // 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存
- 一份,然后给this+1
- // 而temp是临时对象,因此只能以值的方式返回,不能返回引用
- Date operator++(int)
- {
- Date temp(*this);
- _day += 1;
- return temp;
- }
- private:
- int _year;
- int _month;
- int _day;
- };
- int main()
- {
- Date d;
- Date d1(2022, 1, 13);
- d = d1++; // d: 2022,1,13 d1:2022,1,14
- d = ++d1; // d: 2022,1,15 d1:2022,1,15
- return 0;
- }
7.const成员
将
const
修饰的
“
成员函数
”
称之为
const
成员函数
,
const
修饰类成员函数,实际修饰该成员函数
的*this,表明在该成员函数中
不能对类的任何成员进行修改。
- class Date
- {
- public:
- Date(int year, int month, int day)
- {
- _year = year;
- _month = month;
- _day = day;
- }
- void Print()
- {
- cout << "Print()" << endl;
- cout << "year:" << _year << endl;
- cout << "month:" << _month << endl;
- cout << "day:" << _day << endl << endl;
- }
- void Print() const
- {
- cout << "Print()const" << endl;
- cout << "year:" << _year << endl;
- cout << "month:" << _month << endl;
- cout << "day:" << _day << endl << endl;
- }
- private:
- int _year; // 年
- int _month; // 月
- int _day; // 日
- };
- void Test()
- {
- Date d1(2022,1,13);
- d1.Print();
- const Date d2(2022,1,13);
- d2.Print();
- }
请思考下面的几个问题:
1. const
对象可以调用非
const
成员函数吗?
肯定是不可以的,一个只读的对象怎么可以用一个可读可写的*this接收呢
2.
非
const
对象可以调用
const
成员函数吗?
这个是可以的,从可读可写到只读是权限的缩小
3. const
成员函数内可以调用其它的非
const
成员函数吗?
不可以,
const成员函数内部只能调用const成员函数。因为const成员函数内部的this指针已经具有常属性的,万一这个非const成员函数去修改了
成员变量的内容就会出问题了
4.
非
const
成员函数内可以调用其它的
const
成员函数吗?
肯定是可以的啦