1. 程式人生 > >【C++探索之旅】第二部分第一課:面向對象初探,string的驚天內幕

【C++探索之旅】第二部分第一課:面向對象初探,string的驚天內幕

信息技術 false cli 方法 復雜 weixin include 命令 就是

技術分享圖片


內容簡單介紹

1、第二部分課:面向對象初探。string的驚天內幕

2、第二部分第二課預告:掀起了"類"的蓋頭來(一)



面向對象初探,string的驚天內幕


上一課《【C++探索之旅】第一部分第十二課:指針一出。誰與爭鋒》中。大家辛苦了。


誠然,指針是不easy啃的硬骨頭。只是,假以時日,小火慢燉,能夠成為一碗上好的骨頭湯,對你的C++水平那可是大補。


好了,口水擦一擦,我們正式進入C++探索之旅的第二部分啦,激動不?剛擦完的哈喇子可不要繼續流啊。


這一部分的課程稱為:C++之面向對象


由於我們要探索"面向對象編程"(OOP: Object-Oriented Programming) 。

這是一種不同於以往的編程模式。


這樣的方法不會馬上使你的程序發生革命性的改變。並且剛開始接觸的時候你甚至會認為這樣的模式沒什麽用。


眼下為止我們的編程模式一直是:面向過程。也就是:我想要實現什麽功能,就寫什麽函數。聲明相應變量。等等。比方我要顯示一段信息到屏幕上。我就創建一個函數:


void showMessage(string message);


可是,相信我:慢慢地。你會發現面向對象的編程模式比面向過程更符合人類的思維。你將能更好地組織你的代碼。


註意:這一課接下來提到的"對象"。事實上有時是指對象。有時是指類。由於要到下一課我們才學習"類"的概念。臨時先不細分。希望通過這一課讓大家對面向對象有大致了解。




對象...有啥用啊?


聽到面向對象編程,你最好奇的詞應該是"對象"(英語是object)吧。


你會想:這不會是又一種神奇的設計吧?難道是一個宿醉後的瘋狂程序猿想出來的東西?


事實上不然。

我們身邊充滿著對象:你的車是一個對象,你的電腦是一個對象。你的手機是一個對象,等等。


事實上,我們所知道的全部東西都能夠被看作對象。面向對象編程。就是在代碼中操作這些被稱為"對象"的元素。


以下列舉幾種平時我們在編程中常見的對象:


  • 一個窗口:比如QQ的一個聊天對話框。繪圖軟件的主界面。

  • 一個button:比方安裝軟件時那些"下一步"的button。

  • 遊戲裏的一個人物。

  • 一首歌。一個視頻


說了這麽多。對象究竟是啥呀?是一種新的變量嗎,還是一種新的函數類型?


不不不,都不是。

對象是一種新的編程元素!


或者說得更詳細一些,對象裏面混合了變量和函數。


可不要被這句話嚇跑了。我們一點點來探索。



想象一下... 一個對象。

少年,不是讓你想象結婚對象好嘛~


還是來點實際的圖片好了,能幫助我們理解。



設想:

有一天,一個程序猿決定要寫一個圖形界面的程序,這個程序能夠在屏幕上顯示窗口,調整窗口大小。移動窗口,刪除窗口。在窗口裏能夠畫各種圖形。等等。


要實現這些功能,代碼是比較復雜的:須要不少函數。這些函數之間互相調用。並且還須要非常多變量。比如窗口位置(x坐標和y坐標),窗口的寬,高,等等。


這個程序猿花了不少的時間來寫這個程序。

程序有點復雜。可是他完畢了。終於。他實現的代碼包括了非常多的函數和變量。


當我們第一次看到他的程序時,就好像看一個化學家的一個實驗環境一樣,啥也看不懂。

例如以下圖:


技術分享圖片


只是,這個程序猿對自己的程序還是非常愜意的。他甚至要把程序公布到網上,讓大家能夠用他的程序來創建窗口,直接拿來用就能夠了,而不須要從零開始寫代碼。


只是,有一個問題。假設你不是化學專家,你可能得花不少時間搞懂這一堆東西究竟是怎麽運作的:哪一個函數最先被調用呢?為了改變窗口的大小,哪個變量要被傳遞到哪個函數裏呢?總之。我們非常害怕把整個實驗環境給弄炸了。


在接到一些用戶的建議和抱怨後,這個程序猿決定替用戶著想。

他又一次設計了他的代碼。從面向過程的模式改為面向對象的模式。


這就好比他把全部和這個化學實驗有關的東西都放到一個慷慨盒子裏。

這個慷慨盒子就是我們所說的"對象"。例如以下圖:


技術分享圖片


在上圖中,慷慨盒子的一部分被設為透明,是有益的,使你能夠看到裏面的景象。

是的,我們的化學實驗的全部設備都在慷慨盒子裏。可是。實際當中。慷慨盒子是全然不透明的,用戶從外面看不到裏面究竟有什麽。

例如以下圖:


技術分享圖片


這個慷慨盒子裏存放著函數和變量(那些化學儀器,試管,燒杯,等等),但這些元素對於用戶卻是不可見的。



這樣,用戶看到的就不再是成堆的試管,燒杯,等等讓其抓狂的東西了。在慷慨盒子外面,我們呈現給用戶的就僅僅有一些button:一個button用於"打開窗口"。一個button用於"改變窗口大小"。一個button用於"關閉窗口"。等等。用戶全然不須要理解慷慨盒子裏面的運作原理。



因此。以面向對象的模式來編程,就是:

程序猿編寫代碼(可能非常復雜)。然後將這些復雜的代碼都裝到一個慷慨盒子(對象)裏。用戶從外面不能看到裏面的實現細節。

因此,對於使用這個對象的用戶來說,操作起來就easy多了:僅僅需按下相應的button。不須要精通化學也能夠使用整個實驗環境提供的功能了。



當然了。上面的比喻僅僅是一個大致概念。


這一課我們臨時還不學習廣義上怎麽創建對象。我們反其道而行之,先來使用一個對象。事實上這個對象我們之前已經接觸過好多次了,就是string。



好幾課之前。我們已經說過。string這樣的類型和普通的int,bool,float。double等不一樣。後面的這些是C++的基礎數據類型,用於存放簡單的數據。


但string卻不是這樣。事實上它是一個對象。string這個類型背後隱藏了非常多細節。



揭開string背後的神奇內幕


別看string寶寶非常乖的,白天好像非常正經的大公司老板Bruce Wayne(布魯斯·韋恩),事實上他是蝙蝠俠。背後秘密可多了。


多虧了面向對象的編程模式,我們在第一部分課程裏就已經開始使用string了。當時我們還不知道string背後的秘密。以下我們就來揭開string神奇的面紗。看看裏面的機制大概是什麽樣。


對於電腦來說,字符事實上並不存在


為什麽說string內部的機制事實上比較復雜呢?


首先,我們之前把string稱為"字符串",但事實上電腦並不認識string當中的那些字符。


還記得嗎?我們說電腦是一臺僅僅知道計算的大計算器而已(英語computer就是"計算機"的意思),它僅僅認識數字。


可是,假設電腦僅僅知道操作數字,那麽它又是怎樣在屏幕上顯示那麽多字符的呢?比如你如今就用屏幕在看小編寫的文章。


信息技術的先驅們早就想到了好辦法。或許你聽說過ASCII(發音是[aski])表。

ASCII是American Standard Code for Information Interchange的縮寫,表示"美國信息交換標準碼"。



ASCII表就是為數字和字符的相應轉換服務的一個表格。以下給出ASCII表的一部分:


數字

字符

數字

字符

64

@

96

65

A

97

a

66

B

98

b

67

C

99

c

68

D

100

d

69

E

101

e

70

F

102

f

71

G

103

g

72

H

104

h

73

I

105

i

74

J

106

j

75

K

107

k

76

L

108

l

77

M

109

m


如我們所見。大寫字母A相應了65,小寫字母a相應了97。

全部的英文字母都包括在這個表中。



那麽,就是說:每次電腦僅僅要看到數字65。就會把它當作大寫字母A來處理咯?


不是的。僅僅有我們要求電腦翻譯一個數字到字符的時候,它才會照做(果然非常呆萌)。實際當中。電腦是依據我們聲明的變量的類型來決定怎樣"看待"每個存儲在變量中的數字的。大致規則例如以下:


  • 比如,我們用int型的變量來儲存數字65。那麽電腦就把它當成一個數字來看待。

  • 相反地。假如我們用char型的變量來儲存數字65,那麽電腦就把它當成一個字符來看待。電腦會說:嗯。這是大寫字母A。事實上,char是英語character的縮寫,表示"字符"。專門用來儲存字符的。


因此,char類型的變量儲存數字,可是這個數字會被"解釋"成字符。

只是,一個char型變量一次僅僅能儲存一個字符,那麽我們怎麽儲存一個句子呢?我們接著學習。


字符串就相似字符數組


既然char僅僅能儲存一個字符(由於char的大小是一個字節。也就是8個bit位,正好是一個英文字符的大小,中文字符占2個字節),因此。程序猿們就想到了創建char型數組。


我們已經學過數組了,也就是內存中相鄰的相同類型的變量的集合。

因此,用字符數組來存放多個字符的集合(比如:一句話)是非常棒的選擇,我們通常也將字符數組稱為"字符串",由於它是一串字符麽。



因此,我們僅僅要這樣聲明一個char型數組:


char text[100];


text這個char型數組裏能夠儲存100個字符。

只是這個數組是靜態的,大小不可變。

假設你要創建大小能夠改變的"動態數組",就能夠用vector這樣的類型啦。


比如:


vector<char> text;


理論上。我們能夠使用靜態char型數組或動態char型數組來存放一串字符。

只是這樣非常不方便,因此C++的設計者決定將有關字符串的操作都封裝到一個對象裏。那就是string。



創建並使用string對象


經過剛才那一節,我們了解到:操作字符串事實上並不簡單啊。

須要創建一個char型數組,數組中的每個元素是一個字符。並且,我們須要創建足夠大的數組。以便裝下我們要存放的字符串。

總之,有非常多方面要考慮。


這時,面向對象編程就能夠派上用場了。還記得我們上面的圖片中那個慷慨盒子嗎?string就是這樣一個"慷慨盒子"。


創建一個string對象


創建一個對象和我們之前創建一個普通的變量(比方int型變量)是相似的。比如:


#include <iostream>
#include <string> // 必須引入string頭文件,由於string的原型就定義在裏面

using namespace std;

int main()
{??? 
    string mString; //創建一個string類型的對象mString???
    ? 
    return 0;
}


上面的程序不復雜,我們主要關註那句創建string對象的指令:


string mString;


故此。創建對象的方法和創建一個變量是一樣的咯?


並不盡然,創建一個對象有幾種方式。我們方才演示的僅僅是當中最簡單的一種。

只是。這樣的方式確實和創建一個普通的變量沒什麽大差別。


這樣的話,我們怎樣區分對象和普通變量呢?


為了區分變量和對象,能夠從命名上來看,我們有規則:


  • 變量的類型以小寫字母開始。比如普通的變量類型int

  • 對象的類型以大寫字母開始,比如:Car


我知道你肯定會說:你看string不就是以小寫字母開始的嗎?它也是對象的類型啊。


我承認,這是一個例外,上面的規則並非強加給我們的。非常顯然。實現string的程序猿並沒有遵守這個規則。


只是。大部分的情況下,對象的類型是以大寫字母開始的。


當聲明字符串時對其初始化


為了在創建對象時就對其初始化,有多種方式,我們來看看最簡單的一種:


int main()
{??? 
    string mString("Hello !");??? 
    //創建一個string類型的對象mString,並初始化它的值 
??? 
??? return 0;
}


這樣的方式的初始化,和C++的基礎變量類型,比如int,double等,是一樣的。


除了上面的這樣的方式,我們也能夠用以下方式來初始化我們的string對象:


string mString = "Hello !";


我們既然創建了一個string對象。並為其賦值"Hello !",我們能夠來打印這個對象的內容。


int main()
{??? 
    string mString("Hello !");
    
??? cout << mString << endl;??? 
??? //顯示string的內容,就好像它是一個字符串???? 
??? 
??? return 0;
}


執行。顯示:


Hello !


在字符串初始化後再改變其值


如今我們已經創建了我們的字符串。並且對其賦了初值。我們還能夠再改變其值。


int main()
{
??? string mString("Hello !");
??? cout << mString << endl;
?? ?
??? mString = "How are you ?

"; ??? cout << mString << endl; ? ??? return 0; }


為了改變string變量的值。我們須要用=號(賦值符號)。


上面演示的方法,事實上和之前我們使用普通變量類型時並沒太大差別。

我在這裏僅僅是要向你展示面向對象的奇妙之處。


你,也就是用戶,剛才就好像按下了一個button,這個button的作用是發出一個命令:我要將字符串的內容從"Hello !"改寫為"How are you ?"。


盡管你的指令非常簡短,但string對象內部的那些函數接到指令後卻開始忙乎起來了(讓我想到了小黃人),它們依次做了以下幾件事:


  1. 首先檢測當前存放"Hello !"的char型數組是否足夠容納"How are you ?

    "這個字符串。

  2. 答案是否定的。

    因此它們創建一個新的char型數組。大小足以容下"How are you ?"這個新的字符串。

  3. 一旦新的char型數組創建完畢。就把舊的數組銷毀。畢竟沒用了。

  4. 然後把"How are you ?"這個字符串的內容復制到新的char型數組裏。


看到吧。這就是面向對象編程的一個強大之處:我們全然不知道string對象裏面原來發生了這麽多事。


字符串的串聯


假如我們想要將兩個字符串的內容前後相接。合並為一個字符串。理論上說來,這是不easy實現的。可是string對象中早就設計好這樣的機制啦:


int main()
{
??? string mString1("Hello !");
??? string mString2("How are you ?");
??? string mString3;
?
??? mString3 = mString1 + " " + mString2;
??? cout << mString3 << endl;
?
??? return 0;
}


非常easy不是嗎?僅僅要用加號將兩個字符串連起來就好了,至於string對象內部做了多少復雜的操作,我們"VIP用戶"根本不在乎,有權就是這麽"任性"~


字符串的比較


還要繼續學嗎?非常不錯。就要這樣的精神。


我們能夠用==和!=符號來比較字符串,分別表示相等和不相等。


int main()
{
??? string mString1("Hello !");
??? string mString2("How are you ?

"); ? ??? if (mString1 == mString2) // 顯然兩個字符串不相等。括號裏條件語句為假 ??? { ??????? cout << "兩個字符串相等." << endl; ??? } ??? else ??? { ??????? cout << "兩個字符串不相等." << endl; ??? } ? ??? return 0; }


事實上。在string對象內部,這個比較的過程是兩個字符串的一個字符接一個字符來比較的(借助一個循環)。

可是我們不須要在意這些細節,就是這麽瀟灑。




string的一些方法


string對象的功能可不至於此。當中還有非常多實用的方法,我們能夠來見識幾個經常使用的。



屬性和方法


我們之前說過。一個對象中包括了變量和函數。

只是,我們既然要學習面向對象編程了,就要用更專業的術語。


在面向對象的領域,對象中的變量被稱為"屬性",而當中的函數被稱為"方法"。僅僅是稱呼不同而已。


你能夠把對象提供的每個方法想象成我們之前那個慷慨盒子外面對用戶可見的那些button。


屬性和方法又被稱為"成員變量"和"成員函數"。


為了調用一個對象中的方法。我們用一種你已經見識過的方式:


object.method()


我們在對象和其成員函數之間用一個點來連接。

這意味著:對於此對象,我調用它的這種方法。


理論上,我們也能夠用相同的方式來訪問成員變量(屬性)。然而,在面向對象編程中,我們盡量避免用戶直接訪問我們的私人成員變量。

關於這個,有非常多知識點,我們以後的課程會講到。


調用成員函數和成員變量的方式也是對象獨有的,普通的變量類型並不能這樣做。

這也是除了名字之外差別對象和變量的好方法。


我們來看幾個string提供的成員函數吧。


size方法


size在英語中是"大小。尺寸"的意思。

因此。size方法用於獲取string對象的大小,也就是裏面包括的字符數目。


size方法的使用方法非常easy。沒有參數:


int main()
{
??? string mString("Hello !");
??? cout << "字符串的長度是 " << mString.size();
?
??? return 0;
}


執行,顯示:


字符串的長度是 7


erase方法


erase方法用於刪去字符串的內容,由於erase在英語中是"刪除。清除"的意思。


int main()
{??? 
    string mString("Hello !");
    
????cout << "字符串的內容是 : " << mString << endl;
????
??? mString.erase();
??? 
??? cout << "調用erase方法後, 字符串的內容是 : " << mString << endl;???? 
??? return 0;
}


執行,顯示:


字符串的內容是 : Hello !

調用erase方法後, 字符串的內容是 :


正如我們預期的,調用erase方法後。字符串的內容被清空了。



事實上。erase方法的效果和

?

mString = "";


是一樣的。


substr方法


substr是sub和string的縮合,sub表示"子的。副的"。string就是"字符串"啦,因此,顧名思義,substr就是取得一個字符串的一部分。



substr方法的原型例如以下:


string substr( size_type index, size_type num = npos );


能夠看到。substr的返回值也是string類型。它接收兩個參數,一個是index。表示從字符串的第幾個字符開始截取。第二個參數是num。表示截取多少個字符。


事實上,更確切地說,substr方法接收兩個參數,第一個參數是必須的。第二個參數是非必須的。就是說。第一個參數必須要提供。第二個參數假如沒有提供,那麽num就會取默認值。默認值是npos。


看到上面的原型中有 num = npos了嗎?臨時還不須要深究,這個事實上是C++的一種特性,術語稱為"默認參數"。就是說,假如函數被調用時。這個參數沒有被賦予值。那麽這個參數會取等號後面的默認值(相似"備胎"的概念)。


npos這個值表示:取余下的全部字符,直到最後一個。


舉個樣例吧:


int main()
{
??? string mString("Hello !");
??? cout << mString.substr(3) << endl;
?
??? return 0;
}


執行,顯示:


lo !


由於我們僅僅給了substr一個參數,就是3,所以substr的第一個參數index就被賦值為3。而第二個參數num沒有被賦值。就會取默認值。



因此。表示從mString的第四個字符開始截取,一直截取到最後。



再來試試給第二個參數num賦值的情況。


int main()
{??? 
    string mString("Surprise !");??? 
    cout << mString.substr(2, 4) << endl;???? 
    
    return 0;
}

執行,顯示:


rpri


也非常好理解。我們給index和num分別賦值2和4。那麽就是從mString的第3個字符開始截取,截取4個字符。


我們之前在數組的那一課已經學習過了,我們能夠對string類型的對象用相似數組的方式來訪問當中某一個字符,使用中括號[]。比如:


string mString("Surprise!");
cout << mString[3] << endl;? //顯示第4個字符,就是 ‘p‘


好了,這一課就結束了。


這一課的主要目的是不想讓你對面向對象編程感到恐懼。經過此課的學習,你是否已經對面向對象有點概念了呢?事實上,在這之前。你就已經用過對象了,string和vector事實上都是。


我希望你已經做好準備來創建屬於你自己的對象了。都說程序猿沒有對象。那麽我們自己new一個唄~


這是下一課開始的目標。




總結


  1. 面向對象編程是設計代碼的一種方法。操作的是被稱為"對象"的元素。

  2. 對象裏面的實現細節能夠非常復雜。可是使用對象的人卻並不須要關心這些細節,僅僅須要知道怎樣使用就能夠了。這是面向對象編程的一大優勢。

  3. 一個對象是由一些屬性和方法組成的,也就是變量和函數。

  4. 我們能夠調用對象的方法來實現各種需求。

  5. 在內存中,字符串的操作事實上是非常復雜的。

    為了替我們這些C++的使用者著想,C++的設計者為我們設計了靜止的對象:string。

    我們僅僅須要用string來創建字符串實例,操作這些字符串,而並不須要關心內存中究竟發生了什麽。



第二部分第二課預告


今天的課就到這裏。一起加油吧!

下一課我們學習:掀起了"類"的蓋頭來(一)

【C++探索之旅】第二部分第一課:面向對象初探,string的驚天內幕