C语言操作符详解

目录

前言

一、操作符的分类

二、各个操作符的详细分析

1、算术操作符

 2、移位操作符

2.1左移操作符

2.2右移操作符

2.2.1逻辑右移

2.2.2算术右移

3、位操作符

3.1  按位与  &

3.2按位或  | 

3.3按位异或  ^

3.4一些细节问题和一些练习:

4.赋值操作符

4.1复合赋值符

5.单目操作符

单目操作符介绍:

单目操作符详解:

!                逻辑取反操作符

-        负值                +        正值

&        取地址                *        解引用操作符

++和--

 ~        二进制按位取反

 sizeof                   求操作数的类型的长度(单位:字节)

(类型)                   强制类型转换

6.关系操作符

7.逻辑操作符

&&        逻辑与

||        逻辑或

8.条件操作符

 9.逗号表达式

10.下标引用、函数调用和结构体成员

10.1下标引用操作符  [ ]

10.2函数调用操作符        ( )

10.3结构体成员访问操作符

11.表达式求值

11.1隐式转换

11.2算术转换

11.3操作符的属性

12.一些问题表达式:


前言

在前面几期 初识C语言 的文章中介绍过一些操作符,例如算术操作符(+ - * / %),逻辑操作符(&&  ||   !)以及位操作符(& | ^)等;其实还有很多操作符或者一些细节性的知识没有介绍到。例如:移位操作符和它的细节、逗号表达式以及操作符的属性、优先级、控制求值顺序等。本期小编将一一介绍清楚这些操作符以及其细节。

一、操作符的分类

1、算术操作符

2、移位操作符

3、位操作符

4、赋值操作符

5、单目操作符

6、关系操作符

7、逻辑操作符

8、条件操作符

9、逗号表达式

10、下标引用、函数调用和结构体成员操作符

二、各个操作符的详细分析

1、算术操作符

+        -        *        /        %

加、减、乘 和数学中的一样应该都没有问题,这里就不在啰嗦了!

/   取商 操作符  需要注意的是:该操作符的两边如果操作数都是整数时结果为整数。两操作数至少有一个是浮点数时结果为浮点数!另外除数不能为0!

看例子:

int main()
{
	printf("%d\n", 3 / 2);
	printf("%lf\n", 3.0 / 2);
	printf("%lf\n", 3 / 2.);
	printf("%lf\n", 3. / 2.);
	printf("%lf\n", 3.0 / 2.0);
	return 0;
}

看结果:

 除了 % 操作符意外,其他算术操作符都可以用于一个操作数是整数一个个是小数!

% 取余(模)操作符  它的两个操作数都必须为整数,返回的是相除后的余数!

栗子:

int main()
{
	printf("%d\n", 5 % 2);
	printf("%d ", 5 % 3);
	return 0;
}

运行结果:

 2、移位操作符

首先要说明的一点是:移位操作符和后面的位操作符的位是二进制位!而二进制有三种形式:

原码、反码、补码!

原码:就是按照十进制写出来对应的二进制序列!

反码:原码符号位不变其他位按位取反

补码:反码加1

正数的原反补三码相同

而负数的则要按照上面的规则进行计算(其实还有一种计算原返补的方法后面数据的存储会介绍)!!二进制序列中:0代表正,1代表负,最高位是符号位,其他位是数值位

一般进行操作的是补码,因为内存中存的是补码(后面会正在数据的存储中详解)而打印的是原码!!!

举个栗子:

移位操作符的分类: 

左移: <<                 右移: >> 

注意:移位操作符的操作数一定只能是整数 ,且被移动的那个变量值的本身是不变的!

2.1左移操作符

规则:左边抛弃,右边补 0

例如:int a = 1; 把 a 左移1位,  b = 2, b 左移1 位!

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

我们先来根据上面的说的规则来分析一下(a 和b 均为int 类型32个Byte):

通过按照规则移动后我们发现,a << 1 == 2   b << 1 == 4;我们来一起看看结果:

果然一样,我们还发现,a = 1;当a  << 1 后变成了2,b = 2,b << 1后变成了4,其实不难发现 << z左移有乘 2 的作用(其实他比乘的效率还高,他直接移动二进制位来实现的),而a和b本身的值没变!这与上面说的一致且 a 和 b 都是正数 原返补一样!

我们来个负数的:-2  << 2; 

int main()
{
	int x = -2;
	printf("x << 2 = %d\n", x << 2);
	printf("x = %d\n", x);
	return 0;
}

代码运行:

 代码分析:

2.2右移操作符

右移操作符分为为两种,逻辑右移和算术右移!

2.2.1逻辑右移

规则:右边丢弃,左边补0

2.2.2算术右移

规则:右边丢弃,左边补符号位

那么问题来了,我们平时用的到底是算术右移还是逻辑右移呢?答案是:一般的编译器都采用时算术右移。C语言本身没有规定时算术右移还是逻辑右移,取决于编译器!

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

看结果:如果是-1则证明是算术右移,否则就是逻辑右移:

 果然是算术右移!

注意:对于移位操作符而言不能移动的数是负数,这个是C语言标准未规定的!!!

例如:3 << -1 或  5 >> -2;这两种都是未规定的!!!

3、位操作符

位操作符有三种: &(按位与)、|(按位或)、^(按位异或)

注意:他们和上面的移位操作符一样,操作数必须是整数!

3.1  按位与  &

语法规则:两个位上都是1,&后的结果是1,只要有一个位上的是0,则&的结果为0

举个栗子:3 & 5

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

我们还是先来分析一下:

让我们 看看结果是不是1呢?

3.2按位或  | 

语法规则:两个位上至少有一个是1,则 | 后的结果是1,两个位都为0则, | 后的结果是 0;

举个例子:7 | 5

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

分析:

运行结果:

3.3按位异或  ^

语法规则:当两个位相同时为0,相异时为1

 举个例子:2 ^ 3

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

分析:

运行结果:

注意:位操作符实际上操作的是其二进制的补码,这几个例子都是正数(原反补一样)我们来看一负数的 -1 ^ 3:

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

 先分析:-1的补码是32个1:

分析结果应该是-4:

3.4一些细节问题和一些练习:

介绍完了位操作符,接下来就不得不介绍一下一道很奇葩的面试题了:

写一个代码实现:交换两个变量的值(不能创建第三个临时变量)

这道题我给出两种解法:

(1)加减实现:

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a=%d b = %d\n", a, b);
	a = a + b;
	b = a - b;
	a = a - b;
	printf("交换后:a=%d b = %d\n", a, b);
	return 0;
}

这种思路还是相对容易的也是很容易让人想到的!但,这种方法有一个缺点就是:如果两个数的值很大时a = a +b就会溢出,相应的后面的计算就会出现问题!因此这不是一个最好i的方法!

(2)按位异或实现:

int main()
{
	int a = 3;
	int b = 5;
	printf("交换前:a=%d b = %d\n", a, b);
	a = a ^ b;
	b = a ^ b;
	a = a ^ b;
	printf("交换后:a=%d b = %d\n", a, b);
	return 0;
}

不知道您能不能看懂?我来在大概说一下吧:我们发现一个变量与他本身异或的时候是0,与0异或的时候是本身!

了解到这里,我们就可以很好的了解上面的代码了:a = a ^ b;   b =  a ^ b;(b = a ^ b ^b;由上面可知b^ b == 0;0 ^ a==a,然后把a 赋值给b),a = a ^ b;同理!!!而且用异或你根本不用担心溢出的情况,因为他连进位都没有!!!

咋样,还好吧!!我们再来一道:

写一个代码实现:给定的一个整型数组中只出现一次的元素!

 看到这个题我猜您的第一思路一定是:用两个循环来解决也就是我们说的暴力求解!当让这种方法没问题,可行!但是不是太麻烦了呀!有没有好方法呢?我们还是异或实现!

int main()
{
	int arr[] = { 1,2,3,4,5,4,3,2,1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	int temp = 0;
	for (int i = 0; i < sz; i++)
	{
		temp ^= arr[i];
	}
	printf("%d\n", temp);
	return 0;
}

哈哈,是不是感觉很神奇!这样的还有好多,小编后面会介绍到的!

4.赋值操作符

赋值操作符,就是给变量等赋值的!

举个栗子:

int main()
{
	int a = 0;
	a = 3;
	return 0;
}

定义了一个变量a并赋值为0.下面一行对a进行了再赋值为3!赋值操作符还可以来连续赋值!例如:

int main()
{
	int a = 0;
	int b = 2;
	int x = 5;
	a = b = x = 20;
	return 0;
}

先把20赋值给x,x在赋值给b,b在赋值a;这样的代码存在的问题是可读性不高!!如果你这样写代码估计你会被你的老大"鼓励"的。

其实他可以写成:x = 20; b = x; a = b;这样就很了,可读性变高了!

4.1复合赋值符

+=        -=        *=        /=         %=        >>=        <<=        &=        |=        ^=

这些复合符都是有一些简单步骤复合起来的!例如:

int main()
{
	int a = 0;
	for (int i = 0; i < 5; i++)
	{
		a += i;//a = a + i;
		printf("%d\n", a);
	}
	return 0;
}

同理:&=    就是:a &= b《===》 a = a & b其他也是一样的,不在多做解释!

5.单目操作符

要介绍单目操作符之前我们要先了解一些知识:例如什么是操作数?

什么是操作数?

操作数就是操作符执行的参数的个数!例如:=  他的操作数就有两个,一个接受,一个赋值!

什么是单目操作符?

单目操作符就是只有一个操作数的操作符!

单目操作符介绍:

!                        逻辑取反

-                           负值

+                          正值

&                          取地址

sizeof                   求操作数的类型的长度(单位:字节)

~                          对一个数的二进制进行按位取反

--                          减减(分为前置--和后置--)

++                        加加(分为前置++和后置++)

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

(类型)                   强制类型转换

单目操作符详解:

!                逻辑取反操作符

在C语言中,0表示假,非0表示真,但一般对假取反后是1。比如:int  a = 1; !a == 0;    int b = 0; !b == 1;

验证一下:

逻辑取反这个操作符一般用于条件判断(举个栗子):

int main()
{
	int a = 5;
	int temp = 0;
	for (int i = 0; i < 10; i++)
	{
		if (i != a)
		{
			temp += 1;
			printf("%d ", temp);
		}
	}
	return 0;
}
-        负值                +        正值

这两个操作符就是表示正负的和数学中的一样!前面初识C语言中也说过而且难度不大,不在多说了!

&        取地址                *        解引用操作符

这两个操作符应运于指针:举个栗子:

int main()
{
	int a = 3;
	int* pa = &a;
	*pa = 5;
	printf("%d\n", a);
	return 0;
}

解释:

先用 &(取地址操作符)取出a 的地址,我们知道变量的地址是一个十六进制的序列也就是一个数,取出来的数要存起来,就得要一个变量----指针变量pa里面,int * pa = &a;意思是,int *的这个*说明pa是一个指针变量,指向的是int类型。再用 *(解引用操作符)找到a的那块空间,并把里面的值改为5!

说到这里不知您是否理解,没理解也没有关系,下一期我就来介绍指针会再详细的介绍一遍!

++和--

++分为前置++和后置++

前置++是,先自加1后使用,后置++是先使用在自加1。

--也是分为前置--和后置--

前置--是,先自减1后使用,后置--是先使用在自减1。

举个例子:(后置++,--)

int main()
{
	int a = 0;
	int b = 10;
	for (int i = 0; i < 10; i++)
	{
		if (i < 10)
		{
			a++;
			b--;
		}
		printf("第%d次 a = %d b = %d\n", i, a, b);
	}
	return 0;
}

a 应该是每次加1的,b应该是每次减1的:看结果:

 栗子2:(前置--,++)

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

 ~        二进制按位取反

既然是二进制位操作的必然是补码了!这个操作符也很简单,就是把二进制位上的1变0,0变1;

举个例子:

int main()
{
	int a = 5;
	a |= (1 << 1);
	printf("%d\n", a);
	a &= (~(1 << 1));
	printf("%d\n", a);
	return 0;
}

这个代码说的是把 a = 5改成a = 7再把a = 7 改回a = 5,这里之所以选这种方法是为了让大家来了解~操作符!!!

 另外,如果你刷过题的话,你绝对碰到过多组输入的问题!C语言中多组输入的方式,小编知道的有三种,这里就介绍 ~ 的这种。

int main()
{
	int a = 0;
	int b = 0;
	while (~scanf("%d%d", &a,&b))
	{
		printf("%d + %d = %d\n", a, b, a + b);
	}
	return 0;
}

先来看效果:

我们看到已经实现了多组输入的效果,但为什么呢?其实原理很简单,当我们通过scanf输入数后scanf 的返回值的补码不全为1(即-1,-1实际上是EOF(sizeof读取失败就会返回EOF也就是-1))~ 后的值就不是0,就可以多组输入了!!那么怎么让他停下来呢?ctrl +z 就可以停下来,小编的vs2019可能有bug要按3次!

 sizeof                   求操作数的类型的长度(单位:字节)

说到 sizeof 很多人以为他是一个函数,其实不是,他是一个单目作符。其作用是计算数组、变量以及类型的空间大小,单位是字节!

举个栗子:

int main()
{
	int a = 3;
	int b = -6;
	int* pa = &a;
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof a );
	printf("%d\n", sizeof(int));
	printf("%d\n", sizeof(b));
	printf("%d\n", sizeof(pa));
	printf("%d\n", sizeof(int *));
	return 0;
}

看上面代码,如果说sizeof是函数的话,那他的后面的括号是不可能去掉的,而这里不仅去掉了而且还能正常运行!所以这里就证明了sizeof是操作符不是函数!!!

a和b都是int 类型的所以都是4个字节,pa 是int*指针类型的!指针类型大小是看平台的,32位平台上是4个字节,64位平台上8个字节。

sizeof和++的一个坑:

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

先看这段代码,思考一下结果是多少?

我分先来分析一下:++是后置++所以先使用在++,i是int类型所以是4,然后i++变成了2!所以打印结果是4 2

我们来看看结果:

 ???,怎么是4  1。其实原因是当sizeof的操作数是一个表达式时,这个表达式根本不会计算!!!只是按照规则判断表达式的类型,然后返回类型的大小即可!!所以,是4   1!

下来我们再来看看数组与sizeof的关系:

先来看一段代码:

void test1(int arr[])
{
	printf("%d\n", sizeof(arr));
}

void test2(char str[])
{
	printf("%d\n", sizeof(str));
}

int main()
{
	int arr[10] = { 0 };
	char str[10] = { 0 };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(str));
	test1(arr);
	test2(str);
	return 0;
}

先思考一下,想想会打印什么?下面我们来看看结果:

我们来分析一下 :

我们前面介绍过:数组名一般表示的是数组首元素的地址!但有两个例外:(1)sizeof(数组名)的时候表示的是,计算整个数组的大小!(2)&数组名的时候,表示去除整个数组的地址!

(类型)                   强制类型转换

强制类型转换是将一种数据类型强转为另一种数据类型的方法,在这个过程中会用到强制类型转换操作符(类型)。

为什么有强制类型转换?

C语言它允许我们对不同类型的数据类型进行各种运算,但有时候我们需要将一种数据类型转换为另一种数据类型(为了符合需求),所以就产生了强制类型转换!

举个栗子:

int main()
{
	double b = 520.1314;
	printf("%d\n", (int)b);
	return 0;
}

看结果:

这就是强制类型转换!他有很多用处,例如在计算平均值等,另外在java 语言中多态那里,有个上转型和下转型就是用的强制类型转换!

6.关系操作符

>                >=

<                <=

!=                ==

关系操作符比较简单,但要注意的是 == 是判断相等不要与 = 赋值操作符混淆了!

7.逻辑操作符

&&        逻辑与

||           逻辑或

这两个逻辑操作符都是双目操作符。

&&        逻辑与

语法:当两个操作数都为真时,结果才为真;当两个操作数至少有一个是假时,结果为假!

举个栗子:

int main()
{
	int a = 3;
	int b = -2;
	if (a >= 0 && b >= 0)
	{
		printf("haha\n");
	}
	return 0;
}

当两边同时成立才执行后面的操作!此时应该什么都不打印,因为a和b不同时大于0成立!

||        逻辑或

语法:当两个操作数中至少有一个操作数的结果为真时,结果就为真;两个操作数的结果都为假时才为假! 

举个栗子:

int main()
{
	int a = 3;
	int b = -2;
	if (a >= 0 || b >= 0)
	{
		printf("haha\n");
	}
	return 0;
}

只要有一个操作数为真结果就为真!此时应该打印haha :

介绍到这里,我又想起了一个&&和||同时使用的题!(多组输入年份,判断是否是闰年) :

int main()
{
	int year = 0;
	while (~scanf("%d", &year))
	{
		if (year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
		{
			printf("%d 是闰年!\n", year);
		}
		else
		{
			printf("%d 不是闰年!\n", year);
		}
	}
	return 0;
}

这里还要注意的一点是,很多同学会把&和&&以及|和||混淆;其实他们是没关系的!&& 是判断表达式的真假而&是按位与,其操作的是二进制位,这两者不是一个东西,仅仅是有一点点像!

到这里我们已经介绍完了逻辑操作符的基本语法打他有哪些特性呢?我们还不知道,下面小编来介绍一下&&和||的特性:

首先来看一道曾今360的笔试题:

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 \n d = %d\n", a, b, c, d);
	return 0;
}

思考一下上面这道题答案是多少?OK,看答案:

不知您答对了没?下面小编来给大家分析一下:

&&这个操作符的特性是:左边为假,右边直接不再计算!

a 一开始的初始值是0,a++是后置++,先用在++,所以a++&&++b的时候a=0直接结束了后面的++b等都没有参与计算!而a使用完了后在++变成了1。所以打印:1,2,3,4!

再看下面这道题:思考一下对答案是多少?

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 \n d = %d\n", a, b, c, d);
	return 0;
}

OK,看答案:

 不知您答对了没?下面小编来给您分析分析:

|| 这个操作符的特性是:左边为真,右边直接不再计算!

a = 0 || ++b == 1 为真了右边不在计算,a用完了++ a = 1,b = 3,c没变,c= 3,d没变d = 4,所以打印1,3,3,4。

8.条件操作符

条件表达式又称三目操作符,顾名思义,他有三个操作数!下面让我们来看看它的语法吧:

语法:exp 1  ?   exp2  :  exp3;       

如果表达式1成立,则执行表达式2,表达式2的结果就是整个表达式的结果!否则,执行表达式3,表达式3的结果为整个表达式的结果!

举个栗子:

int Max(int a, int b)
{
	return (a > b) ? a : b;
}

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

看结果:

实际上他就是和if else 的多个选择!与下面的代码等价:

int Max(int a, int b)
{
	if (a > b)
		return a;
	else
		return b;
}

 9.逗号表达式

提到逗号表达式,有很多同学可能都没听过,不过问题不大,下面小编给您一一介绍清楚:

逗号表达式,就是用逗号隔开的多个表达式!其特点是:从左向右一次执行,最后一个表达式的结果为整个表达式的结果!

语法:exp1, exp2,exp3, exp4,....,expN

举个栗子:

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

int c 这一行就是逗号表达式!先是a > b显然是不大于的,结果为0!,然后b + 10赋值给a,然后a= 12,然后a + 1赋值给b,此时,b = 13,所以整个表达式的结果就是13,所以c = 13!

逗号表达式一般不常见,但是他也是很多题目的挖坑点!例如:

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

思考一下,arr数组的大小是多少?

看结果:

答案是6,因为(4,5)是一个逗号表达式,实际上是一个元素!所以是6个元素,而arr数组未定大小,所以元素个数就是数组的大小! 

10.下标引用、函数调用和结构体成员

10.1下标引用操作符  [ ]

下标引用操作符,毫无疑问就是用来访问数组的!

操作数: 一个数组名    +    一个下标(索引)

举个栗子:

int main()
{
	int arr[10] = { 1,3,5,7,9,2,4,6,8,10 };
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);//这里的arr[i]的[]就是下标引用操作符,arr和i是它的两个操作数
	}
	return 0;
}

10.2函数调用操作符        ( )

注意:函数调用操作符有多个参数!第一个操作数是它的函数名 + 传参的实参的参数!

举个栗子:

#include<assert.h>
int MyStrlen(char* str)
{
	assert(str != NULL);
	if (*str == '\0')
		return 0;
	else
		return 1 + MyStrlen(str + 1);
}

int main()
{
	char str[] = "abcdef";
	//这里的函数名 MyStrlen和实参str是就是()的操作数
	int len = MyStrlen(str);//这里的()就是函数调用操作符
	printf("%d\n", len);
	return 0;
}

10.3结构体成员访问操作符

结构体成员访问操作符有两个 .    和   ->虽都是访问结构体成员的但略有差异!

.                结构体 . 成员名

->              结构体指针->成员名

OK举个栗子来说明一下:

struct Stu
{
	char name[10];
	int age;
	char sex[5];
};

int main()
{
	struct Stu stu;
	struct Stu* pStu = &stu;

	stu.age = 10;
	printf("%d\n", stu.age);
	pStu->age = 20;
	printf("%d\n", pStu->age);
	return 0;
}

OK,不知您看懂了没?没看懂也没关系,后面会再次详解结构体的!

11.表达式求值

表达式就值的顺序一部分是由操作符的优先级和结合性决定的!

同样,有些表达式的操作数在求值的过程中可能需要转化为其他类型。

类型转换又分为两种:隐式转换和显示类型转换(强制类型转换),隐式类型转换又分为:算术转换和整型提升。操作数的字节数 >= 4字节的是算术转换,操作数的字节数 小于int 类型的用的是整型提升!

什么是算术转换?什么是隐式转换?我们下来一起聊一聊:

11.1隐式转换

这块我以前写过一篇文章,很详细,所以这里就不在多说了。有需要的朋友可以看看那篇文章!下面是这块的文章链接:

隐式类型转换http://t.csdn.cn/QtkqK

11.2算术转换

如果某个操作符的各个操作数属于不同类型,那么除非一个操作数转换为另一个操作数的类型,否则操作数无法进行运算!下面的层次体系被称为寻常算术转换

long double

double 

flaot

unsigned long 

long

unsigned int 

int

如果某个操作数的类型在这个列表中的排名比较低,那么这个操作数首先要换砖为另外一个操作数的类型然后才能进行计算!

注意:算术转换也要注意合理性,否则会出问题!

float f = 200.345f;

int num = f;//精度会丢失

11.3操作符的属性

我们知道:复杂表达式的求值有三个影响因素!

1.操作符的优先级

2.操作符的结合性

3.是否控制求值顺序

两个相邻的操作符限制性哪一个?

答案是取决于他们的优先级,如果他们的优先级相同,取决于他们的结合性。

下面是C语言所有操作符的优先级、结合性、以及是否控制求值顺序的表:

 这么多的操作符的优先级都要记住吗?答案是:不用都记住,多看,多查。遇事不决加上()!

12.一些问题表达式:

我们上面知道了,操作符的优先级、结合性等,但优先级和结合性都知道的情况下我们的表达式求值顺序就一定唯一吗?答案是不一定的!例如下面的表达式:

(1)a * b + c * d +  e * f 

这个表达式我们知道 * 的优先级比 + 的高,但我们能确定这个表达式的计算路径吗?答案是:不能!请看:

我们发现,同一个表达式在确定操作符优先级的情况下,任然计算路径有问题,不统一,这个表达式就是有问题的。你肯定会反驳我,这个表达式的两种计算方式的结果 不都一样吗?光看这个式子是一样的。但如果abcd等不是一个变量,而是一个表达式呢?那问题可就大了!!!

在看一个例子:
(2)c + --c

我们知道,前置--的优先级比 + (加法) 的高。但我们不知道的是,+ 左边的操作数是 --后的还是--前的!无法得知。因此计算结果不可控!

(3)++与--谁先之计算机都不知道

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

这可不是一般的代码,这也不是王维诗里的代码,这是连计算机都不知道怎么算的代码!!!

小编的VS2019上跑出来的结果是4;但他的结果可不止一个!有人用了好多编译器测试过,每一个编译器的结果都不一样!!!下面就是限额是结果: 

这足以说明这段代码的问题很严重!!

(4)看似没问题实则有问题

int fun()
{
	static int count = 1;
	return ++count;
}

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

这段代码乍一见看好像一点问题都没有!但我们仅仅能确定的实则是 * 的优先级比 - 的高,但具体先调用哪个函数,我们是不清楚的!!!

(5)走火入魔的++

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

这段代码的结果也是不统一的!!在小编的 VS2019上是 9   3  而在linux的环境下,他是10   4.

究其原因,还是计算路径有歧义:我们现在只知道++ 的优先级比+的高,但和第一个例子一样路径不唯一,再者,当第一个+执行的时候第三个++是否已经执行?这都是不可控的,因此结果有问题!

OK,好兄弟,我们下期再见!

  • 19
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
C语言中的string函数主要包括字符串的处理函数和字符串的操作函数。 字符串的处理函数主要有以下几个: 1. strlen:用于计算字符串的长度,即包含的字符数目。 2. strcpy:用于将一个字符串复制到另一个字符串中。 3. strcat:用于将两个字符串连接起来。 4. strcmp:用于比较两个字符串的大小关系。 5. strchr:用于在一个字符串中查找指定字符的位置。 6. strstr:用于在一个字符串中查找指定子串的位置。 字符串的操作函数主要有以下几个: 1. sprintf:用于将格式化的数据写入字符串中。 2. sscanf:用于从字符串中读取格式化的数据。 3. strtok:用于将一个字符串按照指定的分隔符进行分割。 4. strncmp:用于比较两个字符串的前n个字符的大小关系。 5. strncpy:用于将一个字符串的部分内容复制到另一个字符串中。 6. memset:用于给字符串的指定范围内的每个字符赋予相同的值。 这些函数可以帮助我们在C语言中方便地处理字符串,实现字符串的复制、连接、比较、查找等操作。通过这些函数,我们可以更高效地处理文本数据,提高代码的可读性和可维护性。 需要注意的是,使用这些函数时要确保输入的参数合法,以避免内存越界等错误。同时,字符串的内存空间需要提前分配好,以免出现不可预知的问题。在实际编程中,我们需要灵活运用这些函数,结合具体需求,进行字符串的处理。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值