第4章 複合類型

来源:https://www.cnblogs.com/young520/archive/2022/07/24/16515192.html
-Advertisement-
Play Games

看《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_tchar32_t,可分別使用首碼 LuU 來創建這些類型的數組和這些類型的字元串常量。

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類型的原始字元串結合時,可使用 RLLR,前後順序無規定。

//原始字元串,使用預設定界符
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等效,都表示地址,記號*ptrhiggens等效,都表示值。註意:不要使用未經初始化的指針來訪問記憶體

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 創建動態數組

對於小型數據對象(如intdouble等),使用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[]進行釋放。

newdelete格式不匹配導致的後果是不確定的。

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 種管理數據記憶體的方式:自動存儲靜態存儲動態存儲(也稱自由存儲空間)。

  1. 自動存儲:在函數內部定義的常規變數使用自動存儲空間,被稱為自動變數,自動變數是一個局部變數,其作用域為包含它的代碼塊(代碼塊指包含在花括弧中的一段代碼)。自動變數通常存儲在棧(stack)中,執行代碼塊時,其中的變數將依次加入到棧中,離開代碼塊時,將按相反的順序自動釋放這些變數,即後進先出(LIFO),這種機制使得棧的記憶體通常是連續的。
  2. 靜態存儲:靜態變數將存在於程式的整個生命周期,而不是代碼塊內,使變數成為靜態變數的方式有兩種:在函數外面定義它、聲明變數時加上關鍵字static
  3. 動態存儲newdelete管理一個記憶體池,這在 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,指針變數ptrptrc的區別如下:

  • 變數ptr的類型是short*,存儲的是一個 2 位元組記憶體塊的地址,它指向的對象是short類型,記號*ptrtacos[0]等價。
  • 變數ptrc的類型是short(*)[10],存儲的是一個 20 位元組記憶體塊的地址,它指向的對象是包含 10 個元素的short數組,記號*ptrctacos等價。
//聲明並初始化數組
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 數組的替代品

模板類 vectorarray 可以用做數組的替代品。

4.10.1 模板類 vector

模板類vector是一種動態數組,它自動使用newdelete來管理記憶體,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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 樹形數據的一些相關處理方法 以下方法待補充優化😁... // 用於測試的樹形數據 const treeData = [ { id: '1', name: '測試1', pId: '0', children: [ { id: '11', name: '測試11', pId: '1', childre ...
  • 1.常用標簽 1.基礎標簽 <!DOCTYPE> 定義文檔類型。 <html> 定義 HTML 文檔。 <head> 定義關於文檔的信息。 <title> 定義文檔的標題。 <body> 定義文檔的主體。 <h1> to <h6> 定義 HTML 標題。 <p> 定義段落。 <br> 定義簡單的折行 ...
  • JavaScript進階內容——jQuery 我們在前面的文章中已經掌握了JavaScript的全部內容,現在讓我們瞭解一下JavaScript庫 這篇文章主要是為了為大家大致講解JavaScript庫以及使用方法,本篇不會完全講解jQuery的全部語法 如果希望完全掌握,可以參考網站jQuery ...
  • 概述 ​ SpringBoot中集成官方的第三方組件是通過在POM文件中添加組件的starter的Maven依賴來完成的。添加相關的Maven依賴之後,會引入具體的jar包,在SpringBoot啟動的時候會根據預設自動裝配的配置類的註入條件判斷是否註入該自動配置類到Spring容器中。自動配置類中 ...
  • 貪心演算法 一、 演算法概述 1、 簡介 貪心演算法,又稱貪婪演算法,是一種在每一步選擇中都採取在當前狀態下最好或最優(即最有利)的選擇,從而希望導致結果是最好或最優的演算法。[1]比如在旅行推銷員問題中,如果旅行員每次都選擇最近的城市,那這就是一種貪心演算法。 貪心演算法在有最優子結構的問題中尤為有效。最優子結 ...
  • PHP變數規則 變數以$符號開頭,其後是變數的名稱; 變數名稱必須已字母或者下劃線開頭; 變數名稱不能以數字開頭; 變數名稱只能包含字母數字字元和下劃線(A-z、0-9以及 _ ); 變數名稱對大小寫敏感($y和$Y是兩個不同的變數) 註釋:PHP變數名稱對大小寫敏感 創建PHP變數 PHP變數沒有 ...
  • java變數類型 1、局部變數 概念:類的方法中的變數 2、實例變數 概念:定義在類裡面,方法體、構造方法和語句塊外,且不被static修飾的變數,用於描述一個類中包含哪些數據 3、類變數 概念:獨立於方法之外的變數,有static修飾 public class able{ static int a ...
  • 基於SSM框架開發的CRM客戶管理系統,適合剛學完SSM的同學,幫助夯實javase到ssm之間的知識,提升學生的邏輯思維,也瞭解到企業軟體開發的流程及代碼編寫規範。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...