主頁 > 後端開發 > 第4章 復合型別

第4章 復合型別

2022-07-25 08:30:24 後端開發

說明

看《C++ Primer Plus》時整理的學習筆記,部分內容完全摘抄自《C++ Primer Plus》(第6版)中文版,Stephen Prata 著,張海龍 袁國忠譯,人民郵電出版社,只做學習記錄用途,

目錄
  • 說明
  • 4.1 陣列
    • 4.1.1 陣列訪問
    • 4.1.2 陣列初始化及賦值
  • 4.2 字串
    • 4.2.1 C - 風格字串的初始化及拼接
    • 4.2.2 C - 風格字串的使用
    • 4.2.3 C - 風格字串的輸入與輸出
  • 4.3 string 類簡介
    • 4.3.1 string 物件的初始化
    • 4.3.2 string 物件的賦值、拼接和附加
    • 4.3.3 string 物件的其他操作
    • 4.3.4 string 物件的輸入與輸出
    • 4.3.5 其他形式的字串常量
  • 4.4 結構簡介
    • 4.4.1 定義和使用結構
    • 4.4.2 結構初始化
    • 4.4.3 結構賦值及其他屬性
    • 4.4.4 結構陣列
    • 4.4.5 結構中的位欄位
  • 4.5 共用體
  • 4.6 列舉
    • 4.6.1 列舉的定義
    • 4.6.2 列舉的取值范圍
    • 4.6.3 列舉變數的賦值及其他屬性
  • 4.7 指標
    • 4.7.1 宣告和初始化指標
    • 4.7.2 使用 new 來分配記憶體
    • 4.7.3 使用 delete 來釋放記憶體
    • 4.7.4 使用 new 創建動態陣列
    • 4.7.5 使用 new 創建動態結構
    • 4.7.6 記憶體管理
  • 4.8 指標算術和陣列名
    • 4.8.1 指標算術
    • 4.8.2 陣列名
    • 4.8.3 指標和字串
  • 4.9 型別組合
  • 4.10 陣列的替代品
    • 4.10.1 模板類 vector
    • 4.10.2 模板類 array(C++11)
    • 4.10.3 比較陣列、vector 物件和 array 物件

復合型別是基于基本整型和浮點型別創建的,影響最為深遠的復合型別是,除類外,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

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/500181.html

標籤:其他

上一篇:C語言-陣列(定義、初始化和使用)

下一篇:【github專案】-CRM客戶管理系統(基于SSM)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more