C++ Primer 学习笔记(第三章 字符串、向量和数组)

目录

3.1 命名空间的using声明

3.2 标准库类型 string

3.3 标准库类型 vector

3.4 迭代器介绍

3.5 数组

3.6 多维数组


第 3 章  字符串、向量和数组

3.1 命名空间的using声明

using 声明形式:using namespace :: name

注意:每个名字都需要独立的 using 声明,头文件中不应包含using声明。

using std::cin;
using std::cout;        // 只有声明了的名字可以用
using std::endl;

using namespace std;    // std里的所有名字都可以用

3.2 标准库类型 string

标准库类型 string 表示可变长的字符序列,使用时必须包含 string 头文件,string 定义在命名空间 std 中。

#include <string>
using std::string

Ⅰ) 定义和初始化string对象 

string s1;                // 默认初始化为空字符串

string s2 = s1;           // s2是S1的副本                            拷贝初始化
string s2(s1);            // s2是S1的副本                            直接初始化

string s3 = "hiya";       // s3是字符串字面值的副本                   拷贝初始化
string s3("hiya");        // s3是字符串字面值的副本                   直接初始化

string s4(10,'c');        // s4是“cccccccccc”                        直接初始化

 Ⅱ) string 对象上的操作

表 3.1:string的操作

os<<s

将s写到输出流中

Is>>s

从is中读取s,每个字符串以空格分隔

getline(is,s)

从is中读取一行赋给s,包含空格,不包含换行符

s.empty()

s为空返回true,否则返回false

s.size()

返回s中字符的个数

s[n]

返回s中第n个字符的引用,从0开始

s1 + s2

返回s1和s2连接后的结果

s1 = s2

用s2的副本代替s1中原来的内容

s1 == s2

完全一样则相等,大小写敏感

s1 != s2

-

<, <=, >, >=

字典顺序比较,大小写敏感

 1)读写string对象

#include <string>
using std::string

int main(){
    string s;                    // 空字符串
    cin >> s;                    // 自动忽略开头的空格符、换行符、制表符等空白,将string对象读入s,直到遇到下一个空白停止
    cout << s << endl;           // 输出s
    return 0;
}

 2)两个string对象相加,字面值和string对象相加

注意:切记,字符串字面值和string对象是两个不同的类型,并且从左往右看,不能将两个字面值直接相加,如:

// 两个string对象相加
string s1 = "Hello, ", s2 = "world\n";
string s3 = s1 + s2;
s1 += s2;

// 字面值和string对象相加
string s1 = "Hello", s2 = "world";
string s3 = s1 + ", " + s2 + ‘\n’;
string s4 = s1 + ", ";
string s5 = "hello" + ", ";                // 错误!两个运算对象都不是string
string s6 = s1 + ", " + "world";
string s7 = "hello" + ", " + s2;           // 错误!字面值不能直接相加

 3)处理string对象中的字符

cctype头文件:C++标准库中除了定义C++语言特有的功能外,还兼容了C语言的标准库。C语言的头文件是name.h,c++将这些文件命名为cname,去掉了.h后缀,在文件名前面添加了字母c

表 3.2:字符检查
isalnum(c)检查字符c是否是字母或数字
isalpha(c)检查字符c是否是字母
isblank(c)检查字符c是否为空白字符
iscntrl(c)检查c是否是控制字符
isdigit(c)检查字符c是否为数字
isgraph(c)检查字符c是否是具有图形表示的字符
islower(c)检查c是否是小写字母
isprint(c)检查c是否是可打印的字符
ispunct(c)检查c是否是标点符号
isspace(c)检查c是否是空格字符

Ⅲ) 基于范围的for语句

// expression 部分是一个对象,用于表示一个序列。
// declaration 部分负责定义一个变量,该变量用于访问序列中的基本元素。

for ( declaration : expression)
    statement
string str("some string");
for (auto c : str)
    cout << c << endl;

 Ⅳ) 下标运算符:而要想访问string对象中的单个字符,可以使用下标运算符([ ])或者迭代器。


3.3 标准库类型 vector

标准库类型 vector 表示对象的集合,其中所有对象的类型都相同,因为vector容纳着其他对象,所以也常被称为容器。和string类似,要想使用vector,必须包含适当的头文件,并且vector 是模板而非类型,由它生成的模板必须包含vector中元素的类型,因为引用不是对象,所以不存在包含引用的vector。

#include <vector>
using std::vector;
vector<int> ivec;
vector<string> svec;
vector<vector<string>> file;    // 老式声明:在里面的vector右边加空格vector<vector<string> >

 初始化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<string> v1{"Hello","World"};    // 列表初始化
vector<string> v1("Hello","World");    // 错误!不能使用()

vector<int> ivec(10,-1);               // 10个int类型的元素,每个都被初始化为-1
vector<string> svec(10,"Hello");       // 10个string类型的元素,每个都被初始化为“Hello”

向vector对象中添加元素:利用vector的成员函数push_back向vector中添加元素,push_back负责。

注意:如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。

表 3.3:其他vector操作
v.vector() 如果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
<, <=, >, >=以字典顺序进行比较

注意:1)要使用size_type,需首先指定它是由哪种类型定义的。vector对象的类型总是包含着元素的类型:

vector<int>::size_type        // 正确
vector::size_type             // 错误!

 2)vector对象以及string对象的下标运算符可用于访问已存在的元素,而不能用于添加元素,只能对确知已存在的元素进行下标操作(确保下标合法的一种有效手段就是尽可能使用范围for语句):

vector<int> ivec;            // 空vector对象
cout << ivec[0];             // 错误!ivec不包含任何元素

vector<int> ivec2(10);       // 含有10个元素的vector对象
cout << ivec2[10];           // 错误!ivec2元素的合法索引是0~9

3.4 迭代器介绍

        迭代器相当于指向容器元素的指针,它在容器内可以向前移动,也可以做向前或向后双向移动。string和vector都支持迭代器,和指针不同的是,获取迭代器不是使用取地址符。

        比如,这些类型都拥有名为begin和end的成员,其中begin成员负责返回指向第一个元素的迭代器,end成员负责返回指向容器“尾元素的下一位置”的迭代器(尾后迭代器)。如果容器为空,则begin和end返回的是同一个迭代器。因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作

auto b = v.begin(), e = v.end();    // b表示v的第一个元素,e表示v尾元素的下一位置。

vector<int> v;
const vector<int> cv;
auto it1 = v.begin();               // it1的类型是vector<int>::iterator 
auto it2 = cv.begin();              // it2的类型是vector<int>::const_iterator

auto it3 = v.cbegin();              // it3的类型是vector<int>::const_iterator,与vector对象本身是否是常量无关

        begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。为了便于专门得到const_iterator类型的返回值,C++11 新标准引用了两个新函数,分别是cbegin和cend,常用于只需读操作无需写操作时。

 Ⅰ)迭代器运算符:

表3.4:标准容器迭代器的运算符
*iter返回迭代器iter所指元素的引用
iter->men解引用iter并获取该元素的名为men的成员,等价于(*iter).men
++iter令iter指示容器中的下一个元素
--iter令iter指示容器中的上一个元素
iter1 == iter2判断两个迭代器是否相等,如果两个迭代器指示的是同一个元素或它们是同一个容器的尾后迭代器,则相等;反之,不相等
iter1 != iter2

Ⅱ) 泛型编程:迭代器有点类似下标运算的意思,C++程序员习惯于使用 !=,其原因和他们更愿意使用迭代器而非下标一样:因为所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符,这种编程风格在标准库提供的所有容器上都有效。

迭代器的含义有3种:1)迭代器本身;2)容器定义的迭代器类型;3)某个迭代器对象。

Ⅲ) 解引用和成员访问操作

(*it).empty()        // 解引用it,然后调用结果对象的empty对象
*it.empty()          // 错误!试图访问it的名为empty的成员,但it是个迭代器,没有empty成员
it->empty()          // 和(*it).empty()效果一样,先解引用,再调用结果对象的对象。

注意:但凡使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。 

Ⅳ) 迭代器运算: 

表 3.5:vector和string迭代器支持的运算
iter + n向前移动n个位置,还是一个迭代器,指示容器内或尾后位置
iter - n向后移动n个位置,还是一个迭代器,指示容器内或尾后位置
iter1 += n迭代器加法的赋值复合语句
iter1 -= n迭代器减法的赋值复合语句
iter1 - iter2两个迭代器之间的距离,必须指向同一个容器中的元素或尾后位置,结果是带符号整数型difference_type
>、>=、<、<=反映迭代器指示位置的前后关系

3.5 数组

数组与vector类似,只是数组的大小确定不变,不能随意向数组中添加元素,当不清楚元素的个数时,使用vector。

Ⅰ) 定义和初始化内置数组:

int *ptrs[10];                  // []优先级比*高,先看[],知声明的是数组,再看*,数组中装的是指针,即指针数组。
int &refs[10] = "hello world!"; // 同上,但是不存在引用数组,所以这个声明错误
int (*Parray)[10] = &arr;       // ()具有最高的优先级,因此先看(),里面是*,声明的是一个指针,再看[],说明该指针指向的是一个数组,即数组指针。
int (&arrRef)[10] = arr;        // 同上,()具有最高的优先级,因此先看(),里面是&,声明的是一个引用,再看[],说明该引用指向的是一个数组,即数组引用。
int *(&arry)[10] = ptrs;        // 先看(),再看[],再看*。由()知总体是一个引用,再看[]知是一个数组,即含有10个指针元素的数组的引用。

Ⅱ) 访问数组元素:范围for语句或下标运算符。使用下标运算符的时候要特别注意是否溢出,防止数组下标越界。

Ⅲ) 指针和数组:使用数组的时候,编译器一般会把它转换为指针。

int ia[] = {0,1,2,3,4,5,6,7,8,9};
auto ia2(1a);                        // ia2是一个整型指针,指向ia的第一个元素,等价于:
auto ia2(&ia[0]);
ia2 = 42;                            // ia2是一个指针,不能用int型进行赋值

注意,使用decltype关键字时,数组名表示的是整个数组,而不是数组首地址,因此,不能进行上面的赋值。

decltype(ia) ia3 = {0,1,2,3,4,5,6,7,8,9};
ia3 = p;                                    // 错误!不能用整型指针给数组赋值
ia3[4] = i;                                 // 正确,把i的值赋给ia3的一个元素

 Ⅳ)标准库函数begin和end:这两个函数和容器的两个同名成员功能类似,不过数组不是类类型,因此这两个函数不是成员函数,使用时需要将数组作为它们的参数。

#include <iterator>
int ia[] = {0,1,2,3,4,5,6,7,8,9};
int *beg = begin(ia);               // 指向ia首元素的指针
int *last = end(ia);                // 指向尾元素下一个位置的指针

Ⅴ) C风格字符串:建议在C++程序中最好不要使用它们,因为既不方便又容易引发漏洞,而是使用标准库string。

注意:现代的C++程序应当尽量使用vector和迭代器,避免使用内置数组和指针;应该尽量使用标准库string,避免使用C风格的基于数组的字符串。


3.6 多维数组

严格说来,C++ 中并没有多维数组,多维数组其实就是数组的数组。

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

 注意:要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。

size_t cnt = 0;
for (auto &row : ia)
    for (auto &col : row) {
        col = cnt;
        ++cnt;
}
/*
* 注意:在上面的例子中,因为要改变数组元素的值,
* 所以我们选择引用类型作为循环控制变量,还有一个原因:
* 为了避免数组被自动转化成指针。
* 如下面的两个例子:
*/
for (const auto &row : ia)
    for (auto col : row) {                  // 正确
        cout << col << endl;
}
// 程序无法编译通过,初始化row时会自动将数组形式的元素
// 转换成指向该数组内首元素的指针,row的类型就是int *。
for (auto row : ia)
    for (auto col : row) {
        cout << col << endl;                
}

本章参考教程:    C++ String类型

                             C++基本序列式容器

                             C++ 字符串


版权声明:本文为TAN_YUAN原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。