操作符详细解析(二)

上篇文章我们介绍了二进制,移位操作符,位操作符的相关知识,今天我们将继续学习操作符相关的知识。

1.逗号表达式

exp1, exp2, exp3, …expN
逗号表达式,就是⽤逗号隔开的多个表达式。
逗号表达式,从左向右依次执⾏。整个表达式的结果是最后⼀个表达式的结果。
示例代码:
int main()
{
	int a = 1;
	int b = 5;
	int c = (b = a + b, a - b, a + 9);
	printf("%d %d %d\n", a, b, c);//1 6 10
	return 0;
}
a = get_val();
count_val(a);
while (a > 0)
{
 //业务处理
 a = get_val();
 count_val(a);
}
如果使⽤逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
 //业务处理
}

2.下标访问[],函数调用()

2.1下标引用操作符[]

操作数:⼀个数组名 + ⼀个索引值
int arr[ 10 ]; // 创建数组
arr[ 9 ] = 10 ; // 实⽤下标引⽤操作符。
[ ] 的两个操作数是 arr 9

2.2函数调用操作符

接受⼀个或者多个操作数:第⼀个操作数是函数名,剩余的操作数就是传递给函数的参数。
# include <stdio.h>
void test1 ()
{
printf ( "hehe\n" );
}
void test2 ( const char *str)
{
printf ( "%s\n" , str);
}
int main ()
{
test1(); // 这⾥的 () 就是作为函数调⽤操作符。
test2( "hello bit." ); // 这⾥的 () 就是函数调⽤操作符。
return 0 ;
}

3.操作符的属性:优先级,结合性

C语⾔的操作符有2个重要的属性:优先级、结合性,这两个属性决定了表达式求值的计算顺序。

3.1优先级

优先级指的是,如果⼀个表达式包含多个运算符,哪个运算符应该优先执⾏。各种运算符的优先级是不⼀样的。
例如:3 + 4 * 5
该示例中,既有加法运算符,也有乘法运算符,乘法优先级高于加法,所以会先计算4*5,而不是先计算3+4

3.2结合性

如果两个运算符优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合,还是右结合,决定执⾏顺序。⼤部分运算符是左结合(从左到右执⾏),少数运算符是右结合(从右到左执⾏),⽐如赋值运算符( = )。
例如:5 * 6 / 2
上面示例中,* 和 / 的优先级相同,都是左结合运算符,所以从左到右执行。

在实际编程中我们并不需要记住所有操作符的优先级,如果不确定,我们可以用圆括号改变其他运算符的优先级(圆括号优先级最高)。

下面是 C语言操作符的优先级和结合性:

 参考:C 运算符优先级 - cppreference.comhttps://zh.cppreference.com/w/c/language/operator_precedence

4.表达式求值 

4.1整形提升

C语⾔中整型算术运算总是⾄少以缺少整型类型的精度来进⾏的。
为了获得这个精度,表达式中的字符和短整型操作数在使⽤之前被转换为普通整型,这种转换称为整型提升。
意义:
表达式的整型运算要在CPU的相应运算器件内执⾏,CPU内整型运算器(ALU)的操作数的字节⻓度⼀般就是int的字节⻓度,同时也是CPU的通⽤寄存器的⻓度。
因此,即使两个char类型的相加,在CPU执⾏时实际上也要先转换为CPU内整型操作数的标准长度。
通⽤CPU(general-purpose CPU)是难以直接实现两个8⽐特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种⻓度可能⼩于int⻓度的整型值,都必须先转换为int或unsigned int,然后才能送⼊CPU去执⾏运算。

如何进行整形提升

1. 有符号整数提升是按照变量的数据类型的符号位来提升的
2. ⽆符号整数提升,⾼位补0
// 负数的整形提升
char c1 = -1 ;
变量 c1 的⼆进制位 ( 补码 ) 中只有 8 个⽐特位:
11111111
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为 1
提升之后的结果是:
11111111  11111111  11111111  11111111
// 正数的整形提升
char c2 = 1 ;
变量 c2 的⼆进制位 ( 补码 ) 中只有 8 个⽐特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,⾼位补充符号位,即为 0
提升之后的结果是:
00000000  00000000  00000000  00000001
// ⽆符号整形提升,⾼位补 0

4.2算数转换

如果某个操作符的各个操作数属于不同的类型,那么除⾮其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就⽆法进⾏。下⾯的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上⾯这个列表中排名靠后,那么⾸先要转换为另外⼀个操作数的类型后执⾏运算。

5.问题表达式解析

5.1表达式1

a * b + c * d + e * f

表达式1在计算的时候,由于 * + 的优先级⾼,只能保证, * 的计算是⽐ + 早,但是优先级并不
能决定第三个 * ⽐第⼀个 + 早执⾏。

虽然这个表达式的优先级并不能影响到你的结果,但是这种写法是非常不推荐的,有潜在的风险。

5.2表达式2

c + --c;

同上,操作符的优先级只能决定⾃减 -- 的运算在 + 的运算的前⾯,但是我们并没有办法得知, + 操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

5.3表达式3

int main()
{
	int i = 10;
	i = i-- - --i * (i = -3) * i++ + ++i;
	printf("i = %d\n", i);
	return 0;
}

上述的这个代码在不同编译器甚至能得到不同的结果,虽然服务器没有报错,但这样的代码能称之为正确吗?

 5.4表达式4

int fun()
{
	static int count = 1;
	return ++count;
}
int main()
{
	int answer;
	answer = fun() - fun() * fun();
	printf("%d\n", answer);//输出多少?
	return 0;
}

 这个代码看着不没有什么实际问题,在大多数编译器上的结果也是相同的,但是answer = fun() - fun() * fun()这一行代码,我们知道他是先算乘法,再算加法,那么是哪个fun()函数先计算呢,我们并不能确定。

函数的调用先后顺序无法通过操作符的优先级确定。

5.5表达式5

int main()
{
	int i = 1;
	int ret = (++i) + (++i) + (++i);
	printf("%d\n", ret);
	printf("%d\n", i);
	return 0;
}

上述代码在不同编译器里也得到了不同的结果,因为第⼀个 + 在执⾏的时候,第三个++是否执⾏,这个是不确定的,依靠操作符的优先级和结合性是⽆法决定第⼀个 + 和第三个前置 ++ 的先后顺序。

即使知道了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯⼀的计算路径,一个表达式尽量不要太复杂。