有序數據的集合。
陣列中每個元素的資料型態都相同。
元素可透過陣列名加上『下標』來取得。
下標從 0
開始計數。
整個陣列代表一組同類型的變數。
注意:字元陣列。
一般型式:
型態 陣列名[常數表達式]
int a[10]; // 定義陣列a,這個陣列具有10個int的元組。
a
:陣列名就是變數名
中括號([]
)括起來的『常數表達式』,一般多為數值字面值,如
10
。或是常數表達式 2 + 8
。
C++/C 程式不允許對陣列的大小做動態定義,故陣列大小無法依賴於程式執行過程中的變數值。
陣列定義的時候,大小必確定。
一維陣列帶有一組『中括號([]
)』。
a[10]
具有 10 個元素。
合法可以使用的的元素是 a[0]
~
a[9]
。
但非法使用
a[10]
(如為a[10]
賦值),系統並不會提示錯誤,但會給程式帶來不確定的未爆彈。
因 a[10]
所屬的記憶體空間並不屬於開發者所定義來使用,故有可能會將其他程式用到該位址的部分給覆蓋掉,則會引發許多非預期的錯誤。
int a[10]; // a陣列的元素下標為 0 ~ 9。
a[10] = 8; // 這是危險的。雖然系統不會提示錯誤,但會給程式留下巨大的隱憂。
一維陣列的引用:
C++/C 程式規定只能引用陣列的元素,『無法引用整個陣列』。
一般型式:
陣列名[下標]
int i, a[10];
for (i = 0; i <= 9; i++) // i = 0 ~ 9
a[i] = i; // for loop若不加{},for loop的有效範圍是到第一個分號結束。
for (i = 0; i <= 9; i++)
printf("a[%d] = %d\n", i, a[i]);
一維陣列初始化:
定義陣列時不初始化
int a[10]; // 只給定義不給初始值,則陣列內部元素值為不確定的隨機值。
定義陣列時初始化
int a[10] = {9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
只給一部分元素初始化
int a[10] = {9, 8, 7, 6}; // 其他陣列元素值則自動設為0。
char s[10] = {'A', 'B', 'C'}; // 其他陣列元素值則自動設為''。
若對全部陣列設定初始化,可以不給陣列長度。
一般型式:
型態 陣列名[常數表達式][常數表達式]
float a[3][4]; // 定義二維陣列a,注意a[3, 4]是錯誤的用法。
定義出3列(rows)4行(columns)的陣列。
可將二維陣列理解為含有多個元素的的一維陣列。
意指 a
是一個具有『3個元素的一維陣列』(a[0]
、a[1]
、a[2]
),每個元素則是一個包含
4 個元素的『一維陣列』。
故 a
共有12個元素。
在C/C++語言,二維陣列存放的順序為:『按列順序(by-row order)』。
故第一維下標變化最慢,最右邊的維度變化最快。
二維陣列的引用:
引用型式:
陣列名[下標][下標]
注意:引用『二維陣列』必須帶有『兩個中括號』。其他多維陣列以此類推。
下標可以為整數表達式,如
a[5-2][4-1]
,但一般不會這樣寫。直接寫成整數。
陣列元素可以出現在表達式中,就像變數一樣使用,亦也可被賦值操作。
無論是一維陣列或是二維陣列,其陣列元素(a[1]
或是a[1][2]
)都應該被看成一個普通變數。
int a[3][4]; // 定義二維陣列
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
a[i][j] = i * j;
}
}
for (i = 0; i < 3; i++)
{
for (j = 0; j < 4; j++)
{
printf("a[%d][%d] = %d\t", i, j, a[i][j]);
}
std::cout << std::endl;
}
二維陣列初始化:
按列(row)給二維陣列初始化:
int a[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} };
將所有數據放在大括號({}
)內:
但初始化看起來不清晰,容易遺漏出錯。
int a[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
對部分元素設定初始化:
int a[3][4] = {{1}, {3, 4}};
這裡大括號的內容代表一列,這裡省略了第三列,也就是沒給第三列元素賦值。
其他未被賦值的元素都會被預設為 0
。
int a[3][4] = {{1}, {}, {9}};
第二列不賦值。
其他未被賦值的元素都會被預設為 0。
若對全部元素設定初始化,則定義陣列時第一維度的長度可以不指定,但第二維陣列的長度(其他維度長度)不能省略。
int a[][4] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
// 等同於
// int a[3][4] = {1, 2, 3, 4, ,5, 6, 7, 8, 9, 10, 11, 12};
int a[][4] = {{0, 0, 3}, {}, {0, 10}};
// 等同於
// int a[][4] = {{0, 0, 3, 0}, {0, 0, 0, 0}, {0, 10, 0, 0}};
int a[][2][2] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; // 三維陣列時
int a[][3] = {{1, 2, 3}, {4, 5}, {7, 8}, {9}};
for (size_t i = 0; i < sizeof(a)/sizeof(a[0]); i++)
{
for (size_t j = 0; j < sizeof(a[0])/sizeof(a[0][0]); j++)
{
std::cout << "a[" << i << "]" << "[" << j << "] = " << a[i][j] << std::endl;
}
}
int a[][5] = {{1, 2, 3}, {4, 5, 6}};
size_t n = sizeof(a)/sizeof(a[0]);
std::cout << "row = " << n << std::endl;
size_t m = sizeof(a[0])/sizeof(a[0][0]);
std::cout << "column = " << m << std::endl;
std::cout << "第二列為:";
for (size_t i = 0; i < sizeof(a[0])/sizeof(a[0][0]); i++)
{
std::cout << a[1][i] << ", ";
}
std::cout << std::endl;
float a[2][3][4];
// a[0][0][0], a[0][0][1], a[0][0][2], a[0][0][3],
// a[0][1][0], a[0][1][1], a[0][1][2], a[0][1][3],
// a[0][2][0], a[0][2][1], a[0][2][2], a[0][2][3],
// a[1][0][0], a[1][0][1], a[1][0][2], a[1][0][3],
// a[1][1][0], a[1][1][1], a[1][1][2], a[1][1][3],
// a[1][2][0], a[1][2][1], a[1][2][2], a[1][2][3],
實務上一維與二維陣列較常用,三維陣列與多維陣列較少用。
int a[] = {100, 200, 300};
// int a2[] = a; // 錯誤:不允許使用一個陣列來初始化另一個陣列。
// int a2[3];
// a2 = a; // 錯誤:不能把一個陣列直接賦值給另一個陣列。
陣列可存放許多型態的物件。例如:
指標的陣列:一堆指標。
陣列的指標:指向陣列的指標
陣列的參考:綁定陣列的參考
範例:
int arr[10]; // 含有10個整數的陣列。
int* ptr[10]; // ptr是含有10個整數型態指針的陣列。指標的陣列。
// int& ref[10]; // 錯誤:因為參考並非物件,故不存在參考的陣列。
int (*Parray)[10] = &arr; // Parray指向一個含有10個整數的陣列。陣列的指標。注意陣列的大小。
int (&arrRef)[10] = arr; // arrRef是一個含有10個整數陣列的別名。陣列的參考。
ptr
,從右至左:首先知道是一個長度為 10
的陣列,它的名字是 ptr
,然後知道陣列中存放的是指向
int
的指標( int*
)。Parray
,由內而外:先看()
的部分,*Parray
意指 Parray
是一個指標,接著觀察右邊,可知 Parray
是一個指向大小為 10
之陣列的指標。最後觀察左邊,得知陣列中的元素是 int
。&arrRef
,由內而外,先看()
的部分,可知
arrRef
是一個參考,它綁定的物件是一個大小為 10
的陣列,而陣列中的元素為 int
。int arr[] = {1, 2, 3, 4, 5};
int (*ptr2)[5] = &arr;
// 使用指標 ptr2 存取陣列元素
std::cout << "第一個元素:" << (*ptr2)[0] << std::endl;
std::cout << "第二個元素:" << (*ptr2)[1] << std::endl;
int a[] = {100, 200, 300};
std::cout << "a = " << a << std::endl;
int *b = a;
int (*ptr)[3] = &a;
std::cout << *b << std::endl;
std::cout << (*ptr)[0] << std::endl;
b
可以通過解參考操作
(*b
) 來取得其指向的整數值。
ptr
也可以通過解參考操作來取得整個陣列,但需要使用陣列下標或指標算術操作來存取陣列中的元素。
在這段程式碼中,當嘗試輸出 (*ptr)
時,會遇到一個問題,因為 (*ptr)
代表的是
ptr
所指向的整個陣列,它不是一個單一的數值,而是一個整個陣列的地址。
C++ 標準程式庫中 std::cout
的
operator<<
在處理
(*ptr)
時會遇到一個
int (*)[3]
型態的指標,因此會將其視為一個記憶體位置,而不是陣列中的值。因此,這樣的輸出不會產生預期的結果,而是顯示該陣列的起始記憶體位置。
指標與陣列關係密切。
使用陣列時,編譯器會將其轉換為指標。
使用『陣列』型態的物件時,其實就是使用指向『該陣列首位址的指標』。
int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
// int (*ptr)[] = &a; // 錯誤:指向的陣列,其大小必須確定。
int (*ptr)[10] = &a; // 正確。
int ia[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
auto ia2(ia); // ia2是一個『整數型態指標』,指向ia的第一個元素。
int* ia3(ia); // ia3是一個『整數型態指標』,指向ia的第一個元素。
auto ia4 = ia; // ia4是一個『整數型態指標』,指向ia的第一個元素。
// ia2 = 42; // 錯誤:ia2是一個指標,不能用int賦值給指標。
// 在大部分表達式中時,陣列名 a 被隱式轉換為指向其首元素的指標。
// 這意味著如果 a 是一個一維陣列,例如 int a[10];,則在大多數上下文中 a 被視為指向 int 的指標(即 int*)。
int a[][3] = {1, 2, 3, 4, 5, 6};
std::cout << "a is the 2 * 3 array: " << std::endl;
std::cout << "a = " << a << std::endl;
std::cout << "a[0] = "<< a[0] << std::endl;
std::cout << "a[0][0] = " << a[0][0] << std::endl;
int (*ptr11)[3] = a;
int *ptr12 = &a[0][0];
std::cout << ptr11 << std::endl;
std::cout << ptr12 << std::endl;
// 使用 &a 可以獲得整個陣列的地址,這會返回一個『指向整個陣列的指標』,其類型與陣列維度有關。
int (*ptr21)[2][3] = &a;
int (*ptr22)[3] = &a[0];
int (*ArrPtr)[][3] = &a;
std::cout << "ptr21 = " << ptr21 << std::endl;
std::cout << "ptr22 = " << ptr22 << std::endl;
std::cout << "ArrPtr = " << ArrPtr << std::endl;
int a[] = {100, 200, 300};
int *p1 = nullptr;
int *p2 = nullptr;
p1 = a;
p2 = &a[0]; // p1 和 p2 均初始化為 nullptr,接著指向陣列 a 的首位址。注意,陣列名 a 本身在表達式中可以被當成指向第一个元素的指標。
std::cout << "a = " << a << std::endl;
std::cout << "p1 = " << p1 << std::endl;
std::cout << "p2 = " << p2 << std::endl; // 因 p1 和 p2 都指向陣列的第一個元素,這三行打印出相同的位址。
std::cout << a[0] << std::endl;
std::cout << *p1 << std::endl;
std::cout << a[1] << std::endl;
std::cout << *(p1 + 1) << std::endl; // 透過陣列下標和透過指標加上偏移量(下標)存取陣列元素的方式。
int (*ArrPtr)[3] = &a; // ArrPtr 是一個指向『整個陣列』的指標。
std::cout << ArrPtr << std::endl; // 輸出陣列a的位址
std::cout << *ArrPtr << std::endl; // 輸出陣列a的位址,因『解參考』指向陣列的指標得到『陣列本身』。
std::cout << **ArrPtr << std::endl; // 輸出陣列第一個元素,即100。
std::cout << (*ArrPtr)[0] << std::endl;
std::cout << (*ArrPtr)[1] << std::endl;
std::cout << (*ArrPtr)[2] << std::endl;
int a[] = {100, 200, 300};
int (*ArrPtr)[3] = &a; // ArrPtr 是一個指向包含三個整数的陣列的指標,並初始化為指向 a。
// 複製陣列指標:
int (*ptr)[3] = ArrPtr; // ptr 是另一個指向整個陣列的指標,與 ArrPtr 型態一致。此行程式碼將 ArrPtr 之值赋值给 ptr,故 ptr 也指向陣列 a。
auto *p1 = &ArrPtr; // p1 是一個『自動型態推論』的指標,指向 ArrPtr。由於 ArrPtr 的型態是 int (*)[3](指向包含三個整数的陣列的指標),故 p1 的型態為 int (*(*)[3]) — 指向 int (*)[3] 型態的指標。
int (*(*p2))[3] = &ArrPtr;
定義:『字元陣列』中每個元素都是字元(char
)。
char c[10]; // 能引用的元素c[0] ~ c[9]。
c[0] = 'I';
c[1] = ' ';
c[2] = 'a';
c[3] = 'm';
c[4] = ' ';
c[5] = 'h';
c[6] = 'a';
c[7] = 'p';
c[8] = 'p';
c[9] = 'y';
字元陣列初始化
逐個字元賦值給陣列中的元素
若提供的初始值個數預定的陣列長度相同,定義時可以省略長度。
若初始值大於陣列長度,則出現『語法錯誤』。
若初始值個數小於陣列長度,則只將這些字元賦值給前面的元素,其餘元素可能給
'\0'
,也可能無法確定之。故強烈建議不要使用這些未確定之元素值。
字串與字串的結束標記('\0'
)
比較並思考下列二者的涵義與差異:
char c[] = {'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y'}; // 定義一個包含10個元素的字元陣列
char c[] = {"I am happy"}; // 利用字串常數來初始化字元陣列,其中{}可以省略。
// char c[] = "I am happy";
第二例實際上是定義一個類似 char c[11]
的陣列,長度為
11。
第二例可以引用的元素為 c[0]
~
c[10]
,且在 c[10]
裡面被系統自動填入一個
'\0'
字元。
'\0'
為字元的 ASCII碼,而'\0'
就是
0。其則為字串『結束標記(結束字元)』。代表字串結束。
上例字串常數 "I am happy"
共有10
個『可見字元』(含空白字元 " "
)。
每個『可見字元』佔 1 個位元組,但實際上該字串常數在內存共佔
11位元組,其中最後 1 個位元組放的正是 '\0'
。
當有了字串結束標記 '\0'
後,字串陣列的長度即可確定(因程式就是以 '\0'
來判斷字串是否結束)。
char c[10] = "I am happy"; // 錯誤:因為 c陣列無法儲存 "I am happy"。
char c[11] = "I am happy"; // 沒問題。剛好放得下。
char c[100] = "I am happy"; // 當然可以。
比較下列兩行程式碼差異:
char c[] = {'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y'};
char c[] = {"I am happy"};
'\0'
。再比較下列兩行程式碼差異:
char c[] = {'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y', '\0'};
char c[] = {"I am happy"};
字元陣列並沒有規定最後一個字元為
'\0'
。是否加入 '\0'
取決於程式設計師。
若使用字元陣列並對其進行初始化,建議與字串常數保持一致,請加入
'\0'
,以確定字串常數的長度。
char c[100] = "I am happy"; // 最普遍與常見的寫法
char c[] = {'I', ' ', 'a', 'm', ' ', 'h', 'a', 'p', 'p', 'y'};
printf(" %s\n", c);
char c[] = "I am happy";
printf(" %s\n", c);
字元陣列的輸入/輸出
printf
函數可利用 %s
格式符可輸出打印字串。
char c[] = "NCCU";
printf(" %s\n", c);
printf
函數向螢幕輸出結果時,遇到字元
'\0'
就會停止輸出打印。
printf
函數輸出打印字串中並不包含
'\0'
,且 '\0'
也不是可見字元。
printf(" %s\n", c);
,其輸出項為『字元陣列名』,不可以是
c[0]
、c[1]
等。
即便陣列定義時的長度大於字符串實際長度時,也只輸出到
'\0'
結束。
char c[100] = "NCCU";
printf(" %s\n", c);
若字元陣列裡包含多個 '\0'
,則printf
函數遇到第一個 '\0'
時就會停止輸出。
字串處理函數(HW.)
strcat(字元陣列1, 字元陣列2)
strcpy(字元陣列1, 字元陣列2)
strcmp(字串1, 字串2)
strlen(字元陣列)
字串是特殊的字元陣列。
與普通的字元陣列不同的是字串是在末尾處有一個『結束字元
'\0'
』,用以表示字串的結束。
所以可以這麼認為:並沒有一種專門的字串型態。字串其實是字元陣列。
字串也有字面值(用雙引號
""
)的定義方式:"Hello Quant"
。
雖然字元指標(字元陣列首位址)與字元陣列都能實現字串的儲存,但兩者還是有區別。
字元陣列由若干元素組成,每個元素存放1個字元。
字元指標存放的僅是字串的『首位址』(千萬不要誤解成將字串存放至字元指標)。
賦值方式:
char str[100] = "I am happy!"; // 定義時初始化。
char str[100];
//str = "I am happy!"; // 錯誤:無法直接賦值。
strcpy(str, "I am happy!"); // 利用strcpy進行複製完成。
const char *a; // 可採字元指標
a = "I am happy!";
指標變數是可以修改的:指標指向的位置可以被改變。
const char *a = "I am happy!";
a = a + 7;
printf("%s\n", a);
陣列名稱雖然代表陣列首位址,但其值無法改變(Top-Level
const
)。
char a[] = "I am happy!";
// a = a + 7; // 錯誤:因為陣列名稱所代表的首位址是無法被更改的。
printf(" %s\n", a);
int a[]{100, 200, 300};
int *p = a;
std::cout << a << std::endl;
std::cout << p << std::endl;
std::cout << (*a) << std::endl;
std::cout << (*p) << std::endl;
std::cout << "*(a + 1) = " << *(a + 1) << std::endl; // *(a + 1) = 200
// std::cout << *(++a) << std::endl; // 錯誤:因為『前遞增運算子』會修改『不能修改的 a』。
std::cout << "*(++p) = "<< *(++p) << std::endl; // *(++p) = 200
std::cout << "*(p++) = " << *(p++) << std::endl; // *(p++) = 200
const char *p = "Hello";
std::cout << "p = " << p << std::endl; // p = Hello
std::cout << "*p = " << *p << std::endl; // *p = H
printf("%p\n", p); // 0x104887f3c
char arr1[] = "I am Happy";
char arr2[] = {'A', 'B', 'C'};
int arr3[] = {100, 200, 300};
const char *ptr = "I am Happy";
std::cout << arr1 << std::endl;
std::cout << arr2 << std::endl;
std::cout << arr3 << std::endl;
std::cout << ptr << std::endl;
在 C++ 中,當使用 std::cout
打印一個字元指標 (char*
)
時,std::cout
被設計來特別處理這種情況。
字元指標通常被認為是指向一個 C 字串(一個 null 字元
'\0'
結尾的字元數組)。因此,標準輸出流(std::cout
)對『字元指標』進行特殊處理:
不會打印出指標的記憶體地址,而是從指標所指向的地址開始,逐字元輸出,直到遇到結束字元(null
字元 '\0'
)為止。
該特性是根據 operator<<
重載實現的,該重載專門處理 char*
參數。這樣做的目的是因為在 C 和 C++
中,字串通常以這種方式表示和處理。例如,標準函數庫中的字串函數(如
strlen
、strcpy
等)都預期以 '\0'
結尾的字元陣列作為輸入。
陣列的長度在定義時必須確定,不能是變數。
陣列定義後,其長度也不能改變。
無法直接使用陣列為另一個陣列賦值。
無法對陣列進行插入的操作。
在使用陣列時無法直接取得陣列的長度。
因此,C++ 標準程式庫提供功能強大的『類別模板(class template)』-
vector
,來克服上述缺點。
vector
vector
型態簡介vector
型態是標準程式庫內的一種型態。
是一種 容器(container)、集合與動態陣列的概念。
vector
內的元素型態必須相同(同質性):可以將一堆同型態的物件放在
vector
容器中。
要使用 vector
型態,需要在原始檔(.cpp
)包含 vector
標頭檔:#include <vector>
用法:
#include <vector> // 需要在使用vector型態的原始檔加入vector的標頭檔
std::vector <int> vec; // 定義一個vector型態的物件,名稱為 vec。
上列程式碼定義一個 vector
型態的物件。
此物件(容器)內存放 int
型態的數據。
<int>
寫法,為一種『類別模板(template)』實例化的概念。
vector
不是一個完整的類別型態。
透過『實例化』後,
vector<int>
才是一個定義完整的類別型態。
vector <int*> vec2; // 正確:向量內存放的是指向 int 的指標。
// vector <int&> vec3; // 錯誤:參考只是一個別名,並不是物件。
struct student
{
int num;
};
std::vector <student> studList; // 正確:向量內存放的是struct型態的物件。
std::vector <std::vector<student>> V; // 正確:該向量內每個元素都一個vector物件。
vector
物件空 vector
物件
std::vector <std::string> mystr; // 創建一個string型態的空vector物件(容器)
mystr.push_back("abcd");
mystr.push_back("def");
當 vector
物件內元素型態相同下,進行
vector
物件的『元素複製(新的副本)』
std::vector <std::string> mystr2(mystr); // 將mystr元素複製給mystr2。
std::vector <std::string> mystr3 = mystr; // 將mystr元素複製給mystr3。
使用 C++ 11 初始化列表
std::vector <std::string> def = {"aaa", "bbb", "ccc"};
std::vector <int> v = {}; // v為空 vector 物件。
創建指定個數的元素:具有元素個數概念的初始化,都是使用
()
。
std::vector <int> v2(3, -200); // 創建一個包含3個int型態(值為-200)的向量,向量名稱為v2。
std::vector <std::string> v3(5, "Hello");
若不為元素設定初值,則元素的初值乃根據元素型態而定。例如:若元素型態為
int
,則系統設定初值為 0。若元素型態為
std::string
,則系統設定初值為 ""
。
但也存在某些型態,必須設定初值,否則程式將出錯。
std::vector <int> v4;
std::vector <std::string> v5;
其他初始化,使用 {}
。
{}
一般表示元素內容的概念。
std::vector <int> i1(10); // 10為元素個數,每個元素預設為0。
std::vector <int> i2{10}; // {}括住單個數字10,表示1個元素。
std::vector <std::string> s1{"hello"}; // 1個元素,其值為 "hello"。
std::vector <std::string> s2{10}; // 10個元素,每個元素都為""。因為10這個數字無法作為std::string的物件,故系統將它轉換為元素的個數。不建議這樣的寫法。
std::vector <std::string> s3(10, "hello"); // 10個元素,每個元素內容都是 "hello"。
建議當使用 {}
進行初始化時,則 {}
內的值型態應與 <>
內的元素型態一致。
vector
對於二維 vector,可視為一個一維 vector,其每個元素都是都是一維 vector。
下列範例 std::vector<double>(cols)
用於建立一個包含 cols
個 double
型態元素的一维
std::vector
,然後
std::vector<std::vector<double>> array(rows, ...)
用於創建一個包含 rows
個一维 std::vector
的二维 std::vector
。
#include <iostream>
#include <vector>
// #include <string>
#include <random>
int main(int argc, char const *argv[])
{
// 使用隨機設備,隨機產生亂數種子。
// std::random_device rd;
// std::default_random_engine generator(rd()); // 從 random_device 來初始化一個隨機數生成器。
int seed = 42;
std::default_random_engine generator(seed); // 使用固定種子初始化隨機數生成器
std::normal_distribution<double> distribution(0.0, 1.0); // 常態分配,均值為0,標準差為1。
// 建立一个1000x365的陣列(矩陣)
const int rows = 1000;
const int cols = 365;
std::vector<std::vector<double>> array(rows, std::vector<double>(cols)); // 嵌套式定義。
// 對矩陣填值。
for (int i = 0; i < rows; ++i)
{
for (int j = 0; j < cols; ++j)
{
array[i][j] = distribution(generator);
}
}
// 打印矩陣一部分數據。
for (int i = 0; i < 5; ++i)
{
for (int j = 0; j < 5; ++j)
{
std::cout << array[i][j] << "\t";
}
std::cout << std::endl;
}
return 0;
}
vector
物件的操作判斷是否為空:empty()
,回傳 布林值。
std::vector <int> ivec; // 定義ivec為空向量。
if (ivec.empty())
{
std::cout << "ivec為空向量" std::endl;
}
push_back()
:用於向 vector
物件末尾增加元素。
std::vector <int> ivec;
ivec.push_back(1);
ivec.push_back(2);
for (int i = 3; i <= 100; i++)
{
ivec.push_back(i);
}
size()
:回傳元素個數。
std::cout << ivec.size() << std::endl;
clear()
:移除所有元素,將容器清空。
ivec.clear();
std::cout << ivec.size() << std::endl;
v[n]
:回傳 v
中的第 n
個元素:
回傳 v
中第 n
個元素(n
為整數型態)。其位置從 0
開始計算。n
也必須小值 size()
值。
若下標值 n
超過 size()
的範圍,或用下標存取空向量,則會發生不可預的後果(編譯器可能不會檢查出這類錯誤)。
賦值運算子(=
)
vector <int> ivec; // 先宣告一個空vector物件
ivec.push_back(1);
ivec.push_back(2);
for (int i = 3; i <= 100; i++ )
{
ivec.push_back(i);
}
std::vector <int> ivec2;
ivec2.push_back(111);
ivec2 = ivec; // 用ivec中的內容取代ivec2中原有內容。
ivec2 = {12, 13, 14, 15}; // 用{}中的值取代ivec2原有內容。
std::cout << ivec2.size() << std::endl; // 4。
判斷相等與不相等(==
與 !=
)
兩個 vector 物件相等:
元素數量相等。
對應位置的元素值相等。
std::vector<int> ivec;
ivec.push_back(1);
ivec.push_back(2);
for (int i = 3; i <= 100; i++ )
{
ivec.push_back(i);
}
std::vector <int> ivec2;
ivec2 = ivec;
if (ivec2 == ivec)
std::cout << "ivec2 == ivec" << std::endl;
if (ivec2 != ivec)
std::cout << "ivec2 != ivec" << std::endl;
range for
的運用。
std::vector <int> vec{1, 2, 3, 4, 5};
for (auto& vecitem: vec)
vecitem *= 2;
for (auto& vecitem: vec)
std::cout << vecitem << std::endl;
使用 range for
可遍歷 vector
容器內所有的元素。
vecitem
被定義為一個變數,而 vec
則為容器(序列)。
使用 auto
來確保序列中的每個元素都能順利轉換為
vecitem
對應的型態(由編譯器來進行型態推論)。
不要在 range for
中加入改變 vector
內容的程式碼,結果以及輸出則會出現錯誤或混亂:
std::vector <int> vec{1, 2, 3, 4, 5};
for (auto& vecitem: vec)
{
vec.push_back(888);
std::cout << vecitem << std::endl;
}
在 std::vector
中大量插入新資料時,最好的做法是先預先分配足夠的空間以避免多次內存重新分配,然後再插入資料。這樣可以提高效率並減少內存重新分配的開銷。
這些方法可以幫助你在 std::vector
中高效地插入大量新資料,確保程序性能和穩定性。
預先分配空間
使用 vector
的 reserve
方法來預先分配足夠的空間,這樣可以避免在插入資料過程中多次內存重新分配。
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec;
// 預先分配空間,假設我們知道要插入 1000 個元素
vec.reserve(1000);
// 插入大量新資料
for (int i = 0; i < 1000; ++i) {
vec.push_back(i);
}
// 輸出結果
std::cout << "Size: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
return 0;
}
批量插入
如果你已經有一個容器(如另一個 vector
或一個陣列)包含了所有要插入的新資料,可以使用 insert
方法進行批量插入。
#include <vector>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::vector<int> new_data = {6, 7, 8, 9, 10};
// 預先分配足夠的空間
vec.reserve(vec.size() + new_data.size());
// 批量插入新資料
vec.insert(vec.end(), new_data.begin(), new_data.end());
// 輸出結果
for (const auto& elem : vec) {
std::cout << elem << " ";
}
std::cout << "\nSize: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
return 0;
}
使用迭代器範圍插入
如果要從另一個容器中插入元素,可以使用迭代器範圍來插入。
#include <vector>
#include <list>
#include <iostream>
int main() {
std::vector<int> vec = {1, 2, 3, 4, 5};
std::list<int> new_data = {6, 7, 8, 9, 10};
// 預先分配足夠的空間
vec.reserve(vec.size() + new_data.size());
// 使用迭代器範圍插入新資料
vec.insert(vec.end(), new_data.begin(), new_data.end());
// 輸出結果
for (const auto& elem : vec) {
std::cout << elem << " ";
}
std::cout << "\nSize: " << vec.size() << ", Capacity: " << vec.capacity() << std::endl;
return 0;
}
string
string
型態簡介在 C++ 中,因為有標準程式庫的存在,亦定義很多標準程式庫定義的型態。
最常見的 C++ 標準程式庫型態:std::vector
型態與
std::string
型態。
其中,std::string
型態是用來處理可變長度(variable-length)
『字串(字元序列)』使用。
在C++中,仍然可以使用字元陣列來表示字串,也可以使用
std::string
來表示字串。
『字元陣列』與 『std::string
』
可以進行型態互相轉換。
std::string
轉換到 C
風格字元陣列:std::string
提供成員函數
c_str()
,此函數回傳一個指向以
null
結尾的字元陣列的常數指針(const char*
),該陣列包含與
std::string
相同的數據。#include <iostream>
#include <string>
int main() {
std::string str = "Hello, world!";
const char* cstr = str.c_str();
std::cout << "std::string: " << str << "\n";
std::cout << "C string: " << cstr << std::endl;
return 0;
}
從 C 風格字元陣列轉換到
std::string
:可直接使用 C 風格的字元陣列來初始化
std::string
,或者將其賦值給一個已經存在的
std::string
物件。這是因為
std::string
的構造函數和賦值運算符重載支持從
const char*
進行初始化和賦值。
#include <iostream>
#include <string>
int main() {
const char* cstr = "Hello, world!";
std::string str = cstr;
std::cout << "C string: " << cstr << "\n";
std::cout << "std::string: " << str << std::endl;
return 0;
}
當使用 c_str()
獲得
const char*
時,回傳的指標不應被修改,且其壽命與原
std::string
物件綁定。如果
std::string
被修改或銷毀,則該指標可能指向無效的內存。
確保從 const char*
轉換到
std::string
時,該
const char*
必須是有效的以
null
結尾的字元陣列。如果它不是以 null
結尾,則轉換可能導致運行時錯誤(如讀取越界)。
要使用 std::string
型態,需要在原始檔(.cpp
)包含 string
標頭檔:#include <string>
。
string
物件如何初始化一個物件是由其類別所決定。
一個類別可以有很多種初始化物件的方法。可能因下列因素有所不同:
初始值數數量的不同。
初始值的型態不同。
初始化方法 | 意義 |
---|---|
std::string s1 |
預設初始化,s1為一個空字串。 |
std::string s2(s1) |
s2為s1的複製。 |
std::string s2 = s1 |
s2為s1的複製。 |
std::string s3("value") |
s3是字面值 "value"的複製,但不包括字面值最後的空字元。 |
std::string s3 = "value" |
s3是字面值 "value"的複製,但不包括字面值最後的空字元。 |
std::string s4(n, 'c') |
將s4初始化為連續n個字元 'c' 組成的字串。 |
string
物件的『預設初始化』是設定為『空字串』。std::string s1; // 預設初始化:s1 = ""; 代表為空字串。
std::string s2 = "I am happy!"; // 將 "I am happy!" 這個字串複製至s2代表的一塊記憶體空間中。
std::string s3("I am happy!"); // 效果與s2一樣。
std::string s4 = s2; // 將s2的內容複製至s4代表的一塊記憶體空間中。
int num = 6;
std::string s5(num, 'a'); // 將s5初始化為連續num個字元組成的字串。但此方法系統內部會創建臨時物件,故不推薦此寫法。
使用等號( =
)初始化:複製初始化(copy
initialization)。
否則為直接初始化(direct initialization)。
string
物件的操作(HomeWork)string
物件string
物件getline()
讀取一整列empy()
與 size()
操作string::size_type
型態string
物件的比較string
物件的賦值操作string
物件的相加string
物件的相加string
物件中的字元iterator
)迭代器功能:可遍歷容器內元素的數據型態。
迭代器類似指標,可用來指向容器內的某個元素(如第一個元素或最後一個元素後面的元素)。
string
與 vector
可透過[下標]
來取得字元與元素。但實際上,C++很少透過下標來進行存取,一般來說都是採用『迭代器』進行遍歷操作。
透過迭代器可讀取元素值、修改容器中(如
list
、map
等容器)某個迭代器所指向的元素值。
也可進行 ++
、–-
的操作:從容器中某個元素移到另一個元素。
C++ 標準程式庫為每一個容器定義了對應的迭代器型態。
std::vector<int> iv = {100, 200, 300}; // 定義容器。
std::vector<int>::iterator iter; // 定義 vector<int>::iterator 型態的迭代器iter。
begin/end
與反向迭代器 rbegin/rend
操作每一個容器(如 vector
),都會定義一個名為
begin
與 end
的成員函數:
begin
回傳一個迭代器類型。
iter = iv.begin(); // begin 回傳的迭代器指向容器中第一個元素。
end
回傳一個迭代器類型。
iter = iv.end(); // end 回傳的迭代器指向最尾端元素後面的位置。故指向一個不存在的元素。
若容器為空,則 begin
回傳的迭代器與 end
回傳的迭代器『相同』。
std::vector<int> iv2;
std::vector<int>::iterator iterbegin = iv2.begin();
std::vector<int>::iterator iterend = iv2.end();
if (iterbegin == iterend)
{
std::cout << "The container iv2 is empty." << std::endl;
}
end
回傳的迭代並不指向容器中的任何元素,它比較像是『標示』的概念。
若迭代器從 begin
位置開始遍歷容器中的元素,當
iter
走到 end
的位置,則表示已經遍歷完整個容器的內容。
std::vector<int> iv = {100, 200, 300};
for (std::vector<int>::iterator iter = iv.begin(); iter != iv.end(); ++iter)
{
std::cout << *iter << std::endl;
}
std::list<int> li = {100, 200, 300};
std::list<int>::iterator iterbegin = li.begin();
std::list<int>::iterator iterend = li.end();
for (std::list<int>::iterator i = iterbegin; i != iterend; i++)
{
std::cout << *i << std::endl;
}
// std::cout << *(iterbegin + 1) << std::endl; // 錯誤:迭代器不支援直接加1。
std::list<int>::iterator nextIter = iterbegin;
std::advance(nextIter, 1); // 移動迭代器位置
std::cout << *nextIter << std::endl; // 打印移動後的值
std::list<int>::iterator prevIter = iterend;
std::advance(prevIter, -1); // 移動迭代器位置
std::cout << *prevIter << std::endl; // 打印移動後的值
rbegin
回傳一個反向迭代器類型,指向容器最後一個元素。
rend
回傳一個反向迭代器類型,指向容器第一個元素前面的位置。
std::vector<int> iv = {100, 200, 300};
for (std::vector<int>::reverse_iterator riter = iv.rbegin(); riter != iv.rend(); ++riter)
{
std::cout << *riter << std::endl;
}
*iter
:回傳迭代器 iter
所指向元素的引用。但需確保迭代器指向的是有效的容器元素,故不能指向
end
。
++iter
:與 iter++
是一樣的功能。讓迭代器指向下一個元素。但指向 end
的迭代器不能在進行 ++
操作。
--iter
:與 iter--
是一樣的功能。
std::vector<int> iv = {100, 200, 300};
std::vector<int>::iterator iter;
for (iter = iv.begin(); iter != iv.end(); ++iter)
{
std::cout << *iter << std::endl;
}
// ++iter;
--iter; // 等於iter--。
std::cout << *iter << std::endl; // 300
iter1 == iter2
或
iter1 != iter2
:判斷兩個迭代器是否相等。若兩個迭代器指向的是同一個元素,則相等,否則不相等。
struct
成員的引用:
struct student
{
int num;
};
std::vector<student> sv;
student mystu;
mystu.num = 100;
sv.push_back(mystu); // 將物件mystu『複製』至sv容器中。
mystu.num = 200; // 修改mystu中的內容,並不會對容器中元素值造成影響,因容器中的內容是複製進去的。
std::vector<student>::iterator iter;
iter = sv.begin(); // 指向第一個元素。
std::cout << (*iter).num << std::endl; // 引用的方法
std::cout << iter-> num << std::endl; // 引用的方法
const_iterator
迭代器const_iterator
:該迭代器所指向的元素不能改變,但不代表該迭代器本身不能改變。
故該迭代器只能讀取容器中的元素,不能透過該迭代器修改容器中的元素。
若容器為 const
,則必須使用
const_iterator
,否則會出現錯誤。
const std::vector<int> iv = {100, 200, 300};
std::vector<int>::const_iterator iter;
for (iter = iv.begin(); iter != iv.end(); ++iter) // 這裡的begin與end回傳const_iterator。回傳是iterator還是const_iterator取決於該容器是否為const。
{
std::cout << *iter << std::endl;
}
C++ 11 引入兩個新函數:cbegin
與 cend
,兩成員函數皆回傳 const_iterator
。
std::vector<int> iv = {100, 200, 300};
for (auto iter = iv.cbegin(); iter != iv.cend(); ++iter)
{
*iter = 99; // 錯誤:無法賦值。
}
任何一種改變容器物件大小的操作,如
push_back
,都會使當下的容器物件的迭代器失效(無法代表或指向容器的元素)。
不同容器實作的原理不同(例如有些容器內部數據是連續儲存,當插入元素時一旦原有的記憶體空間不夠用,則可能會導致容器中原有數據全部遷移至新的記憶體空間位置。例如:vector),不同的插入操作,或是不同的插入位置,會導致迭代器、指標、參考等產生失效。
當進行刪除操作時:若從容器刪除元素,則當下指向這個被刪除的迭代器、指標與參考則會失效。
迭代器強大的地方在於它們與 C++ 標準程式庫中的算法(位於
<algorithm>
標頭檔中)的結合使用。
迭代器允許以幾乎相同的方式對所有容器進行排序、搜索、變換等操作。例如,使用
std::sort
對
std::vector
進行排序:
#include <algorithm>
#include <vector>
#include <iostream>
int main(int argc, char const *argv[])
{
std::vector<int> vec = {5, 3, 1, 4, 2};
// 使用 std::sort 進行排序
std::sort(vec.begin(), vec.end());
for (int num : vec)
{
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
#include <iostream>
#include <map>
#include <string>
int main() {
std::map<std::string, int> myMap = {{"apple", 2}, {"banana", 3}, {"cherry", 5}};
// 使用迭代器遍歷 map
for (std::map<std::string, int>::iterator it = myMap.begin(); it != myMap.end(); ++it) {
std::cout << it->first << ": " << it->second << std::endl;
}
return 0;
}