写点什么

2. 字符串、向量和数组

作者:鱼书
  • 2022 年 8 月 30 日
    北京
  • 本文字数:7402 字

    阅读完需:约 24 分钟

String、vector 是俩种最重要的标准库类型,String 支持可变长字符串,vector 支持可变长的集合


迭代器是一种与 string 和 vector 配套的标准库类型。常用于访问 string 中的字符或 vector 中的元素


内置数组是一种基础的类型,string 和 vector 都是对它的某种抽象。

🍌3.1 命名空间的 using 声明

  • 使用某个命名空间:例如 using std::cin 表示使用命名空间 std 中的名字 cin。

  • 头文件中不应该包含 using 声明。这样使用了该头文件的源码也会使用这个声明,对于某些程序来说,可能会名字冲突,带来风险。


using std::cin;
复制代码

🍌3.2 标准库类型 string

  • 标准库类型 string 表示可变长的字符序列。

  • string 定义在命名空间 std 中。(包含头文件)

  • string 对象:不同于字符串字面值。


#include <string>using std::string
复制代码

🍑3.2.1 定义和初始化 string 对象

string 默认初始化是一个空字符串初始化string对象的方式:



注意:


  • 在进行拷贝操作时,是不包含最后一个空字符串的

  • 使用字符串字面值或字符数组初始化 string 对象时,string 对象中是不包含末尾的空字符的,它会将字符数组中末尾的空字符去掉。


初始化方式


  • 拷贝初始化

  • 直接初始化

  • 列表初始化


string s5 = "hello";           //拷贝初始化string s6("hello");            //直接初始化string s7{hello};              //列表初始化
复制代码

🍑3.2.2 string 对象上的操作

string 常用操作



注意:


  • 通过cin >> string,在读取 string 对象时,string 对象会自动忽略开头的空白(空格、换行符、等)并从第一个真正的字符串开始读,知道遇见下一个空白为止,因此不能使用 cin 读取句子,但是可以读取单词

  • 字符串字面值与 string 是两种不同的类型


string s;cin >> s;             //输入hello worldcout << s << endl;    //输出为hello
复制代码


读写 string 对象


使用cincout来读写 string 对象


使用 getline 函数读取一行


  • getline()函数定义在头文件string

  • getline()参数是一个输入流和一个 string 对象,函数从给定的输入流中读取内容,知道遇到换行符位置(注意是换行符和上面空白字符不一样

  • 读取到文件末尾结束


注意 :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 规定允许使用autodeclltype来获取此类型

  • 在具体使用时,通过作用域操作符来表明 size_type 是在类 string 中定义的。


auto len = s.size();// len 的类型是 string::size_type
复制代码


注意:在一条表达式中已经有了 size()函数,就不要使用 int 了,这样可以避免混用 int 和 unsigned 可能会带来问题。


俩个 string 对象相加


  • 俩个 string 对象相加得到一个新的 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 对象和字符字面值混在一起的时候,必须保证每个加法运算(+)的俩侧的运算对象至少有一个是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
复制代码


  • 当要改变 string 对象中的值时,需要把循环变量定义成引用类型。必须通过显示添加 & 符号来声明引用类型。

  • 不能在范围 for 语句中改变所遍历序列的大小。


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

  • vector 是一个类模板而非类型,vector 是一个类型。

  • vector 同时也是一个容器,可以容纳各种数据类型

  • 本身是一个类模板,但是可以实例化出一个类

🍑3.3.1 定义和初始化 vector 对象

vector <int>  v1;                 //vector默认初始化是一个0.vector<string> v2(v1);            // v2=v1vector<string> v2 = v1;           //等价于v2(v1)vector<string> v3(10,"dainian");  // 10个stringvector<string> v4(10);            // 10个空string
vector<string> v5{"a","hahah"}; // 列表初始化vector<string> v5 = {"a","hahah"}; //等价上面
复制代码


列表初始化


  • 使用一对{}来表示列表初始化,初始化过程会尽量把花括号内的值当作一个初始值列表来处理。

  • 如果花括号内的值不能用来列表初始化,比如对一个 string 的 vector 初始化,但是花括号内的值为整型


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可以被一个变量替代。
复制代码


用户头像

鱼书

关注

还未添加个人签名 2022.08.29 加入

还未添加个人简介

评论

发布
暂无评论
2. 字符串、向量和数组_c++_鱼书_InfoQ写作社区