c++笔记之字符串、向量、和数组
问题
- 使用加号连接字符串/string时要注意什么
- string 的索引是什么类型,s.size() 返回什么类型。
- 如何方便地判断 string 中的某个字符的类型(比如是数字还是字母)以及转换某个字符的大小写。
- 值初始化的结果是怎样的
- 定义 c 风格数组时数组维度的限制条件
- 如何使用数组来初始化 vec
- string 类型可以隐式转化为 c 风格字符串(即字符数组)吗?
- 如何将 string 类型转化为 c 风格字符串
- 使用 getline() 函数从输入流读取字符串存到 string 中,存储的内容有换行符吗?
- 使用范围for循环要注意什么?
回答
- 加号两边至少有一个是 string 类型,不能都是字符串
- 都是 string::size_type 类型,是无符号值。
- 使用 cctype 头文件中的 isalnum(), isalpha(), isdigit(), isupper(), islowwer(), ispunct(), isspace(), tolower(), toupper() 等类型。
- 值初始化会将内置类型初始化为 0,类类型由类自己来默认初始化。
- 维度必须是个常量表达式,即在编译阶段就可以确定值。(因为数组维度是数组的类型的一部分,而 C++ 是静态语言,即在编译阶段就要确定类型)
- vector vec(begin(arr),end(arr));
- 不可以(从 C 风格字符串到 string 的转换是用了 string 的转换构造函数,而 string 并没有定义到 C 风格字符串的类型转换运算符)
- 使用 c_str() 函数
- 没有换行符。
- 如果要修改循环变量的值要将其声明为引用类型:auto &
问题
- 如果容器为空,begin() 的返回值是什么?
- 使用数组时要注意数组维度的什么特点?
- 区分 int *ptrs[10]; int (*ptrs)[10]; int (&ptrs)[10] 的不同含义
- C风格字符串属于什么类型?
回答
- 返回的是尾后迭代器,和 end() 的返回值一样。
- 使用数组时注意数组的维度必须是个常量表达式,因为数组的维度也属于数组类型的一部分,而编译器在编译阶段就需要知道数组类型。
- 他们分别定义了:一个包含10个整型指针的数组,一个指向包含10个整型值的数组的指针,一个包含10个整型值的数组的引用。
- C风格字符串本身不是类型,而是一种写法,它的类型是字符数组。要从字符数组的角度来理解C风格字符串的各项操作。
第3章 字符串、向量和数组
string、vector是两种最重要的标准库类型,迭代器是一种与 string 和 vector 配套的标准库类型。
内置数组和其他内置类型一样,数组的实现和硬件密切相关,因此与string和vector相比,数组在灵活性上稍显不足。
3.1 命名空间的using声明
可以对单个名字进行独立的using声明
1 |
|
●using namespace::name;
●using namespace nameSpaceName;
1 |
|
头文件里不应包含 using 声明,因为会被拷贝到引用该头文件的文件中
3.2 标准库类型string
string 表示可变长的字符序列,需要包含string头文件
string 定义在命名空间 using 中。
3.2.1 定义和初始化string对象
string 默认初始化为一个空的 string。
1 | string s1; //将 s1 默认初始化为一个空的 string |
注意:使用字符串字面值或字符数组初始化 string 对象时,string 对象中是不包含末尾的空字符的,它会将字符数组中末尾的空字符去掉。
初始化方式
拷贝初始化:使用等号
直接初始化:不使用等号
列表初始化:使用花括号{}
3.2.2 string对象上的操作
| os<<s | 将s写到输出流os当中,返回os |
| is>>s | 从is中读取字符串赋给s,字符串以空白分隔,返回is |
| getline( is,s ) | 从is中读取一行赋给s,返回is |
| s.empty() | s为空返回true,否则返回false |
| s.size() | 返回s中字符的个数,返回值为string::size_type类型 |
| s[n] | 返回s中第n个字符的引用,位置n从0记起 |
| s1+s2 | 返回s1和s2连接后的结果 |
| s1=s2 | 用s2的副本替代s1中原来的字符 |
| s1==s2 | 判断s1和s2是否完全一样 |
| s1!=s2 | 判断s1和s2是否不完全一样 |
| <,<=,>,>= | 利用字符在字典中的顺序进行比较 |
因为 cin 会自动忽略开头的空白并遇到空白就停止读取,因此不能使用 cin 读取句子;
字符串字面值与 string 是两种不同的类型
读写string对象
可以使用 cin, cout 来读写 string 对象,也可以使用 stringstream 来读写 string 对象。
getline 函数
getline() 定义在头文件 string 中,以一个 istream 对象和一个 string 对象为输入参数。getline() 读取输入流的内容直到遇到换行符停止,然后将读入的数据存入 string 对象。
注意 getline 会将换行符也读入,但是不将换行符存入 string 对象。即触发 getline() 函数返回的那个换行符实际上被丢弃掉了。
getline() 只要一遇到换行符就结束读取操作并返回结果,即使一开始就是换行符也一样,这种情况下会得到一个空 string。
getline() 与 << 一样,会返回它的流参数。所以可以用 getline 的结果作为条件。
string::size_type 类型
string 的 size() 成员函数返回一个 string::size_type 类型的值。
大多数标准库类型都定义了几种配套的类型,这些配套类型体现了标准库与机器无关的特性。
在具体使用时,通过作用域操作符来表明 size_type 是在类 string 中定义的。
string::size_type 是无符号值,可以确定的是它足够存放任何 string 对象的大小。
C++11 允许通过 auto 和 decltype 来获得此类型。
1 | auto len = s.size();// len 的类型是 string::size_type |
不要在同一个表达式中混用 size_type 和 int 类型。
3.2.3 处理string对象中的字符
cctype 头文件中有下列标准库函数来处理 string 中的字符。
C++语言中,字符串字面值并不是stirng对象。
下面这些函数的输入和返回值实际都是 int 类型的,且输入的值 c 必须满足 -1<=c<=255,即输入必须是 ASCII 字符。
| cctype头文件(ctype.h)中的函数(不需要背,学一遍知道有就可以了) | |
|---|---|
| isalnum(c) | 如果c是字母或数字,该函数返回true |
| isalpha(c) | 如果c是字母,该函数返回真 |
| iscntrl(c) | 如果c是控制字符,该函数返回true |
| isdigit(c) | 如果c是数字(0~9),该函数返回true |
| isgraph(c) | 如果c是除空格之外的打印字符,该函数返回true |
| islower(c) | 如果c是小写字母,该函数返回true |
| isprint(c) | 如果c是打印字符(包括空格),该函数返回true |
| ispunct(c) | 如果c是标点符号,该函数返回true |
| isspace(c) | 如果c是标准空白字符,如空格、进纸、换行符、回车、水平制表符或者垂直制表符,该函数返回true |
| isupper(c) | 如果c是大写字母,该函数返回true |
| isxdigit(c) | 如果c是十六进制的数字,即0~9、a |
| tolower(c) | 如果c是大写字符,则返回其小写,否则返回该参数 |
| toupper(c) | 如果c是小写字母,则返回其大写,否则返回该参数 |
建议:使用 c++ 版本的标准库头文件,即 cname 而非 name.h 类型的头文件。cname 头文件中的名字都从属于命名空间 std;
范围for语句
1 | string str; |
当要改变 string 对象中的值时,需要把循环变量定义成引用类型。必须通过显示添加 & 符号来声明引用类型。
不能在范围 for 语句中改变所遍历序列的大小。
1 | for(auto &c:str) |
对 string 的最后一个字符进行索引:s[s.size()-1];
索引必须大于等于 0 小于 size,使用索引前最好用 if(!s.empty()) 判断一下字符串是否为空。
任何表达式只要是整型值就可以作为索引。索引是无符号类型 size_type;
3.3 标准库类型vector
vector:对象的集合
vector是模板,由vector生成的模板必须包含vector中元素的类型
例如vector
引用不是对象,所以不存在包含引用的vector
3.3.1 定义和初始化vector对象
vector 默认初始化为一个空 vector。
| 更多操作初始化vector对象的方法 | |
|---|---|
| vector |
v1是一个空vector,它潜在的元素是T类型的,指向默认初始化 |
| vector |
v2中包含有v1所有元素的副本 |
| vector |
等价于v2(v1) |
| vector |
v3包含n个重复个元素,每个元素的值都是val |
| vector |
v4包含了n个重复地执行了值初始化的对象 |
| vector |
v5包含了初始值个数的元素,每个元素都被赋予相应的初始值 |
| vector |
等价于vector |
值初始化
值初始化的方式:如果对象是内置类型,则初始值为 0,如果是类类型,则由类默认初始化。
列表初始化
使用花括号一般表示列表初始化:初始化过程会尽量把花括号内的值当作一个初始值列表来处理。
如果花括号内的值不能用来列表初始化,比如对一个 string 的 vector 初始化,但是花括号内的值为整型,如下:
1 | vector<string> v {10}; // v 有 10 个默认初始化的元素 |
3.3.2 向vector对象中添加元素
vector可以高效增长,通常先定义一个空 vector,然后在添加元素会更快速。
定义 vector 时,已知 vector 的大小,如果初始值都一样,初始化时确定大小与值会更快。如果初始值不全一样,即使已知大小,最好也先定义一个空的 vector,再添加元素。
3.3.3 其他vector操作
| Vector****支持的操作 | |
|---|---|
| v.empty() | 如果v不含有任何元素,返回阵:否则返回假 |
| v.size() | 返回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 序列的元素
3.4 迭代器介绍
类似指针
使用迭代器可以访问某个元素,迭代器也能从一个元素移动到另一个元素。
有迭代器的类型都拥有begin和end成员
begin:返回指向第一个元素(或字符)的迭代器
end:尾后迭代器,即尾元素的下一个位置(一个本不存在的元素)
所有标准库容器都可以使用迭代器
3.4.1 使用迭代器
1 | auto b = v.begin(), e = v.end(); //b和e的类型相同 |
如果容器为空,则 begin 和 end 返回的都是尾后迭代器
| 标准容器迭代器的运算符 | |
|---|---|
| *iter | 返回迭代器iter所指元素的引用 |
| iter->mem | 解引用iter并获取该元素的名为mem的成员,等价于(*iter).mem |
| ++iter | 令iter指示容器中的下一个元素(尾后迭代器除外) |
| –iter | 令iter指示容器中的上一个元素 |
| iter1 == iter2 | 如果两个迭代器指示的是同一个元素则相等,否则不等。 |
| iter1 != iter2 | 同上 |
尾后迭代器 并不实际指示某一个元素,所以不能对其进行递增或解引用
拥有迭代器的标准类型使用iterator和const_iterator(和常量指针差不多)
1 | vector<int>::iterator it = v.begin(); |
迭代器类型:
拥有迭代器的标准类型使用iterator和const_iterator(和常量指针差不多)
1 | vector<int>::iterator it; //it能读写vector<int>元素 |
如果对象是常量,begin和end返回const_iterator,否则返回iterator:
1 | vector<int> v; |
有时候我们希望即使对象不是常量,我们也要使用const_iterator:
C++11新标准引入了cbegin和cend:
1 | auto it3 = v.cbegin(); //it3的类型是vector<int>::const_iterator |
结合解引用的成员访问:
1 | vector<string> v; |
1 | int main() { |
任何一种可能改变vector对象容量的操作,都会使得对于的迭代器失效
3.4.2 迭代器运算
string 和 vector 支持的迭代器运算。注意不能将两个迭代器相加。
1 | iter+n |
可以令迭代器和一个整数相加(或相减),其返回值是向前(或向后)移动了若干位置的迭代器。
3.5 数组
数组:复合类型(声明形如:a[d])
a:数组名称
d:元素个数(必须是常量表达式)
1 | unsigned cnt = 42; |
constexpr只限制只读,并不要求在编译时就确定,可以在运行时确定。只有编译时constexpr才可以表示数组大小
3.5.1 定义和初始化内置数组
不存在引用数组
可以使用列表初始化,但必须指定数组类型,不允许使用auto
1 | const unsigned sz = 3; |
字符数组的特殊性:字符串字面值的结尾处还有一个空字符
1 | char a1[] = {'C','+','+'}; //列表初始化,没有空字符 |
不能用数组为另一个数组赋值或拷贝。可以按元素一个一个拷贝,但不能直接拷贝整个数组。
1 | int a[] = {0,1,2}; |
按照由内向外的顺序理解数组的类型
1 | int *ptrs[10]; //ptrs是含有10个元素(整型指针)的数组 |
3.5.2 访问数组元素
数组下标通常用 size_t 类型
使用范围 for 语句遍历数组元素
3.5.3 指针和数组
使用数组的时候,编译器一般会把它转换成指针
指向数组元素的指针等价于 vector 中的迭代器
3.5.4 C风格字符串
c++ 支持 c 风格字符串,但是最好不要使用,c 风格字符串使用不便,并且极易引发程序漏洞
c 风格字符串不是一种类型,而是一种写法,是为了表达和使用字符串而形成的一种约定俗成的写法。
用这种写法书写的字符串存放在字符数组中并以空字符(’\0’)结束。
c 风格字符串函数
1 | strlen(p); // 返回 p 的长度,不包括空字符 |
这些函数都不验证参数。传入参数的指针必须指向以空字符结束的数组。必须确保数组足够大。
1 | char ca[] = {'q','b','d'}; // 使用列表定义的都没有空字符 |
对于 string,可以使用 s = s1 + s2,s1 > s2 等加和与比较,而 c 风格字符串不行,因为他们实际上是指针。
3.5.5 与旧代码的接口
string对象和C风格字符串的混用
可以使用字符串字面值来初始化 string 对象或与 string 对象加和,所有可以用字符串字面值的地方都可以使用以空字符结束的字符数组来代替。
反过来不能使用 string 对象初始化字符数组,必须要用 c_str() 函数将 string 对象转化为 c 风格字符串
1 | const char* cp = s.c_str(); // s.c_str() 返回一个指向以空字符结束的字符数组的指针。 |
使用数组初始化 vector 对象
可以使用数组来初始化 vector 对象,用两个指针来表明范围(左闭合区间)
1 | int arr[] = {0, 1, 2, 3, 4, 5}; |
建议不要使用 c 风格字符串和内置数值,都使用标准库容器
3.6 多维数组
严格来说 C++ 中没有多维数组,那实际是数组的数组。
1 | int arr[10][20][30] = {0}; // 将所有元素初始化为 0 |
多维数组的初始化
1 | '显式初始化所有元素' |
多维数组的下标引用
1 | int arr[2][3]; |
使用范围 for 语句处理多维数组
新标准中可以使用范围 for 语句处理多维数组。
注意范围 for 语句中改变元素值要显示使用 & 符号声明为引用类型。
注意:使用范围 for 循环处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
因为如果不声明为引用类型,编译器会自动将控制变量转换为指向数组首元素的指针,就不能在内层继续使用范围 for 循环处理该控制变量了。
1 | for(auto& row : arr) |
使用 3 种方式来输出 arr 的元素
1 | // 范围 for 语句-不使用类型别名 |







