3.2 表达式——C语言的“词组”
表达式(Expression)类似于自然语言中的词组,是由C语言中某些种类的单词依照语法规则构成的。
更具体地说,表达式就是一个“初等表达式”(Primary expressions)(1)或者由“初等表达式”与运算符构成的一个合乎C语言语法的序列组合。
那么,什么又是初等表达式呢?
3.2.1 初等表达式
初等表达式有以下几种。
变量名(2)或函数名
常量
字符串文字量
此外,有一种被很多数人误解为运算的初等表达式。
3.2.2 被误解的“()”
(表达式)
一个被分隔符(3)“()”括起来的表达式也是一个初等表达式。在这里的()不是运算符,只是一个分隔符。只有在函数调用时跟在函数名之后的()才是运算符。
有人认为,不同的“()”可以被一勺烩地称为“圆括号”,并可以都被当作为运算符,甚至觉得这种运算符可以改变优先级。实际上,这完全忽视了不同场合下“()”有不同的含义这个性质。本书在前面多次提到过,C语言具有这样特点:同一个符号出现在不同的场合中具有不同的含义,“()”也是如此。
如下面一段代码。
程序代码3-1
printf、"%d %d\n"、i、(2 * i)、system、"PAUSE"、0等都是初等表达式。
初等表达式是完整独立的、不可分割的、构成表达式的基本单元之一。由于任何运算符都要求运算对象,所以运算符必须和初等表达式一起才能够构成表达式。
练习
说出程序代码2-14中有那些是初等表达式。
3.2.3 带运算符的表达式
带运算符的表达式由运算符和初等表达式构成。如:
3.2.4 不像表达式的表达式
有些表达式看起来不像表达式,比如:
1 注意后面没有“;”。
实际上,这的确是一个表达式。printf作为函数名是一个初等表达式,"abcd\n"作为裸串也是一个初等表达式。两者通过运算符“()”组合成了一个新的表达式。
()紧跟在函数名之后进行函数调用是一种运算,叫函数调用(Function Calls)运算,因此这种表达式可称之为函数调用表达式。这种运算的机制比较复杂,将在后面有关函数的章节详细介绍。在这里只要知道函数调用也是一个表达式就可以了。
3.2.5 表达式:专业与副业
表达式是用来求值的,因此每个表达式都有一个值。没有值的表达式被称之为void类型的表达式,这种数据类型的特征就是没有值(值为空集),常用于描述某些函数和某类指针。
void也是C语言的一个关键字,这个关键字是在C89中首次出现的。
表达式求值之外带来的一切效果都叫副效应(Side Effect)。比如:
这个表达式是求在标准输出上输出了几个字符,而输出字符则是它的一个副效应。关于表达式的副效应问题后面将进一步讨论。
3.2.6 赋值运算符左侧的标识符称为左值
有些表达式或子表达式(4)可能有两种含义:值或一块内存,根据代码的上下文才能完全确定究竟是哪种含义。比如下面的例子:
在这个表达式中,赋值号右面的表达式中的i可以理解为i所占据的那块内存中二进制数据的值,但赋值号左面的表达式i则应该理解为i所占据的那块内存。否则这个表达式的含义很难解释清楚。
表示值含义的表达式常常被称之为右值(Right Value, Rvalue)。有些表达式只有值的含义不可能表示一块内存,如i * 3。有些表达式可以表示值,也可以表示内存。例如前面表达式中的i。这种表达式表示值的含义时也是右值。但是当这种表达式表示一块内存(5)的含义时,则被称为左值(Left Value, Lvalue)。
尽管目前遇到的左值表达式只有变量名,但所谓左值、右值都是对表达式而言的。不是所有的左值都可以出现在赋值运算符的左侧,也不是只有变量名才能作左值,当然更不能说出现在赋值运算符右侧的表达式就一定是右值,这只有在具体的运算场合下才能完全确定。
只有左值才可以出现在赋值运算符左侧被赋值,这也是“左值”这个名字最初的由来——即可以出现在赋值运算符左边。然而随着语言的发展,现在这个概念变得有些复杂了,已经不是三言两语能说得清的了,请容后面再详细讨论。这里之所以提出这个概念是因为,在编译程序时可能出现这方面的错误,例如:
说的就是非左值被赋值这样的错误(6)。这里提示我们的是,运算符不仅应该与数据类型相匹配(double类型就不可以做%运算),同时可能还要遵守其他一些约束条件才可能构成合法的表达式。
有些表达式的值因为和我们的常识不符而今初学者感到有些别扭,比如:
在这个表达式中,(a=3)和(b=3)是两个初等表达式,初等表达式的值就是()内表达式的值。两个()内的表达式都是赋值表达式,而赋值表达式的值就是变量被赋的值,因此两个初等表达式的值都是3。(a=3)+(b=3)构成了一个加法表达式,这个加法表达式的值为6,这个值又与变量c及运算符=构成了另外一个赋值表达式,计算机在求这个赋值表达式的值的时候会为变量c赋值6,因此整个表达式的值为6。这里a、b、c都是左值表达式,但a=3、b=3和c=(a=3)+(b=3)不是左值表达式。
从这个例子里可以看到,正是由于C语言的表达式才使得C语言的表达能力很强、很灵活也很简洁,因而易于表达复杂的思想。所以理解表达式是理解语句的基础,而善用表达式是书写优美语句的前提。
3.2.7 函数调用是表达式不是语句
函数调用也是一种表达式,例如:
这个表达式是一个函数调用表达式,函数调用要求计算机先求出()里面各表达式的值,而求表达式printf("abcdef")的值将产生一个函数调用,在标准输出设备上输出abcdef,输出之后printf("abcdef")这个函数调用表达式将求得一个值——6(在标准输出设备上输出的字符数)(7),这样,求下面表达式的值:
其效果将是输出:
而表达式printf("%d", printf ("abcdef"))求得的值为1。下面的代码的运行结果可以证实这一点。
程序代码3-2
作为表达式,函数调用的真正意义在于求值。而其他的效果,比如在标准输出设备上输出文字,仅仅是求值之外顺带的副效应(Side Effect)而已。这就是C语言的处世方式,一切都是在求值,而不是“函数化”。当然,求值所收获的的可能不仅仅是值,还有副效应。
练习
1.输出表达式putchar('A')的值。
2.输出表达式getchar()的值,观察这个值是否是输入字符的ASCII码值。
3.设若有 int i;分别输出表达式scanf("%d",&i)在如下输入时的值。
练习
设若有 int i,j;分别输出scanf("%d%d",&i,&j)在如下输入时的值。