Featured image of post C++ Primer Ch03

C++ Primer Ch03

Strings, vectors and arrays

# 字符串、向量和数组

# 命名空间的 using 声明

我们使用的库函数都有一个对应的命名空间,通常需要在声明或初始化变量时指定命名空间。为了简化这个操作,我们可以使用using进行声明:

1
2
3
using std::cout;  // 单独使用某个函数

using namespace std;  // 批量声明 std 中所有函数

头文件中不应该包含 using 声明,这样使用了该头文件的源文件也会使用这个声明,会带来风险。

# 标准库类型 string

标准库类型 string 表示可变长的字符序列,使用 string 类型必须首先包含 string 头文件:

1
2
3
#include <string>

using std::string

# 定义和初始化 string 对象

初始化 string 对象的方式:

方式 解释
string s1 默认初始化,s1是个空字符串
string s2(s1) s2s1的副本
string s2 = s1 等价于s2(s1)s2s1的副本
string s3("value") s3是字面值“value”的副本,除了字面值最后的那个空字符外
string s3 = "value" 等价于s3("value")s3是字面值"value"的副本
string s4(n, 'c') s4初始化为由连续n个字符c组成的串

拷贝初始化(copy initialization):使用 = 将一个已有的对象拷贝到正在创建的对象。

直接初始化(direct initialization):通过括号给对象赋值。

# string 对象的操作

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中字符的个数
s[n] 返回s中第n个字符的引用,位置n从0计起
s1+s2 返回s1s2连接后的结果
s1=s2 s2的副本代替s1中原来的字符
s1==s2 如果s1s2中所含的字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感
s1!=s2 同上
<, <=, >, >= 利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较)

读取 string 对象:

  • 使用 IO 操作符 >> 读取:忽略开头的空白(空格符、换行符、制表符等),从第一个真正的字符开始读起,直到遇到下一个空白。
  • 使用 getline() 函数读取:将一整行读取为 string 对象,包括空白。

s.size() 返回 string::size_type 类型,是 无符号 类型的值,不能和 int 混用。

s1 + s2 使用时,必须保证至少其中一个为 string 类型。例如:string s = "hello" + "world" 错误,其 + 两边都为字符串字面值。

字符串字面值string 是不同的类型。

# 处理 string 对象中的字符

C++ 修改了 c 的标准库 ctype.hcctype,其中定义了一组标准函数:

函数 解释
isalnum(c) c是字母或数字时为真
isalpha(c) c是字母时为真
iscntrl(c) c是控制字符时为真
isdigit(c) c是数字时为真
isgraph(c) c不是空格但可以打印时为真
islower(c) c是小写字母时为真
isprint(c) c是可打印字符时为真
ispunct(c) c是标点符号时为真
isspace(c) c是空白时为真(空格、横向制表符、纵向制表符、回车符、换行符、进纸符)
isupper(c) c是大写字母时为真
isxdigit(c) c是十六进制数字时为真
tolower(c) c是大写字母,输出对应的小写字母;否则原样输出c
toupper(c) c是小写字母,输出对应的大写字母;否则原样输出c

遍历字符串:

1
2
3
for (auto c : str) {
    ...
}

str[idx] 中的 idxstring::size_type 类型,如果使用 int 会隐式转换为该类型。

# 标准库类型 vector

标准库类型 vector 表示对象的集合,其中给所有对象的类型都相同。因为 vector 容纳着其他对象,所以称其为 容器(container),使用 vector 必须包含其头文件:

1
2
3
#include <vector>

using std::vector

vector 同时也是 类模板(class template),模板本身不是类或函数,但可以使用模板创建类,这个过程称为 实例化(instantiation)

当使用模板时,需要指出编译器应把类或函数实例化成何种类型:

1
2
vector<int> ls;  // ls 保存 int 类型的对象
vector<vector<string>> files;  // 该向量中的元素是 vector 对象

vector 是模板,vector<int> 是类型。

# 定义和初始化 vector 对象

初始化vector对象的方法:

方法 解释
vector<T> v1 v1是一个空vector,它潜在的元素是T类型的,执行默认初始化
vector<T> v2(v1) v2中包含有v1所有元素的副本
vector<T> v2 = v1 等价于v2(v1)v2中包含v1所有元素的副本
vector<T> v3(n, val) v3包含了n个重复的元素,每个元素的值都是val
vector<T> v4(n) v4包含了n个重复地执行了值初始化的对象
vector<T> v5{a, b, c...} v5包含了初始值个数的元素,每个元素被赋予相应的初始值
vector<T> v5={a, b, c...} 等价于v5{a, b, c...}

# vector 对象的操作:

vector支持的操作:

操作 解释
v.emtpy() 如果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 v1v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同
v1 != v2 同上
<,<=,>, >= 以字典顺序进行比较

# 迭代器介绍

除了下标运算符外,迭代器(iterator) 也可以访问对象中的元素,所有标准库的容器都支持迭代器。类似于指针类型,迭代器也提供了对对象的间接访问。

# 使用迭代器

拥有迭代器的类型都具有 beginend 成员,其中 begin 成员返回指向第一个元素的迭代器:

1
2
vector<int> ls{1, 2, 3};
auto b = v.begin(), e = v.end();  // b 和 e 类型相同

end 成员返回指向容器“尾元素的下一个位置(one past the end)”的迭代器,即 end 指向容器的 尾后(off the end) 元素。这样的迭代器通常没有意义,只是作为标记,被称为 尾后迭代器(off-the-end iterator)尾迭代器(end iterator)

若容器为空,则 beginend 都返回尾后迭代器。

标准容器迭代器的运算符:

运算符 解释
*iter 返回迭代器iter所指向的元素的引用
iter->mem 等价于(*iter).mem
++iter iter指示容器中的下一个元素
--iter iter指示容器中的上一个元素
iter1 == iter2 判断两个迭代器是否相等

泛型编程:尽量使用 != 来对迭代器进行判断

迭代器也拥有自己的类型:

1
2
vector<int>::iterator it;  // it 是 vector<int> 类型的迭代器,可以读写元素
vector<int>::const_iterator it2;  // it2 只能读,不能写

如果容器中的值为常量,则 beginend 返回 const_iterator,否则返回 iterator

解引用和成员访问:解引用迭代器可以获得迭代器所指的对象,如果该对象是一个类,则可以进一步访问其成员:

1
2
3
4
5
6
vector<string> ls{"str1", "str2"};

auto it = ls.begin();
string s = *it;  // s 为 "str1"
bool flag = (*it).empty();  // 解引用访问 string 成员
bool flag = it->empty();  // 作用同上

# 迭代器运算

stringvector 的迭代器提供了额外的运算符,支持迭代器的关系运算和跨过多个元素,这些运算称为 迭代器运算(iterator arithmetic)

运算符 解释
iter + n 迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。
iter - n 迭代器减去一个整数仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。
iter1 += n 迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1
iter1 -= n 迭代器减法的复合赋值语句,将iter2减n的加过赋给iter1
iter1 - iter2 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置。
>>=<<= 迭代器的关系运算符,如果某迭代器

当两个迭代器指向同一个容器时,它们可以进行加减操作得到距离,这个距离的类型为 difference_type 类型,是带符号整数型。

# 数组

数组可以看做 vector 的低配版,其 长度固定

# 定义和初始化内置数组

数组的声明和定义形如 a[d],其中 a 是数组的名字,d 是数组的维度(大于 0):

1
2
3
4
5
6
7
8
int cnt = 42;  // 非常量
const int cnt2 = 42;  // 常量

int arr[10]; // 含有 10 个整数的数组
int *arr2[10];  // 含有 10 个整型指针的数组
int arr3[cnt];  // 报错,cnt 非常量
int arr4[cnt2];  // 含有 42 和整数的数组
int arr5[] = {1, 2, 3};  // 自动计算长度的数组

字符数组具有一定特殊性,使用字符串初始化字符数组时在结尾处必须增加一个空字符:

1
2
char arr[] = "hello";
char arr2[5] = "hello";  // 报错,长度不够

不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值:

1
2
3
int a[] = {1, 2, 3};
int a2[] = a;  // 报错,不能用数组来初始化数组
a2 = a;  // 报错,不能用数组进行赋值

# 访问数组元素

数组的下标为 size_t 类型,是一种机器相关的无符号类型,它被设计得足够大以便能够表示内存中任意对象的大小。

下标存在越界导致缓冲区溢出等情况,这种情况需要程序员自行检查。

# 指针和数组

使用数组时,编译器会将其转换成指针。使用取地址符可以获取数组的元素的指针,如果是取数组的指针,则默认返回数组第一个元素的指针:

1
2
3
int ls[] = {1, 2, 3}
int *p = &ls[0];  // 数组的元素的指针
int *p2 = ls;  // 等价于 *p2 = &ls[0]

# C 风格字符串

字符串字面值是一种通用结构的实例,这种结构是 C++C 继承而来的 C 风格字符串(C-style character string) 。 按此习惯书写的字符串存放在字符数组中并以 空字符结束(null terminated)

C++ 程序中尽量不要使用 C 风格字符串,容易引起安全漏洞且不方便。

C标准库String函数,定义在<cstring> 中:

函数 介绍
strlen(p) 返回p的长度,空字符不计算在内
strcmp(p1, p2) 比较p1p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一个正值;如果p1<p2,返回一个负值。
strcat(p1, p2) p2附加到p1之后,返回p1
strcpy(p1, p2) p2拷贝给p1,返回p1

# 多维数组

严格来说,C++ 语言中没有多维数组,所谓的多维数组实际是数组的数组。

1
int arr[10][20] = {0};  // 长度为 10 的数组,其中每个元素是长度为 20 的数组,且都初始化为 0

# 使用范围 for 语句处理多维数组

1
2
3
4
5
6
7
size_t cnt = 0;

for (auto &row: arr) {
    for (auto &col: row) {
        col = cnt; cnt ++;
    }
}
Built with Hugo
Theme Stack designed by Jimmy