《C++ Primer Plus》阅读笔记(第5章)

笔记不定期更新,读到哪里更到哪里:D

5.1 for循环
使用方法:
int i;//举例
for(i=0;i<5;i++)    //控制部分,注意用分号分隔每个部分,结尾不需要分号
    cout<<"哼哼啊啊啊啊啊";        //循环体注意缩进

格式:
    for(初始化,循环测试,循环更新)
    循环测试部分的参数会被强制转换为bool类型,非0转为true,0转为false(如果通过这种方式转换为ture将会导致死循环)
    也就是说,循环测试部分的参数就像汇编语言中的cmp和je,先比较,比较完得出参数(个人理解,这部分可以划掉)。
    
    循环将在循环测试部分变为0后停止。

1.表达式和语句
    表达式的概念:任何值或任何有效的值和运算符的组合都是表达式
    在C++中,每个表达式都有值(由此可判断是不是表达式),如:
        22+22 是值为44的表达式。

    像x<y这样的关系表达式将被判定为bool值,如:
        int x;
        x=100;
        cout<<(x<3);    //结果是0,因为cout在显示bool值前会将其转换为int类型

    为了判定:表达式x=100,C++将会把100赋给x,
    当判定一个表达式的值这种操作改变了内存中的数据的值时,我们说表达式有副作用。
    我们在使用这种表达式时把赋值当成主要目的,但从C++的角度来看,判定才是主要目的。

    判定x+15并没有副作用,因为它将计算出一个新的值,因此,一般有副作用的表达式都是赋值表达式。

    将表达式转换成语句的方式:加上分号
    如:
          x=10        //是一个表达式
          x=10;    //是一个语句(表达式语句)

    编译器允许无意义的语句,但可能会直接跳过执行。

2.非表达式和语句
    任何表达式加上分号都能成为语句,但语句去掉分号不一定是表达式,如:
        int toad;         //它没有值,因此去掉分号也不是表达式
    也并非所有语句都满足语句=表达式+分号,如for语句。
        
    不是表达式就不能赋值和被赋值(因为不是表达式就没有值)

3.修改规则:
    可以这么做:
        for(int i=0;i<5;i++)     
    不要被书中的格式误导,不可以直接省略分号!!!(编程得来的经验)

    在循环初始化部分声明的变量在循环结束后就会消失(但某些老式实现会保留变量)

5.1.2 
    阶乘的方法:
           x*(x-1)!    //零阶乘(0!)被定义为1
    
     程序5.4:
    由于前两个阶乘的结果都是1,所以在循环开始前直接为数组进行赋值。

    表达式i<ArSize导致循环在数组索引到ArSize-1的地方停止

5.1.3
    可以通过修改循环更新表达式来改变步长。
    如:    
        int by=2;
        for(int i=0,i<5,i=i+by)    //注意赋值表达式的使用,i+by是无意义的


重点:循环更新表达式可以是任何有效的表达式

5.1.4
程序5.6:
    循环初始化中int i=word.size() - 1   //这里的字符串是使用cin得来的,结尾包含空字符,所以最后-1
    该程序使用了关系运算符大于或等于(>=)

5.1.5
    粗略地讲,a++意味着使用a的当前值进行计算,然后再将a的值+1
    ++a则是先把a+1,再计算。

    注意:不要在同一个语句内对同一个值递增或递减多次

5.1.6
    副作用指的是在计算表达式时对某些东西进行了修改,
    顺序点是程序执行过程中的一个点,在这里确保所有副作用都已完成。

    语句中的每个分号都是顺序点,任何完整的表达式末尾也是顺序点。
    
    y=(4+x++)+(6+x++);//分号是顺序点,因此这条语句在结束时才完成修改

5.1.7
    如果递增运算符仅进行计算,没有被使用,则使用前缀格式和后缀格式没有任何区别,
    但在用户定义的类中,前缀版本的效率比后缀版本的高。

5.1.8
    将*和++同时用于指针时:
    前缀递增递减和解除引用运算符的优先级相同,从右到左结合。
    后缀递增递减的优先级比前缀递增递减高,从左到右结合。

5.1.9 组合赋值运算符
    格式:
    +=、-=、*=、/=、%=
    含义:先将两个操作数进行计算,然后将得到的结果赋给左边的操作数

5.1.10
    可以在循环体内包含任意条语句,方法是用花括号构造一个代码块(代码块被视为一条语句)

    如果在代码块中声明变量,在代码块执行完毕后会释放该变量。

    如果内外部语句块中有两个名称相同的变量,则在内部语句块结束之前,新变量将代替旧变量。

5.1.11 逗号运算符
    逗号运算符允许两个表达式放到只允许放一个表达式的地方。

    在同一语句内,逗号只能有一种含义(列表分隔符或运算符),例如:
    for(int x=0,y=10; ......)不被允许,因为逗号已经是列表分割符了。

    逗号运算符还有另外两个特性:
        1.逗号运算符是一个顺序点
        2.逗号表达式的值是逗号右边的值

     编程测试结果:逗号表达式的值为最右边的值,无论有多少个逗号都是如此。

    在所有运算符中,逗号运算符的优先级是最低的,例如:
        x=7,240; //结果为7,240不起作用
    但这样的语句:
        x=(7,240);      //结果为240,原因为特性2

5.1.12 关系表达式
    关系运算符可以对数字进行比较,for语句中就是靠关系运算符得出ture和false,
    可以将它们用于:
        数字、字符、string类型。不能用于C风格字符串。

编程测试结果:对C风格字符串直接使用关系运算符不会报错,但无法得到想要的结果。

5.1.14
    因为C++将C风格字符串视为第一个字符的地址,因此无法使用==进行比较
    如果想要比较C风格字符串,需要使用strcmp()函数,该函数接受两个字符串地址作为函数,
    如果相同则返回0,如果第一个字符串的ASCII码比第二个字符串小,则返回负数,大则返回正数。
    (ASCII码中,大写字母的编码比小写字母小)

    这里顺便提一下之前学过的知识:C风格字符串的结尾是通过空字符而非数组长度确定的。

    可以使用strcmp()函数在字符串相等时返回0(false)的特性用它来进行循环比较。

    strcmp()的用法和汇编语言cmp有点像,如:
        int str1=1,str2=0;
        strcmp(str1,str2)>0;    //将返回true

编程测试结果:strcmp仅能用来比较字符串,可以是数组、C风格字符串,但不能是string,因为

strcmp的参数标准格式为const char *,无法从string转换为char *,

而对数组名或字符串使用时,将会比较整个字符串,而非其元素首地址。

5.1.15
    可以将关系运算符(如!=)用于string对象。例如:
        word !="mate"
    同时,可以使用数组表示法表示string对象的内容(老知识)

5.2 while循环
    while是只有测试条件和循环体的for循环。
    
        while(word[i] !='\0')
    它的循环体必须修改i的值,否则将陷入死循环。
    可以改成这样:
        while(word[i])
    它的效果不变,因为当i为空字符时,其编码为0,也就是false。
        
重点:不同于C风格字符串,string类不使用空字符来标记字符串末尾。

5.2.1
    省略for循环的测试表达式将会导致死循环。

    通常,程序员使用for循环,因为它能将所有相关的信息放到一个地方,
    而选择在无法预先知道循环的次数时使用while循环。

    记住,不要往循环的括号后面加分号,因为分号用来表示语句结束。

5.2.2
    为了实现延时,可以使用循环,但是更好的方式是使用系统时间完成这个任务。

    函数clock()可以完成这个任务,它返回开始执行后的系统时间,
    为了控制其返回的时间单位和类型,需要使用头文件<ctime>中的定义。
        它定义了一个符号常量:CLOCKS_PER_SEC,该常量为每秒钟包含的系统时间数,
        将系统时间除以它将得到秒数,将秒数乘以它也能得到系统时间。

        在ctime中定义了clock的返回类型(系统时间的类型)的别名clock_t,
        使用clock_t创建变量将会被转换为头文件定义的系统时间的类型。
        
        clock_t 变量名=输入的秒数*CLOCKS_PER_SEC;//可以将输入的秒数转换成系统时间
        
    程序5.14解读:
        clock_t delay =secs * CLOCKS+PER_SEC;//同上
        clock_t start=clock();    //获得循环开始前的时间
        while(clock() - start<delay);   //用循环开始后的时间减去开始前的时间,算出已经循环了多久

    类型别名:
        C++为类型建立别名的方式有两种:
            第一种:使用预处理器:#define 替换类型名 被替换的类型名
            第二种:使用关键字:typedef 被替换的类型名 替换类型名
            第二种方式同样可用于某种类型的指针,如:
                typedef char * byte_pointer;  //用byte_pointer代替char类型指针

            使用tapedef进行置换可以在声明指针变量时无视必须在每个指针前都加上*的规则,如:
                typedef type * char;
                type pa,pb;

5.3 do while循环
    do while循环不同于前面的两种循环是入口条件循环,它是出口条件循环,两者的差别是:
    入口条件循环有可能一次都不会执行循环体内语句,出口条件循环则至少会执行一次循环体内语句。

    do while循环多用于比较用户的输入。

5.4 基于范围的for循环(C++11)
    这种循环简化了对数组或容器(模板)类反复执行同样的操作的循环,如下所示:

        double prices[5]={4.99,10.99,6,87,7,99,8,49};
        for(double x : prices)     //冒号后面应为元素(使用{})或可代表元素的对象名(如数组名和指针)
            cout<<x;         //在这里,x代表prices的第一个元素

    如果要用这种循环修改数组的内容,则需要使用引用变量(第8章再谈具体的):
        for(double &x : prices)

5.5 循环和文本输入

5.5.1 cin进行输入
    cin将会忽略换行符和空格,而且更为复杂的是,发送给cin的输入将会被缓冲,
    也就是说,只有用户按下回车后,内容才会被发给程序。
    (这个地方建议自己去编程理解)

5.5.2
    这个地方就不多说了,使用cin.get()虽然能读入空格,但是输入依旧会被缓冲。
    引用是C++新增的一种类型,头文件iostream将cin.get(ch)的参数声明为引用类型(这段不重要)。

5.5.3
    C++中通过函数重载,可以声明多参数列表不同的同名函数(不同的函数版本)。
    剩下的依旧是在讲C++和C语言的不同之处,就不多说了。
    
5.5.4 文件尾条件
    前面的程序表明,使用某个符号表示输入结束很难令人满意(因为输入将被缓冲)
    如果输入来自文件,则可以使用一种技术:检测文件尾(EOF)

    很多操作系统都允许用文件替换键盘输入(重定向),
    例如有一个名为    gofish.exe的可执行程序和一个名为fishtale的文本文件,则可以:
        gofish<fishtale        //在命令提示符中输入这行命令,<是命令提示符模式的重定向运算符

重点:有些C++实现允许通过键盘来模拟文件尾条件,大多为使用Ctrl+Z(我使用的是Microsoft Visual)
    因此应在行首按下Ctrl+Z随后按下回车键。

    检测到EOF后,cin将两位(eobit和failbit)都设置为1,因此有两种方法可检测EOF是否被检测到:
        如果检测到EOF,则函数cin.eof()返回ture,否则将返回false    
        如果eobit或failbit位被设置为1,则函数cin.fail()返回ture,否则返回false

    注意,cin.eof和cin.fail在事后报告,因此应在读取后再使用它们。

    

fail()相较于eof()可用于更多的实现中,因此程序5.18中使用了fail(),
             它使用cin.fail()检测EOF是否被检测到,如果结果为ture则循环检测部分为false(0),循环                 体不执行。

    通过重定向,也可以该程序显示文本文件(暂时没那么重要)

    1.EOF结束输入
        前面说过,检测到EOF后,cin将会设置eofbit,设置eofbit后,cin将不读取输入
        然而在程序中可能会出现使用模拟EOF结束循环,随后继续输入的例子,
        这种情况可以使用cin.clear来恢复输入(但是有些系统中按下Ctrl+Z无法使用cin.clear恢复)
        (我是用的是Microsoft Visual,可以使用cin.clear恢复输入)

    2.常见的字符输入做法
        程序5.18中使用了这种设计,但还可以简化:
            while(!cin.fail())    //取反(!)是第6章内容,可以将ture切换成false或将false切换成ture

     一,cin.get(ch)的返回值是一个cin对象。

     二,当cin出现在需要bool值的地方(如循环测试部分),cin将会被转换为bool值。

     三,如果循环终止前的最后一次读取成功了,则cin转换得来的bool值为ture,否则为false。

        因为以上三点,可以将上面的while循环测试部分改写成:
            while (cin)    //这比上面的两种格式更通用,因为它能检测到其他失败原因

        这里可能对新手而言不太好理解,我们复习一下函数的返回值:
            循环体中的cin.get(ch)将会把返回值返回给cin对象,然后再回到循环测试部分。
        
        也可以使用这种格式:while(cin.get(ch)),因为cin.get(ch)同样也会返回值给cin,
        这种情况下cin.get(ch)只会在循环开始时被调用一次,
      而非上面几种方式的开始前调用一次,循环开始时仅仅检测不调用,循环时调用一次(共两次)

这里我把我测试时用的程序附上,可以根据本节内容进行修改,测试不同的循环测试方式。      

#include<iostream>
using namespace std;
int main()
{
	cout << "请输入字符:" << endl;
	char ch;
	int count = 0;
	while (cin.get(ch))
	{
		++count;
		cout << ch;
	}
	cout << "字符数为:" << count << endl;
	cin.clear();
	cout << "请继续输入以测试输入是否恢复:";
	char b;
	cin >> b;
	cout << b;
	return 0;
}

5.5.5
    C语言中的输入/输出函数getchar()和putchar()在C++中依旧适用,但是要包含头文件<cstdio>
    不接受任何函参数的cin.get()仅接受一个字符,工作方式与getchar()类似:
        ch=cin.get();    
    这样的函数将读取的字符(编码)以int类型值返回,
    而有参数的cin.get()和cin>>ch返回的是一个cin对象而非读取的字符

重点:cin是C++的标准输入流,其本身是一个对象而非函数,并不存在返回值,
      应该关注的是重载运算符(如>>),是它们决定返回了什么,
      一般情况下,返回值为cin本身。
    
    同样,也可以使用cout.put函数,工作方式类似于putchar,输出一个字符,如:
        cout.put(ch);
注意:如果cout.put()的参数为int类型而非char,需要显式地使用强制类型转换,否则将发生错误。

    使用cin.get()到达EOF时,将会返回一个用符号常量EOF表示的值,无需关心这个常量的实际值,
    只需知道如何使用这个常量便可。
        使用方法例如:
            ch=cin.get();    //这里使用无参数的cin.get()
            while(ch!=EOF)    //这里使用了符号常量EOF
            {
                ch=cin.get();    //循环体中
            }    
    同样,也可以使用在循环开始时进行输入的格式
            while(ch=cin.get()!=EOF)    //值最终将会被赋给ch

总结:有字符参数的格式更适合对象方式,因为其返回值是istream类对象cin,这意味着可以拼接成员函数
      cin.get(ch1).get(ch2);    
重点:函数调用cin.get(ch1)返回一个cin对象,然后便可利用该对象调用get(ch2),
      这就是为什么可以使用拼接成员函数的根本原因。

    而不带参数的get()和put()的主要用处是替换getchar()和putchar(),
    我个人认为暂时用处不大。

5.6 嵌套循环与二位数组
    二维数组简介:一维数组可以看作一行数据,而二维数组可以看作一个既有行也有列的表格
    创建方法:
        int maxtemps[4][5]; //该数组每一个元素都是一个由5个整数组成的数组
    可以认为,第一个下标表示行,第二个下标表示列
        
    假设想要打印二维数组中的所有内容,可以用嵌套循环(一个循环内嵌一个循环),
    一个改变行,一个改变列(王爽的8086汇编里也用到过原理类似的循环)

5.6.1 初始化二维数组
    初始化二位十足的步骤和初始化一维数组原理相同,但在细节上有些不同:
    二维数组需要用到多个列表初始化,因此需要花括号括起来,每个列表之间用逗号分隔。

5.6.2 使用二维数组
    程序5.20
    这里的程序就不细讲了,把主要部分说一下:
        1,这个程序的循环部分真的和王爽8086汇编语言中的列表循环很像,
             循环初始化部分声明的变量city就像寄存器BX,用来指明要打印的元素。
        
        2,这里用了一个指针数组来指出字符串的地址,
        这个指针数组是一个包含了5个指向不同字符串的指针的数组,
        循环用到的指针数组名cities 代表的是指针数组中第一个元素的地址,
        打印时将连续打印直至空字符或数组结束。

        3,这个指针数组可用二维数组替换,但是指针数组存储的是5个指向字符串的指针的地址,
             而二位数组要存储全部字符串,因此使用指针数组更为经济(节省空间)

        4,使用二维字符数组存储字符串时,不必给每个字符串都套上花括号。

        5,可使用string对象代替以上的所有数组,初始化列表与用于显示字符串的代码与数组相同
             (显示时使用数组名方式进行显示)    
            在希望可以调整字符串长度的情况下,这种方式更为方便    。

  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值