一文讓你學完C++,乾貨收藏!!!
- 2020 年 4 月 3 日
- 筆記
本文來源於網路,整理自公眾號C語言編程
大家好,應粉絲要求,我們今天帶來了C++的基礎知識大匯總,當然這是精簡版的,但是篇幅也不少,應該說該有的也都有了,建議大家收藏慢慢學習,同時希望對大家的C++學習有所幫助。
一 、從」hello world「 入門C++!
C++總覽簡介
C++ 是一種靜態類型的、編譯式的、通用的、大小寫敏感的、不規則的程式語言,支援過程化編程、面向對象編程和泛型編程。
C++ 被認為是一種中級語言,它綜合了高級語言和低級語言的特點。
C++ 是由 Bjarne Stroustrup 於 1979 年在新澤西州美利山貝爾實驗室開始設計開發的。C++ 進一步擴充和完善了 C 語言,最初命名為帶類的C,後來在 1983 年更名為 C++。
C++ 是 C 的一個超集,事實上,任何合法的 C 程式都是合法的 C++ 程式。
注意:使用靜態類型的程式語言是在編譯時執行類型檢查,而不是在運行時執行類型檢查。
面向對象程式設計
c++最大的亮點就是面向對象程式設計理念的運用。包括面向對象開發的四大特性:
- 封裝
- 抽象
- 繼承
- 多態
C++的組成部分
標準的 C++ 由三個重要部分組成:
- 核心語言,提供了所有構件塊,包括變數、數據類型和常量,等等。
- C++ 標準庫,提供了大量的函數,用於操作文件、字元串等。
- 標準模板庫(STL),提供了大量的方法,用於操作數據結構等。
C++的標準
對一門程式語言來說,遵循統一的標準是必需的。下面的表格列出了c++標準的發展歷史。
發布時間 | 文檔 | 通稱 | 備註 |
|---|---|---|---|
2015 | ISO/IEC TS 19570:2015 | - | 用於並行計算的擴展 |
2015 | ISO/IEC TS 18822:2015 | - | 文件系統 |
2014 | ISO/IEC 14882:2014 | C++14 | 第四個C++標準 |
2011 | ISO/IEC TR 24733:2011 | - | 十進位浮點數擴展 |
2011 | ISO/IEC 14882:2011 | C++11 | 第三個C++標準 |
2010 | ISO/IEC TR 29124:2010 | - | 數學函數擴展 |
2007 | ISO/IEC TR 19768:2007 | C++TR1 | C++技術報告:庫擴展 |
2006 | ISO/IEC TR 18015:2006 | - | C++性能技術報告 |
2003 | ISO/IEC 14882:2003 | C++03 | 第二個C++標準 |
C++ 程式結構
讓我們看一段簡單的程式碼,可以輸出單詞 Hello World。
#include <iostream> using namespace std; // main() 是程式開始執行的地方 int main() { cout << "Hello World"; // 輸出 Hello World return 0; }讓我們一起來研究一下上面的程式碼結構:
C++ 語言定義了一些頭文件,這些頭文件包含了程式中必需的或有用的資訊。上面這段程式中,包含了頭文件 <iostream>。
下一行 using namespace std; 告訴編譯器使用 std 命名空間。命名空間是 C++ 中一個相對新的概念。
下一行 // main() 是程式開始執行的地方 是一個單行注釋。單行注釋以 // 開頭,在行末結束。
下一行 int main() 是主函數,程式從這裡開始執行。
下一行 cout << "Hello World"; 會在螢幕上顯示消息 "Hello World"。
下一行 return 0; 終止 main( )函數,並向調用進程返回值 0。
C++ 中的分號 & 語句塊
在 C++ 中,分號是語句結束符。也就是說,每個語句必須以分號結束。它表明一個邏輯實體的結束。
例如:下面就一共有兩條語句。
x = y; y = x+1;
語句塊是一組使用大括弧括起來的按邏輯連接的語句。例如:
{ cout << "Hello World"; // 輸出 Hello World return 0; }C++ 關鍵字
下表列出了 C++ 中的保留字。這些保留字不能作為常量名、變數名或其他標識符名稱。
asm | else | new | this |
|---|---|---|---|
auto | enum | operator | throw |
bool | explicit | private | true |
break | export | protected | try |
case | extern | public | typedef |
catch | false | register | typeid |
char | float | reinterpret_cast | typename |
class | for | return | union |
const | friend | short | unsigned |
const_cast | goto | signed | using |
continue | if | sizeof | virtual |
default | inline | static | void |
delete | int | static_cast | volatile |
do | long | struct | wchar_t |
double | mutable | switch | while |
dynamic_cast | namespace | template |
C++ 標識符
C++ 標識符是用來標識變數、函數、類、模組,或任何其他用戶自定義項目的名稱。一個標識符以字母 A-Z 或 a-z 或下劃線 _ 開始,後跟零個或多個字母、下劃線和數字(0-9),不允許改變標識符組成規則。
下面列出幾個有效的標識符:
test _temp
二 、 c++基本數據類型及流控制語句詳解
C++ 數組
C++ 支援數組數據結構,它可以存儲一個固定大小的相同類型元素的順序集合。數組是用來存儲一系列數據,但它往往被認為是一系列相同類型的變數。
數組的聲明並不是聲明一個個單獨的變數,比如 number0、number1、...、number99,而是聲明一個數組變數,比如 numbers,然後使用 numbers[0]、numbers[1]、...、numbers[99] 來代表一個個單獨的變數。數組中的特定元素可以通過索引訪問。
所有的數組都是由連續的記憶體位置組成。最低的地址對應第一個元素,最高的地址對應最後一個元素。
聲明數組
在 C++ 中要聲明一個數組,需要指定元素的類型和元素的數量,如下所示:
type arrayName [ arraySize ];
這叫做一維數組。arraySize 必須是一個大於零的整數常量,type 可以是任意有效的 C++ 數據類型。例如,要聲明一個類型為 double 的包含 10 個元素的數組 balance,聲明語句如下:
double balance[10];
現在 balance 是一個可用的數組,可以容納 10 個類型為 double 的數字。
初始化數組
在 C++ 中,可以逐個初始化數組,也可以使用一個初始化語句,如下所示:
double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};大括弧 { } 之間的值的數目不能大於我們在數組聲明時在方括弧 [ ] 中指定的元素數目。
如果省略掉了數組的大小,數組的大小則為初始化時元素的個數。因此,如果:
double balance[] = {1000.0, 2.0, 3.4, 7.0, 50.0};將創建一個數組,它與前一個實例中所創建的數組是完全相同的。
下面是一個為數組中某個元素賦值的實例:
balance[4] = 50.0;
上述的語句把數組中第五個元素的值賦為 50.0。所有的數組都是以 0 作為它們第一個元素的索引,也被稱為基索引,數組的最後一個索引是數組的總大小減去 1。以下是上面所討論的數組的的圖形表示:

訪問數組元素
數組元素可以通過數組名稱加索引進行訪問。元素的索引是放在方括弧內,跟在數組名稱的後邊。例如:
double salary = balance[9];
上面的語句將把數組中第 10 個元素的值賦給 salary 變數。下面的實例使用了上述的三個概念,即,聲明數組、數組賦值、訪問數組:
#include <iostream> using namespace std; #include <iomanip> using std::setw; int main () { int n[ 10 ]; // n 是一個包含 10 個整數的數組 // 初始化數組元素 for ( int i = 0; i < 10; i++ ) { n[ i ] = i + 100; // 設置元素 i 為 i + 100 } cout << "Element" << setw( 13 ) << "Value" << endl; // 輸出數組中每個元素的值 for ( int j = 0; j < 10; j++ ) { cout << setw( 7 )<< j << setw( 13 ) << n[ j ] << endl; } return 0; }C++ 字元串
C++ 提供了以下兩種類型的字元串表示形式:
- C 風格字元串
- C++ 引入的 string 類類型
C 風格字元串
C 風格的字元串起源於 C 語言,並在 C++ 中繼續得到支援。字元串實際上是使用 null 字元 '' 終止的一維字元數組。因此,一個以 null 結尾的字元串,包含了組成字元串的字元。
下面的聲明和初始化創建了一個 "Hello" 字元串。由於在數組的末尾存儲了空字元,所以字元數組的大小比單詞 "Hello" 的字元數多一個。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', ''};依據數組初始化規則,可以把上面的語句寫成以下語句:
char greeting[] = "Hello";
以下是 C/C++ 中定義的字元串的記憶體表示:

其實,不需要把 null 字元放在字元串常量的末尾。C++ 編譯器會在初始化數組時,自動把 '' 放在字元串的末尾。讓我們嘗試輸出上面的字元串:
實例
#include <iostream> using namespace std; int main (){ char greeting[6] = {'H', 'e', 'l', 'l', 'o', ''}; cout << "Greeting message: "; cout << greeting << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Greeting message: Hello
C++ 中有大量的函數用來操作以 null 結尾的字元串:supports a wide range of functions that manipulate null-terminated strings:
序號 | 函數 & 目的 |
|---|---|
1 | strcpy(s1, s2); 複製字元串 s2 到字元串 s1。 |
2 | strcat(s1, s2); 連接字元串 s2 到字元串 s1 的末尾。 |
3 | strlen(s1); 返回字元串 s1 的長度。 |
4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,則返回 0;如果 s1<s2 則返回值小於 0;如果 s1>s2 則返回值大於 0。 |
5 | strchr(s1, ch); 返回一個指針,指向字元串 s1 中字元 ch 的第一次出現的位置。 |
6 | strstr(s1, s2); 返回一個指針,指向字元串 s1 中字元串 s2 的第一次出現的位置。 |
下面的實例使用了上述的一些函數:
實例
#include <iostream> #include <cstring> using namespace std; int main () { char str1[11] = "Hello"; char str2[11] = "World"; char str3[11]; int len ; // 複製 str1 到 str3 strcpy( str3, str1); cout << "strcpy( str3, str1) : " << str3 << endl; // 連接 str1 和 str2 strcat( str1, str2); cout << "strcat( str1, str2): " << str1 << endl; // 連接後,str1 的總長度 len = strlen(str1); cout << "strlen(str1) : " << len << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
strcpy( str3, str1) : Hellostrcat( str1, str2): HelloWorldstrlen(str1) : 10C++ 中的 String 類
C++ 中的 string 類
C++ 標準庫提供了 string 類類型,支援上述所有的操作,另外還增加了其他更多的功能。現在讓我們先來看看下面這個實例:
實例
#include <iostream> #include <string> using namespace std; int main () { string str1 = "Hello"; string str2 = "World"; string str3; int len ; // 複製 str1 到 str3 str3 = str1; cout << "str3 : " << str3 << endl; // 連接 str1 和 str2 str3 = str1 + str2; cout << "str1 + str2 : " << str3 << endl; // 連接後,str3 的總長度 len = str3.size(); cout << "str3.size() : " << len << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
str3 : Hello str1 + str2 : HelloWorld str3.size() : 10
C++ 循環
有的時候,可能需要多次執行同一塊程式碼。一般情況下,語句是順序執行的:函數中的第一個語句先執行,接著是第二個語句,依此類推。
程式語言提供了允許更為複雜的執行路徑的多種控制結構。
循環語句允許我們多次執行一個語句或語句組,下面是大多數程式語言中循環語句的一般形式:

循環類型
C++ 程式語言提供了以下幾種循環類型。
循環類型 | 描述 |
|---|---|
while 循環 | 當給定條件為真時,重複語句或語句組。它會在執行循環主體之前測試條件。 |
for 循環 | 多次執行一個語句序列,簡化管理循環變數的程式碼。 |
do...while 循環 | 除了它是在循環主體結尾測試條件外,其他與 while 語句類似。 |
嵌套循環 | 可以在 while、for 或 do..while 循環內使用一個或多個循環。 |
循環控制語句
循環控制語句更改執行的正常序列。當執行離開一個範圍時,所有在該範圍中創建的自動對象都會被銷毀。
C++ 提供了下列的控制語句。
控制語句 | 描述 |
|---|---|
break 語句 | 終止 loop 或 switch 語句,程式流將繼續執行緊接著 loop 或 switch 的下一條語句。 |
continue 語句 | 引起循環跳過主體的剩餘部分,立即重新開始測試條件。 |
goto 語句 | 將控制轉移到被標記的語句。但是不建議在程式中使用 goto 語句。 |
無限循環
如果條件永遠不為假,則循環將變成無限循環。for 循環在傳統意義上可用於實現無限循環。由於構成循環的三個表達式中任何一個都不是必需的,可以將某些條件表達式留空來構成一個無限循環。
實例
#include <iostream> using namespace std; int main () { for( ; ; ) { printf("This loop will run forever.n"); } return 0; }當條件表達式不存在時,它被假設為真。也可以設置一個初始值和增量表達式,但是一般情況下,C++ 程式設計師偏向於使用 for(;;) 結構來表示一個無限循環。
注意:可以按 Ctrl + C 鍵終止一個無限循環。
C++ 判斷
判斷結構要求程式設計師指定一個或多個要評估或測試的條件,以及條件為真時要執行的語句(必需的)和條件為假時要執行的語句(可選的)。
下面是大多數程式語言中典型的判斷結構的一般形式:

判斷語句
C++ 程式語言提供了以下類型的判斷語句。
語句 | 描述 |
|---|---|
if 語句 | 一個 if 語句 由一個布爾表達式後跟一個或多個語句組成。 |
if...else 語句 | 一個 if 語句 後可跟一個可選的 else 語句,else 語句在布爾表達式為假時執行。 |
嵌套 if 語句 | 可以在一個 if 或 else if 語句內使用另一個 if 或 else if 語句。 |
switch 語句 | 一個 switch 語句允許測試一個變數等於多個值時的情況。 |
嵌套 switch 語句 | 可以在一個 switch 語句內使用另一個 switch 語句。 |
三 、 c++進階 基本輸入輸出、指針、引用
C++ 基本的輸入輸出
今天我們來熟悉一下C++ 編程中最基本和最常見的 I/O 操作。
C++ 的 I/O 發生在流中,流是位元組序列。如果位元組流是從設備(如鍵盤、磁碟驅動器、網路連接等)流向記憶體,這叫做輸入操作。如果位元組流是從記憶體流向設備(如顯示器、印表機、磁碟驅動器、網路連接等),這叫做輸出操作。
I/O 庫頭文件
下列的頭文件在 C++ 編程中很重要。
頭文件 | 函數和描述 |
|---|---|
<iostream> | 該文件定義了 cin、cout、cerr 和 clog 對象,分別對應於標準輸入流、標準輸出流、非緩衝標準錯誤流和緩衝標準錯誤流。 |
<iomanip> | 該文件通過所謂的參數化的流操縱器(比如 setw 和 setprecision),來聲明對執行標準化 I/O 有用的服務。 |
<fstream> | 該文件為用戶控制的文件處理聲明服務。我們將在文件和流的相關章節討論它的細節。 |
標準輸出流(cout)
預定義的對象 cout 是 iostream 類的一個實例。cout 對象"連接"到標準輸出設備,通常是顯示器。cout 是與流插入運算符 << 結合使用的,如下所示:
實例
#include <iostream> using namespace std; int main( ) { char str[] = "Hello C++"; cout << "Value of str is : " << str << endl; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Value of str is : Hello C++
C++ 編譯器根據要輸出變數的數據類型,選擇合適的流插入運算符來顯示值。<< 運算符被重載來輸出內置類型(整型、浮點型、double 型、字元串和指針)的數據項。
流插入運算符 << 在一個語句中可以多次使用,如上面實例中所示,endl 用於在行末添加一個換行符。
標準輸入流(cin)
預定義的對象 cin 是 iostream 類的一個實例。cin 對象附屬到標準輸入設備,通常是鍵盤。cin 是與流提取運算符 >> 結合使用的,如下所示:
實例
#include <iostream> using namespace std; int main( ) { char name[50]; cout << "請輸入您的名稱:"; cin >> name; cout << "您的名稱是:" << name << endl; }當上面的程式碼被編譯和執行時,它會提示用戶輸入名稱。當用戶輸入一個值,並按回車鍵,就會看到下列結果:
請輸入您的名稱:cplusplus 您的名稱是:cplusplus
C++ 編譯器根據要輸入值的數據類型,選擇合適的流提取運算符來提取值,並把它存儲在給定的變數中。
流提取運算符 >> 在一個語句中可以多次使用,如果要求輸入多個數據,可以使用如下語句:
cin >> name >> age;
這相當於下面兩個語句:
cin >> name; cin >> age;
標準錯誤流(cerr)
預定義的對象 cerr 是 iostream 類的一個實例。cerr 對象附屬到標準錯誤設備,通常也是顯示器,但是 cerr 對象是非緩衝的,且每個流插入到 cerr 都會立即輸出。
cerr 也是與流插入運算符 << 結合使用的,如下所示:
實例
#include <iostream> using namespace std; int main( ) { char str[] = "Unable to read...."; cerr << "Error message : " << str << endl; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Error message : Unable to read....
標準日誌流(clog)
預定義的對象 clog 是 iostream 類的一個實例。clog 對象附屬到標準錯誤設備,通常也是顯示器,但是 clog 對象是緩衝的。這意味著每個流插入到 clog 都會先存儲在緩衝在,直到緩衝填滿或者緩衝區刷新時才會輸出。
clog 也是與流插入運算符 << 結合使用的,如下所示:
實例
#include <iostream> using namespace std; int main( ) { char str[] = "Unable to read...."; clog << "Error message : " << str << endl; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Error message : Unable to read....
通過這些小實例,我們無法區分 cout、cerr 和 clog 的差異,但在編寫和執行大型程式時,它們之間的差異就變得非常明顯。所以良好的編程實踐告訴我們,使用 cerr 流來顯示錯誤消息,而其他的日誌消息則使用 clog 流來輸出。
C++ 指針
學習 C++ 的指針既簡單又有趣。通過指針,可以簡化一些 C++ 編程任務的執行,還有一些任務,如動態記憶體分配,沒有指針是無法執行的。所以,想要成為一名優秀的 C++ 程式設計師,學習指針是很有必要的。
每一個變數都有一個記憶體位置,每一個記憶體位置都定義了可使用連字號(&)運算符訪問的地址,它表示了在記憶體中的一個地址。請看下面的實例,它將輸出定義的變數地址:
實例
#include <iostream> using namespace std; int main () { int var1; char var2[10]; cout << "var1 變數的地址:"; cout << &var1 << endl; cout << "var2 變數的地址:"; cout << &var2 << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
var1 變數的地址:0xbfebd5c0 var2 變數的地址:0xbfebd5b6
通過上面的實例,我們了解了什麼是記憶體地址以及如何訪問它。接下來讓我們看看什麼是指針。
什麼是指針?
指針是一個變數,其值為另一個變數的地址,即,記憶體位置的直接地址。就像其他變數或常量一樣,必須在使用指針存儲其他變數地址之前,對其進行聲明。指針變數聲明的一般形式為:
type *var-name;
在這裡,type 是指針的基類型,它必須是一個有效的 C++ 數據類型,var-name 是指針變數的名稱。用來聲明指針的星號 * 與乘法中使用的星號是相同的。但是,在這個語句中,星號是用來指定一個變數是指針。以下是有效的指針聲明:
int *ip; /* 一個整型的指針 */ double *dp; /* 一個 double 型的指針 */ float *fp; /* 一個浮點型的指針 */ char *ch; /* 一個字元型的指針 */
所有指針的值的實際數據類型,不管是整型、浮點型、字元型,還是其他的數據類型,都是一樣的,都是一個代表記憶體地址的長的十六進位數。不同數據類型的指針之間唯一的不同是,指針所指向的變數或常量的數據類型不同。
C++ 中使用指針
使用指針時會頻繁進行以下幾個操作:定義一個指針變數、把變數地址賦值給指針、訪問指針變數中可用地址的值。這些是通過使用一元運算符 *來返回位於操作數所指定地址的變數的值。下面的實例涉及到了這些操作:
實例
#include <iostream> using namespace std; int main () { int var = 20; // 實際變數的聲明 int *ip; // 指針變數的聲明 ip = &var; // 在指針變數中存儲 var 的地址 cout << "Value of var variable: "; cout << var << endl; // 輸出在指針變數中存儲的地址 cout << "Address stored in ip variable: "; cout << ip << endl; // 訪問指針中地址的值 cout << "Value of *ip variable: "; cout << *ip << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Value of var variable: 20 Address stored in ip variable: 0xbfc601ac Value of *ip variable: 20
C++ 引用
引用變數是一個別名,也就是說,它是某個已存在變數的另一個名字。一旦把引用初始化為某個變數,就可以使用該引用名稱或變數名稱來指向變數。
C++ 引用 vs 指針
引用很容易與指針混淆,它們之間有三個主要的不同:
- 不存在空引用。引用必須連接到一塊合法的記憶體。
- 一旦引用被初始化為一個對象,就不能被指向到另一個對象。指針可以在任何時候指向到另一個對象。
- 引用必須在創建時被初始化。指針可以在任何時間被初始化。
C++ 中創建引用
試想變數名稱是變數附屬在記憶體位置中的標籤,可以把引用當成是變數附屬在記憶體位置中的第二個標籤。因此,可以通過原始變數名稱或引用來訪問變數的內容。例如:
int i = 17;
我們可以為 i 聲明引用變數,如下所示:
int& r = i; double& s = d;
在這些聲明中,& 讀作引用。因此,第一個聲明可以讀作 "r 是一個初始化為 i 的整型引用",第二個聲明可以讀作 "s 是一個初始化為 d 的 double 型引用"。下面的實例使用了 int 和 double 引用:
實例
#include <iostream> using namespace std; int main () { // 聲明簡單的變數 int i; double d; // 聲明引用變數 int& r = i; double& s = d; i = 5; cout << "Value of i : " << i << endl; cout << "Value of i reference : " << r << endl; d = 11.7; cout << "Value of d : " << d << endl; cout << "Value of d reference : " << s << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Value of i : 5 Value of i reference : 5 Value of d : 11.7 Value of d reference : 11.7
引用通常用於函數參數列表和函數返回值。下面列出了 C++ 程式設計師必須清楚的兩個與 C++ 引用相關的重要概念:
概念 | 描述 |
|---|---|
把引用作為參數 | C++ 支援把引用作為參數傳給函數,這比傳一般的參數更安全。 |
把引用作為返回值 | 可以從 C++ 函數中返回引用,就像返回其他數據類型一樣。 |
四、 C++ 類與對象初探:繼承和重載
C++ 類 & 對象
C++ 在 C 語言的基礎上增加了面向對象編程,C++ 支援面向對象程式設計。類是 C++ 的核心特性,通常被稱為用戶定義的類型。
類用於指定對象的形式,它包含了數據表示法和用於處理數據的方法。類中的數據和方法稱為類的成員。函數在一個類中被稱為類的成員。
C++ 類定義
定義一個類,本質上是定義一個數據類型的藍圖。這實際上並沒有定義任何數據,但它定義了類的名稱意味著什麼,也就是說,它定義了類的對象包括了什麼,以及可以在這個對象上執行哪些操作。
類定義是以關鍵字 class 開頭,後跟類的名稱。類的主體是包含在一對花括弧中。類定義後必須跟著一個分號或一個聲明列表。例如,我們使用關鍵字 class 定義 Box 數據類型,如下所示:
class Box { public: double length; // 盒子的長度 double breadth; // 盒子的寬度 double height; // 盒子的高度 };關鍵字 public 確定了類成員的訪問屬性。在類對象作用域內,公共成員在類的外部是可訪問的。也可以指定類的成員為 private 或 protected。
定義 C++ 對象
類提供了對象的藍圖,所以基本上,對象是根據類來創建的。聲明類的對象,就像聲明基本類型的變數一樣。下面的語句聲明了類 Box 的兩個對象:
Box Box1; // 聲明 Box1,類型為 Box Box Box2; // 聲明 Box2,類型為 Box 對象 Box1 和 Box2 都有它們各自的數據成員。
訪問數據成員
類的對象的公共數據成員可以使用直接成員訪問運算符 (.) 來訪問。為了更好地理解這些概念,讓我們嘗試一下下面的實例:
實例
#include <iostream> using namespace std; class Box { public: double length; // 長度 double breadth; // 寬度 double height; // 高度 }; int main( ) { Box Box1; // 聲明 Box1,類型為 Box Box Box2; // 聲明 Box2,類型為 Box double volume = 0.0; // 用於存儲體積 // box 1 詳述 Box1.height = 5.0; Box1.length = 6.0; Box1.breadth = 7.0; // box 2 詳述 Box2.height = 10.0; Box2.length = 12.0; Box2.breadth = 13.0; // box 1 的體積 volume = Box1.height * Box1.length * Box1.breadth; cout << "Box1 的體積:" << volume <<endl; // box 2 的體積 volume = Box2.height * Box2.length * Box2.breadth; cout << "Box2 的體積:" << volume <<endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Box1 的體積:210 Box2 的體積:1560
需要注意的是,私有的成員和受保護的成員不能使用直接成員訪問運算符 (.) 來直接訪問。
C++ 繼承
面向對象程式設計中最重要的一個概念是繼承。繼承允許我們依據另一個類來定義一個類,這使得創建和維護一個應用程式變得更容易。這樣做,也達到了重用程式碼功能和提高執行時間的效果。
當創建一個類時,您不需要重新編寫新的數據成員和成員函數,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱為基類,新建的類稱為派生類。
繼承代表了 is a 關係。例如,哺乳動物是動物,狗是哺乳動物,因此,狗是動物,等等。
基類 & 派生類
一個類可以派生自多個類,這意味著,它可以從多個基類繼承數據和函數。定義一個派生類,我們使用一個類派生列表來指定基類。類派生列表以一個或多個基類命名,形式如下:
class derived-class: access-specifier base-class
其中,訪問修飾符 access-specifier 是 public、protected 或 private 其中的一個,base-class 是之前定義過的某個類的名稱。如果未使用訪問修飾符 access-specifier,則默認為 private。
假設有一個基類 Shape,Rectangle 是它的派生類,如下所示:
實例
#include <iostream> using namespace std; // 基類 class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 派生類 class Rectangle: public Shape { public: int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; Rect.setWidth(5); Rect.setHeight(7); // 輸出對象的面積 cout << "Total area: " << Rect.getArea() << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Total area: 35
訪問控制和繼承
派生類可以訪問基類中所有的非私有成員。因此基類成員如果不想被派生類的成員函數訪問,則應在基類中聲明為 private。
我們可以根據訪問許可權總結出不同的訪問類型,如下所示:
訪問 | public | protected | private |
|---|---|---|---|
同一個類 | yes | yes | yes |
派生類 | yes | yes | no |
外部的類 | yes | no | no |
一個派生類繼承了所有的基類方法,但下列情況除外:
- 基類的構造函數、析構函數和拷貝構造函數。
- 基類的重載運算符。
- 基類的友元函數。
繼承類型
當一個類派生自基類,該基類可以被繼承為 public、protected 或 private 幾種類型。繼承類型是通過上面講解的訪問修飾符 access-specifier 來指定的。
我們幾乎不使用 protected 或 private 繼承,通常使用 public 繼承。當使用不同類型的繼承時,遵循以下幾個規則:
- 公有繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,但是可以通過調用基類的公有和保護成員來訪問。
- 保護繼承(protected):當一個類派生自保護基類時,基類的公有和保護成員將成為派生類的保護成員。
- 私有繼承(private):當一個類派生自私有基類時,基類的公有和保護成員將成為派生類的私有成員。
多繼承
多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。
C++ 類可以從多個類繼承成員,語法如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,… { <派生類類體> };其中,訪問修飾符繼承方式是 public、protected 或 private 其中的一個,用來修飾每個基類,各個基類之間用逗號分隔,如上所示。現在讓我們一起看看下面的實例:
實例
#include <iostream> using namespace std; // 基類 Shape class Shape { public: void setWidth(int w) { width = w; } void setHeight(int h) { height = h; } protected: int width; int height; }; // 基類 PaintCost class PaintCost { public: int getCost(int area) { return area * 70; } }; // 派生類 class Rectangle: public Shape, public PaintCost { public: int getArea() { return (width * height); } }; int main(void) { Rectangle Rect; int area; Rect.setWidth(5); Rect.setHeight(7); area = Rect.getArea(); // 輸出對象的面積 cout << "Total area: " << Rect.getArea() << endl; // 輸出總花費 cout << "Total paint cost: $" << Rect.getCost(area) << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Total area: 35 Total paint cost: $2450
C++ 重載運算符和重載函數
C++ 允許在同一作用域中的某個函數和運算符指定多個定義,分別稱為函數重載和運算符重載。
重載聲明是指一個與之前已經在該作用域內聲明過的函數或方法具有相同名稱的聲明,但是它們的參數列表和定義(實現)不相同。
當您調用一個重載函數或重載運算符時,編譯器通過把您所使用的參數類型與定義中的參數類型進行比較,決定選用最合適的定義。選擇最合適的重載函數或重載運算符的過程,稱為重載決策。
C++ 中的函數重載
在同一個作用域內,可以聲明幾個功能類似的同名函數,但是這些同名函數的形式參數(指參數的個數、類型或者順序)必須不同。您不能僅通過返回類型的不同來重載函數。
下面的實例中,同名函數 print() 被用於輸出不同的數據類型:
實例
#include <iostream> using namespace std; class printData { public: void print(int i) { cout << "整數為: " << i << endl; } void print(double f) { cout << "浮點數為: " << f << endl; } void print(char c[]) { cout << "字元串為: " << c << endl; } }; int main(void) { printData pd; // 輸出整數 pd.print(5); // 輸出浮點數 pd.print(500.263); // 輸出字元串 char c[] = "Hello C++"; pd.print(c); return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
整數為: 5 浮點數為: 500.263 字元串為: Hello C++
C++ 中的運算符重載
您可以重定義或重載大部分 C++ 內置的運算符。這樣,您就能使用自定義類型的運算符。
重載的運算符是帶有特殊名稱的函數,函數名是由關鍵字 operator 和其後要重載的運算符符號構成的。與其他函數一樣,重載運算符有一個返回類型和一個參數列表。
Box operator+(const Box&);
聲明加法運算符用於把兩個 Box 對象相加,返回最終的 Box 對象。大多數的重載運算符可被定義為普通的非成員函數或者被定義為類成員函數。如果我們定義上面的函數為類的非成員函數,那麼我們需要為每次操作傳遞兩個參數,如下所示:
Box operator+(const Box&, const Box&);
下面的實例使用成員函數演示了運算符重載的概念。在這裡,對象作為參數進行傳遞,對象的屬性使用 this 運算符進行訪問,如下所示:
實例
#include <iostream> using namespace std; class Box { public: double getVolume(void) { return length * breadth * height; } void setLength( double len ) { length = len; } void setBreadth( double bre ) { breadth = bre; } void setHeight( double hei ) { height = hei; } // 重載 + 運算符,用於把兩個 Box 對象相加 Box operator+(const Box& b) { Box box; box.length = this->length + b.length; box.breadth = this->breadth + b.breadth; box.height = this->height + b.height; return box; } private: double length; // 長度 double breadth; // 寬度 double height; // 高度 }; // 程式的主函數 int main( ) { Box Box1; // 聲明 Box1,類型為 Box Box Box2; // 聲明 Box2,類型為 Box Box Box3; // 聲明 Box3,類型為 Box double volume = 0.0; // 把體積存儲在該變數中 // Box1 詳述 Box1.setLength(6.0); Box1.setBreadth(7.0); Box1.setHeight(5.0); // Box2 詳述 Box2.setLength(12.0); Box2.setBreadth(13.0); Box2.setHeight(10.0); // Box1 的體積 volume = Box1.getVolume(); cout << "Volume of Box1 : " << volume <<endl; // Box2 的體積 volume = Box2.getVolume(); cout << "Volume of Box2 : " << volume <<endl; // 把兩個對象相加,得到 Box3 Box3 = Box1 + Box2; // Box3 的體積 volume = Box3.getVolume(); cout << "Volume of Box3 : " << volume <<endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Volume of Box1 : 210 Volume of Box2 : 1560 Volume of Box3 : 5400
五 、C++進階 多態和數據抽象
C++ 多態
多態按字面的意思就是多種形態。當類之間存在層次結構,並且類之間是通過繼承關聯時,就會用到多態。
C++ 多態意味著調用成員函數時,會根據調用函數的對象的類型來執行不同的函數。
下面的實例中,基類 Shape 被派生為兩個類,如下所示:
實例
#include <iostream> using namespace std; class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } int area() { cout << "Parent class area :" <<endl; return 0; } }; class Rectangle: public Shape{ public: Rectangle( int a=0, int b=0):Shape(a, b) { } int area () { cout << "Rectangle class area :" <<endl; return (width * height); } }; class Triangle: public Shape{ public: Triangle( int a=0, int b=0):Shape(a, b) { } int area () { cout << "Triangle class area :" <<endl; return (width * height / 2); } }; // 程式的主函數 int main( ) { Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); // 存儲矩形的地址 shape = &rec; // 調用矩形的求面積函數 area shape->area(); // 存儲三角形的地址 shape = &tri; // 調用三角形的求面積函數 area shape->area(); return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Parent class area Parent class area
導致錯誤輸出的原因是,調用函數 area() 被編譯器設置為基類中的版本,這就是所謂的靜態多態,或靜態鏈接 - 函數調用在程式執行前就準備好了。有時候這也被稱為早綁定,因為 area() 函數在程式編譯期間就已經設置好了。
但現在,讓我們對程式稍作修改,在 Shape 類中,area() 的聲明前放置關鍵字 virtual,如下所示:
class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } virtual int area() { cout << "Parent class area :" <<endl; return 0; } };修改後,當編譯和執行前面的實例程式碼時,它會產生以下結果:
Rectangle class area Triangle class area
此時,編譯器看的是指針的內容,而不是它的類型。因此,由於 tri 和 rec 類的對象的地址存儲在 *shape 中,所以會調用各自的 area() 函數。
正如大家所看到的,每個子類都有一個函數 area() 的獨立實現。這就是多態的一般使用方式。有了多態,就可以有多個不同的類,都帶有同一個名稱但具有不同實現的函數,函數的參數甚至可以是相同的。
虛函數
虛函數 是在基類中使用關鍵字 virtual 聲明的函數。在派生類中重新定義基類中定義的虛函數時,會告訴編譯器不要靜態鏈接到該函數。
我們想要的是在程式中任意點可以根據所調用的對象類型來選擇調用的函數,這種操作被稱為動態鏈接,或後期綁定。
純虛函數
大家可能想要在基類中定義虛函數,以便在派生類中重新定義該函數更好地適用於對象,但是大家在基類中又不能對虛函數給出有意義的實現,這個時候就會用到純虛函數。
我們可以把基類中的虛函數 area() 改寫如下:
class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } // pure virtual function virtual int area() = 0; };= 0 告訴編譯器,函數沒有主體,上面的虛函數是純虛函數。
C++ 數據抽象
數據抽象是指,只向外界提供關鍵資訊,並隱藏其後台的實現細節,即只表現必要的資訊而不呈現細節。
數據抽象是一種依賴於介面和實現分離的編程(設計)技術。
讓我們舉一個現實生活中的真實例子,比如一台電視機,可以打開和關閉、切換頻道、調整音量、添加外部組件(如喇叭、錄像機、DVD 播放器),但是我們不知道它的內部實現細節,也就是說,我們並不知道它是如何通過纜線接收訊號,如何轉換訊號,並最終顯示在螢幕上。
因此,我們可以說電視把它的內部實現和外部介面分離開了,我們無需知道它的內部實現原理,直接通過它的外部介面(比如電源按鈕、遙控器、聲量控制器)就可以操控電視。
現在,讓我們言歸正傳,就 C++ 編程而言,C++ 類為數據抽象提供了可能。它們向外界提供了大量用於操作對象數據的公共方法,也就是說,外界實際上並不清楚類的內部實現。
例如,程式可以調用 sort() 函數,而不需要知道函數中排序數據所用到的演算法。實際上,函數排序的底層實現會因庫的版本不同而有所差異,只要介面不變,函數調用就可以照常工作。
在 C++ 中,我們使用類來定義我們自己的抽象數據類型(ADT)。可以使用類 iostream 的 cout 對象來輸出數據到標準輸出,如下所示:
實例
#include <iostream> using namespace std; int main( ) { cout << "Hello C++" <<endl; return 0; }在這裡,暫時不需要理解 cout 是如何在用戶的螢幕上顯示文本。只需要知道公共介面即可,cout 的底層實現可以自由改變。
訪問標籤強制抽象
在 C++ 中,我們使用訪問標籤來定義類的抽象介面。一個類可以包含零個或多個訪問標籤:
- 使用公共標籤定義的成員都可以訪問該程式的所有部分。一個類型的數據抽象視圖是由它的公共成員來定義的。
- 使用私有標籤定義的成員無法訪問到使用類的程式碼。私有部分對使用類型的程式碼隱藏了實現細節。
訪問標籤出現的頻率沒有限制。每個訪問標籤指定了緊隨其後的成員定義的訪問級別。指定的訪問級別會一直有效,直到遇到下一個訪問標籤或者遇到類主體的關閉右括弧為止。
數據抽象的好處
數據抽象有兩個重要的優勢:
- 類的內部受到保護,不會因無意的用戶級錯誤導致對象狀態受損。
- 類實現可能隨著時間的推移而發生變化,以便應對不斷變化的需求,或者應對那些要求不改變用戶級程式碼的錯誤報告。
如果只在類的私有部分定義數據成員,編寫該類的作者就可以隨意更改數據。如果實現發生改變,則只需要檢查類的程式碼,看看這個改變會導致哪些影響。
如果數據是公有的,則任何直接訪問舊錶示形式的數據成員的函數都可能受到影響。
數據抽象的實例
C++ 程式中,任何帶有公有和私有成員的類都可以作為數據抽象的實例。請看下面的實例:
實例
#include <iostream> using namespace std; class Adder{ public: // 構造函數 Adder(int i = 0) { total = i; } // 對外的介面 void addNum(int number) { total += number; } // 對外的介面 int getTotal() { return total; }; private: // 對外隱藏的數據 int total; }; int main( ) { Adder a; a.addNum(10); a.addNum(20); a.addNum(30); cout << "Total " << a.getTotal() <<endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Total 60
上面的類把數字相加,並返回總和。公有成員 addNum 和 getTotal 是對外的介面,用戶需要知道它們以便使用類。私有成員 total 是用戶不需要了解的,但又是類能正常工作所必需的。
C++ 數據封裝
所有的 C++ 程式都有以下兩個基本要素:
- 程式語句(程式碼):這是程式中執行動作的部分,它們被稱為函數。
- 程式數據:數據是程式的資訊,會受到程式函數的影響。
封裝是面向對象編程中的把數據和操作數據的函數綁定在一起的一個概念,這樣能避免受到外界的干擾和誤用,從而確保了安全。數據封裝引申出了另一個重要的 OOP 概念,即數據隱藏。
數據封裝是一種把數據和操作數據的函數捆綁在一起的機制,數據抽象是一種僅向用戶暴露介面而把具體的實現細節隱藏起來的機制。
C++ 通過創建類來支援封裝和數據隱藏(public、protected、private)。我們已經知道,類包含私有成員(private)、保護成員(protected)和公有成員(public)成員。默認情況下,在類中定義的所有項目都是私有的。例如:
class Box { public: double getVolume(void) { return length * breadth * height; } private: double length; // 長度 double breadth; // 寬度 double height; // 高度 };變數 length、breadth 和 height 都是私有的(private)。這意味著它們只能被 Box 類中的其他成員訪問,而不能被程式中其他部分訪問。這是實現封裝的一種方式。
為了使類中的成員變成公有的(即,程式中的其他部分也能訪問),必須在這些成員前使用 public 關鍵字進行聲明。所有定義在 public 標識符後邊的變數或函數可以被程式中所有其他的函數訪問。
把一個類定義為另一個類的友元類,會暴露實現細節,從而降低了封裝性。理想的做法是儘可能地對外隱藏每個類的實現細節。
數據封裝的實例
C++ 程式中,任何帶有公有和私有成員的類都可以作為數據封裝和數據抽象的實例。請看下面的實例:
實例
#include <iostream> using namespace std; class Adder{ public: // 構造函數 Adder(int i = 0) { total = i; } // 對外的介面 void addNum(int number) { total += number; } // 對外的介面 int getTotal() { return total; }; private: // 對外隱藏的數據 int total; }; int main( ) { Adder a; a.addNum(10); a.addNum(20); a.addNum(30); cout << "Total " << a.getTotal() <<endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Total 60
上面的類把數字相加,並返回總和。公有成員 addNum 和 getTotal 是對外的介面,用戶需要知道它們以便使用類。私有成員 total 是對外隱藏的,用戶不需要了解它,但它又是類能正常工作所必需的。
六、c++進階 模板和stl入門
C++ 模板
模板是泛型編程的基礎,泛型編程即以一種獨立於任何特定類型的方式編寫程式碼。
模板是創建泛型類或函數的藍圖或公式。庫容器,比如迭代器和演算法,都是泛型編程的例子,它們都使用了模板的概念。
每個容器都有一個單一的定義,比如 向量,我們可以定義許多不同類型的向量,比如 vector <int> 或 vector <string>。
您可以使用模板來定義函數和類,接下來讓我們一起來看看如何使用。
函數模板
模板函數定義的一般形式如下所示:
template <class type> ret-type func-name(parameter list) { // 函數的主體 }在這裡,type 是函數所使用的數據類型的佔位符名稱。這個名稱可以在函數定義中使用。
下面是函數模板的實例,返回兩個數中的最大值:
實例
#include <iostream> #include <string> using namespace std; template <typename T> inline T const& Max (T const& a, T const& b) { return a < b ? b:a; } int main () { int i = 39; int j = 20; cout << "Max(i, j): " << Max(i, j) << endl; double f1 = 13.5; double f2 = 20.7; cout << "Max(f1, f2): " << Max(f1, f2) << endl; string s1 = "Hello"; string s2 = "World"; cout << "Max(s1, s2): " << Max(s1, s2) << endl; return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
Max(i, j): 39 Max(f1, f2): 20.7 Max(s1, s2): World類模板
正如我們定義函數模板一樣,我們也可以定義類模板。泛型類聲明的一般形式如下所示:
template <class type> class class-name { . . . }在這裡,type 是佔位符類型名稱,可以在類被實例化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型數據類型。
下面的實例定義了類 Stack<>,並實現了泛型方法來對元素進行入棧出棧操作:
實例
#include <iostream> #include <vector> #include <cstdlib> #include <string> #include <stdexcept> using namespace std; template <class T> class Stack { private: vector<T> elems; // 元素 public: void push(T const&); // 入棧 void pop(); // 出棧 T top() const; // 返回棧頂元素 bool empty() const{ // 如果為空則返回真。 return elems.empty(); } }; template <class T> void Stack<T>::push (T const& elem) { // 追加傳入元素的副本 elems.push_back(elem); } template <class T> void Stack<T>::pop () { if (elems.empty()) { throw out_of_range("Stack<>::pop(): empty stack"); } // 刪除最後一個元素 elems.pop_back(); } template <class T> T Stack<T>::top () const { if (elems.empty()) { throw out_of_range("Stack<>::top(): empty stack"); } // 返回最後一個元素的副本 return elems.back(); } int main() { try { Stack<int> intStack; // int 類型的棧 Stack<string> stringStack; // string 類型的棧 // 操作 int 類型的棧 intStack.push(7); cout << intStack.top() <<endl; // 操作 string 類型的棧 stringStack.push("hello"); cout << stringStack.top() << std::endl; stringStack.pop(); stringStack.pop(); } catch (exception const& ex) { cerr << "Exception: " << ex.what() <<endl; return -1; } }當上面的程式碼被編譯和執行時,它會產生下列結果:
7 hello Exception: Stack<>::pop(): empty stack
C++ STL 入門
上面我們已經了解了 C++ 模板的概念。C++ STL(標準模板庫)是一套功能強大的 C++ 模板類,提供了通用的模板類和函數,這些模板類和函數可以實現多種流行和常用的演算法和數據結構,如向量、鏈表、隊列、棧。
C++ 標準模板庫的核心包括以下三個組件:
組件
描述 | |
|---|---|
容器(Containers) | 容器是用來管理某一類對象的集合。C++ 提供了各種不同類型的容器,比如 deque、list、vector、map 等。 |
演算法(Algorithms) | 演算法作用於容器。它們提供了執行各種操作的方式,包括對容器內容執行初始化、排序、搜索和轉換等操作。 |
迭代器(iterators) | 迭代器用於遍歷對象集合的元素。這些集合可能是容器,也可能是容器的子集。 |
這三個組件都帶有豐富的預定義函數,幫助我們通過簡單的方式處理複雜的任務。
下面的程式演示了向量容器(一個 C++ 標準的模板),它與數組十分相似,唯一不同的是,向量在需要擴展大小的時候,會自動處理它自己的存儲需求:
實例
#include <iostream> #include <vector> using namespace std; int main() { // 創建一個向量存儲 int vector<int> vec; int i; // 顯示 vec 的原始大小 cout << "vector size = " << vec.size() << endl; // 推入 5 個值到向量中 for(i = 0; i < 5; i++){ vec.push_back(i); } // 顯示 vec 擴展後的大小 cout << "extended vector size = " << vec.size() << endl; // 訪問向量中的 5 個值 for(i = 0; i < 5; i++){ cout << "value of vec [" << i << "] = " << vec[i] << endl; } // 使用迭代器 iterator 訪問值 vector<int>::iterator v = vec.begin(); while( v != vec.end()) { cout << "value of v = " << *v << endl; v++; } return 0; }當上面的程式碼被編譯和執行時,它會產生下列結果:
vector size = 0 extended vector size = 5 value of vec [0] = 0 value of vec [1] = 1 value of vec [2] = 2 value of vec [3] = 3 value of vec [4] = 4 value of v = 0 value of v = 1 value of v = 2 value of v = 3 value of v = 4
關於上面實例中所使用的各種函數,有幾點要注意:
- push_back( ) 成員函數在向量的末尾插入值,如果有必要會擴展向量的大小。
- size( ) 函數顯示向量的大小。
- begin( ) 函數返回一個指向向量開頭的迭代器。
- end( ) 函數返回一個指向向量末尾的迭代器。
