4.C++中的语句类型详解及用法:从表达式到异常处理

一、前言

从较为底层的角度来看,C++程序其实就是一系列的函数调用,即使是所谓的类,依旧只是一系列的函数调用而已。

这涉及到比较底层的概念,有兴趣的可以自己搜索了解,或者直接阅读本站逆向系列的文章,里面就对其做了比较详细的介绍。

最直观的就是我们前面已经用到的main函数,它就是一个函数。

而一个函数的内部则是由一系列的语句所构成的,C++中存在好几种语句,大致可以分为以下这些:

  • 表达式语句
  • 声明语句
  • 复合语句
  • 条件语句
  • 循环语句
  • 跳转语句
  • 异常处理语句

这些语句看起来非常庞杂,但实际上并不需要考虑这么多,甚至你都不需要记它们的名字,写着写着便会了。

本文会大致介绍一下各类型语句长什么样子以及各自的注意事项。

二、表示式语句

首先是表达式语句,同时也是最常见的语句类型,由表达式和分号组成,用以执行计算或操作。

比如下面这几条语句都是表达式语句:

#include <iostream>
int main() {
    int x = 5;            // 赋值表达式语句
    x++;                  // 自增表达式语句
    std::cout << x;       // 函数调用表达式语句
    static_cast<void>(x); // 类型转换表达式语句
    ;                     // 空表达式语句
    return 0;
}

识别表达式语句非常简单,就是一条语句和一个分号组成,所以我们所写的大部分语句其实都是表达式语句。

而要注意的地方也是分号,新手写代码往往忘记添加分号、或者使用中文分号,从而导致编译器报错。

三、声明语句

声明语句可以算是表达式语句的子类,它同样是表达式,但作用在于声明。

比如下面三个例子:

int x;   // 变量声明语句
class MyClass {};  // 类型声明语句

这就是在分别声明变量、类。

声明正如其字面意思,仅仅只是声明,并没有实际操作,而与声明相对应的叫做定义,它便有了具体的操作:

int x=10; //定义变量
const double pi = 3.14;  // 定义常量

这里具体的操作指的就是给变量与常量赋值。

而现实开发变成中,大多数人其实并不会太过严格的去区分这两者之间的区别,至少在变量上是如此。

而如果涉及到了函数,那么这两者之间还是有很大的区别的。

你可以多次声明一个函数,就像下面这样:

void fun();
void fun();
void fun();
void fun();

由于这只是在声明,所以你写多个并没有影响,这只是在告诉编译器我们有这个函数。

但实际上到底有没有,取决于你写没写这个函数的定义:

void fun(){
	printf("www.kucoding.com");
}

所谓定义就是有没有包含函数体,只要包含了,就代表你定义了这个函数,同时代码中也只能存在一份函数定义。

四、复合语句

复合语句很简单,就是有多条语句组成的语句块,一般由大括号包裹,就像下面这样。

{
    int x = 5;
    x++;
    std::cout << x;
}

这看起来平平无奇,但如果你对比一下函数体,就会发现两者非常像:

void fun()
{
    int x = 5;
    x++;
    std::cout << x;
}

所以你完全可以将函数体看作是一个复合语句。

但大括号是可以单独使用的,这样做一般是为了限制变量的范围:

void fun()
{
	{
	    int x = 5;
	    x++;
	    std::cout << x;	
	}
	{
	    int x = 5;
	    x++;
	    std::cout << x;
	}
}

由于变量x在大括号内部,所以它的作用范围也被限制在了其大括号内部。

此时你会发现我可以同时定义两个同名的x变量,正常来说这在C++中是绝对不允许的,而这便是用大括号限制的好处。

五、条件语句

编程中只有三种结构,一种是默认的顺序结构,也是我们目前为止一直使用的结构,代码只能从上到下挨个顺序执行。

而除了顺序结构外,另外两个结构分别是选择结构、循环结构。

条件语句便属于选择结构,C++中用的最多的条件语句便是if:

int x=0;
if(x==0){
	printf("x==0");
}

以及它的扩展:

int x=0;
if(x==0){
	printf("x==0");
}else if(x==1){
	printf("x==1");
}
else if(x==2){
	printf("x==2");
}else{
	printf("x==其它");
}

中间的else if语句可以添加任意多个,最后可以用else语句收尾代表其它所有情况(也可以不写else语句)。

其含义非常明确,如果x等于0,就执行第一个“x==1”的语句,如果不等于0,就会继续判断x是否等于1,是否等于2,如果都不等于,就进入最后的else语句块中执行。

这就是条件语句的核心精髓,判断某个条件,当其满足时便去执行某些语句。

除了if语句外,C/C++中还存在另外一个switch语句,其作用与if相同,但在某些情况下会比if更加简洁:

int x=0;
switch(x)
{
	case 0:
		printf("x==0");
		break;
	case 1:
		printf("x==1");
		break;
	case 2:
		printf("x==2");
		break;
	default:
		printf("x==其它");
		break;
}

上面这条switch语句和前面的if语句效果完全等同,但看起来会更加优雅一些。

它通过判断传入x变量是否满足某个case语句后面的值,比如这里的0、1、2,如果满足就进入执行:后面的语句,遇到break就跳出switch语句块,否则会继续往后面执行,如果都不满足就会执行default后面的语句,它的作用等同于if语句中的else语句,用于兜底。

这里的break关键字的含义为跳出,如果不添加这个关键字:

	int x = 0;
	switch (x)
	{
	case 0:
		printf("x==0\n");
	case 1:
		printf("x==1\n");
	case 2:
		printf("x==2\n");
	default:
		printf("x==其它\n");
	}

此时就会出现一种情况,由于这里x==0,所以第一个case条件就会执行,但它执行完成后并不会立刻结束,只要没有遇到break,那么它就会继续执行后面的语句,直到全部执行完毕:

image.png

六、循环语句

除了顺序结构与选择结构外,最后一种便是循环结构。

顾名思义,循环结构就是能不断的循环执行某段语句,在C/C++中,存在三种循环语句写法。

首先是while语句:

int x=10;
while(x>0){
	printf("x=%d\n",x);
	x--;
}

上面的语句很简单,就是只要x大于0,那么就会一直反复去执行内部的语句,直到x不大于0为止。

而在while语句块的内部,我打印了每次循环时x变量的值,并且每次都让它自减1。

因此总结一下就是,这段语句会循环执行10次,并且会打印10-1这10个数值:

image.png

除了先判断后执行外,C++还支持先执行后判断的写法:

int x=10;
do{
	printf("x=%d\n",x);
	x--;
}while(x>0);

在这段语句中,两者最终打印的结果并没有什么区别,这种结构适用于第一次判断前需要先执行语句块中语句时使用。

但上面两种写法在很多时候都有些冗余,比如变量x对于这里来说,仅仅只是计数而已,但它却放在了while语句的外面,这会导致外层没法再声明变量名x了。

int x=10;
do{
	printf("x=%d\n",x);
	x--;
}while(x>0);

double x=100.0; //错误,x变量名已经被前面使用了

这显然不太好,所以C++还提供了for语句,可以简化这一流程:

for(int x=10;x>0;x--){
	printf("x==%d\n",x);
}

可以看到,在for语句中,不仅变量定义放在了for中,连变量执行语句x--同样也能放在for中。

这段代码与上面的while语句作用完全一致,但明显更加简洁优雅、不占用外面的变量名。

for结构中的()内部分为三个部分,由两个;进行分割,第一个部分用于定义变量,只会执行一次,第二个部分用于判断循环条件,每次都会判断,第三个部分用于执行循环操作,每次都会执行。

注意,这三个部分都是可以为空的,并且只要中间部分为空,那就是无限循环:

	for (int i = 0;; i++)
	{
		printf("%d\n", i);
	}

其等价于:

int i=0;
while(true){
	printf("%d\n", i);
	i++;
}

由于条件永远为真,那么该循环就永远不会退出。

七、跳转语句

跳转语句我们已经见过好几个了,首先便是最先见到的return语句:

void fun(){
	return;
	//不会执行
}

它的作用就是提前结束函数的执行,return语句后面的语句都不会被再执行。

如果一个函数有返回类型,那么return也必须要添加返回值:

int fun(){
	return 10;
	//不会执行
}

然后就是前面见过的break语句,它的作用是跳出循环或switch语句。

前面我们已经见过它跳出switch语句的作用了,事实上它最常用的还是跳出循环:

while(true){
	break;
	//不会执行
}

//执行

for(;;){
	break;
	//不会执行
}
//执行

即使是无限循环,只要遇到break语句,其后的代码都不会执行,循环立马结束。

而continue语句相比于while语句则要温和一些,它并不跳出循环,它仅仅只是跳过当前循环,进入下一次循环:

while(true){
	printf("111");
	continue; //跳过当前循环,不再执行后面的代码,进入下一次循环
	printf("222"); //永远不会被执行
}

比如上面这段代码中,会永远陷入死循环,不断打印”111“这个字符串,但却永远不会打印后面222这个字符串。

其原因就在于continue这条语句会跳过当前循环,进入下一次循环,所以永远都执行不到后面的语句

八、异常处理语句

相比于前面的语句,异常处理语句就显得要高级很多了,它涉及到了C++中的类这一概念。

这里简单聊聊,后面文章会对其做出更加详细的介绍。

总的来说,异常处理语句只有三个关键字:try、catch、throw

其中try用于捕获正常代码中的异常、catch用于处理捕获到的异常、throw用于抛出异常。

一个简单的例子如下:

    try {
        printf("111"); //正常执行
        throw 111; //抛出异常、进入catch块
        printf("222"); //不会执行
    }
    catch (int e) {
        printf("%d",e); //打印抛出的异常
    }

只要try语句块内部的语句中执行了throw关键字抛出一个值,那么try语句块中后面的代码都不会再被执行,而是直接跳转到catch语句块中执行,其得到的参数值就是其抛出的值。

如果使用throw语句抛出了异常值,其外层却没有使用try…catch语句进行捕获的话,程序就会直接崩溃。

这仅仅只是一个简单的样例,演示了这三个关键字的作用,在实际开发中,其逻辑会很复杂。

比如,其内部抛出的可能并不是数字,而是字符串,此时怎么办呢?

答案就是写多个catch语句:

    try {
        printf("111"); //正常执行
        throw 111; //抛出异常、进入catch块
        printf("222"); //不会执行
    }
    catch (int e) {
        printf("%d",e); //打印抛出的异常
    }
    catch (const char* e) {
        printf("%s", e); //打印抛出的异常
    }

但更多时候,抛出的异常并不像是在这里,直接在try语句块中抛出,我们可以轻易直到它的类型,而是在try语句块中调用了一个函数,该函数又调用了子函数,一层一层调用,然后其中某个子函数抛出了异常、一层一层往外抛,直到抛到我们这里的try语句。

这时候定义一种通用的异常类型就显得很重要了,否则我们很难使用catch语句去捕获所有可能类型,而这便涉及到了C++中的异常类。

作者:余识
全部文章:0
会员文章:0
总阅读量:0
c/c++pythonrustJavaScriptwindowslinux