【C语言】万字详讲操作符

news2024/10/5 12:58:48

目录

前言

一、操作符分类

二、算数操作符

三、移位操作符

四、位操作符

五、赋值操作符

六、单目操作符

6.1 逻辑反操作

6.2 负值与正值

6.3 取地址

6.4 sizeof

6.5 取反操作符

6.6 --和++操作符

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

6.8 强制类型转换

七、关系操作符

八、逻辑操作符

九、条件操作符

十、逗号表达式

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

11.1 [] 下标引用操作符

11.2  () 函数调用操作符

11.3 访问结构成员操作符

十二、表达式求值

12.1 隐式类型转换

12.2 算数转换

12.3 操作符的属性

总结


前言

这篇文章将对C语言的操作符相关知识点,进行详细的讲解,读完本篇文章,您对C语言的了解将更进一步。


一、操作符分类

C语言中对于操作符一共分为十类:(进行简单介绍)

  • 算数操作符:加减乘除取余
  • 移位操作符:左移(<<),右移(>>)
  • 位操作符:按位与(&),按为或(|),按位异或(^)
  • 赋值操作符:简单赋值:=;复合赋值:与前三类结合
  • 单目操作符:sizeof、取反~、取非!、+、-、++、--、*、强制类型转换()
  • 关系操作符:大于等于小于等等
  • 逻辑操作符:逻辑与(&&),逻辑或(||)
  • 条件操作符:也就是三目运算符
  • 逗号表达式:多个表达式由逗号分隔
  • 下标引用、函数调用和结构成员:点、->、()、[]

二、算数操作符

算数操作符,也就是对应数学上的算数运算符,最常见的为:加减乘除。

不过在C语言中,还会用到取模(取余)操作,算出操作符的书写形式如下:

+:加
-:减
*:乘
/:除
%:取余

可以看出,加减书写方式与数学中是一样的,乘除与数学中的不同,这里重点讲解一下取余操作符:

#include<stdio.h>
// 算数操作符示例代码:
int main()
{
	int a = 10;
	int b = 3;
	printf("加法:%d\n", a + b);
	printf("减法:%d\n", a - b);
	printf("除法:%d\n", a / b);
	printf("取余法:%d\n", a % b);
	return 0;
}

通过结果可知:

  • 除法的结果是商。
  • 取余的结果是余数

我们再看看几个例子:

对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

加减乘除都可以用在不同类型中,那取余操作符呢?

我们可以得出:

  • 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  • % 操作符的两个操作数必须为整数。返回的是整除之后的余数。

三、移位操作符

<<:左移
>>:右移

关于移位操作符,我们先要了解一些前备知识点。

移位,移的是什么位呢?其实是二进制的位,那二进制是什么呢?

日常生活中,我们看到的数字是用十进制形式表示的,十进制的数据都是由0~9的数字组成的。相同的,二进制是常用在计算机中,由0~1的数字组成。当然,还有8进制、16进制等等,原理相同。

列如,我们算一下10的二进制,要算10的二进制,这里就涉及到如何将10进制转为二进制的知识:

10进制转二进制,我们常用除2取余的方法:

那将二进制转为其它进制呢?方法是权重法

回归主题,在C语言中,数据是以二进制的形式存储在内存中的,而移位操作符,正是对整数的二进制位进行移动。

既然是对整数的二进制位操作,那我们就需要了解整数的二进制是怎么样的呢?

整数的二进制表示形式有3种:原码、反码、补码

C语言规定:

  • 正数的原反补码都一样,而负数的原反补不一样,要通过计算。
  • 整数在内存中以补码的形式进行存储,对二进制位操作时,也是针对补码,但最终显示给我们看的是原码形式,怎么理解呢?
  • 一个整形有32个二进制位,其中第32个位是符号位:正数符号位为0,负数符号位为1。
  • 例如int a = -2; 显示给我们看的为-2,即二进制为:符号位(1).....010,但如果我们打印&a -2的地址,我们看到的与我们计算出来的不一样,这就是在内存中以补码形式进行存储,显示时以原码形式。

我们对-2进行图解,讲解原反补的概念:

我们也可以通过编译器来看看:-5在编译器中存储

 

既然内存中以补码形式存储,对于正数来说,原反补都一样,那负数进行二进制操作后,如何将补码转回原码呢?

两种方法:

  1. 补码-1取反
  2. 补码取反+1

<<左移操作符:二进制位相左移动,右边补0。

对a的二进制位进行操作,a的值会改变吗?并不会,这就跟int a = 2; int b = a+2;操作一样,并不会改变a的值,如果要改变,必须对自身赋值。


>>右移操作符:有两种:

  1. 算数右移:右边丢弃,左边用原理的符号位补充(0为正数、1为负数)
  2. 逻辑右移:右边丢弃,左边用0填充。

具体那种形式,是由编译器决定的,对于大多数编译器来说,用的是算数右移,具体可以看图解:

最后,关于移位操作符有两点需要注意:

  • 移位操作符的操作数只能是整数。
  • 对于移动运算符,不要移动负数位,这个是标准未定义的。如:a << -1、a >> -2 .....

四、位操作符

&:按位与
|:按位或
^:按位异或
注:他们的操作数必须是整数

关于位操作符,从名字就能猜出,是针对于二进制位的操作,具体作用如下:

&:一个为0,都为0,两个为1才为1

|:一个为1,都为1,两个为0才为0

^:相同为0,相异为1

代码+图解:

//位操作符
int main()
{
	int num1 = 2;
	int num2 = 3;
	int num3 = num1 & num2;
	int num4 = num1 | num2;
	int num5 = num1 ^ num2;
	printf("&:%d\n", num3);
	printf("|:%d\n", num4);
	printf("^:%d\n", num5);
	return 0;
}

按位异或的三个特点:

  • 两个相同值进行异或等于0,因为相同为0
  • 任何数与0异或等于本身,因为0遇到1相异为1,遇到0,相同为0,还是本身的二进制
  • 支持交换律,如:a^a^b == a^b^a

五、赋值操作符

赋值操作符是C语言用的最多的一个操作符,没什么好讲的,就是简单的赋值操作。

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
这样的代码感觉怎么样?
那同样的语义,你看看:
x = y+1;
a = x;
这样的写法是不是更加清晰爽朗而且易于调试。

赋值操作符还可以与其它操作符结合,称为复合赋值符:其实自己对自己操作后,赋值给自己

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁。

这里要区分一个点:赋值和初始化是不同的:

  • 创建一个变量的同时又赋值:初始化
  • 创建后,再赋值:赋值

六、单目操作符

!:逻辑反操作
-:负值
+:正值
&:取地址
sizeof:操作数的类型长度(以字节为单位)
~:对一个数的二进制按位取反
--:前置、后置--
++:前置、后置++
*:间接访问操作符(解引用操作符)
(类型):强制类型转换

6.1 逻辑反操作

! 逻辑反操作符,只有两种结果:真变假,假变真,看代码:

//逻辑反操作
int main()
{
	int a = 0;
	if (!a) //当a为假时执行
	{
		;
	}
	return 0;
}

6.2 负值与正值

跟数学中的正负符号相同。不常用,看代码:

//正、负值
int main()
{
	//将a变为-数
	int a = 2;
	int a = -a;
	return 0;
}

6.3 取地址

&取地址操作符常用在获取一个数据的地址,常跟指针搭配使用,看代码:

//&取地址
int main()
{
	//查看a的地址
	int a = 2;
	int* p = &a;
	printf("%p\n", p);
	return 0;
}

6.4 sizeof

sizeof是一个单目操作符,不是一个库函数哦,其作用是求类型的字节大小。看代码理解:

#include <stdio.h>
int main()
{
 int a = -10;
 printf("%d\n", sizeof(a));
 printf("%d\n", sizeof(int));
 printf("%d\n", sizeof a);//这样写行不行?
 printf("%d\n", sizeof int);//这样写行不行?
 return 0;
}

sizeof(a)或sizeof(int) 都是正确的。还有一种特殊的写法:sizeof a , 就是()里放的是变量名时,可以把()去掉,但如果是类型,()不可以去掉。


6.5 取反操作符

~ 取反操作符是针对二进制位操作的,其作用是将二进制的每一位取反,0变1,1变0,如:

int a = 2;
a:  010
~a: 101

这里有一个在做OJ题常见的代码:

while(~scanf("%d", &n))

代码中的~操作怎么理解呢?

因为scanf的返回值是格式化符的个数,当scanf读取失败时,会返回EOF,EOF为-1,
那对-1按位取反,那结果就为0,0为假,则不会进入循环


6.6 --和++操作符

--和++操作符分为前置与后置,下面讲解前置和后置有什么区别:

前置:先++/--后使用。

后置:先使用后++/--

具体看代码:

//++和--运算符
//前置++和--
#include <stdio.h>
int main()
{
    int a = 10;
    int x = ++a;
    //先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
    int y = --a;
    //先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
    return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
    int a = 10;
    int x = a++;
    //先对a先使用,再增加,这样x的值是10;之后a变成11;
    int y = a--;
    //先对a先使用,再自减,这样y的值是11;之后a变成10;
    return 0;
}

我们看看这段代码:

int a = 1;
int b = (++a) + (++a) + (++a)

这是一段问题代码,在不同的编译器上运行的结果是不同的,因此对于++、--操作符,我们尽量不要在一个表达式中使用多次,这样会让代码阅读变差。


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

* 间接引用操作符用是指针的一种标志,跟随指针一起出现。

//*简接引用操作符
int main()
{
	int a = 2;
	//定义指针变量P
	int* p = &a;
	//访问a的值
	printf("%d\n", *p);
	return 0;
}

6.8 强制类型转换

(类型)强制类型转换通常用在对不同类型变量之间,想进行赋值操作时。

//强制类型转换
int main()
{
	float b = 3.0f;
	int res = (int)b;
	printf("%d\n", res);
	return 0;
}

对于强制类型转换,有两点要注意:

  • 建议小转大,大转小会丢失精度
  • 强制类型转化是一种临时的状态,不是永久的

七、关系操作符

>
>=
<
<=
!=      用于测试“不相等”
==      用于测试“相等”

这些运算符没什么讲的,需要注意:

  • 在编程的过程中== 和=不小心写错,导致的错误。
  • 两个等号为等与、一个等号为赋值

八、逻辑操作符

&&:逻辑与(并且)
||:逻辑或(或者)

逻辑操作符的结果只有两种:真或假。

注意区分与按位与和按位或的区别:

按位与和按位或对二进制位操作。
逻辑与和逻辑或关注的是真和假

1&2----->0
1&&2---->1

1|2----->3
1||2---->1

我们看一到360的笔试题:

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    //i = a++||++b||d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}
//程序输出的结果是什么?

如何计算这种表达式呢?

当多个||时,当从前往后执行,当发现前面的为1时,后面不需要计算了,均为1,如:(|| - 两个为假,式子为假,1个为真,式子才为真)
int i = 0, a = 1, b = 2, c = 3, d = 5
i = a++ || ++b || d++
当a==1时,就不需要执行后面的式子了,均为1,因此,当发现前面为1时,后面不会执行

将代码改为||呢?

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;
    //i = a++&&++b&&d++;
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}
//程序输出的结果是什么?
当多个&&时,当从前往后执行,当发现前面的为0时,后面不需要计算了,均为0,如:(&& 一个为假,式子为假,两个为真,式子才为真)
int i = 0, a = 0, b = 2, c = 3, d = 5
i = a++ && ++b && d++
当a==0时,就不需要执行后面的式子了,均为0,因此,当发现前面为0时,后面不会执行

因此,我们可以得出结论:

  • &&操作符左边为假时,右边不会执行,表达式为假。
  • ||操作符左边为1时,右边不会执行,表达式为真
  • 这种现象也叫短路

九、条件操作符

//条件操作符(三目运算符)
exp1 ? exp2 : exp3

首先看exp1表达式结果,如果为真则表达式值为exp2,如果为假,表达式的值为exp3。

if (a > 5)
        b = 3;
else
        b = -3;
转换成条件表达式,是什么样?
int a = 2;
int b = 0;
int res = a>5 ? b=3 : b-3
res值为-3.
b的值为-3.

我们做个练习,使用条件表达式实现找两个数中较大值。

//使用条件表达式实现找两个数中较大值。
int main()
{
	int a = 10;
	int b = 20;
	int max = a > b ? a : b;
	printf("max: %d\n", max);
	return 0;
}


十、逗号表达式

exp1, exp2, exp3,....expN

逗号表达式的特点:

  • 逗号表达式,就是用逗号隔开的多个表达式。
  • 逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

例如以下几个例子:

//代码1
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
c是多少?

c为13。从左向右依次计算,到在最后一个表达式时,a的值已变为12,最后整个表达式的值是最后一个表达式,因此c为13.

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

从左向右执行,代码2中的逗号表达式并没有直接与最后一个表达式关联的变量,因此判断条件仍然为d > 0.

//代码3
a = get_val();
count_val(a);
while (a > 0)
{

  //业务处理
  a = get_val();
  count_val(a);
}

如果使用逗号表达式,改写:
while (a = get_val(), count_val(a), a>0)
{
         //业务处理
}

从代码3可以看出,如果学会逗号表达式,可以大大提高代码书写效率。


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

11.1 [] 下标引用操作符

什么是操作数?操作数就是操作符作用的对象,如:3&&2,则3和2就是&&的操作数。那下标引用操作符的操作数是什么呢?

操作数:一个数组名 + 一个索引值。[]有两种用法如下:

int arr[10];//创建数组
 arr[9] = 10;//实用下标引用操作符。
 [ ]的两个操作数是arr和9。

11.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;
 }

11.3 访问结构成员操作符

.  : 结构体变量.结构成员名
-> :  结构体指针->结构成员名

结构体变量.结构成员名,常用于获取结构体变量形式的结构体成员,如以下代码:

//结构体变量.结构体成员名
struct S
{
	int a;
};
int main()
{
	struct S s = { 20 };
	printf("%d", s.a);
	return 0;
}

结构体指针.结构成员名,常用于结构体指针形式的结构体成员,如以下代码:

struct S
{
	int a;
}s;
void test(struct S* p)
{
	//当然,也可以写成结构体变量.结构体成员名形式
	printf("%d\n", (*p).a);
	//结构体指针.结构体成员名,更加方便
	printf("%d\n", p->a);
}
int main()
{
	test(&s);
	return 0;
}

十二、表达式求值

关于C语言的操作符已经介绍完毕,学习了操作符,其实主要目的就是写出相对应的表达式,但是那么多种操作符如何进行组合呢?组合之后又该怎么确定谁先运行或者运行的方向是怎么样的呢?

关于表达式求值,有两点需要注意:

  • 表达式求值的顺序一部分是由操作符的优先级和结合性决定。
  • 同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。

12.1 隐式类型转换

什么是隐式类型转换,通俗点说就是当char类型和short类型进行算术运算时,程序会隐式的对算数对象进行类型的提升,提升为int型,进行算术运算,具体的看如下讲解:

C的整型算术运算总是至少以缺省整型类型的精度来进行的。

什么意思呢?通俗点讲就是在C语言中,进行整型算术运算时,总是以int类型进行的,也就是说,如果我们对char或short类型进行加减乘除运算的话,会默认将类型变为int来进行。

整型提升:为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型 提升。

整型提升有什么意义呢?

先看一大段概念:

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度 一般就是int的字节长度,同时也是CPU的通用寄存器的长度。 因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长 度。 通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令 中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转 换为int或unsigned int,然后才能送入CPU去执行运算。

通俗讲解:

我们通过几个例子进行说明:

char a = 3;
char b = 127;
char c = a+ b;

两个char类型的变量进行相加,程序会隐式的对这两个变量提升为int型,如何提升呢?

我们知道char类型是1字节,也就是8个二进制位,而int是4字节,也就是32比特位,整型提升的提升本质上是提升二进制位:

 

a和b的值被提升为普通整型,然后在执行加法运行,计算完之后,发现接受的变量c是一个char类型,则需要进行截断操作,也就是只保留8个二进制位:


那么整型提升的规定是什么呢?

  • 有符号位(也就是类型前不加unsigned):
  1. 按照变量的数据类型的符号位来提升
  2. 正数符号位为0,那就将不足的二进制填充为0
  3. 负数符号位为1,那就将不足的二进制填充为1
  • 无符号位(也就是类型前加了unsigned):
  1. 直接高位补0(也就是剩余的二进制位填充0)
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0

看看下面这两个实例:

//实例1
int main()
{
 char a = 0xb6;
 short b = 0xb600;
 int c = 0xb6000000;
 if(a==0xb6)
 printf("a");
 if(b==0xb600)
 printf("b");
 if(c==0xb6000000)
 printf("c");
 return 0;
}

实例1中的a,b要进行整形提升,但是c不需要整形提升 a,b整形提升之后,变成了负数,所以表达式 a==0xb6 , b==0xb600 的结果是假,但是c不发生整形提升,则表 达式 c==0xb6000000 的结果是真. 

//实例2
int main()
{
 char c = 1;
 printf("%u\n", sizeof(c));
 printf("%u\n", sizeof(+c));
 printf("%u\n", sizeof(-c));
 return 0;
}

sizeof求类型的字节大小,单位为字节,为什么得出的大小不同呢?其实也是整型提升的原因,整型提升是针对char类型和short类型表达式的,注意!!是表达式,那+c和-c自然也是表达式了,那就被整型提升成了int类型,因此打印出4.


12.2 算数转换

C语言中不只有隐式转换,隐式转换是针对于小于Intl类型的转换,对象为char类型和short类型,那对于等于int类型或大于int类型的类型呢?就要用到算数转换了。

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

long double
double
float
unsigned long int
long int
unsigned int
int

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运 算。

float f = 3.14;
int num = f;//隐式转换,会有精度丢失
num最终为3,丢失掉了0.14

12.3 操作符的属性

上文提到过,表达式求值有三个影响的因素:

  1. 操作符的优先级(就是先后执行的意思)
  2. 操作符的结合性(就是执行方向,是从左到右还是从右到左)
  3. 是否控制求值顺序(就是某个条件下,操作数可能不会执行,如:&&、||、条件表达式、逗号表达式)

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。注意:操作符的优先级是针对两个相邻操作符进行比较。

可以参考以下这份表格:

C语言操作符表
操作符描述用法示例结构类型结合性是否控制求值顺序
()聚组(表达式)与表达 式同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
&取地址& lexprexpR-L
sizeof取其长度,以字节 表示sizeof rexp sizeof(类型)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
=赋值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
|=以...或lexp |= rexprexpR-L
逗号rexp,rexprexpL-R

按照操作符的优先级和结合性等等就能写出唯一路径的表达式吗??不一定,表达式的求值部分由操作符的优先级决定。如以下的例子:

//表达式1
a*b + c*d + e*f

代码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
c + --c;

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


//代码3-非法表达式
int main()
{
 int i = 10;
 i = i-- - --i * ( i = -3 ) * i++ + ++i;
 printf("i = %d\n", i);
 return 0;
}

表达式3在不同编译器中测试结果:非法表达式程序的结果


//代码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(); 中我们只能通过操作符的优先级得知:先算乘法, 再算减法。 函数的调用先后顺序无法通过操作符的优先级确定。


//代码5
#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0;
}

同样的代码,在不同的编译器上运行出的结果不同。


总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题 的。


总结

这就是我对C语言中操作符相关知识点的讲解,感谢老铁们的耐心阅读,希望多多支持!!!关注我!后续有更多的干货❤❤❤❤

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1625467.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

比较好的平民衣服品牌有哪些?平价质量好短袖品牌推荐

随着气候变暖&#xff0c;夏天的持续时间似乎越来越长&#xff0c;短袖作为夏季的必备服装&#xff0c;受到了广大男士的青睐。然而&#xff0c;面对市场上众多的短袖品牌和不同的质量&#xff0c;大家都觉得选短袖的时候实在难以找到质量好且合适自己的。 选择合适的短袖确实…

中级信息系统管理工程师-必会题锦集

文章目录 中级信息系统管理工程师-必会题锦集题目一CPU[解析]试题二 CPU[解析] 中级信息系统管理工程师-必会题锦集 题目一CPU CPU中&#xff08;1&#xff09;不仅要保证指令的正确执行&#xff0c;还要能够处理异常事件。 A. 运算器 B. 控制器 C. 寄存器组 D. 内部总线 [解…

Elasticsearch单机部署(Linux)

1. 准备环境 本文中Elasticsearch版本为7.12.0&#xff0c;JDK版本为1.8.0&#xff0c;Linux环境部署。 扩展&#xff1a; &#xff08;1&#xff09;查看Elasticsearch对应的常用的jdk版本如下&#xff1a;&#xff08;详情可看官网的支持一览表&#xff09; Elasticsearch a…

牛客NC221 集合的所有子集(二)【中等 深度优先,子集,排列组合 C++/Java/Go/PHP】

题目 题目链接&#xff1a; https://www.nowcoder.com/practice/a3dfd4bc8ae74fad9bc65d5ced7ae813 核心 和n数之和的问题 差不多&#xff0c;都是需要找到数字的排列组合参考答案C class Solution {public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&a…

获取boss直聘城市地区josn数据

获取boss直聘城市地区josn数据 当我需要爬取多个城市的地区的时候&#xff0c;只能手动点击&#xff0c;然后一个一个看 结果&#xff1a; 能看到所有区域所有子地区的地区代码 解析该JSON数据 import pandas as pd import requests code[] area[] 城市代码101210100 res…

ScriptableObject数据容器讲解

概述 是Unity提供的一个用于创建可重用的数据容器或逻辑的基类。 ScriptableObject 是继承自 UnityEngine.Object 的一个类&#xff0c;但与普通的 MonoBehaviour 不同&#xff0c;它不能附加到GameObject上作为组件。 相反&#xff0c;ScriptableObject 通常用于存储和管理…

飞鹤与满趣健达成战略合作 加速深化国际化布局

继获得加拿大地区首张婴幼儿配方奶粉生产执照后&#xff0c;中国飞鹤的海外征途再添新动作。4月25日&#xff0c;中国飞鹤加拿大皇家妙克与美国婴童用品巨头满趣健&#xff08;Munchkin&#xff09;在北京正式达成战略合作。此次合作彰显了中国乳企的硬核实力&#xff0c;也是飞…

java:Http协议和Tomcat

HTTP协议 Hyper Text Transfer Protocol 超文本传输协议,规定了浏览器和服务器之间数据传输的规则 特点: 基于TCP协议,面向连接,安全 基于请求响应模型:一次请求对应一次响应 HTTP协议是无状态协议,对事务的处理没有记忆能力,每次请求-响应都是独立的. 优点 速度较快 …

Win11和WinRAR取消折叠菜单恢复经典菜单

这里写目录标题 前言1. Win11恢复经典右键菜单1.1 修改前1.2 恢复成经典右键菜单1.3 修改后1.4 想恢复怎么办&#xff1f; 2. WinRAR取消折叠菜单恢复经典菜单2.1 修改前2.2 修改恢复为经典菜单2.3 修改后2.4 想恢复怎么办&#xff1f; 前言 最近换回了Windows电脑&#xff0c…

网络攻击近在咫尺:数据加密与SSL成为信息安全之盾

随着互联网的日益普及和科技的迅猛发展&#xff0c;网络攻击已经成为信息安全领域面临的一大难题。近期&#xff0c;一场网络安全实验让我们对网络攻击有了更为深刻的认识。在实验中&#xff0c;网络安全工程师通过模拟攻击&#xff0c;展示了木马植入、文件浏览、键盘监听、病…

618大促有哪些值得买的家居好物?618五款必Buy好物

来了&#xff01;来了&#xff01;万众瞩目的618购物狂欢节即将拉开帷幕&#xff0c;我们的目标清晰而坚定&#xff0c;那就是用最实惠的价格尽情享受购物的乐趣。然而&#xff0c;面对各种纷繁复杂的促销活动和琳琅满目的商品&#xff0c;选择困难症似乎也在悄然滋生。因此&am…

安全AI未来 | C3安全大会 · 2024,数据驱动 AI原生

数字为时代变革注入动力&#xff0c;AI为重塑社会文明带来原力。数智浪潮中&#xff0c;我们见证着时代跃迁的巨变&#xff0c;面临着适变、应变、驭变的挑战。 数字驱动、AI原生。数字的流动不仅承载着信息&#xff0c;更将激活未来的无限价值&#xff1b;AI&#xff0c;不…

FastAPI从入门到实战(16)——请求参数汇总

FastAPI有各种各样的参数,包括: url参数(定义在url中的参数)param参数(使用url后面?xxxx)定义的参数body参数(在请求主体中携带的json参数)form参数(在请求主体中携带的web表单参数)cookie参数(在请求的cookie中携带的参数)file参数(客户端上传的文件) 1. url参数 from fas…

金属冶炼及压延加工制造数字孪生可视化平台,推进行业数字化转型

金属冶炼及压延加工制造数字孪生可视化平台&#xff0c;推进行业数字化转型。随着科技的不断进步和工业的快速发展&#xff0c;金属冶炼及压延加工行业正面临着前所未有的挑战和机遇&#xff0c;数字化转型成为了行业发展的必然趋势。在这个过程中&#xff0c;数字孪生可视化平…

ArcGIS专题图制作—3D峡谷地形

6分钟教你在ArcGIS Pro中优雅完成炫酷的美国大峡谷3D地图 6分钟教你在ArcGIS Pro中优雅完成炫酷的美国大峡谷3D地图。 这一期的制图教程将带我们走入美国大峡谷&#xff0c;让我们一起绘制这张美妙的地图吧&#xff01;视频也上传到了B站&#xff0c;小伙伴可以去&#xff01; …

【项目亮点】大厂中分布式事务的最佳实践 问题产生->难点与权衡(偏爱Saga)->解决方案

【项目亮点】大厂中分布式事务的最佳实践 问题产生->难点与权衡->解决方案->底层实现->应用案例 不断有同学问我大厂中实践分布式事务的问题,这里从分布式事务的产生,到强弱一致性与性能的权衡,再到最终落地的解决方案,再到实际的代码实现,再到我工作中实际使用SA…

数据结构篇其一---顺序表

前言 数据结构篇&#xff0d;&#xff0d;&#xff0d;C语言实现数据结构 &#xff23;语言的基础知识&#xff1a;数组 函数 结构体 指针 动态内存分配。 顺序表 从数据结构的角度看待数组 int arr[10]; 数组是一个基本的数据结构吗&#xff1f; 这里以一维数组为例。 …

信号捕捉、可重入函数、volatile、SIGCHLD

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 信号捕捉 signal 这个函数我们前面关于信号的文章多次提及&#xff0c;这里重点介绍sigaction。 sigaction 只需要看红色框住的属性即可&#xff0c;其余属性我们不使用&#xff0c;不用多管。第一个handler和signal的第二…

Vim学习笔记01~04

第01章&#xff1a; 遁入空门&#xff0c;模式当道 1.什么是vim Vim是一个高效的文本编辑工具&#xff0c;并且可以在编程开发过程中发挥越来越重要的作用。 事实上&#xff0c;有不少编程高手使用他们来进行代码的开发&#xff0c;并且对此赞不绝口。 2.本系列目的 但是让…

图论基础知识 深度优先(Depth First Search, 简称DFS),广度优先(Breathe First Search, 简称DFS)

图论基础知识 学习记录自代码随想录 dfs 与 bfs 区别 dfs是沿着一个方向去搜&#xff0c;不到黄河不回头&#xff0c;直到搜不下去了&#xff0c;再换方向&#xff08;换方向的过程就涉及到了回溯&#xff09;。 bfs是先把本节点所连接的所有节点遍历一遍&#xff0c;走到下…