C++重学之路 5 控制语句和逻辑运算符

本文详细介绍了C++中的循环结构,包括for、while和do...while循环,强调了计数器控制的循环要素和逻辑运算符的使用。讲解了如何计算复利、格式化数值输出以及如何处理输入的EOF指示符。此外,还讨论了switch语句、break和continue语句在程序控制流中的作用,并提到了C++11的类内初始化器。最后,文章探讨了数据类型、运算符优先级和逻辑错误的避免方法,以及左值和右值的概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1 计数器控制的循环的要素

计数器控制的循环需要:

  1. 命名一个控制变量(或者循环计数器)
  2. 设置控制变量(controler)的初值
  3. 定义一个循环继续条件,用于对控制变量终止的测试(例如,循环是否应该继续)
  4. 增值(或减值),即在每次循环过程中修改控制变量的值
#include<iostream>
using namespace std;

int main()
{
	unsigned int counter = 1;
	while (counter <= 10)
	{
		cout << counter << " ";
		++counter;
	}
	cout << endl;
}

也可以用下面的写法让代码更简练

#include<iostream>
using namespace std;

int main()
{
	unsigned int counter = 0;
	while (++counter <= 10)
		cout << counter << " ";
	cout << endl;
}

TIPS:

应使用整数值控制计数器的循环。

自增运算符和自减运算符分别只能和整形操作数一起使用。

2 for循环语句

通常,for语句用来描述计数器控制的循环,而while语句用于表达标记控制的循环。

在这里插入图片描述

如果for语句头部的“初始化”表达式中声明了控制变量,那么该控制变量仅能在for语句的循环体中使用,在此for语句之外它是未知的。

对控制变量名的使用进行的限制称为变量的作用域(scope)。

for循环头部如果要定义多变量,只能定义同种数据类型的变量,否则只能在for循环外定义多于变量,若认为浪费变量名,可以加{}限制作用域。正确定义如下:

for (int i=0 , j = 1; i < 9 && j < 10; i++ , j++)
{
	//blabla...
}

for (int i=0 , j = 1; i < 9 || j < 10; i++ , j++)
{
	//blabla...
}

for循环头部的三个表达式可选,但两个分号必需。

将分号直接放置在for语句头部的右括号的右边,这是一个逻辑错误。

控制变量的值可以在for语句循环体内进行改变,但容易导致难以察觉的逻辑错误。

若循环控制变量增值或减值的步长超过1,应尽量避免在循环条件中使用相等运算符(!===)。

应用:计算复利

#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;

int main()
{
	double amount;
	double principal = 1000.0;
	double rate = .05;
	cout << "Year" << setw(21) << "Amount on deposit" << endl;
	cout << fixed << setprecision(2);
	for (unsigned int year = 1; year <= 10; ++year)
	{
		amount = principal * pow(1.0 + rate, year);
		cout << setw(4) << year << setw(21) << amount << endl;
	}
}

函数pow(x,y) 计算x的y次幂的值,它需要接受两个类型为double 的实参,并返回一个double类型的值。如果这个程序没有头文件<cmath> ,那么将无法通过编译。例子中的变量year是个整数,这个头文件包含信息,告诉编译器在这个函数调用时,把year的值转换成一个临时的double值。这些信息含在pow函数的函数原型中。

如果忘记包含头文件<cmath> ,将会产生一个编译错误。

在金融计算中使用double或float类型的特别提醒

例如,计算机中保存到两笔金额分别为14.234(打印出来是14.23)和18.673(打印出来是18.67),当他们相加时,内部求和的结果是32.907(打印出来是32.91),但是我们自己将打印的数字相加得到的是32.90。应当注意这个隐患。

利用流操纵符格式化数值的输出

在例子中,流操纵符setw(4) 规定了下一个输出值应占用的域宽为4。也就是说,cout 打印一个值,它至少占用4个字符位置。如果输出的值小于4个字符位置的宽度,那么在默认情况下,该值的输出在域宽范围内向右对齐;如果输出的值大于4个字符位置的宽度,那么域宽将向右侧扩展到整个值的实际宽度。

为了指出值要向左对齐输出,只需要简单地输出无参数的流操纵符left(在头文件<iostream>中可以找到)即可。当然向右对齐也可以恢复,只是再输出无参数的流操纵符right 而已。

我们在例子中应用了流操纵符fixedsetprecision ,这些格式的设置如果不被更改则会一直起作用。正因如此,称这样的设置为黏性设置(sticky setting)。可是,指定域宽的setw 只对接下来要输出的值有用。

3 do…while循环语句

do…while语句是循环体执行之后再进行循环继续条件的测试,因此循环体总是至少执行一次。

#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;

int main()
{
	unsigned int counter = 1;
	do
	{
		cout << counter << " ";
		++counter;
	} while (counter <= 10);
	cout << endl;
}

注意while语句的括号右侧有分号。

4 switch多路选择语句

使用switch语句优化GradeBook类:

#include <string>

class GradeBook 
{
public:
    explicit GradeBook(std::string );
    void setCourseName(std::string );
    std::string getCourseName() const;
    void displayMessage() const;
    void inputGrades();
    void displayGradeReport() const;
private:
    std::string courseName;
    unsigned int aCount;
    unsigned int bCount;
    unsigned int cCount;
    unsigned int dCount;
    unsigned int fCount;
};
#include <iostream>
#include "GradeBook.h"
using namespace std;

GradeBook::GradeBook(std::string) 
    : aCount(0), bCount(0), cCount(0), dCount(0), fCount(0);
{
    setCourseName(name); 
}
// SETTERS
void GradeBook::setCourseName(std::string)
{
    if (name.length() <= 25)
        courseName = name;
    else 
    {
        courseName = name.substr(0, 25);
        cerr << "Name \"" << name << "\" exceeds maximum length (25).\n"
             << "Limiting courseName to first 25 characters.\n"
             << endl;
    }
}
// GETTERS
string GradeBook::getCourseName() const 
{
    return courseName; 
}
void GradeBook::displayMessage() const {
    std::cout << "Welcome to the grade book for\n"
              << getCourseName() << "!\n"
              << std::endl;
}
void GradeBook::inputGrades() {
    int grade;

    cout << "Enter the letter grades.\n"
         << endl
         << "Enter the EOF character to end input." << endl;

    while ((grade = cin.get()) != EOF) 
    {
        switch (grade) 
        {
            case 'A':
            case 'a':
                ++aCount;
                break;

            case 'B':
            case 'b':
                ++bCount;
                break;

            case 'C':
            case 'c':
                ++cCount;
                break;

            case 'D':
            case 'd':
                ++dCount;
                break;

            case 'F':
            case 'f':
                ++fCount;
                break;

            case '\n':
            case '\t':
            case ' ':
                break;

            default:
                cout << "Incorrect letter grade entered. Enter a new grade."
                     << endl;
                break;
        }
    }
}
void GradeBook::displayGradeReport() const {
    cout << "\n\nNumber of students who received each letter grade:"
         << "\nA: " << aCount << "\nB: " << bCount << "\nC: " << cCount
         << "\nD: " << dCount << "\nF: " << fCount << endl;
}
#include "GradeBook.hpp"

int main() 
{
    GradeBook myGradeBook("CS101 C++ Programming");

    myGradeBook.displayMessage();
    myGradeBook.inputGrades();
    myGradeBook.displayGradeReport();
}

读入输入的字符

函数cin.get() 从键盘读入一个字符并保存到整型变量grade中,一般字符存储在char类型的变量中,它们也可以存储在任何整数数据类型中,因为类型shortintlonglong long 都可以保证不比char类型小。

所以根据具体用途,即可以把字符当作整数来处理,也可按照字符来对待。如:

cout << "The character (" << 'a' << ") has the value " << static_cast<int>('a') << endl;

打印字符a和它的整数值,结果如下:

The character (a) has the value 97

一般而言,整个赋值表达式的值正是赋给赋值运算符左边变量。因此赋值表达式grade=cin.get()的值等于由cin.get()返回并赋给变量grade的值。

a = b = c = 0;

这行代码首先计算赋值表达式c = 0 的值(=运算符是从右到左结合的)。然后,赋值表达式c = 0 的值(是0)赋给变量b,接着,赋值表达式b = (c = 0) 的值(也是0)赋值给变量a。

输入EOF指示符

EOF代表“end-of-file”,是用于标记“文件结束”的一个符号。在程序中我们常使用EOF(一般取值为-1)做标记值,但不可以直接输入-1或者EOF这三个字符作为这个标记值。准确地说,只能输入一个由具体系统决定的代表“文件结束”的组合键,指示不再有数据需要输入了。

EOF是一个符号整数常量,它通过头文件<iostream> 包含到程序中。EOF具有的数据类型是int

在OS X/UNIX/Linux系统和很多其他系统中,“文件结束”的输入通过在一行上输入如下的组合键实现:<Ctrl> d

在Microsoft Windows之类的其他系统下,“文件结束”的输入通过在一行上输入如下的组合键实现:<Ctrl> z

某些情况下,必须在按了前述组合键后,再按一次回车键。字符^Z 有时出现在屏幕上,表示文件结束。

switch语句中,若在单词case与被测试的整数值之间遗漏了空格,例如将case 3:写成了case3: ,那么会引起一个逻辑错误。

在输入中忽略换行符、制表符和空格

case '\n':
case '\t':
case ' ':
    break;

为了使程序读入字符,必须通过按回车键,这样会在希望处理的字符之后在输入中多加一个换行符。上述语句避免了每次输入换行符、制表符和空格时,都有default 子句打印一条错误信息。

每个case情况只可用于测试整型常量表达式——字符常量和整数常量的任意组合,其计算结果是一个常整数值。同样,每个case标签只能指定一个整型常量表达式。

TIPS:

switch语句的case标签中指定非常量的整型表达式是一个语法错误。

switch语句中,如果提供同样的case标签,则产生一个编译错误。

有关数据类型的说明

C++具有灵活的数据类型长度,每种类型的整数取值的范围由具体平台所决定。

除了intchar之外,C++还提供了类型shortlonglong long 。对于short整数而言,它的最小取值范围是-32768 ~ 32767。对于绝大多数的整数运算来说,long整数就足够了。long整数的最小取值范围是-2147483648 ~ 2147483647。在大多数计算机中,int类型要么与short类型等价,要么与long类型等价。各个int类型的取值范围至少与short类型相同,最多与long类型一样。数据类型char既可以用来表示计算机字符集中的任何字符,也可以用于表示小整数。

C++11的类内初始化器

C++11允许程序员在类声明中声明数据成员时,为它们提供默认值。如:

unsigned int aCount = 0;
unsigned int bCount = 0;
unsigned int cCount = 0;
unsigned int dCount = 0;
unsigned int fCount = 0;

这与之前例子中在类的构造函数中对数据成员初始化不同。

5 break和continue语句

break语句

break语句在whilefordo...while或者switch语句中执行,立刻使程序退出这些语句。

break语句的常见用法是要么提前离开循环,要么用于跳过switch语句的剩余部分。

continue语句

continue语句在whilefordo...while或者switch语句中执行时,是程序跳过循环体内剩下语句,继续进行循环体下一次迭代。

whiledo...while语句中,循环继续条件的测试在continue语句执行之后马上进行。在for语句中,则执行增值表达式,然后对循环继续条件进行测试。

for循环语句中,当continue语句执行后,程序控制转到for头部的控制变量增值部分继续执行。

如果while语句的增值表达式跟随在continue语句之后时,该增值表达式在程序测试循环继续条件之前不会执行,很可能导致死循环。

6 逻辑运算符

逻辑与(&&)运算符

若我们希望在保证两个条件都为true的前提下才能选择某个执行路径,则可以使用&&运算符。

一个逻辑与表达式的右侧只有在其左侧为true的情况下才会被计算。

在这里插入图片描述

TIPS:

在数学中的 3<x<7 是正确的表达,但在C++中要写成 3 < x && x < 7

逻辑或(||)运算符

&&运算符的优先级比||稍高些。两个运算符都是从左到右结合的。

&&一样,含||的表达式,其计算在一旦能够确定整个表达式的真假时,就会立即结束。

逻辑非(!)运算符

一元的逻辑非运算符只用一个条件作为操作数,且放在要“逆转”的条件前面。如:

if(!(grade == sentineValue))

大多数情况下,对于用逻辑非运算符表达的条件,程序员同样可以通过合适的关系运算符或者相等运算符来表达。例如,上面语句可以写成:

if(grade != sentineValue)

7 运算符优先级总表

在上一章的总表中加入逻辑和逗号运算符。

运算符结合律类型
:: ()从左向右最高
++ -- static_cast<类型>()从左向右后缀
++ -- + - 从右向左一元(前缀)
* / %从左向右
+ -从左向右
<< >>从左向右插入/提取
< <= > >=从左向右关系
== !=从左向右相等
&&从左向右逻辑与
||从左向右逻辑或
?:从右向左条件
= += -= *= /= %=从右向左赋值
,从左向右逗号

8 ==运算符和=运算符的混淆问题

在赋值时用==运算符,在比较时用=运算符,都属于逻辑错误,这种互换具有破坏性,因为通常不产生语法错误。

TIPS:

在书写x==7 之类的条件时,通常将变量名放左边,常量放右边。但写成7==x 这种形式会好些,即将常量放左边,变量名放右边,因为当不小心用=代替了== ,编译器将起到保护作用。编译器会认为7=x 是个编译错误,因为常量值是不可更改的。

左值和右值

变量名称为左值(lvalue),因为可以在赋值运算符左边使用;常量称为右值(rvalue),因为只能在赋值运算符右边使用。

左值可以作右值,但右值绝不能作左值。

如果想用一下一条简单语句为一个变量赋值:

x = 1; 

却写成:

x == 1;

这里不是一个语法错误。编译器只是简单判断这个条件表达式。表达式的值为true或false,不管怎样,这里没赋值运算符,所以这个值自然丢失了,同时x的值保持不变。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Lum0s!

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值