看《C++ Primer Plus》時整理的學習筆記,部分內容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯,人民郵電出版社。只做學習記錄用途。 ...
說明
看《C++ Primer Plus》時整理的學習筆記,部分內容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯,人民郵電出版社。只做學習記錄用途。
目錄複合類型是基於基本整型和浮點類型創建的,影響最為深遠的複合類型是類,除類外,C++ 還支持幾種更普遍的複合類型,它們都來自 C 語言,例如:數組、結構、指針等。
4.1 數組
數組能夠存儲多個同類型的值,電腦在記憶體中依次存儲數組的各個元素。數組聲明應包含三點:元素類型、數組名、數組中的元素總數,如下:
//數組聲明
typeName arrayName[arraySize];
其中的表達式 arraySize
不能是變數,它必須是整型常量或const
值,也可以是常量表達式(如8*sizeof(int)
),即表達式中所有值在編譯時都是已知的。C++ 標準模板庫(STL)提供了一種數組替代品,模板類vector
,C++11 新增了模板類array
,這些替代品比內置複合類型數組更複雜、更靈活,這將在後面章節介紹。聲明數組後,數組中的元素總數也可以使用以下方式計算出來:
//數組中的元素總數
arraySize = sizeof(arrayName)/sizeof(typeName);
4.1.1 數組訪問
使用[index]
訪問索引為 index
的數組元素,索引從 0 開始,最後一個元素的索引比數組長度小 1。
//聲明長度為12的short數組
short months[12];
//訪問它的第1個元素
months[0];
//訪問它的最後一個元素
months[11];
註意:編譯器不會檢查索引是否有效,例如可以訪問months[101]
或者給它賦值,編譯器並不會指出錯誤(有時會給個警告,不是以錯誤的形式指出),但是程式運行後,這種賦值可能破壞數據或代碼,也可能導致程式異常終止,因此需人為確保程式使用有效的索引值。
4.1.2 數組初始化及賦值
只有在定義數組時才能使用初始化,也不能將一個數組賦給另一個數組,但可以逐元素賦值。初始化數組時,提供的值可以少於數組的元素數目,剩餘的元素編譯器會自動初始化為 0。
//定義時初始化全部元素
int arr1[4] = {3, 6, 8, 10};
//定義時只指定第一個值,剩餘元素初始化為0
int arr2[4] = {3};
//定義時全部初始化為0
int arr3[4] = {0};
//定義時指定全部元素,讓編譯器自動統計元素個數
int arr4[] = {3, 6, 8, 10};
//C++11初始化方式:可省略等號
int arr5[4]{3, 6, 8, 10};
//C++11初始化方式:預設初始化為0
int arr6[4] = {};
int arr7[4]{};
註意:使用大括弧進行列表初始化時禁止縮窄轉換。
4.2 字元串
C++ 處理字元串的方式有兩種:第一種來自 C 語言,常被稱為 C-風格字元串,第二種是下一節將介紹的string
類。
C-風格字元串將字元串存儲在char
數組中,並以空字元結尾。空字元被寫作\0
,其 ASCII 碼為 0,用來標記字元串的結尾。很多處理字元串的函數(如cout
輸出字元串)都逐個處理字元串中的字元,直到到達空字元為止,無論是否超過了char
數組的實際大小。
4.2.1 C - 風格字元串的初始化及拼接
常用的初始化方法如下:
//逐個字元初始化,需人為加空字元
char cat[8] = {'f','a','t','e','s','s','a','\0'};
//使用字元串常量進行初始化,自動添加空字元
char bird[11] = "Mr. Cheeps";
//字元數組剩餘元素自動初始化為空字元
char boss[8] = "Bozo";
//讓編譯器計算字元數組長度,長度為8
char fish[] = "Bubbles";
//C++11字元串初始化
char fish[] = {"Bubbles"};
char fish[]{"Bubbles"};
註意:字元串常量(使用雙引號)不能與字元常量(使用單引號)互換。在 ASCII 系統上,字元常量'S'
只是 83 的另一種寫法,但"S"
不是字元常量,它表示的是兩個字元(字元S
和字元\0
)組成的字元串,更糟糕的是,"S"
實際上表示的是字元串所在的記憶體地址。
C++ 允許將兩個用引號括起來的字元串拼接成一個,任何兩個由空白分隔的字元串常量都將自動拼接成一個。下麵所有輸出語句都是等效的:
//輸出
cout << "Medical cotton swab.\n";
//使用空格分隔的拼接
cout << "Medical cot" "ton swab.\n";
//使用換行分隔的拼接
cout << "Medical cot"
"ton swab.\n";
註意:拼接時不會在被連接的字元串之間添加空格。
4.2.2 C - 風格字元串的使用
sizeof(arrayName)
指出整個字元數組的長度。strlen(arrayName)
指出字元數組中字元串的長度,且不把空字元計算在內。字元數組的長度不能短於strlen(arrayName)+1
。包含頭文件<cstring>
後,可使用其中的字元串處理函數,常用的有,使用strcpy()
將字元串複製到字元數組中,使用strcat()
將字元串附加到字元數組末尾。還有對應的strncat()
和strncpy()
,它們接受指出目標數組最大允許長度的第三個參數。
//將字元串arrayName2複製到數組arrayName1中
strcpy(arrayName1,arrayName2);
//將字元串arrayName2添加到數組arrayName1末尾
strcat(arrayName1,arrayName2);
4.2.3 C - 風格字元串的輸入與輸出
C - 風格字元串的輸出比較簡單,按以下方式使用即可,它會在遇到第一個空字元時停止輸出:
//C-風格字元串的輸出
cout << arrayName;
C - 風格字元串的輸入比較複雜,需根據不同需求使用不同方法:
// cin使用空白做結束標記,只讀取一個單詞,並丟棄空白
cin >> arrayName;
//getline()函數使用回車輸入的換行符做結束標記,讀取整行,並丟棄換行符
cin.getline(arrayName,arraySiz);
//get()函數與getline()相似,但不讀取且不丟棄換行符,換行符仍在輸入隊列中
cin.get(arrayName,arraySiz);
//不帶任何參數的get()函數讀取下一個字元(包括換行符)
cin.get();
//讀取一個字元到字元變數中(包括換行符)
cin.get(ch);
//讀取整行並丟棄換行符,與getline()函數等效
cin.get(arrayName,arraySiz).get();
選用get()
而不用getline()
的原因主要有兩點:老式實現沒有getline()
、get()
檢查錯誤更簡單。可通過使用get()
檢查下一字元是否是換行符來判斷是否讀取了整行。
註意:get()
函數讀取空行後將設置失效位,接下來的輸入將被阻斷,此時可使用cin.clear()
來恢復輸入。若輸入行的字元數比指定的多,則getline()
和get()
將把餘下的字元留在輸入隊列中,getline()
還會設置失效位,並關閉後面的輸入。使用cin
獲取數字輸入後,回車鍵生成的換行符會留在輸入隊列中,可能會影響獲取後面的輸入。
4.3 string 類簡介
C++98 標準添加了 string
類,它提供了將字元串作為一種數據類型的表示方法,使用起來要比數組簡單,例如:不用擔心字元串會導致數組越界,可以直接使用賦值運算符而不是函數strcpy()
。使用string
類必須包含頭文件<string>
並指出名稱空間std
。
4.3.1 string 對象的初始化
支持以下幾種方式:
//創建空字元串
string str1;
//創建並初始化
string str2 = "panther";
//C++11方式
string str3 = {"The Bread Bowl"};
string str4{"Hank's Fine Eats"};
- 可以使用 C- 風格字元串來初始化
string
對象。 - 可以使用
cin
來將鍵盤輸入存儲到string
對象中。 - 可以使用
cout
來顯示string
對象。 - 可以使用數組表示法來訪問存儲在
string
對象中的字元。 string
類設計讓程式能夠自動處理string
對象的大小。
4.3.2 string 對象的賦值、拼接和附加
可以將一個string
對象使用運算符 = 賦值給另一個string
對象,但 C - 風格字元串不能這樣做。可以使用運算符 + 將兩個string
對象拼接起來,還可以使用運算符 += 將字元串附加到string
對象的末尾。
//賦值
string str1;
string str2 = "panther";
str1 = str2;
//拼接
string str3 = str1 + str2;
//附加
str1 += str2;
4.3.3 string 對象的其他操作
可使用size()
獲得string
對象的字元數:
//獲得string對象的字元數
string str1;
int len = str1.size();
4.3.4 string 對象的輸入與輸出
可以使用cin
和運算符>>
來將輸入存儲到string
對象中,使用cout
和運算符<<
來顯示string
對象,其句法與處理 C-風格字元串相同。但每次讀取一行而不是一個單詞時,使用的句法不同,此時getline()
函數並不是istream
的類方法,而是string
類的一個友元函數。
//讀取整行到string對象
string str;
getline(cin, str);
4.3.5 其他形式的字元串常量
對於類型wchar_t
以及 C++11 新增的類型char16_t
和char32_t
,可分別使用首碼 L、u 和 U 來創建這些類型的數組和這些類型的字元串常量。
wchar_t title[] = L"Chief Astrogator";
char16_t name[] = u"Felonia Ripova";
char32_t cars[] = U"Humber Super Snipe";
C++11 還支持 Unicode 字元編碼方案 UTF-8,字元可能存儲為 1~4 個八位組,可使用首碼 u8 來表示這種類型的字元串常量。
C++11 還新增了一種類型:原始(raw)字元串。在原始字元串中,\n
不表示換行符,而表示兩個常規字元\
和n
。原始字元串使用 "( 和 )" 作定界符,並使用首碼 R 來標識原始字元串。原始字元串語法允許自定義定界符:在開頭 " 和 ( 之間添加自定義字元後,也必須在結尾 ) 和 " 之間添加一樣的字元,添加的字元只能是基本字元,空格、左括弧、右括弧、斜杠和控制字元(如製表符、換行符)除外。還可將首碼 R 與其他字元串首碼結合使用,例如與wchar_t
類型的原始字元串結合時,可使用 RL 或 LR,前後順序無規定。
//原始字元串,使用預設定界符
cout << R"(Jim is a "pig")";
//原始字元串,使用自定義定界符
cout << R"+*(Jim is a "pig")+*";
4.4 結構簡介
結構是一種比數組更靈活的數據格式,同一個數組所有元素的類型必須相同,但同一個結構可以存儲多種類型的元素。創建結構包括兩步:首先,定義結構描述,它描述並標記了能夠存儲在結構中的各種數據類型;然後按描述創建結構變數(結構數據對象)。
4.4.1 定義和使用結構
使用關鍵字struct
定義結構,如下定義了一個新類型,類型名稱為inflatable
,大括弧中包含的是結構存儲的數據類型列表,其中每一條都是一條聲明語句。
//定義結構體,註意尾部的分號
struct inflatable
{
char name[20];
float volume;
double price;
};
使用結構時,可使用常規聲明語句創建這種類型的變數,並使用成員運算符 (.) 來訪問結構的各個成員。
//C語言風格創建結構變數,不省略struct
struct inflatable goose;
//C++風格創建結構變數,可省略struct
inflatable goose;
//訪問結構成員變數
string str = goose.name;
char na = goose.name[1];
float vo = goose.volume;
double pr = goose.price;
4.4.2 結構初始化
結構定義可以放在函數外面,也可以放在函數裡面,這將在第 9 章做更詳細的介紹,C++ 不提倡使用外部變數,但提倡使用外部結構聲明或在外部聲明符號常量。結構常用的初始化方式如下:
//常規初始化
inflatable guest =
{
"Glorious Gloria",
1.88,
29.99
};
//可寫在同一行
inflatable duck = {"Daphne",0.12,9.98};
//C++11風格,不允許縮窄轉換
inflatable duck{"Daphne",0.12,9.98};
//C++11風格,成員變數全設為0或\0
inflatable duck{};
4.4.3 結構賦值及其他屬性
可以使用賦值運算符 = 將結構賦給另一個同類型的結構,這樣結構中每個成員都將被設置為另一個結構中相應成員的值,即使成員是數組,這種賦值被稱為成員賦值。
//結構體賦值
inflatable choice;
inflatable duck = {"Daphne",0.12,9.98};
choice = duck;
可以同時完成定義結構和創建結構變數的工作,只需將變數名放在結束括弧的後面,但是一般將結構定義和變數聲明分開,使得程式更易於閱讀和理解。
//定義並創建結構變數
struct perks
{
int key_number;
char car[12];
} mr_smith, ms_jones;
//定義並創建結構變數並初始化
struct perks
{
int key_number;
char car[12];
} mr_glitz =
{
7,
"Packard"
};
可以聲明沒有名稱的結構類型,方法是省略名稱,同時創建這種類型的變數,但這種類型沒有名稱,只可在定義時創建變數,以後再無法創建這種類型的變數。
//聲明沒有名稱的結構類型
struct
{
int x;
int y;
} position;
相比於 C 結構,C++ 結構有更多的特性,它除了有成員變數外,還可以有成員函數,但這些高級特性一般用於類中,將在第 10 章介紹。
4.4.4 結構數組
可以創建元素為結構的數組,方法和創建基本類型數組完全相同。
//創建並初始化結構數組
inflatable guests[2] =
{
{"Bambi", 0.5, 21.99},
{"Godzilla", 2000, 565.99}
};
//訪問數組中指定結構的成員
string str = guests[index].name;
char na = guests[index].name[1];
float vo = guests[index].volume;
double pr = guests[index].price;
4.4.5 結構中的位欄位
與 C 語言一樣,C++ 也允許指定占用特定位數的結構成員,這常用於低級編程中,創建與某個硬體設備上的寄存器對應的數據結構。欄位的類型應為整型或枚舉,接下來是冒號,冒號後面的數字指定了使用的位數。可以使用沒有名稱的欄位來提供間距。每個成員都被稱為位欄位。
//使用位欄位定義結構
struct torgle_register
{
unsigned int SN : 4;
unsigned int : 4;
bool goodIn : 1;
bool goodTorgle : 1;
};
//創建並初始化
torgle_register tr = {14,true,false};
//訪問
tr.SN;
tr.goodIn;
tr.goodTorgle;
上述結構中的變數SN
記憶體量寬度為 4 位,接著有 4 位寬未使用到的記憶體,變數goodIn
的記憶體量寬度為 1 位,變數goodTorgle
的記憶體量寬度為 1 位。64 位 Windows 系統上對結構torgle_register
使用運算符sizeof()
得到的結果是該結構記憶體量寬度為 8 位元組,具體原因可參考記憶體對齊的相關資料。
4.5 共用體
共用體能夠存儲不同的數據類型,但某個時刻只能存儲其中的一種類型,其用途之一是:當數據項使用兩種或更多格式(但不會同時使用時),可節省空間。共用體的語法與結構相似,但含義不同。
//定義共用體
union one4all
{
int int_val;
long long_val;
double double_val;
};
//創建共用體變數
one4all pail;
//使用共用體存儲int值
pail.int_val = 15;
cout << pail.int_val;
//使用共用體存儲double值,此時int值會丟失
pail.double_val = 1.38;
cout << pail.double_val;
上述例子共用體變數pail
有時是int
變數,有時是double
變數,它每次只能存儲一個值,因此共用體的記憶體量寬度為其最寬成員類型的記憶體量寬度,更深一步地,可能還需要考慮記憶體對齊。最好另使用一個標誌變數來標記共用體當前的數據類型,以防止取出來的值不正確。匿名共用體沒有名稱,其成員將成為位於相同地址處的變數。
//使用包含匿名共用體的結構
struct widget
{
char brand[20];
int type;
union
{
long id_num;
char id_char[20];
};
} prize;
//訪問結構中的共用體成員
if(prize.type == 1)
cin >> prize.id_num;
else
cin >> prize.id_char;
4.6 枚舉
C++ 的enum
工具提供了另一種創建符號常量的方式,這種方式可以代替const
,常用於定義switch
語句(後面第 6 章)中使用的符號常量。枚舉的規則相當嚴格,它只定義了賦值運算符,沒有定義算術運算符,在不進行強制類型轉換的情況下,只能將定義枚舉時使用的枚舉量賦給這種枚舉的變數。
4.6.1 枚舉的定義
第一個枚舉量的值預設為 0,後面的枚舉量預設比其前面的枚舉量大 1,可以人為指定枚舉量的值,早期的 C++ 只能將int
的值賦給枚舉,現在的 C++ 可以使用long
甚至long long
類型的值。
//顯式設置全部枚舉量的值
enum bits{one = 1, two = 2, four = 4, eight = 8};
//顯式設置部分枚舉量的值
enum bigstep{first, second = 100, third};
//可以創建多個值相同的枚舉量
enum bigsame{zero, null = 0, one, numro_uno = 1};
//若只使用枚舉常量,而不使用枚舉變數,可省略名稱
enum {red, orange, yellow, green, blue, violet, indigo, ultraviolet};
//使用預設方式設置枚舉量
enum spectrum{red, orange, yellow, green, blue, violet, indigo, ultraviolet};
4.6.2 枚舉的取值範圍
每個枚舉都有取值範圍,通過強制類型轉換,可以將取值範圍內中的任何整數值賦給枚舉變數,即使這個值不是定義中的枚舉值。
-
枚舉取值範圍上限的計算方式如下:找到大於最大枚舉量的、最小的 2 的冪,將它減去 1 即可。
-
枚舉取值範圍下限的計算方式如下:找到最小枚舉量,若它不小於 0,則取值範圍的下限為 0,否則,採用與上限相同的計算方式,但加上負號。
例如:最大枚舉量為 101,則枚舉取值範圍的上限為\(2^7-1=127\),最小枚舉量為 -6,則枚舉取值範圍的下限為\(-(2^3-1)=-7\)。
4.6.3 枚舉變數的賦值及其他屬性
枚舉變數的聲明與基本類型相似,賦值時最好使用枚舉定義中的值,將一個不適當的值通過強制類型轉換賦給枚舉變數的結果是不確定的。枚舉量在表達式中進行算術運算時將被提升為整型,通常是int
,但整型不能自動轉化為枚舉類型,需使用強制類型轉換。
//聲明枚舉變數
spectrum band;
//枚舉變數的賦值
band = blue;
//取值範圍內的整數需進行強制類型轉換
band = spectrum(3);
枚舉類型的記憶體量寬度由編譯器決定,對於取值範圍較小的枚舉,使用一個位元組或更少的空間,而對於包含long
類型的枚舉,則使用 4 個位元組甚至 8 個位元組,通常情況下,枚舉類型記憶體量寬度為 4 個位元組。
4.7 指針
指針是一種特殊的數據類型,其存儲的是值的地址,而不是值本身,對常規變數應用地址運算符 & 就可以獲得它的位置。指針變數的記憶體量寬度與所指數據類型無關,只與電腦系統有關,32 位系統上為 4 位元組,64 位系統上為 8 位元組,使用 cout
顯示值的地址時,通常採用十六進位表示法(有些實現可能採用十進位),例如 0x0065fd40(32 位系統)、0x000000A9186FFC14(64 位系統),使用cout
顯示字元數組的地址時,需要對其做強制類型轉換(int *)
,否則cout
將輸出字元數組的內容(字元串)。面向對象編程(OOP)與傳統的過程性編程的區別在於:OOP 強調的是在運行階段(而不是編譯階段)進行決策。指針類型為OOP 提供了很大的靈活性,例如,考慮為數組分配記憶體的情況,若每次運行程式時實際使用到的數組長度是不固定的,比如有時需要 20 長度,有時需要 80 長度,最長時需要 200 長度,傳統方法是在創建數組時就為數組指定最大長度 200,這個長度在編譯階段就是已知且固定不變的,這樣,程式在大多數情況下都浪費了記憶體。為了能在每次運行程式時根據需求創建不同大小的數組,需要將創建數組的時機推遲到運行階段,可使用關鍵字new
在程式運行階段請求正確數量的記憶體,然後使用指針來跟蹤新分配的記憶體位置。
4.7.1 聲明和初始化指針
指針名錶示的是地址,對指針使用間接值運算符(也稱解除引用運算符)* 可以得到該地址處存儲的值(C++ 根據上下文來確定所指的是乘法還是解除引用)。和數組一樣,指針都是基於其他類型的,因此指針聲明時必須指定指針指向的數據類型,聲明時運算符 * 兩邊的空格是可選的。
//聲明指向int的指針,C風格
int *ptr;
//聲明指向int的指針,C++風格
int* ptr;
//其他聲明格式
int*ptr;
int * ptr;
//同時聲明兩個
int *ptr1, *ptr2;
在 C++ 中創建指針時,電腦只分配指針變數的記憶體(通常是 4 位元組或 8 位元組),不分配指針所指數據的記憶體。若在聲明時沒有對指針變數進行初始化,則指針所指向的地方是不確定的,最好在聲明時就對指針變數初始化。
//初始化指針指向指定變數
int higgens = 5;
int* ptr = &higgens;
//初始化指針為空指針,C風格
int* ptr = NULL;
int* ptr = 0;
//初始化指針為空指針,C++風格
int* ptr = nullptr;
//初始化為指定地址值,需要強制類型轉換
int* ptr = (int*) 0xB8000000;
指針ptr
指向變數higgens
後,記號ptr
與&higgens
等效,都表示地址,記號*ptr
與higgens
等效,都表示值。註意:不要使用未經初始化的指針來訪問記憶體。
4.7.2 使用 new 來分配記憶體
變數是在編譯時分配的有名稱的記憶體,指針只是為可以通過名稱直接訪問的記憶體提供了一個別名,指針的真正用武之地是在運行階段分配未命名的記憶體以存儲值。在 C 語言中,可以用庫函數 malloc()
在運行階段分配記憶體,用free()
來釋放記憶體。在 C++ 中仍可以這樣做,但它提供了一個更好的方式,使用運算符new
來分配記憶體,運算符delete
來釋放記憶體。成功分配記憶體後,new
返回該記憶體的首地址,當沒有足夠的記憶體滿足new
的請求時,new
通常會拋出一個異常,老式實現的new
會返回 0。為一個數據對象分配記憶體的通用格式如下:
typeName * pointer_name = new typeName;
分配出來的記憶體只能只能通過指針pointer_name
進行訪問。需要註意的是,常規變數聲明分配的記憶體塊一般是在稱為棧的記憶體區中,而new
從被稱為堆或自由存儲區的記憶體區域分配記憶體。
4.7.3 使用 delete 來釋放記憶體
使用完記憶體後,應該用運算符delete
來釋放記憶體,將記憶體歸還給記憶體池。使用delete
時,後面需加上指向記憶體塊的指針(這些記憶體塊最初是用new
分配的),這個操作只會釋放指針所指向的記憶體,而不會刪除指針,後面可以讓指針指向別的數據。有以下幾點需註意:
delete
只能用來釋放new
分配的記憶體。- 對空指針使用
delete
是安全的。 - 不要嘗試釋放已經釋放的記憶體塊,這樣做的結果是不確定的。
- 最好不要創建兩個指向同一個由
new
分配的記憶體塊的指針,這會增加delete
兩次的可能性。 new
出來的記憶體使用結束後,必須配對使用delete
釋放,不然會產生記憶體泄漏,造成被分配的記憶體再也無法使用了。
//配對使用new和delete
typeName * pointer_name = new typeName;
...
delete pointer_name;
//只能釋放一次
int* pa = new int;
int* pb = pa;
delete pb;
4.7.4 使用 new 創建動態數組
對於小型數據對象(如int
、double
等),使用new
通常使程式變得複雜,一般不用;對於大型數據對象(如數組、結構等),可根據實際需求來決定是否使用new
。在編譯階段指定數組長度並給其分配記憶體被稱為靜態聯編,在運行階段自由選擇數組長度並給其分配記憶體被稱為動態聯編,動態聯編創建的數組被稱為動態數組,動態數組可使用new
來創建,但無法用sizeof()
運算符獲得動態數組的總位元組數。
//分配動態數組記憶體的通用格式
typeName * pointer_name = new typeName[arraySize];
//訪問動態數組元素
pointer_name[0];
pointer_name[1];
...
pointer_name[arraySize-1];
//釋放動態數組記憶體
delete[] pointer_name;
delete
使用的註意事項增加兩點:
- 使用
new
分配的記憶體,必須配對使用delete
進行釋放。 - 使用
new[
]分配的記憶體,必須配對使用delete[]
進行釋放。
new
和delete
格式不匹配導致的後果是不確定的。
4.7.5 使用 new 創建動態結構
需要在程式運行時為結構分配空間,可以使用new
運算符來完成,C++ 的類也可以仿照結構來使用new
運算符。創建動態結構的語法與前面相似,但訪問結構成員需要使用成員運算符 -> ,該運算符由連字元和大於號組成,可用於指向結構的指針,就像點運算符用於結構名一樣。
//定義結構體
struct inflatable
{
char name[20];
float volume;
double price;
};
//創建動態結構
inflatable * ptr = new inflatable;
//訪問結構成員,方式一
ptr->volume = 1.66;
//訪問結構成員,方式二
(*ptr).volume = 1.66;
//釋放記憶體
delete ptr;
4.7.6 記憶體管理
C++ 有 3 種管理數據記憶體的方式:自動存儲、靜態存儲和動態存儲(也稱自由存儲空間或堆)。
- 自動存儲:在函數內部定義的常規變數使用自動存儲空間,被稱為自動變數,自動變數是一個局部變數,其作用域為包含它的代碼塊(代碼塊指包含在花括弧中的一段代碼)。自動變數通常存儲在棧(stack)中,執行代碼塊時,其中的變數將依次加入到棧中,離開代碼塊時,將按相反的順序自動釋放這些變數,即後進先出(LIFO),這種機制使得棧的記憶體通常是連續的。
- 靜態存儲:靜態變數將存在於程式的整個生命周期,而不是代碼塊內,使變數成為靜態變數的方式有兩種:在函數外面定義它、聲明變數時加上關鍵字
static
。 - 動態存儲:
new
和delete
管理一個記憶體池,這在 C++ 中被稱為自由存儲空間(free store)或堆(heap)。該記憶體池同自動存儲、靜態存儲是分開的,數據的生命周期完全由程式員控制,可以在一個函數中分配記憶體,而在另一個函數中釋放它,這使得自由存儲空間通常是不連續的。
C++11 新增了第 4 種方式:線程存儲,這將在第 9 章學習。
4.8 指針算術和數組名
4.8.1 指針算術
C++ 允許將指針和整數相加,指針加 1 的結果等於原來的地址值加上指向的對象占用的總位元組數;還可以將指針相減,獲得兩個指針的差,最後得到一個整數,指針減法僅當兩個指針指向同一個數組時,運算結果才有意義。如下示例中假設short
寬 2 位元組,數組首地址為 0x0028ccf0:
//聲明並初始化數組
short tacos[10] = {5,2,8,4,1,2,2,4,6,8};
//聲明並初始化指針為 0x0028ccf0
short* ptr = tacos;
short* ptrb = tacos;
//指針和整數相加
ptr = ptr + 1; //結果為0x0028ccf2
//指針和整數相加
ptr = ptr + 3; //結果為0x0028ccf8
//指針和指針相減
int dif = ptr - ptrb; //結果為4
4.8.2 數組名
C++ 將數組名解釋為其第一個元素的地址,而對數組名應用地址運算符時,得到的是整個數組的地址。從數字上而言,這兩個地址相同,無需區分;但從概念上特別是需要運用指針算術時,需要明白兩者的區別。如下示例中假設short
寬 2 位元組,系統為 32 位,數組首地址為0x0028ccf0,指針變數ptr
和ptrc
的區別如下:
- 變數
ptr
的類型是short*
,存儲的是一個 2 位元組記憶體塊的地址,它指向的對象是short
類型,記號*ptr
與tacos[0]
等價。 - 變數
ptrc
的類型是short(*)[10]
,存儲的是一個 20 位元組記憶體塊的地址,它指向的對象是包含 10 個元素的short
數組,記號*ptrc
與tacos
等價。
//聲明並初始化數組
short tacos[10] = {5,2,8,4,1,2,2,4,6,8};
//聲明並初始化指針
short *ptr = tacos;
short (*ptrc)[10] = &tacos;
//訪問數組第三個元素
cout << tacos[2]; //結果為8
cout << *(tacos+2); //結果為8
cout << ptr[2]; //結果為8
cout << *(ptr+2); //結果為8
cout << (*ptrc)[2]; //結果為8
cout << *(*ptrc+2); //結果為8
//應用sizeof()獲得記憶體量大小
cout << sizeof(tacos); //結果為20
cout << sizeof(ptr); //結果為4
cout << sizeof(ptrc); //結果為4
上述例子中數組名tacos
和指針變數ptrc
以及ptr
的區別如下:
- 數組名
tacos
是常量,值不能修改;ptrc
以及ptr
是變數,值可以修改。 - 對數組名
tacos
使用sizeof()
得到的是整個數組的記憶體量寬度;對ptrc
以及ptr
使用sizeof()
得到的是指針變數的記憶體量寬度。
實際上,上述訪問方式中,C++ 編譯器會自動將 tacos[2]
轉換為*(tacos+2)
,將ptr[2]
轉換為*(ptr+2)
,將(*ptrc)[2]
轉換為*(*ptrc+2)
,前者稱為數組表示法,後者稱為指針表示法。
4.8.3 指針和字元串
在多數 C++ 表達式中,char
數組名、char
指針以及用雙引號括起來的字元串常量都被解釋為字元串第一個字元的地址。一般來說,編譯器在記憶體內會留出一些空間,以存儲程式源代碼中所有用雙引號括起來的字元串,並將每個被存儲的字元串與其地址關聯起來。C++ 不能保證字元串常量被唯一地存儲,例如:若在程式中多次使用了字元串常量"tomato"
,則編譯器將可能存儲該字元串的多個副本,也可能只存儲一個副本。
4.9 類型組合
可以用各種方式組合複合類型以及基本類型。比如結構中可以含有數組、指針,可以創建結構數組、指針數組,可以創建指向指針數組的指針。
//創建基本類型變數
int a1 = 110;
int a2 = 120;
int a3 = 130;
int a4 = 140;
//創建指針數組
int* arr[4] = {&a1, &a2, &a3, &a4};
//創建指向指針數組(第一個元素)的指針
int** ptr = arr;
//創建指向(整個)指針數組的指針
int* (*ptrc)[4] = &arr;
//訪問指針數組第三個元素所指的值
cout << *arr[2]; //結果為130
cout << **(arr+2); //結果為130
cout << *ptr[2]; //結果為130
cout << **(ptr+2); //結果為130
cout << *(*ptrc)[2]; //結果為130
cout << **(*ptrc+2); //結果為130
//應用sizeof()獲得記憶體量大小(32位系統)
cout << sizeof(arr);//結果為16
cout << sizeof(ptr);//結果為4
cout << sizeof(ptrc);//結果為4
這個例子看似複雜,實則與前一個short
數組例子的原理一樣,只是多了一個解除引用運算符 *。
4.10 數組的替代品
模板類 vector 和 array 可以用做數組的替代品。
4.10.1 模板類 vector
模板類vector
是一種動態數組,它自動使用new
和delete
來管理記憶體,vector
對象存在於自由存儲區或堆中,使用時需#include<vector>
並指出名稱空間std
。
//使用模板vector創建動態數組
vector<typeName> arrayName(arraySize);
其中的表達式 arraySize
可以是整型常量,也可以是整型變數。
4.10.2 模板類 array(C++11)
模板類vector
由於使用了動態記憶體分配,其效率會比使用傳統數組低。C++11 新增了數組長度固定的模板類array
,它的對象存在於棧中(靜態聯編),使用時需#include<array>
並指出名稱空間std
。
//使用模板array創建定長數組
array<typeName,arraySize> arrayName;
其中的表達式 arraySize
只能是整型常量。
4.10.3 比較數組、vector 對象和 array 對象
見以下例子:
//聲明並初始化傳統數組
double arr_C[4] = {1.2, 2.4, 3.6, 4.8};
//聲明並初始化vector對象:C++98不支持{}初始化
vector<double> arr_vector(4);
arr_vector[0] = 1.2;
arr_vector[1] = 2.4;
arr_vector[2] = 3.6;
arr_vector[3] = 4.8;
//C++11支持{}初始化
vector<double> arr_vector(4) = {1.2, 2.4, 3.6, 4.8};
//聲明並初始化array對象
array<double,4> arr_array = {1.2, 2.4, 3.6, 4.8};
//訪問數組第三個元素
cout << arr_C[2];
cout << arr_vector[2];
cout << arr_array[2];
cout << arr_vector.at(2);
cout << arr_array.at(2);
訪問數組元素時,中括弧表示法和成員函數at()
的差別在於:
- 成員函數
at()
可捕捉非法索引併在給定非法索引時中斷程式,額外代價是運行時間會更長。 - 中括弧表示法不檢查越界錯誤,可以訪問如
[-2]
、[10000000]
不在數組大小範圍內的值。
本文來自博客園,作者:木三百川,轉載請註明原文鏈接:https://www.cnblogs.com/young520/p/16515192.html