文章目錄
- 1 模板
- 1.1 模板的概念
- 1.2 函式模板
- 1.2.1 函式模板語法
- 1.2.2 函式模板注意事項
- 1.2.3 函式模板案例
- 1.2.4 普通函式與函式模板的區別
- 1.2.5 普通函式與函式模板的呼叫規則
- 1.2.6 模板的局限性
- 1.3 類模板
- 1.3.1 類模板語法
- 1.3.2 類模板與函式模板區別
- 1.3.3 類模板中成員函式創建時機
- 1.3.4 類模板物件做函式引數
- 1.3.5 類模板與繼承
- 1.3.6 類模板成員函式類外實作
- 1.3.7 類模板分檔案撰寫
- 1.3.8 類模板與友元
- 1.3.9 類模板案例
- 2 STL初識
- 2.1 STL的誕生
- 2.2 STL基本概念
- 2.3 STL六大組件
- 2.4 STL中容器、演算法、迭代器
- 2.5 容器演算法迭代器初識
- 2.5.1 vector存放內置資料型別
- 2.5.2 Vector存放自定義資料型別
- 2.5.3 Vector容器嵌套容器
- 3 STL常用容器
- 3.1 string容器
- 3.1.1 string基本概念
- 3.1.2 string建構式
- 3.1.3 string賦值操作
- 3.1.4 string字串拼接
- 3.1.5 string查找和替換
- 3.1.6 string字串比較
- 3.1.7 string字符存取
- 3.1.8 string插入和洗掉
- 3.1.9 string子串
- 3.2 vector容器
- 3.2.1 vector基本概念
- 3.2.2 vector建構式
- 3.2.3 vector賦值操作
本博客只為方便后期復習,不做其他用途!
本博客配套視頻: 視頻鏈接
本博客參考博客: 博客鏈接
其他博客鏈接:
1、C++入門篇
2、C++核心篇
本階段主要針對C++泛型編程和STL技術做詳細講解,探討C++更深層的使用
1 模板
1.1 模板的概念
模板就是建立通用的模具,大大提高復用性
模板的特點:
- 模板不可以直接使用,它只是一個框架
- 模板的通用并不是萬能的
1.2 函式模板
- C++另一種編程思想稱為 泛型編程 ,主要利用的技術就是模板
- C++提供兩種模板機制:函式模板和類模板
1.2.1 函式模板語法
函式模板作用:建立一個通用函式,其函式回傳值型別和形參型別可以不具體制定,用一個虛擬的型別來代表,
語法:
template<typename T>
函式宣告或定義
解釋:
- template:宣告創建模板
- typename:表面其后面的符號是一種資料型別,可以用class代替
- T:通用的資料型別,名稱可以替換,通常為大寫字母
#include<iostream>
using namespace std;
//函式模板
//兩個整型交換函式
void swapInt(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
//交換兩個浮點型函式
void swapDouble(double& a, double& b) {
double temp = a;
a = b;
b = temp;
}
//函式模板
template<typename T> //宣告一個模板,告訴編譯器后面代碼中緊跟著的T不要報錯,T是一個通用資料型別
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
void test01() {
float a = 10;
float b = 20;
//兩種方式使用函式模板
//1、自動型別推導
mySwap(a, b);
cout << "a:" << a << endl;
cout << "b:" << b << endl;
cout << "\n";
//2、顯示指定型別
mySwap<float>(a, b);
cout << "a:" << a << endl;
cout << "b:" << b << endl;
}
int main() {
test01();
system("pause");
system("cls");
}
總結:
- 函式模板利用關鍵字 template
- 使用函式模板有兩種方式:自動型別推導、顯示指定型別
- 模板的目的是為了提高復用性,將型別引數化
1.2.2 函式模板注意事項
注意事項:
- 自動型別推導,必須推匯出一致的資料型別T,才可以使用
- 模板必須要確定出T的資料型別,才可以使用
//利用模板提供通用的交換函式
template<class T>
void mySwap(T& a, T& b){
T temp = a;
a = b;
b = temp;
}
// 1、自動型別推導,必須推匯出一致的資料型別T,才可以使用
void test01(){
int a = 10;
int b = 20;
char c = 'c';
mySwap(a, b); // 正確,可以推匯出一致的T
//mySwap(a, c); // 錯誤,推導不出一致的T型別
}
// 2、模板必須要確定出T的資料型別,才可以使用
template<class T>
void func(){
cout << "func 呼叫" << endl;
}
void test02(){
//func(); //錯誤,模板不能獨立使用,必須確定出T的型別
func<int>(); //利用顯示指定型別的方式,給T一個型別,才可以使用該模板
}
int main() {
test01();
test02();
system("pause");
return 0;
}
總結:使用模板時必須確定出通用資料型別T,并且能夠推匯出一致的型別
1.2.3 函式模板案例
案例描述:
利用函式模板封裝一個排序的函式,可以對不同資料型別陣列進行排序
排序規則從大到小,排序演算法為選擇排序
分別利用char陣列和int陣列進行測驗
#include<iostream>
using namespace std;
//實作通用 對陣列進行排序的函式
//從大到小 選擇排序
//測驗 char陣列、 int陣列
//交換函式模板
template<class T>
void mySwap(T& a, T& b) {
T temp = a;
a = b;
b = temp;
}
//排序演算法:選擇排序,每次選擇最大的一個放在前面
template<class T>
void mySort(T arr[], int len) {
for (int i = 0; i < len; i++) {
int max = i; //認定最大值的下標
for (int j = i + 1; j < len; j++) {
if (arr[max] < arr[j]) {
max = j;
}
}
if (max != i) {
//交換max和i元素
mySwap(arr[max], arr[i]);
}
}
}
//提供列印陣列模板
template<class T>
void printArray(T arr[], int len) {
for (int i = 0; i < len; i++) {
cout << arr[i] << " ";
}
cout << endl;
}
void test01() {
//測驗char陣列
char charArr[] = "badcfe";
int num = sizeof(charArr) / sizeof(char);
mySort(charArr, num);
printArray(charArr, num);
}
void test02() {
//測驗int陣列
int intArr[] = { 7,5,1,3,9,2,4,6,8 };
int num = sizeof(intArr) / sizeof(int);
mySort(intArr, num);
printArray(intArr, num);
}
int main() {
test01();
test02();
system("pause");
system("cls");
}
f e d c b a
9 8 7 6 5 4 3 2 1
請按任意鍵繼續. . .
1.2.4 普通函式與函式模板的區別
普通函式與函式模板區別:
- 普通函式呼叫時可以發生自動型別轉換(隱式型別轉換)
- 函式模板呼叫時,如果利用自動型別推導,不會發生隱式型別轉換
- 如果利用顯示指定型別的方式,可以發生隱式型別轉換
#include<iostream>
using namespace std;
//普通函式和函式模板的區別
//1、普通函式呼叫可以發生隱式型別轉換
//2、函式模板 用自動型別推導,不可以發生隱式型別轉換
//3、函式模板 用顯示指定型別,可以發生隱式型別轉換
//普通函式
int myAdd01(int a, int b) {
return a + b;
}
//函式模板
template<class T>
T myAdd02(T a, T b) {
return a + b;
}
void test01() {
int a = 10;
int b = 20;
char c = 'c'; //99
//cout << myAdd01(a, b) << endl;
cout << myAdd01(a, c) << endl;
//1、自動型別推導 不會發生隱式型別轉換
//cout << myAdd02(a, c) << endl; //報錯,無法推匯出一致的T
//2、顯示指定型別 會發生隱式型別轉換
cout << myAdd02<int>(a, c) << endl;
cout << myAdd02<char>(a, c) << endl;
}
int main() {
test01();
system("pause");
system("cls");
}
總結:建議使用顯示指定型別的方式,呼叫函式模板,因為可以自己確定通用型別T
1.2.5 普通函式與函式模板的呼叫規則
呼叫規則如下:
- 如果函式模板和普通函式都可以實作,優先呼叫普通函式
- 可以通過空模板引數串列來強制呼叫函式模板
- 函式模板也可以發生多載
- 如果函式模板可以產生更好的匹配,優先呼叫函式模板
#include<iostream>
using namespace std;
//普通函式與函式模板的呼叫規則
//1、如果函式模板和普通函式都可以呼叫,優先呼叫普通函式
//2、可以通過空模板引數串列 強制呼叫 函式模板
//3、函式模板可以發生函式多載
//4、如果函式模板可以產生更好的匹配,優先呼叫函式模板
void myPrint(int a, int b)
{
cout << "呼叫的普通函式" << endl;
}
template<class T>
void myPrint(T a, T b) {
cout << "呼叫的模板" << endl;
}
template<class T>
void myPrint(T a, T b, T c) {
cout << "呼叫多載的模板" << endl;
}
void test01() {
int a = 10;
int b = 20;
myPrint(a, b); //呼叫的普通函式
//通過空模板引數串列,強制呼叫函式模板
myPrint<>(a, b); //呼叫模板
myPrint<>(a, b, 100); //呼叫多載的模板
//如果函式模板可以產生更好的匹配,優先呼叫函式模板
char c1 = 'a';
char c2 = 'b';
myPrint(c1, c2); //呼叫模板
}
int main() {
test01();
system("pause");
system("cls");
}
呼叫的普通函式
呼叫的模板
呼叫多載的模板
呼叫的模板
請按任意鍵繼續. . .
總結:既然提供了函式模板,最好就不要提供普通函式,否則容易出現二義性
1.2.6 模板的局限性
局限性:模板的通用性并不是萬能的
template<class T>
void f(T a, T b)
{
a = b;
}
在上述代碼中提供的賦值操作,如果傳入的a和b是一個陣列,就無法實作了
template<class T>
void f(T a, T b)
{
if(a > b) { ... }
}
在上述代碼中,如果T的資料型別傳入的是像Person這樣的自定義資料型別,也無法正常運行
因此C++為了解決這種問題,提供模板的多載,可以為這些特定的型別提供具體化的模板
#include<iostream>
using namespace std;
//模板局限性
//特定資料型別 需要用具體方式做特殊實作
class Person {
public:
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
//姓名
string m_Name;
//年齡
string m_Age;
};
//對比兩個資料是否相等函式
template<class T>
bool myCompare(T& a, T& b) {
if (a == b) {
return true;
}
else {
return false;
}
}
//利用具體化Person的版本實作代碼,具體化優先呼叫
template<> bool myCompare(Person& p1, Person& p2) {
if (p1.m_Name == p2.m_Name && p1.m_Age == p2.m_Age) {
return true;
}
else {
return false;
}
}
void test01() {
int a = 10;
int b = 20;
bool ret = myCompare(a, b);
if (ret) {
cout << "a == b" << endl;
}
else {
cout << "a != b" << endl;
}
}
void test02() {
Person p1("Tom", 10);
Person p2("Tom", 10);
bool ret = myCompare(p1, p2);
if (ret) {
cout << "p1 == p2" << endl;
}
else {
cout << "p1 != p2" << endl;
}
}
int main() {
test01();
test02();
system("pause");
system("cls");
}
總結:
- 利用具體化的模板,可以解決自定義型別的通用化
- 學習模板并不是為了寫模板,而是在STL能夠運用系統提供的模板
1.3 類模板
1.3.1 類模板語法
類模板作用:建立一個通用類,類中的成員 資料型別可以不具體制定,用一個虛擬的型別來代表,
語法:
template<typename T>
類
解釋:
- template:宣告創建模板
- typename:表面其后面的符號是一種資料型別,可以用class代替
- T:通用的資料型別,名稱可以替換,通常為大寫字母
#include<iostream>
using namespace std;
//類模板
template<class NameType, class AgeType>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "name:" << this->m_Name << endl;
cout << "age:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01() {
Person<string, int> p1("張三", 18);
cout << p1.m_Name << "," << p1.m_Age << endl;
p1.showPerson();
}
int main() {
test01();
system("pause");
system("cls");
}
張三,18
name:張三
age:18
請按任意鍵繼續. . .
總結:類模板和函式模板語法相似,在宣告模板template后面加類,此類稱為類模板
1.3.2 類模板與函式模板區別
類模板與函式模板區別主要有兩點:
- 類模板沒有自動型別推導的使用方式
- 類模板在模板引數串列中可以有默認引數
#include<iostream>
using namespace std;
//類模板和函式模板區別
template<class NameType, class AgeType = int>
class Person {
public:
Person(NameType name, AgeType age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "name:" << this->m_Name << endl;
cout << "age:" << this->m_Age << endl;
}
NameType m_Name;
AgeType m_Age;
};
void test01() {
//Person p("張三", 18); //1、類模板沒有自動型別推導使用方式
Person<string, int> p("張三", 18);
p.showPerson();
}
//2、類模板在模板引數串列中可以有默認引數
void test02() {
Person<string> p2("李四", 20); //后一個引數默認整型, 默認引數只有類模板有,函式模板沒有
p2.showPerson();
}
int main() {
test01();
test02();
system("pause");
system("cls");
}
name:張三
age:18
name:李四
age:20
請按任意鍵繼續. . .
總結:
- 類模板使用只能用顯示指定型別方式
- 類模板中的模板引數串列可以有默認引數
1.3.3 類模板中成員函式創建時機
類模板中成員函式和普通類中成員函式創建時機是有區別的:
- 普通類中的成員函式一開始就可以創建
- 類模板中的成員函式在呼叫時才創建
#include<iostream>
using namespace std;
//類模板中成員函式創建時機
//類模板中成員函式在呼叫時才去創建
class Person1 {
public:
void showPerson1() {
cout << "Person1 show" << endl;
}
};
class Person2 {
public:
void showPerson2() {
cout << "Person2 show" << endl;
}
};
template<class T>
class MyClass {
public:
T obj;
//類模板中的成員函式
void func1() {
obj.showPerson1();
}
void func2() {
obj.showPerson2();
}
};
void test01() {
MyClass<Person1> m;
m.func1();
//m.func2(); //報錯,Person1沒有func2()函式
MyClass<Person2> n;
n.func2();
}
int main() {
test01();
system("pause");
system("cls");
}
總結:類模板中的成員函式并不是一開始就創建的,在呼叫時才去創建
1.3.4 類模板物件做函式引數
學習目標:類模板實體化出的物件,向函式傳參的方式
一共有三種傳入方式:
- 指定傳入的型別:直接顯示物件的資料型別
- 引數模板化:將物件中的引數變為模板進行傳遞
- 整個類模板化:將這個物件型別 模板化進行傳遞
#include<iostream>
using namespace std;
#include<string>
//類模板物件做函式引數
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
void showPerson() {
cout << "姓名:" << this->m_Name << ", 年齡:" << this->m_Age << endl;
}
T1 m_Name;
T2 m_Age;
};
//1、指定傳入型別
void printPerson1(Person<string, int>& p) {
p.showPerson();
}
void test01() {
Person<string, int>p("張三", 18);
printPerson1(p);
}
//2、引數模板化
template<class T1, class T2>
void printPerson2(Person<T1, T2>& p) {
p.showPerson();
cout << "T1的型別為:" << typeid(T1).name() << endl;
cout << "T2的型別為:" << typeid(T2).name() << endl;
}
void test02() {
Person<string, int>p("李四", 20);
printPerson2(p);
}
//3、整個類模板化
template<class T>
void printPerson3(T &p) {
p.showPerson();
cout << "T的型別為:" << typeid(T).name() << endl;
}
void test03() {
Person<string, int>p("王五", 22);
printPerson3(p);
}
int main() {
test01();
cout << "\n";
test02();
cout << "\n";
test03();
system("pause");
system("cls");
}
總結:
- 通過類模板創建的物件,可以有三種方式向函式中進行傳參
- 使用比較廣泛是第一種:指定傳入的型別
1.3.5 類模板與繼承
當類模板碰到繼承時,需要注意一下幾點:
- 當子類繼承的父類是一個類模板時,子類在宣告的時候,要指定出父類中T的型別
- 如果不指定,編譯器無法給子類分配記憶體
- 如果想靈活指定出父類中T的型別,子類也需變為類模板
#include<iostream>
using namespace std;
//類模板與繼承
template<class T>
class Base {
T m;
};
class Son :public Base<int> {
};
void test01() {
Son s1;
}
//如果想靈活指定父類中T型別,子類也需要變類模板
template<class T1, class T2>
class Son2 :public Base<T2> {
public:
Son2() {
cout << "T1的型別為:" << typeid(T1).name() << endl;
cout << "T2的型別為:" << typeid(T2).name() << endl;
}
T1 obj;
};
void test02() {
Son2<int, char>S2; //m是char型別, obj是int型別
}
int main() {
test01();
test02();
system("pause");
return 0;
}
T1的型別為:int
T2的型別為:char
請按任意鍵繼續. . .
總結:如果父類是類模板,子類需要指定出父類中T的資料型別
1.3.6 類模板成員函式類外實作
學習目標:能夠掌握類模板中的成員函式類外實作
#include<iostream>
using namespace std;
#include<string>
//類模板成員函式類外實作
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age); //類內寫宣告 類外實作
void showPerson();
T1 m_Name;
T2 m_Age;
};
//建構式的類外實作
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl;
}
void test01() {
Person<string, int> p("張三", 18);
p.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
總結:類模板中成員函式類外實作時,需要加上模板引數串列
1.3.7 類模板分檔案撰寫
學習目標:掌握類模板成員函式分檔案撰寫產生的問題以及解決方式
問題:類模板中成員函式創建時機是在呼叫階段,導致分檔案撰寫時鏈接不到
解決:
- 解決方式1:直接包含.cpp源檔案
- 解決方式2:將宣告和實作寫到同一個檔案中,并更改后綴名為.hpp,hpp是約定的名稱,并不是強制
person.hpp
#pragma once
#include<iostream>
using namespace std;
template<class T1, class T2>
class Person {
public:
Person(T1 name, T2 age); //類內寫宣告 類外實作
void showPerson();
T1 m_Name;
T2 m_Age;
};
//建構式的類外實作
template<class T1, class T2>
Person<T1, T2>::Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
template<class T1, class T2>
void Person<T1, T2>::showPerson() {
cout << "姓名:" << this->m_Name << " 年齡:" << this->m_Age << endl;
}
main.cpp
#include<iostream>
using namespace std;
#include<string>
//#include"person.cpp" //第一種解決方式:.h改為.cpp
//第二種解決方式,將.h和.cpp中的內容寫到一起,后綴名去.hpp
#include"person.hpp"
//類模板檔案撰寫問題以及解決
void test01() {
Person<string, int> p("張三", 18);
p.showPerson();
}
int main() {
test01();
system("pause");
return 0;
}
總結:主流的解決方式是第二種,將類模板成員函式寫到一起,并將后綴名改為.hpp
1.3.8 類模板與友元
學習目標:掌握類模板配合友元函式的類內和類外實作
全域函式類內實作:直接在類內宣告友元即可
全域函式類外實作:需要提前讓編譯器知道全域函式的存在
#include<iostream>
using namespace std;
//通過全域函式 列印person資訊
template<class T1, class T2>
class Person;
//類外實作
template<class T1, class T2>
void printPerson2(Person<T1, T2> p) {
cout << "類外實作-----姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
}
template<class T1, class T2>
class Person {
//全域函式 類內實作
friend void printPerson(Person<T1,T2> p) {
cout << "姓名: " << p.m_Name << " 年齡:" << p.m_Age << endl;
}
//全域函式 類外實作
//加空模板引數串列
friend void printPerson2<>(Person<T1, T2> p);
public:
Person(T1 name, T2 age) {
this->m_Name = name;
this->m_Age = age;
}
private:
T1 m_Name;
T2 m_Age;
};
//1、全域函式在類內實作
void test01() {
Person<string, int>p("張三", 18);
printPerson(p);
}
void test02() {
Person<string, int>p("李四", 20);
printPerson2(p);
}
int main() {
test01();
test02();
system("pause");
return 0;
}
總結:建議全域函式做類內實作,用法簡單,而且編譯器可以直接識別
1.3.9 類模板案例
案例描述: 實作一個通用的陣列類,要求如下:
可以對內置資料型別以及自定義資料型別的資料進行存盤
將陣列中的資料存盤到堆區
建構式中可以傳入陣列的容量
提供對應的拷貝建構式以及operator=防止淺拷貝問題
提供尾插法和尾刪法對陣列中的資料進行增加和洗掉
可以通過下標的方式訪問陣列中的元素
可以獲取陣列中當前元素個數和陣列的容量

MyArray.hpp
#pragma once
//自己的通用的陣列類
#include<iostream>
using namespace std;
template<class T>
class MyArray {
public:
//有參構造 引數 容量
MyArray(int capacity) {
cout << "MyArray的有參構造呼叫" << endl;
this->m_Capacity = capacity;
this->m_Size = 0;
this->pAddress = new T[this->m_Capacity];
}
//拷貝構造
MyArray(const MyArray& arr) {
cout << "MyArray的拷貝構造呼叫" << endl;
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
//this->pAddress = arr.pAddress;
//深拷貝
this->pAddress = new T[arr.m_Capacity];
//將arr中的資料都拷貝過來 如果有資料的話
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
}
//operator= 防止淺拷貝問題
MyArray& operator=(const MyArray& arr) {
cout << "MyArray的operator=呼叫" << endl;
//先判斷原來堆區是否有資料 如果有先釋放
if (this->pAddress != NULL) {
delete[] this->pAddress;
this->pAddress = NULL;
this->m_Capacity = 0;
this->m_Size = 0;
}
//深拷貝
this->m_Capacity = arr.m_Capacity;
this->m_Size = arr.m_Size;
this->pAddress = new T[arr.m_Capacity];
for (int i = 0; i < this->m_Size; i++) {
this->pAddress[i] = arr.pAddress[i];
}
return *this;
}
//尾插法
void Push_Back(const T& val) {
//判斷容量是否等于大小
if (this->m_Capacity == this->m_Size) {
return;
}
this->pAddress[this->m_Size] = val; //在陣列末尾插入資料
this->m_Size++; //更新陣列大小
}
//尾刪法
void Pop_Back() {
//讓用戶訪問不到最后一個元素,即為尾刪,邏輯洗掉
if (this->m_Size == 0) {
return;
}
this->m_Size--;
}
//通過下標方式訪問陣列中的元素
T& operator[](int index) {
return this->pAddress[index];
}
//回傳陣列容量
int getCapacity() {
return this->m_Capacity;
}
//回傳陣列大小
int getSize() {
return this->m_Size;
}
//解構式
~MyArray() {
if (this->pAddress != NULL) {
cout << "MyArray的解構式呼叫" << endl;
delete[] this->pAddress;
this->pAddress = NULL;
}
}
private:
T* pAddress; //指標指向堆區開辟的真實陣列
int m_Capacity; //陣列容量
int m_Size; //陣列大小
};
main.cpp
#include<iostream>
using namespace std;
#include"MyArray.hpp"
void printIntArray(MyArray<int>& arr) {
for (int i = 0; i < arr.getSize(); i++) {
cout << arr[i] << endl;
}
}
//1、全域函式在類內實作
void test01() {
MyArray<int>arr1(5);
//MyArray<int>arr2(arr1);
//MyArray<int>arr3(100);
//arr3 = arr1;
for (int i = 0; i < 5; i++) {
//利用尾插法向陣列中插入資料
arr1.Push_Back(i);
}
cout << "arr1的列印輸出為:" << endl;
printIntArray(arr1);
cout << "arr1的容量為:" << arr1.getCapacity() << endl;
cout << "arr1的大小為:" << arr1.getSize() << "\n" << endl;
MyArray<int>arr2(arr1);
cout << "arr2的列印輸出為:" << endl;
printIntArray(arr2);
cout << "\n";
//尾刪
arr2.Pop_Back();
cout << "arr2尾刪后:" << endl;
cout << "arr2的容量為:" << arr2.getCapacity() << endl;
cout << "arr2的大小為:" << arr2.getSize() << endl;
}
//測驗自定義資料型別
class Person {
public:
Person() {};
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void printPersonArray(MyArray<Person>& arr) {
for (int i = 0; i < arr.getSize(); i++) {
cout << "姓名:" << arr[i].m_Name << " 年齡:" << arr[i].m_Age << endl;
}
}
void test02() {
MyArray<Person> arr(10);
Person p1("張三", 18);
Person p2("李四", 20);
Person p3("王五", 22);
Person p4("趙六", 24);
Person p5("田其", 26);
//將資料插入到陣列中
arr.Push_Back(p1);
arr.Push_Back(p2);
arr.Push_Back(p3);
arr.Push_Back(p4);
arr.Push_Back(p5);
//列印陣列
printPersonArray(arr);
//輸出容量
cout << "arr的容量為:" << arr.getCapacity() << endl;
//輸出大小
cout << "arr大小為:" << arr.getSize() << endl;
}
int main() {
//test01();
test02();
system("pause");
return 0;
}
2 STL初識
2.1 STL的誕生
長久以來,軟體界一直希望建立一種可重復利用的東西
C++的面向物件和泛型編程思想,目的就是復用性的提升
大多情況下,資料結構和演算法都未能有一套標準,導致被迫從事大量重復作業
為了建立資料結構和演算法的一套標準,誕生了STL
2.2 STL基本概念
STL(Standard Template Library,標準模板庫)
STL 從廣義上分為: 容器(container) 演算法(algorithm) 迭代器(iterator)
容器和演算法之間通過迭代器進行無縫連接,
STL 幾乎所有的代碼都采用了模板類或者模板函式
2.3 STL六大組件
STL大體分為六大組件,分別是:容器、演算法、迭代器、仿函式、配接器(配接器)、空間配置器
- 容器:各種資料結構,如vector、list、deque、set、map等,用來存放資料,
- 演算法:各種常用的演算法,如sort、find、copy、for_each等
- 迭代器:扮演了容器與演算法之間的膠合劑,
- 仿函式:行為類似函式,可作為演算法的某種策略,
- 配接器:一種用來修飾容器或者仿函式或迭代器介面的東西,
- 空間配置器:負責空間的配置與管理
2.4 STL中容器、演算法、迭代器
容器:置物之所也
STL容器就是將運用最廣泛的一些資料結構實作出來
常用的資料結構:陣列, 鏈表,樹, 堆疊, 佇列, 集合, 映射表 等
這些容器分為序列式容器和關聯式容器兩種:
- 序列式容器:強調值的排序,序列式容器中的每個元素均有固定的位置,
- 關聯式容器:二叉樹結構,各元素之間沒有嚴格的物理上的順序關系
演算法:問題之解法也
有限的步驟,解決邏輯或數學上的問題,這一門學科我們叫做演算法(Algorithms)
演算法分為:質變演算法和非質變演算法,
- 質變演算法:是指運算程序中會更改區間內的元素的內容,例如拷貝,替換,洗掉等等
- 非質變演算法:是指運算程序中不會更改區間內的元素內容,例如查找、計數、遍歷、尋找極值等等
迭代器:容器和演算法之間粘合劑
提供一種方法,使之能夠依序尋訪某個容器所含的各個元素,而又無需暴露該容器的內部表示方式,
每個容器都有自己專屬的迭代器
迭代器使用非常類似于指標,初學階段我們可以先理解迭代器為指標
迭代器種類:

常用的容器中迭代器種類為雙向迭代器,和隨機訪問迭代器
2.5 容器演算法迭代器初識
了解STL中容器、演算法、迭代器概念之后,我們利用代碼感受STL的魅力
STL中最常用的容器為Vector,可以理解為陣列,下面我們將學習如何向這個容器中插入資料、并遍歷這個容器
2.5.1 vector存放內置資料型別
容器: vector
演算法: for_each
迭代器: vector<int>::iterator

#include<iostream>
using namespace std;
#include<vector>
#include<algorithm> //標準演算法頭檔案
void myPrint(int val) {
cout << val << endl;
}
//vector容器存放內置資料型別
void test01() {
//創建了一個vector容器,陣列
vector<int> v;
//向容器中插入資料
v.push_back(10);
v.push_back(20);
v.push_back(30);
v.push_back(40);
//通過迭代器訪問容器中的資料
vector<int>::iterator itBegin = v.begin(); //起始迭代器 指向容器中第一個元素
vector<int>::iterator itEnd = v.end(); //結束迭代器 指向容器中最后一個元素的下一個位置
//第一種遍歷方式
while (itBegin != itEnd) {
cout << *itBegin << endl;
itBegin++;
}
cout << "\n";
//第二種遍歷方式
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << endl;
}
cout << "\n";
//第三種遍歷方式 利用STL提供遍歷演算法
for_each(v.begin(), v.end(), myPrint);
}
int main() {
test01();
system("pause");
return 0;
}
2.5.2 Vector存放自定義資料型別
學習目標:vector中存放自定義資料型別,并列印輸出
#include<iostream>
using namespace std;
#include<vector>
//vector容器中存放自定義資料型別
class Person {
public:
Person(string name, int age) {
this->m_Name = name;
this->m_Age = age;
}
string m_Name;
int m_Age;
};
void test01() {
vector<Person> v;
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);
//向容器中添加資料
v.push_back(p1);
v.push_back(p2);
v.push_back(p3);
v.push_back(p4);
v.push_back(p5);
//遍歷容器中的資料
for (vector<Person>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:" << (*it).m_Name << " 年齡:" << (*it).m_Age << endl;
//cout << "姓名:" << it->m_Name << " 年齡:" << it->m_Age << endl; //一樣可以
}
}
//存放自定義資料型別 指標
void test02() {
vector<Person*> v;
Person p1("aaa", 10);
Person p2("bbb", 20);
Person p3("ccc", 30);
Person p4("ddd", 40);
Person p5("eee", 50);
//向容器中添加資料
v.push_back(&p1);
v.push_back(&p2);
v.push_back(&p3);
v.push_back(&p4);
v.push_back(&p5);
//遍歷容器
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++) {
cout << "姓名:" << (*it)->m_Name << " 年齡:" << (*it)->m_Age << endl;
}
}
int main() {
test02();
system("pause");
return 0;
}
2.5.3 Vector容器嵌套容器
學習目標:容器中嵌套容器,我們將所有資料進行遍歷輸出
#include<iostream>
using namespace std;
#include <vector>
//容器嵌套容器
void test01() {
vector< vector<int> > v;
vector<int> v1;
vector<int> v2;
vector<int> v3;
vector<int> v4;
for (int i = 0; i < 4; i++) {
v1.push_back(i + 1);
v2.push_back(i + 2);
v3.push_back(i + 3);
v4.push_back(i + 4);
}
//將容器元素插入到vector v中
v.push_back(v1);
v.push_back(v2);
v.push_back(v3);
v.push_back(v4);
for (vector<vector<int>>::iterator it = v.begin(); it != v.end(); it++) {
for (vector<int>::iterator vit = (*it).begin(); vit != (*it).end(); vit++) {
cout << *vit << " ";
}
cout << endl;
}
}
int main() {
test01();
system("pause");
return 0;
}
1 2 3 4
2 3 4 5
3 4 5 6
4 5 6 7
請按任意鍵繼續. . .
3 STL常用容器
3.1 string容器
3.1.1 string基本概念
本質:string是C++風格的字串,而string本質上是一個類
string和char * 區別:
- char * 是一個指標
- string是一個類,類內部封裝了char*,管理這個字串,是一個char*型的容器,
特點:string 類內部封裝了很多成員方法
例如:查找find,拷貝copy,洗掉delete 替換replace,插入insert
string管理char*所分配的記憶體,不用擔心復制越界和取值越界等,由類內部進行負責
3.1.2 string建構式
建構式原型:
string();//創建一個空的字串 例如: string str;string(const char* s);//使用字串s初始化string(const string& str);//使用一個string物件初始化另一個string物件string(int n, char c);//使用n個字符c初始化
#include<iostream>
using namespace std;
#include<iostream>
//string的建構式
void test01() {
string s1; //默認構造
//第二種
const char* str = "hello world";
string s2(str);
cout << "s2 = " << s2 << endl;
//第三種
string s3(s2);
cout << "s3 = " << s3 << endl;
//第四種
string s4(10, 'c');
cout << "s4 = " << s4 << endl;
}
int main() {
test01();
system("pause");
return 0;
}
s2 = hello world
s3 = hello world
s4 = cccccccccc
請按任意鍵繼續. . .
總結:string的多種構造方式沒有可比性,靈活使用即可
3.1.3 string賦值操作
功能描述:給string字串進行賦值
賦值的函式原型:
string& operator=(const char* s);//char*型別字串 賦值給當前的字串string& operator=(const string &s);//把字串s賦給當前的字串string& operator=(char c);//字符賦值給當前的字串string& assign(const char *s);//把字串s賦給當前的字串string& assign(const char *s, int n);//把字串s的前n個字符賦給當前的字串string& assign(const string &s);//把字串s賦給當前字串string& assign(int n, char c);//用n個字符c賦給當前字串
#include<iostream>
using namespace std;
#include<iostream>
//string的賦值操作
void test01() {
string str1;
str1 = "hello world";
//第二種
string str2;
str2 = str1;
//第三種,字符賦值給字串
string str3;
str3 = 'a';
cout << "str3 = " << str3 << endl;
//第四種
string str4;
str4.assign("hello C++");
cout << "str4 = " << str4 << endl;
//第五種
string str5;
str5.assign("hello C++", 3); //賦值前三個字符
cout << "str5 = " << str5 << endl;
//第六種
string str6;
str6.assign(str5);
cout << "str6 = " << str6 << endl;
//第七種
string str7;
str7.assign(5, 'a');
cout << "str7 = " << str7 << endl;
}
int main() {
test01();
system("pause");
return 0;
}
str3 = a
str4 = hello C++
str5 = hel
str6 = hel
str7 = aaaaa
請按任意鍵繼續. . .
總結:? string的賦值方式很多,operator= 這種方式是比較實用的
3.1.4 string字串拼接
功能描述:實作在字串末尾拼接字串
函式原型:
string& operator+=(const char* str);//多載+=運算子string& operator+=(const char c);//多載+=運算子string& operator+=(const string& str);//多載+=運算子string& append(const char *s);//把字串s連接到當前字串結尾string& append(const char *s, int n);//把字串s的前n個字符連接到當前字串結尾string& append(const string &s);//同operator+=(const string& str)string& append(const string &s, int pos, int n);//字串s中從pos開始的n個字符連接到字串結尾
#include<iostream>
using namespace std;
#include<iostream>
//string字串拼接
void test01() {
string str1 = "我";
str1 += "愛玩游戲";
cout << "str1 = " << str1 << endl;
//第二種, 追加一個字符
str1 += ':';
cout << "str1 = " << str1 << endl;
//第三種 拼接一個字串
string str2 = "LOL";
str1 += str2;
cout << "str1 = " << str1 << endl;
//第四種
string str3 = "I";
str3.append(" love ");
cout << "str3 = " << str3 << endl;
//第五種
str3.append("game abcde", 5); //前四個字符
cout << "str3 = " << str3 << endl;
//第六種
str3.append(str2);
cout << "str3 = " << str3 << endl;
//第七種
str3.append(str2, 1,2); //追加str2的兩個字符,從索引1開始的兩個字符
cout << "str3 = " << str3 << endl;
}
int main() {
test01();
system("pause");
return 0;
}
str1 = 我愛玩游戲
str1 = 我愛玩游戲:
str1 = 我愛玩游戲:LOL
str3 = I love
str3 = I love game
str3 = I love game LOL
str3 = I love game LOLOL
請按任意鍵繼續. . .
3.1.5 string查找和替換
功能描述:
- 查找:查找指定字串是否存在
- 替換:在指定的位置替換字串
函式原型:
int find(const string& str, int pos = 0) const;//查找str第一次出現位置,從pos開始查找int find(const char* s, int pos = 0) const;//查找s第一次出現位置,從pos開始查找int find(const char* s, int pos, int n) const;//從pos位置查找s的前n個字符第一次位置int find(const char c, int pos = 0) const;//查找字符c第一次出現位置int rfind(const string& str, int pos = npos) const;//查找str最后一次位置,從pos開始查找int rfind(const char* s, int pos = npos) const;//查找s最后一次出現位置,從pos開始查找int rfind(const char* s, int pos, int n) const;//從pos查找s的前n個字符最后一次位置int rfind(const char c, int pos = 0) const;//查找字符c最后一次出現位置string& replace(int pos, int n, const string& str);//替換從pos開始n個字符為字串strstring& replace(int pos, int n,const char* s);//替換從pos開始的n個字符為字串s
#include<iostream>
using namespace std;
#include<iostream>
//string的查找和替換
//1、查找
void test01() {
string str1 = "abcdefgde";
int index1 = str1.find("de");
cout << "index1 = " << index1 << endl;
int index2 = str1.find("ad"); //-1
if (index2 == -1) {
cout << "未找到字串!" << endl;
}
//rfind
int pos = str1.rfind("de"); //從左往右找
cout << "pos = " << pos << endl;
}
//2、替換
void test02() {
string str1 = "abcdefg";
str1.replace(1, 3, "1111"); //從索引1開始替換3個字符
cout << "str1 = " << str1 << endl;
}
int main() {
test01();
cout << "\n";
test02();
system("pause");
return 0;
}
index1 = 3
未找到字串!
pos = 7
str1 = a1111efg
請按任意鍵繼續. . .
總結:
- find查找是從左往后,rfind從右往左
- find找到字串后回傳查找的第一個字符位置,找不到回傳-1
- replace在替換時,要指定從哪個位置起,多少個字符,替換成什么樣的字串
3.1.6 string字串比較
功能描述:字串之間的比較
比較方式:字串比較是按字符的ASCII碼進行對比
= 回傳 0
> 回傳 1
< 回傳 -1
函式原型:
int compare(const string &s) const;//與字串s比較int compare(const char *s) const;//與字串s比較
#include<iostream>
using namespace std;
#include<iostream>
//string的比較
void test01() {
string str1 = "hello";
string str2 = "hello";
if (str1.compare(str2) == 0) {
cout << "str1 == str2" << endl;
}
string str3 = "hella";
int i = str1.compare(str3); //str1 大于 str3
cout << "i = " << i << endl;
}
int main() {
test01();
system("pause");
return 0;
}
str1 == str2
i = 1
請按任意鍵繼續. . .
總結:字串對比主要是用于比較兩個字串是否相等,判斷誰大誰小的意義并不是很大
3.1.7 string字符存取
string中單個字符存取方式有兩種
char& operator[](int n);//通過[]方式取字符char& at(int n);//通過at方法獲取字符
#include<iostream>
using namespace std;
#include<iostream>
//string字符存取
void test01() {
string str = "hello";
cout << "str = " << str << endl;
//1、通過[]訪問單個字符
for (int i = 0; i < str.size(); i++) {
cout << str[i] << " ";
}
cout << endl;
//2、通過at方式訪問單個字符
for (int i = 0; i < str.size(); i++) {
cout << str.at(i) << " ";
}
cout << endl;
//修改單個字符
str[0] = 'x';
cout << "str = " << str << endl;
str.at(1) = 'x';
cout << "str = " << str << endl;
}
int main() {
test01();
system("pause");
return 0;
}
str = hello
h e l l o
h e l l o
str = xello
str = xxllo
請按任意鍵繼續. . .
總結:string字串中單個字符存取有兩種方式,利用 [ ] 或 at
3.1.8 string插入和洗掉
功能描述:對string字串進行插入和洗掉字符操作
函式原型:
string& insert(int pos, const char* s);//插入字串string& insert(int pos, const string& str);//插入字串string& insert(int pos, int n, char c);//在指定位置插入n個字符cstring& erase(int pos, int n = npos);//洗掉從Pos開始的n個字符
#include<iostream>
using namespace std;
#include<iostream>
//string 插入和洗掉
void test01() {
string str = "hello";
//插入
str.insert(1, "222");
cout << "str = " << str << endl;
//洗掉
str.erase(1, 3); //從索引1開始洗掉3個
cout << "str = " << str << endl;
}
int main() {
test01();
system("pause");
return 0;
}
str = h222ello
str = hello
請按任意鍵繼續. . .
3.1.9 string子串
功能描述:從字串中獲取想要的子串
函式原型:string substr(int pos = 0, int n = npos) const; //回傳由pos開始的n個字符組成的字串
#include<iostream>
using namespace std;
#include<iostream>
//string子串
void test01() {
string str = "abcdef";
string subStr = str.substr(1, 3); //從索引1開始截3個
cout << "sunStr = " << subStr << endl;
}
//實用操作
void test02() {
string email = "zhangsan@sina.com";
int pos = email.find("@");
string usrName = email.substr(0, pos);
cout << "usrName = " << usrName << endl;
}
int main() {
test01();
test02();
system("pause");
return 0;
}
sunStr = bcd
usrName = zhangsan
請按任意鍵繼續. . .
3.2 vector容器
3.2.1 vector基本概念
功能:vector資料結構和陣列非常相似,也稱為單端陣列
vector與普通陣列區別:不同之處在于陣列是靜態空間,而vector可以動態擴展
動態擴展:并不是在原空間之后續接新空間,而是找更大的記憶體空間,然后將原資料拷貝新空間,釋放原空間

vector容器的迭代器是支持隨機訪問的迭代器
3.2.2 vector建構式
功能描述:創建vector容器
函式原型:
vector<T> v;//采用模板實作類實作,默認建構式vector(v.begin(), v.end());//將v[begin(), end())區間中的元素拷貝給本身,vector(n, elem);//建構式將n個elem拷貝給本身,vector(const vector &vec);//拷貝建構式,
#include<iostream>
using namespace std;
#include<vector>
void printVector(vector<int>&v){
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
//vector容器構造
void test01() {
vector<int>v1; //默認構造 無參構造
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//通過區間方式進行構造
vector<int>v2(v1.begin(), v1.end());
printVector(v2);
//n個elem方式構造
vector<int>v3(10, 100); //10個100
printVector(v3);
//拷貝構造
vector<int>v4(v3);
printVector(v4);
}
int main() {
test01();
system("pause");
return 0;
}
3.2.3 vector賦值操作
功能描述:給vector容器進行賦值
函式原型:
vector& operator=(const vector &vec);//多載等號運算子assign(beg, end);//將[beg, end)區間中的資料拷貝賦值給本身,assign(n, elem);//將n個elem拷貝賦值給本身
#include<iostream>
using namespace std;
#include<vector>
void printVector(vector<int>& v) {
for (vector<int>::iterator it = v.begin(); it != v.end(); it++) {
cout << *it << " ";
}
cout << endl;
}
//vector賦值
void test01() {
vector<int>v1;
for (int i = 0; i < 10; i++) {
v1.push_back(i);
}
printVector(v1);
//賦值 =
vector<int>v2;
v2 = v1;
printVector(v2);
//assign
vector<int>v3;
v3.assign(v1.begin(), v1.end());
printVector(v3);
//n個elem方式賦值
vector<int>v4;
v4.assign(10, 100); //10個100
printVector(v4);
}
int main() {
test01();
system("pause");
return 0;
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/310684.html
標籤:其他
