天天看點

C++學習筆記之 類 對象 繼承一、類 對象 繼承二、繼承三、其他參考資料

C++學習筆記 類 對象 繼承等

  • 一、類 對象 繼承
    • 1. 類和對象
    • 2. 資料封裝 public/protected/private
    • 3. 類的函數
      • 3.1 構造函數簡介
      • 3.2 構造函數之使用初始化清單來初始化字段
      • 3.3 拷貝構造函數
      • 3.4 析構函數
      • 3.5 友元函數 friend
      • 3.6 内聯函數 inline
      • 3.7 this指針
      • 3.8 指派運算符重載函數 operator=
      • 3.9 類的靜态成員
  • 二、繼承
    • 1. public繼承
    • 2. protected繼承
    • 3. private繼承
  • 三、其他
    • 1. new/delete 與 malloc/free
    • 2. reference 引用
    • 3. vector基礎使用
    • 4. const
    • 5. size_t
  • 參考資料

一、類 對象 繼承

1. 類和對象

1.1 C++在C的基礎上增加了面向對象程式設計,類和對象就是 面向對象程式設計中 的核心概念

1.2 類是C++的一個核心特性(類就是使用者自定義的類型),類中包含資料元素(變量)和處理資料元素的方法(函數),類中的資料和函數被稱為類的成員

1.3 類提供了對象的藍圖,一般來說對象是根據類建立的。打個比方,類相當于房屋設計圖,對象相當于根據設計圖設計的房子

1.4 類的基礎使用示例:由于成員的屬性為public , 是以可以使用 直接成員通路運算符 . 進行通路

#include <iostream>
using namespace std;
 
class Fruit 
{
	public:
		string name;
   		int quatity;
   		float price;
   		float getTotalPrice();
   		void setPara(string n,int q,float p); 
};

float Fruit::getTotalPrice()  //通過 :: 指定範圍,類的成員函數可以在類中定義,也可以在類外定義 
{
	return quatity*price;
}
void Fruit::setPara(string n,int q,float p)
{
	name=n; quatity=q; price=p;
}

int main( )
{
	Fruit Apple,Banana; 
	float totalPrice;
	
	Apple.name="Apple"; Apple.quatity=10; Apple.price=2.0; //直接成員通路運算符 . 
	totalPrice=Apple.quatity * Apple.price;
	cout<<Apple.name<<"'s total price is: "<<totalPrice<<endl;
	
	Banana.setPara("Banana",20,1.5);
	totalPrice=Banana.getTotalPrice();
	cout<<Banana.name<<"'s total price is: "<<totalPrice<<endl;
	
	return 0;

}
           

2. 資料封裝 public/protected/private

2.1 資料封裝是面向對象程式設計的一個重要特點,用于防止函數直接通路類的内部成員。類成員的通路限制 通過在類内部 使用關鍵字 public, private, protected 進行标記來控制,預設通路修飾符是private

2.2 公有成員 public 可以直接通路,在1.4的代碼示例中已經示範

2.3 私有成員 private 在類的外部不可通路、不可檢視,若不指定,預設情況下類的所有成員都是private 。實際操作中,在私有區域定義資料,在公有區域定義相關函數,以便在類的外部調用這些函數

#include <iostream>
using namespace std;
 
class Fruit 
{
	public:
		string name;
   		int quatity;
   		
   	public:
	   	float getCostPrice()
	   	{
	   		return CostPrice;
		}
	   	void setCostPrice(float cost)
	   	{
	   		CostPrice=cost;
		}
	   	
	private:
		float CostPrice;
};

int main( )
{
	Fruit Orange; 
	float totalCostPrice;
	
	Orange.name="Orange"; Orange.quatity=10; //無法通過 Orange.CostPrice進行通路 
	Orange.setCostPrice(3.12);
	cout<<Orange.name<<"'s cost price is "<<Orange.getCostPrice();
	cout<<" and the total cost price is "<<Orange.getCostPrice()*Orange.quatity;
	
	return 0;

}
           

2.4 受保護成員 protected , protected 和 private幾乎相同,除了protected 可以被子類/派生類通路,如下所示

#include <iostream>
using namespace std;
 
class Fruit 
{ 	
	protected:
		float CostPrice;
	private:
		float MinCost;
};

class HardShellFruit:Fruit //HardShellFruit是繼承自Fruit的子類 
{
	public:
		void setHardShellCostPrice(float cost)
		{
			CostPrice=cost;
			//MinCost=cost; 會觸發報錯,因為MinCost是private 
		} 
		float getHardShellCostPrice(void)
		{
			return CostPrice;
		}
}; 

int main( )
{
	HardShellFruit mangosteen; 
	
	mangosteen.setHardShellCostPrice(16.1);
	cout<<"Cost price of mangosteen is "<<mangosteen.getHardShellCostPrice()<<endl; 
	
	return 0;
}
           

3. 類的函數

3.1 構造函數簡介

構造函數是類的一種特殊成員函數,在每次建立對象時執行。構造函數的名稱與類的名稱完全相同,不會傳回任何類型(包括void)。構造函數預設不帶參數,當構造函數帶參數時,可用于為部分成員變量賦初值

#include <iostream>
using namespace std;
 
class Fruit 
{
	public:
		string name;
   		int quatity;
   		float CostPrice;
   	
   	public:
	   	Fruit(int qua,float cost) //構造函數
		{
			cout<<"Object is being created, the default quatitiy is "<<qua;
			cout<<" , the default CostPrice is "<<cost<<endl;
			quatity=qua;
			CostPrice=cost;
		}
	   	float getCostPrice()
	   	{
	   		return CostPrice;
		}	
};

int main( )
{
	Fruit strawberry(100,5.1); 
	cout<<"the strawberry's default costprice is "<<strawberry.CostPrice;
	cout<<endl;
	strawberry.CostPrice=5.3;
	cout<<"the strawberry's current costprice is "<<strawberry.getCostPrice();
	
	return 0;

}
           

3.2 構造函數之使用初始化清單來初始化字段

示例如下,A B兩種寫法是等價的,A ==Fruit::Fruit(float cost):CostPrice(cost){ } ==就是使用初始化清單來初始化字段

Fruit::Fruit(float cost):CostPrice(cost){ } //A


Fruit::Fruit(float cost) //B
{
	CostPrice=cost;
}

           

當需要初始化多個成員變量時,通過逗号分隔即可

完整編碼如下

#include <iostream>
using namespace std;
 
class Fruit 
{
	public:
		string name;
   		int quatity;
   		float CostPrice;
   	
   	public:
   		Fruit(float cost);
	   	float getCostPrice()
	   	{
	   		return CostPrice;
		}	
};

Fruit::Fruit(float cost):CostPrice(cost){ }

/*
Fruit::Fruit(float cost)
{
	CostPrice=cost;
}
*/

int main()
{
	Fruit strawberry(5.1); 
	cout<<"the strawberry's default costprice is "<<strawberry.CostPrice;
	cout<<endl;
	strawberry.CostPrice=5.3;
	cout<<"the strawberry's current costprice is "<<strawberry.getCostPrice();
	
	return 0;

}
           

3.3 拷貝構造函數

3.3.1 拷貝構造函數是一種特殊的構造函數,它在建立對象時,使用另一個同類的對象來初始化新建立的對象。使用場景:類帶有指針變量并有動态記憶體配置設定(malloc new)。如果類中沒有定義拷貝構造函數,編譯器會自行定義一個

3.3.2 構造函數的基本形态

classname (const classname &obj) //obj是一個對象引用,用于初始化另一個對象
{
   // 構造函數的主體
}
           

3.3.3 編碼示例

#include <iostream>
using namespace std;
 
class Fruit
{
   public:
      float getCost()
      {
      	return *ptrCost;
	  }
	  
      Fruit( float cost )   //Construct Func. 
      {
      	
      	cout << "Construct Func. works..." << endl; 
		ptrCost = new float; //for pointer, memory allocation is first than use.
		*ptrCost = cost;
	  }
	  
      Fruit( const Fruit &obj)     // Copy Construct Func. 
      {
      	cout << "Copy Construct Func. works..." << endl;
      	ptrCost=new float;
      	*ptrCost = *obj.ptrCost; //copy value 
	  }
      
	  ~Fruit()                // deconstruct Func.
	  {
	  	cout << "Deconstruct Func. works and free memory." << endl; 
	  	delete ptrCost;
	  }
 
   private:
      float *ptrCost;
};

int main()
{
   Fruit Lemon(23.2);
   cout<<"Lemon is ready!"<<endl;
   Fruit Grape=Lemon; 
   cout<<"Grape is ready"<<endl;
 
   cout<<"Lemon's cost is: "<<Lemon.getCost()<<endl;
   cout<<"Grape's cost is: "<<Grape.getCost()<<endl;
 
   return 0;
}
           
C++學習筆記之 類 對象 繼承一、類 對象 繼承二、繼承三、其他參考資料

3.4 析構函數

析構函數是類的一種特殊成員函數,在每次删除所建立對象時執行。析構函數的名稱與類的名稱完全相同,但需要在前面加波浪号(~)作為字首,它不會傳回任何值,也不能帶有任何參數。析構函數有助于在跳出程式(如關閉檔案、釋放記憶體)前釋放資源

#include <iostream>
using namespace std;
 
class Fruit 
{
	public:
		string name;
   		int quatity;
   		float CostPrice;
   	
   	public:
   		Fruit()
   		{ cout<<"Object has been created."<<endl; }
   		~Fruit() //析構函數
   		{ cout<<"Object has been deleted."<<endl; }
	   	float getCostPrice()
	   	{
	   		return CostPrice;
		}	
};

int main()
{
	Fruit strawberry; 
	
	return 0;

}
           

3.5 友元函數 friend

友元可以是函數、也可以是類,其關鍵字是friend,友元可以通路類的private成員

#include <iostream>
using namespace std;
 
class Fruit
{
	public:
		friend void printFruit(Fruit fruit); //friend Func.
		Fruit(int qua,float p):quantity(qua),price(p){}; //使用初始化清單來初始化字段
	
	private:
		int quantity;
		float price;
};


void printFruit(Fruit fruit)
{
	cout<<fruit.quantity<<' '<<fruit.price; //friend Func. can access Pirate!
	cout<<endl;
}


int main()
{
	Fruit tomato(100,3.1);
	printFruit(tomato);
	return 0;
}
           

3.6 内聯函數 inline

3.6.1 引入内聯函數,是為了提升程式中函數調用的效率,本質是通過空間換時間,是以内聯函數一般都是不超過10行的小函數

3.6.2 内聯函數的關鍵字是 inline,在使用内聯函數時要注意:

(1) 在内聯函數中不允許使用循環語句和開關語句(break)

(2) 内聯函數的定義必須出現在内聯函數第一次調用之前

(3) 類的成員函數都是内聯函數

#include <iostream>
using namespace std;
 
inline int min(int a,int b) //
{
	return (a<b)?a:b;
}

int main()
{
	cout<<"min(2,3):"<<min(2,3)<<endl;
	cout<<"min(2,1):"<<min(2,1)<<endl;
	
	return 0;
}
           

3.7 this指針

3.7.1 在成員函數内部,this可以用來指向調用對象

3.72 通俗的解釋:當你進入一個房子後,你可以看到房子内的桌子、椅子、地闆,但是你看不到房子的全貌。對于類來說,你可以看到成員函數、成員變量,但你看不到執行個體本身,使用this可以讓我們看到這個執行個體本身。個人了解:類就好比這座房子,this就好比一把鑰匙,通過鑰匙來打開了這座房子的門,那麼裡面的東西就随意你使用

3.7.3 友元函數沒有 this 指針,因為友元不是類的成員,隻有成員函數才有 this 指針

#include <iostream>
using namespace std;
 
class Fruit
{
	public:
		float price;
		Fruit(int p):price(p){
		};
		bool compare(Fruit fruit)
		{
			return this->price > fruit.price;
		}	
};

int main()
{
	Fruit grape(2.23), orange(2.27);
	cout<<"Orange is more expensive than grape: ";
	if(grape.compare(orange))
		cout<<"true."<<endl;
	else
		cout<<"false."<<endl;
	
	return 0;
}
           

3.8 指派運算符重載函數 operator=

3.8.1 operator= 就是對指派運算符 = 進行重載,在部分情況下,類對象進行析構時,會重複釋放一塊記憶體,進而導緻程式崩潰,這種情況下,就需要重載指派運算符 = 了

3.8.2 這裡的部分情況,我還沒有完全了解,就示例中展示的情況是:類對象向類對象指派 且 構造函數裡面涉及指針操控,後面遇到了再研究吧

3.8.3 一般形式如下

String B;
	B = A;
           

3.8.4 示例

#include <cstring>
#include <iostream>
using namespace std;

class String
{
public:
	String()
	{
	}

	String(const char* ptrInputStr) //不加const會引起warning,why?
	{
		ptrStr = new char[strlen(ptrInputStr) + 1];
		strcpy_s(ptrStr, strlen(ptrInputStr) + 1, ptrInputStr);
	}

	~String()
	{
		delete ptrStr;
	}
	
	String& operator=(const String &a_b) //該程式如果沒有 指派運算符 = 重載函數,在vs2019下會觸發程式崩潰
	{
		cout<<"operator= is work."<<endl;
		// 避免自指派
		if (this != &a_b)
		{
			// 避免記憶體洩露
			if (ptrStr != NULL)
			{
				delete ptrStr;
				ptrStr = NULL;
			}

			ptrStr = new char[strlen(a_b.ptrStr)+1];
			strcpy_s(ptrStr, strlen(a_b.ptrStr) + 1,a_b.ptrStr);
		}

		return *this;
	} 

public:
	char* ptrStr;
};

int main()
{
	String A("keep coding");
	cout << A.ptrStr << endl;

	String B;
	B = A;

	cout << B.ptrStr << endl;

	return 0;
}
           

3.8.3 參考資料 C++中的指派運算符重載函數(operator=)作者:liitdar

3.9 類的靜态成員

3.8.1 可以通過static 關鍵字将類成員定義為靜态的。對于靜态成員,無論建立多少個類的對象,靜态成員都隻有一個副本,靜态成員在類的所有對象中是共享的

3.8.2 靜态成員變量的初始化不能放在類的定義中,但是可以在類的外部進行初始化。如果不存在初始化語句,在建立第一個對象時,所有的靜态資料都會被初始化為零。靜态成員變量示例

#include <iostream>
using namespace std;

class Fruit
{
	public:
		static int count;
		Fruit()
		{
			count++;
		}
};

int Fruit::count=0;

int main()
{
	cout<<"Fruit's count is:"<<Fruit::count<<endl;
	Fruit apple;
	cout<<"Fruit's count is:"<<Fruit::count<<endl;
	Fruit banana;
	cout<<"Fruit's count is:"<<banana.count<<endl;
	Fruit orange;
	cout<<"Fruit's count is:"<<apple.count<<endl;
	
	return 0;
}
           

3.8.5 靜态成員函數沒有this指針,隻能通路靜态成員。靜态成員函數即使在類對象不存在的情況下也能被調用,靜态函數隻要使用類名加範圍解析運算符 :: 就可以通路。示例如下:

#include <iostream>
using namespace std;

class Fruit
{
	public:
		static int count;
		Fruit()
		{
			count++;
		}
		static statistics() //靜态成員函數
		{
			return count;
		}
};

int Fruit::count=0;

int main()
{
	cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
	Fruit apple;
	cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
	Fruit banana;
	cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
	Fruit orange;
	cout<<"Fruit's count is:"<<Fruit::statistics()<<endl;
	
	return 0;
}
           

二、繼承

繼承分為public, private, protected三種方式

1. public繼承

#include <iostream>
using namespace std;
 
class Fruit 
{ 	
	public:
		float publicCost;
	protected:
		float protectedCost;
	private:
		float privateCost; 
};

class HardShellFruit:public Fruit //HardShellFruit是 public繼承 Fruit的子類 
{
	public:
		void PrintPrice()
		{
			cout<<publicCost;
			cout<<protectedCost; //基類的protected成員,在派生類中仍是protected,是以可以被派生類通路
			//cout<<privateCost; 基類的private成員不能被派生類通路 
		} 
}; 

int main( )
{
	HardShellFruit mangosteen; 
	
	mangosteen.publicCost=20.1;
	//mangosteen.protectedCost; 無法通路
	// mangosteen.privateCost; 無法通路 
	return 0;
}
           

2. protected繼承

#include <iostream>
using namespace std;
 
class Fruit 
{ 	
	public:
		float publicCost;
	protected:
		float protectedCost;
	private:
		float privateCost; 
};

class HardShellFruit:protected Fruit //HardShellFruit是繼承自Fruit的子類 
{
	public:
		void PrintPrice()
		{
			cout<<publicCost; //protected繼承,基類public成員變為 protected成員,可以在子類通路 
			cout<<protectedCost; //protected繼承,基類protected成員還是protected成員,可以在子類通路
			//cout<<privateCost; 基類的private成員不能被派生類通路
		} 
}; 

int main( )
{
	HardShellFruit mangosteen; 
	
	//mangosteen.publicCost=20.1;  protected繼承,類public成員變為 protected成員,無法在類外通路 
	//mangosteen.protectedCost; 無法通路
	// mangosteen.privateCost; 無法通路 
	return 0;
}
           

3. private繼承

private也是預設繼承

#include <iostream>
using namespace std;
 
class Fruit 
{ 	
	public:
		float publicCost;
	protected:
		float protectedCost;
	private:
		float privateCost; 
};

class HardShellFruit:private Fruit //HardShellFruit是繼承自Fruit的子類 
{
	public:
		void PrintPrice()
		{
			cout<<publicCost; //private繼承,基類public成員變為 private成員,可以在子類通路 
			cout<<protectedCost; //private繼承,基類protected成員變為 private成員,可以在子類通路 
			//cout<<privateCost; 基類的private成員不能被派生類通路
		} 
}; 

int main( )
{
	HardShellFruit mangosteen; 
	
	//mangosteen.publicCost=20.1;  private繼承,類public成員變為 private成員,無法在類外通路 
	//mangosteen.protectedCost; 無法通路
	// mangosteen.privateCost; 無法通路 
	return 0;
}
           

三、其他

1. new/delete 與 malloc/free

  1. new和malloc都是告訴計算機開辟一段新的存儲空間。大部分場景下new和malloc可以通用
  2. 他們之間的差別包括:

    2.1 malloc與free是c++/c語言的标準函數,new/delete是C++的運算符

    2.2 都可用于申請動态記憶體和釋放記憶體。new/delete比malloc/free更加智能,因為new和delete在對象建立的時候自動執行構造函數,對象消亡之前會自動執行析構函數 //目前不是很了解

    2.3 new/delete的功能完全覆寫了malloc和free,但C++不能把malloc/free淘汰,因為C++程式會調用C函數,而C隻能用malloc/free管理動态記憶體

    2.4 new傳回指定類型的指針,并且可以自動計算出所需要的大小。malloc必須使用者指定大小,并且默然傳回類型為void*,必須強行轉換為實際類型的指針。請看示例

#include <stdlib.h>
#include <iostream>
using namespace std;

#define max 10

int main()
{
	int i;
	int *p; //指針使用前先申請空間
	p=(int*)malloc(sizeof(int)) ;
	*p=1;
	
	int *p_new;
	p_new=new int;
	*p_new=2;
	
	cout<<"p="<<*p<<" p_new="<<*p_new<<endl;
	
	free(p) ;
	delete p_new; 
	
	int *a;
	a=(int*)malloc(sizeof(int)*max);
	
	int *b;
	b=new int[max];
	
	for(int i=0;i<max;i++)
	{
		a[i]=i+10;
		b[i]=i+100;
	}	
		
	cout<<"start print:"<<endl;
	i=max;
	while(i--)
	{
		cout<<a[i]<<"---"<<b[i]<<endl;
	}
	
	free(a);
	delete b;
	
	return 0;
}


           

2. reference 引用

  1. reference 引用:引用是已存在變量的另一個名字, 對應的符号是 & (指針的符号是 * )。引用和指針的差別是:

    (1)不存在空引用,引用必須連接配接到一塊合法的記憶體

    (2)一旦引用被初始化為一個對象,就不能被指向到另一個對象。指針可以在任何時候指向另一個對象

    (3)引用必須在建立時被初始化;指針可以在任何時間被初始化

  2. 進一步解釋

    (1)指針 B 指向 A:B 借用了 A 的值,但是B的記憶體位址和A不同。引用 B 引用 A:B的記憶體位址和值 與A 完全相同,A和B任意一個發生改變, A和B同時改變

    (2)套用火影設定:指針更像是普通的分身法(虛拟分身),即使是分身破滅,對自身也沒有影響;引用更像是影分身(實體分身),影分身發生了變化,本身也會受到影響

    (3)你被朋友起了綽号"派大星",這個綽号就相當于你名字的一個引用,在朋友看來,無論叫哪個都是在叫你

  3. 示例代碼
#include <iostream>
using namespace std;
 
int main ()
{
   int i;
   int &reference=i;
   
   i=3;
   cout<<i<<' '<<reference<<endl; //print:3 3
   
   reference=4;
   cout<<i<<' '<<reference<<endl; //print:4 4
   
   return 0;
}
           
  1. 先進行下了解,後面在應用中再繼續補充

3. vector基礎使用

  1. vector是STL中的一種資料結構(也稱為容器),相當于數組的加強版,使用時需要 #include 。STL Standard Template Library 标準模闆庫
  2. 例題:給定n個字元串,對n個字元串按照字典序排列
#include <algorithm>  //sort
#include <iostream>
#include <vector>
using namespace std;

int main()
{
	int input;
	string str;
	vector<string> vs; //vector的存儲對象是string 
	
	cin>>input; //輸入待排序的元素個數 
	
	while(input--)
	{
		cin>>str;
		vs.push_back(str); // push_back():向vs中添加元素 
	}
	
	sort(vs.begin(),vs.end()); //begin() 首元素, vs.end() 尾元素 
	
	vector<string>::iterator vit; //定義疊代器 
	
	cout<<"Print result:"<<endl;
	
	for(vit=vs.begin();vit!=vs.end();vit++)
		cout<<*vit<<endl;
	
	return 0;
} 
           

4. const

const: 定義一個常量,程式執行期間不能改變。把常量定義為大寫字母形式,是一個很好的程式設計習慣

5. size_t

size_t 同 int 一樣,都可以進行變量定義: int a; size_t a; 都代表定義了一個整形變量a. 但是int固定為4byte,而size_t的大小與OS有關、是以移植性更好

參考資料

RUNOOB.com

繼續閱讀