首頁 > 軟體

C++的物件特性和友元你真的瞭解嗎

2022-02-08 19:01:16

物件特徵

建構函式和解構函式

物件的初始化和清理也是兩個非常重要的安全問題

一個物件或者變數沒有初始狀態,對其使用後果也是未知

同樣的使用完一個物件或變數,沒有及時清理,也會造成一定的安全問題 

  • 建構函式:主要作用在於建立物件時為物件的成員屬性賦值,建構函式由編譯器自動呼叫,無須手動呼叫
  • 解構函式:主要作用在於物件銷燬前系統自動呼叫,執行一些清理工作

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

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

2.函數名稱與類名相同

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

4.程式在呼叫物件時候會自動呼叫構造,無須手動呼叫,而且只會呼叫一次 

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

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

2.函數名稱與類名相同,在名稱前加上符號~

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

4.程式在呼叫物件前會自動呼叫解構,無須手動呼叫,而且只會呼叫一次 

#include<iostream>
using namespace std;
//物件的初始化和清理
//1.建構函式  實現初始化的操作
class Person {
public:
	//1建構函式
	//沒有返回值  不用寫void
	//函數名 與類名相同
	//建構函式可以有引數,可以發生過載
	//建立物件時,建構函式會自動呼叫,而且只呼叫一次
	Person() {
		cout<< "Person建構函式的呼叫" << endl;
	}
	//2.解構函式  實現清理的操作
	//沒有返回值 不寫void 
	//函數名和類名相同 在名稱前加一個~
	//解構函式不可以有引數,不可以發生過載
	//物件在銷燬前 會自動呼叫解構函式 而且只會呼叫一次
	~Person() {
		cout << "Person解構函式的呼叫" << endl;
	}
};
//構造和解構都是必須有的實現,如果我們自己不提供,編譯器會提供一個空實現的構造和解構
void test01() {
	Person p;//在棧上的資料,test01執行完畢後,釋放這個物件
}
 int main() {
	test01();
	system("pause");
	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;//預設函數呼叫
	//Person p2(10);//有參建構函式
	//Person p3(p2);//拷貝建構函式
	//注意事項
	//呼叫預設建構函式的時候,不要加()
	//因為下面這行程式碼,編譯器會認為是一個函數的宣告,不會認為在建立物件
	//Person p1();
  	/*cout << "p2的年齡:" << p2.age << endl;
	cout << "p3的年齡:" << p3.age << endl;*/
	//2.顯示法
	//Person p1;
	//Person p2 = Person(10);//有參構造
	//Person p3 = Person(p3);//拷貝構造
	//Person(10);//匿名物件 特點:當前行執行結束後,系統會立即回收掉匿名物件
	//
	// 注意事項2
	// 不要利用拷貝建構函式,初始化匿名物件  編譯器會認為Person(p3) === Person p3;物件宣告
	//Person(p3);
 	//3.隱式轉換法
	Person p4 = 10;//相當於 寫了 Person p4 = Person(10); 有參構造
	Person p5 = p4;//拷貝構造
}
int main() {
	test01();
	system("pause");
	return 0;
}

拷貝建構函式呼叫時機

拷貝建構函式呼叫時機通常有三種情況

1.使用一個已經建立完畢的物件來初始化一個新物件

2.值傳遞的方式給函數引數傳值

3.以值方式返回區域性物件

#include<iostream>
using namespace std;
//拷貝建構函式的呼叫時機
//1.使用一個已經建立完畢的物件來初始化一個新物件
//2.值傳遞的方式給函數引數傳值
//3.值方式返回區域性物件
class Person {
public:
	Person() {
		cout << "Person的預設建構函式呼叫" << endl;
	}
	Person(int age) {
		cout << "Person的有參建構函式呼叫" << endl;
		m_Age = age;
	}
	Person(const Person &p) {
		cout << "Person的拷貝建構函式呼叫" << endl;
		m_Age = p.m_Age;
	}
	~Person() {
 		cout << "Person解構函式呼叫" << endl;
	}
	int m_Age;
};
//拷貝建構函式的呼叫時機
//1.使用一個已經建立完畢的物件來初始化一個新物件
void test01() {
	Person p1(20);
	Person p2(p1);
	cout << "p2的年齡為:" << p2.m_Age << endl;
}
//2.值傳遞的方式給函數引數傳值
void doWork(Person p) {
  }
 void test02() {
	Person p;
	doWork(p);
}
 //3.值方式返回區域性物件
Person doWork2() {
	Person p1;
	return p1;
}
void test03() {
	Person p = doWork2();
}
int main() {	
	//test01();
	//test02();
	test03();
	system("pause");
	return 0;
}

 建構函式呼叫規則

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

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

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

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

建構函式呼叫規則如下

如果使用者定義有參建構函式,c++不再提供預設無參構造,但是會提供預設拷貝構造

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

#include<iostream>
using namespace std;
//建構函式的呼叫規則
//1.建立了一個類,c++編譯器會給每個類都新增至少三個函數
//預設構造  (空實現)
//解構函式  (空實現)
//拷貝構造  (值拷貝)
//2.如果我們寫了有參建構函式,編譯器就不再提供預設構造,依然提供拷貝構造
//如果我們寫了拷貝建構函式,編譯器不再提供其他建構函式了
class Person {
public:
	/*Person() {
		cout << "Person的預設建構函式呼叫" << endl;
	}*/
	/*Person(int age) {
		cout << "Person的有參建構函式呼叫" << endl;
		m_Age = age;
	}*/
	Person(const Person& p) {
		cout << "Person的拷貝建構函式呼叫" << endl;
		m_Age = p.m_Age;
}
 	~Person() {
		cout << "Person的解構函式呼叫" << endl;
	}
	int m_Age;
};
//void test01() {
//	Person p;
//	p.m_Age = 18;
//	Person p2(p);
//	cout << "p2的年齡為:" << p2.m_Age << endl;
//}
void test02() {
	Person p(28);
	Person p2(p);
	cout << "p2的年齡為:" << p2.m_Age << endl;
}
int main() {
	//test01();
	test02();
	system("pause");
	return 0;
}

深拷貝與淺拷貝

  • 淺拷貝:簡單的賦值拷貝操作
  • 深拷貝:在堆區重新申請空間,進行拷貝操作
#include<iostream>
using namespace std;
//深拷貝與淺拷貝
class Person {
public:
	Person() {
		cout << "Person的預設建構函式呼叫" << endl;
	}
	Person(int age,int height) {
		m_Age = age;
		m_Height = new int(height);
		cout << "Person的有參建構函式呼叫" << endl;
	}
	Person(const Person &p) {
		cout << "Person 拷貝建構函式的呼叫" << endl;
		m_Age = p.m_Age;
		//m_Height = p.m_Height; 編譯器預設實現就是這行程式碼
		//深拷貝操作
		//如果不利於深拷貝在堆區建立記憶體,會導致淺拷貝帶來的重複釋放堆區問題
		m_Height = new int(*p.m_Height);
	}
	~Person() {
		//解構堆區,將堆區開闢資料做釋放操作
		if (m_Height != NULL) {
			delete m_Height;
			m_Height = NULL;
			//淺拷貝帶來的問題就是堆區的問題重複釋放
			//淺拷貝的問題 要利用深拷貝進行解決
		}
		cout << "Person的解構函式呼叫" << endl;
	}
	int m_Age;//年齡
	int* m_Height;//身高
};
void test01() {
	Person p1(18,160);
	cout << "p1的年齡為:" << p1.m_Age <<"身高為:"<<*p1.m_Height << endl;
	Person p2(p1);
	cout << "p2的年齡為:" << p2.m_Age <<"身高為:"<<*p2.m_Height << endl;
}
int main() {
	test01();
	system("pause");
	return 0;
}

如果屬性有在堆區開闢的,一定要自己提供拷貝建構函式,防止淺拷貝帶來的問題 

初始化列表

作用:c++提供了初始化列表語法,用來初始化屬性

語法:建構函式():屬性1(值1),屬性2(值2)...{}

#include<iostream>
using namespace std;
//初始化列表
class Person {
public:
	//傳統初始化操作
	/*Person(int a, int b, int c) {
		m_A = a;
		m_B = b;
		m_C = c;
	}*/
	//初始化列表初始化屬性
	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 p(10, 20, 30);
	Person p(30,20,10);
	cout << "m_A = " << p.m_A << endl;
	cout << "m_B = " << p.m_B << endl;
	cout << "m_C = " << p.m_C << endl;
  }
int main() {
	test01();
	system("pause");
	return 0;
}

類物件作為類變數

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

例如:

class A{}

class B

{

         A a;

}

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

#include<iostream>
using namespace std;
//類物件作為類成員
//手機類
class Phone {
public:
	Phone(string pName) {
		cout << "Phone的建構函式呼叫" << endl;
		m_PName = pName;
	}
	~Phone() {
		cout << "Phone的解構函式呼叫" << endl;
	}
	//手機品牌名稱
	string m_PName;
 };
//人類
class Person {
public:
	//Phone m_Phone = pName  隱式轉換法
	Person(string name, string pName):m_Name(name),m_Phone(pName)
	{
		cout << "Person的建構函式呼叫" << endl;
	}
	~Person() {
		cout << "Person的解構函式呼叫" << endl;
	}
	//姓名
	string m_Name;
	//手機
	Phone m_Phone;
};
//當其他類物件作為本類成員,構造時先構造類物件,再構造自身,解構的順序與構造相反
void test01() {
	Person p("張三","蘋果");
	cout << p.m_Name << "拿著" << p.m_Phone.m_PName << endl;
 }
int main() {
	test01();
	system("pause");
	return 0;
}

靜態成員

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

靜態成員分為:

靜態成員變數

所有物件共用一份資料

在編譯階段分配記憶體

類內宣告,類外初始化

靜態成員函數

所有物件共用同一個函數

靜態成員函數只能存取靜態成員變數 

#include<iostream>
using namespace std;
//靜態成員函數
//所有物件共用同一個函數
//靜態成員函數只能存取靜態成員變數
class Person {
public:
	//靜態成員函數
	static void func() {
		m_A = 100;//靜態成員函數可以存取靜態成員變數
		//m_B = 200;//靜態成員函數不可以存取非靜態成員變數,無法區分到底是哪個m_B
		cout << "static void func的呼叫" << endl;
	}
	static int m_A;//靜態成員變數
	int m_B;//非靜態成員變數
	//靜態成員函數也是有存取許可權的
private:
	static void func2() {
		cout << "static void func2的呼叫" << endl;
	}
};
int Person::m_A = 0;
//有兩種存取方式
void test01() {
	//1.通過物件進行存取
	Person p;
	p.func();
	//2.通過類名存取
	Person::func();
	//Person::func2();類外存取不到私有的靜態成員函數
}
int main() {
	test01();
	system("pause");
	return 0;
}

成員變數和成員函數分開儲存

在c++中,類的成員變數和成員函數分開儲存

只有非靜態成員變數才屬於類的物件上

#include<iostream>
using namespace std;
//成員變數和成員函數是分開儲存的
class Person {
	int m_A;//非靜態成員變數  屬於類的物件上的
	static int m_B;//靜態成員變數  不屬於類的物件上
	void func() {}//非靜態成員函數  不屬於類的物件上
	static void func2() {}//靜態成員函數  不屬於類的物件上
};
int Person::m_B = 0;
void test01() {
	Person p;
	//空物件佔用的記憶體空間為:1
	//c++編譯器會給每個空物件也分配一個位元組空間,是為了區分空物件佔記憶體的位置
	//每個空物件也應該有一個獨一無二的記憶體地址
	cout << "size of p = "<<sizeof(p) << endl;
}
void test02() {
	Person p;
	cout << "size of p = " << sizeof(p) << endl;
}
int main() {
	//test01();
	test02();
	system("pause");
	return 0;
}

this指標的用途

  • this指標指向被呼叫的成員函數所屬的物件
  • this指標是隱含每一個非靜態成員函數內的一種指標
  • this指標不需要定義,直接使用即可

this指標的用途:

當形參和成員變數同名時,可用this指標來區分

在類的非靜態成員函數中返回物件本身,可使用return * this返回 

#include<iostream>
using namespace std;
class Person {
public:
	Person(int age) {
		//this指標指向被呼叫的成員函數所屬的物件
		this->age = age;
	}
	int age;
	Person PersonAddAge(Person &p) {
		this->age += p.age;
		//this指向p2的指標,而*this指向的就是p2這個物件本體
		return *this;
	}
};
 //1.解決名稱衝突
void test01() {
	Person p1(18);
	cout << "p1的年齡為:" << p1.age << endl;
}
//2.返回物件本身用*this
void test02() {
	Person p1(10);
	Person p2(10);
	//鏈式程式設計思想
	p2.PersonAddAge(p1).PersonAddAge(p1).PersonAddAge(p1);
	cout << "p2的年齡為:" << p2.age << endl;
}
 int main() {
	//test01();
	test02();
	system("pause");
	return 0;
}

空指標存取成員

c++中空指標也是可以呼叫成員函數的,但是也要注意有沒有用到this指標

如果用到this指標,需要加以判斷保證程式碼的健壯性

#include<iostream>
using namespace std;
//空指標呼叫成員函數
class Person {
public:
 	void showClassName() {
		cout << "this is Person class" << endl;
	}
	void showPersonAge() {
		//報錯原因是因為傳入的指標是為NULL
		if (this == NULL)
		{
			return;
		}
		cout << "age = " <<this-> m_Age << endl;
	}
	int m_Age;
};
void test01() {
	Person* p = NULL;
	//p->showClassName();
	p->showPersonAge();
}
int main() {
	test01();
 	system("pause");
	return 0;
}

const修飾成員函數

常函數

成員函數後加const後我們稱這個函數為常函數

函數內不可以修改成員屬性

成員屬性宣告時加關鍵字mutable後,在常函數中依然可以修改

常物件:

宣告物件前加const稱該物件為常物件

常物件只能呼叫常函數 

#include<iostream>
using namespace std;
//空指標呼叫成員函數
class Person {
public:
 	void showClassName() {
		cout << "this is Person class" << endl;
	}
	void showPersonAge() {
		//報錯原因是因為傳入的指標是為NULL
		if (this == NULL)
		{
			return;
		}
		cout << "age = " <<this-> m_Age << endl;
	}
	int m_Age;
};
void test01() {
	Person* p = NULL;
	//p->showClassName();
	p->showPersonAge();
}
int main() {
	test01();
 	system("pause");
	return 0;
}

友元

友元的目的就是讓一個函數或者類存取另一個類中私有成員 

友元關鍵字為friend  

友元的三種實現:

  • 全域性函數做友元
  • 類做友元
  • 成員函數做友元

全域性函數做友元

#include<iostream>
using namespace std;
#include<string>
//建築物類
class Building {
	//goodGay全域性函數是Building好朋友,可以存取Building中私有成員
	friend void goodGay(Building* building);
public:
	Building() {
		m_SittingRoom = "客廳";
		m_BedRoom = "臥室";
	}
public:
	string m_SittingRoom;//客廳
private:
	string m_BedRoom;//臥室
};
//全域性函數
void goodGay(Building *building) {
	cout << "好基友全域性函數 正在存取:" << building->m_SittingRoom << endl;
	cout << "好基友全域性函數 正在存取:"<<building->m_BedRoom << endl;
}
void test01() {
	Building building;
	goodGay(&building);
}
int main() {
	test01();
	system("pause");
	return 0;
}

類做友元

#include<iostream>
using namespace std;
#include<string>
//類做友元
class Building;
class GoodGay {
public:
	GoodGay();
	void visit();//參觀函數 存取Building中的屬性
	Building* building;
};
class Building {
	//GoodGay類是本來的好朋友,可以存取本類中私有成員
	friend class GoodGay;
public:
	Building();
public:
	string m_SittiingRoom;//客廳
private:
	string m_BedRoom;//臥室
};
//類外寫成員函數
Building::Building() {
	m_SittiingRoom = "客廳";
	m_BedRoom = "臥室";
}
GoodGay::GoodGay() {
	//建立建築物物件
	building = new Building;
}
void GoodGay::visit() {
	cout << "好基友類正在存取:"<<building->m_SittiingRoom << endl;
	cout << "好基友類正在存取:" << building->m_BedRoom << endl;
}
void test01() {
	GoodGay gg;
	gg.visit();
}
int main() {
	test01();
	system("pause");
	return 0;
}

成員函數做友元

#include<iostream>
using namespace std;
#include<string>
class Building;
class GoodGay {
public:
	GoodGay();
	void visit();//讓visit函數可以存取Building中的私有成員
	void visit2();//讓visit函數不可以存取Building中的私有成員
	Building* building;
};
class Building {
	//告訴編譯器 GoodGay類下的visit成員函數作為本類的好朋友,可以存取私有成員
	friend void GoodGay:: visit();
public:
	Building();
public:
	string m_SittingRoom;//客廳
private:
	string m_BedRoom;//臥室
 };
//類外實現成員函數
Building::Building() {
	m_SittingRoom = "客廳";
	m_BedRoom = "臥室";
}
GoodGay::GoodGay() {
	building = new Building;
}
void GoodGay::visit() {
	cout << "visit函數正在存取"<<building->m_SittingRoom << endl;
	cout << "visit函數正在存取" << building->m_BedRoom << endl;
}
void GoodGay::visit2() {
	cout << "visit2函數正在存取" << building->m_SittingRoom << endl;
	//cout << "visit2函數正在存取" << building->m_BedRoom << endl;
}
void test01() {
	GoodGay gg;
	gg.visit();
	gg.visit2();
}
int main() {
	test01();
 	system("pause");
	return 0;
}

總結

本篇文章就到這裡了,希望能夠給你帶來幫助,也希望您能夠多多關注it145.com的更多內容!       


IT145.com E-mail:sddin#qq.com