c++基礎筆記
C++ 基本語法
C++ 程式可以定義為物件的集合,這些物件通過呼叫彼此的方法進行互動。現在讓我們簡要地看一下什麼是類、物件,方法、即時變數。
- 物件 - 物件具有狀態和行為。例如:一隻狗的狀態 - 顏色、名稱、品種,行為 - 搖動、叫喚、吃。物件是類的例項。
- 類 - 類可以定義為描述物件行為/狀態的模板/藍圖。
- 方法 - 從基本上說,一個方法表示一種行為。一個類可以包含多個方法。可以在方法中寫入邏輯、操作資料以及執行所有的動作。
- 即時變數 - 每個物件都有其獨特的即時變數。物件的狀態是由這些即時變數的值建立的。
編譯 & 執行 C++ 程式
接下來讓我們看看如何把原始碼儲存在一個檔案中,以及如何編譯並執行它。下面是簡單的步驟:
- 開啟一個文字編輯器,新增上述程式碼。
- 儲存檔案為 hello.cpp。
- 開啟命令提示符,進入到儲存檔案所在的目錄。
- 鍵入 'g++ hello.cpp ',輸入回車,編譯程式碼。如果程式碼中沒有錯誤,命令提示符會跳到下一行,並生成 a.out 可執行檔案。
- 現在,鍵入 ' a.out' 來執行程式。
- 您可以看到螢幕上顯示 ' Hello World '。
$ g++ hello.cpp $ ./a.out Hello World
請確保您的路徑中已包含 g++ 編譯器,並確保在包含原始檔 hello.cpp 的目錄中執行它。
您也可以使用 makefile 來編譯 C/C++ 程式。
C++ 中的分號 & 語句塊
在 C++ 中,分號是語句結束符。也就是說,每個語句必須以分號結束。它表明一個邏輯實體的結束。
C++ 不以行末作為結束符的標識,因此,您可以在一行上放置多個語句。
語句塊是一組使用大括號{}括起來的按邏輯連線的語句。
C++ 識別符號
C++ 識別符號是用來標識變數、函式、類、模組,或任何其他使用者自定義專案的名稱。一個識別符號以字母 A-Z 或 a-z 或下劃線 _
C++ 註釋
C++ 註釋以 /* 開始,以 */ 終止。
以 // 開始,直到行末為止。
基本的內建型別
C++ 為程式設計師提供了種類豐富的內建資料型別和使用者自定義的資料型別。下表列出了七種基本的 C++ 資料型別:
型別 | 關鍵字 |
---|---|
布林型 | bool |
字元型 | char |
整型 | int |
浮點型 | float |
雙浮點型 | double |
無型別 | void |
寬字元型 | wchar_t |
一些基本型別可以使用一個或多個型別修飾符進行修飾:
- signed
- unsigned
- short
- long
區域性變數
在函式或一個程式碼塊內部宣告的變數,稱為區域性變數。它們只能被函式內部或者程式碼塊內部的語句使用。
全域性變數
在所有函式外部定義的變數(通常是在程式的頭部),稱為全域性變數。全域性變數的值在程式的整個生命週期內都是有效的。全域性變數可以被任何函式訪問。也就是說,全域性變數一旦宣告,在整個程式中都是可用的。
在程式中,區域性變數和全域性變數的名稱可以相同,但是在函式內,區域性變數的值會覆蓋全域性變數的值。
定義常量
在 C++ 中,有兩種簡單的定義常量的方式:
- 使用 #define 前處理器。
- 使用 const 關鍵字。
#define identifier value
const type variable = value
雜項運算子
下表列出了 C++ 支援的其他一些重要的運算子。
運算子 | 描述 |
---|---|
sizeof | sizeof 運算子返回變數的大小。例如,sizeof(a) 將返回 4,其中 a 是整數。 |
Condition ? X : Y | 條件運算子。如果 Condition 為真 ? 則值為 X : 否則值為 Y。 |
, | 逗號運算子會順序執行一系列運算。整個逗號表示式的值是以逗號分隔的列表中的最後一個表示式的值。 |
.(點)和 ->(箭頭) | 成員運算子用於引用類、結構和共用體的成員。 |
Cast | 強制轉換運算子把一種資料型別轉換為另一種資料型別。例如,int(2.2000) 將返回 2。 |
& | 指標運算子 & 返回變數的地址。例如 &a; 將給出變數的實際地址。 |
* | 指標運算子 * 指向一個變數。例如,*var; 將指向變數 var。 |
C++ 函式
函式宣告告訴編譯器函式的名稱、返回型別和引數。函式定義提供了函式的實際主體。
C++ 標準庫提供了大量的程式可以呼叫的內建函式。例如,函式 strcat() 用來連線兩個字串,函式 memcpy() 用來複制記憶體到另一個位置。函式還有很多叫法,比如方法、子例程或程式,等等。
定義函式
C++ 中的函式定義的一般形式如下:
return_type function_name( parameter list ) { body of the function }
在 C++ 中,函式由一個函式頭和一個函式主體組成。下面列出一個函式的所有組成部分:
- 返回型別:一個函式可以返回一個值。return_type 是函式返回的值的資料型別。有些函式執行所需的操作而不返回值,在這種情況下,return_type 是關鍵字 void。
- 函式名稱:這是函式的實際名稱。函式名和引數列表一起構成了函式簽名。
- 引數:引數就像是佔位符。當函式被呼叫時,您向引數傳遞一個值,這個值被稱為實際引數。引數列表包括函式引數的型別、順序、數量。引數是可選的,也就是說,函式可能不包含引數。
- 函式主體:函式主體包含一組定義函式執行任務的語句。
函式宣告
函式宣告會告訴編譯器函式名稱及如何呼叫函式。函式的實際主體可以單獨定義。
函式宣告包括以下幾個部分:
return_type function_name( parameter list );
在函式宣告中,引數的名稱並不重要,只有引數的型別是必需的,因此下面也是有效的宣告:
int max(int, int);
函式引數
如果函式要使用引數,則必須宣告接受引數值的變數。這些變數稱為函式的形式引數。
形式引數就像函式內的其他區域性變數,在進入函式時被建立,退出函式時被銷燬。
當呼叫函式時,有兩種向函式傳遞引數的方式:
呼叫型別 | 描述 |
---|---|
傳值呼叫 | 該方法把引數的實際值複製給函式的形式引數。在這種情況下,修改函式內的形式引數對實際引數沒有影響。 |
指標呼叫 | 該方法把引數的地址複製給形式引數。在函式內,該地址用於訪問呼叫中要用到的實際引數。這意味著,修改形式引數會影響實際引數。 |
引用呼叫 | 該方法把引數的引用複製給形式引數。在函式內,該引用用於訪問呼叫中要用到的實際引數。這意味著,修改形式引數會影響實際引數。 |
預設情況下,C++ 使用傳值呼叫來傳遞引數。一般來說,這意味著函式內的程式碼不能改變用於呼叫函式的引數。
引數的預設值
當您定義一個函式,您可以為引數列表中後邊的每一個引數指定預設值。當呼叫函式時,如果實際引數的值留空,則使用這個預設值。
這是通過在函式定義中使用賦值運算子來為引數賦值的。呼叫函式時,如果未傳遞引數的值,則會使用預設值,如果指定了值,則會忽略預設值,使用傳遞的值。
字串
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 的第一次出現的位置。 |
指標
學習 C++ 的指標既簡單又有趣。通過指標,可以簡化一些 C++ 程式設計任務的執行,還有一些任務,如動態記憶體分配,沒有指標是無法執行的。所以,想要成為一名優秀的 C++ 程式設計師,學習指標是很有必要的。
正如您所知道的,每一個變數都有一個記憶體位置,每一個記憶體位置都定義了可使用連字號(&)運算子訪問的地址,它表示了在記憶體中的一個地址。
指標是一個變數,其值為另一個變數的地址,即,記憶體位置的直接地址。就像其他變數或常量一樣,您必須在使用指標儲存其他變數地址之前,對其進行宣告。
使用指標時會頻繁進行以下幾個操作:定義一個指標變數、把變數地址賦值給指標、訪問指標變數中可用地址的值。這些是通過使用一元運算子 *來返回位於運算元所指定地址的變數的值。
在 C++ 中,有很多指標相關的概念,這些概念都很簡單,但是都很重要。下面列出了 C++ 程式設計師必須清楚的一些與指標相關的重要概念:
概念 | 描述 |
---|---|
C++ Null 指標 | C++ 支援空指標。NULL 指標是一個定義在標準庫中的值為零的常量。 |
C++ 指標的算術運算 | 可以對指標進行四種算術運算:++、--、+、- |
C++ 指標 vs 陣列 | 指標和陣列之間有著密切的關係。 |
C++ 指標陣列 | 可以定義用來儲存指標的陣列。 |
C++ 指向指標的指標 | C++ 允許指向指標的指標。 |
C++ 傳遞指標給函式 | 通過引用或地址傳遞引數,使傳遞的引數在呼叫函式中被改變。 |
C++ 從函式返回指標 | C++ 允許函式返回指標到區域性變數、靜態變數和動態記憶體分配。 |
引用
引用變數是一個別名,也就是說,它是某個已存在變數的另一個名字。一旦把引用初始化為某個變數,就可以使用該引用名稱或變數名稱來指向變數。
C++ 引用 vs 指標
引用很容易與指標混淆,它們之間有三個主要的不同:
- 不存在空引用。引用必須連線到一塊合法的記憶體。
- 一旦引用被初始化為一個物件,就不能被指向到另一個物件。指標可以在任何時候指向到另一個物件。
- 引用必須在建立時被初始化。指標可以在任何時間被初始化。
int& r = i;
double& s = d;
引用通常用於函式引數列表和函式返回值。
日期 & 時間
C++ 標準庫沒有提供所謂的日期型別。C++ 繼承了 C 語言用於日期和時間操作的結構和函式。為了使用日期和時間相關的函式和結構,需要在 C++ 程式中引用 <ctime> 標頭檔案。
有四個與時間相關的型別:clock_t、time_t、size_t 和 tm。型別 clock_t、size_t 和 time_t 能夠把系統時間和日期表示為某種整數。結構型別 tm 把日期和時間以 C 結構的形式儲存。
基本的輸入輸出
I/O 庫標頭檔案
下列的標頭檔案在 C++ 程式設計中很重要。
標頭檔案 | 函式和描述 |
---|---|
<iostream> | 該檔案定義了 cin、cout、cerr 和 clog 物件,分別對應於標準輸入流、標準輸出流、非緩衝標準錯誤流和緩衝標準錯誤流。 |
<iomanip> | 該檔案通過所謂的引數化的流操縱器(比如 setw 和 setprecision),來宣告對執行標準化 I/O 有用的服務。 |
<fstream> | 該檔案為使用者控制的檔案處理宣告服務。我們將在檔案和流的相關章節討論它的細節。 |
輸出流cout,C++ 編譯器根據要輸出變數的資料型別,選擇合適的流插入運算子來顯示值。<< 運算子被過載來輸出內建型別(整型、浮點型、double 型、字串和指標)的資料項。流插入運算子 << 在一個語句中可以多次使用,endl 用於在行末新增一個換行符。輸入流cin,>>
使用 cerr 流來顯示錯誤訊息,而其他的日誌訊息則使用 clog 流來輸出。
輸入輸出流中的函式(模板):
#include <iostream>
#include <iomanip>
using namespace std;
int main()
{
cout<<setiosflags(ios::left|ios::showpoint); // 設左對齊,以一般實數方式顯示
cout.precision(5); // 設定除小數點外有五位有效數字
cout<<123.456789<<endl;
cout.width(10); // 設定顯示域寬10
cout.fill('*'); // 在顯示區域空白處用*填充
cout<<resetiosflags(ios::left); // 清除狀態左對齊
cout<<setiosflags(ios::right); // 設定右對齊
cout<<123.456789<<endl;
cout<<setiosflags(ios::left|ios::fixed); // 設左對齊,以固定小數位顯示
cout.precision(3); // 設定實數顯示三位小數
cout<<999.123456<<endl;
cout<<resetiosflags(ios::left|ios::fixed); //清除狀態左對齊和定點格式
cout<<setiosflags(ios::left|ios::scientific); //設定左對齊,以科學技術法顯示
cout.precision(3); //設定保留三位小數
cout<<123.45678<<endl;
return 0;
}
測試輸出結果:
123.46
****123.46
999.123
1.235e+02
其中 cout.setf 跟 setiosflags 一樣,cout.precision 跟 setprecision 一樣,cout.unsetf 跟 resetiosflags 一樣。
setioflags(ios::fixed) 固定的浮點顯示
setioflags(ios::scientific) 指數表示
setiosflags(ios::left) 左對齊
setiosflags(ios::right) 右對齊
setiosflags(ios::skipws 忽略前導空白
setiosflags(ios::uppercase) 16進位制數大寫輸出
setiosflags(ios::lowercase) 16進位制小寫輸出
setiosflags(ios::showpoint) 強制顯示小數點
setiosflags(ios::showpos) 強制顯示符號
cout.setf 常見的標誌:
標誌 | 功能 |
---|---|
boolalpha | 可以使用單詞”true”和”false”進行輸入/輸出的布林值. |
oct | 用八進位制格式顯示數值. |
dec | 用十進位制格式顯示數值. |
hex | 用十六進位制格式顯示數值. |
left | 輸出調整為左對齊. |
right | 輸出調整為右對齊. |
scientific | 用科學記數法顯示浮點數. |
fixed | 用正常的記數方法顯示浮點數(與科學計數法相對應). |
showbase | 輸出時顯示所有數值的基數. |
showpoint | 顯示小數點和額外的零,即使不需要. |
showpos | 在非負數值前面顯示”+(正號)”. |
skipws | 當從一個流進行讀取時,跳過空白字元(spaces, tabs, newlines). |
unitbuf | 在每次插入以後,清空緩衝區. |
internal | 將填充字元回到符號和數值之間. |
uppercase | 以大寫的形式顯示科學記數法中的”e”和十六進位制格式的”x”. |
iostream 中定義的操作符:
操作符 | 描述 | 輸入 | 輸出 |
---|---|---|---|
boolalpha | 啟用boolalpha標誌 | √ | √ |
dec | 啟用dec標誌 | √ | √ |
endl | 輸出換行標示,並清空緩衝區 | √ | |
ends | 輸出空字元 | √ | |
fixed | 啟用fixed標誌 | √ | |
flush | 清空流 | √ | |
hex | 啟用 hex 標誌 | √ | √ |
internal | 啟用 internal 標誌 | √ | |
left | 啟用 left 標誌 | √ | |
noboolalpha | 關閉boolalpha 標誌 | √ | √ |
noshowbase | 關閉showbase 標誌 | √ | |
noshowpoint | 關閉showpoint 標誌 | √ | |
noshowpos | 關閉showpos 標誌 | √ | |
noskipws | 關閉skipws 標誌 | √ | |
nounitbuf | 關閉unitbuf 標誌 | √ | |
nouppercase | 關閉uppercase 標誌 | √ | |
oct | 啟用 oct 標誌 | √ | √ |
right | 啟用 right 標誌 | √ | |
scientific | 啟用 scientific 標誌 | √ | |
showbase | 啟用 showbase 標誌 | √ | |
showpoint | 啟用 showpoint 標誌 | √ | |
showpos | 啟用 showpos 標誌 | √ | |
skipws | 啟用 skipws 標誌 | √ | |
unitbuf | 啟用 unitbuf 標誌 | √ | |
uppercase | 啟用 uppercase 標誌 | √ | |
ws | 跳過所有前導空白字元 | √ |
iomanip 中定義的操作符:
操作符 | 描述 | 輸入 | 輸出 |
---|---|---|---|
resetiosflags(long f) | 關閉被指定為f的標誌 | √ | √ |
setbase(int base) | 設定數值的基本數為base | √ | |
setfill(int ch) | 設定填充字元為ch | √ | |
setiosflags(long f) | 啟用指定為f的標誌 | √ | √ |
setprecision(int p) | 設定數值的精度(四捨五入) | √ | |
setw(int w) | 設定域寬度為w | √ |
資料結構
C/C++ 陣列允許定義可儲存相同型別資料項的變數,但是結構是 C++ 中另一種使用者自定義的可用的資料型別,它允許您儲存不同型別的資料項。
為了定義結構,您必須使用 struct 語句。struct 語句定義了一個包含多個成員的新的資料型別,在結構定義的末尾,最後一個分號之前,您可以指定一個或多個結構變數,這是可選的。下面是宣告一個結構體型別 Books,變數為 book:
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book;
為了訪問結構的成員,我們使用成員訪問運算子(.)。成員訪問運算子是結構變數名稱和我們要訪問的結構成員之間的一個句號。
指向結構的指標
您可以定義指向結構的指標,方式與定義指向其他型別變數的指標相似,如下所示:
struct Books *struct_pointer;
現在,您可以在上述定義的指標變數中儲存結構變數的地址。為了查詢結構變數的地址,請把 & 運算子放在結構名稱的前面,如下所示:
struct_pointer = &Book1;
為了使用指向該結構的指標訪問結構的成員,您必須使用 -> 運算子,如下所示:
struct_pointer->title;
typedef 關鍵字
下面是一種更簡單的定義結構的方式,您可以為建立的型別取一個"別名"。例如:
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
}Books;
現在,您可以直接使用 Books 來定義 Books 型別的變數,而不需要使用 struct 關鍵字。下面是例項:
Books Book1, Book2;
您可以使用 typedef 關鍵字來定義非結構型別,如下所示:
typedef long int *pint32;
pint32 x, y, z;
x, y 和 z 都是指向長整型 long int 的指標。
類 & 物件
C++ 在 C 語言的基礎上增加了面向物件程式設計,C++ 支援面向物件程式設計。類是 C++ 的核心特性,通常被稱為使用者定義的型別。
類用於指定物件的形式,它包含了資料表示法和用於處理資料的方法。類中的資料和方法稱為類的成員。函式在一個類中被稱為類的成員。
類定義
定義一個類,本質上是定義一個數據型別的藍圖。這實際上並沒有定義任何資料,但它定義了類的名稱意味著什麼,也就是說,它定義了類的物件包括了什麼,以及可以在這個物件上執行哪些操作。
類定義是以關鍵字 class 開頭,後跟類的名稱。類的主體是包含在一對花括號中。類定義後必須跟著一個分號或一個宣告列表。例如,我們使用關鍵字 class 定義 Box 資料型別,如下所示:
class Box
{
public:
double length; // 盒子的長度
double breadth; // 盒子的寬度
double height; // 盒子的高度
};
定義 C++ 物件
類提供了物件的藍圖,所以基本上,物件是根據類來建立的。宣告類的物件,就像宣告基本型別的變數一樣。下面的語句聲明瞭類 Box 的一個物件:
Box Box1; // 宣告 Box1,型別為 Box
訪問資料成員
類的物件的公共資料成員可以使用直接成員訪問運算子 (.) 來訪問。
需要注意的是,私有的成員和受保護的成員不能使用直接成員訪問運算子 (.) 來直接訪問。
類 & 物件詳解
到目前為止,我們已經對 C++ 的類和物件有了基本的瞭解。下面的列表中還列出了其他一些 C++ 類和物件相關的概念,可以點選相應的連結進行學習。
概念 | 描述 |
---|---|
類成員函式 | 類的成員函式是指那些把定義和原型寫在類定義內部的函式,就像類定義中的其他變數一樣。 |
類訪問修飾符 | 類成員可以被定義為 public、private 或 protected。預設情況下是定義為 private。 |
建構函式 & 解構函式 | 類的建構函式是一種特殊的函式,在建立一個新的物件時呼叫。類的解構函式也是一種特殊的函式,在刪除所建立的物件時呼叫。 |
C++ 拷貝建構函式 | 拷貝建構函式,是一種特殊的建構函式,它在建立物件時,是使用同一類中之前建立的物件來初始化新建立的物件。 |
C++ 友元函式 | 友元函式可以訪問類的 private 和 protected 成員。 |
C++ 行內函數 | 通過行內函數,編譯器試圖在呼叫函式的地方擴充套件函式體中的程式碼。 |
C++ 中的 this 指標 | 每個物件都有一個特殊的指標 this,它指向物件本身。 |
C++ 中指向類的指標 | 指向類的指標方式如同指向結構的指標。實際上,類可以看成是一個帶有函式的結構。 |
C++ 類的靜態成員 | 類的資料成員和函式成員都可以被宣告為靜態的。 |
類成員函式
類的成員函式是指那些把定義和原型寫在類定義內部的函式,就像類定義中的其他變數一樣。類成員函式是類的一個成員,它可以操作類的任意物件,可以訪問物件中的所有成員
成員函式可以定義在類定義內部,或者單獨使用範圍解析運算子 :: 來定義。在類定義中定義的成員函式把函式宣告為內聯的,即便沒有使用 inline 識別符號。
class Box
{
public:
double length; // 長度
double breadth; // 寬度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
也可以在類的外部使用範圍解析運算子 :: 定義該函式
double Box::getVolume(void)
{
return length * breadth * height;
}
在這裡,需要強調一點,在 :: 運算子之前必須使用類名。呼叫成員函式是在物件上使用點運算子(.),這樣它就能操作與該物件相關的資料
注:
:: 叫作用域區分符,指明一個函式屬於哪個類或一個數據屬於哪個類。
:: 可以不跟類名,表示全域性資料或全域性函式(即非成員函式)。
類訪問修飾符
資料封裝是面向物件程式設計的一個重要特點,它防止函式直接訪問類型別的內部成員。關鍵字 public、private、protected 稱為訪問修飾符。
一個類可以有多個 public、protected 或 private 標記區域。每個標記區域在下一個標記區域開始之前或者在遇到類主體結束右括號之前都是有效的。成員和類的預設訪問修飾符是 private。
私有(private)成員
私有成員變數或函式在類的外部是不可訪問的,甚至是不可檢視的。只有類和友元函式可以訪問私有成員。預設情況下,類的所有成員都是私有的。
保護(protected)成員
保護成員變數或函式與私有成員十分相似,但有一點不同,保護成員在派生類(即子類)中是可訪問的。
類建構函式 & 解構函式
類的建構函式
類的建構函式是類的一種特殊的成員函式,它會在每次建立類的新物件時執行。
建構函式的名稱與類的名稱是完全相同的,並且不會返回任何型別,也不會返回 void。建構函式可用於為某些成員變數設定初始值。
http://www.runoob.com/cplusplus/cpp-constructor-destructor.html
預設的建構函式沒有任何引數,但如果需要,建構函式也可以帶有引數。這樣在建立物件時就會給物件賦初始值
也可以使用初始化列表來初始化欄位
類的解構函式
類的解構函式是類的一種特殊的成員函式,它會在每次刪除所建立的物件時執行。
解構函式的名稱與類的名稱是完全相同的,只是在前面加了個波浪號(~)作為字首,它不會返回任何值,也不能帶有任何引數。
解構函式有助於在跳出程式(比如關閉檔案、釋放記憶體等)前釋放資源。
拷貝建構函式
拷貝建構函式是一種特殊的建構函式,它在建立物件時,是使用同一類中之前建立的物件來初始化新建立的物件。拷貝建構函式通常用於:
-
一個物件以值傳遞的方式傳入函式體
-
一個物件以值傳遞的方式從函式返回
-
一個物件需要通過另外一個物件進行初始化
如果在類中沒有定義拷貝建構函式,編譯器會自行定義一個。如果類帶有指標變數,並有動態記憶體分配,則它必須有一個拷貝建構函式。
友元函式
類的友元函式是定義在類外部,但有權訪問類的所有私有(private)成員和保護(protected)成員。
儘管友元函式的原型有在類的定義中出現過,但是友元函式並不是成員函式。友元可以是一個函式,該函式被稱為友元函式;友元也可以是一個類,該類被稱為友元類,在這種情況下,整個類及其所有成員都是友元。
如果要宣告函式為一個類的友元,需要在類定義中該函式原型前使用關鍵字 friend
比如宣告類 ClassTwo 的所有成員函式作為類 ClassOne 的友元,需要在類 ClassOne 的定義中放置如下宣告:
friend class ClassTwo;
行內函數
C++ 行內函數是通常與類一起使用。如果一個函式是內聯的,那麼在編譯時,編譯器會把該函式的程式碼副本放置在每個呼叫該函式的地方。
對行內函數進行任何修改,都需要重新編譯函式的所有客戶端,因為編譯器需要重新更換一次所有的程式碼,否則將會繼續使用舊的函式。
如果想把一個函式定義為行內函數,則需要在函式名前面放置關鍵字 inline,在呼叫函式之前需要對函式進行定義。如果已定義的函式多於一行,編譯器會忽略 inline 限定符。
在類定義中的定義的函式都是行內函數,即使沒有使用 inline 說明符。
引入行內函數的目的是為了解決程式中函式呼叫的效率問題,這麼說吧,程式在編譯器編譯的時候,編譯器將程式中出現的行內函數的呼叫表示式用行內函數的函式體進行替換,而對於其他的函式,都是在執行時候才被替代。這其實就是個空間代價換時間的i節省。所以行內函數一般都是1-5行的小函式。在使用行內函數時要留神:
- 1.在行內函數內不允許使用迴圈語句和開關語句;
- 2.行內函數的定義必須出現在行內函數第一次呼叫之前;
- 3.類結構中所在的類說明內部定義的函式是行內函數。
指向類的指標
一個指向 C++ 類的指標與指向結構的指標類似,訪問指向類的指標的成員,需要使用成員訪問運算子 ->,就像訪問指向結構的指標一樣。與所有的指標一樣,您必須在使用指標之前,對指標進行初始化。
類的靜態成員
我們可以使用 static 關鍵字來把類成員定義為靜態的。當我們宣告類的成員為靜態時,這意味著無論建立多少個類的物件,靜態成員都只有一個副本。
靜態成員在類的所有物件中是共享的。如果不存在其他的初始化語句,在建立第一個物件時,所有的靜態資料都會被初始化為零。我們不能把靜態成員的初始化放置在類的定義中,但是可以在類的外部通過使用範圍解析運算子 :: 來重新宣告靜態變數從而對它進行初始化
如果把函式成員宣告為靜態的,就可以把函式與類的任何特定物件獨立開來。靜態成員函式即使在類物件不存在的情況下也能被呼叫,靜態函式只要使用類名加範圍解析運算子 :: 就可以訪問。
靜態成員函式只能訪問靜態成員資料、其他靜態成員函式和類外部的其他函式。
靜態成員函式有一個類範圍,他們不能訪問類的 this 指標。您可以使用靜態成員函式來判斷類的某些物件是否已被建立。
靜態成員函式與普通成員函式的區別:
- 靜態成員函式沒有 this 指標,只能訪問靜態成員(包括靜態成員變數和靜態成員函式)。
- 普通成員函式有 this 指標,可以訪問類中的任意成員;而靜態成員函式沒有 this 指標。
靜態成員變數在類中僅僅是宣告,沒有定義,所以要在類的外面定義,實際上是給靜態成員變數分配記憶體。如果不加定義就會報錯,初始化是賦一個初始值,而定義是分配記憶體。
繼承
面向物件程式設計中最重要的一個概念是繼承。繼承允許我們依據另一個類來定義一個類,這使得建立和維護一個應用程式變得更容易。這樣做,也達到了重用程式碼功能和提高執行時間的效果。
當建立一個類時,您不需要重新編寫新的資料成員和成員函式,只需指定新建的類繼承了一個已有的類的成員即可。這個已有的類稱為基類,新建的類稱為派生類。
基類 & 派生類
一個類可以派生自多個類,這意味著,它可以從多個基類繼承資料和函式。定義一個派生類,我們使用一個類派生列表來指定基類。類派生列表以一個或多個基類命名
一個派生類繼承了所有的基類方法,但下列情況除外:
- 基類的建構函式、解構函式和拷貝建構函式。
- 基類的過載運算子。
- 基類的友元函式。
繼承型別
當一個類派生自基類,該基類可以被繼承為 public、protected 或 private 幾種型別。我們幾乎不使用 protected 或 private 繼承,通常使用 public 繼承。當使用不同型別的繼承時,遵循以下幾個規則:
- 公有繼承(public):當一個類派生自公有基類時,基類的公有成員也是派生類的公有成員,基類的保護成員也是派生類的保護成員,基類的私有成員不能直接被派生類訪問,但是可以通過呼叫基類的公有和保護成員來訪問。
- 保護繼承(protected): 當一個類派生自保護基類時,基類的公有和保護成員將成為派生類的保護成員。
- 私有繼承(private):當一個類派生自私有基類時,基類的公有和保護成員將成為派生類的私有成員。
多繼承
多繼承即一個子類可以有多個父類,它繼承了多個父類的特性。C++ 類可以從多個類繼承成員,語法如下:
class <派生類名>:<繼承方式1><基類名1>,<繼承方式2><基類名2>,…
{
<派生類類體>
};
過載運算子和過載函式
C++ 允許在同一作用域中的某個函式和運算子指定多個定義,分別稱為函式過載和運算子過載。
過載宣告是指一個與之前已經在該作用域內宣告過的函式或方法具有相同名稱的宣告,但是它們的引數列表和定義(實現)不相同。
當您呼叫一個過載函式或過載運算子時,編譯器通過把您所使用的引數型別與定義中的引數型別進行比較,決定選用最合適的定義。選擇最合適的過載函式或過載運算子的過程,稱為過載決策。
C++ 中的函式過載
在同一個作用域內,可以宣告幾個功能類似的同名函式,但是這些同名函式的形式引數(指引數的個數、型別或者順序)必須不同。您不能僅通過返回型別的不同來過載函式。
C++ 中的運算子過載
您可以重定義或過載大部分 C++ 內建的運算子。這樣,您就能使用自定義型別的運算子。
過載的運算子是帶有特殊名稱的函式,函式名是由關鍵字 operator 和其後要過載的運算子符號構成的。與其他函式一樣,過載運算子有一個返回型別和一個引數列表。
多型
多型按字面的意思就是多種形態。當類之間存在層次結構,並且類之間是通過繼承關聯時,就會用到多型。
C++ 多型意味著呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式。
導致錯誤輸出的原因是,呼叫函式 area() 被編譯器設定為基類中的版本,這就是所謂的靜態多型,或靜態連結 - 函式呼叫在程式執行前就準備好了。有時候這也被稱為早繫結,因為 area() 函式在程式編譯期間就已經設定好了。但現在,讓我們對程式稍作修改,在 Shape 類中,area() 的宣告前放置關鍵字 virtual。
虛擬函式
虛擬函式 是在基類中使用關鍵字 virtual 宣告的函式。在派生類中重新定義基類中定義的虛擬函式時,會告訴編譯器不要靜態連結到該函式。
我們想要的是在程式中任意點可以根據所呼叫的物件型別來選擇呼叫的函式,這種操作被稱為動態連結,或後期繫結。
純虛擬函式
您可能想要在基類中定義虛擬函式,以便在派生類中重新定義該函式更好地適用於物件,但是您在基類中又不能對虛擬函式給出有意義的實現,這個時候就會用到純虛擬函式。
1、純虛擬函式宣告如下: virtual void funtion1()=0; 純虛擬函式一定沒有定義,純虛擬函式用來規範派生類的行為,即介面。包含純虛擬函式的類是抽象類,抽象類不能定義例項,但可以宣告指向實現該抽象類的具體類的指標或引用。
2、虛擬函式宣告如下:virtual ReturnType FunctionName(Parameter) 虛擬函式必須實現,如果不實現,編譯器將報錯,錯誤提示為:
error LNK****: unresolved external symbol "public: virtual void __thiscall ClassName::virtualFunctionName(void)"
3、對於虛擬函式來說,父類和子類都有各自的版本。由多型方式呼叫的時候動態繫結。
4、實現了純虛擬函式的子類,該純虛擬函式在子類中就程式設計了虛擬函式,子類的子類即孫子類可以覆蓋該虛擬函式,由多型方式呼叫的時候動態繫結。
5、虛擬函式是C++中用於實現多型(polymorphism)的機制。核心理念就是通過基類訪問派生類定義的函式。
6、在有動態分配堆上記憶體的時候,解構函式必須是虛擬函式,但沒有必要是純虛的。
7、友元不是成員函式,只有成員函式才可以是虛擬的,因此友元不能是虛擬函式。但可以通過讓友元函式呼叫虛擬成員函式來解決友元的虛擬問題。
8、解構函式應當是虛擬函式,將呼叫相應物件型別的解構函式,因此,如果指標指向的是子類物件,將呼叫子類的解構函式,然後自動呼叫基類的解構函式。
檔案和流
到目前為止,我們已經使用了 iostream 標準庫,它提供了 cin 和 cout 方法分別用於從標準輸入讀取流和向標準輸出寫入流。
本教程介紹如何從檔案讀取流和向檔案寫入流。這就需要用到 C++ 中另一個標準庫 fstream,它定義了三個新的資料型別:
資料型別 | 描述 |
---|---|
ofstream | 該資料型別表示輸出檔案流,用於建立檔案並向檔案寫入資訊。 |
ifstream | 該資料型別表示輸入檔案流,用於從檔案讀取資訊。 |
fstream | 該資料型別通常表示檔案流,且同時具有 ofstream 和 ifstream 兩種功能,這意味著它可以建立檔案,向檔案寫入資訊,從檔案讀取資訊。 |
要在 C++ 中進行檔案處理,必須在 C++ 原始碼檔案中包含標頭檔案 <iostream> 和 <fstream>。
開啟檔案
在從檔案讀取資訊或者向檔案寫入資訊之前,必須先開啟檔案。ofstream 和 fstream 物件都可以用來開啟檔案進行寫操作,如果只需要開啟檔案進行讀操作,則使用 ifstream 物件。
下面是 open() 函式的標準語法,open() 函式是 fstream、ifstream 和 ofstream 物件的一個成員。
void open(const char *filename, ios::openmode mode);
在這裡,open() 成員函式的第一引數指定要開啟的檔案的名稱和位置,第二個引數定義檔案被開啟的模式。
模式標誌 | 描述 |
---|---|
ios::app | 追加模式。所有寫入都追加到檔案末尾。 |
ios::ate | 檔案開啟後定位到檔案末尾。 |
ios::in | 開啟檔案用於讀取。 |
ios::out | 開啟檔案用於寫入。 |
ios::trunc | 如果該檔案已經存在,其內容將在開啟檔案之前被截斷,即把檔案長度設為 0。 |
您可以把以上兩種或兩種以上的模式結合使用。例如,如果您想要以寫入模式開啟檔案,並希望截斷檔案,以防檔案已存在,那麼您可以使用下面的語法:
ofstream outfile;
outfile.open("file.dat", ios::out | ios::trunc );
動態記憶體
瞭解動態記憶體在 C++ 中是如何工作的是成為一名合格的 C++ 程式設計師必不可少的。C++ 程式中的記憶體分為兩個部分:
- 棧:在函式內部宣告的所有變數都將佔用棧記憶體。
- 堆:這是程式中未使用的記憶體,在程式執行時可用於動態分配記憶體。
很多時候,您無法提前預知需要多少記憶體來儲存某個定義變數中的特定資訊,所需記憶體的大小需要在執行時才能確定。
在 C++ 中,您可以使用特殊的運算子為給定型別的變數在執行時分配堆內的記憶體,這會返回所分配的空間地址。這種運算子即 new 運算子。
如果您不再需要動態分配的記憶體空間,可以使用 delete 運算子,刪除之前由 new 運算子分配的記憶體。
new 和 delete 運算子
下面是使用 new 運算子來為任意的資料型別動態分配記憶體的通用語法:
new data-type;
在這裡,data-type 可以是包括陣列在內的任意內建的資料型別,也可以是包括類或結構在內的使用者自定義的任何資料型別。讓我們先來看下內建的資料型別。例如,我們可以定義一個指向 double 型別的指標,然後請求記憶體,該記憶體在執行時被分配。我們可以按照下面的語句使用 new 運算子來完成這點:
double* pvalue = NULL; // 初始化為 null 的指標
pvalue = new double; // 為變數請求記憶體
malloc() 函式在 C 語言中就出現了,在 C++ 中仍然存在,但建議儘量不要使用 malloc() 函式。new 與 malloc() 函式相比,其主要的優點是,new 不只是分配了記憶體,它還建立了物件。
在任何時候,當您覺得某個已經動態分配記憶體的變數不再需要使用時,您可以使用 delete 操作符釋放它所佔用的記憶體,如下所示:
delete pvalue; // 釋放 pvalue 所指向的記憶體
模板
模板是泛型程式設計的基礎,泛型程式設計即以一種獨立於任何特定型別的方式編寫程式碼。
模板是建立泛型類或函式的藍圖或公式。庫容器,比如迭代器和演算法,都是泛型程式設計的例子,它們都使用了模板的概念。
每個容器都有一個單一的定義,比如 向量,我們可以定義許多不同型別的向量,比如 vector <int> 或 vector <string>。
您可以使用模板來定義函式和類。
函式模板
模板函式定義的一般形式如下所示:
template <class type> ret-type func-name(parameter list)
{
// 函式的主體
}
在這裡,type 是函式所使用的資料型別的佔位符名稱。這個名稱可以在函式定義中使用。
類模板
正如我們定義函式模板一樣,我們也可以定義類模板。泛型類宣告的一般形式如下所示:
template <class type> class class-name {}
在這裡,type 是佔位符型別名稱,可以在類被例項化的時候進行指定。您可以使用一個逗號分隔的列表來定義多個泛型資料型別。
前處理器
前處理器是一些指令,指示編譯器在實際編譯之前所需完成的預處理。
所有的前處理器指令都是以井號(#)開頭,只有空格字元可以出現在預處理指令之前。預處理指令不是 C++ 語句,所以它們不會以分號(;)結尾。
我們已經看到,之前所有的例項中都有 #include 指令。這個巨集用於把標頭檔案包含到原始檔中。
C++ 還支援很多預處理指令,比如 #include、#define、#if、#else、#line 等,讓我們一起看看這些重要指令。
#define 預處理
#define 預處理指令用於建立符號常量。該符號常量通常稱為巨集,指令的一般形式是:
#define macro-name replacement-text
當這一行程式碼出現在一個檔案中時,在該檔案中後續出現的所有巨集都將會在程式編譯之前被替換為 replacement-text。
引數巨集
您可以使用 #define 來定義一個帶有引數的巨集
#define MIN(a,b) (a<b ? a : b)
條件編譯
有幾個指令可以用來有選擇地對部分程式原始碼進行編譯。這個過程被稱為條件編譯。
條件前處理器的結構與 if 選擇結構很像。請看下面這段前處理器的程式碼:
#ifndef NULL
#define NULL 0
#endif
在c++語言中,#ifdef的作用域只是在單個檔案中。所以如果h檔案裡定義了全域性變數,即使採用#ifdef巨集定義,多個c檔案包含同一個h檔案還是會出現全域性變數重定義的錯誤。
使用#ifndef可以避免下面這種錯誤:如果在h檔案中定義了全域性變數,一個c檔案包含同一個h檔案多次,如果不加#ifndef巨集定義,會出現變數重複定義的錯誤;如果加了#ifndef,則不會出現這種錯誤。
# 和 ## 運算子
# 和 ## 預處理運算子在 C++ 和 ANSI/ISO C 中都是可用的。# 運算子會把 replacement-text 令牌轉換為用引號引起來的字串。
C++ 中的預定義巨集
C++ 提供了下表所示的一些預定義巨集:
巨集 | 描述 |
---|---|
__LINE__ | 這會在程式編譯時包含當前行號。 |
__FILE__ | 這會在程式編譯時包含當前檔名。 |
__DATE__ | 這會包含一個形式為 month/day/year 的字串,它表示把原始檔轉換為目的碼的日期。 |
__TIME__ | 這會包含一個形式為 hour:minute:second 的字串,它表示程式被編譯的時間。 |
標準庫中的順序容器包括:
(1)、vector:可變大小陣列。支援快速隨機訪問。在尾部之外的位置插入或刪除元素可能很慢。
(2)、deque:雙端佇列。支援快速隨機訪問。在頭尾位置插入/刪除速度很快。
(3)、list:雙向連結串列。只支援雙向順序訪問。在list中任何位置進行插入/刪除操作速度都很快。
(4)、forward_list:單向連結串列。只支援單向順序訪問。在連結串列任何位置進行插入/刪除操作速度都很快。
(5)、array:固定大小陣列。支援快速隨機訪問。不能新增或刪除元素。
(6)、string:與vector相似的容器,但專門用於儲存字元。隨機訪問快。在尾部插入/刪除速度快。