C是一种具有模块化设计的命令式编程语言,具有简约、直观的设计风格,与相对清晰、简单的语言结构。 在谈C的语言结构之前,需要先解释一些基本元素的含义。 一、表达式表达式是一个或多个变量、常量、函数与运算符按照特定规则的组合,表达式根据特定的优先级与运算符进行计算并返回一个值。 注意:单个变量、常量或函数名也是一个表达式。 以下面表达式为例: var=fn(1)+5 其中var、fn、1、5都是表达式,其返回值为自身的值;fn(1)也是一个表达式,返回函数调用的返回值;fn(1)+5也是一个表达式,返回算术运算的结果;var=fn(1)+5也是一个表达式,返回赋值号左边的值,此例中此值被丢弃。 特别地,调用返回值为void类型的函数将返回一个void类型的值,但此值无法被使用,只能丢弃。 1、完整表达式如果一个表达式不是其他表达式的子表达式,则称这个表达式为“完整表达式”。 以下面几个语句为例: var=1+2; fn(var+1); if(var+1); ?表达式语句中的整个表达式为完整表达式,如上面的var=1+2和fn(var+1),但第二行的var+1不属于完整表达式。(函数调用实际上是运算符()对函数指针和参数进行运算) ?if、while、switch括号中的表达式以及for括号中的每个分量都是完整表达式,所以第三行的var+1是完整表达式。 二、副作用除了返回值以外对程序造成的其他影响称为副作用。比如修改变量的值,执行I/O操作等。 对于如下表达式: var=5 表达式的返回值为5,副作用为将5赋值给变量var。 而对于以下表达式: 1+2 表达式返回3,没有副作用。 通常说起“副作用”,总是觉得无关紧要或尽量避免,但对于命令式编程语言来说,副作用才是程序执行的主要目的。 比如我们调用printf函数,我们通常并不关心它的返回值,而是需要它把特定的字符输出到屏幕,而标准输出正是这个函数的副作用。 三、语句语句是C的基本执行单元,语句不返回结果,仅执行副作用。语句可分为简单语句和复合语句。 在C语言中,“;”不是分隔符(for语句中的“;”除外),而是大部分语句的结尾。 申明不属于语句,因为申明通常不产生副作用,即使有时候会产生副作用(如初始化),但仍不将其视为语句,申明也以“;”结尾。 C有5种语句: ?表达式语句 ?跳转语句 ?选择语句 ?循环语句 ?标签语句 1、简单语句和复合语句简单语句指内部不包含其他语句的语句。如表达式语句和跳转语句。最简单的语句是只有一个“;”的空语句。 复合语句的定义与简单语句相反,即其内部有其他语句。 将几个语句用{}括起来就形成了复合语句“块”,最简单的复合语句是空块{}。 复合语句可以进行多次复合,比如块可以嵌套,复合语句的子语句可以是其他复合语句。 C语言没有elseif关键字,这种语法结构只是将上一个if语句的else部分复合了另一个if语句,将他们写在一起是为了使代码更简洁。 2、表达式语句表达式语句为一个完整表达式后跟一个分号构成的语句。若表达式为空,就构成了空语句。 表达式语句是最简单也是最常见的语句。以下语句都是表达式语句: ; 1+2; var=5; printf("hello,world\n"); 3、跳转语句跳转语句用于改变代码的执行顺序。跳转语句包括continue、break、return、goto语句。 4、选择语句选择语句是复合语句,其作用是根据特定表达式的值对程序执行进行跳转。如if、ifelse、switch语句。 5、循环语句循环语句是复合语句,其作用是根据特定表达式的值让一部分代码反复执行多次,如while、dowhile、for语句。循环语句也可以通过选择语句和跳转语句实现。 6、标签语句在其他语句前加上标签即是标签语句。标签语句是复合语句,可以在任何语句(包括标签语句)前添加标签。 因为申明不是语句,所以不能在申明前添加标签。对于下面的代码,gcc给出如下错误提示: lable: intvar=0; error:alabelcanonlybepartofastatementandadeclarationisnotastatement case标签是一种特殊的标签,其标志是在标签前的case关键字。case标签只能在switch语句中使用,case标签允许且只允许标签名使用整数,并且把标签的作用域限定在当前的switch语句中。 标签是语句的一部分,而不只是个记号,所以块末尾不能是标签。 比如下面语句: switch(var){ case1: case2: case3: ; } 最后的分号是不可以省略的,空语句复合case3标签形成标签语句,然后又复合case1和case2,所以这个块内只有一条完整的复合语句。 四、C语言结构C语言代码文件包括源文件和头文件,源文件可以进行编译和链接,头文件一般通过预处理指令包含到源文件中使用。 源文件由预处理指令、申明、类型定义、函数定义和注释组成。 预处理指令和注释可以出现在源文件的任何位置而不影响其功能,而申明和类型定义的位置决定了其作用域。 申明有时会伴随定义,定义一定会包含申明。 函数定义由返回值类型、函数名、参数列表和语句块组成。语句只能出现在函数定义内部。 C源文件必须有且只能有一个main函数,C89规定,main函数的返回值必须为int类型,如果程序正常终止,应返回0。 标准的main函数应写为intmain(void);或intmain(intargc,charconst*argv[]);。 五、序列点C语言通过序列点控制副作用的执行。在该点处之前的求值的所有的副作用已经发生,在它之后的求值的所有副作用仍未开始。 序列点的存在一定程度上保证了程序按照预期执行,但仍存在一些未定义的行为。 C中的序列点很少,因为C追求效率,更少的序列点可以给编译器更多优化的空间。 注意:C中有很多符号同时承担多种功能,在不同语境下扮演不同的身份。 C的序列点包括: 1、与运算符 与 运算符会先对左边的表达式求值并执行副作用。 对运算符来说,只有当左边表达式的值为1时才对右边的表达式求值并执行副作用。 这是对程序的一种优化,因为根据“与”逻辑,如果左边表达式的值为0,则总表达式的值定为0,无需对右边表达式进行计算。根据这一特性,可以写出更加符合人类逻辑的代码。 if(var!=03==/var){} 如果没有此序列点,则可能会出现0做除数的错误。 运算符同理,只有当左边表达式的值为0时才对右边的表达式求值并执行副作用。 2、逗号运算符“,”在C语言中有很多用途,在某些地方它是分隔符,在某些地方它是运算符。比如以下表达式: var=1,var=2 这里的“,”不是分隔符,而是运算符。此逗号运算符的两边是两个赋值表达式,逗号表达式先对左边的表达式求值并执行副作用,此时var的值被修改为1,之后对右边的表达式求值并执行副作用,var的值被修改为2,最后,逗号表达式返回右边表达式的值,即2。 逗号表达式的特性可以使两个表达式像两个表达式语句那样执行,适合用在需要用表达式代替语句块的地方,如for语句的括号内。 3、三元运算符?:中的?在?前的表达式求值并执行副作用后,才判断返回其后哪个表达式的值。并且,如果确定返回某个表达式的值,则不会对另一个表达式求值或执行副作用。 ?:表达式的这个特性使其行为与ifelse表现一致。 4、完整表达式的末尾完整表达式的末尾也是一个序列点,这保证了表达式语句的副作用按照其书写顺序执行。 同时,根据前面对完整表达式的定义,if、while、switch括号中的表达式以及for括号中的每个分量都是完整表达式,这些表达式的副作用也都会在语句其他部分开始前执行。 5、函数调用与返回函数调用时参数列表中的逗号不是表达式,而是分隔符。 参数列表的求值顺序是未定义的,比如fn(a++,b--),a++和b--的求值顺序是未知的,取决于编译器。 此处的序列点表现为在进入函数前,所有表达式的副作用都已经完成;函数返回时,返回值已经拷贝到调用处。 6、初始化末尾因为初始化是申明的一部分,不属于语句或表达式,所以不能套用表达式的说法,但其表现是类似的。 如下申明: intvar=5; 在分号前已经完成副作用,即把var初始化为5。 7、初始化列表中的逗号分隔符初始化列表中的逗号是分隔符而不是运算符。 初始化列表中的表达式按照从左到右的顺序求值并执行副作用。 如下代码: intvar=0; intarray[]={var++,var++,var++}; 8、申明中的逗号分隔符申明中的逗号是分隔符而不是运算符。 如下代码: inta=0; intb=a++,c=a++; b、c分别被初始化为0、1。 而且,在逗号前的变量已经申明完成,逗号后的则不然。 如下代码: inta=0,b=a;//Correct intc=d,d=0;//Error 在申明b前,a已经申明并初始化完成,所以可以用a初始化b。而在申明c时还没有申明d,所以初始化会报错。 因为缺少序列点,C会产生很多未定义的行为。最典型的例子是: intvar=0; var=var++; 根据优先级,表达式var=var++的值是确定的,然而赋值和自增副作用的执行顺序是未定义的,所以var的值是未知的。如果用gcc编译这段代码,var的值为0,比较符合预期;但在VC++中,var的值为1。 所以我们应避免在表达式中同时使用某一变量和它的自增表达式。 最后,不管你是转行也好,初学也罢,进阶也可,如果你想学编程~ 我的C/C++编程学习交流俱乐部: 问题答疑,学习交流,技术探讨,还有超多编程资源大全,零基础的视频也超棒~ 点击下方↓原文链接即可直接进入~~~ Z萌宝
|