qfedu-cpp-level/day9/readme.md

469 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# STL
### 1. string 容器
> string 也是字符串的类型,可以理解时它是用于存放字符的容器。
#### 1.1 String 和 c 风格字符串对比
```
1) char * 是一个指针string 是一个类
2) string 封装了很多实用的成员方法
查找 find拷贝 copy删除 delete 替换 replace插入 insert
3) 不用考虑内存释放和越界
string 管理 char*所分配的内存。每一次 string 的复制,取值都由 string 类负责维
护,不用担心复制越界和取值越界等。
```
#### 1.2 string 容器 api
```
// 构造函数
string(); //创建一个空的字符串 例如: string str;
string(const string& str);//使用一个 string 对象初始化另一个 string 对象
string(const char* s);//使用字符串 s 初始化
string(int n, char c); //使用 n 个字符 c 初始化
// 赋值
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 赋给当前字符串
string& assign(const string &s, int start, int n);//将 s 从 start 开始 n 个
字符赋值给字符串
// 取值
char& operator[](int n);//通过[]方式取字符
char& at(int n);//通过 at 方法获取字符
// 字符串拼接
string& operator+=(const string& str);//重载+=操作符
string& operator+=(const char* str);//重载+=操作符
string& operator+=(const char c);//重载+=操作符
string& append(const char *s);//把字符串 s 连接到当前字符串结尾
string& append(const char *s, int n);//把字符串 s 的前 n 个字符连接到当前字符串结尾
string& append(const string &s); //同 operator+=()
string& append(const string &s, int pos, int n);//把字符串 s 中从 pos 的 n 个字符连接到当前字符串结尾
string& append(int n, char c);//在当前字符串结尾添加 n 个字符 c
// 查找与替换
int find(const string& str, int pos = 0) const; //查找 str 第一次出现位置, 从 pos 开始查找, 如果未查找到返回 -1
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 个字符为字符串 str
string& replace(int pos, int n, const char* s); //替换从 pos 开始的 n 个字符为字符串 s
// 比较 ,返回值 0:相等, 1:大于s, -1小于s
int compare(const string &s) const;//与字符串 s 比较
int compare(const char *s) const;//与字符串 s
// 截取子字符串
string substr(int pos = 0, int n = npos) const;//返回由 pos 开始的 n 个字符组成的字符串
// 插入与删除
string& insert(int pos, const char* s); //插入字符串
string& insert(int pos, const string& str); //插入字符串
string& insert(int pos, int n, char c);//在指定位置插入 n 个字符 c
string& erase(int pos, int n = npos);//删除从pos 开始的 n 个
// 转成 char *
const char * c_str() 将当前字符串转成 char *
// 获取字符串的长度
int size();
```
### 2. vector 容器
#### 2.1 vector 与数组的区别
vector 的结构类同于数组,数组是静态的,在定义时确定的数组的大小;而 vector 是动态的添加元素时如果空间不足时则会自动扩容器2^n整体来说vector 比较灵活的,而且 vector 是类模板,可以存放任意类型的元素。
#### 2.2 vector 迭代器
> vector 维护一个线性空间(线性连续空间), vector 迭代器所需要的操作行为 是(\*,->, ++, --, +, -, +=, -=)运算符重载等。vector 支持随机存取vector 提供的是随机访问迭代器(Random Access Iterator), 迭代器中的元素即为 vector 容器中的元素。
<font color=red>【重要说明】: vector 容器扩容之后,原迭代器则无效,需要重新获取获取器</font>
```
所谓动态增加大小,并不是在原空间之后续接新空间(因为无法保证原空间之后尚有可配置的空间),而是一块更大的内存空间,然后将原数据拷贝新空间,并释放原空间。
因此,对 vector 的任何操作一旦引起空间的重新配置指向原vector的所有迭代器就都失效了。这是程序员容易犯的一个错误务必小心。
```
#### 2.3 vector 容器 api
##### 3.3.1 构造函数
```
// 构造函数
vector<T> v; //采用模板实现类实现,默认构造函数
vector(v.begin(), v.end());//将 v[begin(), end())区间中的元素拷贝给本身。
vector(n, elem);//构造函数将 n 个 elem 拷贝给本身。
vector(const vector &vec);//拷贝构造函
```
##### 3.3.2 赋值操作
```c++
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
vector& operator=(const vector &vec);//重载等号操作符
swap(vec);// 将 vec 与本身的元素互换
```
##### 3.3.3 大小操作
```c++
int size(); // 返回容器中元素的个数
bool empty(); //判断容器是否为空, 返回bool值0 1
void resize(int num); //重新指定容器的长度为 num若容器变长则以默认值填充新位置。如果容器变短则末尾超出容器长度的元素被删除。
void resize(int num, elem); //重新指定容器的长度为 num若容器变长则以 elem 值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
int capacity(); //容器的容量
void reserve(int len); //容器预留 len 个元素长度,预留位置不初始化,元素不可访问。
```
##### 3.3.4 存取操作
```c++
at(int idx); //返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range 异常。
operator[](int idx); //返回索引 idx 所指的数据,越界时,运行直接报错
front(); //返回容器中第一个数据元素
back(); //返回容器中最后一个数据元素
```
##### 3.3.5 插入和删除
```c++
insert(const_iterator pos, int count, T ele); //迭代器指向位置 pos 插入 count个元素 ele.
push_back(ele); //尾部插入元素 ele
pop_back();//删除最后一个元素
erase(const_iterator start, const_iterator end); //删除迭代器从 start 到 end 之间的元素, 删除[start, end)区间的所有元素
erase(const_iterator pos); //删除迭代器指向的元素
clear(); //删除容器中所有元素
```
#### 2.4 小技巧示例
##### 2.2.4.1 巧用 swap 收缩内存空间
> resize()+swap()实现 vector 收缩内存空间
```c++
resize(n);
vector<T>(v).swap(v);
```
### 3. deque 容器
#### 3.1 deque 基本概念
vector 容器是单向开口的连续内存空间deque 则是一种双向开口的连续线性空间。
所谓的双向开口意思是可以在头尾两端分别做元素的插入和删除操作当然vector 容器也可以在头尾两端插入元素,但是在其头部操作效率奇差,无法被接受。
qeque 容器和 vector 容器最大的差异:
```
1) 在于 deque 允许使用常数项时间对头端进行元素的插入和删除操作。
2) 在于 deque 没有容量的概念,因为它是动态的以分段连续空间组合而成,随时可以增加一段新的空间并链接起来。
3) deque 没有必须要提供所谓的空间保留(reserve)功能
4) 虽然deque容器也提供了 Random Access Iterator,但是它的迭代器并不是普通的指针其复杂度和vector 不是一个量级这当然影响各个运算的层面。因此除非有必要我们应该尽可能的使用vector而不是deque。
5对 deque 进行的排序操作,为了最高效率,可将 deque 先完整的复制到一个 vector 中,对 vector 容器进行排序,再复制回 deque。
```
#### 3.2 deque 实现原理
逻辑上deque 容器是连续的空间, 是由一段一段的定量的连续空间构成。一旦有必要在 deque 前端或者尾端增加新的空间,便配置一段连续定量的空间,串接在 deque 的头端或者尾端。
deque 最大的工作就是维护这些分段连续的内存空间的整体性的假象,并提供随机存取的接口。
既然 deque 是分段连续内存空间那么就必须有中央控制map 实现的),维持整体连续的假象,数据结构的设计及迭代器的前进后退操作颇为繁琐。
中央控制: 连续小空间,由 map 实现,存放是地址,地址指向的另一个连续空间为缓冲区。
缓冲区:是 deque 的存储空间的主体。
#### 3.3 常用 API
##### 3.3.1 构造函数
```c++
deque<T> deqT;//默认构造形式
deque(beg, end);//构造函数将[beg, end)区间中的元素拷贝给本身。
deque(n, elem);//构造函数将 n 个 elem 拷贝给本身。
deque(const deque &deq);//拷贝构造函数。
```
##### 3.3.2 赋值操作
```c++
assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
assign(n, elem);//将 n 个 elem 拷贝赋值给本身。
deque& operator=(const deque &deq); //重载等号运算符
swap(deq);// 将 deq 与本身的元素互换
```
##### 3.3.3 大小操作
```c++
deque.size();//返回容器中元素的个数
deque.empty();//判断容器是否为空
deque.resize(num);//重新指定容器的长度为 num,若容器变长,则以默认值填充新位置。如果容器变短,则末尾超出容器长度的元素被删除。
deque.resize(num, elem); //重新指定容器的长度为 num,若容器变长,则以 elem 值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除。
```
##### 3.3.4 双端插入和删除
```c++
void push_back(elem);//在容器尾部添加一个数据
void push_front(elem);//在容器头部插入一个数据
void pop_back();//删除容器最后一个数据
void pop_front();//删除容器第一个数据
```
##### 3.3.5 数据存取
```c++
at(idx);//返回索引 idx 所指的数据,如果 idx 越界,抛出 out_of_range。
operator[]; //返回索引 idx 所指的数据,如果 idx 越界,不抛出异常,会直接出错。
front();//返回第一个数据。
back();//返回最后一个
```
operator[] 操作数据,超出范围之后,则会直接报错并不抛出异常
> 【注意】 string 泛型的 deque 默认分配 16 个, int 默认 128 个
##### 3.3.6 插入操作
```c++
void insert(iterator pos,T elem);//在 pos 位置插入一个 elem 元素的拷贝,返回新数据的位置。
void insert(iterator pos,int n,T elem);//在 pos 位置插入 n 个 elem 数据,无返回值。
void insert(iterator pos,iter_beg,iter_end);//在 pos 位置插入[beg,end)区间的数据,无返回值。
```
##### 3.3.7 删除操作
```c++
clear();//移除容器的所有数据
iterator erase(iterator beg,iterator end);//删除[beg,end)区间的数据,返回下一个数据的位置。
iterator erase(iterator pos);//删除 pos 位置的数据,返回下一个数据的位置。
```
### 4 stack 容器
#### 4.1 stack 基本概念
stack 是一种先进后出(First In Last Out,FILO)的数据结构它只有一个出口stack 容器允许新增元素、移除元素、取得栈顶元素,但是除了最顶端外,没有任何其他方法可以存取 stack 的其他元素。换言之stack 不允许有遍历行为。
<img src="./E:/UserFiles/Videos/千锋物联网/第二阶段 C++语言/第二周/c++第九天课堂笔记.assets/image-20230803164259349.png" width=300>
#### 4.2 stack 没有迭代器
stack 所有元素的进出都必须符合”先进后出”的条件,只有 stack 顶端的元素才有机会被外界取用。Stack 不提供遍历功能,也不提供迭代器。
#### 4.3 常用 API
##### 4.3.1 构造函数
```
stack<T> stkT;//stack 采用模板类实现, stack 对象的默认构造形式:
stack(const stack &stk);//拷贝构造函数
```
##### 4.3.2 赋值操作
```
stack& operator=(const stack &stk);//重载等号操作符
```
##### 4.3.3 数据存取
```c++
void push(elem);//向栈顶添加元素
void pop();//从栈顶移除第一个元素
T top();//返回栈顶元素
```
##### 4.3.4 大小操作
```
empty();//判断堆栈是否为空
size();//返回堆栈的大小
```
####
案例: 人员的淘汰方式(业绩从高到低、后入职)
```c++
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
class Worker;
class Date
{
friend Worker;
friend ostream &operator<<(ostream &cout, Date &date)
{
cout << date.year << "年" << date.month << "月" << date.day << "日";
return cout;
}
private:
int year;
int month;
int day;
public:
Date(int year, int month, int day)
{
this->year = year;
this->month = month;
this->day = day;
}
bool operator>(Date &other)
{
// cout << *this << " pk " << other << endl;
return year > other.year || (year == other.year && month > other.month) || (year == other.year && month == other.month && day > other.day);
}
bool operator<=(Date &other)
{
return year <= other.year && month <= other.month && day <= other.day;
}
};
class Worker
{
public:
string name; // 姓名
Date *hire_date; // 入职时间
double volume; // 业绩值
public:
Worker(const string &name, Date *hire_date, double volume)
{
this->name = name;
this->hire_date = hire_date;
this->volume = volume;
}
Worker(const Worker &other)
{
Date *hire_date = new Date(other.hire_date->year, other.hire_date->month, other.hire_date->day);
this->hire_date = hire_date;
this->name = other.name;
this->volume = other.volume;
}
Worker &operator=(const Worker &other)
{
this->hire_date->year = other.hire_date->year;
this->hire_date->month = other.hire_date->month;
this->hire_date->day = other.hire_date->day;
this->name = other.name;
this->volume = other.volume;
return *this;
}
~Worker()
{
if (hire_date)
delete hire_date;
hire_date = NULL;
}
void show()
{
cout << "name=" << name << ",hire_date=" << *hire_date << ",volume=" << volume << endl;
}
};
void sortByHireDate(Worker *p, int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = i;
for (int j = i + 1; j < size; j++)
{
if (*(p[min].hire_date) > *(p[j].hire_date))
{
min = j;
}
}
if (min != i)
{
// cout << p[i].name << " 和 " << p[min].name << " 交换数据" << endl;
Worker tmp = p[i]; // 拷贝构造函数
p[i] = p[min]; // operator=重载
p[min] = tmp;
}
}
}
void sortByVolume(Worker *p, int size)
{
for (int i = 0; i < size - 1; i++)
{
int min = i;
for (int j = i + 1; j < size; j++)
{
if (p[min].volume < p[j].volume)
{
min = j;
}
}
if (min != i)
{
// cout << p[i].name << " 和 " << p[min].name << " 交换数据" << endl;
Worker tmp = p[i]; // 拷贝构造函数
p[i] = p[min]; // operator=重载
p[min] = tmp;
}
}
}
int main(int argc, char const *argv[])
{
Worker w1("disen", new Date(2020, 1, 15), 1000);
Worker w2("jack", new Date(2021, 1, 15), 100);
Worker w3("lucy", new Date(2020, 2, 15), 500);
Worker w4("mack", new Date(2020, 3, 25), 300);
Worker w5("judy", new Date(2022, 5, 18), 400);
Worker w6("rose", new Date(2023, 1, 10), 1200);
Worker m[] = {w1, w2, w3, w4, w5, w6};
// sortByHireDate(m, 6);
sortByVolume(m, 6);
vector<Worker> v1(m, m + 6);
stack<Worker> s1;
vector<Worker>::iterator it = v1.begin();
while (it != v1.end())
{
(*it).show();
s1.push(*it);
it++;
}
// 辞退2位新来的员工
// cout << "-----辞退2位新来的员工----" << endl;
cout << "-----业绩最差的2位员工----" << endl;
for (int i = 0; i < 2; i++)
{
s1.top().show();
s1.pop();
}
return 0;
}
```