语句

语句

使用范围 for 语句(C++11)

范围 for 语句遍历给定序列中的每个元素并对序列中的每个值执行某种操作,语法形式是

1
2
for (声明 : 表达式)
语句;

其中 “表达式” 代表的是一个序列,“声明” 部分负责定义一个变量,这个变量负责访问序列中的基础元素,每次迭代后,“声明” 就会被初始化为 “表达式” 的下一个元素值。例如

1
2
3
string str("some string");
for (auto c : str) //采用auto来让编译器决定c的类型,其实c为char类型,其值是将str中的下一个元素拷贝得到的,所以更改c并不会影响str本身
cout << c << endl;

注:如果想改变序列的内容,必须把循环变量定义为引用类型。如

1
2
3
4
string s("Hello World!!!");
for (auto &c : s)//这里是引用,就相当于绑定到s中的每个字符了。
c = toupper(c);
cout << s << endl;

确保类型相容的最好办法就是使用 auto 类型说明符。

使用范围 for 语句处理多维数组

1
2
3
4
5
6
7
8
int ia[3][4] = {{0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}};
size_t cnt = 0;
for (auto &row : ia)
for (auto &col : row)
{
col = cnt;
++cnt;
}

注:要使用范围 for 语句处理多维数组,除了最内层的循环外,其他所有循环的控制变量都应该是引用类型。如

1
2
3
for (const auto &row : ia) //防止外循环控制变量被自动转成指针
for (auto col : row) //如果外层不是引用的话,编译器自动推断得到的row的数据类型就是 int*,内层循环将会在 int* 内遍历,就不合法了。
cout << col << endl;

switch 表达式与 case 标号

switch 求解的表达式可以定义和初始化一个变量:

1
switch(int ival = get_response());

case 的标号必须是整型常量表达式。

对于 switch 结构,只能在它的最后一个 case 标号或者 default 标号后定义变量。这样做是为了避免出现代码跳过变量的定义和初始化的情况。

goto 语句

goto 语句不能跨越变量的定义语句 向前 跳转,如

1
2
3
4
goto end; //不行,因为goto语句和end标签中间隔着一个定义语句。
int ix = 0;//这就是ix的定义
end:
ix = 42;

如果非要跨越定义语句的话,那这个定义必须放在语句块里面(相当于限制了这个变量的作用域),如

1
2
3
4
5
6
goto end; //这是跨越变量定义的goto语句
{
int ix = 10; //ix的作用域仅限这个语句块
}
end:
//此时ix不再存在

但是 goto 语句 向后 跳过已执行的变量定义语句是合法的。此处的 “向后” 和 “向前” 指的是逻辑上的前后关系。逻辑上向后代表的是前面的代码,向前同理。这种情况的原因是向前跳过定义语句时有可能后面代码里用了这个(被跳过定义的)变量。向后跳过的话就是撤销这个变量,等到程序运行到定义语句里面再对其进行定义。

try 块和异常处理

throw 表达式引发了异常条件

try 块来处理异常,以 try 关键字开始,并以一个或多个 catch 语句结束

由标准库定义的一组异常类,用来在 throwcatch 之间传递有关错误的信息

throw 表达式

使用 throw 可以将对数据的操作和负责和用户交互的部分分开,如

1
2
3
if(!a.equals(b))
throw runtime_error("a is not equals to b");
std::cout << a + b << std::endl; //这样就不用写很多的if-else处理语句了

try 块

通用语法形式如下

1
2
3
4
5
6
7
8
try
{
正常C++代码都可以放在这里; //注意这里也是有作用域的,此处定义的变量出了语句块就用不了了
}
catch (异常说明符)
{
处理语句; //也是有作用域的!
}

函数在寻找处理代码的过程中退出

如果函数出现了 try 块的嵌套,寻找处理代码的过程与这个嵌套调用的过程相反,如果找不到处理函数,程序就要跳转到 terminate 函数,其在中定义。没有 try 块的话就直接跳到 terminate 函数。

标准异常

标准异常类库定义在4个头文件里面:

:类名就是 exception ,只是通知了有异常产生,没有更多信息。

:常见标准异常类,如下。每一个类里面都有一个 what() 函数,该函数返回一个字符串。

1
2
3
4
5
6
7
8
9
10
exception //最常见的问题
runtime_error //运行时错误:仅在运行时检测到的错误
range_error //运行时错误:生成的结果超出了有意义的值域范围
overflow_error //运行时错误:计算上溢
underflow_error //运行时错误:计算下溢
logic_error //逻辑错误:可在运行前检测到的问题
domain_error //逻辑错误:参数的结果值不存在
invalid_error //逻辑错误:不合适的参数
length_error //逻辑错误:试图生成一个超出该类型最大长度的对象
out_of_range //逻辑错误:使用一个超出有效范围的值

除了 exception 这些类里面都定义了一个 string 类型的初始化式,就是调用 what() 时提供错误信息用的。如

1
throw runtime_error("error infomation");

:里面有一个 bad_alloc 类型,提供因为 new 不出来而产生的异常。

<type_info>:定义了 bad_cast 类型。

这两个异常类型没有字符串初始化式。但是有 what() 函数。

使用预处理器进行调试

可以在debug阶段把调试代码写进宏定义中,这样在debug时可以运行这些代码而在release时就不运行这些了,如

1
2
3
4
5
6
int main()
{
#ifndef NDEBUG
cerr << "starting main" << endl;
#endif
}

发布时只需要将main()前面加上 #define NDEBUG 即可

assert(expr);中定义,只要没定义NDEBUG,那么程序就会运行 assert 语句。他会求解条件表达式 expr,如果是 false 就输出信息并终止程序。所以断言语句就是来判断不可能条件的。一旦定义了NDEBUG,那么断言语句就不再生效了。所以它只对调试有帮助。