C++ Primer 第二遍阅读笔记(第三章)

第三章 标准库类型

本章完后先跳至第九章。 然后在返回继续复习


本章将介绍标准库中的 vectorstringbitset 类型。

除了这些在语言中定义的类型外,C++ 标准库还定义了许多更高级的抽象数据类型之所以说这些标准库类型是更高级的,是因为其中反映了更复杂的概念;之所以说它们是抽象的,是因为我们在使用时不需要关心它们是如何表示的,只需知道这些抽象数据类型支持哪些操作就可以了。

两种最重要的标准库类型是 string 和 vector。string 类型支持长度可变的字符串,vector 可用于保存一组指定类型的对象

有一种情况下,必须总是使用完全限定的标准库名字:在头文件中(std::cin、std::cout...);通常,头文件中应该只定义确实必要的东西。请养成这个好习惯。

与其他的标准库类型一样,用户程序要使用 string 类型对象,必须包含相关头文件。如果提供了合适的 using 声明,那么编写出来的程序将会变得简短些:



表 3.1 列出了几个 string 类型常用的构造函数。当没有明确指定对象初始化式时,系统将使用默认构造函数


因为历史原因以及为了与 C 语言兼容,字符串字面值与标准库 string类型不是同一种类型。这一点很容易引起混乱,编程时一定要注意区分字符串字面值和 string 数据类型的使用,这很重要。

string 类型的输入操作符:
读取并忽略开头所有的空白字符(如空格,换行符,制表符)。
读取字符直至再次遇到空白字符,读取终止。

有一个有用的 string IO 操作:getline。这个函数接受两个参数:一个输入流对象和一个string 对象。getline 函数从输入流的下一行读取,并保存读取的内容到不包括换行符。和输入操作符不一样的是,getline 并不忽略行开头的换行符。只要 getline 遇到换行符,即便它是输入的第一个字符,getline 也将停止读入并返回。如果第一个字符就是换行符,则 string 参数将被置为空 string。

把每行输出一个单词改为每次输出一行文本:


表 3.2 列出了常用的 string 操作。



从逻辑上来讲,size() 成员函数似乎应该返回整形数值,或如 2.2 节“建议”中所述的无符号整数。但事实上,size 操作返回的是string::size_type 类型的值。我们需要对这种类型做一些解释。
任何存储 string 的 size 操作结果的变量必须为 string::size_type 类型。特别重要的是,不要把 size 的返回值赋给一个 int 变量。


string 对象比较操作是区分大小写的,即同一个字符的大小写形式被认为是两个不同的字符。在多数计算机上,大写的字母位于小写之前:任何一个大写之母都小于任意的小写字母。

关系操作符比较两个 string 对象时采用了和(大小写敏感的)字典排序相同的策略:
如果两个 string 对象长度不同,且短的 string 对象与长的 string 对象的前面部分相匹配,则短的 string 对象小于长的 string 对象。
如果 string 对象的字符不同,则比较第一个不匹配的字符

举例来说,给定 string 对象;

则 substr 小于 phrase,而 slang 则大于 substr 或 phrase 

当进行 string 对象和字符串字面值混合连接操作时,+ 操作符的左右操作数必须至少有一个是 string 类型的

s5 的初始化方法显得有点不可思议,但这种用法和标准输入输出的串联效果是一样的(1.2 节)。本例中,string 标准库定义加操作返回一个 string 对象。这样,在对 s5 进行初始化时,子表达式 s1 + ", " 将返回一个新 string 对象,后者再和字面值 "world\n"连接。
而 s6 的初始化是非法的。依次来看每个子表达式,则第一个子表达式试图把两个字符串字面值连接起来。这是不允许的,因此这个语句是错误的。

string 类型通过下标操作符([ ])来访问 string 对象中的单个字符。下标操作符需要取一个 size_type 类型的值,来标明要访问字符的位置。这个下标中的值通常被称为“下标”或“索引”


前面讲过,应该用 string::size_type 类型的变量接受 size 函数的返回值。在定义用作索引的变量时,出于同样的道理,string 对象的索引变量最好也用 string::size_type 类型


表 3.3 列出了各种字符操作函数,适用于 string 对象的字符(或其他任何 char 值)。这些函数都在 cctype 头文件中定义。


这里给出一个例子,运用这些函数输出一给定 string 对象中标点符号的个数:


通常,C++ 程序中应采用 cname 这种头文件的版本,而不采用 name.h 版本,这样,标准库中的名字在命名空间 std 中保持一致。使用 .h 版本会给程序员带来负担,因为他们必须记得哪些标准库名字是从 C 继承来的,而哪些是 C++ 所特有的。

vector 是一个类模板(class template)。声明从类模板产生的某种类型的对象,需要提供附加信息,信息的种类取决于模板。以 vector 为例,必须说明 vector 保存何种对象的类型,通过将类型放在类型放在类模板名称后面的尖括号中来指定类型:

和其他变量定义一样,定义 vector 对象要指定类型和一个变量的列表。上面的第一个定义,类型是 vector<int>,该类型即是含有若干 int 类型对象的 vector,变量名为 ivec。第二个定义的变量名是 Sales_vec,它所保存的元素是 Sales_item 类型的对象。

类模板:一个可创建许多潜在类类型的蓝图。使用类模板时,必须给出实际的类型和值。例如,vector 类型是保存给定类型对象的模板。创建一个 vector 对象是,必须指出这个 vector 对象所保存的元素的类型。vector<int>保存 int 的对象,而 vector<string> 则保存 string 对象,以此类推。

vector 不是一种数据类型,而只是一个类模板,可用来定义任意多种数据类型。vector 类型的每一种都指定了其保存元素的类型。因此,vector<int> 和 vector<string> 都是数据类型

表 3.4 列出了vector构造函数


可以用元素个数和元素值对 vector 对象进行初始化。构造函数用元素个数来决定 vector 对象保存元素的个数,元素值指定每个元素的初始值:


如果没有指定元素的初始化式,那么标准库将自行提供一个元素初始值进行值初始化(value initializationd)。这个由库生成的初始值将用来初始化容器中的每个元素,具体值为何,取决于存储在 vector 中元素的数据类型。

如果 vector 保存内置类型(如 int类型)的元素,那么标准库将用 0 值创建元素初始化式
如果 vector 保存的是含有构造函数的类类型(如 string)的元素,标准库将用该类型的默认构造函数创建元素初始化式。

表 3.5 列出了几种最重要的 vector 操作。


使用 size_type 类型时,必须指出该类型是在哪里定义的。vector 类型总是包括总是包括 vector 的元素类型:


push_back 操作接受一个元素值,并将它作为一个新的元素添加到 vector 对象的后面,也就是“插入(push)”到 vector 对象的“后面(back)”:


vector 中的对象是没有命名的,可以按 vector 中对象的位置来访问它们。通常使用下标操作符来获取元素。vector 的下标操作类似于 string 类型的下标操作。

C++ 程序员习惯于优先选用 != 而不是 < 来编写循环判断条件。

试图对不存在的元素进行下标操作是程序设计过程中经常会犯的严重错误。所谓的“缓冲区溢出”错误就是对不存在的元素进行下标操作的结果。这样的缺陷往往导致 PC 机和其他应用中最常见的安全问题。

下边是比较重要的iterator部分,这部分内容非常重要,希望看不懂的读者回去翻翻书,我这里说的只是凤毛麟角(自认为这部分学的不错)。


由 end 操作返回的迭代器并不指向 vector 中任何实际的元素,相反,它只是起一个哨兵(sentinel)的作用,表示我们已处理完 vector 中所有元素。


解引用操作符返回迭代器当前所指向的元素。假设 iter 指向 vector 对象 ivec 的第一元素,那么 *iter 和 ivec[0] 就是指向同一个元素。

迭代器使用自增操作符向前移动迭代器指向容器中下一个元素。从逻辑上说,迭代器的自增操作和 int 型对象的自增操作类似。

由于 end 操作返回的迭代器不指向任何元素,因此不能对它进行解引用或自增操作

另一对可执行于迭代器的操作就是比较:用 == 或 !=操作符来比较两个迭代器,如果两个迭代器对象指向同一个元素,则它们相等,否则就不相等。

标准库为每一种标准容器(包括 vector)定义了一种迭代器类型。迭代器类型提供了比下标操作更通用化的方法:所有的标准库容器都定义了相应的迭代器类型,而只有少数的容器支持下标操作。因为迭代器对所有的容器都适用,现代 C++ 程序更倾向于使用迭代器而不是下标操作访问容器元素,即使对支持下标操作的 vector 类型也是这样。


每种容器类型还定义了一种名为 const_iterator的类型,该类型只能用于读取容器内元素,但不能改变其值。


例如,如果 text 是 vector<string> 类型,程序员想要遍历它,输出每个元素,可以这样编写程序:


使用 const_iterator 类型时,我们可以得到一个迭代器,它自身的值可以改变,但不能用来改变其所指向的元素的值。可以对迭代器进行自增以及使用解引用操作符来读取值,但不能对该元素赋值


不要把 const_iterator 对象与 const 的 iterator 对象混淆起来。声明一个 const 迭代器时,必须初始化迭代器。一旦被初始化后,就不能改变它的值


可以对迭代器对象加上或减去一个整形值。这样做将产生一个新的迭代器,其位置在 iter 所指元素之前(加)或之后(减) n 个元素的位置。加或减之后的结果必须指向 iter 所指 vector 中的某个元素,或者是 vector 末端的后一个元素。


iter1 - iter2该表达式用来计算两个迭代器对象的距离,该距离是名为 difference_typesigned 类型 size_type 的值,这里的 difference_type 是 signed 类型,因为减法运算可能产生负数的结果。该类型可以保证足够大以存储任何两个迭代器对象间的距离。iter1 与 iter2 两者必须都指向同一 vector 中的元素,或者指向 vector 末端之后的下一个元素。


可以用迭代器算术操作来移动迭代器直接指向某个元素,例如,下面语句直接定位于 vector 中间元素:


任何改变 vector 长度的操作都会使已存在的迭代器失效。例如,在调用 push_back 之后,就不能再信赖指向 vector 的迭代器的值了。


bitset部分当时自己学的不是很透彻,所以记录的内容和能会多一些:


有些程序要处理二进制位的有序集,每个位可能包含 0(关)1(开)值。位是用来保存一组项或条件的 yes/no 信息(有时也称标志)的简洁方法。标准库提供的 bitset 类简化了位集的处理。要使用 bitset 类就必须包含相关的头文件。

表 3.6 列出了 bitset 的构造函数。


与 vector 不一样的是 bitset 类型对象的区别仅在其长度而不在其类型。在定义 bitset 时,要明确bitset 含有多少位,须在尖括号内给出它的长度值,给出的长度值必须是常量表达式

当用 unsigned long 值作为 bitset 对象的初始值时,该值将转化为二进制的位模式。而 bitset 对象中的位集作为这种位模式的副本。如果 bitset 类型长度大于 unsigned long 值的二进制位数,则其余的高阶位将置为 0;如果 bitset 类型长度小于 unsigned long 值的二进制位数,则只使用 unsigned 值中的低阶位,超过 bistset 类型长度的高阶位将被丢弃。(贝贝 注:不太理解这段话)

在 32 位 unsigned long 的机器上,十六进制值 0xffff 表示为二进制位就是十六个 1 和十六个 0(每个 0xf 可表示为 1111)。可以用 0xffff 初始化 bitset 对象:


当用 string 对象初始化 bitset 对象时,string 对象直接表示为位模式。从 string 对象读入位集的顺序是从右向左

bitvec4 的位模式中第 2 和 3 的位置为 1,其余位置都为 0。如果 string 对象的字符个数小于 bitset 类型的长度,则高阶位置为 0。(贝贝 注:最后一位脚标是0)

string 对象和 bitsets 对象之间是反向转化的:string 对象的最右边字符(即下标最大的那个字符)用来初始化 bitset 对象的低阶位(即下标为 0 的位)。


bitset操作:



不一定要把整个 string 对象都作为 bitset 对象的初始值。相反,可以只用某个子串作为初始值:


如果 bitset 对象中有一个或几个二进制位置为 1,则 any 操作返回 true,也就是说,其返回值等于 1;相反,如果 bitset 对象中二进制位全为 0,则none 操作返回 true


如果需要知道置为 1 的二进制位的个数,可以使用count 操作,该操作返回置为 1 的二进制位的个数。


count 操作的返回类型是标准库中命名为 size_t 类型。size_t 类型定义在cstddef 头文件中,该文件是 C 标准库的头文件 stddef.h 的 C++ 版本。它是一个与机器相关的 unsigned 类型,其大小足以保证存储内在中对象的大小。

与 vector 和 string 中的 size 操作一样,bitset 的 size 操作返回 bitset 对象中二进制位的个数,返回值的类型是size_t

除了用下标操作符,还可以用 set;、test 和 reset操作来测试或设置给定二进制位的值:


set 和 reset 操作分别用来对整个 bitset 对象的所有二进制位全置 1 和全置 0。



flip 操作可以对 bitset 对象的所有位或个别位取反:



to_ulong 操作返回一个 unsigned long 值,该值与 bitset 对象的位模式存储值相同。仅当 bitset 类型的长度小于或等于 unsigned long 的长度时,才可以使用 to_ulong 操作:


可以用输出操作符输出 bitset 对象中的位模式



为了测试某个二进制位是否为 1,可以用 test 操作或者测试下标操作符的返回值:


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值