跳转到内容

C++ 编程

来自 Wikibooks,为开放世界提供开放书籍

控制流语句

[编辑 | 编辑源代码]

通常程序不是线性指令序列。它可能会重复代码或为给定的路径目标关系做出决定。大多数编程语言都有控制流语句(结构),这些语句提供一些控制结构,用于指定执行程序必须执行的顺序,从而允许此顺序的顺序变化。

  • 语句可能只在特定条件下(条件语句)执行,
  • 语句可能在特定条件下(循环)重复执行,
  • 一组远程语句可能执行(子例程)。
逻辑表达式作为条件
逻辑表达式可以在循环和条件语句中使用逻辑运算符,作为要满足的条件的一部分。

异常和非结构化控制流

[编辑 | 编辑源代码]

有些指令没有特定的结构,但将在塑造其他控制流语句的结构方面具有非凡的用处,必须特别注意防止非结构化和混乱的编程。

break 将强制当前循环迭代退出到循环外部的下一条语句。除了switch 控制语句之外,它在循环结构之外没有用处。

continue 指令用于循环内部,它将停止当前循环迭代,并启动下一个迭代。

goto 关键字不鼓励使用,因为它使得难以跟踪程序逻辑,从而导致错误。goto 语句使当前执行线程跳转到指定的标签。

语法
label:
  statement(s);

goto label;

在一些罕见的情况下,goto 语句允许编写简洁的代码,例如,在处理导致函数退出时清理代码的多个退出点时(并且异常处理或对象析构函数不是更好的选择)。除了这些罕见情况外,无条件跳转的使用通常是设计复杂的征兆,因为存在许多嵌套语句层级。

在特殊情况下,例如重度优化,程序员可能需要对代码行为进行更多控制;goto 允许程序员指定执行流程直接且无条件地跳转到所需的标签。标签是函数中其他地方的标签语句的名称。

注意
在软件工程领域,有一篇经典的论文是由W. A. Wulf撰写的,名为"反对 GOTO 的论据",它是在 1972 年 10 月第 25 届ACM 全国大会上发表的,当时关于goto 语句的争论正达到顶峰。在这篇论文中,Wulf 认为goto 语句应该被视为危险的。Wulf 还以他对效率的评论而闻名:“在效率的名义下(不一定能实现它),犯下的计算错误比任何其他单一原因(包括盲目的愚蠢)都要多。”

例如,goto 可以用于退出两个嵌套循环。此示例在用零替换遇到的第一个非零元素后退出。

for (int i = 0; i < 30; ++i) {
  for (int j = 0; j < 30; ++j) {
    if (a[i][j] != 0) {
       a[i][j] = 0;
       goto done;
     }
  }
}
done:
/* rest of program */

虽然简单,但它们很快就会导致难以阅读和难以维护的代码。

// snarled mess of gotos

int i = 0;
  goto test_it;
body:
  a[i++] = 0;
test_it:
  if (a[i]) 
    goto body;
/* rest of program */

比等效的

for (int i = 0; a[i]; ++i) {
  a[i] = 0;
}
/* rest of program */

Goto 通常用于性能至关重要的函数或机器生成的代码的输出(如由yacc 生成的解析器)。

goto 语句几乎应该总是避免,但有些情况下它会提高代码的可读性。一种情况是“错误部分”。

示例

#include <new>
#include <iostream>

...

int *my_allocated_1 = NULL;
char *my_allocated_2 = NULL, *my_allocated_3 = NULL;
my_allocated_1 = new (std::nothrow) int[500];

if (my_allocated_1 == NULL)
{  
  std::cerr << "error in allocated_1" << std::endl;
  goto error;
}

my_allocated_2 = new (std::nothrow) char[1000];

if (my_allocated_2 == NULL)
{  
  std::cerr << "error in allocated_2" << std::endl;
  goto error;
}
    
my_allocated_3 = new (std::nothrow) char[1000];

if (my_allocated_3 == NULL)
{  
  std::cerr << "error in allocated_3" <<std::endl;
  goto error;
}
return 0;
    
error:
  delete [] my_allocated_1;
  delete [] my_allocated_2;
  delete [] my_allocated_3;
  return 1;

此结构避免了处理错误来源的麻烦,并且比使用控制结构的等效结构更简洁。因此,它不易出错。

注意
虽然上面的示例展示了goto 的合理用法,但在实践中并不常见。异常以更清晰、更有效和更有组织的方式处理这种情况。这将在“异常处理”中详细讨论。使用 RAII 管理内存等资源也可以避免对大多数上面所示的显式清理代码的需求。


abort(), exit() 和 atexit()

[编辑 | 编辑源代码]

正如我们将在后面看到的,C++ 中包含的标准 C 库 还提供了一些有用的函数,可以改变控制流。有些函数允许你终止程序的执行,使你能够设置返回值或在终止请求时启动特殊任务。你将不得不跳到abort() - exit() - atexit() 部分以获取更多信息。

条件语句

[编辑 | 编辑源代码]

很可能在没有任何编写过的有意义的程序中,计算机都不会根据某些特定条件展示基本的决策能力。实际上,可以认为没有人类活动是不涉及决策、本能或其他方面的。例如,在驾驶汽车并靠近红绿灯时,一个人不会想,“我将继续驶过十字路口。”相反,一个人会想,“如果灯是红色,我会停车,如果灯是绿色,我会走,如果灯是黄色,如果我以一定速度行驶并且距离十字路口一定距离,我会走。”这些过程可以使用条件语句进行模拟。

条件语句是指示计算机仅在满足特定条件时才执行某些代码块或更改某些数据的语句。

最常见的条件语句是 if-else 语句,条件表达式和 switch-case 语句通常用作更简洁的方法。

if (分支)

[编辑 | 编辑源代码]

if 语句允许根据指定的条件选择一条可能的路径。

语法

if (condition)
{
  statement;
}

语义

首先,评估条件

  • 如果条件为真,则语句在继续执行主体之前执行。
  • 如果条件为假,则程序跳过语句并继续执行程序的其余部分。

注意
if 语句中的条件可以是任何解析为布尔值或空/非空值的表达式;你可以在其中声明变量、嵌套语句等。这同样适用于其他流程控制条件语句(例如:while),但通常被认为是不好的风格,因为它唯一的好处是通过使代码更难读来简化输入。

这种特性很容易导致简单的错误,比如将 a=b(赋值)误写成 a==b(条件)。因此,人们采用了这样一种编码实践:通过反转表达式(或使用常量变量)自动将错误暴露出来,编译器会生成错误。

最近的编译器支持检测此类事件并生成编译警告。

示例

if(condition)
{
  int x; // Valid code
  for(x = 0; x < 10; ++x) // Also valid.
    {
      statement;
    }
}
flowchart from the example
来自示例的流程图

注意
如果你想避免一直输入 std::cout、std::cin 或 std::endl;你可以在程序的开头包含 using namespace std,因为 cout、cin 和 endl 是 std 命名空间的成员。

有时程序需要根据条件选择两个可能的路径之一。为此,我们可以使用 if-else 语句。

if (user_age < 18)
{
    std::cout << "People under the age of 18 are not allowed." << std::endl;
}
else
{
    std::cout << "Welcome to Caesar's Casino!" << std::endl;
}

在这里,如果用户未满 18 岁,我们将显示一条消息。否则,我们允许用户进入。只有当 'user_age' 小于 18 时才会执行 if 部分。在其他情况下(当 'user_age' 大于或等于 18 时),将执行 else 部分。

if 条件语句可以串联在一起,以实现更复杂的条件分支。在这个例子中,我们扩展了之前的例子,还检查了用户是否超过 64 岁,并在满足条件时显示另一条消息。

if (user_age < 18)
{
  std::cout << "People under the age of 18 are not allowed." << std::endl;
}
else if (user_age > 64)
{
  std::cout << "Welcome to Caesar's Casino! Senior Citizens get 50% off." << std::endl;
}
else
{
  std::cout << "Welcome to Caesar's Casino!" << std::endl;
}
flowchart from the example
来自示例的流程图

注意

  • breakcontinueifelse 无关。
  • 虽然你可以使用多个 else if 语句,但在处理许多相关条件时,建议使用 switch 语句,我们将在下一节中讨论。

switch (多重分支)

[edit | edit source]

switch 语句根据特定的整数值进行分支。

switch (integer expression) {
    case label1:
         statement(s)
         break;
    case label2:
         statement(s)
         break;
    /* ... */
    default:
         statement(s)
}

正如你在上面的方案中看到的,case 和 default 在代码块的末尾都有一个 "break;" 语句。这个表达式将导致程序退出 switch,如果没有添加 break,程序将继续执行其他 case 中的代码,即使整数表达式不等于该 case。这可以在某些情况下被利用,如下一个例子所示。

我们要将输入从数字分离到其他字符。

 char ch = cin.get(); //get the character
 switch (ch) {
     case '0': 
          // do nothing fall into case 1
     case '1': 
         // do nothing fall into case 2
     case '2': 
        // do nothing fall into case 3
     /* ... */
     case '8': 
        // do nothing fall into case 9
     case '9':  
          std::cout << "Digit" << endl; //print into stream out
          break;
     default:
          std::cout << "Non digit" << endl; //print into stream out
 }

在这段小代码中,对于每个小于 '9' 的数字,它将继续传播到 case,直到它到达 case '9' 并打印 "digit"。

如果不是这样,它将直接进入默认的 case,在那里它将打印 "Non digit"。

注意

  • 请务必使用 break 命令,除非你希望多个条件具有相同的操作。否则,它将“穿透”到下一组命令。
  • break 只能退出最内层。例如,如果你在 switch 中,需要退出包围的 for 循环,你可能需要考虑添加一个布尔值作为标志,并在 switch 代码块之后检查该标志,而不是使用其他可用的方法。(尽管如此,根据情况,将代码重构到一个单独的函数中并从该函数中返回可能更干净,并且使用内联函数和/或智能编译器,这样做不会产生任何运行时开销。)
  • continueswitch 代码块无关。在 switch 代码块中调用 continue 将导致包围 switch 代码块的循环 "continue"。

循环(迭代)

[edit | edit source]

循环(也称为迭代或重复)是一系列语句,这些语句只指定一次,但可以连续执行多次。循环“内部”的代码(循环的主体)会按照指定的次数执行,或者针对每个项目执行一次,或者直到满足某个条件为止。

迭代是重复一个过程,通常是在计算机程序中。令人困惑的是,它可以用作通用的术语,与重复同义,也可以用来描述具有可变状态的特定形式的重复。

当以第一种意义使用时,递归是迭代的一个例子。

但是,当以第二种(更严格)意义使用时,迭代描述了在命令式编程语言中使用的编程风格。这与递归形成对比,递归采用更声明性的方法。

由于 C++ 的性质,在区分单词的使用时可能会导致更大的问题,因此为了简化问题,使用“循环”来指代本节中描述的简单递归,使用迭代迭代器(执行迭代的“一个”)来指代 STL 中使用的类迭代器(或与对象/类相关)。

无限循环

有时程序需要永远循环,或者直到出现错误等异常情况。例如,事件驱动的程序可能打算永远循环处理发生的事件,只有在操作员终止进程时才停止。

更常见的是,无限循环是由于条件控制循环中的编程错误造成的,在这种情况下,循环条件在循环内从未改变。

// as we will see, these are infinite loops...
while (1) { }

// or

for (;;) { }


注意
当编译器优化源代码时,所有在检测到的无限循环(永远不会运行)之后的语句将被忽略。编译器通常会对检测到此类情况发出警告。

条件控制循环

大多数编程语言都有用于重复循环直到某个条件改变的结构。

条件控制循环分为两类:预条件或入口条件,将测试放在循环的开头;后条件或退出条件迭代,将测试放在循环的末尾。在前一种情况下,主体可能会完全跳过,而在后一种情况下,主体始终至少执行一次。

在条件控制循环中,breakcontinue 关键字变得重要。break 关键字导致退出循环,继续执行程序的其余部分。continue 关键字终止循环的当前迭代,循环继续进行下一轮迭代。

while (预条件循环)

[edit | edit source]

语法

while (''condition'') ''statement''; ''statement2'';

语义 首先,评估条件。

  1. 如果条件为真,则执行语句并再次评估条件
  2. 如果条件为假,则继续执行语句2

备注语句可以是一个包含多个指令的代码块 { ... }。

'while' 语句与 'if' 语句的不同之处在于,一旦执行了主体(在上面称为语句),它将返回到 'while' 并再次检查条件。如果为真,它将再次执行。实际上,它将一直执行,直到表达式为假。

示例 1

#include <iostream>
using namespace std;
 
int main() 
{
  int i=0;
  while (i<10) {
    cout << "The value of i is " << i << endl;
    i++;
  }
  cout << "The final value of i is : " << i << endl;
  return 0;
}

执行

 The value of i is 0
 The value of i is 1
 The value of i is 2
 The value of i is 3
 The value of i is 4
 The value of i is 5
 The value of i is 6
 The value of i is 7
 The value of i is 8
 The value of i is 9
 The final value of i is 10

示例 2

// validation of an input
#include <iostream>
using namespace std;
 
int main() 
{
  int a;
  bool ok=false;
  while (!ok) {
    cout << "Type an integer from 0 to 20 : ";
    cin >> a;
    ok = ((a>=0) && (a<=20));
    if (!ok) cout << "ERROR - ";
  }
  return 0;
}

执行

 Type an integer from 0 to 20 : 30
 ERROR - Type an integer from 0 to 20 : 40
 ERROR - Type an integer from 0 to 20 : -6
 ERROR - Type an integer from 0 to 20 : 14

do-while (后条件循环)

[edit | edit source]

语法

do {
  statement(s)
} while (condition);
 
statement2;

语义

  1. 执行语句(s)
  2. 评估条件
  3. 如果条件为真,则转到 1)。
  4. 如果条件为假,则继续执行语句2

do - while 循环在语法和目的上与 while 循环类似。该结构将循环继续条件的测试移动到代码块的末尾,以便在任何评估之前至少执行一次代码块。

示例

#include <iostream>

using namespace std;
 
int main() 
{
  int i=0;

  do {
    cout << "The value of i is " << i << endl;
    i++;
  } while (i<10);

  cout << "The final value of i is : " << i << endl;
  return 0;
}

执行

The value of i is 0
The value of i is 1
The value of i is 2
The value of i is 3
The value of i is 4
The value of i is 5
The value of i is 6
The value of i is 7
The value of i is 8
The value of i is 9
The final value of i is 10

for (预条件和计数控制循环)

[edit | edit source]

for 关键字用作预条件循环的特例,它支持用于以步进表达式的形式仅重复循环一定次数的构造,可以测试该表达式并用于通过在每次循环中递增或递减来设置步进大小(变化率)。

语法
for (initialization ; condition; step-expression)
  statement(s);

for 结构是一个通用的循环机制,它包含 4 个部分。

  1. . 初始化,它包含 0 个或多个用逗号分隔的变量初始化语句。
  2. . 测试条件,它被评估以确定是否继续执行 for 循环。
  3. . 递增,它包含 0 个或多个用逗号分隔的语句,这些语句递增变量。
  4. . 语句列表,它包含 0 个或多个语句,这些语句将在每次执行循环时执行。

注意
在循环初始化(或主体)中声明和初始化的变量仅在循环本身的作用域中有效。

for 循环等效于下一个 while 循环

 initialization
 while( condition )
 {
   statement(s);
   step-expression;
 }


注意

循环的每个步骤(初始化、条件和步进表达式)都可以包含多个命令,用,(逗号运算符)分隔。初始化条件步进表达式都是可选参数。在 C++ 中,逗号很少用作运算符。它主要用作分隔符(例如:int x, y; ).

示例 1

// a unbounded loop structure
for (;;)
{
  statement(s);
  if( statement(s) )
    break;
}

示例 2

// calls doSomethingWith() for 0,1,2,..9
for (int i = 0; i != 10; ++i)
{                  
  doSomethingWith(i); 
}

可以改写为

// calls doSomethingWith() for 0,1,2,..9
int i = 0;
while(i != 10)
{
  doSomethingWith(i);
  ++i;
}

for 循环是一个非常通用的结构,它可以运行无界循环(示例 1),并且不需要遵循许多更正式语言中类似命名结构强加的严格迭代模型。C++(就像现代 C)允许在 for 循环的初始化部分声明变量(示例 2),并且通常被认为是一种良好的形式,只在可以初始化时声明对象,并在尽可能小的作用域中进行初始化。本质上,for 和 while 循环是等效的。大多数 for 语句也可以改写为 while 语句。

在 C++11 中,添加了 for 循环的另一种形式。这种循环遍历范围(通常是字符串或容器)中的每个元素。

语法
for (variable-declaration : range-expression)
  statement(s);

示例 2

std::string s = "Hello, world";
for (char c : s) 
{
  std::cout << c << ' ';
}

将打印

H e l l o ,   w o r l d

.

华夏公益教科书