1. 程式人生 > >C++面向物件- -類和物件的使用(二)

C++面向物件- -類和物件的使用(二)

 


目錄

物件指標

1、指向物件的指標

2、指向物件成員的指標

3、指向當前物件的 this 指標

共用資料的保護

1、常物件

2、常物件成員

3、指向物件的常指標

4、指向常物件的指標

5、物件的常引用

6、const 型資料小結


 

物件指標

指標不僅可以指向普通變數,也可以指向物件。

1、指向物件的指標

一個物件儲存空間的起始地址就是物件的指標。換句話說,定義一個指標變數,去存放物件的地址,這就是指向物件的指標變數。其一般形式為: 類名 *物件指標名 。

Time *p;    //指向 Time 類的指標變數
Time t;
p=&t;    //將物件t的起始地址賦給指標p

 

2、指向物件成員的指標

物件中的成員也有地址,那麼用來存放物件中成員地址的指標變數就是指向物件成員的指標。成員分資料成員和成員函式,故指標也有兩類:

  • 指向物件資料成員的指標
Time t ;
int *p ;
p=&t.hour ;
cout<<"hour="<<*p <<endl;

需要注意的是,類中的資料成員需要是 public 型別的,但一般資料成員都是 private ,所以這種指標很少會用到。

  • 延:指向普通函式的指標
int (*p)() ;	//定義一個指向 int 型函式的指標變數 
p=max ;	        //將 max 函式的入口地址賦給指標變數 p ,p此時就指向了函式 max  
(*p)() ;	//呼叫函式 max 
  • 指向物件成員函式的指標

而指向物件成員函式的指標卻不能像指向普通函式一樣

p = t.show ;

因為現所需要呼叫的函式是屬於一個類,用指標指向時需指明是屬於哪個類。

void (Time:: *p)();
p=&Time::show; 

定義指向公用成員函式的指標變數的一般形式為: 資料型別名(類名:: *指標變數名)(引數列表);使指標變數指向一個公用成員函式的一般形式為: 指標變數名 = & 類名:: 成員函式名 。

下面列一個例子有助於瞭解一下:

class Time{
		int hour ;
		int min ;
	public:
		int sec ;
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
		void show(){
			cout<<hour<<":"<<min<<":"<<sec<<endl;
		} 
}; 
void display(){
	cout<<"   " <<endl;
} 
int main(){
	Time t(1,1,1);
	int *p1;		//指向物件資料成員的指標 
	p1=&t.sec;
	cout<<*p1<<endl;
	void (Time :: *p2)();	//指向物件成員函式的指標 
	p2=&Time::show ;   //注意不需要後邊的括號!!!
	(t.*p2)();
	void (*p3)();		//指向普通函式的指標 
	p3=&display;    //注意不需要後邊的括號!!!
	(*p3)();
	Time *p4=&t ;		//指向物件的指標 
	p4->show();
}

注意關於指向物件的成員函式的指標,其成員函式的入口地址正確寫法是 (p2=&Time::show),而不是(p2=&t.show),前面已提到過,成員函式不是存放在物件的空間中,而是在物件外的空間,如果有多個同類的物件,它們是公用的一個函式程式碼段。還可以簡寫:

void (Time :: *p2)()=&Time::show ; 
(t.*p2)();

 

3、指向當前物件的 this 指標

在每一個成員函式中都包含有一個特殊的指標,這個指標有個固定的名字 ,稱為 this ,它是指向本類物件的指標,它的值是當前被呼叫的成員函式所在的物件的起始地址。例如,當呼叫成員函式 t show() 時,編譯系統就把物件 t 的起始地址賦給 this 指標,於是在成員函式引用資料成員時,就按照 this 指標的指向找到物件 t 的資料成員 。

//show()函式輸出時分秒 hour:min:sec ,實際上是執行:
this->hour : this->min : this->sec 
//由於當前的 this 指向 t ,故相當於執行:
a.hour : a.min : a.sec

this 指標時隱式使用的,它是作為引數被傳遞給成員函式。本身成員函式 show()的定義是:

void show(){
    cout << hour << ":" << min << ":" << sec <<ednl;
}

由於 this 的介入,編譯系統是這樣處理的:

void show(Time *this){
    cout << this->hour << ":" << this->min << ":" << this->sec <<endl;
}

即在成員函式的形引數表列中增加一個 this 指標 ,在呼叫該成員函式時,實際上是用以下的方式呼叫的:

t.show(&t);

這些都是編譯系統自動實現的,像我們就沒必要刻意地在形參中增加 this 指標 ,也不必將物件 t 的地址傳給 this 指標,這就是讓我們對 this 指標的作用和實現原理有更清楚的認識。當然也可以顯式地使用 this 指標:

void show(){
	cout << this->hour << ":" << this->min << ":" << this->sec <<endl;
} 

都是正確的。也可以用 *this 表示被呼叫的成員函式所在的物件,*this 就是 this 所指向的物件,即當前的物件,例如在 t show() 的函式體中,如果出現的 *this ,那麼它就是指的本物件 t 。但注意:

void show(){
	cout << (*this).hour << ":" <<(*this).min << ":" << (*this).sec <<endl;
} 

*this 兩側的括號不能省去,因為成員運算子“ ”的優先順序高於指標運算子" * "。

經過 this 的說明,那麼所謂 “呼叫物件 t 的成員函式 show()”,實際上是在呼叫成員函式 show()時使 this 指標指向物件 t ,從而訪問物件 t 的成員 ,這個是對 “呼叫物件 t 的成員函式show()”的正確解釋含義。

 

共用資料的保護

對於有些資料,使它既能在一定範圍內共享,又要保證它不被任意修改,這時就可以把相關資料定義為常量。

1、常物件

在定義物件時前加關鍵字 const ,指定物件時常物件,常物件一定要有初值

定義過常物件之後,在該物件的生命週期內,物件的所有的資料成員的值都不能被改變。定義常物件的一般形式是:       類名 const 物件名(實參表),或者將 const 放到最前邊 : const 類名 物件名(實參表)。在定義物件時必須對之初始化,但如果物件所在的類中有預設建構函式就不做要求,之後物件就不能再改變。

如果一個物件被宣告為常物件,則通過該物件只能呼叫它的常成員函式,而不準呼叫物件的普通成員函式(除系統自動呼叫的建構函式和解構函式),常成員函式是常物件唯一的對外介面,這是為了防止普通成員函式會修改物件中資料成員的值。另外常成員函式可以訪問常物件中的資料成員,但不準修改常物件中資料成員的值。

以上兩點(不能呼叫常物件中的普通成員函式和常成員函式不準改變其物件的資料成員的值)就保證了常物件中的資料成員的值絕對不會被改變。

但有時會需要修改常物件中某個資料成員的值(例如一個用於計數的變數 count ,其值需要能不斷變化),考慮到實際需要,對此作了一個特殊的處理,對該資料成員宣告為 mutable

mutable int count ;

即把 count 宣告為可變的資料成員,這樣就可以用宣告為 const 的成員函式修改它的值。

class Time{
		int hour ;
		int min ;
		mutable int sec ;
	public:
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
		void show() const{
			cout << hour << ":" <<min << ":" << sec <<endl;
		} 
		void count() const ;
}; 
void Time::count()  const {
	sec++;
}
int main(){
	Time const t;
    const Time t1;
	t1.show();
	t.count();
	t.show();
}

 

2、常物件成員

物件的成員也可以宣告為常量,包括常資料成員和常成員函式。

  • 常資料成員

常資料成員的值是不能被改變的,只能通過建構函式的引數初始化表對常資料成員進行初始化,任何其他函式都不能對常資料成員賦值。

const int hour ;


Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
Time (int h=1 ,int m=0 , int s=0):hour(h){
	min=m;
	sec=s;
}

注意是只能通過建構函式的引數初始化表進行初始化!,可以試一下用建構函式的其他初始化方式,發現都會出錯,因為其他建構函式的初始化操作仍屬於對常資料成員的賦值操作。

Time(){
	hour=0;
}
Time(int h){
	hour=h;
}

常物件的資料成員都是常資料成員,因此在定義常物件時,建構函式只能用引數初始化表對常資料成員進行初始化

  • 常成員函式

前邊已提過常成員函式只能訪問本類中的資料成員,而不能進行修改。

宣告常成員函式的一般形式是:型別名 函式名(引數表) const ; 。和宣告常物件時不一樣,const 只能放在最後(函式名和括號之後) 。另外不要忘記的是 const 是函式型別的一部分,在宣告函式和定義函式時都需要有 const 的關鍵字,只有在呼叫時不需要加 const 。如下:

class Time{
		int hour ;
		int min ;
	public:
		int sec ;
		Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
		void show() const{
			cout << hour << ":" <<min << ":" << sec <<endl;
		} 
		void show_1() const ;
}; 
void Time::show_1()  const {
	cout << hour << ":" <<min << ":" << sec <<endl;
}
int main(){
	Time const t;
	const Time t1(1,1,1);
	t1.show_1();
	t.show();
}

 

下面用一個表格來使對資料成員的使用印象更直觀一點:

資料成員

非 const 的普通成員函式

const 成員函式
非 const 的普通資料成員 可以引用,也可以改變值 可以引用,但不可以改變值
const 資料成員 可以引用,但不可以改變值 可以引用,但不可以改變值
const 物件 不允許 可以引用,但不可以改變值

如果在一個類中,有些資料成員的值允許改變,另一些資料成員的值不允許改變,那麼可以將一部分資料宣告為 const ,以保證其值不被改變,用非 const 的成員函式引用這麼資料成員的值,並修改非 const 資料成員的值;如果要求所有的資料的值都不允許改變,則可以將所有的資料成員宣告為 const 或將物件宣告為 const (常物件),然後用 const 成員函式引用資料成員,這“雙保險”的作用,切實保證了資料成員不會被修改。

常物件只保證其資料成員是常資料成員,其值不會被修改,如果在常物件中的成員函式未加 const 宣告,編譯系統把它作為非 const 成員函式處理。另外需注意,常成員函式不能呼叫另一個非 const 成員函式

 

3、指向物件的常指標

說的是將指標變數宣告為 const 型,這樣指標變數始終保持為初值,不能改變,即其指向不能改變。定義指向物件的常指標變數的一般形式為: 類名 *const  指標變數名 ; const位置在指標變數名前面。另外注意應該在定義指標變數時使之初始化。

Time t;
Time * const p=&t ;
//p=&t;
p->show();
t.show();

如果想將一個指標變數固定地與一個物件相聯絡(即該指標變數始終指向一個物件),可以將它指定為 const 型指標變數,這樣可以防止誤操作,增加安全性。

 

4、指向常物件的指標

  • 引:指向常變數的指標
const char *p;

const 的位置在最左側,與型別名 char 緊連,表示指標變數 p 指向的 char 變數是常變數,不能通過 p 來改變其值。

那麼有指向常變數的指標變數的一般形式為: const 型別名 * 指標變數名 ;

  • 如果一個變數已被宣告為常變數,只能用指向常變數的指標變數指向它,而不能用一般的(指向非 const 型變數)指標變數去指向它
const char str[] = "hello" ;
const char *p ;
p=str ;       //陣列名就是陣列中第一個元素的地址
char *q=str ;        //錯誤
  • 指向常變數的指標除了可以指向常變數外,還可以指向未被宣告為 const 的變數,此時不能通過此指標變數改變其變數的值,但如果不是通過指標訪問,則變數的值是可以改變的(即能通過改變原變數的值該改變常指標目前的值)。
const int a=1;
int b=2 ;
const int *p ;
int *q;
//q=&a;	錯誤,無法去指 
p=&b;
cout<<*p<<endl;
//*p=1;	錯誤,不能改變值 
b=3;
cout<<*p<<endl;	
q=&b;
*q=1;
cout<<*q<<endl; 

注意:定義了指向常變數的指標 p 並使它指向 b ,並不意味著把 b 也宣告為常變數,而只是在用指標變數訪問 b 期間,b 具有常變數的特徵(即其值不能改變),在其他情況下, b 仍然是一個普通的變數,其值是可以改變的。

  • 如果函式的形參是指向普通(非 const )變數的指標變數,實參只能用指向普通(非 const)變數的指標,而不能用指向 const 變數的指標,這樣在執行函式的過程中可以改變形參指標變數所指向的變數(也就是實參指標所指向的變數)的值。

使用形參和實參的對應關係表:

形參 實參 合法否 改變指標所指向的變數的值
指向非 const 型變數的指標 非 const 變數的地址 合法 可以
指向非 const 型變數的指標 const 變數的地址 非法   /
指向 const 型變數的指標 const 變數的地址 合法 不可以
指向 const 型變數的指標 非 const 變數的地址 合法 不可以

可見指向常變數的指標變數可以指向 const 和 非 const 型的變數,而指向非 const 型變數的指標只能指向非 const 的變數


指向常物件的指標變數的概念和使用與上邊的很類似,只要將 “變數” 換成 “物件” 即可。

  • 如果一個物件已被宣告為常物件,只能用指向常物件的指標變數指向它,而不能用一般的(指向非 const 型物件)指標變數去指向它
  • 如果定義了一個指向常物件的指標變數,並使它指向一個非 const 型的物件,則其指向的物件是不能通過該指標變數去改變的(同上邊一樣,只能訪問,改變只能是物件自身發生改變)。
Time t(1,1,1);
const Time *p;
p=&t; 
//p->sec=2;	不能被改變 
t.sec=2;
(*p).show();     //注意show()的型別
p->show();

注意:上邊最後提到,指向常變數的指標指向非 const 型變數時,在指標訪問期間,該變數具有了常變數的的特徵。在此處同樣,非 const 型的物件 t 在指標訪問期間有的常物件的特徵,而常物件只能訪問其內的常成員函式,上邊的show()函式應該定義成 const 型。

  • 指向常物件的指標最常用於函式的形參,目的是在保護形參指標所指向的物件,使它在函式執行過程中不被修改
void set(const Time *p){
	//p->sec=1;	 錯誤 
	cout<<p->sec<<endl;
}
int main(){
	Time t;
	set(&t);
}

如果形參不是指向 const 型 Time 物件的指標變數,即函式形參為  “ void set (Time *p)” 此時 t 的值可以在函式 set中被修改。前邊已提過,如果形參是指向非 const 物件的指標變數,那麼實參不能是 const 型的物件。

當希望在呼叫函式時物件的值不被修改,就應當把形參定義為指向常物件的指標變數,同時用物件的地址作實參(物件可以是 const 或 非 const 型);如果要求物件不僅在呼叫函式過程中不被改變,而且要求它在程式執行過程中都不被改變,則應把它定義為 const 型。

  • 如果定義了一個指向常物件的指標變數,是不能通過它改變所指向的物件的值,但指標變數本身的值是可以改變的
Time t1 , t2 ;
const Time *p=&t1;
p=&t2 ;
//p->sec=1;	不能更改 

 

  • 延:為什麼常用指標作函式引數?舉兩個基礎的例子:
//這是以前接觸到的值的互換的函式:
void set(int *p,int *q){		//雖然這方面用引用會更簡單,但這也是為了解釋指標的一個方式 
	int r ;
	r=*p ;
	*p=*q ;
	*q=r ;
}
int main(){
	int a=1,b=2 ;
	set(&a,&b);
	cout<<"a="<<a<<'\t'<<"b="<<b<<endl;
}

//物件指標
void set(Time t){
	t.sec=1;
}
int main(){
	Time t;
	set(t);
	cout<<t.sec<<endl;
} 

因為形參中出現了變數名 t ,是在函式呼叫時建立了一個新的物件 t ,是實參 t 的的一個拷貝,實參把值傳給形參,二者佔用不同的儲存空間,所以無論形參如何變化都不會影響到實參的值。尤其是當物件(變數)的規模比較大時,時間和空間開銷都可能會很大,所以常用指標作函式引數(或者用 引用 表示)。

 

5、物件的常引用

物件的引用和變數的引用用法都一樣,也都表示起了一個別名的作用,也都共用一段儲存空間。此處講的是把引用宣告為 const ,即常引用。

//物件的引用
void set(Time &t){
	t.sec=1;
} 

//物件的常引用
void set(const Time &t){
	t.sec=1;        //常引用時不能改變物件中成員的值
}
int main(){
	Time t;
	set(t);
	t.show();
}

在程式設計中,經常用常指標和常引用用作函式引數,這樣既能保證資料安全,是資料不能被任意修改,在呼叫函式時又不必建立實參的拷貝。後邊會了解到,每次呼叫函式建立實參的拷貝時,都需要呼叫複製建構函式,有時間開銷。用常指標和常引用作函式引數時,可以提高程式執行效率。

 

6、const 型資料小結

從最開始的常物件、常物件成員,這部分還好,認識都比較清晰,但到了指向物件的常指標、指向常物件的指標,在形式上也是能分清,但它們的功能有的都是相互交錯,很容易就混淆了,下面稍微總結一下 const 的用法。

形式 含義
Time const t ; t 是常物件,其值在任何情況下都不能被改變。
void Time :: show() const ; show是 Time 類中的常成員函式,可以引用,但不能修改本類中的資料成員。
Time * const p ; p 是指向 Time 類的常指標變數,p 的值(其指向)不能改變。
const Time * p ; p 是指向 Time類常物件的指標變數, p 指向的類物件的值不能通過 p 來改變。
const Time &t1=t ;

t1 是 Time 類的物件 t 的引用,二者指向同一儲存空間,t 的值不能被改變。

const 型別資料是很重要的,這部分的內容比較繁瑣難記,在學習時不必死記,對它有一定的瞭解即可,以後有不會的就再回顧一下,加深理解,慢慢就掌握了。