文章目录
-
- 1.6个默认成员函数
- 2.构造函数
-
- 2.1 概念
- 2.2 特性
- 3.析构函数
-
- 3.1 概念
- 3.2 特性
- 4.复制结构函数
-
- 4.1 概念
- 4.2 特性
- 5.赋值操作符重载
-
- 5.1 运算符重载
- 5.2 赋值运算符重载
- 5.3 特性
- 6.日期类的完整实现
- 7.const 成员
-
- 7.1 const修饰成员函数
- 7.2 请考虑以下问题:
- 8.取地址及const取地址操作符重载
- 9.思维导图总结
1.6个默认成员函数
如果一个类别中没有成员,它被称为空类。空类中什么都没有吗?不,任何类别都会自动生成以下动生成以下六个默认成员函数。
class Date {
}; 
2.构造函数
2.1 概念
#include<iostream> using namespace std; class Date {
public: //void Display(Date* this) void Display() {
cout << _year << "-" << _month << "-" << _day << endl; //cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl; } //void SetDate(Date* this,int year,int month,int day) void SetDate(int year, int month, int day) {
_year = year;//this->_year = year; _month = month;//this->_month = year; _day = day;//this->_day = year; } private: int _year;//年 int _month;//月 int _day;//日 }; int main) {
Date d1, d2; d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19) d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19) d1.Display();//d1.Display(&d1) d2.Display();//d2.Display(&d2) return 0; } 对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
2.2 特性
构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务
其特征如下:
#include<iostream>
using namespace std;
class Date
{
public:
//void Display(Date* this)
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
}
//void SetDate(Date* this,int year,int month,int day)
void SetDate(int year, int month, int day)
{
_year = year;//this->_year = year;
_month = month;//this->_month = year;
_day = day;//this->_day = year;
}
//构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用
//1.无参数构造函数
Date() {
_year = 0;
_month = 1;
_day = 1;
}
//2.带参数构成函数 无参和带参两种方式均可
Date(int year, int month, int day) {
_year = year;
_month = month;
_day = day;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Date d1(2021, 8, 21);
d1.Display();
d1.SetDate(2021, 10, 1);
d1.Display();
Date d2;
d2.Display();
//Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了
//Date d2(2015, 1, 1);//调用带参的构造函数
//d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19)
//d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19)
//d1.Display();//d1.Display(&d1)
//d2.Display();//d2.Display(&d2)
return 0;
}
Date d2;//后面不能加(),d2(),这是语法规定的
这句话的意思就是”如果我们定义了构造函数,那么C++编译器就会使用我们定义的构造函数来初始化新建的对象;如果我们没有定义构造函数,那么C++编译器会自动生成一个没有参数的默认构造函数,然后用这个默认构造函数去初始化新建的对象。”
看起来编译器默认构造函数啥也没干,还是随机值,那这个默认构造函数有啥用呢?
#include<iostream> using namespace std; class Time { public: Time() { _hour = 0; _minute = 0; _second = 0; cout << "Time()" << endl; } private: int _hour; int _minute; int _second; }; class Date { public: //void Display(Date* this) void Display() { cout << _year << "-" << _month << "-" << _day << endl; //cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl; } //void SetDate(Date* this,int year,int month,int day) void SetDate(int year, int month, int day) { _year = year;//this->_year = year; _month = month;//this->_month = year; _day = day;//this->_day = year; } //我们没有显式定义构造函数,这里编译器生成无参默认构造函数 构造函数 相当于SetDate,初始化,但是由程序自动调用,不需要我们去调用 1.无参数构造函数 //Date() { // _year = 0; // _month = 1; // _day = 1; //} 2.带参数构成函数 无参和带参两种方式均可 //Date(int year, int month, int day) { // _year = year; // _month = month; // _day = day; //} private: int _year;//年 int _month;//月 int _day;//日 Time _t;//自定义类型 }; int main() { //Date d1(2021,8,21); Date d1;//调用了编辑器生成的默认构造函数 d1.Display(); d1.SetDate(2021, 10, 1); d1.Display(); Date d2; d2.Display(); //Date d1;//调用无参构造函数 注意后面不能带(),否则变成函数声明了 //Date d2(2015, 1, 1);//调用带参的构造函数 //d1.SetDate(2021, 8, 19);//d1.SetDate(&d1,2021,8,19) //d2.SetDate(2021, 8, 20);//d2.SetDate(&d2,2021,8,19) //d1.Display();//d1.Display(&d1) //d2.Display();//d2.Display(&d2) return 0; }
添加一个自定义类型Time后,可以发现Date的编译器生成的默认构造函数实际上调用了Time的构造函数。也就是说:
默认生成的无参构造函数(语法坑:双标狗)
为了解决上面内置类型不初始化的问题,C++11打了一个补丁,允许给内置类型设置缺省值。
int _year = 0;
int _month = 1;
int _day = 1;
//注意这里是成员变量的声明,不是定义,没有开空间
**6.无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。**注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。
#include<iostream>
using namespace std;
class Date
{
public:
无参构造函数
//Date() {
// _year = 0;
// _month = 1;
// _day = 1;
//}
带参构成函数
//Date(int year, int month, int day) {
// _year = year;
// _month = month;
// _day = day;
//}
//上面两种方式的合二为一 全缺省构造函数
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Date d1(2021, 10, 1);
Date d2;
d1.Display();
d2.Display();
return 0;
}
注意:无参的构造函数和全缺省的构造函数,只能存在一个;如果两个同时存储,那么调用的时候会产生歧义,因为编译器不知道该调用哪个了。
d对象调用了编译器生成的默认构造函数,但是d对象的_year/_month/_day,依旧是随机值。也就说在这里编译器生成的默认构造函数并没有什么作用??
解答:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char…,。自定义类型就是我们使用class/struct/union自己定义的类型,看看下面的程序
就会发现编译器生成默认的构造函数会对自定类型成员_t调用的它的默认成员函数
(这个前面已经说的很清楚了,语法坑:双标狗)
建议成员变量名称前面加上下划线_
int _year;
int _month;
int _day;
//或者也可以这样
int m_year;
int m_month;
int m_day;
//m-member 成员
3.析构函数
3.1 概念
前面通过构造函数的学习,我们知道一个对象时怎么来的,那一个对象又是怎么没呢的?
析构:对象生命周期到了以后,自动调用。完成对象里面的资源清理(资源:对象创建时向系统申请的空间,比如所 malloc 出来的空间)
3.2 特性
析构函数是特殊的成员函数。其特征如下:
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
cout << "自动调用构造函数" << endl;
}
//析构函数
~Date() {
cout << "自动调用析构函数~" << endl;
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
int main()
{
Date d1(2021, 10, 1);
Date d2;
d1.Display();
d2.Display();
return 0;
}
当我们动态开辟数组,比如malloc/colloc/realloc出来的空间,使用之后需要将这些堆上的空间释放掉,就可以利用析构函数将资源清理掉。
#include<iostream>
using namespace std;
class Stack {
public:
//构造函数,完成初始化
Stack(int n = 10) {
_array = (int*)malloc(sizeof(int) * n);
_size = 0;
_capacity = 10;
}
//析构函数,完成资源清理
~Stack() {
free(_array);
_array = NULL;
_size = 0;
_capacity = 0;
}
private:
int* _array;
int _size;
int _capacity;
};
int main() {
Stack st;
return 0;
}
调用构造函数完成初始化
调用析构函数完成资源清理
一个类要有什么样的成员变量和成员函数,需要根据类的实际使用场景来确定。
下面的程序我们会看到,编译器生成的默认析构函数,对会自定类型成员调用它的析构函数。
(类似于构造函数的语法坑:双标狗)
跟构造函数类似,编译器默认生成的析构函数做了偏心的处理:
#include<iostream>
using namespace std;
class Time {
public:
~Time() {
cout << "~Time()" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
//构造函数
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
cout << "自动调用构造函数" << endl;
}
析构函数
//~Date() {
// cout << "自动调用析构函数~" << endl;
//}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
//cout << this->_year << "-" << this->_month << "-" <<this-> _day << endl;
}
private:
int _year;//年
int _month;//月
int _day;//日
Time _t;//自定义类型
};
int main()
{
Date d1(2021, 10, 1);
Date d2;
d1.Display();
d2.Display();
return 0;
}
4.拷贝构造函数
4.1 概念
在现实生活中,可能存在长得一模一样的事物,我们称其为双胞胎(孪生/双生)。
那在创建对象时,可否创建一个与一个对象一模一样的新对象呢?
4.2 特性
拷贝构造函数也是特殊的成员函数,其特征如下:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 0, 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;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021, 10, 1);
Date d2(2021, 10, 1);
Date d3(d1);
return 0;
}
如果不使用引用,在调用拷贝构造函数之前需要先传参,传参的时候要先进行拷贝构造,拷贝构造之前又要先传参….语义上
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 0, 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;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2021, 10, 1);
Date d2(2021, 10, 1);
Date d3(d1);
return 0;
}
传参的过程又是一个拷贝构造的调用?这句话怎么理解?
func1(int i)
{
//函数内部代码
}
int main()
{
int j = 0;
func1(j);//调用func1的时候,要先将j传给i
}
实参传给形参是一个赋值的方式
换成class Date类的话
d1传给d的过程就是一个赋值的过程,但是这个自定义类的赋值不同于内置类型的直接赋值,而是再次通过拷贝构造函数给其赋值,然后就形成了语义上的无穷递归……
传值的方式,实参接收形参传过来的值时,需要创建一个,就会引发对象的拷贝构造。
而传引用的方式(引用是一个变量的别名)没有传参赋值的这个过程,也就不会形成无穷的递归。
拷贝构造也可以通过类似赋值的方式进行
Date d4 = d1;
Date(const Date &d)这里为什么要加上const的呢?
1)使用const可以保证对象d的内容不被修改
2)如果Date(const Date &d){ };花括号内部代码出错了,比如说写反了,编译器会查出来。
默认的拷贝构造函数,跟默认的构造和析构函数不太一样,不会去区分内置类型和自定义类型,都会区处理
1)内置类型,字节序的浅拷贝
2)自定义类型,会去调用它的拷贝构造函数完成拷贝
字节序的浅拷贝:就像用 memcpy 函数完成拷贝一样(一个字节接着一个字节的拷贝复制)
Stack St1;
Stack st2(st1);
//同一块空间会释放两次
编译器默认生成的拷贝构造函数并不能解决所有问题,所以需要我们定义深拷贝构造来解决这个问题。
#include<iostream>
using namespace std;
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;
};
int main()
{
Date d1;
//这里d2调用默认拷贝构造完成拷贝,d2和d1的值是一样的
Date d2(d1);
return 0;
}
//这里会发现下面的程序会崩溃掉?需要我们以后学了深拷贝去解决
#include<iostream>
using namespace std;
class String
{
public:
String(const char* str = "jack")
{
_str = (char*)malloc(str