【C++】《C++ Primer 5th》笔记-Chapter3-字符串、向量和数组

笔记:
一、命名空间的using声明
1、string表示可变长的字符序列,vector存放的是某种给定类型对象的可变长序列。

2、using声明具有如下的形式:
using namespace::name;

3、位于头文件的代码一般来说不应该试用using声明。这是因为头文件的内容会拷贝到所有引用它的文件中去。对于某些程序来说,由于不经意间包含了一些名字,反而可能产生始料未及的名字冲突。

二、标准库类型string
1、下面是初始化string对象最常用的一些方式:
string s1;                // 默认初始化,s1是一个空字符串
string s2(s1);            // s2是s1的副本
string s2 = s1;            // 等价于s2(s1),s2是s1的副本
string s3 = "hiya";        // s3是该字符串字面值的副本,除了字面值最后的那个空字符外
string s4(5, 'c');        // s4的内容是ccccc

2、如果使用等号(=)初始化一个变量,实际上执行的是拷贝初始化,编译器把等号右侧的初始值拷贝到新创建的对象中去。与之相反,如果不使用等号,则执行的是直接初始化。

3、当初始值只有一个时,使用直接初始化或拷贝初始化都行。如果像上面s4那样初始化要用到的值有多个,一般来说只能使用直接初始化的方式。

4、下面例子中,在执行读取操作时,string对象会自动忽略掉开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇见下一处空白为止。
string s;
cin >> s; // 将string对象读入s,遇到空白停止

5、while(cin >> s) //反复读取,直至到达文件末尾

6、getline函数的参数是一个输入流和一个string字符串,函数从给定的输入流读入内容,直到遇到换行符为止(注意换行符也被读进来了),然后把所读的内容存入到那个string对象中去(注意不存换行符,被丢弃了)。getline只有一遇到换行符就结束读取操作并返回结果,哪怕输入的一开始就是换行符也是如此。如果输入真的一开始就是换行符,那么所得的结果是个空string。
while(getline(cin, line))    // 每次读入一整行,直到到达文件末尾
    cout << line << endl;    // 使用endl结束当前行并刷新显示缓冲区

7、size函数返回string对象的长度(即string对象中字符的个数)。
其实size函数返回的是一个string::size_type类型的值,它是一个无符号类型的值,而且能够存放下任何string对象的大小。在C++11中,允许编译器通过auto或者decltype来推断变量的类型: auto len = line.size();
注意:由于size函数返回的是一个无符号整型数,所以如果在表达式中混用了带符号数和无符号数将可能产生意想不到的结果。例如,假设n是一个具有负值的int,则表达式s.size() < n的判断结果几乎肯定是true。这是因为负值n会自动地转换成一个比较大的无符号值。

8、string("abcde")对象小于string("aj")

9、当把string对象和字符字面值及字符串字面值混在一条语句中使用时,必须确保每个加法运算符(+)的两侧的运算对象至少有一个是string。
string s5 = "hello" + ", ";            // 错误: 两个运算对象都不是string
string s6 = s1 + ", " + "world";    // 正确:每个加法运算符都有一个运算对象是string
string s7 = "hello" + ", " + s2;    // 错误:不能把字面值直接相加。这相等于string s7 = ("hello" + ", ") + s2;是错误的
切记:C++语言中的字符串字面值并不是标准库类型string的对象。

10、判断某个字符的特性可以包含头文件cctype使用相关的库函数。

11、C语言的头文件形如name.h,C++语言则将这些文件命名为cname。也就是去掉了.h后缀,而在文件名name之前添加了字母c,这里的c表示这是一个属于C语言标准库的头文件。特别的,在名为cname的头文件中定义的名字从属于名称空间std,而定义在名为.h的头文件中的则不然。一般来说,C++程序应该使用名为cname的头文件而不使用name.h的形式,标准库中的名字总能在命名空间std中找到。

12、string对象的下标必须大于等于0而小于s.size()。使用超出此范围的下表将引发不可预知的结果,以此推断,使用下标访问空string也会引发不可预知的结果。

13、下标的值称作"下标"或"索引",任何表达式只要它的值是一个整型值就能作为索引。不过,如果某个索引是带符号类型的值将自动转换成由string::size_type表达的无符号类型。

14、不管什么时候,只要对string对象使用了下标,都要确认在那个位置上确实有值。

15、if(a>1 && b>5);    // C++语言规定只有当左侧运算对象为真时才会检查右侧运算对象的情况。

16、使用下标时必须确保其在合理范围之内,也就是说,下标必须大于等于0,而小于字符串的size()的值。一种简答易行的方法是,总是设下标的类型为string::size_type,因为此类型是无符号数,可以确保下标不会小于0。此时,代码只需保证下标小于size()的值就可以了。

三、标准库类型vector
1、标准库类型vector表示对象的集合,其中所有对象的类型都相同。集合中的每个对象都有一个与之对应的索引,索引用于访问对象。因为vector"容纳着"其他对象,所以它也常被称作容器。

2、C++语言既有类模板,也有函数模板,其中vector是一个类模板。
模板本身不是类或函数,相反可以将模板看作为编译器生成类或函数编写的一份说明。编译器根据模板创建类或函数的过程称为实例化,当使用模板时,需要指出编译器应把类或函数实例化成何种类型。

3、vector是模板而非类型,由vector生成的类型必须包含vector中元素的类型,例如vector<int>。

4、vector能容纳绝大多数类型的对象作为其元素,但是因为引用不是对象,所以不存在包含引用的vector。

5、特别指出,在早期版本的C++标准中,如果vector的元素还是vector(或者其他模板类型)
,则其定义的形式与现在C++11新标准略有不同。过去,必须在外层vector对象的右尖括号和其元素类型之间添加一个空格,如应该写成vector<vector<int> >而非vector<vector<int>>。

6、可以默认初始化vector对象,从而创建一个指定类型的空vector。
如果提供的是初始元素值的列表,则只能把初始值都放在花括号里进行列表初始化,而不能放在圆括号里。

7、通常情况下,可以只提供vector对象容纳的元素数量而略去初始值。此时库会创建一个值初始化的元素初值,并把它赋给容器中的所有元素。这个初值由vector对象中元素的类型决定。
vector<string> svec(10);        // 10个元素,每个都是空string对象
对这种初始化的方式有两个特殊限制:其一,有些类要求必须明确地提供初始值,如果vector对象中元素的类型不支持默认初始化,我们就必须提供初始的元素值。对这种类型的对象来说,只提供元素的数量而不设定初始值无法完成初始化工作。其二,如果只提供了元素的数量而没有设定初始值,只能使用直接初始化。

8、花括号与圆括号的区别:
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对象。

另一方面,如果初始化时使用了花括号的形式但是提供的值又不能用来列表初始化,就要考虑用这样的值来构造vector对象了。(确定无法执行列表初始化后,编译器会尝试用默认值初始化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"的元素

9、对vector对象来说,直接初始化的方式适用于三种情况:初始值已知且数量较少、初始值是另一个vector对象的副本、所有元素的初始值都一样。

10、C++标准要求vector应该能在运行时高效快速地添加元素。
注意:如果循环体内部包含有向vector对象添加元素的语句,则不能使用范围for循环。即范围for语句体内不应改变其所遍历序列的大小。(详看C++ Primer 5e P168)

11、vector对象(以及string对象)的下标运算符可用于访问已存在的元素,而不能用于添加元素。
只能对确知已存在的元素执行下标操作。试图用下标的形式去访问一个不存在的元素将引发错误,所谓的缓冲区溢出指的就是这类错误。确保下标合法的一种有效手段就是尽可能使用范围for语句。

四、迭代器介绍
1、所有标准库容器都可以使用迭代器,但是其中只有少数几种才同时支持下标运算符。
严格来说,string对象不属于容器类型。string支持迭代器。vector支持下标运算符。

2、有效的迭代器或者指向某个元素,或者指向容器中尾元素的下一位置;其他所有情况都属于无效。

3、begin成员负责返回第一个元素(或第一个字符)的迭代器。end成员则负责返回指向容器(或string对象)"尾元素的下一个位置"的迭代器。end成员返回的迭代器常被称作尾后迭代器或者简称为尾迭代器。
特殊情况下,如果容器为空,则begin和end返回的是同一个迭代器,都是尾后迭代器。

4、如果两个迭代器指向的元素相同或者都是同一个容器的尾后迭代器,则它们相等,否则不相等。

5、试图解引用一个非法迭代器或者尾后迭代器都是未被定义的行为。
*iter返回迭代器iter所指元素的引用。

6、迭代器使用递增(++)运算符来从一个元素移动到下一个元素。
因为end返回的迭代器并不实际指示某个元素,所以不能对其进行递增或解引用的操作。

7、只有string和vector等一些标准库类型有下标运算符,而并非全都如此。所有标准库容器的迭代器都定义了==和!=,但是它们中的大多数都没有定义<运算符。因此我们要养成使用迭代器和!=的习惯。

8、那些拥有迭代器的标准库类型使用iterator和const_iterator来表示迭代器的类型。
vector<int>::iterator it;              // it能读写vector<int>的元素
vector<int>::const_iterator it2;    // it2只能读元素,不能写元素
const_iterator和常量指针差不多,能读取但不能修它所指的元素值。相反,iterator的对象可读可写。
如果vector对象或string对象是一个常量,只能使用const_iterator;如果vector对象或string对象不是常量,那么既可以使用iterator也可以使用const_iterator。

9、begin和end返回的具体类型由对象是否是常量决定,如果对象是常量,begin和end返回const_iterator;如果对象不是常量,返回iterator。
注意:为了便于专门得到const_iterator类型的返回值,C++11新标准引用了两个新函数,分别是cbegin和cend,这两个函数不管vector对象(或string对象)本身是否是常量,返回值都是const_iterator。

10、虽然vector对象可以动态地增长,但是也会有一些副作用。已知的一个限制是不能在范围for循环中向vector对象添加元素。另外一个限制是任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效。
谨记,但凡是使用了迭代器的循环体,都不要向迭代器所属的容器添加元素。

11、可以令迭代器和一个整数值相加(或相减),其返回值是向前(或向后)移动了若干个位置的迭代器。执行这样的操作时,结果迭代器或者指示原vector对象(或string对象)内的一个元素,或者指示原vector对象(或string对象)尾元素的下一位置。

12、只要两个迭代器指向的是同一个容器中的元素或者尾元素的下一位置,就能将其相减,所得结果是两个迭代器的距离。所谓距离指的是右侧的迭代器向前移动多少位置就能追上左侧的迭代器,其类型是名为difference_type的带符号整数(距离可正可负)。

五、数组
1、与vector不同的地方是,数组的大小确定不变,不能随意向数组中增加元素。因为数组的大小固定,因此对某些特殊的应用来说程序的运行时性能较好,但是相应地损失了一些灵活性。
如果不清楚元素的确切个数,请使用vector。

2、维度说明了数组中元素的个数,因此必须大于0。编译的时候维度应该是已知的,也就是说,维度必须是一个常量表达式。
默认情况下,数组的元素被默认初始化。
和内置类型的变量一样,,如果在函数体内定义了某种内置类型的数组,那么默认初始化会令数组含有未定义的值。

3、定义数组的时候必须指定数组的类型,不允许用auto关键字由初始值的列表推断类型。另外和vector一样数组的元素应为对象,因此不存在引用的数组。

4、const char a[6] = "Daniel";    // 错误:没有空间可存放空字符

5、不能将数组的内容拷贝给其他数组作为其初始值,也不能用数组为其他数组赋值。
注:也有一些编译器支持数组的赋值,这就是所谓的编译器扩展。但一般来说,最好避免使用非标准特性。

6、要想理解数组声明的含义,最好的办法是从数组的名字开始按照由内向外的顺序阅读。
int *ptrs[10];                // ptrs是含有10个整型指针的数组
int &refs[10] = /* ? */;    // 错误:不存在引用的数组
int (*Parray)[10] = &arr;    // Parray指向一个含有10个整数的数组
int (&arrRef)[10] = arr;    // arrRef引用一个含有10个整数的数组
int *(&arry)[10] = ptrs;    // arry是数组的引用,该数组含有10个指针

7、在使用数组下标时,通常将其定义为size_t类型(一种与机器相关的无符号类型,在cstddef头文件中定义了)。

8、因为维度是数组类型的一部分,所以系统知道数组scores中有多少个元素,使用范围for语句可以减轻人为控制遍历过程的负担。
for(auto i : scores)
    cout << i << " ";
    
9、使用数组的时候编译器一般会把它转换成指针。
对数组的元素使用取地址符就能得到指向该元素的指针。
在很多用到数组名字的地方,编译器都会自动地将其替换为一个指向数组首元素的指针。

10、当使用数组作为一个auto变量的初始值时,推断得到的类型是指针而非数组。但当使用decltype关键字时情况不一样。
int ia[] = {0,1,2};            // ia是一个含有3个整数的数组
auto ia2(ia);                // ia2是一个整型指针,指向ia的第一个元素
decltype(ia) ia3 = {0,1,2};    // ia3是一个含有3个整数的数组

11、vector和string迭代器支持的运算,数组的指针全都支持。

12、C++11新标准引入了两个名为begin和end的函数,定义在iterator头文件中:
int ia[] = {0,1,2};
int *beg = begin(ia);    // 指向ia首元素的指针
int *last = end(ia);    // 指向ia尾元素的下一位置的指针
注意:尾后指针不能执行解引用和递增操作。
int *p = ia + 10;        // 错误:ia只有3个元素,p的值未定义
auto n = end(ia) - begin(ia);    // 两个指针相减的结果的类型是一种名为ptrdiff_t的标准库类型,定义在cstddef头文件中,可正可负。

13、标准库类型限定使用的下标必须是无符号类型,而内置的下标运算无此要求(这一点与vector和string不一样)。内置的下标运算符可以处理负值,当然,结果地址必须指向原来的指针所指同一数组中的元素(或是同一数组尾元素的下一位置)。
int *p = &ia[2];        // p指向索引为2的元素
int k = p[-2];            // p[-2]是ia[0]表示的那个元素

14、C风格字符串不是一种类型,而是为了表达和使用字符串而形成的一种约定俗成的写法。按此习惯书写的字符串存放在字符数组中并以空字符结束。以空字符结束的意思是在字符串最后一个字符后面跟着一个空字符('\0')。一般利用指针来操作这些字符串。

15、strlen(p)函数返回p的长度,空字符不计算在内。
strcat(p1, p2)函数将p2附加到p1之后,返回p1
strcpy(p1, p2)函数将p2拷贝给p1,返回p1
上述函数不负责验证其字符串参数,传入此类函数的指针必须指向以空字符作为结束的数组:
char ca[] = {'C', '+', '+'};    // 不以空字符结束
cout << strlen(ca) << endl;        // 严重错误:ca没有以空字符结束。strlen函数将有可能沿着ca在内存中的位置不断向前寻找,直到遇到空字符才停下来。

16、允许使用字符串字面值来初始化string对象:string s("Hello World");
更一般的情况是,任何出现字符串字面值的地方都可以用以空字符结束的字符数组来替代:
①允许使用以空字符结束的字符数组来初始化string对象或为string对象赋值。
②在string对象的加法运算中允许使用以空字符串结束的字符数组作为其中一个运算对象(不能两个运算对象都是);在string对象的复合赋值运算中允许使用以空字符结束的字符数组作为右侧的运算对象。

17、c_str函数的返回值是一个C风格的字符串。也就是说,函数的返回结果是一个指针,该指针指向一个以空字符结束的字符数组。
const char *str = s.c_str();    //结果为const char*,从而确保我们不会改变字符数组的内容
我们无法保证c_str函数返回的数组一直有效,事实上,如果后续的操作改变了s的值就可能让之前返回的数组失去效用。所以如果执行完c_str()函数后程序想一直都能使用其返回的数组,最好将该数组重新拷贝一份。

18、允许使用数组来初始化vector对象,需要指明拷贝区域的首元素地址和尾后地址:
int int_arr[] = {0,1,2,3};
vector<int> ivec(begin(int_arr), end(int_arr));
vector<int> ivec(int_arr+1, int_arr+3);

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

六、多维数组
1、严格来说,C++语言中没有多维数组,通常所说的多维数组其实是数组的数组。
对于二维数组来说,常把第一个维度称作行,第二个维度称作列。

2、允许使用花括号括起来的一组值初始化多维数组。

3、要使用范围for语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。
for(auto row : ia)            // 错误:因为row不是引用类型,所以编译器初始化row时会自动将这些数组形式的元素转换成指向该数组内首元素的指针。
    for(auto col : row)
        cout << col << endl;

4、使用类型别名使"读、写和理解一个指向多维数组的指针"这项工作简单一点:
using int_array = int[4];    // 新标准下类型别名的声明
typedef int int_array[4];    // 等价的typedef声明

一些术语:
1、缓冲区溢出:一种严重的程序故障,主要的原因是试图通过一个越界的索引访问容器的内容,容器的类型包括string、vector和数组等。
2、类模板:用于创建具体类类型的模板。
3、编译器扩展:某个特定的编译器为C++语言额外增加的特性。基于编译器扩展编写的程序不易移植到其他编译器上。
4、拷贝初始化:使用赋值号(=)的初始化形式。新创建的对象是初始值的一个副本。
5、difference_type:由string和vector定义的一种带符号整数类型,表示两个迭代器之间的距离。
6、直接初始化:不使用赋值号(=)的初始化形式。
7、实例化:编译器生成一个指定模板类或函数的过程。
8、ptrdiff_t是cstddef头文件中定义的一种与机器实现有关的带符号整数类型,它的空间足够大,能够表示数组中任意两个指针之间的距离。
9、size_t是cstddef头文件中定义的一种与机器实现有关的无符号整数类型,它的空间足够大,能够表示任意数组的大小。
10、size_type是string和vector定义的类型的名字,能存放下任意string对象或vector对象的大小。在标准库中,size_type被定义为无符号类型。
11、值初始化是一种初始化过车。内置类型初始化为0,类类型,由类的默认构造函数初始化。只有当类包含默认构造函数时,该类的对象才会被值初始化。对于容器的初始化来说,如果只说明了容器的大小而没有指定初始值的话,就会执行值初始化。此时编译器会生成一个值,而容器的元素被初始化为该值。
12、如果p是指针,n是整数,则p[n]与*(p+n)等价。
13、a->b等价于(*a).b。
14、&&运算符:只有当左侧运算对象为真时才会检查右侧运算对象。
15、||运算符:只有当左侧运算对象为假时才会检查右侧运算对象。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值