【C++】面向對象之類和對象(上篇)-003

第四章 類和對象


4.1 類和對象的基本概念


4.1.1 C和C++中struct區別

  • c語言struct只有變量
  • c++語言struct 既有變量,也有函數

4.1.2 類的封裝

我們編寫程序的目的是爲了解決現實中的問題,而這些問題的構成都是由各種事物組成,我們在計算機中要解決這種問題,首先要做就是要將這個問題的參與者:事和物抽象到計算機程序中,也就是用程序語言表示現實的事物。

那麼現在問題是如何用程序語言來表示現實事物?現實世界的事物所具有的共性就是每個事物都具有自身的屬性,一些自身具有的行爲,所以如果我們能把事物的屬性和行爲表示出來,那麼就可以抽象出來這個事物。

比如我們要表示人這個對象,在c語言中,我們可以這麼表示:

typedef struct _Person{
	char name[64];
	int age;
}Person;
typedef struct _Aninal{
	char name[64];
	int age;
	int type; //動物種類
}Ainmal;

void PersonEat(Person* person){
	printf("%s在喫人喫的飯!\n",person->name);
}
void AnimalEat(Ainmal* animal){
	printf("%s在喫動物喫的飯!\n", animal->name);
}

int main(){

	Person person;
	strcpy(person.name, "小明");
	person.age = 30;
	AnimalEat(&person);

	return EXIT_SUCCESS;
}

定義一個結構體用來表示一個對象所包含的屬性,函數用來表示一個對象所具有的行爲,這樣我們就表示出來一個事物,在c語言中,行爲和屬性是分開的,也就是說喫飯這個屬性不屬於某類對象,而屬於所有的共同的數據,所以不單單是PeopleEat可以調用Person數據,AnimalEat也可以調用Person數據,那麼萬一調用錯誤,將會導致問題發生。

從這個案例我們應該可以體會到,屬性和行爲應該放在一起,一起表示一個具有屬性和行爲的對象。

假如某對象的某項屬性不想被外界獲知,比如說漂亮女孩的年齡不想被其他人知道,那麼年齡這條屬性應該作爲女孩自己知道的屬性;或者女孩的某些行爲不想讓外界知道,只需要自己知道就可以。那麼這種情況下,封裝應該再提供一種機制能夠給屬性和行爲的訪問權限控制住。

所以說封裝特性包含兩個方面,一個是屬性和變量合成一個整體,一個是給屬性和函數增加訪問權限。

  • 封裝
  1. 把變量(屬性)和函數(操作)合成一個整體,封裝在一個類中
  2. 對變量和函數進行訪問控制
  • 訪問權限
  1. 在類的內部(作用域範圍內),沒有訪問權限之分,所有成員可以相互訪問
  2. 在類的外部(作用域範圍外),訪問權限纔有意義:publicprivateprotected
  3. 在類的外部,只有public修飾的成員才能被訪問,在沒有涉及繼承與派生時,privateprotected是同等級的,外部不允許訪問
訪問屬性 屬性 對象內部 對象外部
public 公有 可訪問 可訪問
private 私有 可訪問 不可訪問
protected 保護 可訪問 不可訪問
//封裝兩層含義
//1. 屬性和行爲合成一個整體
//2. 訪問控制,現實事物本身有些屬性和行爲是不對外開放
class Person{
//人具有的行爲(函數)
public:
	void Dese(){ cout << "我有錢,年輕,個子又高,就愛嘚瑟!" << endl;}
//人的屬性(變量)
public:
	int mTall; //多高,可以讓外人知道
protected:
	int mMoney; // 有多少錢,只能兒子孫子知道
private:
	int mAge; //年齡,不想讓外人知道
};

int main(){

	Person p;
	p.mTall = 220;
	//p.mMoney 保護成員外部無法訪問
	//p.mAge 私有成員外部無法訪問
	p.Dese();

	return EXIT_SUCCESS;
}

[struct和class的區別?]
.
class默認訪問權限爲private,struct默認訪問權限爲public.

class A{
	int mAge;
};
struct B{
	int mAge;
};

void test(){
	A a;
	B b;
	//a.mAge; //無法訪問私有成員
	b.mAge; //可正常外部訪問
}

4.1.3 將成員變量設置爲private

1. 可賦予客戶端訪問數據的一致性。

如果成員變量不是public,客戶端唯一能夠訪問對象的方法就是通過成員函數。如果類中所有public權限的成員都是函數,客戶在訪問類成員時只會默認訪問函數,不需要考慮訪問的成員需不需要添加(),這就省下了許多搔首弄耳的時間。

2. 可細微劃分訪問控制。

使用成員函數可使得我們對變量的控制處理更加精細。如果我們讓所有的成員變量爲public,每個人都可以讀寫它。如果我們設置爲private,我們可以實現“不準訪問”、“只讀訪問”、“讀寫訪問”,甚至你可以寫出“只寫訪問”。

class AccessLevels{
public:
	//對只讀屬性進行只讀訪問
	int getReadOnly(){ return readOnly; }
	//對讀寫屬性進行讀寫訪問
	void setReadWrite(int val){ readWrite = val; }
	int getReadWrite(){ return readWrite; }
	//對只寫屬性進行只寫訪問
	void setWriteOnly(int val){ writeOnly = val; }
private:
	int readOnly; //對外只讀訪問
	int noAccess; //外部不可訪問
	int readWrite; //讀寫訪問
	int writeOnly; //只寫訪問
};

4.1.3課堂練習

請設計一個Person類,Person類具有nameage屬性,提供初始化函數(Init),並提供對nameage的讀寫函數(set,get),但必須確保age的賦值在有效範圍內(0-100),超出有效範圍,則拒絕賦值,並提供方法輸出姓名和年齡.(10分鐘)

4.2 面向對象程序設計案例


4.2.1 設計立方體類

設計立方體類(Cube),求出立方體的面積( 2*a*b + 2*a*c + 2*b*c )和體積( a * b * c),分別用全局函數和成員函數判斷兩個立方體是否相等。
在這裏插入圖片描述

//立方體類
class Cub{
public:
	void setL(int l){ mL = l; }
	void setW(int w){ mW = w; }
	void setH(int h){ mH = h; }
	int getL(){ return mL; }
	int getW(){ return mW; }
	int getH(){ return mH; }
	//立方體面積
	int caculateS(){ return (mL*mW + mL*mH + mW*mH) * 2; }
	//立方體體積
	int caculateV(){ return mL * mW * mH; }
	//成員方法
	bool CubCompare(Cub& c){
		if (getL() == c.getL() && getW() == c.getW() && getH() == c.getH()){
			return true;
		}
		return false;
	}
private:
	int mL; //長
	int mW; //寬
	int mH; //高
};

//比較兩個立方體是否相等
bool CubCompare(Cub& c1, Cub& c2){
	if (c1.getL() == c2.getL() && c1.getW() == c2.getW() && c1.getH() == c2.getH()){
		return true;
	}
	return false;
}

void test(){
	Cub c1, c2;
	c1.setL(10);
	c1.setW(20);
	c1.setH(30);

	c2.setL(20);
	c2.setW(20);
	c2.setH(30);

	cout << "c1面積:" << c1.caculateS() << " 體積:" << c1.caculateV() << endl;
	cout << "c2面積:" << c2.caculateS() << " 體積:" << c2.caculateV() << endl;

	//比較兩個立方體是否相等
	if (CubCompare(c1, c2)){
		cout << "c1和c2相等!" << endl;
	}
	else{
		cout << "c1和c2不相等!" << endl;
	}

	if (c1.CubCompare(c2)){
		cout << "c1和c2相等!" << endl;
	}
	else{
		cout << "c1和c2不相等!" << endl;
	}
}

4.2.2 點和圓的關係

設計一個圓形類(AdvCircle),和一個點類(Point),計算點和圓的關係。
假如圓心座標爲x0, y0, 半徑爲r,點的座標爲x1, y1

1)點在圓上:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) == r*r
2)點在圓內:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) < r*r
3)點在圓外:(x1-x0)*(x1-x0) + (y1-y0)*(y1-y0) > r*r

//點類
class Point{
public:
	void setX(int x){ mX = x; }
	void setY(int y){ mY = y; }
	int getX(){ return mX; }
	int getY(){ return mY; }
private:
	int mX;
	int mY;
};

//圓類
class Circle{
public:
	void setP(int x,int y){
		mP.setX(x);
		mP.setY(y);
	}
	void setR(int r){ mR = r; }
	Point& getP(){ return mP; }
	int getR(){ return mR; }
	//判斷點和圓的關係
	void IsPointInCircle(Point& point){
		int distance = (point.getX() - mP.getX()) * (point.getX() - mP.getX()) + (point.getY() - mP.getY()) * (point.getY() - mP.getY());
		int radius = mR * mR;
		if (distance < radius){
			cout << "Point(" << point.getX() << "," << point.getY() << ")在圓內!" << endl;
		}
		else if (distance > radius){
			cout << "Point(" << point.getX() << "," << point.getY() << ")在圓外!" << endl;
		}
		else{
			cout << "Point(" << point.getX() << "," << point.getY() << ")在圓上!" << endl;
		}
	}
private:
	Point mP; //圓心
	int mR; //半徑
};

void test(){
	//實例化圓對象
	Circle circle;
	circle.setP(20, 20);
	circle.setR(5);
	//實例化點對象
	Point point;
	point.setX(25);
	point.setY(20);

	circle.IsPointInCircle(point);
}

4.3 對象的構造和析構


4.3.1 初始化和清理

我們大家在購買一臺電腦或者手機,或者其他的產品,這些產品都有一個初始設置,也就是這些產品對被創建的時候會有一個基礎屬性值。那麼隨着我們使用手機和電腦的時間越來越久,那麼電腦和手機會慢慢被我們手動創建很多文件數據,某一天我們不用手機或電腦了,那麼我們應該將電腦或手機中我們增加的數據刪除掉,保護自己的信息數據。

從這樣的過程中,我們體會一下,所有的事物在起初的時候都應該有個初始狀態,當這個事物完成其使命時,應該及時清除外界作用於上面的一些信息數據。

那麼我們c++中OO思想也是來源於現實,是對現實事物的抽象模擬,具體來說,當我們創建對象的時候,這個對象應該有一個初始狀態,當對象銷燬之前應該銷燬自己創建的一些數據。

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

無論你是否喜歡,對象的初始化和清理工作是編譯器強制我們要做的事情,即使你不提供初始化操作和清理操作,編譯器也會給你增加默認的操作,只是這個默認初始化操作不會做任何事,所以編寫類就應該順便提供初始化函數。

爲什麼初始化操作是自動調用而不是手動調用?既然是必須操作,那麼自動調用會更好,如果靠程序員自覺,那麼就會存在遺漏初始化的情況出現。

4.3.1 構造函數和析構函數

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

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

構造函數語法:

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

析構函數語法:

  • 析構函數函數名是在類名前面加”~”組成,沒有返回值,不能有void,不能有參數,不能重載。
  • ~ClassName(){}
class Person{
public:
	Person(){
		cout << "構造函數調用!" << endl;
		pName = (char*)malloc(sizeof("John"));
		strcpy(pName, "John");
		mTall = 150;
		mMoney = 100;
	}
	~Person(){
		cout << "析構函數調用!" << endl;
		if (pName != NULL){
			free(pName);
			pName = NULL;
		}
	}
public:
	char* pName;
	int mTall;
	int mMoney;
};

void test(){
	Person person;
	cout << person.pName << person.mTall << person.mMoney << endl;
}

4.3.1 構造函數的分類及調用

  • 按參數類型:分爲無參構造函數和有參構造函數
  • 按類型分類:普通構造函數和拷貝構造函數(複製構造函數)
class Person{
public:
	Person(){
		cout << "no param constructor!" << endl;
		mAge = 0;
	}
	//有參構造函數
	Person(int age){
		cout << "1 param constructor!" << endl;
		mAge = age;
	}
	//拷貝構造函數(複製構造函數) 使用另一個對象初始化本對象
	Person(const Person& person){
		cout << "copy constructor!" << endl;
		mAge = person.mAge;
	}
	//打印年齡
	void PrintPerson(){
		cout << "Age:" << mAge << endl;
	}
private:
	int mAge;
};
//1. 無參構造調用方式
void test01(){
	
	//調用無參構造函數
	Person person1; 
	person1.PrintPerson();

	//無參構造函數錯誤調用方式
	//Person person2();
	//person2.PrintPerson();
}
//2. 調用有參構造函數
void test02(){
	
	//第一種 括號法,最常用
	Person person01(100);
	person01.PrintPerson();

	//調用拷貝構造函數
	Person person02(person01);
	person02.PrintPerson();

	//第二種 匿名對象(顯示調用構造函數)
	Person(200); //匿名對象,沒有名字的對象

	Person person03 = Person(300);
	person03.PrintPerson();

	//注意: 使用匿名對象初始化判斷調用哪一個構造函數,要看匿名對象的參數類型
	Person person06(Person(400)); //等價於 Person person06 = Person(400);
	person06.PrintPerson();

	//第三種 =號法 隱式轉換
	Person person04 = 100; //Person person04 =  Person(100)
	person04.PrintPerson();

	//調用拷貝構造
	Person person05 = person04; //Person person05 =  Person(person04)
	person05.PrintPerson();
}

b爲A的實例化對象,A a = A(b) 和 A(b)的區別?
.
當A(b) 有變量來接的時候,那麼編譯器認爲他是一個匿名對象,當沒有變量來接的時候,編譯器認爲A(b) 等價於 A b.

**注意:**不能調用拷貝構造函數去初始化匿名對象,也就是說以下代碼不正確:

class Teacher{
public:
	Teacher(){
		cout << "默認構造函數!" << endl;
	}
	Teacher(const Teacher& teacher){
		cout << "拷貝構造函數!" << endl;
	}
public:
	int mAge;
};
void test(){
	
	Teacher t1;
	//error C2086:“Teacher t1”: 重定義
	Teacher(t1);  //此時等價於 Teacher t1;
}

4.3.2 拷貝構造函數的調用時機

  • 對象以值傳遞的方式傳給函數參數
  • 函數局部對象以值傳遞的方式從函數返回(vs debug模式下調用一次拷貝構造,qt不調用任何構造)
  • 用一個對象初始化另一個對象
class Person{
public:
	Person(){
		cout << "no param contructor!" << endl;
		mAge = 10;
	}
	Person(int age){
		cout << "param constructor!" << endl;
		mAge = age;
	}
	Person(const Person& person){
		cout << "copy constructor!" << endl;
		mAge = person.mAge;
	}
	~Person(){
		cout << "destructor!" << endl;
	}
public:
	int mAge;
};
//1. 舊對象初始化新對象
void test01(){

	Person p(10);
	Person p1(p);
	Person p2 = Person(p);
	Person p3 = p; // 相當於Person p2 = Person(p);
}

//2. 傳遞的參數是普通對象,函數參數也是普通對象,傳遞將會調用拷貝構造
void doBussiness(Person p){}

void test02(){
	Person p(10);
	doBussiness(p);
}

//3. 函數返回局部對象
Person MyBusiness(){
	Person p(10);
	cout << "局部p:" << (int*)&p << endl;
	return p;
}
void test03(){
	//vs release、qt下沒有調用拷貝構造函數
	//vs debug下調用一次拷貝構造函數
	Person p = MyBusiness();
	cout << "局部p:" << (int*)&p << endl;
}

Test03結果說明:

編譯器存在一種對返回值的優化技術,RVO(Return Value Optimization).在vs debug模式下並沒有進行這種優化,所以函數MyBusiness中創建p對象,調用了一次構造函數,當編譯器發現你要返回這個局部的對象時,編譯器通過調用拷貝構造生成一個臨時Person對象返回,然後調用p的析構函數。

我們從常理來分析的話,這個匿名對象和這個局部的p對象是相同的兩個對象,那麼如果能直接返回p對象,就會省去一個拷貝構造和一個析構函數的開銷,在程序中一個對象的拷貝也是非常耗時的,如果減少這種拷貝和析構的次數,那麼從另一個角度來說,也是編譯器對程序執行效率上進行了優化。

所以在這裏,編譯器偷偷幫我們做了一層優化:
當我們這樣去調用: Person p = MyBusiness();
編譯器偷偷將我們的代碼更改爲:

 void MyBussiness(Person& _result){
       _result.X:X(); //調用Person默認拷貝構造函數
       //.....對_result進行處理
       return;
   }
int main(){
   Person p; //這裏只分配空間,不初始化
   MyBussiness(p);
}

4.3.3 構造函數調用規則

  • 默認情況下,c++編譯器至少爲我們寫的類增加3個函數
    1.默認構造函數(無參,函數體爲空)
    2.默認析構函數(無參,函數體爲空)
    3.默認拷貝構造函數,對類中非靜態成員屬性簡單值拷貝

  • 如果用戶定義拷貝構造函數,c++不會再提供任何默認構造函數

  • 如果用戶定義了普通構造(非拷貝),c++不在提供默認無參構造,但是會提供默認拷貝構造

4.3.4 深拷貝和淺拷貝

4.3.4.1 淺拷貝

同一類型的對象之間可以賦值,使得兩個對象的成員變量的值相同,兩個對象仍然是獨立的兩個對象,這種情況被稱爲淺拷貝.

一般情況下,淺拷貝沒有任何副作用,但是當類中有指針,並且指針指向動態分配的內存空間,析構函數做了動態內存釋放的處理,會導致內存問題。
在這裏插入圖片描述

4.3.4.2 深拷貝

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

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;
}

4.3.4 多個對象構造和析構

4.3.4.1 初始化列表

構造函數和其他函數不同,除了有名字,參數列表,函數體之外還有初始化列表。
初始化列表簡單使用:

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;
};

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

4.3.4.2 類對象作爲成員

在類中定義的數據成員一般都是基本的數據類型。但是類中的成員也可以是對象,叫做對象成員

C++中對對象的初始化是非常重要的操作,當創建一個對象的時候,c++編譯器必須確保調用了所有子對象的構造函數。如果所有的子對象有默認構造函數,編譯器可以自動調用他們。但是如果子對象沒有默認的構造函數,或者想指定調用某個構造函數怎麼辦?

那麼是否可以在類的構造函數直接調用子類的屬性完成初始化呢?但是如果子類的成員屬性是私有的,我們是沒有辦法訪問並完成初始化的。

解決辦法非常簡單:對於子類調用構造函數,c++爲此提供了專門的語法,即構造函數初始化列表。

當調用構造函數時,首先按各對象成員在類定義中的順序(和參數列表的順序無關) 依次調用它們的構造函數,對這些對象初始化,最後再調用本身的函數體。也就是說,先調用對象成員的構造函數,再調用本身的構造函數。

析構函數和構造函數調用順序相反,先構造,後析構。

//汽車類
class Car{
public:
	Car(){
		cout << "Car 默認構造函數!" << endl;
		mName = "大衆汽車";
	}
	Car(string name){
		cout << "Car 帶參數構造函數!" << endl;
		mName = name;
	}
	~Car(){
		cout << "Car 析構函數!" << endl;
	}
public:
	string mName;
};

//拖拉機
class Tractor{
public:
	Tractor(){
		cout << "Tractor 默認構造函數!" << endl;
		mName = "爬土坡專用拖拉機";
	}
	Tractor(string name){
		cout << "Tractor 帶參數構造函數!" << endl;
		mName = name;
	}
	~Tractor(){
		cout << "Tractor 析構函數!" << endl;
	}
public:
	string mName;
};

//人類
class Person{
public:
#if 1
	//類mCar不存在合適的構造函數
	Person(string name){
		mName = name;
	}
#else
	//初始化列表可以指定調用構造函數
	Person(string carName, string tracName, string name) : mTractor(tracName), mCar(carName), mName(name){
		cout << "Person 構造函數!" << endl;
	}
#endif
	
	void GoWorkByCar(){
		cout << mName << "開着" << mCar.mName << "去上班!" << endl;
	}
	void GoWorkByTractor(){
		cout << mName << "開着" << mTractor.mName << "去上班!" << endl;
	}
	~Person(){
		cout << "Person 析構函數!" << endl;
	}
private:
	string mName;
	Car mCar;
	Tractor mTractor;
};

void test(){
	//Person person("寶馬", "東風拖拉機", "趙四");
	Person person("劉能");
	person.GoWorkByCar();
	person.GoWorkByTractor();
}

4.3.5 explicit關鍵字

c++提供了關鍵字explicit,禁止通過構造函數進行的隱式轉換。聲明爲explicit的構造函數不能在隱式轉換中使用。

[explicit注意]

  • explicit用於修飾構造函數,防止隱式轉化。
  • 是針對單參數的構造函數(或者除了第一個參數外其餘參數都有默認值的多參構造)而言。
class MyString{
public:
	explicit MyString(int n){
		cout << "MyString(int n)!" << endl;
	}
	MyString(const char* str){
		cout << "MyString(const char* str)" << endl;
	}
};

int main(){

	//給字符串賦值?還是初始化?
	//MyString str1 = 1; 
	MyString str2(10);

	//寓意非常明確,給字符串賦值
	MyString str3 = "abcd";
	MyString str4("abcd");

	return EXIT_SUCCESS;
}

4.3.6 動態對象創建

當我們創建數組的時候,總是需要提前預定數組的長度,然後編譯器分配預定長度的數組空間,在使用數組的時,會有這樣的問題,數組也許空間太大了,浪費空間,也許空間不足,所以對於數組來講,如果能根據需要來分配空間大小再好不過。

所以動態的意思意味着不確定性。

爲了解決這個普遍的編程問題,在運行中可以創建和銷燬對象是最基本的要求。當然c早就提供了動態內存分配(dynamic memory allocation),函數mallocfree可以在運行時從堆中分配存儲單元。

然而這些函數在c++中不能很好的運行,因爲它不能幫我們完成對象的初始化工作。

4.3.6.1 對象創建

當創建一個c++對象時會發生兩件事:

  1. 爲對象分配內存
  2. 調用構造函數來初始化那塊內存

第一步我們能保證實現,需要我們確保第二步一定能發生。c++強迫我們這麼做是因爲使用未初始化的對象是程序出錯的一個重要原因。

4.3.6.2 C動態分配內存方法

爲了在運行時動態分配內存,c在他的標準庫中提供了一些函數,malloc以及它的變種callocrealloc,釋放內存的free,這些函數是有效的、但是原始的,需要程序員理解和小心使用。爲了使用c的動態內存分配函數在堆上創建一個類的實例,我們必須這樣做:

class Person{
public:
	Person(){
		mAge = 20;
		pName = (char*)malloc(strlen("john")+1);
		strcpy(pName, "john");
	}
	void Init(){
		mAge = 20;
		pName = (char*)malloc(strlen("john")+1);
		strcpy(pName, "john");
	}
	void Clean(){
		if (pName != NULL){
			free(pName);
		}
	}
public:
	int mAge;
	char* pName;
};
int main(){

	//分配內存
	Person* person = (Person*)malloc(sizeof(Person));
	if(person == NULL){
		return 0;
	}
	//調用初始化函數
	person->Init();
	//清理對象
	person->Clean();
	//釋放person對象
	free(person);

	return EXIT_SUCCESS;
}

問題:

  1. 程序員必須確定對象的長度。
  2. malloc返回一個void指針,c++不允許將void賦值給其他任何指針,必須強轉。
  3. malloc可能申請內存失敗,所以必須判斷返回值來確保內存分配成功。
  4. 用戶在使用對象之前必須記住對他初始化,構造函數不能顯示調用初始化(構造函數是由編譯器調用),用戶有可能忘記調用初始化函數。

c的動態內存分配函數太複雜,容易令人混淆,是不可接受的,c++中我們推薦使用運算符newdelete.

4.3.6.3 new operator

C++中解決動態內存分配的方案是把創建一個對象所需要的操作都結合在一個稱爲new的運算符裏。當用new創建一個對象時,它就在堆裏爲對象分配內存並調用構造函數完成初始化。

Person* person = new Person;
相當於:
Person* person = (Person*)malloc(sizeof(Person));
	if(person == NULL){
		return 0;
	}
person->Init();

New操作符能確定在調用構造函數初始化之前內存分配是成功的,所有不用顯式確定調用是否成功。

現在我們發現在堆裏創建對象的過程變得簡單了,只需要一個簡單的表達式,它帶有內置的長度計算、類型轉換和安全檢查。這樣在堆創建一個對象和在棧裏創建對象一樣簡單。

4.3.6.4 delete operator

new表達式的反面是delete表達式。delete表達式先調用析構函數,然後釋放內存。正如new表達式返回一個指向對象的指針一樣,delete需要一個對象的地址。

delete只適用於由new創建的對象。

如果使用一個由malloc或者calloc或者realloc創建的對象使用delete,這個行爲是未定義的。因爲大多數newdelete的實現機制都使用了mallocfree,所以很可能沒有調用析構函數就釋放了內存。

如果正在刪除的對象的指針是NULL,將不發生任何事,因此建議在刪除指針後,立即把指針賦值爲NULL,以免對它刪除兩次,對一些對象刪除兩次可能會產生某些問題。

class Person{
public:
	Person(){
		cout << "無參構造函數!" << endl;
		pName = (char*)malloc(strlen("undefined") + 1);
		strcpy(pName, "undefined");
		mAge = 0;
	}
	Person(char* name, int age){
		cout << "有參構造函數!" << endl;
		pName = (char*)malloc(strlen(name) + 1);
		strcpy(pName, name);
		mAge = age;
	}
	void ShowPerson(){
		cout << "Name:" << pName << " Age:" << mAge << endl;
	}
	~Person(){
		cout << "析構函數!" << endl;
		if (pName != NULL){
			delete pName;
			pName = NULL;
		}
	}
public:
	char* pName;
	int mAge;
};

void test(){
	Person* person1 = new Person;
	Person* person2 = new Person("John",33);

	person1->ShowPerson();
	person2->ShowPerson();

	delete person1;
	delete person2;
}

4.3.6.5 用於數組的new和delete

使用newdelete在堆上創建數組非常容易。

//創建字符數組
char* pStr = new char[100];
//創建整型數組
int* pArr1 = new int[100]; 
//創建整型數組並初始化
int* pArr2 = new int[10]{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };

//釋放數組內存
delete[] pStr;
delete[] pArr1;
delete[] pArr2;

當創建一個對象數組的時候,必須對數組中的每一個對象調用構造函數,除了在棧上可以聚合初始化,必須提供一個默認的構造函數。

class Person{
public:
	Person(){
		pName = (char*)malloc(strlen("undefined") + 1);
		strcpy(pName, "undefined");
		mAge = 0;
	}
	Person(char* name, int age){
		pName = (char*)malloc(sizeof(name));
		strcpy(pName, name);
		mAge = age;
	}
	~Person(){
		if (pName != NULL){
			delete pName;
		}
	}
public:
	char* pName;
	int mAge;
};

void test(){
	//棧聚合初始化
	Person person[] = { Person("john", 20), Person("Smith", 22) };
	cout << person[1].pName << endl;
    //創建堆上對象數組必須提供構造函數
	Person* workers = new Person[20];
}

4.3.6.6 delete void*可能會出錯

如果對一個void*指針執行delete操作,這將可能成爲一個程序錯誤,除非指針指向的內容是非常簡單的,因爲它將不執行析構函數.以下代碼未調用析構函數,導致可用內存減少。

class Person{
public:
	Person(char* name, int age){
		pName = (char*)malloc(sizeof(name));
		strcpy(pName,name);
		mAge = age;
	}
	~Person(){
		if (pName != NULL){
			delete pName;
		}
	}
public:
	char* pName;
	int mAge;
};

void test(){
	void* person = new Person("john",20);
	delete person;
}

問題:
mallocfreenewdelete可以混搭使用嗎?也就是說malloc分配的內存,可以調用delete嗎?通過new創建的對象,可以調用free來釋放嗎?

4.3.6.7 使用new和delete採用相同形式

Person* person = new Person[10];
delete person;

以上代碼有什麼問題嗎?(vs下直接中斷、qt下析構函數調用一次)

使用了new也搭配使用了delete,問題在於Person有10個對象,那麼其他9個對象可能沒有調用析構函數,也就是說其他9個對象可能刪除不完全,因爲它們的析構函數沒有被調用。

我們現在清楚使用new的時候發生了兩件事: 一、分配內存;二、調用構造函數,那麼調用delete的時候也有兩件事:一、析構函數;二、釋放內存。

那麼剛纔我們那段代碼最大的問題在於:person指針指向的內存中到底有多少個對象,因爲這個決定應該有多少個析構函數應該被調用。換句話說,person指針指向的是一個單一的對象還是一個數組對象,由於單一對象和數組對象的內存佈局是不同的。更明確的說,數組所用的內存通常還包括“數組大小記錄”,使得delete的時候知道應該調用幾次析構函數。單一對象的話就沒有這個記錄。單一對象和數組對象的內存佈局可理解爲下圖:
在這裏插入圖片描述
本圖只是爲了說明,編譯器不一定如此實現,但是很多編譯器是這樣做的。

當我們使用一個delete的時候,我們必須讓delete知道指針指向的內存空間中是否存在一個“數組大小記錄”的辦法就是我們告訴它。當我們使用delete[],那麼delete就知道是一個對象數組,從而清楚應該調用幾次析構函數。

結論:
如果在new表達式中使用[],必須在相應的delete表達式中也使用[].如果在new表達式中不使用[], 一定不要在相應的delete表達式中使用[].

4.3.7 靜態成員

在類定義中,它的成員(包括成員變量和成員函數),這些成員可以用關鍵字static聲明爲靜態的,稱爲靜態成員。

不管這個類創建了多少個對象,靜態成員只有一個拷貝,這個拷貝被所有屬於這個類的對象共享。

4.3.7.1 靜態成員變量

在一個類中,若將一個成員變量聲明爲static,這種成員稱爲靜態成員變量。與一般的數據成員不同,無論建立了多少個對象,都只有一個靜態數據的拷貝。靜態成員變量,屬於某個類,所有對象共享。

靜態變量,是在編譯階段就分配空間,對象還沒有創建時,就已經分配空間。

  • 靜態成員變量必須在類中聲明,在類外定義。
  • 靜態數據成員不屬於某個對象,在爲對象分配空間中不包括靜態成員所佔空間。
  • 靜態數據成員可以通過類名或者對象名來引用。
class Person{
public:
	//類的靜態成員屬性
	static int sNum;
private:
	static int sOther;
};

//類外初始化,初始化時不加static
int Person::sNum = 0;
int Person::sOther = 0;
int main(){


	//1. 通過類名直接訪問
	Person::sNum = 100;
	cout << "Person::sNum:" << Person::sNum << endl;

	//2. 通過對象訪問
	Person p1, p2;
	p1.sNum = 200;

	cout << "p1.sNum:" << p1.sNum << endl;
	cout << "p2.sNum:" << p2.sNum << endl;

	//3. 靜態成員也有訪問權限,類外不能訪問私有成員
	//cout << "Person::sOther:" << Person::sOther << endl;
	Person p3;
	//cout << "p3.sOther:" << p3.sOther << endl;

	system("pause");
	return EXIT_SUCCESS;
}

4.3.7.2 靜態成員函數

在類定義中,前面有static說明的成員函數稱爲靜態成員函數。靜態成員函數使用方式和靜態變量一樣,同樣在對象沒有創建前,即可通過類名調用。靜態成員函數主要爲了訪問靜態變量,但是,不能訪問普通成員變量

靜態成員函數的意義,不在於信息共享,數據溝通,而在於管理靜態數據成員,完成對靜態數據成員的封裝。

  • 靜態成員函數只能訪問靜態變量,不能訪問普通成員變量
  • 靜態成員函數的使用和靜態成員變量一樣
  • 靜態成員函數也有訪問權限
  • 普通成員函數可訪問靜態成員變量、也可以訪問非經常成員變量
class Person{
public:
	//普通成員函數可以訪問static和non-static成員屬性
	void changeParam1(int param){
		mParam = param;
		sNum = param;
	}
	//靜態成員函數只能訪問static成員屬性
	static void changeParam2(int param){
		//mParam = param; //無法訪問
		sNum = param;
	}
private:
	static void changeParam3(int param){
		//mParam = param; //無法訪問
		sNum = param;
	}
public:
	int mParam;
	static int sNum;
};

//靜態成員屬性類外初始化
int Person::sNum = 0;

int main(){

	//1. 類名直接調用
	Person::changeParam2(100);

	//2. 通過對象調用
	Person p;
	p.changeParam2(200);

	//3. 靜態成員函數也有訪問權限
	//Person::changeParam3(100); //類外無法訪問私有靜態成員函數
	//Person p1;
	//p1.changeParam3(200);
	return EXIT_SUCCESS;
}

4.3.7.3 const靜態成員屬性

如果一個類的成員,既要實現共享,又要實現不可改變,那就用 static const 修飾。

定義靜態const數據成員時,最好在類內部初始化。

class Person{
public:
	//static const int mShare = 10;
	const static int mShare = 10; //只讀區,不可修改
};
int main(){

	cout << Person::mShare << endl;
	//Person::mShare = 20;

	return EXIT_SUCCESS;
}

4.3.7.4 靜態成員實現單例模式

單例模式是一種常用的軟件設計模式。在它的核心結構中只包含一個被稱爲單例的特殊類。通過單例模式可以保證系統中一個類只有一個實例而且該實例易於外界訪問,從而方便對實例個數的控制並節約系統資源。如果希望在系統中某個類的對象只能存在一個,單例模式是最好的解決方案。

在這裏插入圖片描述

**Singleton(單例):**在單例類的內部實現只生成一個實例,同時它提供一個靜態的getInstance()工廠方法,讓客戶可以訪問它的唯一實例;爲了防止在外部對其實例化,將其默認構造函數和拷貝構造函數設計爲私有;在單例類內部定義了一個Singleton類型的靜態對象,作爲外部共享的唯一實例。

用單例模式,模擬公司員工使用打印機場景,打印機可以打印員工要輸出的內容,並且可以累積打印機使用次數。

class Printer{
public:
	static Printer* getInstance(){ return pPrinter;}
	void PrintText(string text){
		cout << "打印內容:" << text << endl;
		cout << "已打印次數:" << mTimes << endl;
		cout << "--------------" << endl;
		mTimes++;
	}
private:
	Printer(){ mTimes = 0; }
	Printer(const Printer&){}
private:
	static Printer* pPrinter;
	int mTimes;
};

Printer* Printer::pPrinter = new Printer;

void test(){
	Printer* printer = Printer::getInstance();
	printer->PrintText("離職報告!");
	printer->PrintText("入職合同!");
	printer->PrintText("提交代碼!");
}

4.4 C++面向對象模型初探


4.4.1 成員變量和函數的存儲

在c語言中,“分開來聲明的,也就是說,語言本身並沒有支持“數據”和“函數”之間的關聯性我們把這種程序方法稱爲“程序性的”,由一組“分佈在各個以功能爲導航的函數中”的算法驅動,它們處理的是共同的外部數據。

c++實現了“封裝”,那麼數據(成員屬性)和操作(成員函數)是什麼樣的呢?

“數據”和“處理數據的操作(函數)”是分開存儲的。

  • c++中的非靜態數據成員直接內含在類對象中,就像c struct一樣。
  • 成員函數(member function)雖然內含在class聲明之內,卻不出現在對象中。
  • 每一個非內聯成員函數(non-inline member function)只會誕生一份函數實例.
class MyClass01{
public:
	int mA;
};

class MyClass02{
public:
	int mA;
	static int sB;
};

class MyClass03{
public:
	void printMyClass(){
		cout << "hello world!" << endl;
	}
public:
	int mA;
	static int sB;
};

class MyClass04{
public:
	void printMyClass(){
		cout << "hello world!" << endl;
	}
	static void ShowMyClass(){
		cout << "hello world!" << endl;
	}
public:
	int mA;
	static int sB;
};

int main(){

	MyClass01 mclass01;
	MyClass02 mclass02;
	MyClass03 mclass03;
	MyClass04 mclass04;

	cout << "MyClass01:" << sizeof(mclass01) << endl; //4
	//靜態數據成員並不保存在類對象中
	cout << "MyClass02:" << sizeof(mclass02) << endl; //4
	//非靜態成員函數不保存在類對象中
	cout << "MyClass03:" << sizeof(mclass03) << endl; //4
	//靜態成員函數也不保存在類對象中
	cout << "MyClass04:" << sizeof(mclass04) << endl; //4

	return EXIT_SUCCESS;
}

通過上面的案例,我們可以的得出:C++類對象中的變量和函數是分開存儲。

4.4.2 this指針

4.4.2.1 this指針工作原理

通過上例我們知道,c++的數據和操作也是分開存儲,並且每一個非內聯成員函數(non-inline member function)只會誕生一份函數實例,也就是說多個同類型的對象會共用一塊代碼

那麼問題是:這一塊代碼是如何區分那個對象調用自己的呢?

在這裏插入圖片描述
c++通過提供特殊的對象指針,this指針,解決上述問題。This指針指向被調用的成員函數所屬的對象。

c++規定,this指針是隱含在對象成員函數內的一種指針。當一個對象被創建後,它的每一個成員函數都含有一個系統自動生成的隱含指針this,用以保存這個對象的地址,也就是說雖然我們沒有寫上this指針,編譯器在編譯的時候也是會加上的。因此this也稱爲“指向本對象的指針”,this指針並不是對象的一部分,不會影響sizeof(對象)的結果。

this指針是C++實現封裝的一種機制,它將對象和該對象調用的成員函數連接在一起,在外部看來,每一個對象都擁有自己的函數成員。一般情況下,並不寫this,而是讓系統進行默認設置。

this指針永遠指向當前對象。

成員函數通過this指針即可知道操作的是那個對象的數據。This指針是一種隱含指針,它隱含於每個類的非靜態成員函數中。This指針無需定義,直接使用即可。

注意:靜態成員函數內部沒有this指針,靜態成員函數不能操作非靜態成員變量。

c++編譯器對普通成員函數的內部處理
在這裏插入圖片描述

4.4.2.2 this指針的使用

  • 當形參和成員變量同名時,可用this指針來區分
  • 在類的非靜態成員函數中返回對象本身,可使用return *this.
class Person{
public:
	//1. 當形參名和成員變量名一樣時,this指針可用來區分
	Person(string name,int age){
		//name = name;
		//age = age; //輸出錯誤
		this->name = name;
		this->age = age;
	}
	//2. 返回對象本身的引用
	//重載賦值操作符
	//其實也是兩個參數,其中隱藏了一個this指針
	Person PersonPlusPerson(Person& person){
		string newname = this->name + person.name;
		int newage = this->age + person.age;
		Person newperson(newname, newage);
		return newperson;
	}
	void ShowPerson(){
		cout << "Name:" << name << " Age:" << age << endl;
	}
public:
	string name;
	int age;
};

//3. 成員函數和全局函數(Perosn對象相加)
Person PersonPlusPerson(Person& p1,Person& p2){
	string newname = p1.name + p2.name;
	int newage = p1.age + p2.age;
	Person newperson(newname,newage);
	return newperson;
}

int main(){

	Person person("John",100);
	person.ShowPerson();

	cout << "---------" << endl;
	Person person1("John",20);
	Person person2("001", 10);
	//1.全局函數實現兩個對象相加
	Person person3 = PersonPlusPerson(person1, person2);
	person1.ShowPerson();
	person2.ShowPerson();
	person3.ShowPerson();
	//2. 成員函數實現兩個對象相加
	Person person4 = person1.PersonPlusPerson(person2);
	person4.ShowPerson();

	system("pause");
	return EXIT_SUCCESS;
}

4.4.2.3 const修飾成員函數

  • 用const修飾的成員函數時,const修飾this指針指向的內存區域,成員函數體內不可以修改本類中的任何普通成員變量,
  • 當成員變量類型符前用mutable修飾時例外。
//const修飾成員函數
class Person{
public:
	Person(){
		this->mAge = 0;
		this->mID = 0;
	}
	//在函數括號後面加上const,修飾成員變量不可修改,除了mutable變量
	void sonmeOperate() const{
		//this->mAge = 200; //mAge不可修改
		this->mID = 10;
	}
	void ShowPerson(){
		cout << "ID:" << mID << " mAge:" << mAge << endl;
	}
private:
	int mAge;
	mutable int mID;
};

int main(){

	Person person;
	person.sonmeOperate();
	person.ShowPerson();

	system("pause");
	return EXIT_SUCCESS;
}

4.4.2.4 const修飾對象(常對象)

  • 常對象只能調用const的成員函數
  • 常對象可訪問 const 或非 const 數據成員,不能修改,除非成員用mutable修飾
class Person{
public:
	Person(){
		this->mAge = 0;
		this->mID = 0;
	}
	void ChangePerson() const{
		mAge = 100;
		mID = 100;
	}
	void ShowPerson(){
        this->mAge = 1000;
		cout << "ID:" << this->mID << " Age:" << this->mAge << endl;
	}

public:
	int mAge;
	mutable int mID;
};

void test(){	
	const Person person;
	//1. 可訪問數據成員
	cout << "Age:" << person.mAge << endl;
	//person.mAge = 300; //不可修改
	person.mID = 1001; //但是可以修改mutable修飾的成員變量
	//2. 只能訪問const修飾的函數
	//person.ShowPerson();
	person.ChangePerson();
}

4.5 友元

類的主要特點之一是數據隱藏,即類的私有成員無法在類的外部(作用域之外)訪問。但是,有時候需要在類的外部訪問類的私有成員,怎麼辦?

解決方法是使用友元函數,友元函數是一種特權函數,c++允許這個特權函數訪問私有成員。這一點從現實生活中也可以很好的理解:

比如你的家,有客廳,有你的臥室,那麼你的客廳是Public的,所有來的客人都可以進去,但是你的臥室是私有的,也就是說只有你能進去,但是呢,你也可以允許你的閨蜜好基友進去。

程序員可以把一個全局函數、某個類中的成員函數、甚至整個類聲明爲友元。

4.5.1 友元語法

  • friend關鍵字只出現在聲明處
  • 其他類、類成員函數、全局函數都可聲明爲友元
  • 友元函數不是類的成員,不帶this指針
  • 友元函數可訪問對象任意成員屬性,包括私有屬性
class Building;
//友元類
class MyFriend{
public:
	//友元成員函數
	void LookAtBedRoom(Building& building);
	void PlayInBedRoom(Building& building);
};
class Building{
	//全局函數做友元函數
	friend void CleanBedRoom(Building& building);
#if 0
	//成員函數做友元函數
	friend void MyFriend::LookAtBedRoom(Building& building);
	friend void MyFriend::PlayInBedRoom(Building& building);
#else	
	//友元類
	friend class MyFriend;
#endif
public:
	Building();
public:
	string mSittingRoom;
private:
	string mBedroom;
};

void MyFriend::LookAtBedRoom(Building& building){
	cout << "我的朋友參觀" << building.mBedroom << endl;
}
void MyFriend::PlayInBedRoom(Building& building){
	cout << "我的朋友玩耍在" << building.mBedroom << endl;
}

//友元全局函數
void CleanBedRoom(Building& building){
	cout << "友元全局函數訪問" << building.mBedroom << endl;
}

Building::Building(){
	this->mSittingRoom = "客廳";
	this->mBedroom = "臥室";
}

int main(){

	Building building;
	MyFriend myfriend;

	CleanBedRoom(building);
	myfriend.LookAtBedRoom(building);
	myfriend.PlayInBedRoom(building);

	system("pause");
	return EXIT_SUCCESS;
}

友元類注意
1.友元關係不能被繼承。
2.友元關係是單向的,類A是類B的朋友,但類B不一定是類A的朋友。
3.友元關係不具有傳遞性。類B是類A的朋友,類C是類B的朋友,但類C不一定是類A的朋友。

思考: c++是純面向對象的嗎?

如果一個類被聲明爲friend,意味着它不是這個類的成員函數,卻可以修改這個類的私有成員,而且必須列在類的定義中,因此他是一個特權函數。c++不是完全的面嚮對象語言,而只是一個混合產品。增加friend關鍵字只是用來解決一些實際問題,這也說明這種語言是不純的。畢竟c++設計的目的是爲了實用性,而不是追求理想的抽象。 — Thinking in C++

4.5.2 課堂練習

請編寫電視機類,電視機有開機和關機狀態,有音量,有頻道,提供音量操作的方法,頻道操作的方法。由於電視機只能逐一調整頻道,不能指定頻道,增加遙控類,遙控類除了擁有電視機已有的功能,再增加根據輸入調臺功能。

提示:遙控器可作爲電視機類的友元類。

class Remote;

class Television{
	friend class Remote;
public:
	enum{ On,Off }; //電視狀態
	enum{ minVol,maxVol = 100 }; //音量從0到100
	enum{ minChannel = 1,maxChannel = 255 }; //頻道從1到255
	Television(){
		mState = Off;
		mVolume = minVol;
		mChannel = minChannel;
	}

	//打開電視機
	void OnOrOff(){
		this->mState = (this->mState == On ? Off : On);
	}
	//調高音量
	void VolumeUp(){
		if (this->mVolume >= maxVol){
			return;
		}
		this->mVolume++;
	}
	//調低音量
	void VolumeDown(){
		if (this->mVolume <= minVol){
			return;
		}
		this->mVolume--;
	}
	//更換電視頻道
	void ChannelUp(){
		if (this->mChannel >= maxChannel){
			return;
		}
		this->mChannel++;
	}
	void ChannelDown(){
		if (this->mChannel <= minChannel){
			return;
		}
		this->mChannel--;
	}
	//展示當前電視狀態信息
	void ShowTeleState(){
		cout << "開機狀態:" << (mState == On ? "已開機" : "已關機") << endl;
		if (mState == On){
			cout << "當前音量:" << mVolume << endl;
			cout << "當前頻道:" << mChannel << endl;
		}
		cout << "-------------" << endl;
	}
private:
	int mState; //電視狀態,開機,還是關機
	int mVolume; //電視機音量
	int mChannel; //電視頻道
};

//電視機調臺只能一個一個的調,遙控可以指定頻道
//電視遙控器
class Remote{
public:
	Remote(Television* television){
		pTelevision = television;
	}
public:
	void OnOrOff(){
		pTelevision->OnOrOff();
	}
	//調高音量
	void VolumeUp(){
		pTelevision->VolumeUp();
	}
	//調低音量
	void VolumeDown(){
		pTelevision->VolumeDown();
	}
	//更換電視頻道
	void ChannelUp(){
		pTelevision->ChannelUp();
	}
	void ChannelDown(){
		pTelevision->ChannelDown();
	}
	//設置頻道 遙控新增功能
	void SetChannel(int channel){
		if (channel < Television::minChannel || channel > Television::maxChannel){
			return;
		}
		pTelevision->mChannel = channel;
	}

	//顯示電視當前信息
	void ShowTeleState(){
		pTelevision->ShowTeleState();
	}
private:
	Television* pTelevision;
};


//直接操作電視
void test01(){

	Television television;
	television.ShowTeleState();
	television.OnOrOff(); //開機
	television.VolumeUp(); //增加音量+1
	television.VolumeUp(); //增加音量+1
	television.VolumeUp(); //增加音量+1
	television.VolumeUp(); //增加音量+1
	television.ChannelUp(); //頻道+1
	television.ChannelUp(); //頻道+1
	television.ShowTeleState();
}

//通過遙控操作電視
void test02(){
	//創建電視
	Television television;
	//創建遙控
	Remote remote(&television);
	remote.OnOrOff();
	remote.ChannelUp();//頻道+1
	remote.ChannelUp();//頻道+1
	remote.ChannelUp();//頻道+1
	remote.VolumeUp();//音量+1
	remote.VolumeUp();//音量+1
	remote.VolumeUp();//音量+1
	remote.VolumeUp();//音量+1
	remote.ShowTeleState();
}

4.5 強化訓練(數組類封裝)


MyArray.h

#ifndef MYARRAY_H
#define MYARRAY_H

class MyArray{
public:
	//無參構造函數,用戶沒有指定容量,則初始化爲100
	MyArray();
	//有參構造函數,用戶指定容量初始化
	explicit MyArray(int capacity);
	//用戶操作接口
	//根據位置添加元素
	void SetData(int pos, int val);
	//獲得指定位置數據
	int GetData(int pos);
	//尾插法
	void PushBack(int val);
	//獲得長度
	int GetLength();
	//析構函數,釋放數組空間
	~MyArray();
private:
	int mCapacity; //數組一共可容納多少個元素
	int mSize; //當前有多少個元素
	int* pAdress; //指向存儲數據的空間
};

#endif

MyArray.cpp

#include"MyArray.h"

MyArray::MyArray(){
	this->mCapacity = 100;
	this->mSize = 0;
	//在堆開闢空間
	this->pAdress = new int[this->mCapacity];
}
//有參構造函數,用戶指定容量初始化
MyArray::MyArray(int capacity){
	this->mCapacity = capacity;
	this->mSize = 0;
	//在堆開闢空間
	this->pAdress = new int[capacity];
}
//根據位置添加元素
void MyArray::SetData(int pos, int val){
	if (pos < 0 || pos > mCapacity - 1){
		return;
	}
	pAdress[pos] = val;
}
//獲得指定位置數據
int MyArray::GetData(int pos){
	return pAdress[pos];
}
//尾插法
void MyArray::PushBack(int val){
	if (mSize >= mCapacity){
		return;
	}
	this->pAdress[mSize] = val;
	this->mSize++;
}
//獲得長度
int MyArray::GetLength(){
	return this->mSize;
}
//析構函數,釋放數組空間
MyArray::~MyArray(){
	if (this->pAdress != nullptr){
		delete[] this->pAdress;
	}
}

TestMyArray.cpp

#include"MyArray.h"

void test(){
	//創建數組
	MyArray myarray(50);
	//數組中插入元素
	for (int i = 0; i < 50; i++){
		//尾插法
		myarray.PushBack(i);
		//myarray.SetData(i, i);
	}
	//打印數組中元素
	for (int i = 0; i < myarray.GetLength(); i++){
		cout << myarray.GetData(i) << " ";
	}
	cout << endl;
}

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