天天看点

C++类与对象(中)1.类的6个默认成员函数2.构造函数3.析构函数4. 拷贝构造函数5.赋值运算符重载6.取地址及const取地址操作符重载7const成员

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

类六个默认函数包括构造、拷贝构造、析构、赋值运算符重载、取地址操作符重载、const修饰的取地址操作符重载,默认生成

2.构造函数

构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,保证每个数据成员

都有 一个合适的初始值,并且在对象的生命周期内只调用一次

特性如下

1. 函数名与类名相同。

2. 无返回值。(也没有void返回值)

3. 对象实例化时编译器自动调用对应的构造函数。

4. 构造函数可以重载

  如果类中没有显式定义构造函数,则C++编译器会自动生成一个无参的默认构造函数,一旦用户显式定

义编译器将不再生成。

构造出的对象元素为随机值

重点:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员

class A
{
private:
int a;
public:

A(int b=0)
{
......
}

A()
{
......
}

}
           

上面的代码会编译错误,因为程序无法判断,当设置一个无参数传递的对象调用哪一个构造函数。

小知识点:C++把类型分成内置类型(基本类型)和自定义类型。内置类型就是语法已经定义好的类型:如int/char...,自定义类型就是我们使用class/struct/union自己定义的类型,编译器生成默认的构造函数会对自定类型成员t调用的它的默认成员如下

#include <iostream>
using namespace std;
class A
{
	int _a;
	public:
	A(int a=0)
	{
	a = _a;
	cout<<"使用a的构造函数"<<endl;	
	}	
};
class B
{
	int _b;
	A a;
	public:
};
int main()
{
	B b;
}
           

小知识点:作为成员变量命名最好使用_加名字用来提高代码的可读性,也有加m_(member的首字母)的写法

3.析构函数

与构造函数相对,析构函数:与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作,如内存的释放,指针的归零等。

析构函数是特殊的成员函数。其特征如下:

1. 析构函数名是在类名前加上字符 ~。

2. 无参数无返回值。

3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。系统会自动生成默认的析构函数。

4. 拷贝构造函数

属于构造函数的一种,参数为类的类型的引用,作用用来拷贝一个对象就像是ctrl c ctrl v的功能一样,例子如下

#include <iostream>
using namespace std;
class A
{
	int _a;
	public:
	A(int a=0)
	{
	a = _a;
	cout<<"使用a的构造函数"<<endl;	
	}	
	A(A& a)
	{
	_a = a._a;
	}	
};

int main()
{
	B b;
}
           

注意的是系统默认的拷贝函数使用的是浅拷贝,逐个字节进行拷贝,对于一般类型的成员不会出错,但是对于指针成员,拷贝出的新的指针成员,会指向相同的地址,产生一系列的麻烦。需要人为写拷贝函数,进行深拷贝。

知识点:拷贝函数的形参必须是引用传递,否则会引起无穷递归问题,

A(A a1)
	{
	_a = a1._a;
	}//值传递
//需要给 形参赋值,调用拷贝函数
	A(A a1)//上一个的形参
	{
	_a1 = a1._a;
	}//值传递给 形参的形参 赋值,调用拷贝函数
A(A a1)
	{
	_a1 = a1._a;
	}//值传递给 形参的形参 的形参赋值,调用拷贝函数
A(A a1)
	{
	_a1 = a1._a;
	}//值传递给 形参的形参 的形参 的形参赋值,调用拷贝函数
A(A a1)
	{
	_a1 = a1._a;
	}//值传递给 形参的形参 的形参 的形参 的形参赋值,调用拷贝函数
           

5.赋值运算符重载

根据逻辑,有时候需要比较两个对象的大小或其他属性,但是C++不支持自定义类型的对象的比较大小等操作需要重载运算符,

函数名字为:关键字operator后面接需要重载的运算符符号。

函数原型:返回值类型 operator操作符(参数列表)

但是重载需要注意以下几点

1.重载无法创造新的运算符如@等

2.重载操作数至少有一个类类型或者联合类型

3.无法重载内置类型(int,char等)的操作符

4.共有5个操作符无法重载:.* 、:: 、sizeof 、?: 、. (注意第一个是点*而不是*)

例子如下为对日期的重载:为初始版本十分臃肿,精简的方法为函数的复用:具体看

这里:https://blog.csdn.net/cat_want_fly/article/details/86506354

#include <iostream>
using namespace std;
class Date
{
public:
	
		int GetMonthDay(int year, int month)
{
int monthArray[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31};
if (month == 2 && year%4 == 0&&year%100 !=0||year%400 ==0)
return 29;
else
return monthArray[month];
}

	Date(int year = 2019, int month = 1, int day = 1)
{
	if (year > 0 
	&&month > 0 
	&& month < 13
	&&day > 0
	&&day <= GetMonthDay(year, month))
	{
	_year = year;
	_month = month;
	_day = day;
	}
	// 四个成员函数

else
	{
	cout << "日期非法" << endl;
	_year = 0;
	_month = 0;
	_day = 0 ;
	}
}

print()
{
	cout<<_year<<"-"<<_month<<"-"<<_day<<endl;
}
Date(Date &date)
	{
		_year = date._year;
		_month= date._month;
		_day = date._day; 
	}
           
Date& operator+=(int day)
{   _day += day; 
	while(_day>GetMonthDay( _year, _month))
	{
		if(_month==12)
		{
			_month = 0;
			_year++;
		}
		if(_month!=0)
		{
		_day -= GetMonthDay(_year,  _month);
		_month++;
		}
		if(_month==0)
		{
			_day -= GetMonthDay(_year, 12);
		_month++;
		}
	}
return *this;
}
;

Date &operator-=(int day)
{
	_day -= day; 
	while(_day<1&&_year>0)
	{
	_month--;
	if(_month==0)
	{
		_month = 12;
		_year--; 
	}
	 _day+=GetMonthDay( _year, _month);
	 
	}
return *this;
};
// 前置
Date operator++()
{
	(*this)+=1;
}
;
Date operator--()
{
	(*this)-=1;
};

// 后置返回 改变数值之前的对象一个拷贝,
Date operator++(int)
{
	Date copy((*this));
	(*this)+=1;
	return copy;
}
;
Date operator--(int)
{
	Date copy((*this));
	(*this)-=1;
	return copy;
}
;

// d1-d2

void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}

~Date()
{
	//cout<<"调用析构函数"<<endl;
	_year = 0;
	_month = 0;
	_day = 0; 
} 

private:
	int _year;
	int _month;
	int _day;


};
           

重载完毕后要注意符号优先度如

cout<<d1==d2<<endl;//错误,d1先与<<运算,d2与<<运算,最后进行判断相等操作
cout<< (d1==d2) <<endl;//正确
           

小知识点:

为什么前置++比后置++重载时少了一个int型的形参?

答:因为前置和后置的运算符一样,所以即使对其进行运算符重载,编译器也无法区分两者,所以这是对同名函数的重载,同名函数的重载需要不同的形参列表,所以int型的形参就是为了区分两个操作符函数,int 本身的值无意义。

6.取地址及const取地址操作符重载

这两个默认函数一般不需要重载,编译器会默认生成

如果重载,一般时为了封装,防止访问到对象的地址,

Date*	operator&()
	{
		return nullptr;
	}
           

如果设置一个Date类型的对象,并用&访问它的地址会返回00000000.

7const成员

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this

指针,表明在该成员函数中不能对类的任何成员进行修改。以上面的代码为例

相当于将隐含的this指针形参由Date *转化为了 const Date *

1.const对象可以调用非const成员函数吗?

答:不可以,非const成员函数形参为非const类型,传参时编译器试图将const类型的指针转化成非const类型,无法实现操作。

2. 非const对象可以调用const成员函数吗?

答:可以,允许非const变量转化为const变量,this指针的类型可以转化。

3. const成员函数内可以调用其它的非const成员函数吗?

答:不可以,调用非const成员函数,就可能对const变量进行修改,这是不允许的,

c++规定 const成员函数内不能调用其它的非const成员函数

4. 非const成员函数内可以调用其它的const成员函数吗?

答:可以,理由和第二题相同

(注意唯一的例外是析构函数,C++标准中说

A destructor can be invoked for a const, volatile or const volatile object.

析构函数可以为const、volatile或const volatile对象调用析构函数。

因为析构函数和资源的回收有关,如果无法访问const函数就会造成内存泄漏)

const也可以用来重载函数

class A{
private:
int i;
public:
void f() 
{
cout<<"f()"<<endl;
}

void f() const
{
cout<<"f() const"<<endl;
}

}
           

const类型的对象调用const修饰的函数,非const类型的对象调用非const修饰函数,

(注意const写在形参括号的右边,如果写在了函数名的左边,编译器会认为函数返回类型为const,如果是对void修饰,函数本身依然没有返回值)