天天看點

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修飾,函數本身依然沒有傳回值)