1. 程式人生 > >C++面向物件- -類和物件的基礎認識

C++面向物件- -類和物件的基礎認識

目錄

類的宣告和物件的定義

定義物件的方法

類的成員函式

1、內建成員函式

2、成員函式的儲存方式

物件成員的引用

1、通過物件名和成員運算子訪問物件中的成員

2、通過指向物件的指標訪問物件中的成員

3、通過物件的引用來訪問物件中的成員

類和物件的簡單應用

類宣告和成員函式定義的分離


 

一些囉嗦的概念,瞭解瞭解就行:

物件:客觀世界中任何一個事物都可以看作是一個物件。

任何一個物件都應具有屬性(變數/資料)和行為(函式/操作程式碼)這兩個要素,物件應能根據外界給的資訊進行相應的操作(功能的實現)。一個物件一般是由一組屬性和一組行為構成

,而一組資料是與一組操作對應的。

面向物件程式設計的一個重要特點是:“封裝性” 。所謂封裝性有兩方面含義:一是將有關的資料和操作程式碼封裝在一個物件中,形成一個基本單位,各個物件之間相對獨立,互不干擾。二是將物件中某些部分對外隱蔽,即隱藏其內部細節(私有屬性或者保護屬性),只保留少數介面(公有屬性),以便與外界聯絡,接受外界的訊息。這種對外界隱蔽的做法稱為資訊隱蔽,資訊隱蔽有利於資料安全,防止無關的人瞭解和修改資料。

類是一個物件的型別,是物件的抽象,物件是類的特例,或者說是類的具體表現形式。類是抽象的,不佔用記憶體,而物件是具體的,佔用記憶體儲存空間,類只是給出一種 “模型” ,供使用者定義實際的物件。類是所有面向物件語言的共同特徵,都提供有 類 這種型別,如果一種程式語言不包含類,它就不能稱為面向物件的語言。

多型性:由繼承而產生的相關的不同的類,其物件對同一訊息會做出不同的響應。這和後邊的繼承與派生有所關聯,也經常使用到,多型性是面向物件程式設計的一個重要特性,增加程式的靈活性。C++語言支援兩種多型性:編譯時的多型性和執行時的多型性,編譯時的多型性是通過函式過載和運算子過載來實現的;執行時的多型性是通過虛擬函式來實現的,後邊具體解釋。

在面向物件程式設計中有幾個名詞:行為、物件、方法、訊息。行為:體現為類的成員函式,在面向物件程式理論中也稱為“方法”;方法:指的是對資料的操作,一個方法對應一種操作,顯然只有宣告為公用的方法(成員函式)才能被外界所啟用;訊息:就是一個命令,要求物件執行的一個操作,呼叫一個物件的方法(如 t.set() )就是一個發給物件的訊息,外界就是通過傳送訊息來啟用相關的方法。

 

類的宣告和物件的定義

對於類,在類外不可訪問其非公有屬性的資料成員和成員函式,但一般情況是把資料隱蔽起來,把成員函式作為對外界的介面。在一個類中應當至少有一個公用的成員函式,作為對外的介面,否則就無法對物件進行任何操作。關於類型別的宣告,有其一般形式:

class 類名{
	private:
		私有的資料和成員函式。 
	public:
		公用的資料和成員函式。
	//protected:
		//受保護的資料和成員函式。
		//後邊類的繼承和派生處用到。 
};

private 和 public 稱為成員訪問限定符 ,用它們來宣告各成員的訪問屬性 。如果在類的定義中既不指定 private ,也不指定 public ,系統預設是私有屬性的,也正是由於C++增添了 class 型別後 ,前邊的結構體型別(struct)仍保留著,而且將它的功能擴充套件了,也允許用struct 去宣告一個類,和類(class)的定義一樣,但在用的結構體中,雖然也可以設定訪問屬性,但預設情況下是公用屬性。用 struct 宣告的結構體型別實際上也就是類。在大多情況下,都把所有資料指定為私有,以實現資訊隱蔽。

private 宣告為私有的成員,只能被本類中的成員函式所引用,類外不能呼叫(友元函式除外);public 宣告為公用的成員,既可以被本類中的成員函式引用,也可以被類的作用域內的其他函式所引用;此外還有一種成員訪問限定符 protected (受保護的),用protected 宣告為受保護的成員,它也不能在類外被訪問(這點與私有成員類似),但可以被派生類的成員函式訪問

在一個類體中, 關鍵字 private 和 public 可以分別出現多次 , 即一個類體中可以包含多個 private 和 public 的部分,但為了使程式清晰,應養成一個習慣:使每一種成員訪問限定符在類定義體中只出現一次。我的習慣是那些私有的我省去了 private 這個關鍵字,知道是私有的就行。

 

定義物件的方法

  • 先宣告 類 型別,然後再定義物件。

1): class 類名 物件名 ;

class Student std ;

2): 類名 物件名 ;

Student std ;
  • 在宣告類的同時定義物件。

class Student{
		int num ;
		char sex ;
		string name ;
	public:
		void display(){
			cout << "num=	" <<num <<endl;
			cout << "sex=	" <<sex <<endl;
			cout << "name=	" <<name <<endl;
		}
}std1 , std2;

 

  • 不出現類名,直接定義物件。

class {
		int num ;
		char sex ;
		string name ;
	public:
		void display(){
			cout << "num=	" <<num <<endl;
			cout << "sex=	" <<sex <<endl;
			cout << "name=	" <<name <<endl;
		}
}std1 , std2;

 

這定義方法看個人習慣,我比較喜歡先定義好一個類之後,在主函式中定義物件,這種定義還是比較清晰的。

 

類的成員函式

一般把外界需要呼叫的函式指定為 public ,作為類的對外介面使用 ,但有的函式並不是準備對外呼叫的,而僅僅作為本類中的成員函式所呼叫,那就應該指定為 private ,這種函式的作用是支援其他函式的操作,是類中其他成員的工具函式,使用者不能去呼叫。但像一般學習的過程中,很少用到這種工具函式,畢竟寫的程式不會太大,除非去做一個大的專案。

 

類體中定義函式時,不需要在函式名前加類名,因為函式屬於哪一類很明顯,但如果在類外定義時,必須在函式名前加上類名,予以限定是哪個類中的成員函式。“::” 是作用域限定符或稱作用域運算子 ,用它來宣告函式屬於哪個類。

class Student{
	void display() ;
};
void Student :: display(){
	
} 

如果在作用域運算子 “::” 的前面沒有類名,或者函式名前面既無類名又無作用域運算子,如 “ ::display()” 或 “ display() ” 表示該函式不屬於任何類,不是成員函式,而是全域性函式,即一般函式。

 

1、內建成員函式

在類體中定義的成員函式的規模一般都很小,而系統呼叫函式的過程所花費的時間開銷相對於是比較大的,呼叫一個函式的時間開銷遠遠大於小規模函式體中全部語句的執行時間,故為了減少時間開銷,如果在類體中定義的成員函式中不包括迴圈等控制結構,系統會自動對它們作為內建(inline)函式來處理。用 inline 宣告的作用是在系統呼叫這些函式時,並不是真正執行函式的呼叫過程,而是把函式程式碼嵌入程式的呼叫點,而若不用 inline 宣告,在呼叫時先去函式程式碼段的入口地址,執行完該函式程式碼段後再返回函式呼叫點,可見用 inline (內建)函式可以大大減少呼叫成員函式的時間開銷。

對一般的內建函式要用關鍵字 inline 宣告 ,但對類體內定義的成員函式 ,可以省略 inline ,因為這些成員函式已預設指定為內建函式。如:

class Student{
		int num ;
	public:
		void display(){	// inline void display()
			cout << "num=	" <<num <<endl;
        }
};

對在類體內定義的函式,一般都省略inline ,但如果成員函式不在類體內定義,而在類體外定義,系統並不把它預設成內建函式,呼叫這些成員函式的過程和呼叫一般函式的過程是相同的,如果仍想把這些成員函式指定為內建函式,應當用 inline 做顯式宣告。如:

class {
		int num ;
	public:
		inline void display();	//宣告為內建函式 
};
inline void Student :: display(){	//類外定義函式為內建函式 
	cout << "num=	" << num <<endl;
}

這樣不利於類的介面與類的實現分離,不利於資訊隱蔽,雖然程式的執行效率提高了,但從工程質量角度來看,這並不是好方法,只有在類外定義的成員函式規模很小且呼叫頻率較高時,才指定為內建函式。此處也就是隨口提了一句,後面基本上不做 inline 的宣告,現在瞭解一下即可。

 

2、成員函式的儲存方式

對於同一類的不同物件中的資料成員的值一般是不相同的,而不同物件的函式的程式碼是相同的,不論呼叫哪一個物件的函式的程式碼,其實呼叫的都是同樣內容的程式碼,因為在計算機中人們用一段空間來存放這個共同的函式的目的碼,在呼叫各物件的函式時,都去呼叫這個公共的函式程式碼。這樣做也大大節約了儲存空間,因此每個物件所佔用的儲存空間只是該物件的資料成員所佔用的儲存空間,而不包括函式程式碼所佔用的儲存空間。嘗試一下:

#include <iostream>
using namespace std ;
class Student{
		int i ;
		char j ;
	public: 
		void display() ;
};
int main(){
	cout << sizeof(Student) <<endl;
}

證明了一個物件所佔的空間大小隻取決於該物件中資料成員所佔的空間,與成員函式無關,這並不是說函式不佔用儲存空間,而是說函式的目的碼是儲存在物件空間之外。下面說一下一些需要注意的方面:

  1. 不同的物件使用同一個函式程式碼段,那是如何分別對不同物件中的資料進行操作呢?系統專門設有一個叫 “ this ” 的指標,用於指向不同的物件,當呼叫物件 std1 的成員函式時, this 指標就指向了物件 std1 ,成員函式訪問的就是 std1 的成員,此處就略微介紹一下 this 的情況,後面還有關於 this 的進一步的說明。
  2. 不論成員函式是在類體內定義還是在類外定義,成員函式的程式碼段的儲存方式是相同的,都不佔用物件的儲存空間。
  3. 不要把成員函式的儲存方式和 inline (內建)函式的概念混淆 ,不論是否用 inline 宣告 ,成員函式的程式碼段都不佔用物件的儲存空間, inline 函式隻影響程式的執行效率,而與成員函式是否佔用物件的儲存空間無關 。
  4. 既然成員函式的程式碼段不在物件的儲存空間中,但“物件 std 的成員函式 display() ” 這樣的說法仍是對的,因為從邏輯的角度上看,成員函式是和資料一起封裝在一個物件中,只允許本物件中成員函式去訪問同一物件中的私有資料。

 

物件成員的引用

訪問物件中的成員有三種方法:1)、通過物件名和成員運算子訪問物件中的成員;2)、通過指向物件的指標訪問物件中的成員;3)、通過物件的引用訪問物件中的成員。

 

1、通過物件名和成員運算子訪問物件中的成員

成員運算子 : “ . ” 

其一般形式為: 物件名.成員名 。如:

#include <iostream>
using namespace std ;
class Student{
		int i ;
	public: 
		int j ;
		void display() ;
};
void Student::display(){
	cout << "j= " << j <<endl;
}
int main(){
	Student s ;
	s.j=1 ;
	s.display();
}

 這都是在類外訪問,而在類外只能訪問 public 成員 。

2、通過指向物件的指標訪問物件中的成員

需要定義一個指向類 型別的指標。如:

Student s , *p ;
p=&s ;
p->j=1 ;
p->display(); 

這樣寫的結果同上一樣。對於指標的訪問,還可以有 (*p). j  。

3、通過物件的引用來訪問物件中的成員

引用:引用與其代表的物件共享同一儲存空間,它們代表的是同一個物件,只是用不同的名字表示。用作引用的變數不再分配新空間,另外需要注意的是在定義引用時就必須對其進行初始化,不然會出錯。如:

Student s1 ;
Student &s2=s1 ;        //定義Student類引用s2,並初始化為s1
s2.j=1 ;
s2.display();

 

類和物件的簡單應用

我寫一段短的類和物件的使用,儘可能用的功能多一點。

#include <iostream>
using namespace std ;
class Time{
		int hour ;
		int min ;
	public:
		int sec ;
		void set();
		void show(){
			cout<<"時間是:" << hour << ":" << min << ":" << sec << endl; 
		}
};
void Time::set(){
	cout<<"輸入時、分、秒 :" << endl;
	cin>>hour>>min ;
}
int main(){
	Time t ;
	t.set();
	cin>>t.sec ;
	t.show() ;
}

下面是把物件作為一個普通函式的形參來表示。

#include <iostream>
using namespace std ;
class Time{
		int hour ;
		int min ;
	public:
		int sec ;
		void set(){
			cout<<"輸入時、分 :" <<endl;
			cin>>hour>>min ;
		}
		void show();
};
void Time::show(){
	cout<<"時間是:" << hour << ":" << min << ":" ; 
}
void show(Time &t){
			cout<<t.sec<<endl;
		}
void set(Time &t ,  int s){
	t.sec=s ;
}
int main(){
	Time t ;
	int s ;
	t.set();
	cout<<"輸入秒: "<<endl;
	cin>>s;
	set(t,s);     //傳進去一個“預設”引數。
	t.show();
	show(t);
}

寫的太不正規了,這只是為了看的更清晰一點,如果自己對這方面都掌握的話,寫在程式中別像這樣,看著很亂。另外在寫的時候會發現用物件作為一個普通函式的形參時,在函式體中只能對物件的公用成員進行操作,其 private 成員已經是屬於在類外訪問,不可取。注意區分什麼場合用域運算子“::”,什麼場合用成員運算子“.”,不要搞混。另外對於一些新學的知識,最好是自己把自己的想法都上機演示一下,看看自己想的有沒有毛病。

 

類宣告和成員函式定義的分離

如果一個類被多個程式使用,往往把類的宣告(包含對其成員函式的宣告)放在指定的標頭檔案中,在使用該類時,只要把有關的標頭檔案包含進來即可, 減少工作量,提高了程式設計的效率。另外為了實現之前說的資訊隱蔽,不讓使用者看到函式執行的細節,那麼再把對類成員函式的定義不和類的宣告放一個頭檔案中,而是放在另一個檔案,此時注意在系統提供的標頭檔案中只包括對成員函式的宣告,不包括成員函式的定義,類的宣告和定義是分別放在兩個檔案中。

實際上,一個C++程式是由 3 個部分組成:(1)類宣告標頭檔案(字尾為 .h 或者無後綴);(2)類實現檔案(字尾為 ,cpp ),包括對類成員函式的定義;(3)類的使用檔案(字尾為 .cpp ),即主檔案。下面解釋一下:

這是主函式寫的實現程式碼。這塊的檔名是“main.cpp” ,可見在這函式板塊中,引入了“time.h”標頭檔案後,有關類的宣告和類內部實現的功能都未顯示,但這個程式是正確的,原因就是剛才說的將類宣告和成員函式實行了分離。

//#include <iostream>        //不需要輸入輸出,沒有相關語句
#include "time.h"     //將類宣告的標頭檔案包含進來
int main(int argc, char** argv) {
	Time t;
	t.set();
	t.show();
	return 0;
}

這部分是類的宣告檔案“time.h” ,僅有類的定義、成員的宣告,而沒有成員函式的定義,這就很好地實現的資訊的隱蔽性。

class Time{
		int hour;
		int min;
		int sec;
	public:
		void set();
		void show();
};

這是有關類成員函式的定義,由於這個檔案中需要進行輸入和輸出,相關的標頭檔案也要注意寫。

#include <iostream>
#include "time.h"        //此處不要忘記引入
using namespace std;
void Time::set(){
	cout<<"設定時間:"<<endl;
	cin>>hour>>min>>sec ;
}
void Time::show(){
	cout<<"時間顯示:"<<endl;
	cout<<hour<<":"<<min<<":"<<sec<<endl;
}

但要注意的是,如果想對類的宣告和成員函式定義進行分離的話,需要建一個專案,這些檔案需要在同一個專案中,因為這樣系統能知道哪個是原始檔、標頭檔案,不然這種實現方式是不成功的,或許檔案儲存時改一下型別也有可能正確,但我不是很清楚,有興趣的可以去鑽研一下。

在實際工作中,並不是將一個類宣告做成一個頭檔案,而是若干個常用的功能相近的類宣告集中在一起,形成類庫。類庫有兩種:一種是系統提供的標準類庫;另一種就是使用者根據自己的需要做成的使用者類庫,提供給自己或者其他人使用,這稱為自定義類庫。類庫包括兩個組成部分:(1)類宣告標頭檔案;(2)已經過編譯的成員函式的定義,它是目標檔案。我們只需把類庫裝入到自己的計算機系統中(一般是編譯系統所在的子目錄下,專案下邊就是類似的情況),然後在程式中用“#include”指令將有關的類宣告的標頭檔案包含進來就可以使用。在程式開發工作中,類庫很有用,所以對我們學生來說,先了解一下就好。