3.1 命名空间的using声明
using声明:
- 使用某个命名空间:using namespace + 名称空间名;(如 using namespace std;)
- 使用某个命名空间中的名字:using + 名称空间名 : : 该名称空间中的某个名字;(如 using std: :cin;)
3.2 标准库类型string
标准库类型string表示可变长的字符序列。string对象不同于字符串字面值。
定义和初始化string对象:
方式 | 解释 |
string s1 | 默认初始化,s1是个空字符串 |
string s2(s1) | s2是s1的副本 |
string s2 = s1 | 等价于s2(s1),s2是s1的副本 |
string s3("value") | s3是字面值“value”的副本,除了字面值最后的那个空字符外(直接初始化) |
string s3 = "value" | 等价于s3("value"),s3是字面值"value"的副本 (拷贝初始化) |
string s4(n, 'c') | 把s4初始化为由连续n个字符c组成的串 |
直接初始化:通过括号给对象赋值。
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 | 返回s1和s2连接后的结果 |
s1=s2 | 用s2的副本代替s1中原来的字符 |
s1==s2 | 如果s1和s2中所含的字符完全一样,则它们相等;string对象的相等性判断对字母的大小写敏感 |
s1!=s2 | 同上 |
<, <=, >, >= | 利用字符在字典中的顺序进行比较,且对字母的大小写敏感(对第一个不相同的位置进行比较) |
- 执行读操作>>:忽略掉开头的空白(包括空格、换行符和制表符),直到遇到下一处空白为止。
- getline:读取一整行,包括空白符。
注:
- s.size()返回的是 string: :size_type类型,是一个无符号类型的值,不要和int混用。(有符号和无符号一起运算会出问题)
- 使用 s1+s2 时,保证至少一侧是string类型。(string s1 = "hello" + "world" // 错误,两侧均为字符串字面值)
处理string对象中的字符:
- C++修改了c的标准库,名称为去掉.h,前面加c。(如c++版本为cctype,c版本为ctype.h)
- 建议:尽量使用c++版本的头文件,即cctype
cctype头文件中定义了一组标准函数:
函数 | 解释 |
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 |
- 基于范围的for语句:for (declaration : expression) {statament} (C++11)
- 使用范围for遍历字符串: for (auto c: str),或者 for (auto &c: str)使用引用可改变字符串中的字符。
- 下标运算符 [ ]: 接收的输入参数是 string::size_ type 类型 的值,这个参数表示要访问的字符的位置,返回值是该位置上字符的引用。
注:注意检查下标的合法性!不管什么时候只要对string对象使用了下标,都要确认在那个位置上确实有值。(如果s为空,则s[0]的结果将是未定义的)
3.3 标准库类型vector
vector是一个容器,也是一个类模板
列表初始化: vector<string> vec{"a", "an", "the"}; (C++11)
- 容器:包含其他对象,但所有的对象必须类型相同。
- 类模板:本身不是类,但可以实例化出一个类(如 vector是一个模板, vector是一个类型)
定义和初始化vector对象:
方法 | 解释 |
vectorv1 | v1是一个空vector,它潜在的元素是T类型的,执行默认初始化 |
vectorv2(v1) | v2中包含有v1所有元素的副本 |
vectorv2 = v1 | 等价于v2(v1),v2中包含v1所有元素的副本 |
vectorv3(n, val) | v3包含了n个重复的元素,每个元素的值都是val |
vectorv4(n) | v4包含了n个重复地执行了值初始化的对象 |
vectorv5{a, b, c...} | v5包含了初始值个数的元素,每个元素被赋予相应的初始值 |
vectorv5={a, b, c...} | 等价于v5{a, b, c...} |
列表初始化: vector<string> vec{"a", "an", "the"}; (C++11)
vector<int> v3(10, 1); // v3有10个元素,每个的值都是I vector<int> v4{10, 1}; // v4有2个元素,值分别是10和1 vector<int> v7{10}; // v7有10个默认初始化的元素 vector<int> v8{10,"hi"}; // v8有10个值为"hi"的元素(确认无法执行列表初始化后,编译器会尝试用默认值初始化vector对象)如果用的是圆括号( ),可以说提供的值是用来 构造 vector对象的。
如果用的是花括号{ },可以表述成我们想 列表初始化 该vector对象。也就是说,初始化过程会尽可能地 把花括号内的值当成是元素初始值的列表 来处理,只有在无法执行列表初始化时才会考虑其他初始化方式。
注:
difference_type:保证足够大,足以存储任何两个迭代器对象间的距离,可正可负。
其他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 | v1和v2相等当且仅当它们的元素数量相同且对应位置的元素值都相同 |
v1 != v2 | 他们的元素数量不同或对应位置元素值不同,则满足不等 |
<,<=,>, >= | 以字典顺序进行比较 |
- 范围for语句内不应该改变其遍历序列的大小(size)。
- vector对象的下标运算符,只能对确知已存在的元素执行下标操作,不能用于添加元素(应该用push_back)。
- 只有当元素的值可比较时,vector对象才能被比较。
3.4 迭代器介绍
- 每种标准库容器都有自己的迭代器。
- 类似于指针类型,迭代器也提供了对对象的间接访问。但迭代器不是指针,是类模板,只是表现得像指针。
使用迭代器:
- vector::iterator iter。
- auto b = v.begin(); 返回指向第一个元素的迭代器。
- auto e = v.end(); 返回指向最后一个元素的下一个 (哨兵) 迭代器。
- 如果容器为空, begin()和 end()返回的是同一个迭代器,都是尾后迭代器。
- 使用解引用符 * 访问迭代器指向的元素。
- 养成使用迭代器和 != 的习惯(泛型编程)
- const_iterator:只能读取容器内元素不能改变。
- ->运算符: 解引用 + 成员访问(如 it->mem等价于 (*it).mem)
- 谨记:但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。
标准容器迭代器的运算符:
运算符 | 解释 |
*iter | 返回迭代器iter所指向的元素的引用 |
iter->mem | 等价于(*iter).mem |
++iter | 令iter指示容器中的下一个元素 |
--iter | 令iter指示容器中的上一个元素 |
iter1 == iter2 | 判断两个迭代器是否相等 |
迭代器运算:
vector和string迭代器支持的运算: 运算符 | 解释 |
iter + n | 迭代器加上一个整数值仍得到一个迭代器,迭代器指示的新位置和原来相比向前移动了若干个元素。结果迭代器或者指示容器内的一个元素,或者指示容器尾元素的下一位置。(可能会after end !) |
iter - n | 迭代器减去一个证书仍得到一个迭代器,迭代器指示的新位置比原来向后移动了若干个元素。结果迭代器或者指向容器内的一个元素,或者指示容器尾元素的下一位置。(可能会before begin !) |
iter1 += n | 迭代器加法的复合赋值语句,将iter1加n的结果赋给iter1 |
iter1 -= n | 迭代器减法的复合赋值语句,将iter2减n的加过赋给iter1 |
iter1 - iter2 | 两个迭代器相减的结果是它们之间的距离,也就是说,将运算符右侧的迭代器向前移动差值个元素后得到左侧的迭代器。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置 |
>、>=、<、<= | 迭代器的关系运算符,如果某迭代器指向的容器位置在另一个迭代器所指位置之前,则说前者小于后者。参与运算的两个迭代器必须指向的是同一个容器中的元素或者尾元素的下一位置 |
3.5 数组
数组相当于vector的低级版,长度固定。
定义和初始化内置数组:
初始化:int arr[buffer_size]; 长度必须是const常量表达式,或者不写,让编译器自己推断。
unsigned n = 42; constexpr unsigned sz = 42; // sz是常量表达式 int arr[10] ; // 含有10个int的数组 int *arr1[sz] ; // 含有42个int*的数组 string arr2[cnt] ; // 错误: n不是常量表达式 string arr3[get_ size()]; // 当get_ size是constexpr时正确;否则错误 char arr4[] = {'C','+','+'}; // 列表初始化,没有空字符 char arr5[] = "C++" // 自动添加表示字符串结束的空字符 int arr6[] =arr; // 错误: 不允许使用一个数组初始化令一个数组 int arr6 = arr ; // 错误: 不能把一个数组直接赋值给另一个数组 int &arr7[] = /*?*/ // 错误: 不存在引用的数组注:
- 数组长度必须是const常量表达式。或者不写,直接让编译器自己推断。
- 使用字符串初始化数组,会在数组的最后 自动添加表示字符串结束的空字符。
- 数组不允许直接 赋值或初始化 另一个数组。
- 不存在引用的数组。(数组的元素应该为对象,引用不是对象)
访问数组元素:
- 数组下标的类型:size_t
- 字符数组的特殊性:结尾处有一个空字符(如 char a[] = "hello"; )
用数组初始化 vector:
int a[] = {1,2,3,4,5}; vector<string> vec(begin(a), end(a));
数组和指针:
在表达式中使用数组名时,数组名会自动转换成指向数组的第一个元素的指针。
int arr[] ={1,2,3}; int *p = &arr[0]; // p指向arr的第一个元素 int *p1 = arr; // 等价于p=&arr[0] int n = *(p1+2); // 等价于i=arr[2] auto a(arr); // a是一个整型指针,指向arr的第一个元素 decltype(arr) b={4,5,6}; // b是一个数组 b = a; // 错误: 不能用整型指针给数组赋值 int *p2 = &arr[2]; int k = p[-2]; // p[-2]是arr[0]表示的那个元素(标准库类型限定使用的下标必须是无符号类型,而内置的下标可以处理负值。)
标准库的begin()和end()函数是C++11新标准引入的函数,可以对数组类型进行操作,返回其首尾指针,对标准库容器操作,返回相应迭代器。
int arr[]={1,2,3,4,5}; int *p = begin(arr); // p为指向arr首元素的指针 int *p2 = end(arr); // p2为指向arr尾元素的下一个位置的指针 auto n = end(arr) - begin(arr) // n的值为5,也就是arr中元素的数量两个指针相减的结果的类型是一种名为 ptrdiff_t 的标准库类型,因为插值可能为负,所以ptrdiff_t是带符号类型。
C风格字符串:
- 从C继承来的字符串,用空字符结束(\0)。
- 建议:对大多数应用来说,使用标准库 string比使用C风格字符串更安全、更高效。
- 获取string中的 cstring : const char *str = s.c_str() ; (c_str函数的返回值是一个C风格的字符串)
C标准库String函数:(定义在头文件中)
函数 | 介绍 |
strlen(p) | 返回p的长度,空字符不计算在内 |
strcmp(p1, p2) | 比较p1和p2的相等性。如果p1==p2,返回0;如果p1>p2,返回一个正值;如果p1 |
strcat(p1, p2) | 将p2附加到p1之后,返回p1 |
strcpy(p1, p2) | 将p2拷贝给p1,返回p1 |
char arr[] = {'C','+','+'}; int n = strlen(arr); // 严重错误:arr没有以空字符结束 const char arr1[] = "A string example"; const char arr2[] = "A different string"; if(arr1 < arr2) // 错误: 实际比较的将是指针而非字符串本身
注:传入此类函数的指针必须指向以空字符作为结束的数组。
3.6 多维数组
多维数组的初始化:
int arr[2][3]={ // 两个元素,每个元素都是大小为3的数组 {1,2,3}, {4,5,6} }; int arr1[2][3]={1,2,3,4,5,6}; // 即使没有标识每一行的花括号,与上面的初始化语句也是等价的
使用范围for语句处理多维数组:
for(auto &row : arr){ // 控制变量row为引用类型(加了&后,row类型为int(&)[3]; 不加&后,row类型为int*) for(auto col :row){ cout << col << endl; } }注:使用范围for语句处理多维数组时,除了最内层的循环外,其他外层循环的控制变量都必须引用类型。(避免数组被自动转化为指针)
指针和多维数组:
- 指针数组:"指针的数组”,是个数组,里面的元素是指针。(如 const char *arr[3]={"I" , "Like" , "C++"}; arr是一个指针数组)
- 数组指针:"数组的指针”,是个指针,指向一个数组。(如 char (*arr)[3] = {'C', '+' , '+'}; arr是一个数组指针)