2018年3月13日 星期二

《筆記》談談C++語言 - 4:定義類別、成員函式、建立物件、呼叫成員函式、private、get 及 set 成員函式

定義類別、成員函式、建立物件、呼叫物件中的成員函式

#include "stdio.h"
#include "stdlib.h"

class GradeBook {

public:

 void displayMessage(){
  printf("Welcome to GeadeBook");
 }

};


int main() {

 GradeBook myGradeBook;
 myGradeBook.displayMessage();

 return 0;
}




首先我們使用保留字 class 定義一個類別,名為 GradeBook 。 可以感覺的出來跟在學 struct 的時候一模一樣,在前一章我們也有說過事實上 class 跟 struct 一樣,是在「定義一個新的型別(type)」。

在 GradeBook 類裡面我們使用了修飾子標記(access-specifier label) public:

public 是存取修飾子(access specifier) ,要記得冒號。

我們先看後面的 void displayMessage() 成員函式,這個函式定義在 public: 之後,代表這個成員函式是「公開」的,什麼是公開的呢?

也就是說「這個成員函式在程式中的其它函式(例如 main 函式)以及其它類別的成員函式都可以搭配物件名稱來呼叫它」

最後在大括弧 } 的後面記得加上分號,這部分跟 struct 是一模一樣的。事實上 class 與 struct 有很多相似之處,或者我們可以說 class 是 struct 的變種版本,至於兩者的差異在哪在這我就不多提了。

在 main 函式裡面我們使用 GradeBook myGradeBook; 來建立一個名為 myGradeBookd 的物件(實體) 其型別(type)為 GradeBook。

這部分也跟 struct 是一模一樣的。甚至下一行的 myGradeBook.displayMessage(); 觀念亦同。

若忘記之前的概念,可以這樣子想一下。我們在定義整數型態的變數時會這樣子定義
int a;

也就是宣告一個名為 a 的變數,型別(type)為 int

當我們宣告 int 型態的變數時,編譯器知道 int 是什麼東西,因為它是基本型別。而編譯器本來是不知道 GradeBook 是什麼型別,直到我們使用保留字 class 來產生一個自定義型別,從此刻起編譯器就知道什麼是 GradeBook 型別了。

因為 C++ 廣泛的用到這樣的概念來進行程式撰寫,所以 C++ 被認為是「可擴充語言(extensible language)」的原因之一。

具有輸入引數的

#include "stdio.h"
#include "stdlib.h"

class GradeBook {

public:

 void displayMessage(char* str){
  printf("Welcome to [%s] GeadeBook",str);
 }

};


int main() {
setvbuf(stdout,NULL,_IONBF,0);

 char nameOfCourse[10];
 GradeBook myGradeBook;
 printf("Enter your Course name : ");
 scanf( "%s", nameOfCourse );

 myGradeBook.displayMessage(nameOfCourse);

 return 0;
}




private

#include "stdio.h"
#include "stdlib.h"

class GradeBook {

public:
 void setCourseName ( char* name){
  courseName = name;
 }

 char* getCourseName (){
  return courseName;
 }

 void displayMessage(){
  printf("Welcome to [%s] GradeBook", getCourseName() );
 }

private:
 char* courseName;

}myGradeBook;


int main() {

 myGradeBook.setCourseName("Advanced Math");
 myGradeBook.displayMessage();

 return 0;
}




前面我們提過在函式裡面宣告的變數稱為區域變數,其生命週期在該函數大括號後結束,並且會被清除。而類別的資料成員也是一樣,其生命週期與「物件」的生命週期一致,也就是該物件存活多久,資料成員就活多久。

除此之外類裡面的資料成員還有一個很重要的點,在於資料的隱藏特性。

上述的程式碼我們定義了三個成員函式為 setCourseName()、getCourseName()、displayMessage(),功能如下

setCourseName 將輸入的引數賦予給 courseName (這個變數的定義在後面提到)
getCourseName 回傳 courseName
displayMessage 顯示 getCourseName 的結果,也就是 courseName

接著我們用了另一個存取修飾子 private ,在該修飾子後方定義的東西(資料成員&成員函式)都會被認為是私有的。什麼是私有呢?

也就是「只能為該類別的成員函式存取(也就是上述三個),類別外部的函式(如 main )或程式中其它類別的成員函式都不能存取它」

將 courseName 定義為 private 後我們就可以理解為什麼有成員函式 getCourseName 的存在,因為 courseName 是私有的,只能被該類別中的成員函式所存取,若我們想要在 main 中得到這個資料成員的資訊,必須用這樣的方式才能取得(後續的例子會在對這部分進行示範)

資料成員設置為 public,直接取得資料成員

#include "stdio.h"
#include "stdlib.h"

class GradeBook {

public:
 void setCourseName ( char* name){
  courseName = name;
 }

 char* getCourseName (){
  return courseName;
 }

 void displayMessage(){
  printf("Welcome to [%s] GradeBook", getCourseName() );
 }

 char* courseName;

}myGradeBook;


int main() {

 myGradeBook.setCourseName("Advanced Math");
 printf("%s", myGradeBook.courseName);

 return 0;
}



我們將 private: 存取修飾子取消掉,這樣子 courseName 就在 public 之下。

如此一來我們就能在 main 函式中使用 myGradeBook.courseName 直接取得 courseName

private 的資料隱藏

#include "stdio.h"
#include "stdlib.h"

class GradeBook {

public:
 void setCourseName ( char* name){
  courseName = name;
 }

 char* getCourseName (){
  return courseName;
 }

 void displayMessage(){
  printf("Welcome to [%s] GradeBook", getCourseName() );
 }

private:
 char* courseName;

}myGradeBook;


int main() {

 myGradeBook.setCourseName("Advanced Math");
 printf("%s", myGradeBook.courseName);

 return 0;
}



我們把 private: 再加回來, 就會出現這個資料成員是 private 而不能成功編譯

預設為 private

#include "stdio.h"
#include "stdlib.h"

class GradeBook {

 char* courseName;

public:
 void setCourseName ( char* name){
  courseName = name;
 }

 char* getCourseName (){
  return courseName;
 }

 void displayMessage(){
  printf("Welcome to [%s] GradeBook", getCourseName() );
 }

}myGradeBook;


int main() {

 myGradeBook.setCourseName("Advanced Math");
 printf("%s", myGradeBook.courseName);

 return 0;
}



我們把 char* courseName; 移到 public: 的上方會發生什麼事呢?

事實上 「class 內的定義(資料成員&成員函式) 預設是 private 的」


話說 get 及 set 成員函式

大多數有經驗的程序設計師在編寫一個類的時候都會有一個習慣,一般而言會把資料成員宣告為 private ;把成員函式宣告為 public 。

這樣的作法當程式建立(實體) 一個物件時,此物件就封裝(隱藏)了資料成員,實現了資料隱藏( data hiding ),只有該物件類別的成員函式能存取它

也因為這樣的特性,往往都會有 get 以及 set 的成員函式來讓外部的函式以及其它類可以間接存取 private 的資料成員。且因為廣泛的應用,這樣的成員函式還具有專有名詞

例如 get 的成員函式通常稱為 存取子(accessor);set 的成員函式通常稱為 改變子(mutator)

事實上這個 get 及 set 函式可以讓用戶端取得或是修改物件的資料,但用戶端並不會知道物件執行這些操作的細節。有些時候,類別對內會用一種方式呈現該資料成員,對外則是用另一種方式來呈現資料成員。

舉例來說用戶端想要取得某個類存放的時間(clock)資料成員,使用 get 函式取得後該成員函式回傳 15:14:30 這樣的格式給使用者;但在該類別內的時間(clock)是 115462315 這種數字。這兩者之間的轉換在 get 函式中進行。

set 與 get 函式可讓用戶端與物件之間進行互動,但物件的 private 資料仍安全地封裝(隱藏)在物件中。

順道一提,即使同一類別中的其它成員函式想要存取資料成員,也應該呼叫 get & set 函式對資料成員進行存取或是讀取的動作,而不要「直接」對該資料成員進行取値 or 修改的動作。看似是一個拐了彎的做法,實際上這樣的方式可以幫助我們有效的維護這個類別,避免我們需要大規模的進行程式的修改,而只需要更改 get & set 函式的部分就好。