c++类与对象之默认成员函数

c++类与对象(二)

1.类的6个默认成员函数

在这里插入图片描述

一:构造函数

  • 构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员都有一个合适的初始值,并且在对象的生命周期内只调用一次。
  • 构造函数是特殊的成员函数,其特征如下:
  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();
}
  1. 构造函数可以在类中定义,也可以在类外定义。
  2. 如果类中没有显式定义构造函数,则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;
}
  1. 无参的构造函数和全缺省的构造函数都称为缺省构造函数,并且缺省构造函数只能有一个。
// 缺省构造函数
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对象应该调用哪一个构造函数。所以会报错。

析构函数

  • 与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类 的一些资源清理和汕尾工作。
  • 析构函数是特殊的成员函数,其特征如下:
  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4. 对象生命周期结束时,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修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。
  • 拷贝构造函数也是特殊的成员函数,其特征如下:
  • 拷贝构造函数是构造函数的一个重载形式。
  • 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。如下图
    在这里插入图片描述
  • 若未显示定义,系统会默认生成默认的拷贝构造函数。 默认的拷贝构造函数会按照成员的声明顺序依次 拷贝类成员进行初始化。
    **请注意:**对象的赋值都是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。而对象的复制则是从无到有建立一个新对象,并使它与一个已有的对象完全相同(包括对象的结构和成员的值)。
  • 普通构造函数和拷贝构造函数的区别:
  1. 形式:
类名(形参表列);  //普通构造函数的声明,如Box(int h, int w, int len);
类名(类名& 对象名);  //拷贝构造函数的声明,如Box(Box &b);
  1. 在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或拷贝构造函数。
Box box1(12,15,16);  //实参为整数,调用普通构造函数
Box box2(box1);   //实参是对象名,调用拷贝构造函数
  1. 在什么情况下调用
  • 普通构造函数在程序中建立对象时被调用。
  • 拷贝构造函数在用一个已有对象复制一个新对象时被调用。
  • 函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时,此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。
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;
}

注意

  • 不能通过连接其他符号来创建新的操作符:比如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;
	}
	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;
}

  • 赋值运算符主要有四点:
  1. 参数类型
  2. 返回值
  3. 检测是否自己给自己赋值
  4. 返回*this

注意:一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成值的拷贝工作。

哪些运算符不能重载?

  • C++中不能重载的运算符:“?:”、“.”、“::”、“sizeof”和”.*”
  • 原因如下:
  • 在具体讲解各个运算符不能重载之前,先来说明下【重载】:重载的本意是让操作符可以有新的语义,而不是更改语法——否则会引起混乱。
    【注】重载的部分规则:运算符函数的参数至少有一个必须是类的对象或者类的对象的引用。
  1. ?:”运算符,假如能够重载,那么问题来了,看下面的语句:
 exp1?exp2:exp3

该运算符的本意是执行exp2和exp3中的一个,可是重载后,你能保证只执行了一个吗?还是说两个都能执行?亦或两条都不能执行? “?:”运算符的跳转性质就不复存在了,这就是“?:”运算符不能够被重载的最主要原因。

  1. “.”运算符,假如能够重载,那么,问题来了,看下面的例子:
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++语言了。

  1. “::”运算符,M::a,该运算符只是在编译的时候域解析,而没有运算的参与进来,由前面【注】重规则可知,如果重载之后,::运算符有了新的语义,那是不是会引起混淆呢?

  2. “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)并不一定是该类占用的字节大小!
  1. ”.*”引用指向类成员的指针
    以上的5个运算符是不能重载的。

还有两个默认成员函数我没有实现,过两天我会补上。

猜你喜欢

转载自blog.csdn.net/qq_40421919/article/details/83447942