《C++ Primer》1.4 控制流

1.4 控制流

1.4.1 while语句

while语句的形式为

while(condition)
    statement

while语句的执行过程是交替地检测condition条件执行关联的语句statement,直至condition为假时停止。所谓条件(condition)就是一个产生真或假的结果的表达式。

例如,

while(val <= 10) {
    sum += val;
    ++val;
}

其中条件中使用了小于等于运算符(<=)来比较val的当前值和10。

所谓的语句块,就是用花括号包围的零条或多条语句的序列。语句块是语句的一种,在任何要求使用语句的地方都可以使用语句块。在本例中,语句块的第一条语句使用了复合赋值运算符(+=)。将右侧的运算对象加到左侧运算对象上,将结果保存在左侧运算对象中。它本质上与一个加法结合一个赋值(assignment)是相同的:

sum = sum + val;

使用前缀递增运算符(++)。递增运算符将运算对象的值增加1。++val等价于val = val + 1。

prog1.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1;

    while(val <= 10) {
        sum += val;
        ++val;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    return 0;
}

prog2.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1;

    while(val <= 10) {
        sum += val;
        val = val + 1;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    return 0;
}

我们看一下,编译器对于独立语句++val和val = val + 1的处理有什么不同:

etc@ruc-etc:~/cpp/ch1/4$ g++ prog1.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ g++ prog2.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ ls
prog1  prog1.cpp  prog1.s  prog2.cpp  prog2.s
etc@ruc-etc:~/cpp/ch1/4$ diff prog1.s prog2.s 
1c1
< 	.file	"prog1.cpp"
---
> 	.file	"prog2.cpp"

由上述结果看出,编译器对独立语句++val和val = val + 1的处理并没有什么不同。同理,我们再测试一下val++和val += 1。

prog3.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1;

    while(val <= 10) {
        sum += val;
        val++;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    return 0;
}

prog4.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1;

    while(val <= 10) {
        sum += val;
        val += 1;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    return 0;
}

测试结果如下:

etc@ruc-etc:~/cpp/ch1/4$ g++ prog3.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ g++ prog4.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ diff prog3.s prog4.s 
1c1
< 	.file	"prog3.cpp"
---
> 	.file	"prog4.cpp"
etc@ruc-etc:~/cpp/ch1/4$ diff prog1.s prog3.s 
1c1
< 	.file	"prog1.cpp"
---
> 	.file	"prog3.cpp"

结论是当++val,val++,val = val + 1和 val += 1作为一条独立的语句存在时,当前编译器对它们的处理是相同的。

我们对程序做简单的修改后,如下:

prog1.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1, n;

    while(val <= 10) {
        sum += val;
        n = ++val;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    cout << "n : " << n << endl;

    return 0;
}

prog2.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1, n;

    while(val <= 10) {
        sum += val;
        n = val = val + 1;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    cout << "n : " << n << endl;

    return 0;
}

prog3.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1, n;

    while(val <= 10) {
        sum += val;
        n = val++; 
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    cout << "n : " << n << endl;

    return 0;
}

prog4.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1, n;

    while(val <= 10) {
        sum += val;
        n = val += 1;
    }

    cout << "Sum of 1 to 10 inclusive is "
         << sum << endl;

    cout << "n : " << n << endl;

    return 0;
}

测试结果如下:

etc@ruc-etc:~/cpp/ch1/4$ g++ prog1.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ g++ prog2.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ g++ prog3.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ g++ prog4.cpp -S
etc@ruc-etc:~/cpp/ch1/4$ diff prog1.s prog2.s 
1c1
< 	.file	"prog1.cpp"
---
> 	.file	"prog2.cpp"
etc@ruc-etc:~/cpp/ch1/4$ diff prog1.s prog3.s 
1c1
< 	.file	"prog1.cpp"
---
> 	.file	"prog3.cpp"
26d25
< 	addl	$1, 24(%esp)
28a28
> 	addl	$1, 24(%esp)
etc@ruc-etc:~/cpp/ch1/4$ diff prog1.s prog4.s 
1c1
< 	.file	"prog1.cpp"
---
> 	.file	"prog4.cpp"

我们发现,当++val,val = val + 1和val += 1作为右值时,当前编译器对它们的处理相同;val++作为右值的时候,当前编译器对其处理与上述三者不同。不难看出执行addl $1, 24(%esp)动作的时机不一样。我们不妨看一下程序运行的结果:

etc@ruc-etc:~/cpp/ch1/4$ g++ prog1.cpp -o prog1 -Wall -g
etc@ruc-etc:~/cpp/ch1/4$ g++ prog3.cpp -o prog3 -Wall -g
etc@ruc-etc:~/cpp/ch1/4$ ./prog1 
Sum of 1 to 10 inclusive is 55
n : 11
etc@ruc-etc:~/cpp/ch1/4$ ./prog3
Sum of 1 to 10 inclusive is 55
n : 10

前缀递增运算符和后缀递增运算符作为右值时,其运行结果也不同。

练习 1.9
#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 50;

    while(val <= 100) {
        sum += val;
        ++val;
    }

    cout << "Sum of 50 to 100 inclusive is "
         << sum << endl;

    return 0;
}
练习 1.10
#include <iostream>

using namespace std;

int main()
{
    int val = 10;

    while(val >= 0) {
        cout << val << endl;
        --val;
    }

    return 0;
}
练习 1.11
#include <iostream>

using namespace std;

int main()
{
    int v1, v2;

    cin >> v1 >> v2;

    if(v1 > v2) {
        int tmp = v1;
        v1 = v2;
        v2 = tmp;
    }

    while(v1 <= v2) {
        cout << v1 << endl;
        ++v1;
    }

    return 0;
}
1.4.2 for语句

在while循环中检测变量、在循环体中递增变量的模式使用非常频繁,以至于C++专门定义了第二种循环语句——for语句,来简化符合这种模式的语句。

for语句的形式为

for(init-statement; condition; expression)
    statement

其执行流程如下:

  1. 先执行初始化语句init-statement;

  2. 再对条件语句condition进行判断。如果condition为真,那么执行statement,否则退出循环;

  3. 执行expression;

  4. 重复第2步的条件检测,只要为真就继续执行剩余步骤。

练习 1.12

功能:求-100到100之间的整数之和。sum的终值应该是0。

练习 1.13

exercise1_13_1.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0;

    for(int val = 50; val <= 100; ++val)
        sum += val;

    cout << "Sum of 50 to 100 inclusive is "
         << sum << endl;

    return 0;
}

exercise1_13_2.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0;

    for(int val = 10; val >= 0; --val)
        sum += val;

    cout << "Sum of 10 to 0 inclusive is "
         << sum << endl;

    return 0;
}

exercise1_13_3.cpp:

#include <iostream>

using namespace std;

int main()
{
    int v1, v2;

    for(cin >> v1 >> v2; v1 <= v2; ++v1)
        cout << v1 << endl;

    return 0;
}
练习 1.14

对于for循环适用于循环次数已知的情况,while循环适用于循环次数未知的情况的说法,我不敢苟同。举一个栗子,带头节点的单链表的遍历。对于带头结点的单链表,我们是无法预知其节点个数的,那么按照上述的说法用while循环比较适合?实际上并不是这样:

struct list_node {
    void *data;
    struct list_node *next;
};

void list_traverse(struct list_node *head, void (*visit)(void *))
{
#if 0
    struct list_node *p = head->next;

    while(p) {
        visit(p->data);
        p = p->next;
    }
#else
    for(struct list_node *p = head->next; p; p = p->next)
        visit(p->data);
#endif
}

不难看出for循环的形式比较清楚简洁。

实际上,for循环和while循环两者之间通常是可以互相替换的。对于while循环,我们能找到一个几乎等价的for循环:

while(condition)
    statement
   
for( ; condition; )
    statement
    
for( ; condition; statement)
    ;

同理,对于for循环,我们也能找到一个几乎等价的while循环:

for(init-statement; condition; expression)
    statement
    
init-statement
while(condition) {
    statement
    expression   
}

实际上,上述等价转换还有一些细微的差异,比如:

for循环的()中init-statement、condition、expression三者都是可以省略的

for( ; ; )

for循环的init-statement、condition、expression都省略是经典的“死循环”,等价于

while(1)

while循环中的condition是不能够省略的,下面的写法会编译报错

while()

另外,for循环和与其几乎等价的while循环在处理statement中continue语句的时候也颇为不同:for循环在执行完continue后,执行流会转到expression,而while循环会跳过expression

例如,求1-100不能被5整除的数之和。我们可以用continue关键字,也可以单纯地使用if-else,这里特意使用continue关键字就是为了感受一下它在两种循环中的差异。

exercise1_14_1.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0;

    for(int val = 1; val <= 100; ++val) {
        if(0 == val % 5)
            continue;

        sum += val;
    }

    cout << "Sum = " << sum << endl;

    return 0;
}

exercise1_14_2.cpp:

#include <iostream>

using namespace std;

int main()
{
    int sum = 0, val = 1;

    while(val <= 100) {
        if(0 == val % 5)
            continue;

        sum += val;
        ++val;
    }

    cout << "Sum = " << sum  << endl;

    return 0;
}

运行结果如下:

etc@ruc-etc:~/cpp/ch1/4$ g++ exercise1_14_1.cpp -o exe14_1 -Wall -g
etc@ruc-etc:~/cpp/ch1/4$ g++ exercise1_14_2.cpp -o exe14_2 -Wall -g
etc@ruc-etc:~/cpp/ch1/4$ ./exe14_1 
Sum = 4000
etc@ruc-etc:~/cpp/ch1/4$ ./exe14_2
^C

exe14_1可以正确执行,exe14_2会死循环。原因是当0 == val % 5满足后执行continue就会跳过后面expression(++val),即val值不会更新。又由于val值没更新condition必然为真,再执行循环体statement的时候会一直满足0 == val % 5执行continue…从而陷入死循环。

对于个人而言,更喜欢使用for循环。因为它清楚明了,多数情况下可以在一行中表示出循环的运作机制。对于while循环,可能需要将condition中涉及到的变量加上高亮,以方便观察这些变量更新的时机(充当左值/自增等),从而弄清楚循环的运行机制。

练习 1.15

略。

1.4.3 读取数量不定的输入数据
std::cin >> value

此表达式从标准输入读取下一个数,保存在value中。输入运算符返回其左侧运算对象。当我们使用一个istream对象作为条件时,其效果是检测流的状态。如果流是有效的,即流末遇到错误,那么检测成功。当遇到文件结束符,或者遇到一个无效输入时(例如读入的值的类型不匹配),istream对象的状态会变为无效。处于无效状态的istream对象会使条件变为假。

在Windows系统中,输入文件结束符的方法是敲Ctrl+Z(按住Ctrl键的同时按住Z键),然后按Enter或Return键

在UNIX系统中,包括Mac OS X系统中,文件结束符输入使用Ctrl+D

修正编译错误的原则:

对于编译时产生的错误信息,按照报告的顺序来逐个修正错误,是一种好习惯。因为一个单个错误常常会具有传递效应,导致编译器在其后报告比实际数量多得多的错误信息。另一个好习惯是在每修正一个错误后就立即重新编译代码,或者最多是修正了一小部分明显的错误后就重新编译。这就是所谓的“编辑-编译-调试”(edit-compile-debug)周期。

练习 1.16
#include <iostream>

using namespace std;

int main()
{
    int sum = 0, value = 0;

    while(cin >> value)
        sum += value;

    cout << "Sum is " << sum << endl;

    return 0;
}
1.4.4 if语句

if语句的形式为

if(condition)
    statement1
else
    statement2

如果condition为真,那么执行statement1;否则执行statement2。

例如,统计输入中每个值连续出现了多少次,prog5.cpp:

#include <iostream>

using namespace std;

int main()
{
    int cur_value = 0, value =  0;

    if(cin >> cur_value) {
        int cnt = 1;

        while(cin >> value) {
            if(cur_value == value)
                ++cnt;
            else {
                cout << cur_value << " occurs " << cnt << " times " << endl;

                cur_value = value;
                cnt = 1;
            }
        }

        cout << cur_value << " occurs " << cnt << " times " << endl;
    }

    return 0;
}

C++用=进行赋值,用==作为相等运算符。一般而言,赋值运算符相对于相等运算符出现得更频繁,因此字符数较少的=就被赋予了更常用的含义——赋值操作。这个逻辑类似于电文传输编码(如Huffman编码)总是希望电文总长尽可能地短。此外,两个运算符都可以出现在条件中。一个常见的错误是想在条件中使用==(相等判断),却误用了=。为了规避上述的错误,对于常量和变量的相等比较,我们应该把常量写在变量前面,这是一个好的编码习惯。当我们少打一个=的时候,编译器就会报错,因为常量是不能作为左值的

test.cpp:

#include <iostream>

using namespace std;

int main()
{
    int a = 5;

    if(3 = a)
        cout << "a = 3" << endl;
    else
        cout << "a != 3" << endl;

    return 0;
}

编译结果:

etc@ruc-etc:~/cpp/ch1/4$ g++ test.cpp -o test -Wall -g
test.cpp: In function ‘int main()’:
test.cpp:9:9: error: lvalue required as left operand of assignment

当变量之间进行相等比较的时候,在编译选项中加入-Wparentheses,并且注意编译警告就显得尤为重要了

test.cpp:

#include <iostream>

using namespace std;

int main()
{
    int a = 5, b = 4;

    if(a = b)
        cout << "a = b" << endl;
    else
        cout << "a != b" << endl;

    return 0;
}

不加相关编译选项的编译结果:

etc@ruc-etc:~/cpp/ch1/4$ g++ test.cpp -o test -g
etc@ruc-etc:~/cpp/ch1/4$ 

加上相关编译选项后的编译结果:

etc@ruc-etc:~/cpp/ch1/4$ g++ test.cpp -o test -g -Wparentheses
test.cpp: In function ‘int main()’:
test.cpp:9:10: warning: suggest parentheses around assignment used as truth value [-Wparentheses]

当然,确实存在需要对变量进行赋值并且检查该变量的新值是否为0的情形。为了避免来自该类编译器警告,我们不应该简单地关闭警告选项,而是显式地进行比较。如使用ch暂存getchar()得到的标准输入的字符,直到收到’\0’(即0,字符串的结束标识)为止。不应写作:

while(ch = getchar())
    do somthing;

而是写作:

while((ch = getchar()) != '\0')
    do somthing;

这种写法也使得代码的意图一目了然,方便阅读者理解。

练习 1.17

如果输入的所有值都是相等的,程序会输出输入值以及输入该值的个数;如果没有重复值,每个输入值都会输出一行,且输出的cnt值为1。

练习 1.18

运行结果如下:

etc@ruc-etc:~/cpp/ch1/4$ g++ prog5.cpp -o prog5 -Wall -g
etc@ruc-etc:~/cpp/ch1/4$ vi case 
etc@ruc-etc:~/cpp/ch1/4$ cat case
42 42 42 42 42
etc@ruc-etc:~/cpp/ch1/4$ ./prog5 < case 
42 occurs 5 times 
etc@ruc-etc:~/cpp/ch1/4$ vi case 
etc@ruc-etc:~/cpp/ch1/4$ !c
cat case
42 43 44 45 46
etc@ruc-etc:~/cpp/ch1/4$ !./
./prog5 < case 
42 occurs 1 times 
43 occurs 1 times 
44 occurs 1 times 
45 occurs 1 times 
46 occurs 1 times 
练习 1.19

修改的程序应该是练习 1.11

#include <iostream>

using namespace std;

int main()
{
    int v1, v2;

    cin >> v1 >> v2;

    while(v1 <= v2) {
        cout << v1 << endl;
        ++v1;
    }

    return 0;
}
C++程序的缩进和格式

对于C++程序缩进和格式风格的重要性1.3 注释简介中有过说明。此外,我们要牢记一件重要的事情:当我们选择一种格式风格时,思考一下它会对程序的可读性易理解性有什么影响,而一旦选择了一种风格,就要坚持使用

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值