物件的初始化和清理

Thrush 發表於 2021-03-28

物件的初始化和清理

建構函式和解構函式

建構函式:主要作用在建立物件時為 物件成員屬性賦值,建構函式由系統自動呼叫,無需手動呼叫

解構函式:主要作用在物件銷燬前系統自動呼叫,執行一些清理工作。

建構函式語法 類名(){}

1、建構函式,沒有返回值,也不寫void

2、函式名稱與類名相同

3、建構函式可以有引數,因此可以發生過載

4、程式在建立物件時候會自動呼叫構造,無需手動呼叫,而且只會呼叫一次

解構函式語法 ~類名(){}

1、解構函式,沒有返回值也不寫void

2、函式名稱與類名相同,在名稱前面加上符號~

3、解構函式不可以有引數,因此不可以發生過載

4、程式在物件銷燬時會自動呼叫析構,無需手動呼叫而且只會呼叫一次

#include<iostream>
using namespace std;
class Person
{
public:
	Person()
	{
		cout << "Person建構函式的呼叫" << endl;
	}
	~Person()
	{
		cout << "Person解構函式的呼叫" << endl;
	}
};
void test()
{
	Person p;//在棧上的資料,test()執行完畢後,釋放這個物件
}
int main() 
{
	test();
	return 0;
}

建構函式的分類及呼叫

分類

​ 按引數分為:有參構造和無參構造

​ 按型別分為:普通構造和拷貝構造

呼叫

​ 括號法

​ 顯示法

​ 隱式轉換法

例項

#include<iostream>
using namespace std;
class Person
{
public:
	//按引數分為:有參構造和無參構造
	Person()
	{
		cout << "Person無參建構函式的呼叫" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "Person有參建構函式的呼叫" << endl;
	}
	//按型別分為:普通建構函式和拷貝建構函式
	//拷貝建構函式:將傳入人身上所有的屬性,拷貝到我身上
	Person(const Person &p)//傳入的東西本身不能發生改變
	{
		age = p.age;
		cout << "Person拷貝建構函式的呼叫" << endl;
	}


	~Person()
	{
		cout << "Person解構函式的呼叫" << endl;
	}


	int age;
};
//呼叫方法
void test()
{
	//括號法
	Person p1;//預設建構函式的呼叫
	Person p2(4);//有參建構函式的呼叫
	Person p3(p2);//拷貝建構函式的呼叫將p2 的所有屬性拷貝給p3
	//注意事項:不可以利用Person p1();呼叫預設建構函式否則編譯器會認為是一個函式的申明而不是建立物件
	
	//顯示法
	Person p4;//預設建構函式
	Person p5 = Person(4);//有參建構函式
	Person p6 = Person(p5);//拷貝建構函式
	//Person(4);匿名物件當前行結束後系統會立即回收匿名物件
	//不要利用拷貝建構函式初始化匿名物件,編譯器會認為Person(p6)==Person p6;
	//隱式轉換法
	Person p7 = 4;//相當於Person p7轉換為Person P7(4);有參構造
	Person P8 = p7;//拷貝建構函式的隱式轉換法

}
int main() 
{
	test();
	return 0;
}

拷貝建構函式的呼叫時機

#include<iostream>
using namespace std;
class Person
{
public:
	
	Person()
	{
		cout << "Person無參建構函式的呼叫" << endl;
	}
	Person(int a)
	{
		age = a;
		cout << "Person有參建構函式的呼叫" << endl;
	}
	
	Person(const Person &p)
	{
		age = p.age;
		cout << "Person拷貝建構函式的呼叫" << endl;
	}


	~Person()
	{
		cout << "Person解構函式的呼叫" << endl;
	}


	int age;
};

void test01()
{
	//1、使用一個已經建立完畢的物件來初始化一個新物件
	Person p1(90);
	Person p2(p1);	
}
void Work01(Person p)
{

}
void test02()
{
	//2、以值傳遞的方式給函式引數傳值
	Person p3;
	Work01(p3);

}
Person Work02()
{
	Person p ;
	return p;
}
void test03()
{	
	//3、值方式返回區域性物件
	Person p3 = Work02();
}
int main() 
{
	test03();
	return 0;
}

建構函式呼叫規則

預設情況下,C++編譯器至少給一個類新增3個函式

1、預設建構函式(無參,函式體為空)

2、預設解構函式(無參,函式體為空)

3、預設拷貝建構函式,對屬性值進行拷貝

建構函式呼叫規則如下:
1、如果使用者定義有參建構函式,C++不再提供預設無參構造,但是會提供預設拷貝構造

2、如果使用者定義拷貝建構函式,C++不會再提供其他建構函式

深拷貝與淺拷貝

淺拷貝:簡單的賦值拷貝操作

深拷貝:在堆區重新申請空間,進行拷貝操作

#include<iostream>
using namespace std;
class Person
{
public:
	
	Person()
	{
		
		cout << "Person預設建構函式的呼叫" << endl;
	}
	Person(int a,int height)
	{
		m_height = new int(height);
		age = a;
		cout << "Person有參建構函式的呼叫" << endl;
	}
	
	~Person()
	{
		if(m_height!=NULL)
		{
			delete m_height;//釋放開闢的記憶體
			m_height = NULL;//防止野指標出現
		}
		cout << "Person解構函式的呼叫" << endl;
	}


	int age;
	int* m_height;
};

void test01()
{
	
	Person p1(9,150);
	cout << "P1的年齡為" << p1.age << "P1的身高為" <<*p1.m_height << endl;
	Person p2(p1);//編譯器提供的拷貝建構函式,進行淺拷貝操作
	cout << "P2的年齡為" << p2.age << "P2的身高為" << *p2.m_height << endl;
}


int main() 
{
	test01();
	return 0;
}

執行上述程式碼會出現以下情況

物件的初始化和清理

原因分析

淺拷貝導致堆區記憶體重複釋放

物件的初始化和清理

解決方法

自己進行深拷貝,避免記憶體的重複釋放

#include<iostream>
using namespace std;
class Person
{
public:
	
	Person()
	{
		
		cout << "Person預設建構函式的呼叫" << endl;
	}
	Person(int a,int height)
	{
		m_height = new int(height);
		age = a;
		cout << "Person有參建構函式的呼叫" << endl;
	}
	
	~Person()
	{
		if(m_height!=NULL)
		{
			delete m_height;//釋放開闢的記憶體
			m_height = NULL;//防止野指標出現
		}
		cout << "Person解構函式的呼叫" << endl;
	}
	Person(const Person& p)
	{
		cout << "Person的拷貝建構函式呼叫" << endl;
		age = p.age;
		//m_height = p.m_height;
		//編譯器預設提供的簡單的賦值操作
		//自己寫一個深拷貝解決問題
		m_height = new int(*p.m_height);//在堆區重新開闢一塊記憶體,避免記憶體的重複釋放
	}


	int age;
	int* m_height;
};

void test01()
{
	
	Person p1(9,150);
	cout << "P1的年齡為" << p1.age << ",P1的身高為" <<*p1.m_height << endl;
	Person p2(p1);//編譯器提供的拷貝建構函式,進行淺拷貝操作
	cout << "P2的年齡為" << p2.age << ",P2的身高為" << *p2.m_height << endl;
}


int main() 
{
	test01();
	return 0;
}

初始化列表

#include<iostream>
using namespace std;
class Person
{
public:
	
	//初始化列表初始化屬性
	Person(int a, int b, int c) :m_a(a), m_b(b), m_c(c)
	{
			
	}
	int m_a;
	int m_b;
	int m_c;
};
void test01()
{
	
	Person p1(10,20,30);
	cout <<"A="<< p1.m_a << endl;
	cout << "B=" << p1.m_b << endl;
	cout << "C=" << p1.m_c << endl;
	
}
int main() 
{
	test01();
	return 0;
}

類物件作為類成員

C++中的成員可以是另一個類的物件,我們稱該成員為物件成員

例如:

class A {}
class B
{
	A a;
}

B類中有物件A作為成員,A為物件成員

當其他類物件作為本類成員,構造時候先構造類物件,再構造自身

析構與構造相反

#include<iostream>
using namespace std;
#include<string>
class Phone
{
public:
	Phone(string Pname)
	{
		name = Pname;
		cout << "Phone構造" << endl;
	}
	~Phone()
	{
		cout << "Phone析構" << endl;
	}
	 
	string name;
};
class Person
{
public:
	Person(string name, string  Pname): m_name(name), p_name(Pname)
	{
		cout << "Person構造" << endl;
	}
	~Person()
	{
		cout << "Person析構" << endl;
	}
	string m_name;
	Phone p_name;
};
void test01()
{
	
	Person p1("張三","華為");
	cout << p1.m_name << "拿著:" << p1.p_name.name << endl;
}
int main() 
{
	test01();
	return 0;
}

靜態成員

靜態成員就是在成員變數和成員函式前加上關鍵詞static,稱為靜態成員

靜態成員分為:

#### 	靜態成員變數:

所有物件共享同一份資料

在編譯階段分配記憶體

類內申明,類外初始化

#### 	靜態成員函式:

所有物件共享同一個函式

靜態成員函式只能訪問靜態成員變數

#include<iostream>
using namespace std;
#include<string>
class Person
{
public:
	static void age()
	{
		//m_name = 89;
		m_age = 90;//靜態成員函式只能訪問靜態成員變數
		cout << "Person靜態成員函式的呼叫" << endl;
	}
	static int m_age;//類內定義
	int m_name;
private:
	static void name()
	{
		cout << "Person私有靜態成員函式的呼叫" << endl;
	}
};
static int m_age = 9;//類外初始化
//有兩種訪問方式
void test01()
{
	//1,建立物件訪問
	Person p;
	p.age();
	//2,通過類名訪問
	Person::age();
	//Person::name();類外訪問不到私有靜態成員函式
	
}
int main() 
{
	test01();
	return 0;
}