C++ 学习笔记之(3)-字符串、向量和数组
命名空间的using声明
C++的命名空间的目的是为了防止名字冲突。为了使用命名空间的成员,可以使用using
声明。有了using
声明就无需专门的前缀,using
声明的形式如下
using namespace::name;
- 每个名字都需要独立的
using
声明。 - 头文件不应该包含
using
声明,这是因为头文件的内容会拷贝到所有引用它的文件中去,如果头文件里有某个using
声明,那么每个使用了该头文件的文件都会有此声明。可能会导致名字冲突
注意:命名空间需要深入学习
标准库类型 string
标准库类型string
表示可变长的字符序列,string类型定义在命名空间std
中。
定义和初始化 string 对象
- 直接初始化:不使用等号的初始化
- 拷贝初始化:使用等号
=
初始化一个变量,编译器把等号右侧的初始值拷贝到新创建的对象中去。
注意:直接初始化和拷贝初始化需要深入学习
string 对象上的操作
类除了定义初始化其对象的方式外,还要定义对象所能执行的操作。
如果表达式中存在
size()
函数,就不要再使用int
类型了,因为size
函数返回的是string
类的配套类型string::size_type
, 一个无符号类型的值,混用int
和unsigned
会出现问题。标准库允许把字符字面值和字符串字面值转换成
string
对象,即可以将其混用在一条语句中使用,但必须确保每个加法运算符两侧的运算对象至少有一个是string
string s1 = "hello", s2 = "world"; string s3 = s1 + ", " + s2 + '\n';
由于历史原因,且为了与
C
兼容,C++中的字符串字面值并不是标准库类型string
的对象。切记,字符串字面值与string
是不同的类型。
处理 string 对象中的字符
我们经常需要单独处理string
对象中的字符,cctype
头文件定义了一组标准库函数处理这部分工作
C语言头文件形如
name.h
, C++将其命名为cname
, C++程序应该使用后者,因为标准库的名字总能在命名空间std
中找到,若使用.h
头文件,则无法找到。C++11使用范围
for
语句可以对string
对象的每个字符操作,如果要改变string
队形中该字符的值,必须把循环变量定义成引用类型。for(auto &c: str) c = toupper(c); // c 是一个引用,因此赋值语句将改变 s 中字符的值
标准库类型 vector
标准库类型vector
表示对象的集合,其中所有对象的类型都相同,也被称为容器
vector
是模板而非类型,由vector
生成的类型必须包含vector
中元素的类型,例如vector<int>
vector
能容纳绝大多谢类型对象作为元素,但不能是引用,因为引用不是对象,元素也可以是vector
定义和初始化 vector 对象
下表列出了定义vector
对象的常用方法
列表初始化:C++11新标准提供的为
vector
对象的元素赋初值的方法。拷贝初始化:即使用
=
时,只能提供一个初始值如果提供的是一个类内初始值,则只能用拷贝初始化或使用花括号的形式初始化
如果提供是的初始元素值的列表,则只能把初始值放在花括号里进行列表初始化,不能放在圆括号。
vector<int> v1(10); // v1 有 10 个元素,每个值都为0 vector<int> v2{10}; // v2 有 1 个元素,该元素的值是10 vector<int> v3(10, 1); // v3 有 10 个元素,每个的值都是1 vector<int> v4{10, 1}; // v4 有 2 个元素,值为 10 和 1 // 若花括号提供的值不能用来列表初始化,则考虑用值构造 vector 对象 vector<string> v5{"hi"}; // 列表初始化,v5 有一个元素 vector<string> v6("hi"); // 错误:不能使用字符串字面值构建 vector 对象 vector<string> v7{10}; // v7 有 10 个默认初始化的元素 vector<string> v8{10, "hi"}; // v8 有 10 个值为 "hi" 的元素
上面后四条语句除了第二条语句都用了花括号,但只有v5是列表初始化。要想列表初始化,花括号里的值必须与元素类型相同。
其他 vector 操作
vector
对象以及string
对象的下标运算符可用于访问已存在的元素,而不能用于添加元素- 范围
for
语句体内不应改变其所遍历序列的大小
迭代器介绍
迭代器提供了对对象的间接访问,对象是容器中的元素或者string
对象中的字符,迭代器有有效和无效之分,有效的迭代器指向某个元素,或容器中尾元素的下一位置,其他情况都是无效。
使用迭代器
- 有迭代器的类型都拥有返回迭代器的成员, 比如
end
成员,用来返回指向容器尾元素的下一位置
的迭代器, 也被称为尾后迭代器
。如果容器为空,则begin
和end
返回的是同一个迭代器,都是尾后迭代器
- 尽量使用!=
而非<
, 因为所有标准库容器的迭代器都定义了==
和!=
, 但大多都没有定义<
运算符
标准库类型一般使用
iterator
和const_iterator
来表示迭代器类型vector<int>::iterator it; // it 能读写 vector<int> 的元素 vector<int>::const_iterator it3; // it3 只能读元素,不能写元素
如果
vector
或string
对象是常量,则只能使用const_iterator
凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素
迭代器运算
- 两个迭代器相减的结果类型是名为
difference_type
的带符号整型数, 表示两个迭代器的距离
数组
数组与vector
类似,但大小确定不变。
定义和初始化内置数组
数组的维度在编译时应该是已知的, 即维度必须是一个常量表达式
unsigned cnt = 42; // 不是常量表达式 constexpr unsigned sz = 42; // 常量表达式 int arr[10]; // 含有 10 个整数的数组 int *parr[sz]; // 含有 42 个整型指针的数组 string bad[cnt]; // 错误: cnt 不是常量表达式 string strs[get_size()]; // 当 get_size 是 constexpr 时正确,否则错误
默认情况下,数组的元素被默认初始化,如果在函数体内部定义的话,则含有未定义的值
定义数组的时候必须指定数组的类型,不允许使用
auto
关键字推断类型int *ptrs[10]; // ptrs 是含有 10 个整型指针的数组 int &refs[10] = /* ? */; // 错误:不存在引用的数组 int (*parray)[10] = &arr; // Parray 指向一个含有 10 个整数的数组 int (&arrRef)[10] = arr; // arrRef 引用一个含有 10 个整数的数组
使用数组下标访问数组元素的时候,类型为
size_t
, 一种与机器相关的无符号类型,在cstdef
头文件中。
指针和数组
使用数组时,编译器一般会把它转换成指针
在大多数表达式中,使用数组类型的对象其实是使用一个指向该数组首元素的指针
string nums[] = {"one", "two", "three"}; // 数组的元素是 string 对象 string *p = &nums[0]; // p 指向 nums 的第一个元素 string *p2 = nums; // 同上 auto nums2(nums); // nums2 是一个 string 类型指针,指向 nums 的第一个元素,等价于 auto nums2(&nums[0]) decltype(nums) nums3 = {"three", "two", "one"}; // nums3 是一个含有三个string类型对象的数组
C++11新标准引入了两个名为
begin
和end
的函数,与容器的同名函数功能类似,begin
函数返回指向数组首元素指针,end
函数返回指向数组尾元素下一位置的指针int ia[] = {0, 1, 2, 3, 4, 5}; int *beg = begin(ia); // 指向 ia 首元素的指针 int *last = end(ia); // 指向 arr 尾元素的下一位置的指针
两个指针兴建的结果是他们之间的距离,结果是
ptrdiff_t
的带符号标准库类型,定义在cstddef
中标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求,即内置的下表运算符可以是负值。
C 风格字符串
字符串字面值是一种通用结构的实例,该结构即是继承于C的C风格字符串,习惯是字符串存放在字符数组中,并以空字符结束\0
上述函数不负责验证其字符串参数(比如,字符串未以空字符结尾,会报错)
为了衔接使用了C借口的C++程序,C++提供一组功能;
允许C风格字符串初始化
string
对象,反之不行,但可以使用std::c_str
函数获取C风格字符串,返回结果指针,类型是const char *
允许用数组初始化
vector
对象
int int_arr[] = {0, 1, 2, 3, 4, 5}; vector<int> ivec(begin(int_arr), end(int_arr));
多维数组
严格来说,C++没有多维数组,通常诉说的多维数组其实就是数组的数组
int ia[3][4] = {{0}, {4}, {8}}; // 显示的初始化每行的首元素
int ix[3][4] = {0, 3, 6, 9}; // 显示的初始化第一行,其他元素执行值初始化
要使用范围
for
语句处理多维数组时,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。for (auto row : ia) for(auto col : row)
如上所示,由于
row
不是引用类型,所以编译器初始化row
时会自动将这些数组形式的元素转换成指向该数组内荣元素的指针,这样row
的类型是int *
, 所以内层循环会不合法。
结语
string
和vector
是两种最重要的标准库类型。string
对象是一个可变长的字符序列,vector
对象是一组同类型对象的容器
迭代器允许对容器中的对昂进行间接访问,对于string
对象和vector
对象来说,可以通过迭代器访问元素或者在元素箭移动
数组和指向数组元素的指针在一个较低层次上实现了与标准库类型string
和vector
类似的功能。一般啦说,应该优先选用标准库提供的类型,之后在考虑C++语言内置的底层的替代品数组或指针。