WHCSRL 技术网

C++ Primer 第三章学习 随笔 —— “字符串、向量和数组”

嗯,自己就可感觉到第二章笔记整理的比较糟糕,诸如,叙述风格的不统一,缺乏总结性的概括,文本格式有些乱等问题。需要更多的积累与锻炼呀,希望在第三章的笔记中得到改进。那么就开始正题吧!

字体颜色说明:

黑色字体,从书本中截取或提炼的主要说明性内容

黑色粗体,本章的关键词

绿色字体,多为废话,更多的是我”苦中作乐“的体现吧,不至于让我记录笔记的过程过于乏味。

黄色字体,包含了较多个人理解,补充了书本此处未涉及的说明性内容。

3.1 命名空间的using声明

在前两章为使用命名空间中的成员(如cin,cout),每次使用都通过作用域操作符将命名空间显式的标注出来。例如:std::cin

上面的方法确实有些烦琐,这里将开始使用更简单的途径来使用命名空间里的成员。本章将学习其中最安全的方法,使用using声明。在遥远的第18章才会讲解另一种方法,从叙述中就可体会到,另一种方法安全性更低

using声明格式如下:

        using namespace::name;        //例如:using std::cin;

一旦声明了上述语句,就可以直接访问命名空间中的名字。通俗的说我们把这个名字给暴露出来了。

每个名字都需要独立的using声明

按照规定,每个using声明引入命名空间中的一个成员。如果你习惯于使用 using namespace std;这个”大杀器“,想必你依旧会觉得这个方法比较繁琐,但便利的使用往往要承担更高的风险,后面会简单提及使用using的弊端。

头文件不应包含using声明

位于头文件的代码一般来说不应该使用using声明。因为头文件有被拷贝到其它文件的可能,如果某个头文件使用了uisng声明,那么每个包含这个头文件的的文件都会有这个声明。对某些程序而言,由于不经意间包含了一些名字,会产生始料未及的名字冲突。

3.2 标准库类型string

标注库类型string表示可变长的字符序列,欲使用string需要先包含string头文件

        #include<string>

欲”直呼其名“需using声明

        using std::string

3.2.1 定义和初始化string对象

string s1

默认初始化,s1是一个空串

string s2(s1)s2是s1的副本

string s2 = s1

s2是s1的副本
string s3("value")s3是字符串常量”value“的副本
string s3 = "value"s3是字符串常量”value“的副本
string s4(n,’c‘)把s4初始化n个连续的字符c组成的字符串

直接初始化和拷贝初始化

使用等号初始化一个变量,执行的是拷贝初始化,编译器把等号左侧的初始值拷贝到新创建的对象中去。如果不使用等号,则执行的是直接初始化。

        string s1 = "hello";        //拷贝初始化

        string s2 ("hello");        //直接初始化

这两个声明语句,产生的效果是相同的(都调用了拷贝构造函数),差异体现在行为的意图上,前者是在用一个对象去初始化另一个对象,后者在通过构造函数显式的参数来初始化。

3.2.2 string对象上的操作

该部分属于具体操作的使用讲解,记录于笔记当中过于烦琐,出于此,该部分只会记录一些零散的知识点(不那么司空见惯的知识点)

读写string对象

string对象执行读取操作的规则:string对象会自动忽略开头的空白(即空格符、换行符、制表符等)并从第一个真正的字符开始读起,直到遇到下一处空白为止。

使用getline读取一整行

如果希望字符串中能读取到输入时的空格符,那么应该运用getline函数,而非<<运算符。

getline函数接收两个参数,一个是输入流对象,一个是string对象。函数从给定输入流中读入内容,直到遇到换行符为止(注意这个换行符也被读进来了),然后把所读内容存入string对象当中(注意不存换行符)。getline函数,只要遇到换行符就结束,如果一开始就是换行符,那么得到的结果是空string。

下面从程序可以一行一行的读取内容,直到内容读完为止

  1. int main()
  2. {
  3. string line;
  4. while (getline(cin, line))
  5. cout << line << endl;
  6. return 0;
  7. }

string::size_type类型

size_type类型配套于string类,在具体使用时,通过作用域操作符来表明size_type是在类string中定义的。

string的size成员函数返回的就是一个string::size_type类型的值。

string::size_type的具体细节不必过多了解,但要明确它是一个无符号类型的值。当然该类型的存在必然有其价值,不然为何不让size函数直接返回unsigned int类型呢?

区别在于,string::size_type类型可以在不同机器上表现出不同的大小,从而使得程序有更好的可移植性。

比较string对象

一组值的比较必然会比单个值的比较要麻烦许多,这里有必要记录一下。

string对象对于相等性运算符(==和!=)的检验是苛刻的。必须长度相等,每一个字符都相同且对应,并且区分大小写(string对象的比较对大小写是敏感的)。

关系运算符 <,<=,>,>= 比较规则如下:

1.两个string对象,元素从左往右,如果在某个位置对应的字符不一样,以这两个字符为依据,按照字典排序确定两个string对象大小。

2.如果比较到较短的那个string对象元素末尾为止,两个string对象所有字符都相同且对应,那么较短string对象小于较长string对象。

3.2.3 处理string对象中的字符

遇到了些我未曾了解过的内容,看来这里要多花点时间了

处理string对象中的字符串,在cctype头文件中定义了一组标准库函数处理这部分工作,下表列出了主要的函数名及其含义。(不得不说,一边看下来,啥也记不住)

isalnum(c)当c是字母或数字时为真
isalpha(c)当c是字母时为真
iscntrl(c)当c是控制字符时为真
isdigit(c)当c是数字时为真
isgraph(c)当c不是空格但可打印时为真
islower(c)当c是小写字母时为真
isprint(c)当c是可打印字符时为真(即c是空格或c具有可视形式)
ispunct(c)当c是标点符号时为真(即c不是控制字符、数字、字母、可打印空白中的一种)
isspace(c)当c是空白时为真(即c是空格、横向制表符、纵向制表符、回车符、换行符、进纸符中的一种)
isupper(c)当c是大写字母时为真
isxdigit(c)当c是十六进制数字时为真
tolower(c)如果c是大写字母,输出对应小写字母;否则原样输出c
toupper(c)如果c是小写字母,输出对应大写字母;否则原样输出c

处理每个字符?使用基于范围的for语句

如果相对string对象中的每个字符做点什么操作,目前最好的办法是使用C++11新标准提供的一种语句:范围for语句。这种语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,其语法形式是:

        for (declaration : expression)

                statement

其中,expression部分是一个序列性的对象。declaration部分负责定义一个变量将变量被用于访问序列中的基础元素。每次迭代,declaration部分的变量会被初始化为expression部分的下一个元素值。(其实就是在遍历序列中的每一个元素)

让我们来尝试运用一下范围for语句吧,并且简单结合一下前面所学的内容。实现一个可以统计string对象中有多少个标点符号的程序:

  1. #include<iostream>
  2. #include<string> //为了使用string
  3. #include<cctype> //为了使用ispunct函数,如果该字符为标点,返回结果为true
  4. using std::string; //将位于命名空间std中的string这一名称暴露出来
  5. using std::cout; //将位于命名空间std中的 cout 这一名称暴露出来
  6. using std::endl; //将位于命名空间std中的 endl 这一名称暴露出来
  7. int main()
  8. {
  9. string str("Hello World!!!"); //声明定义一个string对象,用一段字符串字面值直接初始化
  10. decltype(str.size())num = 0; //声明定义了无符整型的变量num,并将它初始化为0,用来表示标点的数量
  11. for (auto c : str) //遍历string对象str,用推导出来的字符型变量c来依次储存字符串中的元素(注意c的生命周期只有一个循环,一个循环结束后,旧的c被释放掉了,又重新定义了一个新的c)
  12. if (ispunct(c)) //判断该字符是否为标点
  13. num++; //如果真的是标点,那么让num加一
  14. cout << "该字符串当中 标点 的数量为:" << num << endl; //输出一个结果
  15. system("pause"); //功能就是“按任意键继续”,以免程序结束的太突然
  16. return 0;
  17. }

程序运行后的结果就是:该字符串当中 标点 的数量为:3

使用范围for语句改变字符串中的字符

如果现在想改变string对象中字符的值该怎么做呢?比如,将string对象里的字符元素全转为大写,这里的关键点在于引用的使用,程序的实现部分代码如下:

  1. string str("Hello World!!!"); //创建一个将被转化的string对象
  2. for (auto& c : str) //遍历string类型的str对象中的每一个元素
  3. c = toupper(c); //如果c所表示的字符是小写的,那么改为大写的
  4. cout << str << endl; //输出结果

程序输出结果为:HELLO WORLD!!!

如果c不是引用而只是一个变量的话,你会发现字符串str将毫无改变。

为什么呢?其中的原理和 经典的值传递与地址传递的问题 的道理互通。想要较好的理解其中的原理,图文并茂的简单描述下内存模型才是最优解,当然对于这一章而言有些超纲了,所以书本在这里也并未做出解释。(说了这么多,然而我也并不打算做出解释,狗头)

后面关于下标运算符的内容就省略了,未完待续,每天继续施工。

推荐阅读