C++primer 读书笔记 Chapter 3 some tips about library types

1.string类型和字符串字面值:

书上有一句话,很重要:

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

字符串字面值的类型是字符常量的数组,现在可以更明确地认识到:字符串字面值的类型就是const char类型的数组.C++从C语言继承下来的一种通用结构是C风格字符串(C-style   character   string),而字符串字面值就是该类型的实例.

关于字符串字面值,一向对这个概念很模糊,好不容易搜到一篇资料加深理解:http://learn.akae.cn/media/ch08s04.html

 

2.string的输出输入:

 

从标准输入读取 string 并将读入的串存储在 s 中。string 类型的输入操作符:

 

  • Reads and discards any leading whitespace (e.g., spaces, newlines, tabs)

    读取并忽略开头所有的空白字符(如空格,换行符,制表符)。

  • It then reads characters until the next whitespace character is encountered

    读取字符直至再次遇到空白字符,读取终止。

注意:如果给定和上一个程序同样的输入,则输出的结果是"Hello World!"(注意到开头和结尾的空格),则屏幕上将输出"Hello",而不含任何空格。

 

(问题:while (cin >> word)
             cout << word << endl;
string型遇到空白字符都会终止。但是上面这个循环,我输入空格的时候不会输出,输入换行符才会执行输出语句,这是为什么呢?

答:缓冲方式的输入,是系统来处理的,直到发生回车这样的输入或者缓冲区满,比如,当你输入个abcde fghij klmn只要你还没按回车,那么,实际上,你的程序没有接收到任何输入,这些输入都在系统缓冲区呢,与你的程序无关 。

这里补充关于缓冲区的知识,网上搜到的:http://www.examda.com/ncre2/cpp/jichu/20100815/082041550.html

原文如下:

 什么是缓冲区
  缓冲区根据其对应的是输入设备还是输出设备,分为输入缓冲区和输出缓冲区。
  为什么要引入缓冲区
  我们为什么要引入缓冲区呢?
  比如我们从磁盘里取信息,我们先把读出的数据放在缓冲区,计算机再直接从缓冲区中取数据,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作大大快于对磁盘的操作,故应用缓冲区可大大提高计算机的运行速度。
  又比如,我们使用打印机打印文档,由于打印机的打印速度相对较慢,我们先把文档输出到打印机相应的缓冲区,打印机再自行逐步打印,这时我们的CPU可以处理别的事情。
  现在您基本明白了吧,缓冲区就是一块内存区,它用在输入输出设备和CPU之间,用来缓存数据。它使得低速的输入输出设备和高速的CPU能够协调工作,避免低速的输入输出设备占用CPU,解放出CPU,使其能够高效率工作。
  
缓冲区的类型
  缓冲区 分为三种类型:全缓冲、行缓冲和不带缓冲。
  1、全缓冲
  在这种情况下,当填满标准I/O缓存后才进行实际I/O操作。全缓冲的典型代表是对磁盘文件的读写。
  2、行缓冲
  在这种情况下,当在输入和输出中遇到换行符时,执行真正的I/O操作。这时,我们输入的字符先存放在缓冲区,等按下回车键换行时才进行实际的I/O操作。典型代表是键盘输入数据。
  3、不带缓冲
  也就是不进行缓冲,标准出错情况stderr是典型代表,这使得出错信息可以直接尽快地显示出来。
  
缓冲区的刷新
  下列情况会引发缓冲区的刷新:
  1、缓冲区满时;
  2、执行flush语句;
  3、执行endl语句;
  4、关闭文件。
  可见,缓冲区满或关闭文件时都会刷新缓冲区,进行真正的I/O操作。另外,在C++中,我们可以使用flush函数来刷新缓冲区(执行I/O操作并清空缓冲区),如:
  cout<<flush; //将显存的内容立即输出到显示器上进行显示
  endl控制符的作用是将光标移动到输出设备中下一行开头处,并且清空缓冲区。
  cout<<endl;
  相当于
  cout<<”/n” <<flush;
  通过实例演示说明
  1、文件操作演示全缓冲
  创建一个控制台工程,输入如下代码:
  #include <fstream>
  using namespace std;
  int main()
  {
  //创建文件test.txt并打开
  ofstream outfile("test.txt");
  //向test.txt文件中写入4096个字符’a’
  for(int n=0;n<4096;n++)
  {
  outfile<<’a’;
  }
  //暂停,按任意键继续
  system("PAUSE");
  //继续向test.txt文件中写入字符’b’,也就是说,第4097个字符是’b’
  outfile<<’b’;
  //暂停,按任意键继续
  system("PAUSE");
  return 0;
  }

 

3.getline函数:

getline函数接受两个参数:一个输入流对象和一个 string 对象。getline 函数从输入流的下一行读取,并保存读取的内容到不包括换行符。和输入操作符不一样的是,getline 并不忽略行开头的换行符。只要 getline 遇到换行符,即便它是输入的第一个字符,getline 也将停止读入并返回。getline 函数将 istream 参数作为返回值
int main()
     {
         string line;
         // read line at time until end-of-file
         while (getline(cin, line))
             cout << line << endl;
         return 0;
     }


4.string::size_type型:

 

书上描述到:

string 类类型和许多其他库类型都定义了一些配套类型(companion type)。通过这些配套类型,库类型的使用就能与机器无关(machine-independent)。size_type 就是这些配套类型中的一种。它定义为与 unsigned 型(unsigned int 或 unsigned long)具有相同的含义,而且可以保证足够大能够存储任意 string 对象的长度。为了使用由 string 类型定义的 size_type 类型是由 string 类定义。

这句话怎么解释呢?看看神奇的百度知道上的答案吧:

size_type是为string类类型和vector类类型定义的类型,用以保存任意string对象或vector对象的长度,标准库类型将size_type定义为unsigned类型   string抽象意义是字符串, size()的抽象意义是字符串的尺寸, string::size_type抽象意义是尺寸单位类型   string::size_type它在不同的机器上,长度是可以不同的,并非固定的长度。但只要你使用了这个类型,就使得你的程序适合这个机器。与实际机器匹配。   eg   string::size_type从本质上来说,是一个整型数。关键是由于机器的环境,它的长度有可能不同。 例如:我们在使用 string::find的函数的时候,它返回的类型就是 string::size_type类型。而当find找不到所要找的字符的时候,它返回的是 npos的值,这个值是与size_type相关的。假如,你是用 string s; int rc = s.find(.....); 然后判断,if ( rc == string::npos ) 这样在不同的机器平台上表现就不一样了。如果,你的平台的string::size_type的长度正好和int相匹配,那么这个判断会侥幸正确。但换成另外的平台,有可能 string::size_type的类型是64位长度的,那么判断就完全不正确了。 所以,正确的应该是: string::size_type rc = s.find(.....); 这个时候使用 if ( rc == string::npos )就回正确了。

这样就清楚了吧

 

为什么要使用size_type型呢?书上已经做出了解释了:

虽然我们不知道 string::size_type 的确切类型,但可以知道它是 unsigned 型。对于任意一种给定的数据类型,它的 unsigned 型所能表示的最大正数值比对应的 signed 型要大倍。这个事实表明 size_type 存储的 string 长度是 int 所能存储的两倍。

使用 int 变量的另一个问题是,有些机器上 int 变量的表示范围太小,甚至无法存储实际并不长的 string 对象。如在有 16 位 int 型的机器上,int 类型变量最大只能表示 32767 个字符的 string 个字符的 string 对象。而能容纳一个文件内容的 string 对象轻易就会超过这个数字。因此,为了避免溢出,保存一个 stirng 对象 size 的最安全的方法就是使用标准库类型 string::size_type。

 

 

5.string和字符串字面值的连接:

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

string s5 = s1 + ", " + "world"; // ok: each + has string operand
string s6 = "hello" + ", " + s2; // error: can't add string literals

 

 

6.下标操作:

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

下表操作可用作左值。

 

任何可产生整型值的表达式可用作下标操作符的索引。如:

str[someotherval * someval] = someval;(someotherval和someval为整型)

虽然任何整型数值都可作为索引,但索引的实际数据类型却是类型 unsigned 类型 string::size_type。

 

 

注意:使用 size_type 类型时,必须指出该类型是在哪里定义的。如:

vector<int>::size_type

 

7.string 对象中字符的处理:

适用于 string 对象的字符(或其他任何 char 值)。这些函数都在 cctype 头文件中定义。

 

8.关于vector:

vector的初始化:

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

vector的下标操作 :

注意:vector的下标操作不添加元素。如:

vector<int> ivec;   // 空的vector
     for (vector<int>::size_type ix = 0; ix != 10; ++ix)
         ivec[ix] = ix; // 错误: ivec没有元素
重点注意的一点是:
仅能对确知已存在的元素进行下标操作

试图获取不存在的元素必须产生运行时错误。和大多数同类错误一样,不能确保执行过程可以捕捉到这类错误,运行程序的结果是不确定的。由于取不存在的元素的结果标准没有定义,因而不同的编译器实现会导致不同的结果,但程序运行时几乎肯定会以某种有趣的方式失败。如:

 

vector<int> ivec;      // empty vector
     cout << ivec[0];       // Error: ivec has no elements!

  

vector<int> ivec2(10); // vector with 10 elements
     cout << ivec[10];      // Error: ivec has elements 0...9

 

 

9.const_iterator和const的iterator的区别:

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

vector<int> nums(10);  // nums is nonconst
vector<int>::const_iterator cit = nums.begin();
     *cit = 1;               // 错误,不能改变其指向的值
     ++cit;                  // 正确,迭代器自身的值可以改变

而声明一个 const 迭代器时,必须初始化迭代器。一旦被初始化后,就不能改变它的值。

vector<int> nums(10);  // nums is nonconst
     const vector<int>::iterator cit = nums.begin();
     *cit = 1;               // 正确,可以改变其指向的值

     ++cit;                  // 错误,迭代器自身的值不能改变

 

10.迭代器支持的算术操作:

iter+n/iter-n

加上或减去的值的类型应该是 vector 的 size_type 或 difference_type 类型

 

iter1-iter2 

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

 

迭代器不支持两个迭代器相加的算术操作:

iter1+iter2  //error

 

使用迭代器要注意的问题是:

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

 

11.bitset

对于bitset概念不是很熟,抄下书上的概念总结如下:

类似于 vector,bitset 类是一种类模板;而与 vector 不一样的是 bitset 类型对象的区别仅在其长度而不在其类型。在定义 bitset 时,要明确 bitset 含有多少位,须在尖括号内给出它的长度值:

bitset<32> bitvec; // 32 bits, all zero

 

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

 

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

     string strval("1100");
     bitset<32> bitvec4(strval);

  bitvec4 的位模式中第 2 和 3 的位置为 1,其余位置都为 0。如果 string 对象的字符个数小于 bitset 类型的长度,则高阶位置为 0。

注意:

string 对象和 bitsets 对象之间是反向转化的:string 对象的最右边字符(即下标最大的那个字符)用来初始化 bitset 对象的低阶位(即下标为 0 的位)。当用 string 对象初始化 bitset 对象时,记住这一差别很重要。


与 vector 和 string 中的 size 操作一样,bitset 的 size 操作返回 bitset 对象中二进制位的个数,返回值的类型是 size_t::。size_t 类型定义在 cstddef 头文件中,该文件是 C 标准库的头文件 stddef.h 的 C++ 版本。它是一个与机器相关的 unsigned 类型,其大小足以保证存储内在中对象的大小。

你可能感兴趣的:(C++,String,vector,读书,library,Types)