内容包括
-
1.类的6个默认成员函数
-
2.构造函数
-
3 析构函数
-
4.拷贝构造函数
-
5.赋值操作符重载
-
6.默认拷贝构造与赋值运算符重载的问题
1.类的6个默认成员函数
构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载,const修饰的取地址操作符重载
2.构造函数
- 引出概念
对于以下的日期类
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Display()
{
cout <<_year<< "-" <<_month << "-"<< _day <<endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1,d2;
d1.SetDate(2018,5,1);
d1.Display();
Date d2;
d2.SetDate(2018,7,1);
d2.Display();
return 0;
}
对于Date类,可以通过SetDate公有的方法给对象设置内容,但是如果每次创建对象都调用该方法设置信息,未免有点麻烦,那能否在对象创建时,就将信息设置进去呢?
构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有 一个合适的初始值,并且在对象的生命周期内只调用一次。
- 特性
构造函数是特殊的成员函数
1.函数名与类名相同
2.无返回值
3.对象构造(对象实例化)时编译器自动调用对应的构造函数
4.构造函数可以重载
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函数,该函数无参,返回一个日期类型的对象
Date d3();
}
5.构造函数可以在类中定义,也可以在类外定义。
6.如果类中没有显示定义函数,则c++编译器会自动生成一个无参的默认构造函数,一旦用户显示定义编译器将不再生产。
默认生成的构造函数
对于基本类型(int,char,)不做处理
对于自定义类型(class,struct)调用无参默认构造初始化
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
// 没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
Date d;
}
7.无参的构造函数和全缺省的构造函数都称为缺省构造函数,并且缺省构造函数只能有一个。
// 缺省构造函数
class Date
{
public:
Date()
{
_year = 1900 ;
_month = 1 ;
_day = 1;
}
Date (int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private :
int _year ;
int _month ;
int _day ;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;
}
上述代码不能编译通过,因为 “Date”: 指定了两个个缺省构造函数
,使Data对重载函数的调用不明确。
修改下既可以
class Date
{
public:
Date()
{
_year = 1900 ;
_month = 1 ;
_day = 1;
}
Date (int year , 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 ;
};
// 以下测试函数能通过编译吗?
void Test()
{
Date d1;
Date d1(30);
}
结果为:1900-1–1与30-1-1
3.析构函数
- 概念:与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和汕尾工作。
- 特性:
析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
class SeqList
{
public:
SeqList(int capacity = 10)
{
_array = (int*)malloc(sizeof(int)*capacity);
_size = 0;
_capacity = capacity;
}
~SeqList()
{
free(_array);
_array = NULL;
_capacity = _size = 0;
}
private:
int* _array;
size_t _size;
size_t _capacity;
};
析构函数内不是删除对象,而是做一些对象删除前的相关清理工作。
析构函数按照反顺序析构。
4.拷贝构造函数
- 概念:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类型对象创建新对象时由编译器自动到调用。
- 特征
1.拷贝构造函数是构造函数的一个重载形式
2.拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。
3.拷贝构造默认—浅拷贝—按照字节拷贝
但是顺序表那种类就不可以实现。还有深拷贝。
4.若未显示定义,系统会默认生成默认的拷贝函数,默认的拷贝函数会按照成员的声明顺序依次拷贝成员进行初始化。
class Date
{
public:
Date(int year = 1901, 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;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
d1.Print();
Date d2( 10, 21);
d2.Print();
Date d3(2019);
d3.Print();
Date d4(d3); // 拷贝构造 -> 浅拷贝(按字节进行拷贝)
d4.Print();
system("pause");
return 0;
}
5.赋值运算符重载
- 运算符重载
运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似,函数名字为:关键字operator后面接需要重载的运算符符号。返回值类型operator需要重载的操作符(参数列表)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// d1.Equal(&d1, d2)
bool Equal(const Date& d2)
{
return this->_year == d2._year
&& this->_month == d2._month
&& this->_day == d2._day;
}
bool operator==(const Date& d2)
{
return this->_year == d2._year
&& this->_month == d2._month
&& this->_day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018, 10, 20);
Date d2(2018, 10, 20);
cout << d1.Equal(d2) << endl;
cout << (d1 == d2) << endl; // 可读性
cout << d1.operator==(d2) << endl;
//cout << d1.operator==(&d1,d2) << endl;
system("pause");
return 0;
}
注意:
不能通过连接其他符号来创建新的操作符:比如operator@
重载操作符必须有一个类类型或者枚举类型的操作数
用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
作为类成员的重载函数,其形参看起来比操作数数目少1成员函数的
操作符有一个默认的形参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;
}
// d2 = d1
// d2.operator=(d1)
// d2.operator=(&d2, d1);
// d2 = d2
// d1 = d2 = d3
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2018, 10, 20);
Date d2(2018, 10, 21);
Date d3(201,222,333);
d3 = d2 = d1; // d3 = (d2.operator=(&d2, d1))
d2.Print();
d3.Print();
system("pause");
return 0;
}
赋值运算符主要有四点:
- 参数类型
- 返回值
- 检测是否自己给自己赋值
- 返回*this
注意:一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成值的拷贝工作。
6.默认拷贝构造与赋值运算符重载的问题
通过前面的学习知道,如果一个类没有显式定义拷贝构造函数与赋值运算符重载,编译器将会生成一个。
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 ;
};
void Test()
{
Date d1(2018, 9, 26);
Date d2(d1);
Date d3;
d3 = d2;
}
对于上述的日期类以及其Test函数,编译运行不会出现任何问题,那是否就说明拷贝构造函数与赋值运算符重载就是多余的?
答案肯定是不是。
typedef int DataType;
class SeqList
{
public :
SeqList (int capacity = 10)
{
_pData = (DataType*)malloc(capacity * sizeof(DataType));
assert(_pData);
_size = 0;
_capacity = capacity;
}
~SeqList()
{
if(_pData)
{
free(_pData );//释放堆上的空间
_pData = NULL; //将指针置为空
_capacity = 0;
_size = 0;
}
}
private :
int* _pData ;
size_t _size;
size_t _capacity;
};
void Test()
{
SeqList s1(10);
SeqList s2(s1);
SeqList s3;
s3 = s2;
}
对于SeqList类,发现如果使用编译器生成的默认拷贝构造函数与赋值运算符重载,编译没有问题,代码运行也没有任何问题,但是在离开函数作用域销毁对象时,代码崩溃。
崩溃的原因是:当用s1拷贝构造s2时,编译器将s1中的值原模原样的拷贝到s1中,s2给s3赋值也是类似,因为编译器生成的默认拷贝构造与赋值的方式只是简单将一个对象中的内容原封不动的拷贝到另一个对象中,结果导致s1、s2、s3共用同一块资源,在对象销毁时同一块内存销毁多次引起程序崩溃。将编译器生成的默认拷贝构造函数以及赋值运算符重载称之为浅拷贝方式,即值的拷贝。因此如果一个类中涉及到资源管理,不能使用编译器生成的默认拷贝构造以及赋值运算符重载。