C++带你深入了解类和对象(超细节)
目录
1. 引言
class Date() {}
一个类,如果没有任何成员,则称为空类。而当一个类什么都不创建时,里面真的什么都没有吗? 并不是!
编译器会自动生成六个类默认的成员函数:
1.构造函数
2.析构函数
3.拷贝构造函数
4.运算符重载函数
5.const成员函数
6.取地址及const取地址操作符重载函数
这六个默认函数都遵守的规则是:如果类中主动写有自定义的默认成员函数,则采取手动编写的,否则采取编译器默认生成的。
本篇就以Date日期类举例,重点介绍六位天选之子的默认成员函数。
2.构造函数
2.1 介绍与初始化
这里有Date类,对于Date类:
class Date()
{
public:
void Init(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year = 2023;//年;可以在声明位置给上初始值,但并非初始化,而是给缺省值
int _month;//月
int _day;//日
};
int main()
{
Date x;
x.Init(2022,3,4);
Date y;
x,Init(2023,3,4)
return 0;
}
在这里创建了Init()函数进行了初始化,但是如果每次调用对象都需要用Init来调用未免有些不方便,所以C++提供了构造函数。
构造函数名称与类名相同,创建类对象由编译器自动生成,每个成员的初始值在对象的整个生命周期只有一次。
class Date()
{
public:
void Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;//年
int _month;//月
int _day;//日
};
注意: 这里类内的构造函数成员只是进行了声明,并不是初始化,没有开辟内存空间 ,只有当对象调用了之后才会开辟空间。
2.2 另一种初始化玩法:初始化列表初始化
除了上述最基本初始化方法初始化,还有构造函数初始化的另一种玩法:初始化列表。这种初始化具有一定的意义,即当类中有三种:
1.const 类型
2.引用类(如:int&)
3.自定义类
当一个类中有以上三种成员时,必须使用初始化列表初始化! 而不能用一般的初始化方法。具体例子如下:
class Date()
{
public:
Date(int year,int month,int day)
: _year(year)
, _month(month)
, _day(day)
{...}
private:
int _year;
int _month;
int _day;
};
以上初始化列表的格式需要注意以下问题:
1.初始化列表写在具体实现前(应在{…}上面)
2.冒号开始,逗号封装
当了解初始化列表以后,我们需要明白两件事:
1.以后尽量使用初始化列表初始化,因为无论你使不使用,对于自定义类型成员,一定会先使用初始化列表初始化(编译器默认)。
2.成员变量在类中声明的次序就是初始化列表中初始化的顺序,与其在初始化列表中的先后次序无关。
3.析构函数
3.1 介绍
通过构造函数我们知道一个对象是怎么来的,那么一个对象又是怎么没的呢?是通过析构函数。
析构函数不是完成对象的销毁工作的,销毁工作由编译器完成。对象被销毁时会自动调用析构函数,
完成资源清理的工作。
3.2 特性
1.析构函数名与类型相同,不过在类名前加上~。
2.无参数无返回值。
3.一个类只能由一个析构函数。
4.对象生命周期结束时,C++自动调用析构函数。
~Date()
{
cout << "调用析构函数" << endl;
...
}
如果类中没有申请资源时,比如Date类,就可以直接使用编译器生成的默认析构函数。但是如果动态开辟了空间占用了内存,则必须手写析构函数释放掉空间,否则会造成资源泄露,比如当使用malloc动态开辟了内存的类。
4. 拷贝构造函数
4.1 介绍
在生活中,长得一摸一样的人我们称之为双胞胎。在创建对象时我们也可以通过函数创建一个一样的对象。
那么,在什么情况下会使用拷贝构造函数呢?
● 使用已存在的对象创建对象
● 函数参数类型为类类型对象
● 函数返回值为类类型对象
4.2 特征
拷贝构造函数特征如下:
1.拷贝构造函数是构造函数的一个特殊的重载形式。
2.拷贝构造函数的参数==只有一个且必须是类类型对象的引用==(一般还用const修饰),如 :const Date& d。使用值传递的方式(const Date d)会引 发无限递归编译器直接报错。
3.拷贝构造函数在用已存在的类类型对象创建时由编译器自动调用。
4.未手动定义,编译器默认生成拷贝构造函数
具体使用方式如下:
class Date()
{
public:
Date(const Date& d) //拷贝构造函数 d为要构建的一摸一样的对象
{
_year = d._year;
_month = d._month;
_day = d._day;
}
private:
int _year;
int _month;
int _year;
};
注意:
1.拷贝构造函数内,内置类型如:int、double等使用时是按字节方式直接进行浅拷贝(值拷贝)的,而自定义类型是调用其拷贝构造函数完成拷贝的。
2.类中无资源动态申请的拷贝构造函数是否写都可以,但是一旦涉及动态申请了,就必须手写拷贝构造函数,否则将一直保持浅拷贝状态。
5. 运算符重载函数
5.1运算符重载
5.1.1 介绍
假如有两个对象d1、d2,在Date类中想要比较两个对象的日期有很多种方法,但是有些方法很麻烦。所以想法大胆一点,能不能直接使用d1>d2的方法直接比较? 可以! 只要使用运算符重载 ,就相当于赋给了运算符新的使用方法!
C++为了增加代码的可读性引入了运算符重载,运算符重载是特殊的函数,也具有返回值、名字和参数。
5.1.2 特性
函数名:operator + 重载运算符号
函数原型:返回值类型 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& d)
{
if (_year < d._year)
return false;
else if (_month < d._month)
return false;
else if (_day < d._day)
return false;
else
return true;
}
在使用中有以下几点需要注意:
● ( . : : sizeof ?: .* ) 以上五个运算符不能重载。
● 不能通过链接其他符号来创建新的操作符。如operator @ 。
● 用于内置计算的运算符,其含义不能改变。如内置+。
● 作为类成员函数重载时,其形参看起来比操作数数目少1,因为还有一个隐藏的指针this。加上this指针,参数超过三个及以上就会报错。
● 重载函数必须有一个类的类型参数(如:Date)。
5.2 赋值运算符(operator=)重载
5.2.1 介绍
默认重载的赋值运算符功能很简单,就是将原有对象的所有成员变量一一赋值给新对象,这和默认拷贝构造函数的功能类似。 对于简单的类,默认的赋值运算符一般就够用了,我们也没有必要再显式地重载它。 但是当类持有其它资源时,例如动态分配的内存、打开的文件、指向其他数据的 指针 、网络连接等,默认的赋值运算符就不能处理了,我们必须显式地重载它,这样才能将原有对象的所有数据都赋值给新对象。
5.2.2 特性
● 参数类型: const C&(C为类名)
● 返回值类型:C&
● 返回*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 = year;
_month = month;
_day = day;
}
Date& operator = (const Date& d) //operator=的赋值运算符重载
{
if(this != &d)
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
}
private:
int _year;
int _month;
int _day;
}
● 注意: 赋值运算符只能重载成员函数不能重载全局函数
6.const成员函数
6.1 介绍
将const修饰的成员函数称为const成员函数,本质上是修饰this指针,表明在该成员函数中不能对任何的成员进行修改。
class Date()
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void Print() const //const写在后
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year = 2023;//年;可以在声明位置给上初始值,但并非初始化,而是给缺省值
int _month;//月
int _day;//日
};
int main()
{
Date x;
x.Init(2022,3,4);
Date y;
x,Init(2023,3,4)
return 0;
}
● 注意 :**const成员无法调用非const函数 ,而非const成员可以调用const函数。**不过这样不利于封装,这样一来就可以在外部调用到const成员函数调用到private的内部私有成员。
7.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!
int main()
{
Date x;
x.Init(2022,3,4);
Date y;
x,Init(2023,3,4)
return 0;
}
> ● 注意 :**const成员无法调用非const函数 ,而非const成员可以调用const函数。**不过这样不利于封装,这样一来就可以在外部调用到const成员函数调用到private的内部私有成员。
## 7.取地址及const取地址操作符重载
这两个默认成员函数一般不用重新定义 ,编译器默认会生成。
```c++
class Date
{
public :
Date* operator&()
{
return this ;
}
const Date* operator&()const
{
return this ;
}
private :
int _year ; // 年
int _month ; // 月
int _day ; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!