---恢复内容开始---

c++ primer plus 第6版 部分二    5-  章

第五章

计算机除了存储外 还可以对数据进行分析、合并、重组、抽取、修改、推断、合成、以及其他操作

1.for循环的组成部分

  a  设置初始值

  b  执行测试,看循环时候应当继续进行

  c  执行循环操作

  d  更新用于测试的值

  只要测试表达式为true    循环体就会执行

  for (initialization; test-expression; update-expression)

    body

  test-expression决定循环体是否被执行,通常这个表达式是关系表达式,c++会将此结果强制转换为bool类型。值0的表达式将被转换为bool值false,导致循环结束。

  

for   后面跟着一对括号    它是一个c++关键字   因此编译器不会将for视为一个函数

 1. 表达式和语句

    任何值或任何有效的值和运算符的组合都是表达式。c++中每个表达式都有值。通常值是很明显的。

   c++将赋值表达式的值定义为左侧成员的值,因此这个表达式的值为20.由于赋值表达式有值,可以编写

    maids=(cooks=4)+3;     值为7

  赋值表达式从右向左结合

  x<y    这样的关系表达式被判定为true或者false

  int x;

  cout<<(x=100)<<endl;  //x=100

  cout<<(x<3)<<endl;   //0

  cout<<(x>3)<<endl;   //1 

  cout.setf(ios_base::boolalpha);//老式的c++实现使用ios:boolalpha     来作为setf()的参数

  cout<<(x<3)<<endl;  //false

  cout在现实bool值之前将他们转换为int      但是cout.setf(ios::boolalpha)  函数调用设置了一个标记,该标记命令cout显示true和false   而不是1和0;

  判定表达式的值 改变了内存中数据的值时,表达式有副作用 side effect。

  例如x++     判定赋值表达式   改变了x的值  所以有副作用

  有例如x+100    判定赋值表达式   计算出一个新值   未改变x的值    所以没有副作用

  表达式到语句的转换非常容易    只需要加上分号即可。

 2.非表达式和语句

  返回语句     声明语句 和for语句都不满足“语句=表达式+分号”这种模式

  int a;  这是一条语句   但是int a并不是表达式   因为它没有值

  所以这些代码是非法的:

    eggs=int a *1000;

    cin>>int a;

  不能把for循环赋值给变量

 3.修改规则

  c++    在c循环的基础上添加了一些特性   可以对for循环句法做一些调整

  for(expression;expression;expression)

    statement;

  示例:

  for(int i=0;i<5;i++)     int i=0   叫做生命语句表达式  (不带分号的声明)  此种只能出现在for语句中

  其中 int i=0   为声明 不是表达式    这由于上面的语法向矛盾

  所以这种调整已经被取消

  修改之后的语法:

  for (for-init-statement condition;expression)

    statement

  感觉奇怪,这里只用了一个分号,但是这是允许的因为for-init=statement被视为一条语句,而语句有自己的分号。对于for-init-statement来说,它既可以是表达式语句,也可以是声明。

  语句本身有自己的分号,这种句法规则用语句替换了后面跟分号的表达式。在for循环初始化部分中声明和初始化变量。

  在for-init-statement中声明变量还有其实用的一面。这种变量只存在于for语句中,当程序离开循环后,这种变量将消失。

  for(int i=0;i<5;i++)

  较老的c++实现遵循以前的规则,对于前面的循环,将把i视为在循环之前声明   所以在循环结束后,i变量仍然存在。

  

4.回到for循环

  const int arsize=16;//   定义整型的常量   在下方程序中使用  数组长度的定义

  for(int i=2;i<arsize;i++)  //i<arsize   下标从0到arsize-1       所以数组索引应该在arsize-1的位置停止。也可以使用i<=arsize-1  但是没有前面的表达式好。

5.修改步长

  之前的循环计数都是加1或者减1     可以通过修改更新表达式来修改步长。i=i+by

  int by;

  cin>>by;

  for(int i=0;i<100;i=i+by)

  注意检测不等号通常要比检测相等要好。上例中如果将i<100改为i==100  则不可行  因为i的取值不会为100

  using声明和using编译指令的区别。

6.使用for循环访问字符串

 示例:

  

#include <iostream>
#include <string>

int main(){
using namespace std;
cout<<"enter a word"<<endl;
string word;    //定义对象
cin>>word;  

for(int i=word.size()-1;i>=0;i--)   //word对象的字符串长度   size成员函数
cout<<word[i];      //输出各个字符
cout<<"\n----Bye.\n"<<endl;
return 0;
}

  如果所用的实现没有添加新的头文件,则必须使用string.h    而不是cstring

7.递增运算符++     递减运算符--

  都是讲操作数相加或者相减   他们有两种不同的形式   一种为前缀   另一种为后缀    ++x       x++

  但是他们对操作数的影响的时间是不同的

  int a=20    b=20;

  cout<<"a++="<<a++<<"++b= "<<++b<<endl;

  cout<<"a="<<a<<"b= "<<b<<endl;

  a++为使用a的当前值计算表达式   然后将a的值加1         使用后修改

  ++b是先将b的值加1,然后使用新的值来计算表达式  修改后再使用

8.副作用 和顺序点

  side effect  副作用

  指的是在计算表达式时对某些东西进行了修改  例如存储在变量中的值

  顺序点是程序执行过程中的一个点。c++中    语句中的分号就是一个顺序点   这就意味着程序处理下一条语句之前,赋值运算符、递增运算符和递减运算符执行的所有修改都必须完成。任何完整的表达式末尾都是一个顺序点。

  表达式的定义:不是另外一个表达式的子表达式

  顺序点有助于阐明后缀递增何时进行

  while(guest++<10)

    cout<<guests<<endl;

  类似于只有测试表达式的for循环。可能会被认为是使用值然后再递增即cout中先使用guests的值   然后再将其值加1。

  但是guests++<10 是一个完整的表达式,因为它是while循环的一个测试条件。所以该表达式的末尾是一个顺序点。所以c++确保副作用(将guests+1) 在程序进入cout之前完成,然而使用后缀的格式,可以确保将guests同10进行比较后再将其值加1。

  y=(4+x++)+(6+x++);

  表达式4+x++不是一个完整的表达式,因此,c++不保证x的值在计算子表达式4+x++后立刻增加1;在这个例子中  红色部分是一个完整的表达式  其中的分号标示了顺序点,因此c++只保证程序执行到下一条语句之前,x的值将被递增两次。c++没有规定是在计算每个子表达式之后将x的值递增,还是在整个表达式计算完毕后才将x的值递增,有鉴于此,应该避免使用这样的表达式。

  c++11文档中,不再使用术语“顺序点”了,因为这个概念难以用于讨论多线程执行。相反,使用了术语“顺序”,它表示有些事件在其他事件前发生。这种描述方法并非要改变规则,而旨在更清晰第描述多线程编程。

10.前缀格式和后缀格式

  显然,如果变量被用于某些目的(如用作函数参数或给变量赋值),使用前缀格式和后缀格式的结果将是不同的。然而,如果递增表达式的值没有被使用,情况会变得不同例如

  x++;

  ++x;

  for(n=lim;n>0;--n)

  for(n=lim;n>0;n--)

  从逻辑上说,在上述两种情形下,使用前缀格式和后缀格式没有任何区别。表达式的值未被使用,因此只存在副作用。

  在上面的例子中使用这些运算符的表达式为完整表达式,因此将x+1和n-1的副作用将在程序进入下一步之前完成,所以前缀与后缀格式的最终效果相同。

  前缀与后缀的区别2:它们的执行速度有略微的差别   虽然对程序的行为没有影响

    c++允许您针对类定义这些运算符,在这种情况下,用户这样定义前缀函数:将值加1然后返回结果。

                                  后缀版本:首先复制一个副本,将其加1  然后将复制的副本返回

    显然前缀的效率要比后缀版本高。

  总之,对于内置类型,采用哪种格式不会有差别;但是对于用户定义的类型,如果有用户定义的递增和递减运算符,则前缀格式的效率更高。

11.递增/递减运算符和指针

  可以将递增运算符用于指针和基本变量。将递增运算符用于指针时,将把指针的值增加其指向的数据类型占用的字节数,这种规则适用于对指针递增和递减。

  double arr[5]={21.1,32.8,23.4,45.2,37.4}

  double * pt=arr;    //pt points to arr[0],i.e. to 21.1

  ++pt;//指向arr[1]    i.e.  to   32.8

  也可以结合使用这些运算符来修改指针指向的值。将*和++同时用于指针时提出了这样的问题:

    将什么解除引用,将什么递增?

  取决于运算符的位置和优先级。

  1.前缀递增、后缀递减和解除引用运算符的优先级相同,以从右到左的方式进行结合。

  2.后缀递增和后缀递减的优先级相同,但是比前缀运算符的优先级高,这两个运算符以从左到右的方式进行结合。

  例如:*++pt的含义  先将++应用于pt   然后再将*应用于递增后的pt      pt的指向已经变化

    ++*pt 先取得pt指向的值,然后再将这个值加1        pt的指向没有变化

    (*pt)++    pt指针先解除引用  得到值  然后再加1   原来为32.8   之后的值为32.8+1=33.8     pt的指向未变

    x=*pt++;  后缀的运算符++的优先级更高,意味着将运算符用于pt   而不是*pt,因此对指针递增。然后后缀运算符意味着将对原来的地址&arr[2],而不是递增后的新地址解除引用,因此*pt++的值为arr[2],

  

#include <iostream>
int main(){
using namespace std;
int word[10]={1,2,3,4,5,6,7,8,9,10};//定义数组
int *pt= word; //定义指针指向的数组
int x;
for(int i=0;i<10;i++)
cout<<word[i]<<" , ";
cout<<endl;
cout<<&word<<endl;//输出数组首地址
cout<<pt<<endl; //指针的方式输出数组首地址
for(int i=0;i<10;i++)
{
cout<<"dizhi= "<<&(word[i])<<" "; //输出数组的每个元素的地址
cout<<"*pt++= "<<*pt++<<" "; //输出指针形式的每个元素
cout<<"&pt=="<<pt<<endl; //地址的变化 关键是查看这两行
}
return 0;

}

12.组合赋值运算符

   i=i+by  更新循环计数

   i+=by

   +=运算符将两个操作数相加,并将结果赋值给左边的操作数。这意味着左边的操作数必须能够被赋值,如变量、数组元素、结构成员或者通过对指针解除引用来标识的数据:

 示例:

  k+=3;        左边可以被赋值  可以k=k+3

  int * pa= new int[10]; pa指向数组int 

  pa[4]=12;      

  pa+=2;       pa指向  pa[4]

  34+=10;       非法   34不是一个变量   不可以被赋值

    

13.复合语句     (语句块)

  for循环中的循环体  就是复合语句 由大括号括进

  外部语句块中定义的变量在内部语句块中也定义的变量 情况如下:在声明位置到内部语句块结束的范围之内,新变量将隐藏旧变量,然后旧变量再次可见如下

  #inlcude <iostream>

  {  using namespace std;

    int x=20;          //原始的变量

    {          //代码块开始

      cout<<x<<endl;    //使用原始的变量   20

      int x=100;        //定义新的变量

      cout<<x<<endl;      //使用新的变量  100

    }

    cout<<x<<endl;      //使用旧的变量  20

    return 0;

  }

14.其他语法技巧----逗号运算符

  语句块允许把两条或更多条语句放到按c++句法只能放一条语句的地方。

  逗号运算符对表达式完成同样的任务,允许将两个表达式放到c++句法只允许放一个表达式的地方。

  例如循环控制部分的更新部分  只允许这里包含一个表达式,但是有i和j两个变量,想同时变化  此时就需要逗号运算符将两个表达式合并成一个

    ++j,--i      这两个表达式合并成一个

  逗号并不总是逗号运算符  例如   声明中的逗号将变量列表中相邻的名称分开:

    int i,j;

  for(j=0,i=word.size()-1;j<i;--i,++j)

  逗号运算符另外的特性:确保先进算第一个表达式,然后计算第二个表达式(逗号运算符是一个顺序点)

    i=20,j=2*i

  另外逗号表达式的值为上面第二部分的值  上面为40,因为j=2*i的值为40.  逗号的运算符的优先级是最低的。

  data=17,24  被解释为(data=17),24      结果为17         24不起作用

  data=(17,24)  结果为24    因为括号的优先级最高   从右向左结合   括号中的值为24(逗号右侧的表达式的值)   所以data的值最后为24

15 关系表达式

  

  关系运算符的优先级比算数运算符的低。  注意将bool的值提升为int后,3>y要么是1  或者是0   所以表达式有效

  for(x=1;y!=x;++x)    有效

  for(cin>>x;x==0;cin>>x)

  (x+3)>(y-2)       有效

  x+(3>y)-2     有效

16赋值 比较和可能犯的错误

    ==    和=  是有区别的  前者是判断 关系表达式        后者是赋值表达式

  如果用后者则当在for循环中    测试部分将是一个复制表达式 ,而不是一个关系表达式,此时虽然代码仍然有效,但是赋值表达式 为非0    则结果为true    。

  示例:测试数组中的分数

  for(i=0;scores[i]==20;i++)    测试成绩是否为20   原来的数组值不变

  for(i=0;socres[i]=20;i++)  测试成绩是否为20  但是原来的数组值已经都赋值为20了

    1.赋值表达式 为非0   则  始终为true      2.实际上修改的数组的元素的数据      3. 表达式一直未true   所以程序再到达数组结尾后,仍然在不断地修改数据

  但是代码在语法上是正确的,因此编译器不会将其视为错误

  对于c++类,应该设计一种保护数组类型来防止越界的错误。循环需要测试数组的值和索引的值。

17.c风格字符串的比较

 数组名代表数组首地址

 引号括起来的字符串常量也是其地址

  所以word=="mate"   并不是判断两个字符串是否相同,而是查看它们是否存储在相同的地址上   前者为数组    后者为字符串常量 ,所以两个地址一定是不同的,即使word数组中的数据是mate字符串。

  由于c++将c风格的字符串视为地址,所以使用关系运算符来比较它们,将不会得到满意的结果。

  相反应该使用c风格字符串库中的strcmp()函数来比较。  此函数接收两个字符串地址作为参数,参数可以使指针、字符串常量或者字符数组名,如果两个字符串相同,则函数返回0;如果第一个字符串的字母顺序排在第二个字符串之前,则strcmp()函数将会返回一个负值,相反则返回一个正值。

  还有按照“系统排列顺序”比“字母顺序”更加准确,此时意味着字符是针具字符的系统编码来进行比较的ascii码。所有的大写字母的编码都比小写字母要小,所以按顺序排列,大写位于小写之前。

  另一个方面 c字符串通过结尾的空值字符定义,而不是数组的长度

  所以char big[80]="daffy"    与   char little[10]="daffy"    字符串是相同的

  不能用关系运算符来比较字符串,但是可以用来比较字符,因为字符实际上是整型   所以可以用下面的代码显示字母表中的字符

  for(ch='a';ch<='z';ch++)

    cout<<ch;

 示例:

  

#include <iostream>
#include <cstring>

int main(){

using namespace std;
char word[5]="?ate"; //定义字符数组
for(char ch='a';strcmp(word,"mate");ch++)//判断字符数组中的首元素是否为m 为循环的条件 如果不是则返回负值 非0 所以为真,直到 ch=l时 ch++=m strcmp返回0 =false 结束循环
{
cout<<word<<endl;
word[0]=ch;//修改字符数组的首元素为ch
}
cout<<"after loop ends,word is "<<word<<endl;
return 0;
}

    

检测相等或排列顺序

  可以使用strcmp()来测试c风格字符串是否相等  排列顺序,如果str1和str2相等,则下面的表达式为true  

    strcmp(s1,s2)==0  s1 s2相等   函数返回0    0==0   则为true

    strcmp(s1,s2)!=0  s1  s2相等    ..       0!=0  、为false

      strcmp(s1,s2)    s1   s2相等返回0  为false

    strcmp(s1,s2)<0    s1在s2的前面为true     s1-s2<0

  所以根据测试的条件    strcmp()可以扮演==     !=     <和>运算符的角色

  char实际上是整型    修改char 类型的操作实际上是修改存储在变量中的整数的编码   另,使用数组索引可使修改字符串中的字符更为简单

18.比较string类字符串

  使用string类的字符串   比使用c风格字符串要简单   类设计可以使用关系运算符进行比较。

  原因是因为类函数重载(重新定义)了这些运算符

  示例:

  

#include <iostream>
#include <string>   //头文件不同

int main(){

using namespace std;
string word="?ate"; //定义字符串对象
for(char ch='a';word!="mate";ch++)//循环
{
cout<<word<<endl;
word[0]=ch;//修改字符数组的首元素为ch
}
cout<<"after loop ends,word is "<<word<<endl;
return 0;
}

程序说明:

  word!="mate"      使用了关系运算符    左边是一个string类的对象,右边是一个c风格的字符串

  string类重载运算符!=的方式可以使用的条件:

  1.至少有一个操作数为string对象,另一个操作数可以是string对象,也可以是c风格字符串

  2.此循环不是计数循环   并不对语句块执行指定的次数,相反   它根据实际的情况来确定是否停止。  此种情况通常使用while循环

二  while循环

  while循环是没有初始化和更新部分的for循环    它只有测试条件和循环体

  while (test-condition)

    body

  首先程序计算圆括号内测试条件表达式    如果表达式为true   则执行body     如果表达式维false 则不执行

  body代码中必须有完成某种影响测试条件表达式的操作。 例如  循环可以将测试条件中的变量加1或者从键盘输入读取一个新值。

  while循环也是一种入口条件循环。因此  测试条件一开始便为false   则程序将不会执行循环体。

  while(name[i]!='\0')

  测试数组中特定的字符是不是空值字符。

  1.for与while

  本质上两者是相同的

  for(init-expression;test-expression;update-expression)

  { statement(s) }

  可以写成如下的形式

  init-expression;

  while(test-expression)

  {

    statement(s)

    update-expression;

  }

  同样的

  while(test-expression)   body

  可以写成

  for(;test-expression;)

    body

  for循环需要3个表达式   技术上说,需要1条后面跟两个表达式的语句。不过它们可以是空表达式,只有两个分好是必须的。

  另外for循环中  省略测试表达式时 测试结果将是true   因此  for(;;) body    会一直执行下去

  差别:

  1.for  没有测试条件   则条件为true

  2.for中可以使用初始化语句声明一个局部变量   但是while不能这样做

  3.如果循环体中包含continue语句  情况将会有所不同。

  通常如果将初始值,终止值和更新计数器的、都放在同一个地方,那么要使用for循环格式

  当在无法预先知道循环将执行的次数时,程序员常使用while循环

  当使用循环时   要注意下面的几条原则

    1.指定循环终止的条件

    2.在首次测试之前初始化条件

    3.在条件被再次测试之前更新条件

  错误的标点符号

    for循环和while循环都由用括号括起的表达式和后面的循环体(包含一条语句)组成。

    语句块 由大括号括起   分号结束语句

 

2.等待一段时间,编写延时循环

  clock()函数     返回程序开始执行后所用的系统时间

#include <iostream>

#include <ctime>//describes clock() function, clock_t loop
int main()
{
using namespace std;
cout<<"enter the delay time,in seconds: ";
float secs;
cin>>secs;
clock_t delay=secs * CLOCKS_PER_SEC;//转化成clock ticks的形式 始终滴答形式
cout<<"starting\a\n";
clock_t start=clock();
while(clock()-start<delay)//等待时间 直到为假时跳出循环
;            //    空操作     空语句
cout<<"done \a\n";
return 0;
}

头文件ctime  (time.h)    提供了这些问题的解决方案。首先,定义了一个符号常量--CLOCKS_PER_SEC  该常量等于每秒钟包含的系统时间单位数。因此,将系统时间除以这个值,可以得到秒数。或者将秒数乘以CLOCK_PER_SEC  可以得到以系统时间单位为单位的时间。其次,ctime将clock_t作为clock()返回类型的别名  ,这意味着可以将变量声明为clock_t类型    编译器将把它转换为long 、unxigned int 或者适合系统的其他类型。

  类型的别名

c++为类型建立别名的方式有两种。

  一种是使用预处理器:

  #define BYTE char

  这样预处理器将在编译程序时用char 替换所有的BYTE,从而使BYTE称为char的别名

  第二种是使用c++和c的关键字typedef来创建别名。例如要讲byte作为char的别名,

   typedef char byte;

  通用格式:typedef typeName aliasName;     aliasName为别名

  两种比较

    示例:

    #define FLOAT_POINTER float *

    FLOAT_POINTER pa,pb;

  预处理器置换将该声明转换为如下的形式

    float *pa,pb;     此时   pa为指针   而pb不是指针 只是float类型

  typedef   方法不会有这样的问题    它能够处理更加复杂的类型别名     最佳选择,有时候是唯一的选择

    不会创建新的类型  仅仅是这种类型的另一个名称

三 do-while循环

    前面两种是入口条件的循环  ,而这种事出口条件的循环    意味着这种循环将首先执行循环体,然后再判定测试表达式,决定是否应该继续执行循环。如果条件为false  则循环终止。否则进入新的执行和测试。   此时循环体至少要循环一次。

  do

    body

  while(test-expression);

  

通常情况下  入口条件循环要比出口条件循环好   因为入口条件循环在循环开始之前对条件进行检查,但是有时候需要do while更加合理  例如请求用户输入时,程序必须先获得输入,然后对它进行测试

//dowhile.cpp
#include <iostream>
int main(){
using namespace std;
int n;
cout<<"enter number in the range 1-10 to find"<<endl;
cout<<"my favorite number\n";
do{
cin>>n;//接收键盘的输入
}
while (n!=7);//当 输入的值不等于7时 判别式为真值 继续循环 等于7就退出循环 进行下面的语句的执行
cout<<"yes,7 is my favorite.\n";
return 0;
}

奇特的for循环

int i=0;

for(;;)

  {

    i++;

    cout<<i<<endl;

    if(30>=i) break;

  }

或者另外一种变体

  int i=0;

  for(;;i++)

  {

    if(30>=i) break;

    //do something

  }

  上述的代码基于这样的一个事实:for循环中的空测试条件被视为true。  不利于阅读  在do while中更好的表达它们的意思

  int i=0;

  do{

    i++;

    //do something

  }while(30>=i);

  第二个例子的转化

    while(i<30)

    {

      //do something;

      i++;

    }

4.基于范围的for循环(c++11)

  c++11新增的一种循环

  基于范围  range-based的for循环:对数组(或容器类,如vector和array)的每个元素执行相同的操作

  double prices[5]={4.99,10.99,6.87,7.99,8.49};

  for(double x:prices)

    cout<<x<<std::endl;

  其中x最初表示数组prices的第一个元素。显示第一个元素后,不断执行循环,而x依次表示数组的其他元素。因此,上述代码显示全部5个元素,每个元素占据一行。总之,该循环显示数组中的每个值,要修改数组的元素,需要使用不同的循环变量的语法:

  for(double &x:prices)

    x=x*0.80;

  示例1

#include <iostream>
int main(){
using namespace std;
double prices[5]={4.99,10.99,6.87,7.99,8.49};
for(double x:prices)  //x最初表示首元素   之后x依次表示数组的其他元素
cout<<x<<endl;

return 0;
}

  将上面的代码中的循环更改为for(double &x:prices)

            x=x*0.80;   //价格变为8折

  &表明x是一个引用变量(而不是取地址),这种声明让接下来的代码能够修改数组的内容,而第一种语法不能。

  也可以使用基于范围的for循环

  for(int x:{3,5,2,8,6})

     cout<<x<<" ";

5.循环和文本输入

  系统中最常见的   最重要的任务:逐字符地读取来自文件或键盘的文本。

  例如想编写一个能够计算输入中的字符数,行数和字数的程序,c++与c在i/o工具不尽相同    cin对象支持3中不同模式的单字符输入,其用户接口各部相同。

  1.使用原始的cin进行输入

  如果程序要使用循环来读取来自键盘的文本输入,则必须要知道何时停止读取。一种方法就是选择某个字符  作为哨兵字符  将其作为停止标记。

  示例:

//testin1.cpp
#include <iostream>
int main(){
using namespace std;
char ch;
int count=0;
cout<<"enter characters;enter # to quit:\n";
cin>>ch;
while(ch!='#')//以#字符为终止符     如果输入中不是#  就继续执行循环
{
cout<<ch;
++count;
cin>>ch;//读取下一个字符  很重要   如果没有此条,就会重复处理第一个字符
}
cout<<endl<<count<<"characters read\n";
return 0;
}

程序说明

  此循环遵循了前面的循环原则,结束条件为最后读取的字符为#,在循环之前读取一个字符进行初始化,而通过循环体结尾读取下一个字符进行更新。

  在输入时  可以输入空格和回车   但是在输出时,cin会将输入的字符中的空格和换行符忽略。所以输入的空格或者回车符没有被回显,也没有被包括在计数内。

  更加复杂的是,发送给cin的输入被缓冲,这意味着只有在用户按下回车键后,他输入的内容才会被发送给程序。这就是在运行该程序时,可以在#后面输入字符的原因。按下回车键后,这个字符序列讲被发送给程序,但是程序在遇到#字符后将结束对输入的处理。

 2.使用cinget(char)进行补救

  通常,逐个字符读取输入的程序需要检查每个字符,包括空格、制表符和换行符。cin所属的istream类(在iostream中定义)中包含一个能够满足这种要求的成员函数。

  cin.get(ch)读取输入中的下一个字符(即使它是空格),并将其赋值给变量ch。使用这个函数调用替换cin>>ch,可以修补上面程序的问题

  

//testin1.cpp
#include <iostream>
int main(){
using namespace std;
char ch;
int count=0;
cout<<"enter characters;enter # to quit:\n";
cin.get(ch);  //会显示空格   制表符等其他的字符
while(ch!='#')
{
cout<<ch;
++count;
cin.get(ch);
}
cout<<endl<<count<<"characters read\n";
return 0;
}

程序说明:可能的误解:c语言中    cin.get(ch)调用将一个值放在ch变量中,这意味着将修改该变量的值

  c中要修改变量的值,必须将变量的地址传递给函数。

  在上例中  cin.get()  传递的是ch   而不是&ch      在c中此代码无效,但是在c++中有效,只要函数将参数声明为引用即可。

  引用在c++中对比c新增的一种类型。头文件iostream  将cin.get(ch)的参数声明为引用类型,因此该函数可以修改其参数的值。

3.使用哪一个cin.get()

  char name[arsize];

  ...

  cout<<"enter your name:\n";

  cin.get(name,arsize).get();//相当于两个函数的调用。  cin.get(name,arsize);cin.get();

  cin.get  的一个版本接收两个参数   :数组名   (地址)和arsize   (int 类型的整数)    数组名的类型为 char *

  cin.get()    不接收任何参数

  cin.get(ch)  只有一个ch参数

  在c中不可想象    参数数量不够 或过多  会造成函数的出错

  但是在c++中,这种被称为函数重载的oop特性    允许重载创建多个同名函数,条件是它们的参数列表不同。如果c++中使用cin.get(name,arsize),则编译器将找到使用char* 和int作为参数的cin.get()版本。  如果没有参数,则使用无参的版本。

4.文件尾条件

  如果正常的文本中#正常的字符  那么用#来用作结尾符不会合适

  如果输入来自于文件,可以使用一种功能更加强大的技术    EOF   检测文件尾。

  c++输入工具和操作系统协同工作,来检测文件尾并将这种信息告知程序。

  cin和键盘输入  相关的地方是重定向 <  或者 >符号

  操作系统允许通过键盘来模拟文件尾条件。

    在unix中   可以在行首按下CTRL+D来实现。

    在windows命令提示符下,可以在任意位置按CTRL+Z和enter  

很多的pc编程环境都讲CTRL+z视为模拟的EOF

  检测EOF的方法原理:

  检测EOF后,cin将两位(eofbit和failbit)都设置为1;

  查看eofbit是否被设置   使用成员eof()函数

  如果检测到EOF,则cin.eof()将返回bool值true,否则返回false。

  同样,如果eofbit或者fialbit被设置为1,则fail()成员函数返回true,否则返回false。

  eof()和fial()方法报告最近的读取结果。因此应将cin.eof() 或者cn.fail()测试放在读取后

   

#include <iostream>

int main()
{
using namespace std;
char ch;
int count=0;
cin.get(ch);
while(cin.fail()==false)//判断cin.fail()的值 是否被设置,如果ctrl+z 和enter 已经按下 则返回true 条件不符合,跳出循环
{
cout<<ch;
++count;
cin.get(ch);
}
cout<<endl<<count<<"characters read\n";
return 0;
}

 程序说明:windows 7系统上运行该程序,因此可以按下ctrl+z和回车键来模拟EOF条件,在unix和类unix(包括linux)等系统张,用户可以按ctrl+z组合键将程序挂起,而命令fg恢复程序的执行。

  通过使用重定向,可以用此程序来显示文本文件。并报告它包含的字符数   主要在unix中测似乎

  1.EOF结束输入

  cin方法检测到EOF时,将设置cin对象中一个指示EOF条件的标记。设置这个标记后,cin将不读取输入,再次调用cin也不管用。

  上面的情况 是对文件的输入有道理,因为程序不应该读取超出文件尾的内容

  对于键盘的输入,有可能使用模拟EOF来结束循环,但是稍后还要读取其他输入。

  cin.clear()方法用来清除EOF标记,使输入继续进行。

  在有些系统中CTRL+Z实际上会结束输入和输出。    而cin.clear()将无法恢复输入和输出。

  2.常见的字符输入做法

  每次读取一个字符,直到遇到EOF的输入循环的基本设计如下:

  cin.get(ch);

  while(cin.fail()==false)  //这里可以用!逻辑运算符  去掉等号    while(!cin.fail())

  {

    ...

    cin.get(ch);

  }

  方法cin.get(char)的返回值是一个cin对象。然而,istream类提供了一个可将istream对象转换为bool值的函数;

  当cin出现在需要bool值的地方(如在while循环的测试条件中)时,该转换函数将被调用。另外,如果最后一次读取成功了,则转换得到的bool值为true;否则为false。

  意味着可以改写为

  while(cin)//while input is successful

  这样比!cin.fail()或者!cin.eof()更通用,因为它可以检测到其他失败的原因,如磁盘故障

  最后,由于cin.get(char)的返回值为cin,因此可以将循环精简成这种格式:

  while(cin.get(sh))

  {

    ...

  }

  这样 cin.get(char)只被调用了一次,而不是两次:循环前一次、循环结束后一次。为判断循环测试条件,程序必须首先调用cin.get(ch).如果成功,则将值放入ch中。然后,程序获得函数调用的返回值,即cin。之后对cin进行bool转换,如果输入成功,则结果为true,否则为false。三条指导原则   全部被放在循环条件中。

 5.另一个cin.get()版本

  旧式的c代码  i/o函数    getchar()和putchar()函数   仍然适用,只要像c语言中那样包含头文件stdio.h或者新的cstdio即可。

  也可以使用istream和ostream类中类似功能的成员函数。

  cin.get()    不接受任何参数   会返回输入中的下一个字符

  ch=cin.get();

  与getchar()相似,将字符编码作为int类型返回;

  cn.get(ch)则会返回一个对象,而不是读取的字符。

  cout.put(ch) 显示字符   此函数类似于c中的putchar()     只不过参数类型为char   而不是int

    put()最初只有一个原型 put(char)    ,可以传递一个int参数给它,该参数将被强制转换为char。c++标准还要求只有一个原型。然后c++的有些实现都提供了3个原型

    参数分别为char      signed char       unsigned char   。这些实现中如果给put()传递一个int参数将导致错误消息,因为转换int的方式不止一种,如果使用显式强制类型转换的原型(cin.put(char(ch)))可以使用int参数

  成功使用cin.get()     需要了解EOF条件。

  当函数到达EOF时,将没有可以返回的字符。相反,cin.get()将返回一个用符号常量EOF表示的特殊值。该常量是在头文件iostream中定义的。EOF值必须不同于任何有效的字符值,以便程序不会将EOF于常规字符混淆。通常EOF被定义为值-1,因为没有ascii码为-1的字符,但并不需要知道它实际的值,在程序中使用EOF即可。

  示例:

  char ch;          替换为   int ch;

  cin.get(ch);         替换为 ch=cin.get();

  wihile(cin.fail()==false)  替换为