【C语言】操作符大全万字详解

昨天已成为历史,明天是未知的,而今天是上天赐予我们的礼物,这就是为什么我们把它叫做现在!——《功夫熊猫》


目录

1、操作符的分类

2、算术操作符

2.1+、-、*(加减除)

2.2 /、%(除取余)

3、移位操作符

3.1原码反码补码

3.2<<(左移操作符)

3.3>>(右移操作符)

4、位操作符

4.1& (按位与)

4.2 |(按位或)

4.3 ^ (按位异或)

5、赋值操作符

6、单目操作符

6.1!(逻辑反操作)

6.2-(负值)、+(正值)

6.3&(取地址)

6.4sizeof(取字节操作符)

6.5~(二进制位取反)

6.6--、++(前后置--、++)

6.7*(间接访问操作符、解引用操作符)

6.8sizeof和数组

7、关系操作符

8、逻辑操作符

8.1&&、||(逻辑与逻辑或)

9、条件操作符

9.1?:三目操作符

10、逗号表达式

11、下标引用、函数调用和结构成员

11.1[]下标引用操作符

11.2()函数调用操作符

11.3.、->结构体访问操作符

12、表达式求值

12.1一些有问题的表达式

12.2操作符优先级表格


前言:

大家好我是拳击哥,今天我给大家展现是C语言中各种各样的操作符。操作符是说明特定操作的符号,它是构造C语言表达式的工具,下面我就来介绍它们的详细用法,并且目录12.2中有从高到低的操作符优先级表格供大家参考。


1、操作符的分类

操作符分为:

  • 算术操作符
  • 移位操作符
  • 位操作符
  • 赋值操作符
  • 单目操作符
  • 关系操作符
  • 逻辑操作符
  • 条件操作符
  • 逗号表达式
  • 下标引用、函数调用和结构成员

2、算术操作符

算术操作符有:+(加)、-(减)、*(乘)、/(除)、%(取余、取模)

2.1+、-、*(加减除)

+、-、*(加减除)跟我们数学中的意思是一致的,我们来看一组代码:

#include<stdio.h>

int main()
{
	int a = 5;
	int b = 5;
	int c = 0;
	c = a + b;
	printf("%d\n", c);
	c = a - b;
	printf("%d\n", c);
	c = a * b;
	printf("%d\n", c);
	return 0;
}

输出结果:

10

0

25


2.2 /、%(除取余)

 /、%(除、取余)跟我们数学中的除法和取余有较大差别,我们先来看一组程序:

#include<stdio.h>

int main()
{
	int a = 10;
	int b = 3;
	printf("%d\n", a/b);//除法
	printf("%d\n", a%b);//取余
    printf("%f\n", a / 2.0);//除法可以有小数,%不允许有小数
	return 0;
}

输出结果:

3

1

5.000000


可以看到C语言中的除法返回值是除数,取余返回的是余数。并不像数学那般得到小数。并且C语言中只有除法/两边能有小数,取余%则不能。5.0000000因为浮点数编译器默认打印六位0,您可以在f前面加.n代表你要保留几位小数。如:%.3f保留三位小数。

  • 除了 % 操作符之外,其他的几个操作符(+、-、*、/)可以作用于整数和浮点数。
  • 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
  • % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

3、移位操作符

<<(左移)、>>(右移)

3.1原码反码补码

在计算机中数据是按二进制存储的,二进制有三种形式分为原码、反码、补码。数字是用原码表示的,但在内存中的存储是以补码的形式存储的。

int main()
{
	int a = 10;
	int b = -10;
	return 0;
}

以上定义了整形a和b两个变量,整数分为正数负数。正数负数补码的形式也是不同的。正数的原码、反码、补码都是一样的,且符号位为0。负数的反码是原码的符号位不变其余位按位与,补码则是反码加1。因为是32位操作系统,a和b是整形,整形占4个字节,所以有32个bit位。

首先我们看a的原码、反码、补码:

10的原码:00000000 00000000 00000000 00001010

10的反码:00000000 00000000 00000000 00001010

10的补码:00000000 00000000 00000000 00001010

我们可以看到正数的原码、反码、补码都是一样的,且符号位为0。符号位就是第一位数


 我们再来看b的原码、反码、补码:

-10的原码:10000000 00000000 00000000 00001010

-10的反码:111111111 111111111 111111111 111110101

-10的补码:111111111 111111111 111111111 111110110

负数的原码、反码、补码。负数的原码符号位是1,反码是原码的符号位不变其余位按位与,补码则是反码+1。

符号位不变就是这一串二进制的第一位1不变,其余位按位与的意思是其余的所有位的1变为0,0变为1。


3.2<<(左移操作符)

有了原码、反码、补码的理解后,我们来了解左移操作符(<<)。移位规则:左边抛弃、右边补0

#include<stdio.h>

int main()
{
	int a = 10;
	int b = -10;
	int c = a << 1;
	int d = b << 1;
	printf("%d ", c);
	printf("%d\n", d);
	return 0;
}

 输出结果:20 -20

左移操作符具体怎么移呢,左移时。这个数的补码往左移动一位,移动后的补码最左边的那一位丢弃最右边空着的位补0。我们来看一个图理解:

从侧面说明了左移一位也就代表着这个数乘以2,这个数可以是正数或负数。 


3.3>>(右移操作符)

(>>)右移操作符与左移操作符有些许不同分为两种;

  • 算术移位,左边用原该值的符号位填充,右边丢弃。
  • 逻辑移位,左边用0填充,右边丢弃。

#include<stdio.h>

int main()
{
	int a = 10;
	int b = -10;
	int c = a >> 1;
	int d = b >> 1;
	printf("%d ", a);
	printf("%d ", b);
	printf("%d ", c);
	printf("%d\n", d);
	return 0;
}

输出结果:10 -10 5 -5 


右移分为算术移位和逻辑移位,我们首先来看算术移位,算术右移就是整个补码往右边移动一位,移动完后,最左边的空着的位数补符号位,最右边的多出的一位丢弃。我们来看一个图理解:

从侧面说明了,这个数右移一位就是这个数除以2。以上程序就是算术右移。 


我们再来看逻辑移位,逻辑右移就是整个补码往右边移动一位,移动完后,最左边的空位补0,最右边多的一位丢弃。我们也来看一个图理解:


注意:一个数值在内存中存放的是补码,但是补码的二进制转换成的十进制数并不是我们实际的数值。当我们知道一个数值的补码时,正数的原反补码都是一样的我们直接化成十进制就好了。而负数的补码我们可以通过原码变成补码这个过程逆序求出原码。也就是补码-1然后符号位不变其余位取反得到的就是原码。

警告 :对于移位运算符,不要移动负数位。


4、位操作符

位操作符有:& (按位与)、| (按位或)、^ (按位异或)。他们的操作数必须是整数,我们来看一组代码:

#include<stdio.h>

int main()
{
	int a = 10;
	int b = -10;
	printf("%d ", a & b);
	printf("%d ", a | b);
	printf("%d\n", a ^ b);
}

输出: 2 -2 -4


 位操作符也是按照补码来进行运算的,首先我们得到a的补码

原码、反码、补码:00000000 00000000 00000000 00001010

我们再来求b的补码

原码:10000000 00000000 00000000 00001010

反码:11111111 11111111 11111111 11110101

补码:11111111 11111111 11111111 11110110

知道了a和b的补码,我们来讲解& (按位与)、| (按位或)、^ (按位异或)。


4.1& (按位与)

& (按位与)就是两个数的补码相与时只要有一个数为0则整个表达式的结果为0,我们来看一个图理解:


4.2 |(按位或)

 |(按位或)就是两数补码相或时只要有一个数为1,则整个表达式结果为1,我们也来看一个图理解:


4.3 ^ (按位异或)

^ (按位异或)就是两数补码异或时,两数不同时为1相同时为0,我们来看图理解:


🤼‍♀️ 练习:不创建临时变量,实现两个数的交换。

//方法1
#include<stdio.h>

int main()
{
	int a = 5;
	int b = 2;
	a = a + b;
	b = a - b;
	a = a - b;
	printf("a=%d b=%d\n", a, b);
	return 0;
}
//方法2
#include<stdio.h>

int main()
{
	int a = 5;
	int b = 2;
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("a=%d b=%d\n", a, b);
	return 0;
}

输出的都是:a=2 b=5 

方法2中用到了异或,异或在二进制是相同为0不相同为1,那么此题中是什么样呢:

第一步,a=a^b;把a,b化为二进制,a:101,b:010。异或后111,此时a=7

第二步,b=a^b;把此时的a,b化为二进制,a:111,b:010。异或后101,此时b=5

第三步,a=a^b;把此时的a,b化为二进制,a:111,b:101。异或后010,此时a=2


5、赋值操作符

=(赋值操作符)是一个很友好的操作符,当你初始化一个数后,你想更改这个变量。这时候赋值操作符就排上用场了,我们来看一组代码:

#include<stdio.h>

int main()
{
	int a = 520;
	a = 1314;
    printf("%d\n",a);
	return 0;
}

 输出:1314


符合赋值操作符:+=、-=、*=、/=、%=、>>=、<<=、&=、|=、^=。可见符合赋值操作符有多种多样,我们拿+=、-=、*=、/=、%=来举例,先看一组代码:

#include<stdio.h>

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

 输出结果:13 10 30 10 1

a += b<=>a = a+b,a -= b<=>a = a-b,a *= b<=>a = a*b,a /= b<=>a = a/b,a %= b<=>a = a%b。 (<=>是相等的意思)


6、单目操作符

单目操作符有:!(逻辑反操作)、-(负值)、+(正值)、&(取地址)、sizeof(取字节数)、~(二进制位取反)、--(前置、后置--)、++(前后置++)、*(解引用操作符)、强制类型转换,下面我就来一一介绍。

6.1!(逻辑反操作)

#include<stdio.h>

int main()
{
	int a = 0;
	if (!a)
	{
		printf("happy\n");
	}
	return 0;
}

 输出结果:happy

C语言中0表示假(false)非0表示真(true),所以上述程序a=0,!a就是非0。所以在任意一个表达前面加!号就是把这个表达式取反的意思。


6.2-(负值)、+(正值)

-(负值)、+(加值)很容易理解,我们来看一组程序:

#include<stdio.h>

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

 输出结果:10 -10 -10 10

我们可以看到+(正值)对各个数字没有影响。-(负值)就是把各个数字取反了。


6.3&(取地址)

&(取地址)见名知意,就是取出这个变量在内存中的地址,如定义一个整形变量,我想知道这个变量的地址这时候就可以用到&操作符。

#include<stdio.h>

int main()
{
	int a = 10;
	printf("%p\n", &a);
	return 0;
}

输出结果:001BF724

注意,定义一个变量,这个变量会在内存中随机找一个地址存储。因此每次输出的地址值都不是一样的。当然我们也可以取一个数组的地址,有以下程序:

#include<stdio.h>

int main()
{
	int arry[3] = { 1,2,5 };
	printf("%p\n", &arry);
	printf("%p\n", &arry[0]);
	return 0;
}

输出结果

001FF8E8
001FF8E8

可见取数组名的地址就是取整个数组的地址,也可以认为取数组名的地址就是取该数组第一个元素的地址。


6.4sizeof(取字节操作符)

当我们想要知道数据的类型占多少字节时可以用sizeof来求,当我们想知道数组的长度的时候也可以用sizeof来求。

#include<stdio.h>

int main()
{
	int arry[] = { 1,2,3,4,5,6,7,8,9,10 };
	printf("%d  ",sizeof(int));
	printf("%d\n",sizeof(arry)/sizeof(arry[0]));
	return 0;
}

输出结果:4  10 

整形int在内存中占4个字节可以用sizeof求得,数组大小可以由sizeof数组名/sizeof数组第一个元素如:sizeof(arry)/sizeof(arry[0])。

练习,以下程序的结果是什么:

#include<stdio.h>

int main()
{
	int a = 10;
	short s = 3;
	printf("%d ", sizeof(s = a + 3));
	printf("%d\n", s);
	return 0;
}

输出:2 3 

sizeof里面的表达式的最终结果返回给了s,而sizeof求的就是s的数据类型大小。也就是short的占字节数 。并且最终的s还是不变的,s只是在sizeof的()里面改变,实质上的s还是原来的s。


6.5~(二进制位取反)

来看一组代码:

#include<stdio.h>

int main()
{
	int a = 3;
	printf("%d", ~a);
	return 0;
}

输出结果:-4 

我们知道原、反、补码的运算后,很容易求出3的补码是00000000 00000000 00000000 00000011

对3的补码取反得到11111111 11111111 11111111 11111100,可以看出这是一个负数因为符号位为1

把这一串二进制化为原码为:10000000 00000000 00000000 00000100,所以最后结果为-4。

所以~波浪号是把一个数值的补码全部取反。因此一个正数或负数可能因为这个操作改变原来的值。


6.6--、++(前后置--、++)

我们经常用到的循环语句中常遇到这两种操作符,来看组代码:

#include<stdio.h>

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

输出结果:

a=11 b=11
a=12 b=11
a=11 b=11
a=10 b=11

b=++a时,a先自加了然后再把自加后的值赋值给b,此时运算顺序为先a=11,b=11;

b=a++时,a先把自己赋值给b然后再自增,此时运算顺序为先b=11,a=12;

b=--a时,a先自减然后再把自减后的值赋值给b,此时运算顺序为a=11,b=11;

b=a--时,a先把自己赋值给b然后再自增,此时运算顺序为b=11,a=10;

也就是当--或++在前面时,先进行自减或自增并且把自减或自增的值赋值给前者变量。当--或++在后面时,先不自减或自增把自身赋值给前者后,再进行自减或自增。以上就是位置导致的赋值优先级。


6.7*(间接访问操作符、解引用操作符)

这里的*号并不是乘号而是,间接访问的一个符号。通常在指针中我们用到这个符号,我们来看一组代码:

#include<stdio.h>

int main()
{
	int arry[3] = { 1,2,3 };
	int* p1 = arry;
	int* p2 = &arry[2];
	printf("%d ", *p1);
	printf("%d ", *p2);
	int a = 10;
	int* p3 = &a;
	*p3 = 20;
	printf("%d\n", a);
	return 0;
}

输出结果:1 3 20 

int* p1=arry,实际上p1指向的是arry数组的首地址,再对p1解引用也就是*p1得到的值就是p1指向的arry数组首地址的值

int* p2=arry[2],实际上p2指向的是arry数组的第三个地址因为数组下标从0开始的,再对p2解引用也就是*p2得到值就是p2指向的arry数组第三个元素的值 

int* p3=&a,就是p3指向a的地址,再把20赋值给解引用后的p3也就是*p3。因此原来a的地址里面的值被指针p3改变了。

以上就是*(解引用)的基本操作


6.8sizeof和数组

我们来看一组代码:

#include <stdio.h>
void test1(int arr[])
{
	printf("%d ", sizeof(arr));//(3)
}
void test2(char ch[])
{
	printf("%d\n", sizeof(ch));//(4)
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d ", sizeof(arr));//(1)
	printf("%d ", sizeof(ch));//(2)
	test1(arr);
	test2(ch);
	return 0;
}

 输出结果:40 10 4 4 

我们很容易看到(1)、(2)输出的是两个数组的总字节数。(1)中10个int类型就是40个字节,(2)中10个char类型就是10个字节。那为啥(3)、(4)输出的各是4呢,原因是数组在传参的时候传给形参的是数组的首地址 。并且数组在传参的时候可以是数组名也可以是指针,但有一点无论你传过去的是数组名亦或者是指针,形参都认为这是一个指针类型。指针类型占4个字节,无论是指针的char类型或者int类型甚至是double类型我们都按照4个字节来算,因此(3)、(4)输出4。


7、关系操作符

关系操作符有这些:>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、!=(不等于)  、==  (等于),相信大家都已经见过这些操作符了,我们来看一组代码:

#include<stdio.h>

int main()
{
	int a = 5;
	int b = 9;
	if (a > b)
		printf("a大于b\n");
	if (a < b)
		printf("a小于b\n");
	return 0;
}

输出结果:a小于b 

关系操作符常用在if判断语句中,它与数学表达的意思一样,只是写法不同。关系操作符判断的关系成立时返回是真,否则返回假。这与布尔类型(true和false)保持一致。注意,C语言中的等于是==而不是=,C语言中的=号是赋值的意思,==是等于的意思。


8、逻辑操作符

8.1&&、||(逻辑与逻辑或)

逻辑操作符有:&&、||。&&表示的是并且的意思,||表示的是或者的意思。我们就拿求闰年来做讲解:

#include<stdio.h>

int main()
{
	int year = 2400;
	if (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0))
	{
		printf("这是一个闰年\n");
	}
	return 0;
}

输出结果:这是一个闰年 

闰年的满足条件是能被4整数但不能被100整除或者能被400整除,这样的类型就可以用到逻辑操作符,并且&&和或者||。注意,&&是两边表达式都成立时才为真,||是两边表达式只要有一方成立时就为真。


一道笔试题,程序的输出结果是什么:

#include <stdio.h>
int main()
{
  int i = 0,a=0,b=2,c =3,d=4;
  i = a++ && ++b && d++;
  printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
  i = a++||++b||d++;
  printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
  return 0;
}

输出结果

a=1 b=2 c=3 d=4
a=2 b=2 c=3 d=4 

i=a++ && ++b && d++; 先拿a&&++b,此时(a&&++b)表达式已经为0因此++b不用执行,(a++&&++b)也为0,因此d++也不执行了。所以最后只有a自增了一次,其余的bcd都保持不变。

i=a++||++b||d++;先拿a与++b做||或运算,因为a非0因此a++为1因此(a++||++b)整个表达式为1,所以++b也不用运算,随之后面的d++也不用运算。

以上的题目就体现出了,&&是两边表达式都成立时才为真,||是两边表达式只要有一方成立时就为真。从侧面可以这样理解,&&两边表达式只要有一边为假整个表达式返回假另一个表达式不用判断了,||两边表达式只要有一边为真整个表达式返回真另一个表达式也不用判断了。


9、条件操作符

9.1?:三目操作符

条件操作符:exp1 ? exp2 : exp3。三个表达式组成的操作,因此我们称为的三目运算符。

我们来看一个程序,求两数之间的较大值:

#include<stdio.h>

int main()
{
	int a = 5;
	int b = 3;
	int max = 0;
	max = a > b ? a : b;
	printf("%d ", max);
	max = a < b ? b : a;
	printf("%d\n", max);
	return 0;
}

输出结果:5 5 

可见?号操作符是做一个判断作用的跟if语句一样返回的是真或假。:号操作符是做一个选择作用,当?号返回的是真就执行:号前面的表达式,否则就执行:号后的表达式。以上程序我们也可以写成这个样子:

#include<stdio.h>

int main()
{
	int a = 5;
	int b = 3;
	int max = 0;
	if (a > b)
	{
		max = a;
	}
	else
	{
		max = b;
	}
	return 0;
}

10、逗号表达式

逗号表达式:ex1,ex2,ex3,.....exn。逗号表达式,就是用逗号隔开的多个表达式。它从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

//代码1
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
//代码2
if (a = b + 1, c = a / 2, d > 0)

代码1输出的结果是13,尽管c = (a > b, a = b + 10, a, b = a + 1)的结果是最后一个表达式b=a+1的值,但在此之前的所有表达式都进行了运算。

代码2,if语句里面判断是最后一个逗号后面的表达式d>0。在此之前的表达式没有起到任何作用。


11、下标引用、函数调用和结构成员

11.1[]下标引用操作符

[]下标引用操作符就是我们数组中的[],那么[]的两边是有操作数的。分别是数组名和一个索引值,索引值是一个常量。我们来看一组代码:

#include<stdio.h>

int main()
{
	int arry[3] = { 1,2,3 };
    arry[2];
	return 0;
}

首先定义了一个整形数组arry有三个元素。那么arry[3]中的arry和3就是[]下标引用操作符前后的两个操作数。arry[2]也是如此,arry和2是[]的两个操作数。通过这一个操作可以找到该数组下标为几的地址里面存在的元素。 

最常见的应该就是定义一个数组,然后打印这个数组,我们用for循环来遍历这个数组,有以下程序:

#include<stdio.h>

int main()
{
	int arry[] = { 1,2,3,4,5,6,7,8,9 };
	int n = sizeof(arry) / sizeof(arry[0]);
	for (int i = 0; i <n; i++)
	{
		printf("%d ", arry[i]);
	}
	return 0;
}

输出结果:1 2 3 4 5 6 7 8 9 

我们遍历是从0到n-1结束,正好对应了数组的下标。这就是[]下标访问符的作用。 


11.2()函数调用操作符

函数调用操作符就是(),当我们创建一个函数的时候通过()来运算,我们来看一组代码:

#include<stdio.h>

#include<string.h>

int main()
{
	int len = strlen("abcdef");
	printf("%d\n", len);
	return 0;
}

输出结果:6 

函数调用操作符()的操作数是什么呢,拿上述程序来说。()左边的strlen是一个操作数,右边的"abcdef"也是操作数,只不过我们称"abcdef"为参数。当()的操作数只有一个的时候,代表这个函数没有任何的参数如strlen();也就是()里面没有参数;


11.3.、->结构体访问操作符

结构体访问操作符有两个一个是.(点号),->(箭头),点号是用来访问成员,->也是用来访问成员的。

  • .为结构体.成员名
  • ->为结构体指针->成员名

我们来看一组程序:

#include<stdio.h>

struct student
{
	char name[20];
	int age;
	char sex[5];
	int score;
};
int main()
{
	struct student stu1= { "张三",33,"男",66};
	printf("%s %d %s %d\n", stu1.name, stu1.age, stu1.sex, stu1.score);
	struct student* p = &stu1;
	printf("%s %d %s %d\n", (*p).name, (*p).age, (*p).sex, (*p).score);
	printf("%s %d %s %d\n", p->name, p->age, p->sex, p->score);
	return 0;
}

输出结果

张三 33 男 66 

张三 33 男 66 

张三 33 男 66 

上述程序中,自定义了一个结构体变量student里面成员变量有四个,和结构体student变量stu1。那么我们可以通过stu1来.号成员变量 ,或者把stu1的地址给一个结构体student指针变量p。p来.号成员变量也可以得到该成员变量,前提是对p进行解引用。

我们也可以用p->箭头来访问成员变量,既然p指向了stu1的地址。那么p指向stu1里面的成员的地址也可以得到该成员的值。


12、表达式求值

表达式的求值的顺序一部分是按照操作符的优先级和结合性决定的,同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型我们称为强制转换。我们来看一组程序:

#include<stdio.h>

int main()
{
	int a = 3;
	int b = 4;
	int c = a + a * b + b;
	int d = (int)3.14;
	printf("%d ", c);
	printf("%d\n", d);
	return 0;
}

输出结果:19 3

以上代码中c=a+a*b+b;中*号表达式优先级大于+号,所以是先a*b得到12然后再进行+号运算最终得到19。

int d=(int)3.14;中我希望d是一个整形的值,但是我赋值给d的是一个浮点型3.14。那么这时候我们就可以用强制类型转换把3.14强转为整形,如下图所示: 


上述代码中c=a+a*b+b;是先乘后加,那么我们想要先加后乘怎么做呢。我们可以把两个加法用()号括起来,()号的优先级是大于*号的,我们来看代码:

#include<stdio.h>

int main()
{
	int a = 3;
	int b = 4;
	int c = (a + a) * (b + b);
	printf("%d\n", c);
	return 0;
}

 输出结果:48

因此,我们想要表达式的顺序改变,可以将某一块代码用优先级高的操作符引起来,比如说()号。它是所有表达式中优先级最高的。


12.1一些有问题的表达式

//代码1
a* b + c * d + e * f;
//代码2
c++ --c;
//代码3
int main()
{
   int i = 10;
   i = i-- - --i*(i=-3)*i++ + ++i;
   printf("i=%d\n",i);
   return 0;
}

代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不
能决定第三个*比第一个+早执行。

所以计算机认为的顺序可能是:a*b,c*d,a*b+c*d,e*f,a*b+c*d+e*f或者:a*b,c*d,e*f,a*b+c*d,a*b+c*d+e*f


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


代码3在不同的编译器中测试的结果是不同的,如下表所示:

编译器
-128Tandy 6000 Xenix 3.2
-95Tink C 5.02(Macintosh)
-86IBM PowerPC AIX 3.2.5
-85Sun Sparc cc(K&C编译器)
-63gcc,HP_UX9.0,Power C 2.0.0

12.2操作符优先级表格

下表中从上到下优先级是由高到低的,结合性中的N/A是没有结合性,L-R是从左往右,R-L是从右往左。是否控制求值顺序意思什么呢,比如&&只要两边表达式有一个为假。整个表达式为假,那么&&它就是控制求值顺序的。

操作符描述       用法示例结果类型结合性是否控制求值顺序
()聚组(表达式)与表达式相同N/A
()函数调用rexp(rexp,...,rexp)rexpL-R
[]下标引用rexp[rexp]lexpL-R

.

访问结构成员lexp.member_namelexpL-R
->访问结构体成员rexp->member_namelexpL-R
++后缀自增lexp++rexpL-R
--后缀自减lexp--rexpL-R
逻辑反!rexprexpR-L
~按位取反~rexprexpR-L
+单目,正值+rexprexpR-L
-单目,负值-rexprexpR-L
++前缀自增++lexprexpR-L
--前缀自减--lexprexpR-L
*间接访问*rexplexpR-L
&取地址&rexprexpR-L
sizeof取字节数sizeof(类型或rexp)rexpR-L
(强转)类型转换(类型)rexprexpR-L
*乘法rexp*rexprexpL-R
/除法rexp/rexprexpL-R
%整数取余rexp%rexprexpL-R
+加法rexp+rexprexpL-R
-减法rexp-rexprexpL-R
<<左移rexp<<rexprexpL-R
>>右移rexp>>rexprexpL-R
>大于rexp>rexprexpL-R
>=大于等于rexp>=rexprexpL-R
<小于rexp<rexprexpL-R
<=小于等于rexp<=rexprexpL-R
==等于rexp==rexprexpL-R
!=不等于rexp!=rexprexpL-R
&按位与rexp&rexprexpL-R
|按位或rexp|rexprexpL-R
^按位异或rexp^rexprexpL-R
&&逻辑与rexp&&rexprexpL-R
||逻辑或rexp||rexprexpL-R
?:条件操作符rexp?rexp:rexprexpN/A
=赋值rexp=rexprexpR-L
+=以..加lexp+=rexprexpR-L
-=以..减lexp-=rexprexpR-L
*=以..乘lexp*=rexprexpR-L
/=以..除lexp/=rexprexpR-L
%=以..取余lexp%=rexprexpR-L
<<=以..左移lexp<<=rexprexpR-L
>>=以..右移lexp>>=rexprexpR-L
&=以..与lexp&=rexprexpR-L
|=以..或lexp|=rexprexpR-L
^=以..异或lexp^=rexprexpR-L
,逗号rexp,rexp,...,rexprexpL-R

本期博文到这里就结束,感谢各位的耐心观看

 Never Give Up

  • 44
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 41
    评论
评论 41
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一只爱打拳的程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值