文章目錄
- 一、數組
- 1.一維數組
- 2.多維數組
- 二、vector
- 三、string
一、數組
1.一維數組
??在C++中,數組用于存儲具有相同類型和特定大小的元素集合。數組在內存中是連續存儲的,并且支持通過索引快速訪問元素。
數組的聲明:
??數組的聲明指定了元素的類型和數組的長度。
// 聲明一個整型數組,具有10個元素
int arr[10];// 聲明并初始化整型數組
int arr[] = {1, 2, 3, 4, 5};
注意:不要使用
extern
指針來聲明數組,應該extern int array[];
即Unknown Bounded Array 聲明錯誤示例:
`file1.cpp`: int array[3] = {1, 2, 3};`file2.cpp`://正確寫法 extern int array[];`file2.cpp`://錯誤寫法 extern int* array;
數組初始化:
??數組的初始化可以是隱式的,也可以是顯式的。
-
缺省初始化
在C++中,如果數組是非靜態的局部數組,其元素將包含未定義的值,因為它們沒有默認初始化。全局數組和靜態數組將會被默認初始化為零。
void function() {int localArray[10]; // 未定義的值static int staticArray[10]; // 初始化為0 }
-
聚合初始化( aggregate initialization )
//使用大括號{} int arr[] = {1, 2, 3, 4, 5}; //根據初始化列表推導出int[5]
注意事項:
-
不能使用
auto
來聲明數組類型auto b = {1,2,3}; //b推導出的類型為std::initializer_list<int>int c[3] = {1,2,3}; auto d = c; //d推導出的類型為int*,類型退化了
-
C++中,數組(內置數組)不能直接復制。因為它們沒有像類或結構體那樣的拷貝構造函數或賦值運算符
-
賦值操作:對于數組,你不能使用簡單的賦值操作(
=
)來復制數組。例如,以下代碼是錯誤的:int arr1[10] = {1, 2, 3}; int arr2[10]; arr2 = arr1; // 錯誤:數組之間不能使用賦值操作符
-
拷貝構造函數:傳統數組沒有拷貝構造函數,這意味著你不能通過構造函數來創建一個數組的深拷貝
盡管傳統數組不能直接通過賦值或拷貝構造函數來復制,你仍然可以通過其他方法來復制數組的內容。
-
手動復制:使用循環手動逐個復制數組的每個元素
-
std::copy:使用C++標準庫中的
<algorithm>
頭文件提供的std::copy
函數 -
拷貝構造函數(對于數組類型的對象):如果你有一個包含數組的類或結構體,你可以定義一個拷貝構造函數來復制數組。
struct MyStruct {int arr[10];MyStruct(const MyStruct& other) {std::copy(other.arr, other.arr + 10, arr);} };
-
C++11標準庫容器:如
std::array
或std::vector
提供了拷貝構造函數和賦值運算符來復制其內容。std::array<int, 10> arr1 = {{1, 2, 3}}; std::array<int, 10> arr2 = arr1; // 使用std::array的拷貝構造函數std::vector<int> vec1 = {1, 2, 3}; std::vector<int> vec2 = vec1; // 使用std::vector的拷貝構造函數
-
-
元素個數必須是常量表達式(編譯期可計算的值)
-
字符串數組的特殊性
char str[] = "Hello"; //char[6] char str[] = {'H', 'e', 'l', 'l', 'o'}; //char[5] char str[] = {'H', 'e', 'l', 'l', 'o', '\0'}; //char[6]
使用字符串字面量初始化字符數組:
char str[] = "Hello"; // char[6]
在這行代碼中,
"Hello"
是一個字符串字面量,它包含了字符串"Hello"
以及一個隱式的空字符\0
,用作字符串的終止符。因此,當你使用這個字符串字面量來初始化字符數組str
時,數組的大小是6個字符,包括5個可見字符和一個空字符。使用字符字面量初始化字符數組:
char str[] = {'H', 'e', 'l', 'l', 'o'}; // char[5]
在這個例子中,使用花括號
{}
包圍的字符字面量列表來初始化字符數組str
。這種方式不會自動添加空字符\0
,因此初始化后的數組str
將包含5個字符,即'H'
,'e'
,'l'
,'l'
, 和'o'
。數組的實際大小是5個字符。注意事項:
- 當使用字符串字面量初始化字符數組時,編譯器會自動在末尾添加一個空字符
\0
,所以數組需要有足夠的空間來存儲這個額外的字符。 - 當使用字符字面量列表初始化字符數組時,需要確保數組有足夠的空間來存儲所有字符,且不會自動添加空字符
\0
。如果你需要一個以空字符終止的字符串,必須手動添加\0
。 - 字符串字面量和字符字面量列表在初始化字符數組時的行為不同,這可能會影響程序中字符串的處理和字符串函數的使用。
- 當使用字符串字面量初始化字符數組時,編譯器會自動在末尾添加一個空字符
數組(數組到指針的隱式轉換):
??數組名在大多數表達式中會被解釋為指向數組首元素的指針。使用數組對象時,通常情況下會產生數組到指針的隱式轉換,隱式轉換會丟失一部分類型信息,可以通過聲明引用來避免隱式轉換。

int arr[10];
int* ptr = arr; // ptr是指向數組首元素的指針,數組到指針的隱式轉換auto& b = arr; //聲明引用來避免隱式轉換
由于數組名可以作為指針使用,所以可以進行指針運算。
#include <iostream>int main()
{int arr[10];for (int* ptr = arr; ptr < arr + 10; ++ptr) {// 操作數組元素*ptr = *ptr + 1; //1}
}
指針數組與數組指針:
-
指針數組
指針數組是一個包含多個指針的數組,每個指針可以指向不同的對象。
語法:指針數組在聲明時,指針符號
*
放在數組名的前面。int* arr[3];
-
數組指針
數組指針是指向數組的指針。這種指針指向整個數組,而不是數組中的單個元素。
語法:數組指針在聲明時,指針符號
*
放在數組類型后面,使用括號括起來// 創建一個整型數組 int arr[] = {10, 20, 30};// 創建一個指向數組的指針 int (*ptrToArr)[3] = &arr;
ptrToArr
是一個指向數組的指針,它指向一個包含3個int
的數組。ptrToArr
不是一個指向int
的指針,而是指向整個數組。
注意:
- 當傳遞數組作為函數參數時,實際上傳遞的是指向數組首元素的指針,而不是整個數組。
- 數組的生命周期和存儲空間必須在數組指針使用期間保持有效。
- 指針數組和數組指針在內存布局和訪問方式上有所不同,需要根據具體需求選擇合適的類型。
聲明數組的引用:(C++中沒有引用數組的概念)
??聲明數組的引用意味著創建一個引用,它指向整個數組。數組引用在函數參數傳遞、大型數據結構的傳遞等方面非常有用,因為它們允許函數直接操作傳入的數組,而不是數組的副本。
int (&arr)[5] = data; // 假設 'data' 是一個已定義的數組
數組作為函數參數時,由于數組不能直接拷貝,通常會退化為指針。為了避免這種情況,可以使用數組引用作為函數參數。
void printArray(int (&arr)[5]) {for (int i = 0; i < 5; ++i) {std::cout << arr[i] << " ";}std::cout << std::endl;
}int data[5] = {1, 2, 3, 4, 5};
printArray(data);
printArray
函數接受一個數組的引用作為參數。這意味著函數可以直接訪問和修改數組 data
中的元素。如果想限制數組中的元素不能修改,則void printArray(const int (&arr)[5])
。
注意:
- 數組引用必須在聲明時被初始化。
- 數組引用的大小(數組的長度)必須是已知的,因此它們不能指向未指定大小的數組。
- 數組引用通常用于函數參數,以便安全地傳遞數組并避免數組退化為指針
數組的元素訪問:
-
數組對象是一個左值(如果作為右值使用,類型會發生隱式轉換:
int[3] --> int*
)C++中左值與右值:
在C++中,表達式可以根據它們是否可以取得內存地址被分為兩類:l-value(左值)和r-value(右值)。
-
l-value:l-value是指具有內存地址的表達式,這個內存地址可以被讀取和寫入。l-value通常用在賦值表達式的左邊
l-value的例子:
- 變量:
int a;
- 數組元素:
int arr[10]; arr[0];
- 函數參數:當函數參數是一個引用類型時,它是一個l-value。
- 位域:當位域是一個對象成員時。
- 變量:
-
r-value:r-value是指那些沒有內存地址或者有臨時內存地址的表達式,它們不能被賦值,因為它們的存在時間很短暫。r-value通常用在賦值表達式的右邊,比如字面量、臨時對象、表達式的結果等。
以下是一些r-value的例子:
- 字面量:
10
- 表達式:
a + b
- 函數返回值:除非函數返回一個引用,否則返回的是一個r-value。
int b = 20; // b是一個l-value int c = a + b; // a + b是一個r-value
- 字面量:
-
-
數組訪問
使用時數組名通常會轉換成相應的指針類型(指向數組首元素的指針),本質上:
arr[index] = *(arr + index)
,實際上是利用指針運算來訪問數組元素int arr[5] = {1, 2, 3, 4, 5}; int firstElement = arr[0]; // 獲取第一個元素,值為1 int thirdElement = arr[2]; // 獲取第三個元素,值為3
-
范圍檢查
C++不提供自動的數組范圍檢查,所以訪問數組元素時應該確保索引不會超出數組的界限。
int arr[5]; for (int i = 0; i < 5; ++i) {// 安全的訪問 arr[i] }
-
越界訪問
如果嘗試訪問超出數組實際大小的索引,將導致未定義行為,這可能引發程序錯誤,如數組越界錯誤、野指針引用等
獲得指向數組開頭與結尾的指針 :
int a[3] = {1,2,3};獲得指向數組開頭的指針:a &(a[0]) std::begin(a) std::cbegin(a)
獲得指向數組結尾的下一個元素的指針:a+3 &(a[3]) std::end(a) std::cend(a)std::begin(a)與std::end(a)獲得的類型為int*
std::cbegin(a)與std::cend(a)獲得的類型為const int*
指針算術運算:
-
增加、減少
指向數組下一位置元素的指針
-
比較
如果參數比較運算的指針是指向一個數組中的兩個位置 ,是可以進行比較運算的;如果指向的是不同數組,不建議這樣做,可能產生未定義的行為
-
求距離
#include <iostream>int main() {int a[3] = {1, 2, 3};auto ptr = a;auto ptr2 = a +3;std::cout << ptr2 - ptr << std::endl; //3std::cout << ptr - ptr2 << std::endl; //-3 }
-
解引用
-
指針索引
指針索引使用方括號
[]
來指定偏移量,從而訪問指針指向的內存位置之后的某個位置。
數組的其他操作:
-
求元素個數
以下三種方法都是對數組進行操作(獲取數組的定義),而不是指針
sizeof
方法: C語言方式,有危險,不推薦std::size
方法(C++17及以后):推薦的方式std::end() - std::begin()
方法:在運行期進行指針運算,但其實數組信息在編譯器就可以看到,增加了運行期負擔,不推薦
#include <iostream>int main() {int a[3];std::cout << sizeof(int) << std::endl; //4std::cout << sizeof(a) << std::endl; //12//sizeof()方法求元素個數std::cout << sizeof(a)/sizeof(int) << std::endl; //3//std::size() C++17// std::cout << std::size(a) << std::endl;//end-beginstd::cout << std::end(a) - std::begin(a) << std::endl; //3 }
-
元素遍歷
- 基于元素個數
- 基于?begin/?end
- 基于
range-based
for循環:語法糖
#include <iostream>int main() {int a[3] = {2, 3, 5};//基于元素個數--C++17size_t index = 0;while(index < std::size(a)){std::cout << a[index] << std::endl;index = index + 1;}//基于(c)begin/(c)endauto ptr = std::cbegin(a);while(ptr != std::cend(a)){std::cout << *ptr << std::endl;ptr = ptr + 1;}//基于`range-based ` for循環for (int x : a){std::cout << x << std::endl;} }
數組–C字符串:
- C 字符串本質上是字符數組
- C 語言提供了額外的函數來支持 C 字符串相關的操作 :
strlen
,strcmp
…,這些操作都需要’\0’,知道C字符串到哪里結束strlen()
:統計長度直到找到’\0’
2.多維數組
??多維數組本質上是數組的數組。其內存布局仍是連續存儲。
int x[3][4]; //x是一個數組,包含3個元素,x的每個元素是一個int[4]數組。
x[0]; //類型是int(&)[4]
聲明多維數組:
??多維數組的聲明涉及指定每個維度的大小。在C++中,通常使用方括號來聲明多維數組。
// 聲明一個3x3的二維整型數組
int multiArray[3][3];// 聲明一個5x10的二維整型數組
int anotherArray[5][10];
初始化多維數組:
??初始化時,每個維度的元素應該用花括號 {}
包圍,維度之間的元素應該用逗號 ,
分隔。
-
缺省初始化
-
聚合初始化
一層大括號與多層大括號
//其余元素默認初始化為0
int x2[3][4] = {1, 2, 3, 4, 5, 6};//其余元素默認初始化為0
int x3[3][4] = {{1, 2, 3, 4}, {5, 6, 7, 8}};//其余元素默認初始化為0
int x3[3][4] = {{1, 2, 3, 4}, {5, 6, 7}};// 初始化一個3x3的二維數組
int multiArray[3][3] = {{1, 2, 3},{4, 5, 6},{7, 8, 9}
};// 使用缺省初始化
int defaultArray[3][3] = {};
多維數組只能省略最高位的維度如:
int x[][3]
,但是不建議這么干
多維數組索引與遍歷:
-
使用多個中括號來索引,訪問多維數組元素
int value = multiArray[1][2]; // 獲取第二行第三列的元素,值為6
-
使用多重循環來遍歷多維數組
#include <iostream>int main() {int multiArray[3][3] = {{1, 2, 3},{4, 5, 6},{7, 8, 9}};//基于`range-based` for循環for (auto& p : multiArray) //auto& 非常重要{for (auto q : p){ std::cout << q << std::endl;}} }
下面分析
for (auto& p : multiArray)
與for (auto p : multiArray)
的區別
指針與多維數組:
??在C++中,多維數組的數組名可以被看作一個指向數組首元素的指針。可以使用指針運算來操作多維數組。
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*ptrToRow)[3] = arr; // ptrToRow是一個指向具有3個整數的數組的指針// 訪問第一行的第二個元素
int value = (*ptrToRow)[1]; // 等于 arr[0][1],值為2
-
多維數組可以隱式轉換為指針,但只有最高維會進行轉換,其它維度的信息會被保留。
-
使用類型別名來簡化多維數組指針的聲明
#include <iostream>using A = int[4]; int main() {int x[3][4];A* ptr = x; }
-
使用指針來遍歷多維數組
#include <iostream>int main() {int x2[3][4] = {};auto ptr = std::begin(x2);while(ptr != std::end(x2)){auto ptr2 = std::begin(*ptr);while(ptr2 != std::end(*ptr)){ptr2 = ptr2 + 1;}ptr = ptr + 1;} }
二、vector
??C++標準庫中的 std::vector
是一個序列容器,是 C++ 標準庫中定義的一個類模板。與內建數組相比,std::vector
提供了更多的功能,例如自動調整大小、范圍檢查和一些有用的成員函數(更側重易用性)。std::vector
可復制、可在運行期動態改變元素個數(內建數組不可以)。
構造與初始化:
-
缺省初始化
-
聚合初始化
-
其他初始化方式–構造函數
std::vector<T,Allocator>::vector,根據參數調用不同的構造函數
std::vector<int> vec; // 默認構造
std::vector<int> vec(10, 1); // 10個整數初始化為1
std::vector<int> vec = {1, 2, 3}; // 列表初始化
其他方法:
-
獲取元素個數、判斷元素是否為空、容量
#include <iostream> #include <vector>int main() {std::vector<int> x(3, 1);//獲取元素個數--3std::cout << x.size() <<std::endl;//判斷元素是否為空,空返回1,不空返回0std::cout << x.empty() <<std::endl;//容量--3std::cout << x.capacity() <<std::endl;}
-
插入元素
vec.push_back(4); // 在末尾添加一個元素 vec.push_back({5, 6, 7}); // 從初始列表添加多個元素
-
刪除元素
vec.pop_back(); // 刪除最后一個元素 vec.erase(vec.begin() + 1); // 刪除第二個元素
-
vector
比較逐個比較兩個vector的元素,如果元素相同,則比較下一個元素;如果第一個元素不同,則由第一個元素的大小決定;
-
清空
vec.clear(); // 刪除所有元素,保留容量
-
修改容器大小
vec.resize(5); // 改變vec的大小為5,如果縮小則可能刪除元素 vec.resize(7, 1); // 如果需要,用1填充額外的空間以改變大小為7
-
元素操作
vec.front(); // 返回第一個元素 vec.back(); // 返回最后一個元素
vector 中元素的索引與遍歷:
-
[]
與at
實現vector 中元素的索引std::vector<int> vec(10, 1); // 10個整數初始化為1 int firstElement = vec[0]; // 獲取第一個元素 vec[1] = 20; // 設置第二個元素的值//at方法 vec.at(2) = 30;
-
?begin / ?end 函數 V.S. ?begin / ?end 方法
#include <iostream> #include <vector>int main() {std::vector<int> x1 = {1, 2, 3};//begin、end函數auto p = std::begin(x1);while(p != std::end(x1)){std::cout << *p << std::endl;p = p + 1;}//begin、end方法auto q = x1.begin(); //返回指向第一個元素的迭代器,迭代器解引用也可以獲取元素的值while(q != x1.end()){std::cout << *q << std::endl;q = q + 1;}//forfor (auto x : x1){std::cout << x << std::endl;} }
vector
中?begin / ?end 方法返回的是隨機訪問迭代器迭代器:
-
模擬指針行為
-
包含多種類別,每種類別支持的操作不同
-
vector 對應隨機訪問迭代器
-
解引用與下標訪問
-
移動
-
兩個迭代器相減求距離
-
兩個迭代器比較
這兩個迭代器必須指向相同的vector
-
-
vector相關的其他內容:
-
添加元素可能使迭代器失效
-
多維 vector
如:
std::vector<std::vector<int>>
-
從
.
到->
操作符#include <iostream> #include <vector>int main() {std::vector<int> x = {1, 2, 3};std::vector<int>* ptr = &x;std::cout << x.size() << std::endl; //3std::cout << ptr->size() << std::endl; //3 }
-
vector 內部定義的類型
-
size_type
-
iterator
/const_iterator
-
注意事項:
std::vector
會根據需要自動調整大小,但可能會引起性能開銷,因為它可能需要重新分配內存和復制元素。- 訪問
std::vector
中的元素時,要注意范圍檢查,避免越界訪問。- 與原始數組相比,
std::vector
更安全且易于使用,但可能會占用更多的內存,并且訪問速度可能稍慢。
三、string
??std::string
是標準庫中的一個類模板實例化,它提供了一個可變長度的字符序列,用于內建字符串的代替品。與內建字符串相比,更側重于易用性。string
可復制、可在運行期動態改變字符個數。
Type | Definition |
---|---|
std::string | std::basic_string |
參考:std::basic_string
構造與初始化:
#include <string>std::string str; // 默認構造,創建一個空字符串
std::string str("Hello, World!"); // 直接初始化
std::string str(10, 'A'); // 初始化為10個字符'A'
string其他方法:
-
訪問元素
char ch = str[0]; // 獲取第一個字符,與C風格字符串不同,這是安全的
-
添加和修改字符串
str += "追加的字符串"; // 追加字符串 str.append("追加的字符串"); // 另一種追加方式 str.insert(1, "插入的字符串"); // 在指定位置插入字符串 str.replace(1, 2, "新字符串"); // 替換部分字符串 str.erase(1, 4); // 刪除從索引1開始的4個字符 str.back() = '!'; // 修改最后一個字符
-
尺寸相關方法(size/length)
#include <iostream> #include <vector>int main() {std::string str= "Hello World!";size_t size = str.size();size_t len = str.length(); // 獲取字符串長度std::cout << size << std::endl;std::cout << len << std::endl;str.resize(10); // 改變字符串長度為10std::cout << str.size() << std::endl;std::cout << str.length() << std::endl; }
-
查找和比較
size_t pos = str.find("sub"); // 查找子串"sub"的位置 bool isEqual = str.compare("anotherString"); // 比較字符串
-
子字符串
std::string sub = str.substr(1, 4); // 獲取從索引1開始的4個字符的子字符串
-
清空
str.clear(); // 刪除所有字符,保留容量
-
交換
str.swap(anotherStr); // 交換兩個字符串的內容
-
轉換為C字符串,返回一個指向’\0’結束字符數組的指針
const char* cstr = str.c_str(); // 獲取C風格的字符數組
注意事項:
std::string
是一個類模板的實例化,所以它不是像原始數組那樣的低級類型。- 字符串字面量(如
"Hello"
)是const char
類型的數組,它們在C++中被定義為const
,因此不能被修改。- 當需要C風格的字符串時,可以使用
c_str()
成員函數來獲取,但要注意,這個操作可能涉及內存分配,因此不應該頻繁調用。std::string
提供了范圍檢查的訪問方式,避免了數組越界的問題