c ++ :對象的構造和析構

目錄

一、構造函數和析構函數

二、 構造函數的分類及調用

三、拷貝構造函數的調用時機

四、構造函數調用規則

五、深拷貝和淺拷貝

六、初始化列表

七、explicit關鍵字


 

對象的初始化和清理也是兩個非常重要的安全問題,一個對象或者變量沒有初始時,對其使用後果是未知,同樣的使用完一個變量,沒有及時清理,也會造成一定的安全問題。c++爲了給我們提供這種問題的解決方案,構造函數析構函數,這兩個函數將會被編譯器自動調用,完成對象初始化和對象清理工作。

 

一、構造函數和析構函數

構造函數主要作用在於創建對象時爲對象的成員屬性賦值,構造函數由編譯器自動調用,無須手動調用。

析構函數主要用於對象銷燬前系統自動調用,執行一些清理工作。

構造函數語法:

  1. 構造函數函數名和類名相同,沒有返回值,不能有void,但可以有參數。
  2. ClassName(){ }

析構函數語法:

  1. 析構函數函數名是在類名前面加”~”組成,沒有返回值,不能有void,不能有參數,不能重載。
  2. ~ClassName(){ }

 

二、 構造函數的分類及調用

  1. 按參數類型:無參構造函數、有參構造函數
  2. 按類型分類:普通構造函數、拷貝構造函數(複製構造函數)

例子:創立一個Person類

1、構造函數的分類

class Person
{
public:

	Person()
	{
		cout << "Person的默認構造函數調用" << endl;
	}

	Person(int a)
	{
		cout << "Person的有參構造函數調用" << endl;
	}

	//拷貝構造  //值傳遞的本質 就是調用 拷貝構造函數
	Person(const Person &p)
	{
		cout << "Person的拷貝構造函數調用" << endl;
		m_Age = p.m_Age;
	}

	~Person()
	{
		cout << "Person的析構函數調用" << endl;
	}

	int m_Age;
};

2、構造函數的調用

void test02()
{
//1 括號法

	Person p1(10); //有參構造函數調用
	p1.m_Age = 18;
	Person p2(p1);  //利用括號法  調用拷貝構造函數
	cout << "p2的年齡爲: " << p2.m_Age << endl;

//注意事項1 : 不要利用括號法 調用默認構造函數 Person p(); 原因將代碼看成 函數的聲明,不會認爲是在創建對象
	Person p();
	void func();

//2 顯示法
	Person p3 = Person(10); //有參構造調用
	//顯示法 調用  拷貝構造函數
	Person p4 = Person(p3);


	Person(10); //單獨寫 Person(10); 稱爲 匿名對象 特點:當本行執行完畢,立即釋放
	cout << "aaaaaaaaa" << endl;

//注意事項2 : 不要利用拷貝構造函數 初始化匿名對象
	Person(p4); // 當寫成Person(p4);  編譯器會認爲寫了 Person p4  如果已經有p4就是重定義 匿名對象放到右值沒問題


//3 隱式轉換法 可讀性低
	Person p5 = 10; //編譯器隱式將代碼轉爲 Person p5 = Person(10); 
	//利用隱式轉換法  調用拷貝構造函數
	Person p6 = p5; // 隱式轉爲 Person p6 = Person(p5);
}

 

   b爲A的實例化對象,A a = A(b) 和 A(b)的區別?

   當A(b) 有變量來接的時候,那麼編譯器認爲是一個匿名對象,當沒有變量來接的時候,編譯器認爲A(b) 等價於 A b.

 

三、拷貝構造函數的調用時機

值傳遞的本質:就是調用 拷貝構造函數

 

3種情況可能用到:

  1. 對象以值傳遞的方式傳給函數參數
  2. 函數局部對象以值傳遞的方式從函數返回(vs debug模式下調用一次拷貝構造,qt不調用任何構造)
  3. 用一個對象初始化另一個對象

用代碼解釋上面3中情況:

class Person
{
public:

	Person()
	{
		cout << "Person的默認構造函數調用" << endl;
	}

	Person(int a)
	{
		cout << "Person的有參構造函數調用" << endl;
	}

	//拷貝構造  //值傳遞的本質 就是調用 拷貝構造函數
	Person(const Person &p)
	{
		cout << "Person的拷貝構造函數調用" << endl;
		m_Age = p.m_Age;
	}

	~Person()
	{
		cout << "Person的析構函數調用" << endl;
	}

	int m_Age;
};

//1、用已經創建好的對象 初始化新的對象
void test01()
{
	Person p1;
	p1.m_Age = 10;

	Person p2(p1);//拷貝構造函數調用
	cout << "p2的年齡爲: " << p2.m_Age << endl;
}

//2、值傳遞的方式 給函數參數傳值
void doWork( Person p)
{

}

void test02()
{
	Person p;
	doWork(p);
}

//3、以值的方式返回局部對象
Person doWork2()
{
	Person p;
	return p;
}

void test03()
{
	Person p = doWork2();
}

 

四、構造函數調用規則

  • 默認情況下,c++編譯器至少爲我們寫的類增加3個函數:
  1. 默認構造函數(無參,函數體爲空)
  2. 默認析構函數(無參,函數體爲空)
  3. 默認拷貝構造函數,對類中非靜態成員屬性簡單值拷貝
  • 如果用戶定義拷貝構造函數,c++不會再提供任何默認構造函數
  • 如果用戶定義了普通構造(非拷貝),c++不在提供默認無參構造,但是會提供默認拷貝構造

 

五、深拷貝和淺拷貝

當類中有指針,並且此指針有動態分配空間,析構函數做了釋放處理,往往需要自定義拷貝構造函數,自行給指針動態分配空間,深拷貝。

如果是深拷貝, 堆區的數據可能會被釋放多次。

拷貝構造函數例子:

class Person{
public:
	Person(char* name,int age){
		pName = (char*)malloc(strlen(name) + 1);
		strcpy(pName,name);
		mAge = age;
	}
	//增加拷貝構造函數
	Person(const Person& person){
		pName = (char*)malloc(strlen(person.pName) + 1);
		strcpy(pName, person.pName);
		mAge = person.mAge;
	}
	~Person(){
		if (pName != NULL){
			free(pName);
		}
	}
private:
	char* pName;
	int mAge;
};

void test(){
	Person p1("Edward",30);
	//用對象p1初始化對象p2,調用c++提供的默認拷貝構造函數
	Person p2 = p1;
}

 

六、初始化列表

注意:初始化成員列表(參數列表)只能在構造函數使用。

例子:

class Person{
public:
#if 0
	//傳統方式初始化
	Person(int a,int b,int c){
		mA = a;
		mB = b;
		mC = c;
	}
#endif
	//初始化列表方式初始化
	Person(int a, int b, int c):mA(a),mB(b),mC(c){}
	void PrintPerson(){
		cout << "mA:" << mA << endl;
		cout << "mB:" << mB << endl;
		cout << "mC:" << mC << endl;
	}
private:
	int mA;
	int mB;
	int mC;
};

 

七、explicit關鍵字

[explicit注意]

  1. explicit用於修飾構造函數,防止隱式轉化
  2. 是針對單參數的構造函數(或者除了第一個參數外其餘參數都有默認值的多參構造)而言
class MyString
{
public:

	explicit MyString(int len)
	{
		cout << "MyString有參構造函數(int  )調用" << endl;
	}

	MyString(char * str)
	{
		cout << "MyString有參構造函數(char * )調用" << endl;
	}

};

void test01()
{
	MyString str = "abcde";
	MyString str2("abcde");
	MyString str3 = MyString("abcde");

	//MyString str4 = 10; // 有人會認爲是  字符串是  "10"  也有人會認爲字符串長度爲10
	//爲了防止這種寫法,可以用關鍵字 explicit

	MyString str5(10);

	MyString str6 = MyString(10);

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章