c++类与对象之默认成员函数
c++类与对象(二)
1.类的6个默认成员函数
一:构造函数
- 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
- 构造函数是特殊的成员函数,其特征如下:
- 函数名与类名相同。
- 无返回值。
- 对象构造(对象实例化)时编译器自动调用对应的构造函数。
- 构造函数可以重载。
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();
}
- 构造函数可以在类中定义,也可以在类外定义。
- 如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定义编译器将不再生成。
class Date
{
public:
void SetDate(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
// 如果用户显式定义了构造函数,编译器将不再生成
Date (int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
void Test()
{
// 加入没有定义构造函数,对象也可以创建成功,因此此处调用的是编译器生成的默认构造函数
Date d;
}
- 无参的构造函数和全缺省的构造函数都称为缺省构造函数,并且缺省构造函数只能有一个。
// 缺省构造函数
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;
}
以上测试函数能通过编译吗?
不能,当创建对象d1时,程序不知道d1对象应该调用哪一个构造函数。所以会报错。
析构函数
- 与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类 的一些资源清理和汕尾工作。
- 析构函数是特殊的成员函数,其特征如下:
- 析构函数名是在类名前加上字符 ~。
- 无参数无返回值。
- 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
- 对象生命周期结束时,C++编译系统系统自动调用析构函数。
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;
};
- 注意:析构函数体内不是删除对象,而是做一些对象删除前的相关清理工作。
拷贝构造函数
- 只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
- 拷贝构造函数也是特殊的成员函数,其特征如下:
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。如下图
- 若未显示定义,系统会默认生成默认的拷贝构造函数。 默认的拷贝构造函数会按照成员的声明顺序依次 拷贝类成员进行初始化。
**请注意:**对象的赋值都是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。而对象的复制则是从无到有建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)。 - 普通构造函数和拷贝构造函数的区别:
- 形式:
类名(形参表列); //普通构造函数的声明,如Box(int h, int w, int len);
类名(类名& 对象名); //拷贝构造函数的声明,如Box(Box &b);
- 在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或拷贝构造函数。
Box box1(12,15,16); //实参为整数,调用普通构造函数
Box box2(box1); //实参是对象名,调用拷贝构造函数
- 在什么情况下调用
- 普通构造函数在程序中建立对象时被调用。
- 拷贝构造函数在用一个已有对象复制一个新对象时被调用。
- 函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时,此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。
Box f()
{
Box box1(12,15,18);
return box1;
}
int main()
{
Box box2;
box2=f();
return 0;
}
赋值操作符重载
- 运算符重载
- 运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似,函数名字为:关键字operator后面接需要重载的运算符符号。返回值类型
operator 需要重载的操作符(参数列表)
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date (const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date operator+(int days)
{
Date temp(*this);
temp._day += days;
return temp;
}
private:
int _year;
int _month;
int _day;
};
void Test ()
{
Date d(2018, 9, 26);
d = d + 10;
}
注意:
- 不能通过连接其他符号来创建新的操作符:比如[email protected]
- 重载操作符必须有一个类类型或者枚举类型的操作数
- 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员的重载函数,其形参看起来比操作数数目少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;
}
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;
};
int main()
{
Date d1;
Date d2(d1);
Date d3(2018, 10, 27);
d2 = d3;
}
- 赋值运算符主要有四点:
- 参数类型
- 返回值
- 检测是否自己给自己赋值
- 返回*this
注意:一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成值的拷贝工作。
哪些运算符不能重载?
- C++中不能重载的运算符:“?:”、“.”、“::”、“sizeof”和”.*”
- 原因如下:
- 在具体讲解各个运算符不能重载之前,先来说明下【重载】:重载的本意是让操作符可以有新的语义,而不是更改语法——否则会引起混乱。
【注】重载的部分规则:运算符函数的参数至少有一个必须是类的对象或者类的对象的引用。
- “?:”运算符,假如能够重载,那么问题来了,看下面的语句:
exp1?exp2:exp3
该运算符的本意是执行exp2和exp3中的一个,可是重载后,你能保证只执行了一个吗?还是说两个都能执行?亦或两条都不能执行? “?:”运算符的跳转性质就不复存在了,这就是“?:”运算符不能够被重载的最主要原因。
- “.”运算符,假如能够重载,那么,问题来了,看下面的例子:
class Y
{
public:
void fun();
// ...
};
class X
{ // 假设可以重载"."运算符
public:
Y* p;
Y& operator.()
{
return *p;
}
void fun();
// ...
};
void g(X& x){
x.fun(); //请告诉我,这里的函数fun()到底是X的,还是Y的?
}
“.”运算符的本意是引用对象成员的,然而被重载后就不能保证本意,从而带来运算符意义的混淆,如果每个人都这么重载,那更不容易学习C++语言了。
-
“::”运算符,M::a,该运算符只是在编译的时候域解析,而没有运算的参与进来,由前面【注】重规则可知,如果重载之后,::运算符有了新的语义,那是不是会引起混淆呢?
-
“sizeof”运算符,该运算符不能被重载的主要原因是内部许多指针都依赖它,举例说明重载的后果:
A b[10];//A是类
A* p = &a[3];
A* q = &a[3];
p++;//执行后,p指向a[4],记住是指向a[4]!根据C++规定,该操作等同于p+sizeof(A),此时p应该比q大A类所占字节的大小,事实上,p并不一定会比q大这么多,因为你把sizeof()运算符重载了啊!这时的sizeof(A)并不一定是该类占用的字节大小!
- ”.*”引用指向类成员的指针
以上的5个运算符是不能重载的。