C++Primer第五版学习笔记 第三章
第三章 字符串,向量和数组
命名空间
-
域操作符::的含义是编译器应从操作符左侧名字所示的作用域中寻找右侧那个名字,因此,std::cin 的意思是要使用命名空间std中的名字cin。
-
可以使用using声明命名空间,using namespace::name,如using std::cin; 声明了std标准库的cin。
-
可以使用using声明整个命名空间,如using namespace std;
-
位于头文件的代码,一般来说不应该使用using声明。这是因为头文件的内容会拷贝到所有引用它的文件去,如果头文件有using声明,那么使用了该头文件的文件都会有这个声明,可能会产生冲突。
string标准库
- 声明string库
#include<string>
using std::string;
- 初始化string对象
string s1; // 默认初始化为空字符串
string s2 = s1; // s1的副本
string s3 = 'string'; // 字符串的副本
string s4 = string(10, 's'); // 内容是'ssssssssss'
// 前面是拷贝初始化, 后面是直接初始化。
// 直接初始化的可读性更高!
string s5(10, 's'); // 同上
string s6('string');
- string操作
操作 | 含义 |
---|---|
cin>>s | 输入字符串,以空格或回车键结尾 |
cout<<s | 输出字符串 |
getline(cin, s) | 从is中读取一行赋值给s |
s.empty() | 字符串为空返回True,否则false |
s.size() | 返回字符串长度,string::size_type类型 |
s[n] | 返回下标为n的字符 |
s1+s2 | 返回s1和s2连接后的结果 |
s1=s2 | 赋值 |
s1==s2 s1!=s2 | 比较,大小写敏感 |
<, <=, >, >= | 利用字典中的顺序对字符串比较,大小写敏感 |
- 注意的是,由于size()函数返回的不是int类型,尽可能使用auto来自动判别size的返回值,避免int和unsigned混用带来的问题。
auto len = str.size()
- 字符串相加,需要注意的是字符串字面值的类型其实是const char[],不是string,这是为了与C兼容。
string str1("hello ");
string str2("world.");
cout<<str1 + str2<<endl;
cout<<str1 + "world."<<endl;
cout<<"Hello" + "world"<<endl; //错误,字面值不能直接相加。会报错invalid operands of types 'const char [6]' and 'const char [6]' to binary 'operator+'
cout<<str1 + "," + "world"<<endl;//合法,意义和下一句相同
cout<< (str1 + ",") + "world"<<endl;
- 使用基于范围的for语句对字符串中的字符进行操作,该语句对于便利给定序列中的每个元素并对序列中的每个值执行某种操作。相较之下,范围for更加简洁,但是,范围for语句内不应该改变其遍历序列的大小!!!
#include<cctype>
// 形式 for(declaration : expression) statement
string str("string");
for (auto c : str) cout<<c<<endl; //这里的auto返回类型char,故可以换成char
for (auto &c : str) c = toupper(c); //引用才能改变str里的值。
// 下标类型为string::size_type, 一般认为是unsigned。
cstring库
- 使用string标准库比cstring更安全和快捷。
#include<cstring>
#include<string.h>
// 传入cstring库函数中的字符串必须是以空字符作为结束的数组
char ca[] = {'C', '+', '+'}; //不以空字符结束,需要注意的是,此类初始化不能使用指针遍历,因为其没有空字符作为结尾,详细看习题3-37
cout << strlen(ca); //错误!必须是空字符结束的字符串
char ca[] = "C++";
cout << strlen(ca); //3
函数 | 作用 |
---|---|
strlen( p) | 返回p的长度,空字符不计算在内 |
strcmp(p1, p2) | 比较p1和p2的相等性,p1==p2, 返回0,p1>p2, 返回正数,p1< p2, 返回负数 |
strcat(p1, p2) | 将p2附加给p1,返回p1 |
strcpy(p1, p2) | 将p2拷贝给p1, 返回p1 |
cstring库和string库的接口
- 允许使用以空字符串结束的字符串数组来初始化string对象或为string对象赋值
- 在string对象的加法运算中允许使用以空字符串结束的字符数组作为其中一个运算对象,不能全是,在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。
- string提供了一个c_str函数用以返回一个C风格的字符串,返回一个以空字符结束的指针。
#include<string>
#include<cstring>
char ca[] = "str";
string s("str");
char *cb = s; //错误
char *cb = s.c_str();
cctype标准库
- 使用C++语法声明C语言的标准库,一般是前面加上c,后面去掉.h。可以帮助区分那些是C++特有的,那些是继承C语言的。
#include<ctype.h>
#include<cctype> //建议使用
- cctype标准库函数
函数 | 作用:判断类型 |
---|---|
isalnum( c) | 字母或数字 |
isalpha( c) | 字母 |
iscntrl( c) | 控制字符 |
isdigit( c) | 数字 |
isgraph( c) | 不是空格但是可以打印 |
islower( c) | 小写字母 |
isprint( c) | 可打印字符,即空格或者具有可视形式 |
ispunct( c) | 标点符号 |
isspace( c) | 空白,即空格,横、纵向制表符,回车符,换行符,进纸符 |
isupper( c) | 大写字母 |
isxdigit( c) | 十六进制 |
tolower( c) | 小写字母不变,大写字母转换成小写字母 |
toupper( c) | 大写字母不变,小写字母转换成大写字母 |
vector标准库
#include<vector>
using std::vector;
// 其实可以把vector看成一个类模板,所以当初始化类模板时,需要进行实例化,即指出编译器应把类或函数实例化成某种类型。
vector<int> ivec;
vector<double> dvec;
vector<vector<int>> ivec_2d; //C++11之前的版本需要在最右边的两个尖括号叫上空格,即vector<vector<int> >
- 初始化vector
vector<T> v1; //默认初始化
vector<T> v2(v1); //v2是包含v1所有元素的副本
vector<T> 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..};
vector<T> v5
v5 = {a, b, c..};
//列表初始化只能使用花括号,圆括号只能使用拷贝法或者同时初始化n个val值的元素。
//注意的是,当使用花括号时,编译器会默认使用列表初始化方法,只有当括号内的值不适用于列表初始化才会使用其他方法
vector<string> v6 = {10};//10显然无法初始化string对象,所以是一个容量为10的容器
vector<string> v6 = {10, 'hi'};
//使用数组初始化数组
int arr[] = {1,2,3,4,5,6};
vector<int> ivec(begin(arr), end(arr)); //声明首元素指针和尾后指针
vector<int> ivec(arr, arr+2); //前三个元素。
- vector函数
vector<int> v1;
//如果循环体内部有包含向vector对象添加元素的语句,则不能使用范围for语句
for(auto &v : v1) // auto == vector<int>::type_size
v ++;
v1.push_back(1); //错误
函数 | 作用 |
---|---|
v1.push_back(i) | 把值i放到v1的尾部 |
v1.empty() | 判断是否为空 |
v1.size() | 返回元素个数 |
v1 == v2 or v1 != v2 | 比较所有元素 |
>, <, >=, <= | 按字典序大小进行比较 |
v1[n] | 返回下标为n的元素 |
迭代器
-
标准库的容器都定义了 != 和 == 运算符, 而并非所有容器都定义了<,>运算符。所以需要养成使用迭代器而不是数组下标的情况。
-
需要注意的是,使用了迭代器的循环体,都不要想迭代器所属的容器添加元素。
vector<T> v;
auto begin = v.begin(); //指向v的第一个元素
auto end = v.end() //指向v的最后一个元素的下一个位置
//若容器为空,则begin == end
- 迭代器运算
运算符 | 作用 |
---|---|
*iter | 返回迭代器iter指向的元素的引用 |
iter->mem,(*iter).mem | 解引用iter并获取该元素的名为mem的成员 |
++iter, --iter | 指向下/上一个元素,end()不指向某个元素,不能进行该操作 |
==, != | 判断迭代器指向是否相等 |
iter+n, iter-n, iter += n, iter -=n | 迭代器加一个整数值,结果迭代器指向前进/后退的第n个元素,或者返回容器end() |
iter1 - iter2 | 两个迭代器之间的距离,由于这个距离可正可负,所以返回一个带符号的整型数 difference_type ,注意的是迭代器之间没有定义加法运算 |
<,>,>=,<= | 如果某个迭代器指向的容器位置在另一个迭代器所指向位置之前,则说明前者小于后者。 |
- 常量迭代器const_iterator
vector<int>::iteritor it;
string::iterator it2;
vector<int>::const_iterator it3;
string::const_iterator it4;
//const_iterator迭代器和常量指针类似,可以指向常量或非常量对象,但是如果对象是常量,只能使用const_iterator.
//begin和end的返回迭代器是否是const由对象是否常量决定。
//如果希望返回的是const_iterator,可以使用cbeing()和cend()
vector<int> v1;
vector<int>::const_iterator it3 = v1.cbegin(); //const_iterator
- 通过解引用访问容器成员,需要加上括号!!
(*iter).empty();
*iter.empty(); //错误!!!iter是个迭代器,不是容器
iter->empty(); //正确
数组
- 数组的初始化不允许使用auto自动判断类型。
//初始化
const int len = 3;
int arr1[len] = {0, 1, 2}; //含有三个元素的数组的初始化
int arr2[] = {0, 1, 2}; //初始化数组,长度为3
int arr3[4] = {0, 1, 2}; //含有五个元素的数组,初始化前三个,后两个元素默认初始化。
char arr4[] = {'c', 'p', 'p'}; //初始化,没有空字符
char arr5[] = {'c', 'p', 'p', '\n'}; //初始化,含有空字符
char arr6[] = "cpp"; //初始化,自动添加表示字符串结束的空字符,长度为4
char arr7[4] = "test"; //错误,字符串实际长度为5,空字符也有一位
int arr[2][2] = {1, 2, 3, 4};
int arr[2][2] = {{1, 2}, {3, 4}}; //含义相同
int arr[2][2] = {{1}, {2}} //初始化每行的首元素
int arr[2][2] = {1, 2}; //初始化首行元素
//初始化不能使用数组去赋值数组
- 数组的声明
int *ptrs[10]; //包含10个整形指针的指针数组
int (*Parray)[10] = &arr; //Parray指向一个含有十个整数的数组
int(&arrRef)[10] = arr; //arrRef引用一个含有十个整数的数组
int *(&array)[10] = ptrs; //arry是数组的引用,该数组含有十个指针
- 数组可以使用范围for语句进行遍历。
int arr[10] = {0};
for(auto x : arr) cout<<x<<" ";
int arr[10][10] = {/* */};
for(auto &row : arr)
for(auto &col : row)
col++;
// 注意,使用范围for语句处理多为数组,除了最内层的循环之外,外层循环的控制变量都应该是引用类型。
for(auto row : arr)
for(auto col : row)
/* */
// 如上面语句中的row,如果不是引用类型,程序无法通过编译,因为编译器初始化row时会自动将这些数组形式的元素转换成指向该元素首元素的指针,这样row的类型就是int *,显然内层循环就不合法了。
- 数组和指针
//两者等价
int *p = arr;
int *p = &arr[0];
//数组头元素指针
auto pa(arr); // 当使用数组名称时,真正用的是指向数组首元素的指针
auto pa(&arr[0]);
auto *pa = begin(arr); // begin()和end()不是数组的成员函数!
//尾后指针,指向不存在的数组尾部一个元素,end的作用是标定边界,不能进行解引用和递增操作。
int *end = &arr[10];
int *end = arr + sizeof(arr);
int *end = end(arr);
for(int *b = arr; b != &arr[10]; ++b) /* 遍历 */
int arr[10][10];
for(auto b = begin(arr); b != end(arr); b++)
for(auto bb = begin(*b); bb! = *b + 10; bb++) //判定条件为*b + 4而不是 b + 4
/* 遍历二维数组 */
- 指针相减和容器一样,返回两者间的距离 prtdiff_t类型的有符号整形,指向不同对象元素的指针之间无法进行比较。