類的六個默認成員函數

類的六個默認成員函數

寫在前面:
如果我們定義一個空類如下:

class name
{
};

這時候是不是類裏面什麼都沒有呢?
不是!
如果一個類中什麼成員都沒有,簡稱爲空類。空類中什麼都沒有嗎?並不是的,任何一個類在我們不寫的情 況下,都會自動生成下面6個默認的具有一定功能的成員函數。

初始化和清理
構造函數完成初始化工作,析構函數完成一些資源的清理工作
拷貝複製
拷貝構造是完成同類對象初始化創建對象,賦值重載主要把賦值對象給另一個對象
取地址重載
主要是取地址和const對象取地址,這兩個很少自己實現。

我們具體再來說一下吧!

1、構造函數(主要完成初始化)

先來看一下我們一般是如何初始化的。

#include<iostream>
#include<stdlib.h>
using namespace std;
class Date
{
public:
	void Init(int _year,int _month,int _day)
	{
		year=_year;
		month=_month;
		day=_day;
	}
	void Print()
	{
		cout<<year<<" "<<month<<" "<<day<<endl;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	Date d1,d2;
	d1.Init(2019,10,27);
	d1.Print();
	d2.Init(2021,6,8);
	d2.Print();
	system("pause");
	return 0;
}

可以看到對於Date類,可以通過 Init 公有的方法給對象設置內容,但是如果每次創建對象都調用該方法設置信息,未免有點麻煩,那能否在對象創建時,就將信息設置進去呢?

因此引入構造函數的概念

1.定義:

構造函數是一個特殊的成員函數,名字與類名相同,創建類類型對象時由編譯器自動調用,保證每個數據成員 都有一個合適的初始值,並且在對象的生命週期內只調用一次

2.特性:

構造函數是特殊的成員函數,需要注意的是,構造函數的雖然名稱叫構造,但是需要注意的是構造函數的主要任務並不是開空間創建對象,而是初始化對象。

特徵:

  1. 函數名與類名相同。
  2. 無返回值。
  3. 對象實例化時編譯器自動調用對應的構造函數。
  4. 構造函數可以重載
class Date
{
public:
	Date()//無參構造函數,初始化時給的是隨機值
	{}
	Date(int _year,int _month,int _day)//帶參構造函數
	{
		year=_year;
		month=_month;
		day=_day;
	}
void Print()
	{
		cout<<year<<" "<<month<<" "<<day<<endl;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	Date d1;//注意是分號結尾,不然就成了聲明一個返回值爲Date類的無參函數
	d1.Print();
	Date d2(2019,2,3);//調用帶參構造函數
	d2.Print();
	system("pause");
	return 0;
}

這是我們實現的構造函數,那當我們沒有定義構造函數時會怎麼樣呢?

class Date
{
public:
	//Date(int _year,int _month,int _day)//帶餐構造函數
	//{
	//	year=_year;
	//	month=_month;
	//	day=_day;
	//}
void Print()
	{
		cout<<year<<" "<<month<<" "<<day<<endl;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	Date d1;
	d1.Print();
	system("pause");
	return 0;
}

在這裏插入圖片描述
這裏我們可以看到,並沒有構造函數,而我們還是可以成功定義 d1 並完成隨機值的初始化,這是爲什麼呢?

沒有定義構造函數,對象也可以創建成功,因此此處調用的是編譯器生成的默認構造函數

注意:
無參的構造函數和全缺省的構造函數都稱爲默認構造函數,並且默認構造函數只能有一個。注意:無參構造函數、全缺省構造函數、我們沒寫編譯器默認生成的構造函數,都可以認爲是默認成員函數。

class Date
{
public:
	Date()//定義兩個默認構造函數
	{
       year=2018;
	   month=3;
	   day=6;
	}
	Date(int _year=2109,int _month=5,int _day=5)//帶參構造函數
	{
		year=_year;
		month=_month;
		day=_day;
	}
void Print()
	{
		cout<<year<<" "<<month<<" "<<day<<endl;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	Date d1;
	d1.Print();
	//Date d2(2018,5,6);
	//d2.Print();
	system("pause");
	return 0;
}

在這裏插入圖片描述
可以看到這裏報了一個錯和一個警告,第一個是兩個函數構成重載,但函數調用不明確,不能達到初始化的效果,且報了一個警告,顯示指定多個默認構造函數,因此建議大家,儘量定義一個。但是大家還是覺得構造函數沒什麼用處,彆着急,以後深入學習之後,你就會對它有更深刻的理解。

2、析構函數(完成清理工作)

1.定義:

析構函數:與構造函數功能相反,析構函數不是完成對象的銷燬,局部對象銷燬工作是由編譯器完成的。而對象在銷燬時會自動調用析構函數,完成類的一些資源清理工作。

2.特徵:

析構函數是特殊的成員函數。
其特徵如下:

  1. 析構函數名是在類名前加上字符" ~ " 。
  2. 無參數無返回值。
  3. 一個類有且只有一個析構函數,若未顯式定義,系統會自動生成默認的析構函數。
  4. 對象生命週期結束時,C++編譯系統系統自動調用析構函數。

來看一段代碼,看他是否會調用析構函數。

class String
{
public:
	String(const char* _str="xiaoming")
	{
		str=(char*)malloc(strlen(_str)+1);
		strcpy(str,_str);
		cout<<"String"<<endl;
	}
	~String()
	{
		cout<<"~String()"<<endl;
		free(str);
	}
private:
	char *str;
};
class Student
{
public:
	//String b;
};
int main()
{
	String c;
	//a.b.~String();
	system("pause");
	return 0;
}

這裏的結果應該是:
在這裏插入圖片描述
可以看到,調用了我們定義的構造和析構函數。
(在這裏發生了一個小插曲,在我的vs2012無論如何都不調用我定義的析構函數,後來在朋友vs2013上顯示調用了,建議大家還是儘量不要用vs2012的吧,版本問題很難受)

3、拷貝構造函數

1.定義:

構造函數:只有單個形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象 創建新對象時由編譯器自動調用

拷貝構造函數也是特殊的成員函數,其特徵如下:

  1. 拷貝構造函數是構造函數的一個重載形式。
  2. 拷貝構造函數的參數只有一個必須使用引用傳參,使用傳值方式會引發無窮遞歸調用
#include<iostream>
#include<stdlib.h>
using namespace std;
class Date
{
public:
	Date(int _year=1900,int _month=6,int _day=8)
	{
		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;
	Date d2(d1);
	d1.Print();
	d2.Print();
	system("pause");
	return 0;
}

看一下結果:
在這裏插入圖片描述
可以看到,我們就好像給d1造了一雙胞胎一樣,是因爲本質上,拷貝構造與構造函數構成重載。

若未顯示定義,系統生成默認的拷貝構造函數。 默認的拷貝構造函數對象按內存存儲按字節序完成拷貝,這種拷貝我們叫做淺拷貝,或者值拷貝。
比如:

class Date
{
public:
	Date(int _year=1900,int _month=6,int _day=8)
	{
		year=_year;
		month=_month;
		day=_day;
	}
	//這裏我們不定義拷貝構造函數,看編譯器是否會生成默認的構造函數
	void Print()
	{
		cout<<year<<" "<<month<<" "<<day<<endl;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	Date d1;
	Date d2(d1);
	Date d3(d2);
	d1.Print();
	d2.Print();
	d3.Print();
	system("pause");
	return 0;
}

運行結果:
在這裏插入圖片描述
可以看到編譯器是會自動生成一個默認拷貝構造函數的。

現在只是簡單的值拷貝,以後會講到深拷貝。

4.賦值運算符重載

C++爲了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數名的函數,也具有其返回值類型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。

函數名字爲:關鍵字operator後面接需要重載的運算符符號

函數原型:返回值類型 operator操作符(參數列表)

注意:

不能通過連接其他符號來創建新的操作符:比如operator@
重載操作符必須有一個類類型或者枚舉類型的操作數 用於內置類型的操作符,
其含義不能改變,例如:內置的整型+,不能改變其含義。
作爲類成員的重載函數時,其形參看起來比操作數數目少1成員函數的 操作符有一個默認的形參this,限定爲第一個形參
.* 、 ::sizeof?:. 注意以上5個運算符不能重載。

具體來看一個重載**" == "** 來判斷兩個時間是否相同的功能實現:

#include<iostream>
#include<stdlib.h>
using namespace std;
class Date
{
public:
	Date(int _year,int _month,int _day)
	{
		year=_year;
		month=_month;
		day=_day;
	}
	~Date()
	{
		year=0;
		month=0;
		day=0;
	}
//private://這裏定義成public是因爲需要訪問,成員變量,那就失去了封裝性,這個在以後的友元函數再做介紹
	int year;
	int month;
	int day;
};
bool operator==(const Date& d1,const Date& d2)
{
	return d1.year==d2.year&&
		d1.month==d2.month&&
		d1.day==d2.day;
}
void Test()
{
	Date d1(2019,2,3);
	Date d2(2019,2,3);
	Date d3(2019,4,5);
	Date d4(2018,5,4);
	cout<<(d1==d2)<<endl;
	cout<<(d3==d4)<<endl;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

看一下結果:
在這裏插入圖片描述
可以看到,當時間相同時返回1,不同時返回 0。

通過上面的代碼,我們就簡單的實現了,操作符 " == " 的重載。這是我們定義的全局的operator 也可以定義在類裏面作爲成員函數。

class Date
{
public:
	Date(int _year=1900,int _month=1,int _day=1)
	{
		year=_year;
		month=_month;
		day=_day;
	}
	bool operator==(const Date& d2)//這裏有一個默認的形參Date *this
{   //因此不用寫const Date& d1,否則會顯示調用參數過多
	return year==d2.year
		&&month==d2.month
		&&day==d2.day;
}
//private:
	int year;
	int month;
	int day;
};
void Test()
{
	Date d1(2019,3,4);
	Date d2(2019,3,4);
	cout<<(d1==d2)<<endl;
}
int main()
{
	Test();
	system("pause");
	return 0;
}

在這裏插入圖片描述

5、6取地址及const取地址操作符重載

這兩個默認成員函數一般不用重新定義 ,編譯器默認會生成。

class Date
{
public:
	Date* operator&()
	{
		return this;
	}
	const Date* operator&() const
	{
		return this;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	Date d;
	cout<<&d<<endl;
	cout<<&d<<endl;
	system("pause");
	return 0;
}

在這裏插入圖片描述
這兩個運算符一般不需要重載,使用編譯器生成的默認取地址的重載即可,只有特殊情況,才需要重載,比 如想讓別人獲取到指定的內容!

另外再講一下const

1 const修飾類的成員函數
將const修飾的類成員函數稱之爲const成員函數,const修飾類成員函數,實際修飾該成員函數隱含的this 指針,表明在該成員函數中不能對類的任何成員進行修改。

看一個例子:

class Date
{
public:
	void  Init(int _year,int _month,int _day) //const
	{
		year=_year;
		month=_month;
		day=_day;
	}
	void Print() const
	{
		cout<<year<<" "<<month<<" "<<day<<endl;
	}
private:
	int year;
	int month;
	int day;
};
int main()
{
	//const Date d;
	//d.Init(2019,2,3);
    //d.Print();
	Date d1;
	d1.Init(2019,2,3);
    d1.Print();
	system("pause");
	return 0;
}

在這裏插入圖片描述
在這裏插入圖片描述
可以看到當我們要修改某個值時,不能用const來修飾函數,而只讀的話,是允許的,而當const 訪問const時也是可以的。
在這裏插入圖片描述
總結爲下:

const對象調用const對象沒問題
普通對象調用普通對象也沒問題
但const不能調用普通對象(非const對象),而普通對象可以調用const對象。
簡單理解起來就是:權限不能被放大。(例如,我是隻讀,你可以讀,但是你不能修改我)
在這裏插入圖片描述

發佈了25 篇原創文章 · 獲贊 37 · 訪問量 3079
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章