第六章 语句

空语句

如果在程序的某个地方,语法上需要一个语句,但逻辑上并不需要,此时应该使用空语句。

这种用法常见于在循环条件判断部分就能完成全部循环工作的情况。例如,下面程序从输入流中读取数据,在获得某个特殊值前无需作任何操作:

     // read until we hit end-of-file or find an input equal to sought
     while (cin >> s && s != sought)
         ; // null statement
循环条件从标准输入中读入一个值并检验 cin 的读入是否成功。如果成功读取数据,循环条件紧接着检查该值是否等于 sought
如果找到了需要的值,则退出 while 循环;否则,循环条件再次从 cin 里读入另一个值继续检验。

由于空语句也是一个语句,因此可用在任何允许使用语句的地方。

由于这个原因,那些看似非法的分号往往只不过是一个空语句而已:

     // ok: second semicolon is superfluous null statement
     ival = v1 + v2;;
这个程序段由两条语句组成:一条表达式语句和一条空语句。

<Beware>:

无关的空语句并非总是无害的。

whileif 条件后面额外添加分号,往往会彻底改变程序员的意图:

     // disaster: extra semicolon: loop body is this null statement
     while (iter != svec.end()) ; // null statement--while body is empty!
         ++iter;     // increment is not part of the loop

这个程序将会无限次循环。与缩进的意义相反,此自增语句并不是循环的一部分。
由于循环条件后面多了一个分号,因此循环体为空语句。

复合语句(块)

复合语句,通常被称为,是用一对花括号括起来的语句序列(也可能是空的)。
块标识了一个作用域,在块中引入的名字只能在该块内部或嵌套在块中的子块里访问。
通常,一个名字只从其定义处到该块的结尾这段范围内可见。

复合语句用在语法规则要求使用单个语句但程序逻辑却需要不止一个语句的地方。
例如,whilefor 语句的循环体必须是单个语句。然而,大多数情况都需要在循环体里执行多个语句。
因而可使用一对花括号将语句序列括起来,使其成为块语句。

以其中用到的 while 循环为例:

     // if so, read the transaction records
     while (std::cin >> trans)
         if (total.same_isbn(trans))
             // match: update the running total
             total = total + trans;
        else {
             // no match: print & assign to total
             std::cout << total << std::endl;
             total = trans;
     }

else 分支中,程序逻辑需要输出 total 的值,然后用 trans 重置 total。但是,else 分支只能后接单个语句。于是,用一对花括号将上述两条语句括起来,使其在语法上成为单个语句(复合语句)。这个语句既符合语法规则又满足程序的需要。


<Note>:与其他大多数语句不同,块并不是以分号结束的。

像空语句一样,程序员也可以定义空块,用一对内部没有语句的花括号实现:

     while (cin >> s && s != sought)
         { } // empty block

6.4. 语句作用域

有些语句允许在它们的控制结构中定义变量:

     while (int i = get_num())
         cout << i << endl;
     i = 0; // error: i is not accessible outside the loop
<Note>:

在条件表达式中定义的变量必须初始化,该条件检验的就是初始化对象的值。

在语句的控制结构中定义的变量,仅在定义它们的块语句结束前有效。这种变量的作用域限制在语句体内。

通常,语句体本身就是一个块语句,其中也可能包含了其他的块。

一个在控制结构里引入的名字是该语句的局部变量,其作用域局限在语句内部。

     // index is visible only within the for statement
     for (vector<int>::size_type index = 0;
                     index != vec.size(); ++index)
     { // new scope, nested within the scope of this for statement
         int square = 0;
         if (index % 2)                      // ok: index is in scope
             square = index * index;
         vec[index] = square;
     }
     if (index != vec.size()) // error: index is not visible here

如果程序需要访问某个控制结构中的变量,那么这个变量必须在控制语句外部定义。

  vector<int>::size_type index = 0;
     for ( /* empty */ ; index != vec.size(); ++index)
         // as before
     if  (index != vec.size()) // ok: now index is in scope
         // as before

早期的 C++ 版本以不同的方式处理 for 语句中定义的变量的作用域:将 for 语句头定义的变量视为在 for 语句之前定义。

有些更旧式的 C++ 程序代码允许在 for 语句作用域外访问控制变量。


对于在控制语句中定义的变量,限制其作用域的一个好处是,这些变量名可以重复使用而不必担心它们的当前值在每一次使用时是否正确。

对于作用域外的变量,是不可能用到其在作用域内的残留值的。


6.6.1. 使用 switch

直接使用 switch 语句解决上述问题:

     char ch;
     // initialize counters for each vowel
     int aCnt = 0, eCnt = 0, iCnt = 0,
         oCnt = 0, uCnt = 0;
     while (cin >> ch) {
         // if ch is a vowel, increment the appropriate counter
         switch (ch) {
             case 'a':
                 ++aCnt;
                 break;
             case 'e':
                 ++eCnt;
                 break;
             case 'i':
                 ++iCnt;
                 break;
             case 'o':
                 ++oCnt;
                 break;
             case 'u':
                 ++uCnt;
                 break;
         }
     }
     // print results
     cout  << "Number of vowel a: \t" << aCnt << '\n'
           << "Number of vowel e: \t" << eCnt << '\n'
           << "Number of vowel i: \t" << iCnt << '\n'
           << "Number of vowel o: \t" << oCnt << '\n'
           << "Number of vowel u: \t" << uCnt << endl;

通过对圆括号内表达式的值与其后列出的关键字做比较,实现 switch 语句的功能。

表达式必须产生一个整数结果,其值与每个 case 的值比较。关键字 case 和它所关联的值称为 case 标号

每个 case 标号的值都必须是一个常量表达式。除此之外,还有一个特殊的 case 标号——default 标号。

如果表达式与其中一个 case 标号的值匹配,则程序将从该标号后面的第一个语句开始依次执行各个语句,直到 switch 结束或遇到 break 语句为止。

如果没有发现匹配的 case 标号(并且也没有 default 标号),则程序从 switch 语句后面的第一条继续执行。

在这个程序中,switch 语句是 while 循环体中唯一的语句,于是,switch 语句匹配失败后,将控制流返回给 while 循环条件。

switch 中的控制流
<Note>:

存在一个普遍的误解:以为程序只会执行匹配的 case 标号相关联的语句。

实际上,程序从该点开始执行,并跨越 case 边界继续执行其他语句,直到 switch 结束或遇到 break 语句为止。

有时候,这种行为的确是正确的。程序员也许希望执行完某个特定标号的代码后,接着执行后续标号关联的语句。

但更常见的是,我们只需要执行某个特定标号对应的代码。

为了避免继续执行其后续 case 标号的内容,程序员必须利用 break 语句清楚地告诉编译器停止执行 switch 中的语句。

大多数情况下,在下一个 case 标号之前的最后一条语句是 break。例如,下面统计元音出现次数的 switch 语句是不正确的:

     // warning: deliberately incorrect!
     switch (ch) {
          case 'a':
               ++aCnt;   // oops: should have a break statement
          case 'e':
               ++eCnt;   // oops: should have a break statement
          case 'i':
               ++iCnt;   // oops: should have a break statement
          case 'o':
               ++oCnt;   // oops: should have a break statement
          case 'u':
               ++uCnt;   // oops: should have a break statement
     }

为了搞清楚该程序导致了什么结果,假设 ch 的值是 'i' 来跟踪这个版本的代码。程序从 case 'i' 后面的语句开始执行,iCnt 的值加 1。
但是,程序的执行并没有在这里停止,而是越过 case 标号继续执行,同时将 oCntuCnt 的值都加了 1.如果 ch'e' 的话,那么 eCntiCntoCnt 以及 uCnt 的值都会加 1。

<Note>:

尽管没有严格要求在 switch 结构的最后一个标号之后指定 break 语句,但是,为了安全起见,最好在每个标号后面提供一个 break 语句,

即使是最后一个标号也一样。如果以后在 switch 结构的末尾又需要添加一个新的 case 标号,则不用再在前面加 break 语句了。


6.6.4. switch 表达式与 case 标号

switch 求解的表达式可以非常复杂。特别是,该表达式也可以定义和初始化一个变量:

     switch(int ival = get_response())

在这个例子中,ival 被初始化为 get_response 函数的调用结果,其值将要与每个 case 标号作比较。

变量 ival 始终存在于整个 switch 语句中,在 switch 结构外面该变量就不再有效了。


case 标号必须是整型常量表达式。例如,下面的标号将导致编译时的错误:

     // illegal case label values
     case 3.14:  // noninteger
     case ival:  // nonconstant

如果两个 case 标号具有相同的值,同样也会导致编译时的错误。

6.6.5. switch 内部的变量定义

对于 switch 结构,只能在它的最后一个 case 标号或 default 标号后面定义变量:

     case true:
          // error: declaration precedes a case label
          string file_name = get_file_name();
          break;
     case false:
          // ...

制定这个规则是为避免出现代码跳过变量的定义和初始化的情况。

回顾变量的作用域,变量从它的定义点开始有效,直到它所在块结束为止。

现在考虑如果在两个 case 标号之间定义变量会出现什么情况。该变量会在块结束之前一直存在。

对于定义该变量的标号后面的其他 case 标号,它们所关联的代码都可以使用这个变量。

如果 switch 从那些后续 case 标号开始执行,那么这个变量可能还未定义就要使用了。

在这种情况下,如果需要为某个特殊的 case 定义变量,则可以引入块语句,在该块语句中定义变量,从而保证这个变量在使用前被定义和初始化。

     case true:
         {
             // ok: declaration statement within a statement block
             string file_name = get_file_name();
             // ...
         }
       break;
         case false:
             // ...

6.7. while 语句

当条件为真时,while 语句反复执行目标语句。它的语法形式如下:

     while (condition)
              statement

只要条件 condition 的值为 true,执行语句 statement(通常是一个块语句)。condition 不能为空。
如果第一次求解 condition 就产生 false 值,则不执行 statement

循环条件 condition 可以是一个表达式,或者是提供初始化的变量定义。

     bool quit = false;
     while (!quit) {                  // expression as condition
         quit = do_something();
     }
     while (int loc = search(name)) { // initialized variable as condition
             // do something
     }

在循环条件中定义的任意变量都只在与 while 关联的块语句中可见。每一次循环都将该变量的初值转换为 bool

如果求得的值为 true,则执行 while 的循环体。通常,循环条件自身或者在循环体内必须做一些相关操作来改变循环条件表达式的值。

否则,循环可能永远不会结束。


while 循环的使用

前面的章节已经用过很多 while 循环,但为更完整地了解该结构,考虑下面将一个数组的内容复制到另一个数组的例子:

     // arr1 is an array of ints
     int *source = arr1;
     size_t sz = sizeof(arr1)/sizeof(*arr1); // number of elements
     int *dest = new int[sz];                // uninitialized elements
     while (source != arr1 + sz)
         *dest++ = *source++; //  copy element and increment pointers

首先初始化 sourcedest,并使它们各自指向所关联的数组的第一个元素。while 循环条件判断是否已经到达要复制的数组的末尾。

如果没有,继续执行循环。循环体只有单个语句,实现元素的复制,并对两个指针做自增操作,使它们指向对应数组的下一个元素。


“简洁即是美”的建议,C++ 程序员应尝试编写简洁的表达式。while 循环体中的语句:

     *dest++ = *source++;

是一个经典的例子。这个表达式等价于:

     {
         *dest = *source; // copy element
         ++dest;  // increment the pointers
         ++source;
     }
<Tips>:

while 循环内的赋值操作是一种常见的用法。因为这类代码广为流传,所以学习这种表达式非常重要,要一眼就能看出其含义来。



for 循环的使用

Given the following for loop, which prints the contents of a vector,

假设有下面的 for 循环,用于输出一个 vector 对象的内容:

     for (vector<string>::size_type ind = 0;
                   ind != svec.size(); ++ind) {
         cout << svec[ind]; // print current element
         // if not the last element, print a space to separate from the next one
         if (ind + 1 != svec.size())
            cout << " ";
     }

它的计算顺序如下:

1.
循环开始时,执行一次  init-statement。在这个例子中,定义了  ind,并将它初始化为 0。
2.
接着,求解  condition。如果  ind 不等于  svec.size(),则执行  for 循环体。否则,循环结束。如果在第一次循环时,条件就为  flase,则不执行  for 循环体。
3.
如果条件为  true,则执行  for 循环体。本例中, for 循环体输出当前元素值,并检验这个元素是否是最后一个。如果不是,则输出一个空格,用于分隔当前元素和下一个元素。
4.
最后,求解  expression。本例中, ind 自增 1。

这四步描述了 for 循环的第一次完整迭代。接着重复第 2 步,然后是和 3、4 步,直到 condition 的值为 false,即 ind 等于 svec.size() 为止。

<Note>:

应该谨记:在 for 语句头定义的任何对象只限制在 for 循环体里可见。因此,对本例而言,在执行完 for 语句后,ind 不再有效(即不可访问)。

省略 for 语句头的某些部分
for 语句头中,可以省略 init-statementcondition 或者 expression(表达式)中的任何一个(或全部)。

如果不需要初始化或者初始化已经在别处实现了,则可以省略 init-statement

例如,使用迭代器代替下标重写输出 vector 对象内容的程序,为了提高易读性,可将初始化移到循环外面:

     vector<string>::iterator iter = svec.begin();
     for( /* null */ ; iter != svec.end(); ++iter) {
         cout << *iter; // print current element
         // if not the last element, print a space to separate from the next one
         if (iter+1 != svec.end())
             cout << " ";
     }

注意此时必须要有一个分号表明活力了 init-statement——更准确地说,分号代表一个空的 init-statement

省略 condition,则等效于循环条件永远为 true

     for (int i = 0; /* no condition */ ; ++i)

相当于程序写为:

     for (int i = 0; true ; ++i)

这么一来,循环体内就必须包含一个 break 或者 return 语句。否则,循环会一直执行直到耗尽系统的资源为止。

同样地,如果省略 expression,则必须利用 breakreturn 语句跳出循环,或者在循环体内安排语句修改 condition 所检查的变量值。

     for (int i = 0; i != 10; /* no expression */ ) {
        // body must change i or the loop won't terminate
     }

如果循环体不修改 i 的值,则 i 始终为 0,循环条件永远成立。

for 语句头中的多个定义

可以在 for 语句的 init-statement 中定义多个对象;但是不管怎么样,该处只能出现一个语句,因此所有的对象必须具有相同的一般类型:

     const int size = 42;
     int val = 0, ia[size];
     // declare 3 variables local to the for loop:
     // ival is an int, pi a pointer to int, and ri a reference to int
     for (int ival = 0, *pi = ia, &ri = val;
           ival != size;
           ++ival, ++pi, ++ri)
                   // ...


do while 语句

在实际应用中,可能会要求程序员编写一个交互程序,为用户实现某种计算。

一个简单的例子是:程序提示用户输入两个数,然后输出读入数之和。

在输出和值后,程序可以让用户选择是否重复这个过程计算下一个和。

程序的实现相当简单。只需输出一个提示,接着读入两个数,然后输出读入数之和。输出结果后,询问用户是否继续。

关键在于控制结构的选择。问题是要到用户要求退出时,才中止循环的执行。

尤其是,在第一次循环时就要求一次和。do while 循环正好满足这样的需要。

它保证循环体至少执行一次。

     do
             statement
     while   (condition);


<Note>:

while 语句不同。do-while 语句总是以分号结束。


在求解 condition 之前,先执行了 do 里面的 statementcondition 不能为空。如果 condition 的值为假,

则循环结束,否则循环重复执行。使用 do while 循环,可以编写程序如下:

     // repeatedly ask user for pair of numbers to sum
     string rsp; // used in the condition; can't be defined inside the do
     do {
        cout << "please enter two values: ";
        int val1, val2;
        cin  >> val1 >> val2;
        cout << "The sum of " << val1 << " and " << val2
             << " = " << val1 + val2 << "\n\n"
             << "More? [yes][no] ";
        cin  >> rsp;
     } while (!rsp.empty() && rsp[0] != 'n');

循环体与之前编写的其他循环语句相似,因此很容易理解。奇怪的是此代码把 rsp 定义在 do 之前而不是在循环体内部。

如果把 rsp 定义在 do 内部,那么 rsp 的作用域就被限制在 while 前的右花括号之前了。

任何在循环条件中引用变量都必须在 do 语句之前就已经存在。

因为要到循环语句或者语句块执行之后,才求解循环条件,因此 do while 循环不可以采用如下方式定义变量:

     // error: declaration statement within do condition is not supported
     do {
         // ...
         mumble(foo);
     } while (int foo = get_foo()); // error: declaration in do condition
如果可以在循环条件中定义变量的话,则对变量的任何使用都将发生在变量定义之前!

break 语句

break 语句用于结束最近的 whiledo whileforswitch 语句,并将程序的执行权传递给紧接在被终止语句之后的语句。例如,下面的循环在 vector 中搜索某个特殊值的第一次出现。一旦找到,则退出循环:

     vector<int>::iterator iter = vec.begin();
     while (iter != vec.end()) {
        if (value == *iter)
             break; // ok: found it!
        else
             ++iter; // not found: keep looking
     }// end of while
     if (iter != vec.end()) // break to here ...
         // continue processing

本例中,break 终止了 while 循环。执行权交给紧跟在 while 语句后面的 if 语句,程序继续执行。

break 只能出现在循环或 switch 结构中,或者出现在嵌套于循环或 switch 结构中的语句里。

对于 if 语句,只有当它嵌套在 switch 或循环里面时,才能使用 break

break 出现在循环外或者 switch 外将会导致编译时错误。

break 出现在嵌套的 switch 或者循环语句中时,将会终止里层的 switch 或循环语句,而外层的 switch 或者循环不受影响:

     string inBuf;
     while (cin >> inBuf && !inBuf.empty()) {
         switch(inBuf[0]) {
         case '-':
             // process up to the first blank
             for (string::size_type ix = 1;
                         ix != inBuf.size(); ++ix) {
                   if (inBuf[ix] == ' ')
                        break; // #1, leaves the for loop
                   // ...
             }
             // remaining '-' processing: break #1 transfers control here
             break; // #2, leaves the switch statement
         case '+':
             // ...
         } // end switch
         // end of switch: break #2 transfers control here
     }  // end while

#1 标记的 break 终止了连字符('-')case 标号内的 for 循环,但并没有终止外层的 switch 语句,而且事实上也并没有结束当前 case 语句的执行。

接着程序继续执行 for 语句后面的第一个语句,即处理连字符 case 标号下的其他代码,或者执行结束这个 casebreak 语句。

#2 标记的 break 终止了处理连字符情况的 switch 语句,但没有终止 while 循环。

程序接着执行 break 后面的语句,即求解 while 的循环条件,从标准输入读入下一个 string 对象。

continue 语句

continue 语句导致最近的循环语句的当次迭代提前结束。对于 whiledo while 语句,继续求解循环条件。而对于 for 循环,程序流程接着求解 for 语句头中的 expression 表达式。

例如,下面的循环每次从标准输入中读入一个单词,只有以下划线开头的单词才做处理。如果是其他的值,终止当前循环,接着读取下一个单词:

     string inBuf;
     while (cin >> inBuf && !inBuf.empty()) {
             if (inBuf[0] != '_')
                  continue; // get another input
             // still here? process string ...
     }
continue 语句只能出现在 forwhile 或者 do while 循环中,包括嵌套在这些循环内部的块语句中。


try 块和异常处理

在设计各种软件系统的过程中,处理程序中的错误和其他反常行为是困难的部分之一。

像通信交换机和路由器这类长期运行的交互式系统必须将 90% 的程序代码用于实现错误检测和错误处理。

随着基于 Web 的应用程序在运行时不确定性的增多,越来越多的程序员更加注重错误的处理。

异常就是运行时出现的不正常,例如运行时耗尽了内存或遇到意外的非法输入。

异常存在于程序的正常功能之外,并要求程序立即处理。

在设计良好的系统中,异常是程序错误处理的一部分。当程序代码检查到无法处理的问题时,异常处理就特别有用。在这些情况下,

检测出问题的那部分程序需要一种方法把控制权转到可以处理这个问题的那部分程序。

错误检测程序还必须指出具体出现了什么问题,并且可能需要提供一些附加信息。

异常机制提供程序中错误检测与错误处理部分之间的通信。C++ 的异常处理中包括:

  1. throw 表达式,错误检测部分使用这种表达式来说明遇到了不可处理的错误。可以说,throw 引发了异常条件。

  2. try,错误处理部分使用它来处理异常。try 语句块以 try 关键字开始,并以一个或多个 catch 子句结束。在 try 块中执行的代码所抛出(throw)的异常,通常会被其中一个 catch 子句处理。由于它们“处理”异常,catch 子句也称为处理代码

  3. 由标准库定义的一组异常类,用来在 throw 和相应的 catch 之间传递有关的错误信息。

throw 表达式
系统通过 throw 表达式抛出异常。throw 表达式由关键字 throw 以及尾随的表达式组成,通常以分号结束,这样它就成为了表达式语句。

throw 表达式的类型决定了所抛出异常的类型。

try

try 块的通用语法形式是:

     try {
         program-statements
     } catch (exception-specifier) {
         handler-statements
     } catch (exception-specifier) {
         handler-statements
     } //...

try 块以关键字 try 开始,后面是用花括号起来的语句序列块。try 块后面是一个或多个 catch 子句。

每个 catch 子句包括三部分:关键字 catch,圆括号内单个类型或者单个对象的声明——称为异常说明符,以及通常用花括号括起来的语句块。

如果选择了一个 catch 子句来处理异常,则执行相关的块语句。一旦 catch 子句执行结束,程序流程立即继续执行紧随着最后一个 catch 子句的语句。

try 语句内的 program-statements 形成程序的正常逻辑。这里面可以包含任意 C++ 语句,包括变量声明。

与其他块语句一样,try 块引入局部作用域,在 try 块中声明的变量,包括 catch 子句声明的变量,不能在 try 外面引用。

编写处理代码

在前面的例子中,使用了 throw 来避免将两个表示不同书的 Sales_items 对象相加。

想象一下将 Sales_items 对象相加的那部分程序与负责与用户交流的那部分是分开的,则与用户交互的部分也许会包含下面的用于处理所捕获异常的代码:

     while (cin >> item1 >> item2) {
         try {
             // execute code that will add the two Sales_items
             // if the addition fails, the code throws a runtime_error exception
         } catch (runtime_error err) {
             // remind the user that ISBN must match and prompt for another pair
             cout << err.what()
                  << "\nTry Again? Enter y or n" << endl;
             char c;
             cin >> c;
             if (cin && c == 'n')
                 break;     // break out of the while loop
         }
     }

关键字 try 后面是一个块语句。这个块语句调用处理 Sales_item 对象的程序部分。

这部分也可能会抛出 runtime_error 类型的异常。


上述 try 块提供单个 catch 子句,用来处理 runtime_error 类型的异常。

在执行 try 块代码的过程中,如果在 try 块中的代码抛出 runtime_error 类型的异常,则处理这类异常的动作在 catch 后面的块语句中定义。

本例中,catch 输出信息并且询问用户是否继续进行异常处理。

如果用户输入'n',则结束 while;否则继续循环,读入两个新的 Sales_items 对象。


通过输出 err.what() 的返回值提示用户。大家都知道 err 返回 runtime_error 类型的值,因此可以推断出 whatruntime_error 类的一个成员函数)。

每一个标准库异常类都定义了名为 what 的成员函数。这个函数不需要参数,返回 C 风格字符串。在出现 runtime_error 的情况下,what 返回的 C 风格字符串,

是用于初始化 runtime_errorstring 对象的副本。如果在前面章节描述的代码抛出异常,那么执行这个 catch 将输出。

     Data must refer to same ISBN
     Try Again? Enter y or n


6.14. 使用预处理器进行调试

C++ 程序员有时也会使用类似的技术有条件地执行用于调试的代码。这种想法是:程序所包含的调试代码仅在开发过程中执行。

当应用程序已经完成,并且准备提交时,就会将调试代码关闭。可使用 NDEBUG 预处理变量实现有条件的调试代码:

     int main()
     {
     #ifndef NDEBUG
     cerr << "starting main" << endl;
     #endif
     // ...

如果 NDEBUG 未定义,那么程序就会将信息写到 cerr 中。如果 NDEBUG 已经定义了,那么程序执行时将会跳过 #ifndef#endif 之间的代码。

默认情况下,NDEBUG 未定义,这也就意味着必须执行 #ifndef#endif 之间的代码。在开发程序的过程中,只要保持 NDEBUG 未定义就会执行其中的调试语句。

开发完成后,要将程序交付给客户时,可通过定义 NDEBUG 预处理变量,(有效地)删除这些调试语句。大多数的编译器都提供定义 NDEBUG 命令行选项:

     $ CC -DNDEBUG main.C
这样的命令行行将于在 main.c 的开头提供 #define NDEBUG 预处理命令。

预处理器还定义了其余四种在调试时非常有用的常量:

__FILE__ name of the file.

__FILE__ 文件名

__LINE__ current line number.

__LINE__ 当前行号

__TIME__ time the file was compiled.

__TIME__ 文件被编译的时间

__DATE__ date the file was compiled.

__DATE__ 文件被编译的日期

可使用这些常量在错误消息中提供更多的信息:

     if (word.size() < threshold)
         cerr << "Error: " << _ _FILE_ _
              << " : line " << _ _LINE_ _ << endl
              << "       Compiled on " << _ _DATE_ _
              << " at " << _ _TIME_ _ << endl
              << "      Word read was " << word
              << ": Length too short" << endl;

如果给这个程序提供一个比 threshold 短的 string 对象,则会产生下面的错误信息:

     Error: wdebug.cc : line 21
               Compiled on Jan 12 2005 at 19:44:40
               Word read was "foo": Length too short

另一个常见的调试技术是使用 NDEBUG 预处理变量以及 assert 预处理宏

assert 宏是在 cassert 头文件中定义的,所有使用 assert 的文件都必须包含这个头文件。


预处理宏有点像函数调用。assert 宏需要一个表达式作为它的条件:

     assert(expr)
只要 NDEBUG 未定义,assert 宏就求解条件表达式 expr,如果结果为 falseassert 输出信息并且终止程序的执行。

如果该表达式有一个非零(例如,true)值,则 assert 不做任何操作。

与异常不同(异常用于处理程序执行时预期要发生的错误),程序员使用 assert 来测试“不可能发生”的条件。

例如,对于处理输入文本的程序,可以预测全部给出的单词都比指定的阈值长。那么程序可以包含这样一个语句:

     assert(word.size() > threshold);
在测试过程中,assert 等效于检验数据是否总是具有预期的大小。一旦开发和测试工作完成,程序就已经建立好,并且定义了 NDEBUG。在成品代码中,assert 语句不做任何工作,因此也没有任何运行时代价。

当然,也不会引起任何运行时检查。assert 仅用于检查确实不可能的条件,这只对程序的调试有帮助,但不能用来代替运行时的逻辑检查,也不能代替对程序可能产生的错误的检测。



















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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值