- 类的六个默认成员函数:
1、构造函数
- 概念:
(1)构造函数是用于构造新对象,并将初始值赋给对象的数据成员。
(2)类型转化,适用于单参的构造函数。
用explicit修饰构造函数,抑制由构造函数定义的隐式转换,erplicit关键字类内部的构建声明上,在类的定义体外部的定义上不再重复。
- 特征:
(1)构造函数的函数名与类名相同;
(2)构造函数无返回值;
(3)对象构造时系统自动调用相应的构造函数;
(4)构造函数可以重载;
(5)构造函数可以在类内/类外定义;
(6)无参的构造函数和全缺的构造函数只能有一个;
(7)不能用const, virtual修饰 ;
(8)没有显式定义,编译器会自动合成一个。
- 举例子:
#include<iostream>
using namespace std;
class Data
{
public:
Data(int year = 1990, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Data B(1996, 10, 6);
B.Display();
return 0;
}
- 初始化列表:
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year,int month,int day);//声明构造函数Date的原型
void setDate(int year,int month,int day);
void showDate();
private:
int _year;
int _month;
int _day;
};
Date::Date(int year,int month,int day)//定义构造函数Date
:_year(year)//成员初始化列表
,_month(month)
,_day(day)
{}
void Date::setDate(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
void Date::showDate()
{
cout<<_year<<"->"<<_month<<"->"<<_day<<endl;
}
int main()
{
Date date(2016,1,1);
date.setDate(2016,9,27);
date.showDate();
system("pause");
return 0;
}
初始化列表比函数内初始化更高效!
对于自定义类型,在初始化时系统会自动生成初始化列表(即,自定义类型,构造时会先走一遍初始化列表),然后再调用一遍函数体内的初始化,也就是初始化了两遍。可以说,初始化列表是成员变量定义的地方。
那些构造函数只能在初始化列表中初始化?(那些构造函数只能自己写,不能用系统自动生成的?)
(1)const修饰的常量,在定义时初始化;
(2)引用变量,必须在定义时初始化,且只能初始化一次;
(3)没有缺省构造函数的自定义的成员变量。
- 如果一个类中没有定义任何的构造函数,那么编译器只有在以下三种情况,才会提供默认的构造函数:
(1)如果类有虚拟成员函数或者虚拟继承父类(即有虚拟基类)时;
(2)如果类的基类有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数);
(3)在类中的所有非静态的对象数据成员,它们对应的类中有构造函数(可以是用户定义的构造函数,或编译器提供的默认构造函数)
- 无参构造函数和带有缺省值得构造函数都认为是缺省构造函数,并且缺省构造函数只能有一个。
因为在调用是如果出现Date( ),编译器并不知道该调用哪一个构造函数;
Date( int year = 1990, int month = 1, int day = 1)
{
}
//
Date( )
{
}
【思考】为什么构造函数不能用const修饰?
`
2、拷贝构造函数
- 概念:
用于根据一个已经存在的对象复制出一个新的该类的对象,一般在函数中会将已经存在对象的数据成员的值复制一份到新的对象中,如果没有写拷贝构造函数,则系统会默认创建一个复制构造函数,当类中有指针成员时,由系统默认创建该构造函数会存在风险。
- 特征:
(1)创建对象时使用同类进行初始化,是特殊的构造函数,遵循构造函数特征;
(2)拷贝构造函数其实是一个构造函数的重载;
(3)拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用;
(4)若为显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会依次拷贝类成员进行初始化。
- 使用场景:
这是函数定义:
【使用场景】
(1)对象实例化对象
Date d1(1990, 1, 1);
Date d2(d1);
(2)传值方式作为函数的参数
void FunTest(const Date date)
{}
(3)传值方式作为函数返回值
Date FunTest()
{
Date date;
return date;
}
3、析构函数:
- 概念:
与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作。
- 特征:
(1)析构函数在类名(即构造函数名)加上字符~。
(2)析构函数无参数无返回值。
(3)一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
(4)对象生命周期结束时,C++编译系统系统自动调用析构函数。
(5)注意析构函数体内并不是删除对象,而是做一些清理工作(清理对象资源)。
(6) 认识到析构函数体本身并不直接销毁成员是非常重要的。成员是在析构函数体之后隐含的析构阶段中被销毁的。
- 【什么时候会调用析构函数】
无论何时,一个对象被销毁,就会自动调用其析构函数:
(1)变量离开其作用域是被销毁;
(2)当一个对象销毁时,其成员被销毁;
(3)容器(无论是标准库容器还是数组)被销毁时,其元素被销毁;
(4)对于动态分配的对象,当指向它的指针用delete运算符被销毁;
(5)对于临时对象,当创建它的完整表达式结束时被销毁。
- 【 warning】
当指向一个对象的引用或指针离开作用域时,析构函数不会执行。
- 基类的析构函数加上virtual关键字—采用虚析构函数避免内存泄漏。
虚析构函数:如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
4、赋值运算符重载
- 研究赋值操作符重载之前,先搞清楚运算符重载是什么?
运算符重载的实质就是函数重载或函数多态。
运算符重载是一种形式的C++多态。目的在于让人能够用同名的函数来完成不同的基本操作。要重载运算符,需要使用被称为运算符函数的特殊函数形式
运算符函数形式:operator p(argument-list)//p为运算符
运算符函数的参数至少有一个必须是类的对象或者类的对象的引用。这种规定可以防止改变内置类型的函义。
- 5个不能重载的运算符:
. (成员访问运算符)
.* (成员指针访问运算符)
:: (域运算符)
sizeof (长度运算符)
?: (条件运算符)
1、不能通过连接其他符号来创建新的操作符:比如operator@;
void operator @(){}
2、重载操作符必须有一个类类型或者枚举类型的操作数
int operator +(const int _iNum1 , const int _iNum2 ) // 报错
{
return ( _iNum1 + _iNum2);
}
typedef enum TEST {one ,two ,three };
int operator+(const int _iNum1 , const TEST _test )
{
return _iNum1;
}
3、用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
5、不在具备短求职特性
重载操作符不能保证操作符的求职顺序,在重载&&和||中,对每个操作数
都要进行求值,而且对操作数的求职顺序不能做规定,因此:重载&&、
||和逗号操作符不是好的做法。
6、作为类成员的重载函数,其形参看起来比操作数数目少1
成员函数的操作符有一个默认的形参this,限定为第一个形参。
CTest operator+(const CTest test1, const CTest test2)const // 报错
{
return test1;
}
CTest operator+(const CTest test1)const
{
return test1;
}
7、一般将算术操作符定义为非成员函数,将赋值运算符定义成员函数
8、操作符定义为非类的成员函数时,一般将其定义为类的友元函数
9、== 和 != 操作符一般要成对重载
10、下标操作符[]:一个非const成员并返回引用,一个是const成员并返回引用
11、解引用操作符*和->操作符,不显示任何参数
12、自增自减操作符
前置式++/–必须返回被增量或者减量的引用
后缀式操作符必须返回旧值,并且应该是值返回而不是引用返回
13、输入操作符>>和输出操作符<<必须定义为类的友元函数
【建议】
使用重载操作符,可以令程序更自然、更直观,而滥用操作符重载会使得类难以理解,在实践中很少发生明显的操作符重载滥用。但有些程序员会定义operator+来执行减法操作,当一个重载操作符不明确时,给操作符取一个名字更好,对于很少用的操作,使用命名函数通常比用操作符好,如果不是普通操作,没有必要为简洁而用操作符。
赋值操作符重载;(以Date类为例)
Date& operator=(const Date& d)//定义为类的成员函数
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this ;
}
取地址操作符重载和const修饰的取地址操作符重载;
class CTest
{
public:
CTest* operator&()
{
return this ;
}
const CTest* operator &()const
{
return this ;
}
};