第四章 複合型別
1. 陣列概述
1.1 陣列的定義
陣列(array)是一種資料格式,能夠儲存多個同型別的值。每個值都儲存在一個獨立的陣列元素中,計算機在記憶體中依次儲存陣列的各個元素。
陣列宣告的三個特點:
- 儲存在每個元素中的值的型別
- 陣列名
- 陣列中的元素數
C++中可以通過修改簡單變數的宣告,新增中括號(其中包含元素數目)來完成陣列宣告。
例如:
short days[24]; // 一天有24個小時
1.2 陣列的宣告
宣告陣列的的一般語法格式為:
// 陣列型別 陣列名字[陣列的大小]
int score[4]; // 四個人的分數,整型陣列
陣列的大小是指定元素的數目
,必須是整型常數或const值
,也可以是常量表示式(8*sizeof(int))
1.3 複合型別的陣列
可以使用其他的型別來建立(C語言使用術語:派生型別
)
陣列的用途,可以單獨訪問陣列元素,方法是:使用下標
或索引
對元素進行編號。從0開始編號
。
編譯器不會檢查下標是否有效,所以要注意下標合法性,避免程式異常問題。
C++使用索引的方括號表示法來指定陣列元素。
1.4 陣列的初始化規則
1.只有在定義陣列時才能初始化,此後不能使用,也不能將一個數值賦給另一個陣列。
2.初始化陣列時,提供的值少於陣列的元素數目。
3.如果只對陣列的一部分進行初始化,則編譯器把其他元素設定為0。
4.如果初始化為{1}
而不是{0}
,則第一個元素被設定為1,其他元素都被設定為0.
5.如果初始化陣列方括號內([])
為空,C++編譯器將計算元素個數
。 例如:short things[] = {1,3,5,7};
1.5 C++11陣列初始化方法
C++11將使用大括號的初始化(列表初始化)
作為一種通用的初始化方式,可用於所有型別。
在C++中列表初始化就增加了一些功能:
- 初始化陣列時,可省略
等號(=)
double earnings[4] {1.2e4,1.6e4,1.1e4,1.7e4};
- 可不在大括號內包含任何東西,這會將所元素都設定為零。
unsigned int const[10] = {};
float balances[100] {};
- 列表初始化禁止縮窄轉換。
long num[] = {25,92,3.0}; // 浮點數轉換為整型是縮窄操作
例子:
#include<iostream>
using namespace std;
int main()
{
// 建立一個名字為yams的陣列,包含了3個元素,編號是0~2.
int yams[3];
yams[0] = 7;
yams[1] = 8;
yams[2] = 6;
// 使用逗號分隔的值列表(初始化列表),然後用花括號括起來即可。
// 列表中的空格是可選的,如果沒有初始化函式中定義的陣列,其元素值也是不確定。
int yamcosts[3] = {1,2,3};
cout<<"yams 陣列是:"<<yams[0]+yams[1]+yams[2]<<endl;
cout<<"yams[1] = "<<yams[1]<<endl;
int total = yams[0] * yamcosts[0] + yams[1] * yamcosts[1];
total = total + yams[2] * yamcosts[2];
cout<<"total yam = "<<total<<endl;
// sizeof運算子返回型別或資料物件的長度(單位為位元組)。
// 如果將sizeof運算子用於陣列名,得到的是整個陣列的位元組數。
// 如果sizeof用於陣列元素,得到的是元素的長度(單位為位元組)。
cout<<"\n yams陣列的大小 = "<<sizeof(yams)<<" Bytes.\n";
cout<<"一個元素的大小 = "<<sizeof(yams[0])<<" Bytes.\n";
return 0;
}
2. 字串
字串是儲存在記憶體的連續位元組中的一系列字元。
2.1 C++處理字串的兩種方式:
- C語言,常常被稱為
C-風格字串(C-style String)
以空字元(\0,ASCII碼對應為0)來標記字串的結尾。
- 基於String類庫的方法
儲存在連續位元組
中的一系列字元意味著可以將字串
儲存在char陣列
中。其中每個字元都位於自己的陣列元素中。
使用引號
括起來的字串,這種字串叫 字串常量(String constant)
或 字串字面值(string literal)
。
字串常量(使用雙引號)不能與字元常量(使用單引號)互換。
例如:
char name[] = "Soler";
字串結尾的空字元
,不用直接顯式包括
,機器在鍵盤輸入,將字串讀入到char型別
中,會在結尾自動加上空字元
。
⚠️注意:確定了儲存字串所需的最短陣列時,不要忘記把結尾的空字元
包括在內。
2.2 字串常量的拼接
方法:直接兩個引號括起來的字串合併為一個。任何兩個由空白(空格、製表符和換行符)
分隔的字串常量都將自動拼接成一個。
cout<<"My name is " "Soler HO.\n"
2.3 在陣列中使用字串
將字串儲存到陣列的常用方法:
- 將陣列初始化為字串常量
- 將鍵盤或檔案輸入讀入到陣列中。
#include <iostream>
#include <cstring> /*提供strlen()函式*/
using namespace std;
const int Size = 15;
int main()
{
char name1[Size];
char name2[Size] = "C++owboy";
// 字串的拼接
cout<<"Howdy!I'm "<< name2;
cout<<"!,What's your name?\n";
cin>>name1;
cout<<"Well, "<<name1<<",your name has : "<<strlen(name1)<<" letters and is stored!\n" ;
cout<<"In an array of "<<sizeof(name1)<<" Bytes\n";
cout<<"Your iniatial is "<<name1[0]<<".\n"; // name1陣列中的第一個元素
name2[3] = '\0';
cout<<"Here are the first 3 characters of my name:"<<name2<<endl;
return 0;
}
strlen() 函式
和 sizeof()運算子
的區別
-
strlen()
函式- 返回的是
儲存在陣列中的字串的長度
,而~~不是陣列本身的長度~~
。 - strlen()只計算
可見的字元
,而不把空字元計算在內。
- 返回的是
-
sizeof()
運算子- 指出
變數
或資料型別
的位元組大小
。 - 可用於獲取
類、結構、共用體和其他使用者自定義資料型別
的大小。
- 指出
2.4 讀取一行字串的輸入
解決沒有逐行讀取輸入的缺陷。
istream中提供了面向行的類成員函式:getline()
和 get()
函式
2.4.1 面向行的輸入:getline()
使用通過Enter鍵輸入的換行符來確定輸入結尾。使用 cin.getline()
。
函式有兩個引數:
- 第一個引數:儲存輸入行的
陣列名稱
。 - 第二個引數:要讀取的字元數(注意包含結尾的
空字元(\0)
)。
格式:
cin.getline(name,ArSize);
2.4.2 面向行的輸入:get()
與getline()
函式類似,接受的引數相同
,解釋引數的方式也相同,並讀到行尾
。
區別:get()
讀取並丟棄
換行符,將其留在輸入佇列中。
格式:
cin.get(name,ArSize);
get() 將兩個類成員函式拼接(合併):
cin.get(name,ArSize).get();
⚠️注意:get() 函式讀取空行後設定會失效,輸入會被阻斷。可用如下恢復:
cin.clear();
混合輸入數字和麵向行的字串會導致的問題:無法輸入地址。
解決方法:直接使用get()進行讀取之前丟棄換行符。
3. string類
string類
位於名稱空間std
中,所以需要提供using指令
或者是直接使用std::string
進行引用。
要使用string類
,必須在程式中包含標頭檔案string
中。
string類定義隱藏了字串的陣列性質。
3.1 string物件的方式
使用string物件的方式和使用字元陣列相同。
C-風格字串
來初始化string物件中。- 使用
cin來將鍵盤輸入
儲存到string物件中。 - 使用
cout
來顯示string物件。 - 可以使用
陣列表示方法
來訪問儲存在string1物件中的字元。
賦值 —— 不能將一個陣列賦給另一個陣列,但可以將一個string物件賦另一個string物件。
char char01[20]; // 建立一個空列表
char char02[20] = "Jason"; // 建立一個初始化陣列
string str01; // 建立一個空的string物件
string str02 = "Soler Ho"; // 建立一個初始化的string物件
char01 = char01; // 不可執行,一個陣列不能賦值給另一個陣列
str01 = str02; // 可執行,可將一個string物件賦給另一個string物件。
3.2 複製、拼接和附加
string類簡化字串合併操作。
- 利用
運算子 +
將兩個string物件合併起來。
string str01;
string str02 = "Soler Ho";
string = str01 + str02;
- 可以使用
運算子 +=
將字串附加
到string物件的末尾
。
string str01;
string str02 = "Soler Ho";
str01 += str02;
4. 結構簡介
結構是使用者定義
的型別,而結構宣告定義了型別的資料屬性
。
定義型別之後,就直接建立型別的變數。
結構比陣列靈活,同一個結構中可以儲存多種型別的資料。
4.1 建立結構的步驟:
-
定義結構描述 —— 描述並標記能夠儲存在結構中的各種資料型別
-
按描述建立結構變數(結構資料物件)。
4.2 結構的定義:
struct(關鍵字) 型別名(標記成為新型別的名稱)
{
結構成員1;
結構成員2;
結構成員3;
};//(結束結構宣告)
對於結構中的成員,使用成員運算子(.)
來進行訪問各個成員。
4.3 結構的初始化(C++11)
- 與陣列一樣,列表的初始化用於結構,且
等號(=)可有可無
。
infor Soler_infor {"Soler HO",55,168}; // 在C++11中,= 號可以省略
- 如果大括號內未包含任何東西,各個成員都將設定為零。
infor Soler_infor {};
- 不允許縮窄轉換
✅ 小Tips:C++允許在宣告結構變數時省略關鍵字struct。
4.4 成員賦值
成員賦值(memberwise assignment):可以使用賦值運算子(=)
將結構賦另一個同型別的結構。這樣結構中的每個成員都將被設定為另一個結構中相應成員的值。即使成員是陣列。這種方式就是成員賦值
。
5. 共用體
共用體(union),也叫做聯合(union)
。一種 構造資料型別
。
關鍵字:union
聯合(union):將不同型別的資料
在一起共同佔用同一段記憶體
儲存不同的資料型別,但只能同時儲存其中的一種型別
示例:
union sample
{
int int_val;
long long_val;
double double_val;
};
5.1 結構體和共用體的區別
- 結構可以
同時儲存int、long和double
。 - 共用體
只能儲存int、long和double
三種。 - 含義不同。
- 關鍵字不同
- 結構體:struct
- 共用體:union
5.2 共用體的用途:
- 當資料使用兩種格式或更多格式(但不會同時使用)時,可以節省空間。
- 嵌入式系統程式設計(如控制烤箱、MP3播放器),記憶體非常寶貴。
- 常用於作業系統資料結構或硬體資料結構。
5.3 匿名共用體
匿名共用體(anonymous union)沒有名稱
,其成員將成為位於相同地址
處的變數。
6. 列舉
C++的enum工具提供了另一種建立符號常量
的方式,可以代替const,允許定義新型別,但必須有嚴格限制。
使用enum的語法格式與結構的使用類似。
enum color{red,orange,yellow,green,blue,voilet};
6.1 設定列舉量的值
enum week{Monday = 1,Tuesday = 2;Wednesday = 3;Thursday = 4};
指定的值必須是整數
。也可以只顯示定義其中一些列舉量的值
。
如果第一個變數未初始化,預設為0。後面沒有被初始化的列舉量的值將比其前面的列舉量大1。也可以建立多個值相同的列舉量。
enum {zero,null = 0,numero_one,one = 1};
6.2 列舉的取值範圍
每個列舉都有取值範圍的上限,通過強制型別轉換,可以將取值範圍中的任何整數值賦給列舉常量,即使這個值不是列舉值。
6.3 取值範圍的定義
- 找出上限,需要知道列舉量的最大值。
- 找到大於最大值的,最小的2的冪,減去1,得到就是取值範圍的上限。
- 計算下限,知道列舉量的最小值。
- 如果不小於0,則取值範圍的下限為0,否則,採用尋找上限方式相同的方式,但是要加上負號。
對於選擇使用多少空間來儲存列舉由編譯器
決定。
7. 指標和自由空間
對於地址顯示結果是十六進位制表示法
,因為都是常常描述記憶體的表示法
。
-
指標與C++基本原理
物件導向程式設計和傳統的過程性程式設計的區別,OOP強調的是執行階段(而不是編譯階段)進行決策。
- 執行階段:程式正在執行是,取決於不同的情況。
- 編譯階段:編譯器將程式組合起來時。堅持原先設定的安排
指標用於儲存值的地址。指標名錶示的是地址。
*運算子
稱為間接值或解除引用運算子,將其應用於指標,得到該地址處儲存的值。
7.1 宣告和初始化指標
指標的宣告必須指定指向的資料的型別
。
int *p_updates;
*p_updates
的型別是int
,所以*運算子
被用於指標
,所以p_updates變數必須是指標。
運算子*兩邊的空格
是可選的。
int *ptr; /*該情況強調:*ptr是一個int型別的值。*/
int* ptr; /*該情況強調:int* 是一種型別,指向int的指標。*/
在C++中,int*
是一種複合型別,是指向int的指標
。
double *tax_ptr;
7.2 指標的危險
在C++建立指標時,計算機將分配用來儲存地址的記憶體
,但是不會分配用來儲存指標所指向的資料的記憶體。
⚠️注意:一定要在對指標應用解除引用運算子(*)
之前,將指標初始化為一個確定
的、適當的地址
。
7.3 指標和數字
整數可以加減乘除等運算,而指標
描述的是位置
。
C++語言數字不能作為地址使用,如果要把數字當地址來使用,應通過強制型別轉換
將數字轉換為適當的地址型別。
7.4 使用new分配
和delete釋放
記憶體
指標在執行階段
分配未命名的記憶體以儲存值。然後使用記憶體來訪問記憶體。
C語言中,使用 庫函式malloc()來分配記憶體。C++中使用 ———— new運算子。
7.4.1 要注意使用delete進行記憶體的釋放
需要記憶體時,直接使用new來請求,這是記憶體管理資料包的一個方面。
如果使用了delete運算子
,使得在使用完記憶體後,能夠將其歸還給記憶體池
,這是有效使用記憶體的關鍵。
使用delete時,後面要加上指向記憶體塊的指標。
int * ps = new int; // 使用new進行記憶體分配
...
delete ps; // 使用delete進行記憶體的釋放
⚠️注意點:
1.使用delete釋放ps的記憶體,但是不會刪除指標ps本身。
2.只能用delete
來釋放使用new分配的記憶體
,但是如果是空的指標
使用delete是安全的。
使用delete的關鍵:用於new分配的記憶體
。不是要使用於new的指標,而是用於new的地址
。
❌警告:不能建立兩個指向同一個記憶體塊的指標。會增加錯誤地刪除同一個記憶體塊兩次的可能性。
7.5 使用new建立動態陣列
C++中,建立動態陣列,只需要將陣列的元素型別
和元素數目
告訴new即可。必須在型別名
後面加上方括號
,其中包含了元素數目。
通用格式:
Type_name *pointer_name = new Type_name[num_element];
//例子
int * psome =new int[10]; // 建立10個int元素的陣列
new運算子會返回第一個元素的地址
如果使用完new分配的記憶體,使用delete進行記憶體的釋放。
delete [] psome; // 進行記憶體的釋放
delete和指標直接的方括號告訴程式,應釋放整個陣列
,不僅僅是指標指向的元素。
delete中的方括號的有無
取決於使用new時的方括號有無
。
對於指標陣列的使用,直接可以按照普通陣列的使用即可。
7.6 使用new和delete時,要遵循的規則
- 不要使用delete來釋放不是new分配的記憶體。
- 不要使用delete釋放同一個記憶體塊兩次。
- 如果使用
new[]
為陣列
分配記憶體時,則應使用delete[]
來釋放。 - 如果使用new[]為一個
實體
分配記憶體,則應使用delete(沒有方括號)
來釋放。 - 對空指標使用delete時很安全。
8. 指標、陣列和指標算術
指標和陣列基本等價的原因:指標算術(pointer arithmetic)
和C++ 內部處理陣列的方式
。
- 對
整數變數
+ 1,其值
增加1- 對
指標變數
+ 1,增加的量等於它指向的型別的位元組數
。
獲取陣列地址的兩種方式
double * pw = wages; // 陣列名 = 地址 ;將pw宣告為指向double型別的指標。然後將其初始化為wages - - - wages陣列中第一個元素的地址。
short * ps = &wages[0]; // 使用地址操作;使用地址運算子來將ps指標初始化為stacks陣列的第一個元素。
8.1 指標問題小結
8.1.1 宣告指標
要宣告指向特定型別的指標,語法格式:
TypeName *pointerName;
// 例子
double * pn; // pn 指向一個double型別
char * ps; // ps 指向一個char型別
8.1.2 給指標賦值
將記憶體地址賦給指標。可以對變數名應用 & 運算子
,來獲得被變數名的記憶體地址
,new運算子返回未命名的記憶體的地址。
示例:
double * pn; // pn 指向一個double型別
double * pa; // pa 指向一個double型別
char * pc; // pc 指向一個char型別
double bubble = 3.2;
pn = &bubble; // 把bubble的地址賦值給 pn
pc = new char; // 新建char地址並分配給pc
8.1.3 對指標解除引用
對指標解除引用意味著獲得指標指向的值
。
- 方法1:對指標應用解除
引用
或間接值運算子(*)
來解除引用。
cout<<*pn;
*pc = 's';
- 方法2:使用
陣列表示法
。不可以對未初始化為適當地址的指標解除引用。
8.1.4 陣列名
多數情況下,C++將陣列名
視為陣列的第一個元素的地址
。
int tacos[10]; // 此時的tacos同樣也是&tacos[0]
8.1.5 指標算術
C++中允許指標和整數相加
。加1 的結果等於原來的地址值
加上指向的物件佔用的總位元組數
。
也可以將一個指標減去另一個指標,獲得兩個指標的差。得到一個整數,僅當兩個指標指向同一個陣列(也可以指向超出結尾的一個位置)時,這種情況會得到兩個元素的間隔。
8.1.6 陣列的動態聯編和靜態聯編
使用陣列宣告來建立陣列時,將採用靜態聯編
,即陣列長度在編譯
時設定。
int tacos[10] // 靜態聯編
使用new[]運算子
建立陣列時,將採用動態聯編(動態陣列)
,即將在執行時為陣列分配空間,其長度為執行時設定。
使用這類陣列後,要使用
delete[]
釋放所佔用的記憶體。
8.1.7 陣列表示法和指標表示法
使用方括號陣列表示法
等同於對指標解除引用
。
陣列名和指標變數也是一樣。所以對於指標和陣列名,既可以使用指標表示法
,也可以使用陣列表示法
。
int * pt = new int [10];
*pt = 5;
pt[0] = 6;
pt[9] = 5;
int coats[10];
*(coats + 4) = 12;
8.2 指標和字串
陣列名是第一個元素地址
。
如果給cout提供一個字元的地址,則它將從該字元開始列印,直到遇到空字元為止。
在cout和多數C++表示式中,char陣列名
、char指標
以及用引號括起來的字串常量
都被解釋為字串第一個字元的地址
。
不要使用字串常量或未被初始化的指標來接收輸入。
在字串讀入程式時,應使用已分配的記憶體地址。該地址不是陣列名,也可以使用new初始化過的指標。
strcpy()
接受兩個引數,第一個:目標地址
,第二個:要複製的字串的地址
。
要確定目標空間有足夠的空間來儲存副本。
8.3 使用new建立動態結構
對於在指定結構成員時,句點運算子
和箭頭運算子
的選擇時:
- 如果結構識別符號是
結構名
,則使用句點運算子(.)
。 - 如果識別符號是
指向結構的指標
,則使用箭頭運算子(->)
。
把new用於結構的兩個步驟
- 建立結構
要建立結構,需要同時使用結構型別和new。
- 建立訪問其成員。
8.4 C++管理資料記憶體的方式
- 自動儲存
在函式內部定義的常規變數使用自動儲存空間,稱為自動變數
。只在特定函式被執行時存在。
自動變數時一個區域性變數
,作用域為包含它的程式碼塊
。通常儲存在棧
中,遵循後進先出(LIFO)
。
-
靜態儲存
- 變數稱為靜態的方式
- 在函式外面定義
- 在宣告變數時使用關鍵字static。
整個程式執行期間都存在的儲存方式(存在於程式的
整個生命週期
)。 - 變數稱為靜態的方式
-
動態儲存
記憶體池(自由儲存空間或堆)用於靜態變數和自動變數,且記憶體是分開的。 -
執行緒儲存(C++11特性)
9. 陣列替代品 --- 模板類
模板類vector
和array
是陣列的替代品。
9.1 模板類vector
模板類vector
類似於string
類,也是一種動態陣列
。
vector物件
包含在vector標頭檔案
中。- vector包含在名稱空間std中,使用
using編譯指令
、using宣告
或std::vector
。 - 模板使用不同的
語法
來指出它儲存的資料型別
。 - vector類使用不用的語法來指定
元素數
。
9.2 模板類array(C++11)
位於名稱空間std
中,與陣列一樣,array物件的長度固定
,也使用棧(靜態記憶體分配)
,而不是自由儲存區
。
標頭檔案 array。
9.3 陣列、vector和array的區別
無論是陣列、vector物件還是array物件,都可使用標準陣列表示法
來訪問各個元素。
從地址
可知,array物件和陣列儲存在相同的記憶體區域(即棧)
中,vector物件儲存在自由儲存區域或堆
中。
可以將一個array物件賦給另一個array物件,對於陣列,必須逐個
元素複製
資料。
Github地址:https://github.com/SolerHo/cpp-Primer-Plus-6e-Notes/blob/master/Chapter04/README.md
第四章 學習筆記完畢,如有大佬在文中發現錯誤,請指出,謝謝