C++學習 第十章
技術標籤:C++學習
1.面向物件程式設計的最重要的特性
- 抽象
- 封裝和資料隱藏
- 繼承
- 多型
- 程式碼可重用性
2.C++中的類
類是一種將抽象轉換為使用者定義型別的C++工具,他將資料表示和操縱資料的方法組合成一個簡潔的包。
- 類的定義方式
1.類宣告:以資料成員的方式描述資料部分,以成員函式的方式描述共有介面。
2.類方法定義:描述如何實現類成員函式。
通常,C++程式設計師將介面(類定義)放在標頭檔案中,並將實現(類方法的程式碼)放在原始碼檔案中。我們也採用這種典型做法,為了幫助識別類,我們使用一個常見但是不通用的約定,將類的首字母大寫。下面看一個類定義的工作方式:
//stock00.h
#ifndef STOCK00_H_
#define STOCK00_H_
#include<string>
//關鍵字class指出下列程式碼是一個類的設計
class Stock
{
private:
std::string company; //公司名稱
long shares; //股票數量
double share_val; //每股價格
double total_val; //總價
void set_tot(){ total_val = shares * share_val;}
public:
void acquire(const std::string & co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
#endif
//可以使用class後的類名稱建立兩個類的物件
//下面就是建立的兩個類的物件sally和solly
Stock sally;
Stock solly;
- 訪問控制:
private和public也是新的關鍵字,他們描述了對類成員的訪問限制。使用類物件的程式都可以直接訪問到共有部分(public),但是隻能通過公有成員函式(或友元函式)來訪問物件的私有成員。例如:要修改Stock類的shares成員,只能通過Stock的成員函式。因此,公有成員函式是程式和物件的私有成員之間溝通的橋樑,提供了物件和程式之間的介面。C++中還提供第三個關鍵字protected,在後面介紹。 - 封裝性
類定義儘可能將公有介面和實現細節分開,公有介面表示設計的抽象元件,將實現細節放在一起將他們他們與抽象分開被稱為封裝。資料隱藏也是一種封裝。 將類函式定義和類宣告放在不同檔案中也是一種封裝。 - 資料隱藏的好處
資料隱藏不僅可以防止直接訪問資料,還讓開發者無需瞭解資料是如何被表示的。作為類的使用者,並不需要知道資料是使用哪種方法表示的,只需要知道各種成員函式的功能即可。將實現細節從埠設計中分離開來,如果以後有更好的,實現資料表示或成員函式細節的方法,可以對這些細節進行修改。而不需修改程式介面。 - 控制對成員的訪問 公有和私有
類很像是一個結構型別,事實上C++中對結構進行了擴充套件,使之擁有了和類相同的特性,而類和結構的唯一區別就是,類預設的儲存型別是private也就是私有的,而結構預設的儲存型別是public,就是公有的。類要求資料隱藏,所以一般都把資料放入私有部分中,使用公有函式對他們進行控制。
class World
{
//上面預設宣告為private
float mass;
char name[20];
public:
void tellall(void);
...
}
3.實現類成員函式
- 定義成員函式時,使用作用域解析運算子::來標識函式所屬類
作用域解析運算子確定了方法定義對應的類的身份,類中的其他成員函式呼叫類中的函式時是不需要加作用域解析運算子的,但是在類宣告和方法定義之外使用update()時,則需要特殊的方法。 - 類方法可以訪問類的private元件
類的私有資料成員,是不能試圖通過非成員函式訪問的(友元函式除外),只有通過類的公有函式才能訪問他們。
//定義成員函式
void Stock::show()
{
...
}
//類方法訪問private元件
void Stock::show()
{
std::cout << "Company: " << company
<< " Shares: " << shares << endl;
}
4.內聯方法
在定義位於類宣告中的函式都將自動稱為行內函數,也就是說Stock::set_tot()是一個行內函數。類宣告常將短小的成員函式作為內斂函式,set_tot()符合這樣的要求。如果願意,也可以在類宣告之外定義成員函式,使其稱為行內函數,為此,只需要在類的實現部分中定義韓式時使用inline限定符即可。
不過需要注意的是行內函數的規則,要在每個使用它的檔案中進行定義,確保內聯定義在每個檔案中都是可用的,最簡便的方法就是:將內聯定義放在標頭檔案之中。
//預設情況下的行內函數
//set_tot() 就是一個行內函數
class Stock
{
private:
std::string company; //公司名稱
long shares; //股票數量
double share_val; //每股價格
double total_val; //總價
void set_tot(){ total_val = shares * share_val;}
public:
void acquire(const std::string & co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
//顯式宣告的行內函數
class Stock
{
private:
std::string company; //公司名稱
long shares; //股票數量
double share_val; //每股價格
double total_val; //總價
void set_tot();
public:
void acquire(const std::string & co, long n, double pr);
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
inline void Stock::set_tot()
{
total_val = shares * share_val;
}
//注意:上述二者等價
5.方法使用哪個物件
首先類定義物件的方式和定義變數的方式類似,所建立的每個物件都有自己的儲存空間,用於儲存其內部變數和類成員。但是同一個類的所有物件都是共享同一組類方法,即每種方法只有一個副本。
Stock sing,song;
//這裡sing song 是Stock類的兩個物件
//sing.shares佔用一個記憶體單元 song.shares佔用另一個記憶體單元
//但是sing.show()和song.show()都是呼叫的同一個方法,他們執行的是同一個程式碼塊
//不同的是程式碼塊中的資料
6.類的建構函式
- 為什麼需要這種函式
C++的目標之一就是讓使用類物件就想使用標準型別一樣,但是現在我們的程式碼並不能讓我們像初始化int一樣,來對我們的類進行初始化,因為我們類的資料訪問狀態是私有的,所以並不能像是初始化結構或者變數那樣對類進行初始化,因為只有成員函式能夠訪問私有資料,所以要對類進行初始化也需要建構函式。C++提供了一種特殊的成員函式,類建構函式,用來構造新物件,將值賦給他們的資料成員。更準確的說,C++為這種成員函式提供了名稱和使用語法,而程式設計師則需要提供方法定義。 - 建構函式
建構函式名稱與類名相同,例如,Stock類一個可能的建構函式是名為Stock()的成員函式。建構函式的原型和函式頭有一個特徵—雖然沒有返回值但是也沒有被宣告為void型別。實際上,建構函式沒有宣告型別。 - 宣告和定義建構函式
建立stock的建構函式,由於需要為Stock物件提供三個值,因此應為建構函式提供三個引數。當然在建構函式中也可以使用預設引數,具體如下:
//第一個引數是指向字串的指標,用於初始化company
//n和pr的引數用於為shares和share_val提供值
//注意 沒有返回值型別
Stock(const string & str, long n = 0; double pr = 0.0);
//函式原型位於類宣告的公有部分
//一種建構函式的可能的定義方式
Stock::Stock(const string & str, long n, double pr)
{
company = co;
//為了簡便就不進行錯誤勘察了
shares = n;
share_val = pr;
set_tot();
}
//程式在宣告物件的時候,將自動呼叫建構函式
//注意:不要將公有函式的引數名設定成和類的成員變數一樣的名字
//否則會發生如下問題
Stock::Stock(const string & company, long shares, double shares_val)
{
company = company
shares = shares;
share_val = share_val;
}
//會導致程式造成混亂
- 使用建構函式的方式
//1.顯式使用
Stock food = Stock("SHIYUQI",156,2.36);
//2.隱式使用
Stock eat("QIBAO",156,2.46);
//每次建立類物件,即使是new分配記憶體,C++都會使用類建構函式
Stock *pt = new Stock("QIQIDAKEAI",156,2.67);
//注意:建構函式是用來建立物件的
//所以不能通過 物件.成員函式 的方式使用
- 預設建構函式
預設建構函式是在未提供顯式初始值時,用來建立物件的建構函式,如下例:
Stock cat;
//之所以這條語句能夠正常執行,完全是因為有預設建構函式
//C++中,如果沒有提供任何建構函式,C++將自動提供預設的建構函式
//它是預設構造引數的隱式版本,不作任何工作
//預設構造引數可能如下
Stock:Stock(){}
//因此將建立cat物件,但是並不會給他賦值
//但是需要注意的是,當且僅當沒有提供顯式的建構函式的時候,系統才會有預設建構函式
//也就是說如果類中提供了建構函式,系統將不再提供預設建構函式
//這樣一來上面的Stock cat; 在提供建構函式的情況下就是一個錯誤語句了
如果想要建立物件,而不顯式的初始化,則必須定義一個不接受任何引數的預設建構函式。
- 一種方法是給建構函式的所有引數都加上預設引數
- 另一種方式是通過函式過載來定義另一個構造引數,一個沒有引數的建構函式
//一個為Stock類定義的預設建構函式
Stock::Stock()
{
company = "NO NAME";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
//一個提供了所有預設引數的預設建構函式
Stock::Stock(const string & company = "ERROR" , long shares = 0, double shares_val = 0.0);
//使用上述任何一種方式建立了預設建構函式,就可以宣告物件變數,但是不進行顯式初始化了
Stock first;
Stock first = Stock();
Stock *pt = new Stock;
7.類的解構函式
- 解構函式的作用
用建構函式建立物件後,程式負責跟蹤該物件,直到其過期為止。物件過期時,程式將自動呼叫一個特殊的成員函式,也就是解構函式來完成清理工作。解構函式的名稱也和建構函式一樣,非常特殊,解構函式的名稱是~Stock,而且解構函式也可以沒有返回值和宣告型別,與建構函式不同的是,解構函式沒有引數,也就是說解構函式的原型必須如下:
//解構函式原型
~Stock();
//Stock類 可能的解構函式
Stock::~Stock()
{
}
- 什麼時候呼叫解構函式
由編譯器決定什麼時候對解構函式進行呼叫,另外通常不應該在程式碼中顯式地呼叫解構函式。如果建立的是靜態儲存類物件,則其解構函式將在程式結束時被自動呼叫。如果建立的是自動儲存類物件,則其解構函式將會在程式執行完程式碼塊時自動被呼叫。如果物件是通過new建立的,則它將駐留在佔記憶體或自由儲存區中,當delete來釋放記憶體時,其解構函式將自動被呼叫。 - 預設解構函式
如果類中沒有定義解構函式,則C++會隱式的宣告一個預設解構函式,並在發現導致物件被刪除的程式碼後,提供預設解構函式的定義。
8.一個完整的多檔案Stock類的實現與使用
//標頭檔案 stock01.h
#ifndef STOCK01_H_
#define STOCK01_H_
#include<string>
class Stock
{
private: //可以省略
std::string company;
long shares;
double share_val;
double total_val;
void set_tot() { total_val = shares * share_val; }
public:
Stock(); //過載的無參建構函式
Stcok(const std::string & co, long n = 0, double pr = 0.0); //有引數的建構函式
~Stock(); //解構函式
void buy(long num, double price);
void sell(long num, double price);
void update(double price);
void show();
};
#endif
//實現檔案 stock01.cpp
#include<iostream>
#include"stock01.h" //""預設現在檔案目錄下進行查詢
Stock::Stock()
{
std::cout << "Default constructor called\n";
company = "no name";
shares = 0;
share_val = 0.0;
total_val = 0.0;
}
Stock::Stock(const std::string & co, long n, double pr)
{
company = co;
//為了簡便就不進行錯誤勘察了
shares = n;
share_val = pr;
set_tot();
}
Stock::~Stock()
{
std::cout << "Bye\n" ;
}
void Stock::buy(long num,double price)
{
if (num<0)
{
std::cout << "ERROR!" << std:endl;
}
else
{
shares += num;
share_val = price;
set_tot();
}
}
void Stock::sell(long num,double price)
{
if (num<0)
{
std::cout << "ERROR!" << std:endl;
}
else if(shares < num)
{
std::cout << "NOT ENOUGH!\n";
}
else
{
shares -= num;
share_val = price;
set_tot();
}
}
void Stock::update(double price)
{
share_val = price;
set_tot();
}
void Stock::show()
{
using std::cout;
using std::ios_base;
using std::endl;
ios_base::fmtflags orig = cout.setf(ios_base::fixed,ios_base::floatfield);
std::streamsize prec = cout.precision(3);
cout << "company: " << company << endl;
cout << "shares: " << shares << endl;
cout << "share_val: "<< share_val << endl;
cout.precision(2);
cout << "total_val: "<< total_cal << endl;
cout.setf(orig,ios_base::floatfield);
cout.precision(prec);
}
//使用檔案 usestock01.cpp
#include<iostream>
#include"stock01.h"
int main()
{
{
using std::cout;
cout << "Using constructors to create new objects\n";
Stock stock1("SHIYUQI",12,100.0);
stock1.show();
Stock stock2 = Stock ("SHISHI",25,10.0);
stock2.show();
//類的物件可以進行賦值
stock1 = stock2;
stock1.show();
stock2.show();
//建構函式不只可以初始化,也可以進行賦值
stcok1 = Stock("QIQI",100,10.0);
}
return 0;
}
注意:之所以在使用檔案的大括號前加一個程式碼塊,是為了能讓程式顯示出來解構函式的輸出值,如果沒有這個程式碼塊,main()函式執行完畢之後,這些變數才會被銷燬,那樣呼叫視窗可能在解構函式被呼叫之前就關閉,我們就可能看不到解構函式顯示的語句。
注意:類的物件允許進行賦值運算,被賦值的物件會被完全覆蓋掉。
注意:建構函式不止可以用於初始化的物件,還可以用於存在的物件。
9.列表初始化用於類
Stock jock = {"SHIYUQI",100,285.0};
Stock jack{"SHIYUQI",100,285.0};
Stock juck{};
10.const成員函式
const Stock land = Stock("SHIYUQI");
land.show();
//這個show()函式不被允許,因為C++不能保證show()被呼叫的時候物件不被修改
//那麼我們是否可以像以前一樣通過將引數宣告為const引用或者const型別指標來解決這個問題呢
//對於show()函式好像不行
//因為show()函式沒有引數,他所石油的物件是由方法呼叫隱式提供的
//那麼我們就需要一種新語法
//宣告稱const成員引數
//從現在起我們遵循這種規則,如果成員函式不對呼叫物件進行修改
//就將其宣告為const型別
void Stock::show() const;
注意:如果建構函式中使用了new,則必須在解構函式必須使用delete。
11.this指標
每個類成員函式涉及到的物件數並不一定是一個,可能會有方法涉及兩個物件,這種時候就需要C++的this指標。比如我們希望有一個方法:比較兩隻股票的價格,並且返回股價較高的那一支的引用。
//首先如何將兩個要比較的物件提供給成員函式呢?
//例如命名這個方法為topval()
//那麼函式呼叫stock1.topval()會訪問stock1的物件資料
//stock2.topval()會訪問stock2的物件資料
//如果希望該方法將兩個物件進行比較,則必須有一個物件作為引數傳入
//為了節約空間,可以使用引用來傳遞物件
//所以這種方法的原型應該為如下:
const Stock & topval(const Stock & s) const;
//括號中的const表示,傳入物件的顯式物件是const型別,不應該被修改
//最右側的const表示,隱式使用的物件也應該是const型別,不應該被修改
//因為返回的物件是兩個const物件之一,所以返回值型別也是const
//下面寫一下函式體
//會發現函式體會有一些小問題
const Stock & topval(const Stock & s) const
{
if(s.total_val > total_val)
return s;
else
return ????;
}
//我們發現 如果隱式宣告的物件比較大的時候
//我們並不知道應該如何表達這個隱式物件
//this指標就是用來這樣使用的,使用被稱為this的特殊指標
//this指標指向用來呼叫成員函式的物件
//這樣stock1.topval(stock2)中this指標指向的就是stock1物件
//所以上述的函式體可以寫作:
const Stock & topval(const Stock & s) const
{
if(s.total_val > total_val)
return s;
else
return *this;
}
//注意:返回的並不是this,而是*this,也就是物件本身
12.物件陣列
使用者通常要建立一個類的多個物件,可以建立獨立物件變數,就想本章前面示例所做的那樣,也可以使用物件陣列,物件陣列聽起來很殘暴,但是他的建立方式居然和普通陣列沒有啥區別。
//建立物件陣列
Stock myStuff[4];
//初始化陣列元素
Stock myStuff[4] =
{
Stock("NANA",15,10.0),
Stock("BOBO",16,10.5),
Stock("GOGO",17,10.8),
Stock("TOTO",18,10.9)
};
//初始化陣列可以使用不同建構函式
//餘下的沒有進行顯式初始化的元素會使用預設建構函式隱式構造
Stock myStuff[4] =
{
Stock("NANA",15,10.0),
Stock()
};
13.類作用域
類中定義的名稱(類資料成員名和類成員函式名)作用域為整個類,作用域為整個類的名稱只在該類中是已知的,在類外是不可知的。因此不同類使用相同的類成員名不會造成衝突。
注意:類作用域也意味著不能從外部直接訪問類的成員,公有函式也是如此,要呼叫公有函式,需要通過物件,定義成員函式的時候也需要使用作用域解析運算子。
class Ik
{
private:
int fuss;
pu
}