1. 程式人生 > >c++建構函式總結

c++建構函式總結

1、使用建構函式的目的:為了在定義的時候,馬上給變數進行賦值(防止多執行緒賦值出錯)。

2、解構函式 : ~+類名  沒有任何的引數,也沒有返回值。

3、建構函式的幾點總結:

3.1 建構函式名字跟型別是一樣的,沒有返回值,但可以過載。

3.2 如果顯示的寫了一個普通建構函式, 會隱藏預設的無慘建構函式,但不會隱藏預設的拷貝建構函式。

3.3 如果顯示的寫了一個拷貝建構函式 ,會隱藏預設的無參建構函式和預設的拷貝建構函式

3.4 如果顯示的寫了一個解構函式, 會隱藏預設的解構函式

3.5 不要在建構函式中巢狀建構函式

4、關於拷貝建構函式:

class Test
{
public:

	Test(int x, int y)
	{
		m_x = x;
		m_y = y;
	}
	
	//拷貝建構函式 ,想通過另一個Test物件 將本物件進行拷貝。
	Test(const Test & another)//注意這裡不能寫成指標 const Test * another 拷貝建構函式的形式是固定的。
	{
		m_x = another.m_x;
		m_y = another.m_y;
	}
	
	//等號操作符
	void operator = (const Test &t)
	{
		m_x = t.m_x;
		m_y = t.m_y;
	}

private:
	int m_x;
	int m_y;
};
//呼叫拷貝建構函式的兩種方式
Test t2(10, 20);

Test t3(t2); //呼叫t3的拷貝建構函式  

Test t4 = t2; //呼叫t4的拷貝建構函式

Test t5; //先呼叫無慘構造。
t5 = t2; //不會呼叫拷貝建構函式,而是呼叫=號過載操作符,因為建構函式只有在初始化的時候會呼叫,賦值是不會呼叫建構函式的。

5、建構函式執行過程分析

class Test
{
public:
	Test(int x, int y)
	{

		m_x = x;
		m_y = y;
		cout << "呼叫了有引數的建構函式" << endl;
	}

	//無引數的建構函式
	Test(){
		m_x = 0;
		m_y = 0;
		cout << "呼叫了無引數的建構函式" << endl;
	}


	//拷貝建構函式 
	Test(const Test & another)
	{
		m_x = another.m_x;
		m_y = another.m_y;
		cout << "呼叫了拷貝建構函式" << endl;
	}

	//等號操作符
	void operator = (const Test &t)
	{
		cout << "呼叫了=號操作符" << endl;
		m_x = t.m_x;
		m_y = t.m_y;
	}

	void printT()
	{
		cout << "x : " << m_x << ", y : " << m_y << endl;
	}

	//提供一個解構函式
	~Test()
	{
		cout << "~Test()解構函式被執行了" << endl;
		cout << "(" << m_x << ", " << m_y << ")" << "被析構了" << endl;
	}

private:
	int m_x;
	int m_y;
};



Test func()
{
	cout << "func begin..." << endl;
	Test temp(10, 20); //呼叫temp的帶引數建構函式  
	Test temp1(100, 200); //呼叫temp的帶引數建構函式  
	cout << "func end.." << endl;
	return temp; //建立一個臨時的匿名物件 = temp ,把temp的資料給到了臨時的匿名物件。
				//呼叫這個臨時匿名物件的拷貝建構函式, 將temp傳進去。
}

void test1() 
{
	cout << "test1 begin " << endl;
	func();

	/*
	匿名物件在此被析構了, 如果一個臨時的匿名物件,沒有任何變數去接收它,
	編譯器認為這個臨時匿名物件沒有用處。會立刻銷燬這個臨時的匿名物件。
	*/
	cout << "test1 end" << endl;
}

/*
呼叫test1的執行結果:
test1 begin 
func begin...
呼叫了有引數的建構函式
呼叫了有引數的建構函式
func end..
~Test()解構函式被執行了
(100, 200)被析構了
~Test()解構函式被執行了
(10, 20)被析構了
test1 end

*/

void test2()
{
	cout << "test2 begin ..." << endl;
	Test t1 = func();
	
	
	//如果有一個變數去接收這個臨時的匿名物件, 編譯器認為這個匿名物件有用,就不會立刻給他銷燬。
	
	/*
	t1 = 匿名的臨時物件 為什麼不會發生拷貝構造??
	
	此時的t1 去接收這個匿名的臨時物件,不是重新建立一個t1,而是給這個匿名物件起個名字就叫t1。
	一旦這個匿名物件有了自己的名字,編譯器就不會立刻給這個匿名物件銷燬了,就當普通區域性變數處理了。
	
	可以理解為匿名變數只有靈魂,沒有肉體,執行 Test t1 = func() 相當於讓 t1 變成他的肉體。
	
	再說說拷貝構造:
	Test t2(10,20);
	Test t1 = t2; 
	這種情況是拷貝構造,通過一個物件給另一個物件賦值,t1,t2都是有靈魂有肉體的。
	*/
	cout << "test2 end..." << endl;

	//在此時析構的t1
}

/*
呼叫test2的執行結果:
test2 begin ...
func begin...
呼叫了有引數的建構函式
呼叫了有引數的建構函式
func end..
~Test()解構函式被執行了
(100, 200)被析構了
test2 end...
~Test()解構函式被執行了
(10, 20)被析構了

下面是分析內容:
為什麼只被析構了兩次??
因為 Test t1 = func(); 從巨集觀上看只有一個變數t1。
*/

void test3()
{
	cout << "test3 begin..." << endl;
	Test t1; //呼叫t1的無引數建構函式
	t1 = func(); 
	/*
	呼叫了t1的=號操作符 , t1 = 匿名物件。
	此時匿名物件並沒有找到一個宿主,因為t1本身就已經存在了,
	所以編譯器就會立刻銷燬他。
	*/
	cout << "test3 end..." << endl;
}

/*
呼叫test3的執行結果:
test3 begin...
呼叫了無引數的建構函式
func begin...
呼叫了有引數的建構函式
呼叫了有引數的建構函式
func end..
~Test()解構函式被執行了
(100, 200)被析構了
呼叫了=號操作符
~Test()解構函式被執行了
(10, 20)被析構了
test3 end...
~Test()解構函式被執行了
(10, 20)被析構了


下面是分析內容:
為什麼被析構了三次??
因為從巨集觀上看有兩個變數,一個是t1,一個是匿名的臨時變數。
*/

int main(void)
{
	
	// test1();
	// test2();
	test3();


	return 0;
}

6、淺拷貝

class Teacher
{
public:
	//有引數的建構函式
	Teacher(int id, const char *name)
	{
		cout << "呼叫了Teacher 的建構函式" << endl;
		//是給id 賦值
		m_id = id;

		//給姓名賦值
		int len = strlen(name);
		m_name = (char*)malloc(len + 1);
		strcpy(m_name, name);
	}

	~Teacher() {
		//在建構函式中, 已經開闢了記憶體 所以為了防止洩露
		//在解構函式中,在物件銷燬之前,把m_name的記憶體釋放掉
		if (m_name != NULL) {
			free(m_name);
			m_name = NULL;
			cout << "釋放掉了m_name" << endl;
		}
	}
private:
	int m_id;
	char *m_name;
};

int main(void)
{
	Teacher t1(1, "zhang3");

	//如果不提供一個顯示的拷貝建構函式, 通過系統自帶的預設拷貝建構函式
	Teacher t2(t1); //會呼叫t2的拷貝建構函式,將t1的值拷貝給t2
	
	return 0;
}

/*
執行結果:	
呼叫了Teacher 的建構函式
釋放掉了m_name
*** Error in `./a.out': double free or corruption (fasttop): 0x08c79008 ***
Aborted (core dumped)

原因:呼叫了系統預設的拷貝建構函式,指標只是進行了簡單的賦值,兩個物件t1,t2
內部的指標成員變數指向的是同一個地址。當t2物件析構後,釋放了這塊記憶體。但t1
物件中這個指標值還不是null,所以又進行了一次釋放,造成記憶體洩露。
*/

7、深拷貝

class Teacher
{
public:
	//有引數的建構函式
	Teacher(int id, const char *name)
	{
		cout << "呼叫了Teacher 的建構函式" << endl;
		//是給id 賦值
		m_id = id;

		//給姓名賦值
		int len = strlen(name);
		m_name = (char*)malloc(len + 1);
		strcpy(m_name, name);
	}

	//顯示寫一個拷貝建構函式
	//通過顯示拷貝建構函式提供了深拷貝的動作
	Teacher(const Teacher &another)
	{
		m_id = another.m_id; //給id賦值

		int len = strlen(another.m_name);
		m_name = (char*)malloc(len + 1);

		strcpy(m_name, another.m_name);
	}
	~Teacher() {
		//在建構函式中, 已經開闢了記憶體 所以為了防止洩露
		//在解構函式中,在物件銷燬之前,把m_name的記憶體釋放掉
		if (m_name != NULL) {
			free(m_name);
			m_name = NULL;
			cout << "釋放掉了m_name" << endl;
		}
	}
private:
	int m_id;
	char *m_name;
};

int main(void)
{
	Teacher t1(1, "zhang3");
	
	Teacher t2(t1); 
	
	return 0;
}
/*
執行結果:	
呼叫了Teacher 的建構函式
釋放掉了m_name
釋放掉了m_name
*/

總結:當類的成員變數有指標時,一定要手動寫拷貝建構函式。否則會造成淺拷貝。

8、建構函式的初始化列表

class A
{
public:
	A(int a)
	{
		m_a = a;
		cout << "a = " << m_a << "呼叫了建構函式" << endl;
	}
	
	void printA()
	{
		cout << " a = " << m_a << endl;
	}
	
	~A()
	{
		cout << "a = " << m_a << "被析構了" << endl;
	}

private:
	int m_a;
};

class B
{
public:
	B(int b) :m_a1(10), m_a2(100) //在初始化B的時候通過初始化列表給內部物件a1 和a2 進行了初始化
	{
		m_b = b;
		/*
		m_a1(10);//這樣子是不行的
		m_a2(20);
		*/
		cout << "b = " << m_b << "呼叫了建構函式" << endl;
	}
	
	B(A &a1,A &a2,int b) :m_a1(a1), m_a2(a2) //呼叫A的拷貝建構函式,可以在A中手動新增拷貝建構函式進行測試。
	{
		m_b = b;
		cout << "b = " << m_b << "呼叫了建構函式" << endl;
	}

	B(int aa1, int aa2, int b) : m_a1(aa1), m_a2(aa2), m_b(b) //通過初始化列表不僅能夠初始化成員物件, 還可以初始化成員變數 
	{
		//其中	m_a1(aa1), m_a2(aa2) 會呼叫A類的有參建構函式
	}

	~B()
	{
		cout <<"b = " <<m_b << " 呼叫了解構函式" << endl;
	}

private:
	int m_b;
	// const int m_m; //常量成員變數不能夠賦值,只能通過初始化列表進行初始化
	A m_a2;//這樣寫初始化列表就會先初始化 m_a2 再初始化 m_a1 。跟初始化列表中的順序無關
	A m_a1;
};

使用場景:

1、類中包含其他類

2、類中存在常量