C语言之表达式

C语言的一个特点就是它更 多地强调表达式而不是语句。表达式是表示如何计算值的公式最简单的表达式是变量和常量。变量表示程序运行时需要计算的值,常量表示不变的值,更复杂的表达式把运算符用于操作数(操作数自身就是表达式)。在表达式a+ (b*c)中,运算名用于操作数a和(b*c),而这两者自身又都是表达式。

运算符是构建表达式的基本工具,C语言拥有异常丰富的运算符。首先,C语言提供了基本运算符,这类运算符存在于大多数编程语言中。

算术运算符,包括加、减、乘和除。
关系运算符进行诸如“i比0大”这样的比较运算。
逻辑运算符实现诸如“i比0大并且i比10小”这样的关系运算。

1.算术运算符

算术运算符是包括C语言在内的许多编程语言中广泛应用的一种运算符,这类运算符可以
执行加法、减法、乘法和除法。下表展示了C语言的算术运算符。

C语言算术运算符包括加、减、乘、除、取余运算符,它们的图形如下:

加法运算符:+

减法运算符:-

乘法运算符:*

除法运算符:/

取余运算符:%

运算符/和运算符8需要特别注意以下几点。

1.运算符/可能产生意外的结果。当两个操作数都是整数时,运算符/会丢掉分数部分来“截

取”结果。因此,1 /2的结果是0而不是0.5。

2.运算符%要求操作数是整数。如果两个操作数中有一一个不是整数, 程序将无法编译通过。

3.把0用作/或8的右操作数会导致未定义的行为。

1.2 运算符的优先级和结和性

当两个或更多个运算符出现在同一个表达式中时,

可以按运算符优先级从高到低的次序重
复给于表达式添加圆括号,由此来确定编译器解释表达式的方法。下面的例子说明了这种结果:

i+j*k       /*==i+(j*k)*/


-i*-j       /*==(-i)*(-j)*/


+i+j/k  /*==(+i)+(j/k)*/

当表达式包含两个或更多个相同优先级的运算符时,仅有运算符优先级规则定个的用时。这种情况下,运算符的结合性开始发挥作用。如果运算符是从左向右结合的,那么称这种运算符是左结合的。二元算术运算符(即*、/、%、+和-)都是左结合的,所以

i-j-k   ==   (i-j)-k
i*j/k   ==   (+i)+(j/k)

程序:计算通用产品代码的校验位

美国和加拿大的货物生产商会在超市销售的每件商品上放置一一个条形码。 这种被称为通用产品代码(Univeral Product Code, UPC )的条形码可以识别生产商和产品。每个条形码表示一个12位的数,通常这个数会打印在条形码下面。例如,以下的数字来自Soufer's法式面腊肠比萨的包装:
01113800'1517315
数字0 13800 15173 5出现在条形码的下方。第1个数字表示商品的种类(大部分商品用0或者7表示,2表示需要称量的商品,3表示药品或与健康相关的商品,而5表示赠品)。第一组5位数字用来标识生产商( 13800是雀巢美国的冷冻食品公司的代码)。第二组5位数字用来标识产品(包括包装尺寸)。最后一位数字是“校验位”,它唯一的作用是帮助识别前面数字中的错误。如果条形码扫描出现错误,那么前11位数字可能会和最后一位数字不匹配, 超市扫描机将拒绝整个条形码。

下面是计算校验位的方法: 首先把第1位、第3位、第5位、第7位、第9位和第11位数字相加; 然后把第2位、第4位、第6位、第8位和第10位数字相加;接着把第一次加法的结果乘以3,再和第二次加法的结果相加;随后再把上述结果减去l; 相减后的结果除以10取余数;最后用9减去上-一步骤中得到的余数。

用Stouffr's的例子,我们由0+3+0+1+1+3得到第一个和8, 由1+8+0+5+7得到第二个和21。把第-个和乘以3后再加上第二个和得到45,减1得到44把这个值除以10取余数为40再用9减去余数4,结果为5。

需求:编写一个程序来计算任意通用产品代码的校验位。要求用户输人通用产品代册的前的几位数字,然后程序显示出相应的校验位。为了避免混淆,要求用户分3部分输人数字:左边的第一个数字、第一组5位数字以及第二组5位数字。

#include <stdio.h>
int main()
{
	int d, i1, i2, i3, i4, i5, j1, j2, j3, j4, j5, first_sum, second_sum, total;
	printf("Enter the first (single) digit:");
	scanf_s("%1d", & d);
	printf("Enter firstgroup of five digits:");
	scanf_s("%1d%1d%1d%1d%1d", &i1, &i2,& i3, &i4, &i5);
	printf("Enter second group of five digits:");
	scanf_s("%1d%1d%1d%1d%1d", &j1, &j2, &j3, &j4, &j5);
	first_sum = i1 + i2 + i3 + i4 + i5;
	second_sum = j1 + j2 + j3 + j4 + j5;
	total =3*first_sum + second_sum;
	printf("Check digit=%d", 9-((total-1)%10));
	return 0;
}

 运行结果:

Enter the first (single) digit:0
Enter firstgroup of five digits:15700
Enter second group of five digits:23456
Check digit=1

2赋值运算符

复合赋值( compound assignment)运算符
简单赋值
表达式v=e的赋值效果是求出表达式e的值,并把此值复制到V如下面的例子所示,e可以是常量、变量或更为复杂的表达式:

i=5;
/*iisnow5 */
j=i
/*jisnow5 */
k=10*i+j;
/*kis now55*/


如果v和e的类型不同,那么赋值运算发生时会把e的值转换为v的类型:
 

int i
float f;
i = 72. 99f;
/*iisnow72*/
f=136;
/* f is now 136.0 */

在许多编程语言中,赋值是语句;然而,在C语言中,赋值就像+那样是运算符。换句话说,赋值操作产生结果,就如同两个数相加产生结果一样。赋值表达式v= e的值就是赋值运算后v的值。因此,表达式i = 72.99f 的值是72 (不是72.99 )。

2.1左值


大多数C语言运算符允许它们的操作数是变量、常量或者包含其他运算符的表达式。然而,赋值运算符要求它的左操作数必须是左值( lvalue )。左值表示对象,而不是常量或计算的结果。变量是左值,而诸如10或2 * i这样的表达式则不是左值。目前为止,变量是已知的唯一左值。

既然赋值运算符要求左操作数是左值,那么在赋值表达式的左侧放置任何其他类型的表达式都是不合法的:

12=i;       /*** WRONG ***/
i+j=0;      /*** WRONG ***/
-1=j;       /*** WRONG ***/

编译器会检测出这种错误,并给出"ivaid valule in asgiman这样的出错消息。

2.2复合赋值


利用变量的原有值计算出新值并重新赋值给这个变量,这种操作在C语言程序中是非常普
遍的。例如,下 面这条语句就是把变量i的值加上2后再赋值给它自己:
i =i+2;
C语言的复合赋值运算符允许缩短这个语句以及类似的语句。使用+=运算符,可以将上面
的表达式简写为
i += 2; /* sameasi=i+2;*/
+=运算符把右操作数的值加到左侧的变量中去。
还有另外

C语言中有9种复合赋值运算符,分别是:

  1. += 加和赋值运算符,例如 a += b 等价于 a = a + b
  2. -= 减和赋值运算符,例如 a -= b 等价于 a = a - b
  3. *= 乘和赋值运算符,例如 a *= b 等价于 a = a * b
  4. /= 除和赋值运算符,例如 a /= b 等价于 a = a / b
  5. %= 模和赋值运算符,例如 a %= b 等价于 a = a % b
  6. <<= 左移和赋值运算符,例如 a <<= b 等价于 a = a << b
  7. = 右移和赋值运算符,例如 a >>= b 等价于 a = a >> b

  8. &= 按位与和赋值运算符,例如 a &= b 等价于 a = a & b
  9. ^= 按位异或和赋值运算符,例如 a ^= b 等价于 a = a ^ b

注意:在使用复合赋值运算符时,注意不要交换组成运算符的两个字符的位置。交换子好位置产生的表达式也许可以被编译器接受,但不会有预期的意义。例如,原本打丹与表达式主+= j,却写成了i=+j,程序也能够通过编译。但是,后一个表达式i=+j等价于表达式i = ( +j ),只是简单地把j的值赋给i


2.3自增运算符和 自减运算符

最常用于变量的两种运算是“自增”(加1)和“自减(减1)当然,也可以通过下列方式完成这类操作:

i=i+1;
j=j-1;


复合赋值运算符可以将上述这些语句缩短一些:

i+=1;
j-=1;


而c语言允许用++ (自增)和-- (自减)运算符将这些语句缩得更短些。

在同一个表达式中多次使用+ +或-运算符,结果往往很难理解。思考下列语句:

i =1;
j=2;
k =++i +
j++;


在上述语句执行后,i、j和k的值分别是多少呢?因为i是在值被使用前进行自增,而i是在值被使用后进行自增,所以最后一一个语句等价于

i=i+1;
k=i+j;
j=j+1;


因此,最终i、j和k的值分别是2、3和4。如果执行语句

i=
1;
j=2;
k = i+++j++;


i、j和k的值将分别是2、3和3。后缀+和后缀一比一元的正号和负号优先级高,而且这两个后缀都是左结合的。前缀+和前缀与一元的正号 和负号优先级相间,而且这两个前缀都是右结合的。

3. 表达式求值

下面是 C 语言中常用运算符的优先级从高到低的顺序:

  1. (),[],->
  2. ++,--
  3. !,~,+,-,*
  4. /,%
  5. +,-
  6. <<,>>
  7. <,<=,>,>=
  8. ==,!=
  9. &
  10. ^
  11. |
  12. &&
  13. ||
  14. ?: (三目运算符)
  15. =,+=,-=,*=,/=,%=,<<=,>>=,&=,^=,||

请看以下示例:

#include <stdio.h>
int main() {
    int a = 10, b = 5, c = 2;
    int d;
    d = a + b * c;
    printf("a + b * c = %d\n", d);
    d = (a + b) * c;
    printf("(a + b) * c = %d\n", d);
    d = a > b && b > c;
    printf("a > b && b > c = %d\n", d);
    d = (a > b) && (b > c);
    printf("(a > b) && (b > c) = %d\n", d);
    return 0;
}

输出结果:

a + b * c = 20
(a + b) * c = 30
a > b && b > c = 1
(a > b) && (b > c) = 1

从这个例子中,我们可以看到运算符优先级的影响:

  • 在第一行中,使用了乘法运算符,因为它的优先级高于加法运算符,所以先计算 b * c,再加上 a,得到结果 20
  • 在第二行中,使用了括号来改变优先级,因为括号的优先级最高,所以先计算 a + b,再乘上 c,得到结果 30
  • 在第三行和第四行中,使用了逻辑运算符 &&,因为它的优先级低于比较运算符,所以先计算 a > bb > c 的值,再使用逻辑运算符进行运算,得到结果 1。注意,第三行中没有使用括号来改变优先级,所以先计算了 a > bb,再计算 b > c,这会导致结果不正确。所以需要使用括号来明确优先级。

3.1子表达式的求值顺序

有了运算符的优先级和结合性规则,我们就可以将任何C语言表达式划分成子表达式。如果表达式是完全括号化的,那么这些规则还可以确定唯一的添加圆括 号的方式。与之相矛盾的是,这些规则并不总是允许我们确定表达式的值,表达式的值可能依赖于子表达式的求值顺序。

C语言没有定义子表达式的求值顺序[除了含有逻辑与运算符及逻辑或运算符、各件运算符以及逗号运算符的子表达式。因此,在表达式(a+b)" (c-d)中,无法确定子表达式(a + b)是否在子表达式(c - d)之前求值。
不管子表达式的计算顺序如何,大多数表达式有相同的值。但是,当子表达式改变了某个操作数的值时,产生的值可能就不一致了。

a=5;
c=(b=a+2)-(a=1);


第二条语句的执行结果是未定义的,C标准没有规定。对大多数编译器而言,c的值是6
或者2。如果先计算子表达式(b = a + 2),那么b的值为7,C的值为6。但是,如果先计算
子表达式(a = 1),那么b的值为3,c的值为2。

在表达式中,既在某处访问变量的值又在别处修改它的值是不可取的。表达式(b =a + 2)-(a= 1)既访问了a的值(为了计算a + 2),又(通过赋值为1)修改了a的值。有些编译器在遇到这样的表达式时会产生一条类似“operation on‘a' may beundefined"的警告消息。
为了避免出现此类问题,-个好主意就是不在子表达式中使用赋值运算符,而是采用一串分离的赋值表达式。例如,上述语句可以改写成如下形式:

a=5;
b=a+2;
a=1;
c=b-a;


在执行完这些语句后,c的值始终是6。

注意:

C语言中未定义行为(Undefined Behavior,UB)是指编写程序时出现的不确定行为,因为C标准没有规定某些情况下程序应该如何行动或者规定的行为对于编译器实现是自由的。

在以下情况下可能会发生未定义行为:

1.未初始化的变量:如果使用未初始化的变量或指针,则可能会产生未定义的行为。

2.溢出:如果整数或浮点数发生溢出(超出类型所能表示的范围),则会发生未定义的行为。

3.访问越界:如果访问数组中的非法位置,则可能会产生未定义的行为。

4.除以零:如果试图除以零,则会发生未定义的行为。

5.类型转换:如果进行不允许的类型转换,则可能会产生未定义的行为。

6.多次修改同一变量:如果一个变量被多次修改并且没有同步,则可能会发生未定义的行为。

未定义行为可能会导致程序的预期行为不同于实际行为,甚至可能导致崩溃或安全漏洞。因此,在编写C语言程序时,应避免产生未定义的行为。

3.2.表达式语句

C语言有一条不同寻常的规则,那就是任何表达式都可以用作语句。换句话说,不论表达式是什么类型、计算什么结果,我们都可以通过在后面添加分号将其转换成语句。例如,可以把表达式++i转换成语句
执行这条语句时,i先进行自增,然后把新产生的i值取出(与放在表达式中的效果一样)。但是,因为++i不是更长的表达式的一部分,所以它的值会被丢弃,执行下一条语句。(当然,对i的改变是持久的。)

例如,以下是一些使用表达式语句的示例:

  • x = 5; (将5赋值给变量x)
  • y = x + 3; (将变量x的值加上3,然后将结果赋给变量y)
  • print("Hello world!"); (调用print函数,将“Hello world!”打印到控制台上)

在这些示例中,每个语句都是单独的一行,但也可以将多个表达式语句组合在一起,形成一个代码块。

3.3表达式必须包含类类型

表达式必须包含类类型是C++语言中的概念,它表示一个表达式必须包含至少一个类类型的对象或指针。在C++中,类是一种重要的数据类型,可以描述具有某些属性和方法的对象。因此,在使用类的成员函数或成员变量时,需要使用类的实例或指针。

举例来说,假设有一个名为Person的类,其中包含了一个名为age的成员变量和一个名为printAge的成员函数。如果要调用printAge函数,则必须先创建一个Person类的实例或指针,否则会出现表达式必须包含类类型的编译错误。

 

以下是一个示例代码:

#include <iostream>

class Person {
public:
    int age;

    void printAge() {
        std::cout << "My age is " << age << std::endl;
    }
};

int main() {
    // 创建Person类实例并调用printAge函数
    Person p;
    p.age = 29;
    p.printAge();

    // 创建Person类指针并调用printAge函数
    Person* pp = new Person();
    pp->age = 30;
    pp->printAge();
    delete pp;

    return 0;
}

在上面的示例代码中,首先创建了一个Person类的实例并调用了它的printAge函数。然后,创建了一个指向Person类的指针,并同样调用了它的printAge函数。需要注意的是,在使用类指针时,需要使用箭头运算符(->)来访问成员函数和成员变量。

 


 

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值