C++STL學習第一篇(什麼是STL以及string的各種功能用法)

ivanlee717發表於2024-03-06

STL

STL提供了六大元件,彼此之間可以組合套用,這六大元件分別是:容器、演算法、迭代器、仿函式、介面卡、空間配置器。

  1. 資料結構和容器管理:STL 提供了多種資料結構和容器,如向量(vector)、連結串列(list)、集合(set)、對映(map)等。這些容器可以幫助程式設計師方便地儲存和管理資料,根據需求進行動態調整和操作。
  2. 演算法和資料處理:STL 中提供了大量的演算法,如排序、查詢、遍歷等,這些演算法可以直接應用於不同型別的容器,幫助程式設計師高效地對資料進行處理和操作。
  3. 迭代器和訪問控制:STL 中的迭代器提供了統一的訪問介面,使得程式設計師能夠方便地遍歷容器中的元素並進行讀寫操作。迭代器可以靈活地控制訪問範圍和方式,為資料訪問提供了高度的靈活性。
  4. 泛型程式設計:STL 的設計基於泛型程式設計思想,透過模板機制實現了通用的資料結構和演算法。這使得 STL 能夠適用於各種資料型別,並且支援使用者自定義型別的操作,從而提高了程式碼的重用性和可擴充套件性。
  5. 效能最佳化:STL 中的容器和演算法都經過高度最佳化,使用 STL 可以藉助這些最佳化的實現來提高程式的執行效率和效能。

總的來說,STL 適用於需要資料結構、演算法和迭代器等功能的各種場景。它為 C++ 程式設計師提供了豐富的工具和功能,幫助他們更加高效地處理和運算元據,提高程式碼的可讀性、重用性和效能。無論是簡單的資料儲存管理還是複雜的資料處理和演算法實現,STL 都是一個強大而實用的工具庫。

容器:各種資料結構,如vector、list、deque、set、map等,用來存放資料,從實現角度來看,STL容器是一種class template。

演算法:各種常用的演算法,如sort、find、copy、for_each。從實現的角度來看,STL演算法是一種function tempalte.

迭代器:扮演了容器與演算法之間的膠合劑,共有五種型別,從實現角度來看,迭代器是一種將operator* , operator-> , operator++,operator--等指標相關操作予以過載的class template. 所有STL容器都附帶有自己專屬的迭代器,只有容器的設計者才知道如何遍歷自己的元素。原生指標(native pointer)也是一種迭代器。

仿函式:行為類似函式,可作為演算法的某種策略。從實現角度來看,仿函式是一種過載了operator()的class 或者class template

介面卡:一種用來修飾容器或者仿函式或迭代器介面的東西。

空間配置器:負責空間的配置與管理。從實現角度看,配置器是一個實現了動態空間配置、空間管理、空間釋放的class tempalte.

STL優點

  • STL 是 C++的一部分,因此不用額外安裝什麼,它被內建在你的編譯器之內。

  • STL 的一個重要特點是資料結構和演算法的分離。儘管這是個簡單的概念,但是這種分離使得 STL 變得非常通用。例如:在 STL 的 vector 容器中,可以放入元素、基礎資料型別變數、元素的地址;STL 的 sort() 排序函式可以用來操作 vector,list 等容器。

  • 程式設計師可以不用思考 STL 具體的實現過程,只要能夠熟練使用 STL 就 OK 了。這樣他們就可以把精力放在程式開發的別的方面。

  • STL 具有高可重用性,高效能,高移植性,跨平臺的優點。

STL三大元件

容器

常用的資料結構無外乎陣列(array),連結串列(list),tree(樹),棧(stack),佇列(queue),集合(set),對映表(map),根據資料在容器中的排列特性,這些資料分為序列式容器關聯式容器兩種。

Ø 序列式容器就是容器元素在容器中的位置是由元素進入容器的時間和地點來決定。Vector容器、Deque容器、List容器、Stack容器、Queue容器。

Ø 關聯式容器是指容器已經有了一定的規則,容器元素在容器中的位置由我的規則來決定。Set/multiset容器 Map/multimap容器

演算法

演算法分為:質變演算法非質變演算法

質變演算法:是指運算過程中會更改區間內的元素的內容。例如複製,替換,刪除等等

非質變演算法:是指運算過程中不會更改區間內的元素內容,例如查詢、計數、遍歷、尋找極值等等

迭代器

迭代器(iterator)是一種抽象的設計概念,現實程式語言中並沒有直接對應於這個概念的實物。在<>一書中提供了23中設計模式的完整描述,其中iterator模式定義如下:提供一種方法,使之能夠依序尋訪某個容器所含的各個元素,而又無需暴露該容器的內部表示方式。

迭代器的設計思維-STL的關鍵所在,STL的中心思想在於將資料容器(container)和演算法(algorithms)分開,彼此獨立設計,最後再一貼膠著劑將他們撮合在一起。從技術角度來看,容器和演算法的泛型化並不困難,c++的class template和function template可分別達到目標,如果設計出兩這個之間的良好的膠著劑,才是大難題。

迭代器的種類:

輸入迭代器 提供對資料的只讀訪問 只讀,支援++、==、!=
輸出迭代器 提供對資料的只寫訪問 只寫,支援++
前向迭代器 提供讀寫操作,並能向前推進迭代器 讀寫,支援++、==、!=
雙向迭代器 提供讀寫操作,並能向前和向後操作 讀寫,支援++、--,
隨機訪問迭代器 提供讀寫操作,並能在資料中隨機移動 讀寫,支援++、--、[n]、-n、<、<=、>、>=
void test2() {
	vector<int>r;//STL 中的標準容器之一 :動態陣列
	r.push_back(1);//vector 容器提供的插入資料的方法
	r.push_back(2);
	r.push_back(7);
	//vector 容器提供了 begin()方法 返回指向第一個元素的迭代器
	//vector 容器提供了 end()方法 返回指向最後一個元素下一個位置的迭代器
	vector<int>::iterator reg = r.begin();
	vector<int>::iterator ina = r.end();//這是一種隨機訪問型別的迭代器
	while (reg != ina) {
		cout << *reg << " ";
		reg++;
	}
	cout << endl;

	//演算法 count 演算法 用於統計元素的個數
	int total = count(reg, ina, 5);
	cout << "total:" << total << endl;
}

image-20240306181244915

STL 容器不單單可以儲存基礎資料型別,也可以儲存類物件

class Regina {
public:
	Regina(int a) : age(a) {};
	int age;
	~Regina(){};
};
vector<Regina>r;
Regina r1(1), r2(2), r3(7);
r.push_back(r1);
r.push_back(r2);
r.push_back(r3);
vector<Regina>::iterator pStart = r.begin();
vector<Regina>::iterator pEnd = r.end();
while (pStart != pEnd) {
	cout << "pStart->age: " << pStart->age << " ";
	pStart++;
}
cout << endl;

image-20240306183444331

還可以儲存類的指標

在 C++ 中,當我們使用迭代器遍歷容器時,迭代器本身是一個指向容器中元素的物件,而不是實際的元素本身。因此,為了訪問容器中儲存的元素,我們需要透過解引用運算子 * 來獲取迭代器指向的實隕物件.具體的vector怎麼用在下一篇

vector<Regina*> v;//儲存 Teacher 型別指標
Regina* r1 = new Regina(1);
Regina* r2 = new Regina(2);
Regina* r3 = new Regina(7);
v.push_back(r1);
v.push_back(r2);
v.push_back(r3);
//拿到容器迭代器
vector<Regina*>::iterator pStart = v.begin();
vector<Regina*>::iterator pEnd = v.end();
//透過迭代器遍歷
while (pStart != pEnd) {
	cout << (*pStart)->age << " ";
	pStart++;
}
cout << endl;

常用容器講解

string

C風格字串(以空字元結尾的字元陣列)太過複雜難於掌握,不適合大程式的開發,所以C++標準庫定義了一種string類,定義在標頭檔案

String和c風格字串對比:

  • Char是一個指標,String是一個類

    string封裝了char,管理這個字串,是一個char型的容器。

  • String封裝了很多實用的成員方法

    查詢find,複製copy,刪除delete 替換replace,插入insert

  • 不用考慮記憶體釋放和越界

    string管理char*所分配的記憶體。每一次string的複製,取值都由string類負責維護,不用擔心複製越界和取值越界等。

image-20240306184831213

string regina1("regina");
string regina2(7, '$');
string regina3(regina1);
cout << regina1 << endl;  // 輸出 "regina"
cout << regina2 << endl;  // 輸出 "$$$$$$$"
cout << regina3 << endl;  // 輸出 "regina"
regina1 += "baby";
cout << regina1 << endl;  // 輸出 "reginababy"
string regina4 = regina1 + regina3;
char arr[] = "arr loop";
string regina5(arr, 2);
string regina6(arr + 1, arr + 3);
cout << regina1 << endl;  // 輸出 "reginababy"
cout << regina4 << endl;  // 輸出 "reginababyregina"
cout << regina5 << endl;  // 輸出 "ar"
cout << regina6 << endl;  // 輸出 "rr"
string regina7(&regina4[1], &regina4[4]);
string regina8(regina4, 1, 5);
cout << regina7 << endl;  // 輸出 "egi"
cout << regina8 << endl;  // 輸出 "egina"

基本賦值操作

string& operator=(const char* s):將 char* 型別的字串賦值給當前的字串。
string& operator=(const string& s):將另一個字串物件 s 賦值給當前的字串。
string& operator=(char c):將單個字元 c 賦值給當前的字串。
string& assign(const char* s):將 char* 型別的字串 s 賦值給當前的字串。
string& assign(const char* s, int n):將 char* 型別的字串 s 的前 n 個字元賦值給當前的字串。
string& assign(const string& s):將另一個字串物件 s 賦值給當前的字串。
string& assign(int n, char c):使用 n 個字元 c 來賦值給當前的字串。
string& assign(const string& s, int start, int n):將字串 s 從位置 start 開始的 n 個字元賦值給當前的字串。
	string s1, s2, s3;

	// 使用賦值運算子過載將 char* 型別的字串賦值給當前的字串
	s1 = "regina, baby!";
	cout << "s1: " << s1 << endl;

	// 使用賦值運算子過載將另一個字串物件賦值給當前的字串
	s2 = s1;
	cout << "s2: " << s2 << endl;

	// 使用 assign() 成員函式將 char* 型別的字串賦值給當前的字串
	s3.assign("regina", 4);
	cout << "s3: " << s3 << endl;

	// 使用 assign() 成員函式將另一個字串物件賦值給當前的字串
	s1.assign(s3);
	cout << "s1: " << s1 << endl;

	// 使用 assign() 成員函式使用 n 個字元 c 賦值給當前的字串
	s2.assign(8, '*');
	cout << "s2: " << s2 << endl;

	// 使用 assign() 成員函式將字串 s 的子串賦值給當前的字串
	string s4 = "regina, baby!";
	string s5;
	s5.assign(s4, 7, 5); // 從索引 7 開始的 5 個字元
	cout << "s5: " << s5 << endl;

image-20240306194226311

存取字串

  1. char& operator[](int n):透過過載 [] 運算子來獲取字串中索引為 n 的字元。這種方式不進行邊界檢查,如果訪問超出字串範圍的位置,可能導致未定義行為。
  2. char& at(int n):透過 at 方法來獲取字串中索引為 n 的字元。與使用 operator[] 不同的是,at 方法會進行邊界檢查,如果訪問超出字串範圍的位置,會丟擲 out_of_range 異常。
string r = "regina";
// 使用 operator[] 獲取字元
cout << "Character at index 7 (using operator[]): " << r[5] << endl;

// 使用 at 方法獲取字元
try {
	cout << "Character at index 3 (using at): " << r.at(3) << endl;
	cout << "Character at index 13 (using at): " << r.at(13) << endl;
}
catch (const out_of_range& e) {
	cerr << "Exception caught: " << e.what() << endl;
}

image-20240306195945777

cerr 是 C++ 中的一個輸出流物件,它與標準錯誤流 (stderr) 相關聯。與標準輸出流 (cout) 不同,標準錯誤流用於輸出程式中的錯誤訊息和診斷資訊。

cerr 物件通常會直接將訊息輸出到終端或日誌檔案,而不會進行緩衝操作。這意味著錯誤訊息會立即顯示在終端上,而不需要等待緩衝區重新整理。因此,cerr 在處理錯誤和除錯資訊時非常有用。

拼接字串

string& operator+=(const string& str), string& operator+=(const char* str), string& operator+=(const char c):這些是過載 += 運算子的方法,用於將另一個字串、C風格字串或字元連線到當前字串的末尾。

string& append(const char *s):將 C 風格的字串 s 追加到當前字串的末尾。

string& append(const char *s, int n):將 C 風格的字串 s 的前 n 個字元追加到當前字串的末尾。

string& append(const string& s):將字串 s 追加到當前字串的末尾,與 operator+= 的功能類似。

string& append(const string& s, int pos, int n):將字串 s 中從索引 pos 開始的 n 個字元追加到當前字串的末尾。

string& append(int n, char c):在當前字串的末尾新增 n 個字元 c。
string str = "Hello";

    // 使用 += 運算子連線字串
    str += ", world!";
    cout << "After +=: " << str << endl;

    // 使用 append(const char*) 連線字串
    str.append(" This is a test.");
    cout << "After append(const char*): " << str << endl;

    // 使用 append(const string&) 連線字串
    string additional = " Have a nice day!";
    str.append(additional);
    cout << "After append(const string&): " << str << endl;

    // 使用 append(const char*, int) 連線部分字串
    str.append(" Testing", 4); // 新增 " Test"
    cout << "After append(const char*, int): " << str << endl;

    // 使用 append(int, char) 新增字元
    str.append(3, '!'); // 新增 "!!!"
    cout << "After append(int, char): " << str << endl;

image-20240306200732669

查詢和替換

image-20240306225429552

	string words = "regina baby is my sM baby";
	int pos = words.find("baby");
	cout << pos << '\n';
	pos = words.find("sM", 4, 2);
	cout << pos << '\n';
	pos = words.rfind("baby");
	cout << pos << '\n';

image-20240306230809427

比較操作

/*
compare函式在>時返回 1,<時返回 -1,==時返回 0。
比較區分大小寫,比較時參考字典順序,排越前面的越小。
大寫的A比小寫的a小。
*/
int compare(const string& s) const;//與字串s比較
int compare(const char *s) const;//與字串s比較

	string s1 = "regina";
	string s2 = "ivanlee";
	cout << "s1.compare(s2): " << s1.compare(s2) << "\n";
	const char* cs2 = "leeivan";
	cout << "s2.compare(cs2): " << s2.compare(cs2) << "\n";

image-20240306231206035

插入和刪除

string& insert(int pos, constchar* s); //插入字串
string& insert(int pos, conststring& str); //插入字串
string& insert(int pos, int n, char c);//在指定位置插入n個字元c
string& erase(int pos, int n = npos);//刪除從Pos開始的n個字元 
	string s = "hello regina";
	s.insert(5, "beautiful");
	cout << s << "\n";
	s.insert(s.length(), 2, '!'); //單引號
	cout << s << "\n";
	s.erase(5, 3);
	cout << s << "\n";

image-20240306231938091

string和c-style字串轉換

	string s = "regina";
	const char* c = s.c_str();//返回的是一個指向常量字元的指標
	cout << "s.c_str(): " << typeid(c).name() << endl;
	const char* c1 = "regina";
	string s1(s);
	cout << "string s1(s); " << typeid(s1).name() << endl;

image-20240306232810188

在c++中存在一個從const char*到string的隱式型別轉換,卻不存在從一個string物件到C_string的自動型別轉換。對於string型別的字串,可以透過c_str()函式返回string物件對應的C_string.

通常,程式設計師在整個程式中應堅持使用string類物件,直到必須將內容轉化為char*時才將其轉換為C_string.

為了修改string字串的內容,下標運算子[]和at都會返回字元的引用。但當字串的記憶體被重新分配之後,可能發生錯誤.

自動擴容

在 C++ 的標準庫中,std::string 類具有自動調整大小的功能。當你向 std::string 物件新增字元時,如果當前容量不足以儲存新的字元,std::string 會自動重新分配記憶體以擴充套件容量,從而確保可以儲存新的字元並保持字串的有效性。

這種自動調整大小的過程是由 std::string 類內部管理的,開發者無需手動管理字串的記憶體大小。當新增字元導致字串超出當前容量時,std::string 會選擇一個新的更大的記憶體塊,將舊資料複製到新的記憶體塊中,並釋放舊的記憶體塊。

這種自動調整大小的機制使得 std::string 更加方便和易用,開發者可以專注於操作字串內容而不必擔心記憶體管理的細節。

	std::string str = "regina";
	std::cout << "Initial capacity: " << str.capacity() << std::endl;
	// 新增字元,觀察容量的變化
	for (char c = 'a'; c <= 'z'; ++c) {
		str.push_back(c);
		std::cout << "Capacity after adding '" << c << "': " << str.capacity() << std::endl;
	}

std::string 類提供了 capacity() 方法,用於返回當前字串物件的容量大小。容量表示為當前儲存空間的大小,它可能大於等於字串的長度,因為 std::string 可能會分配比實際字串長度更多的記憶體,以便進行後續的新增操作而不需要頻繁地重新分配記憶體。

通常情況下,當你向 std::string 物件新增字元時,如果當前的容量不足以儲存新的字元,std::string 會自動重新分配記憶體以擴充套件容量,從而確保可以儲存新的字元並保持字串的有效性。這樣的設計可以提高效能,因為它避免了頻繁的記憶體分配和釋放操作。

image-20240306233544811

相關文章