String、vector 是俩种最重要的标准库类型,String 支持可变长字符串
,vector 支持可变长的集合
迭代器是一种与 string 和 vector 配套的标准库类型。常用于访问 string 中的字符或 vector 中的元素
内置数组是一种基础的类型,string 和 vector 都是对它的某种抽象。
🍌3.1 命名空间的 using 声明
🍌3.2 标准库类型 string
#include <string>
using std::string
复制代码
🍑3.2.1 定义和初始化 string 对象
string 默认初始化是一个空字符串初始化string
对象的方式:
注意:
初始化方式
string s5 = "hello"; //拷贝初始化
string s6("hello"); //直接初始化
string s7{hello}; //列表初始化
复制代码
🍑3.2.2 string 对象上的操作
string 常用操作
注意
:
string s;
cin >> s; //输入hello world
cout << s << endl; //输出为hello
复制代码
读写 string 对象
使用cin
和 cout
来读写 string 对象
使用 getline 函数读取一行
注意
:getline 会将换行符也读入,但是不将换行符存入 string 对象。触发 getline() 函数返回的那个换行符实际上被丢弃掉了。得到的 string 对象中不包含换行符。
string s; //读取到文件末尾结束
while(getline(cin, s)) //输入为hello world
cout << s << endl; //输出为hello world
复制代码
string::size_type 类型
string 中的 size() 返回值类型是string:: size_type
string::size_type 是一个无符号值,能够存下任何 string 对象的大小,所以用于存放 string 类的 size 函数返回值的变量,都应该是 string::size_type 类型的
c++11 规定允许使用auto
和 declltype
来获取此类型
在具体使用时,通过作用域操作符来表明 size_type 是在类 string 中定义的。
auto len = s.size();// len 的类型是 string::size_type
复制代码
注意:
在一条表达式中已经有了 size()函数,就不要使用 int 了,这样可以避免混用 int 和 unsigned 可能会带来问题。
俩个 string 对象相加
string s1 = "hello,";
string s2 = "world";
string s3 = s1 + s2;
cout << s3 << endl; //hello,world
复制代码
字面值和 string 对象相加
切记字面值和string是不一样的
字面值分为:字符字面值和字符串字面值
俩个 string 对象相加
string s1 = "hello,";
string s2 = "world";
string s3 = s1 + s2;
cout << s3 << endl; //hello,world
复制代码
string 对象和字符字面值相加
string s4 = s1 + "," //正确,左侧为string对象,右侧为字符字面值
string s5 = "hello" + "," //错误,左右俩侧都是字符字面值
string s6 = s1 + "," + "world" //正确
string s7 = "hello" + "," + s1 //错误,俩个字面值不能相加
//等价于string s7 = ("hello" + ",") + s1
复制代码
🍑3.2.3 处理 string 对象中的字符
cctype 头文件中有下列标准库函数来处理 string 中的字符。
建议
:使用 c++ 版本的标准库头文件,即 cname 而非 name.h 类型的头文件。cname 头文件中的名字都从属于命名空间 std;
范围 for 循环
for (auto c : str)
for (auto &c : str)
string str("hello world");
for(auto c:str) // 对于str中的每个字符
cout << c << endl; // 输出当前字符,后面紧跟一个换行符 hello world
复制代码
for(auto &c:s)
c = toupper(c); // 小写转换为大写
复制代码
访问 string 中的某一个字符,有俩种方式 1. 可以通过[ ], 2.可以通过迭代器
s[0] 表示第一个字符
s[size() - 1] 表示最后一个字符
string 对象的下标是从 0 开始到 size() - 1 结束
索引必须大于等于 0 小于 size,使用索引前最好用 if(!s.empty()) 判断一下字符串是否为空。
任何表达式只要是整型值就可以作为索引。索引是无符号类型 size_type;
🍌3.3 标准库类型 vector
🍑3.3.1 定义和初始化 vector 对象
vector <int> v1; //vector默认初始化是一个0.
vector<string> v2(v1); // v2=v1
vector<string> v2 = v1; //等价于v2(v1)
vector<string> v3(10,"dainian"); // 10个string
vector<string> v4(10); // 10个空string
vector<string> v5{"a","hahah"}; // 列表初始化
vector<string> v5 = {"a","hahah"}; //等价上面
复制代码
列表初始化
vector v {10}; // v 有 10 个默认初始化的元素 vector v {10, "hello"}; // v 有 10 个值为 "hi" 的元素
**值初始化**
如果vector 对象的元素是内置类型,比如int 则元素初始值为0,如果是类类型,则由类默认初始化。
### 🍑3.3.2 像vector对象中添加元素
- v.push_back(e) 在尾部增加元素。
- 为了可以使得vector高效增长,通常先定义一个空 vector,然后在添加元素会更快速。
- 如果插入的vector中的初始值值都是一样的此时初始化时确定大小与值会更快,如果初始值值不一样,即使已知大小,最好也先定义一个空的 vector,再添加元素。
### 🍑3.3.3 其他vector操作
复制代码
v.size(); //返回 v 中元素的个数 v.empty(); //如果 v 不含有任何元素,返回真;否则返回假 v.push_back(t); //向 v 的尾端添加一个值为 t 的元素 v[n] //返回 v 中第 n 个位置上元素的引用 v1 = v2 //用 v2 中的元素拷贝替换 v1 中的元素 v1 = {a,b,c...} //用列表中元素的拷贝替换 v1 中的元素 v1 == v2 // v1 和 v2 相等当且仅当它们的元素数量相同且对应位置的元素值都相同 v1 != v2<,<=,>, >= //以字典顺序进行比较
- 可以用范围 for 语句处理 vector 序列的元素,范围for语句内不应该改变其遍历序列的大小。
- vector对象(以及string对象)的下标运算符,只能对确知已存在的元素执行下标操作`(只能访问元素,或者更改已经存在的元素)`,但是`不能用于添加元素。`
## 🍌3.4 迭代器介绍
- 所有标准库容器都可以使用迭代器,但是只有少数几种才同时支持下标操作,`如果是容器,尽量都采用迭代器进行操作`
- 类似于指针类型,迭代器也提供了对对象的间接访问。(可以访问某个元素,也可以移动迭代器,`好比指针`)
- 迭代器分为有效和无效。
### 🍑3.4.1 使用迭代器
- begin 返回指向第一个元素的迭代器
- end 返回指向最后一个元素的下一位的迭代器(通常被称为尾后迭代器)
复制代码
auto b = v.begin(), e = v.end() //返回的是 iterator 类型 auto c = v.cbegin(), f = v.cend() //返回的是 const_iterator 类型
注意:如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器
**迭代器运算符**
| 运算符 | 解释 |
| ---------------- | -------------------------------------- |
| *iter | 返回迭代器iter所指向的**元素的引用** |
| iter->mem | 等价于(*iter).mem |
| ++iter | 令iter指示容器中的下一个元素 |
| - -iter | 令iter指示容器中的上一个元素 |
| iter1 == iter2 | 判断两个迭代器是否相等 |
| iter1 != iter2 | 判断两个迭代器是否不相等 |
**迭代器类型**
迭代器分为俩种类型:
- terator
- const_iterator
复制代码
vector::iterator it; //it 能读写 string::iterator it;
vector::const_iterator it; //it 只能读 string::const_iterator;
**begin和end返回值类型**
- begin 和 end返回值具体类型由对象是否是常量来决定,如果对象是常量则,begin和end返回const_iterator,否则相反。
复制代码
vector v;const vector v1;auto it = v.begin(); //it 类型是 iteratorauto it1 = v1.begin(); //it1 类型为 const_iterator
**注意:凡是使用了迭代器进行循环,都不要向迭代器所属的容器中添加或者删除元素,可以更改元素,都则,迭代器不知道指向哪个元素,迭代器会失效**
**迭代器运算**
string 和 vector支持的迭代运算。注意不能将俩个迭代器相加。
`vector`和`string`迭代器支持的运算:
| 运算符 | 解释 |
| -------------------- | ------------------------------------------------------------ |
| iter + n | 迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。 |
| iter - n | 迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。 |
| iter1 += n | 迭代器加法的复合赋值语句,将`iter1`加n的结果赋给`iter1` |
| iter1 -= n | 迭代器减法的复合赋值语句,将`iter2`减n的加过赋给`iter1` |
| iter1 - iter2 | 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。 |
| >、>=、<、<= | 迭代器的关系运算符,如果某迭代器
- difference_type:保证足够大以存储任何两个迭代器对象间的距离,可正可负。
- 迭代器只有相减,没有相加。
## 🍌3.5 数组
- 数组的大小,长度固定,不可以变,相当于vector的低级版
### 🍑3.5.1 定义和初始化内置数组
- 数组的维度必须是个常量表达式
复制代码
int a[0]; //数组的维度必须是个常量表达式 unsigned cnt = 42 //不是常量表达式 constexpr unsigned cnt = 42; //是常量表达式 string strs[get_size()]; //当 get_size()是 constexpr 时,是常量表达式
- 数组和vector的元素必须是对象,不能是引用
- 数组不能用auto关键由初始值列表推断类型
**字符数组的特殊性**
复制代码
char a1[] = {'c','+', '+' }; //列表初始化,没有空字符,维度是 3char a2[] = "c ++"; //有空字符串,维度是 4;const char a4[3] = "c++"; //错误,没有空间存放空字符
不能用数组为另一个数组赋值或拷贝。可以按元素一个一个拷贝,但不能直接拷贝整个数组。
**理解复杂的数组声明**
因为数组本身是一个对象,所以允许定义数组的指针以及数组的引用。
`从数组的名字开始右内向外的顺序比较好理解`
复制代码
int *ptr[10]; // ptrs 是一个含有 10 个整型指针的数组 int (*ptr)[10] = &arr; // ptrs 是一个指针,指向一个含有 10 个整数的数组 int (&ptr)[10] = &arr; // ptrs 是一个引用,引用一个含有 10 个整数的数组
例如:
- 先读(),*ptr是一个指针,在往右看,这个指针指向一个数组,在往左看,这个数组是 int 型的, 所以int `(*ptr)[10] = &arr`; ptrs是一个指针,指向一个含有10个整数的数组
### 🍑3.5.2 访问数组元素
- 数组的元素可以使用范围`for语句`或者`下标运算符`来进行访问
- 在使用数组下标时候,通常定义为`size_t类型`(是一种无符号类型,他被设计的足够大以便能表示内存中任意对象的大小)
**数组相比vector的缺点是什么**
- 数组的大小是确定的。
- 不能随意增加元素。
- 不允许拷贝和赋值。
数组不能直接拷贝,而是需要每个元素拷贝。而vector可以直接拷贝
//数组需要每个元素进行拷贝
int arr[10];
for (int i = 0; i < 10; ++i) arr[i] = i;
int arr2[10];
for (int i = 0; i < 10; ++i) arr2[i] = arr[i];
//vector可以直接拷贝
vector<int> v(10);
for (int i = 0; i != 10; ++i) v[i] = arr[i];
vector<int> v2(v);
for (auto i : v2) cout << i << " ";
### 🍑3.5.3 指针和数组
- 在大多数情况,使用数组类型的对象其实是使用一个指向该数组首元素的指针
- 标准库类型(如 string、vector 等)的下标都是无符号类型,而数组内置的下标没有这个要求。
- 指向数组元素的指针等价于 vector 中的迭代器
- 指针访问数组:在表达式中使用数组名时,名字会自动转换成指向数组的第一个元素的指针。
- 使用数组时,编译器一般会把它转换成指针。
### 🍑3.5.4 c风格字符串
- c++是由c继承过来的c风格字符串
- c 风格字符串`不是一种类型,而是一种写法`,是为了表达和使用字符串而形成的一种约定俗成的写法。
- 按此习惯书写的字符串存放在字符数组中并以`空字符('\0')`结束。
- c++ 支持 c 风格字符串,但是最好不要使用,极易引发程序漏洞,对大多数应用来说,使用标准库 string比使用C风格字符串更安全、更高效。
| 函数 | 介绍 |
| ---------------- | ------------------------------------------------------------ |
| strlen(p1) | 返回`p1`的长度,空字符不计算在内 |
| strcmp(p1, p2)| 比较`p1`和`p2`的相等性。如果`p1==p2`,返回0;如果`p1>p2`,返回一个正值;如果`p1<p2`,返回一个负值。 |
| strcat(p1, p2) | 将`p2`附加到`p1`之后,返回`p1` |
| strcpy(p1, p2) | 将`p2`拷贝给`p1`,返回`p1`
- 上述表中所列的函数不负责验证其字符串参数
- 传入参数的指针必须指向以空字符结束的数组。必须确保数组足够大。
复制代码
char ca[] = {"hello", "world"} //不以空字符结束 cout << strlen(ca) << endl; //错误:ca 没有以空字符结束
`对于 string,可以使用 s = s1 + s2,s1 > s2 等加和与比较,而 c 风格字符串不行,因为他们实际上是指针。`
### 🍑3.5.5与旧代码的接口
**string对象和C风格字符串的混用**
- 可以使用字符串字面值来初始化 string 对象或与 string 对象加和,所有可以用字符串字面值的地方都可以使用以空字符结束的字符数组来代替。
- 反过来不能使用 string 对象初始化字符数组,必须要用 **c_str()** 函数将 string 对象转化为 c 风格字符串
复制代码
string s ("hello world");const char str = s; //错误,不能用 string 对象初始化 charconst char* cp = s.c_str(); // s.c_str() 返回一个指向以空字符结束的字符数组的指针。
**使用数组初始化 vector 对象**
- 不允许使用一个数组为另一个内置类型的数组赋值
- 可以使用数组来初始化 vector 对象,用两个指针来表明范围(左闭合区间)
复制代码
int arr[] = {0, 1, 2, 3, 4, 5};vector ivec(begin(arr), end(arr));
**建议不要使用 c 风格字符串和内置数值,都使用标准库容器**
## 🍌3.6 多维数组
严格来说 C++ 中没有多维数组,那实际是数组的数组。
复制代码
int arr20[40] //将所有元素初始化为 0
//显示初始化所有元素 int a3 = {1,2,3,4,5,6,7,8,9,10,11,12}int a3 = {{1,2,3},{4,5,6},{7,8,9},{10,11,12}} //与上面等价
//显示初始化部分元素 int a3 = {{0},{1},{2}}; //只是初始化了每一行的第一个元素
int arr3;arr[0];//这是一个有四个元素的一维数组 arr0;//第一行第一列的元素
**使用范围 for 语句处理多维数组**
- c ++11中可以使用范围 for 语句处理多维数组。
- `注意范围 for 语句中改变元素值要显示使用 & 符号声明为引用类型。`
- `注意使用范围 for 循环处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。`
- 因为如果不声明为引用类型,编译器会自动将控制变量转换为指向数组首元素的指针,就不能在内层继续使用范围 for 循环处理该控制变量了。
复制代码
constexpr size_t rowCnt = 3, colCnt = 4;int ia rowCnt;
for(auto& row : arr) //给每一行赋值
for(auto col : row) //给每一列进行赋值,例如先赋值[0][0],[0][1],[0][2]
输出 arr 的元素有四种方法
- 范围 for 语句-不使用类型别名
- 范围 for 语句-使用类型别名
- 普通 for 循环
- 指针
复制代码
// 范围 for 语句-不使用类型别名 for (const int (&row)[4] : arr)
for (int col : row)
cout << col << " ";
复制代码
cout << endl;
// 范围 for 语句-使用类型别名 using int_array = int[4];for (int_array &p : ia)
for (int q : p)
cout << q << " ";
复制代码
cout << endl;
// 普通 for 循环 for (size_t i = 0; i != 3; ++i)
for (size_t j = 0; j != 4; ++j)
cout << arr[i][j] << " ";
复制代码
cout << endl;
// 指针 for (int (*row)[4] = arr; row != arr + 3; ++row)
for (int *col = *row; col != *row + 4; ++col)
cout << *col << " ";
复制代码
cout << endl;
**指针vs引用**
- 引用总是指向某个对象,定义引用时没有初始化是错的。
- 给引用赋值,修改的是该引用所关联的对象的值,而不是让引用和另一个对象相关联
**动态数组**
- 使用 `new`和 `delete`表达和c中`malloc`和`free`类似的功能,即在堆(自由存储区)中分配存储空间。
- 定义: `int *pia = new int[10]; `10可以被一个变量替代。
复制代码
评论