初学C语言:运算符,一个也不放过

家电修理 2023-07-16 17:24www.caominkang.com电器维修

前言本人为C语言初学者,学识尚浅,研究程度存在很大的局限性,眼界很窄。以下所有观点仅代表个人见解和思路,各位游刃有余的前辈可以给予批评和指正!各位与鄙人同路的学子可相互探讨、发表看法,交换观点!

目录

优先级 1 

1.[] - 数组下标操作符

2.() - 圆括号

3.-> - 成员选择(指针)和 . - 成员选择(对象)

4.++ - 自增运算符和 -- - 自减运算符

优先级 2

1. - - 负号运算符

2.(类型) - 强制类型转换

3. - 取值操作符和 & - 取地址操作符

4.! - 逻辑非操作符

5. ~ - 按位取反操作符

6.sizof操作符

优先级 3

/ - 除 - 乘 % - 取模

优先级 4

+ - 加 - - 减

优先级 5

1.<< - 左移操作符

2.>> - 右移操作符

优先级6

优先级 7

优先级 8

& - 按位与

优先级 9

^ - 按位异或

优先级 10

| - 按位或

优先级 11

&& - 逻辑与

优先级 12

|| - 逻辑或

优先级 13?: - 条件运算符

优先级 14

优先级 15

,- 逗号表达式

扩展整型提升


,我们需要看一看这张大表

这里基本包含了C语言中所有的操作符以及其优先级和结合性的排序,我将以优先级为序来逐个剖析一下他们的具体用法

(,在这之前,我们还是先了解一下什么是目即操作数,单目、双目,也就是操作数的个数而已,很好理解)

优先级 1 

1.[] - 数组下标操作符

可以用来访问数组元素,例如以下代码

int arr[5] = {1, 2, 3, 4, 5};
int a = 0;

a = arr[0];
a = arr[1];
a = arr[2];
a = arr[3];
a = arr[4];

这里要注意两点(1)数组的下标从0开始索引 (2)数组下标操作符需要两个操作对象数组名和下标

2.() - 圆括号

其实这里的圆括号有两层含义,一是可以包含表达式,提升表达式的优先级,其实和数学中括号内先算的法则一样;二是作为函数的参数列表,这不属于运算符范围内。

#include 

int ADD(int x, int y)
{
 return x + y;
}

int main()
{
 int a = 1, b = 2, c = 3;
 int result = 0;

 result = a + b  c; //1

 result = (a + b)  c; //2
 
 result = ADD(a, c); //3

 return 0;
}

在1中,可知的优先级大于+,所以bc先运算,在2中,括号的优先级最高,所以a+b先运算,括号更像是一种提升优先级的方法。,在3中,它的作用是放函数参数,参数可以没有,括号却必须要赖在这儿不走。

3.-> - 成员选择(指针)和 . - 成员选择(对象)

这里需要大伙儿掌握结构体的知识,这里我只展示使用方法

	struct BOOK
	{
		char name[20];
		char riter[20];
		float price;
	};

	struct BOOK a = { "《圆圈正义》", "罗翔", 46.5 };

	printf("书名%sn作者%sn定价%.2fn", a.name, a.riter, a.price);

结构体是自己定义的一种复杂类型,比如这本书需要有字符串类型和浮点型,如果我还需要用到更多类型,那么一个个的定义命名和使用就变得非常繁琐,结构体更像是一种集合,方便了许多!

既然作为结构体,需要储存数据,那么在计算机内肯定要开辟一段空间给它,既然这样,它就会有地址,那么是不是可以和char、int之类的类型一样用指针变量存放地址呢?结果是必然的,结构体是什么类型?是......结构体类型!所以,我们也可以向下面这样使用它们

	struct BOOK
	{
		char name[20];
		char riter[20];
		float price;
	};

	struct BOOK a = { "《圆圈正义》", "罗翔", 46.5 };
	struct BOOK p = &a;

	printf("书名%sn作者%sn定价%.2fn", (p).name, (p).riter, (p).price); //1
	printf("书名%sn作者%sn定价%.2fn", p->name, p->riter, p->price); //2

在第一种写法中,只是简单地将p替换了a而已,道理同前;第二种写法,是地址直接指向对象,用到了->这个操作符。

4.++ - 自增运算符和 -- - 自减运算符

如其名般,就是让自己增加1或者让自己减少1嘛,这里需要区分一下前置自增和后置自增,比如++a和a++

int a = 1, b = 0;

b = ++a;

a = 1;

b = a++;

++a,也就是前置++,b的结果是2,这里a是先着急自增了之后才赋值给b;而a++,也就是后置++,b的结果是1,这里a是先把值给了b才自增。 

(后置++优先级比前置++高哦!!!)

优先级 2

1. - - 负号运算符

即将正数变为负数负数变为正数,操作数只有一个

int a = 1;
a = -a; //a的值为 -1

2.(类型) - 强制类型转换

如果我们在一个变量或者数的前面加上(类型),就可以强制转换类型,很多时候是为了必要的时候防止警告,又或者对time函数强制转换成unsigned int获得大于0的整数等......

char a = 10;
int b = 20;
int result = 0;

result = (int)a + b;

srand((unsigned int)time(NULL));

3. - 取值操作符和 & - 取地址操作符

这里主要涉及到了指针的一些知识,如果我们想要取出一个地址中的值,那么我们往往用到  

int a = 1;
int pa = &a;

printf("a = %d", pa);

,这里的定义指针变量的和打印时取值的这个意义大不相同,前一个只是说明这是一个指针变量,存放的地址,用于定义指针变量;而后一个是取出pa地址中的值供printf函数使用。

& - 取地址操作符同理,pa是指针变量,需要存放一个地址,所以我们把a的地址取出来放到pa中可供间接访问,所以它还有一个名字间接访问操作符。

4.! - 逻辑非操作符

! 可以将真变为假,假变为真,,这里我们要清楚真假概念0为假,非0为真,所以对任意一个非零数使用逻辑非,都会变成0,而对于0使用,只会变成1。

int a = 1;

if(a)
{
 printf("hahahan");
}

if(!a)
{
 printf("dadadan");
}

屏幕将会打印“hahaha”。

5. ~ - 按位取反操作符

这里是指对该数的二进制位进行按位取反,,也包括符号位!

	char a = 13;
	a = ~a;
	printf("a = %dn", a);

	return 0;

 我们对此进行分析

a = 13 = 00001101

~a = 11110010

而负数在内存中以补码形式存放,我们想知道它的原码,就得 -1 然后按位取反(这里的按位取反不包括符号位了哦!一定要注意!)

11110010 - 1 = 11110001

~ = 10001110 = - 14

,我脑洞打开,如果把char换成unsigned char会发生什么?同样的代码,让我们看看结果

 那242又是哪里来的?我们再来分析一下

a = 13 = 00001101

~a = 11110010 = 242(无符号)

乍一想挺简单的,实际上还是暗藏了一点玄机,详见文末,扩展1。

6.sizof操作符

计算该类型所占空间大小,可以写成以下几种形式

int a = 1;

sizeof(a);
sizeof a;
sizeof(int);

sizeof int;

这样写是错的哦!以防万一,可以在任何时候都带上括号。

优先级 3

/ - 除 - 乘 % - 取模

对于这类运算符相信大家已经是滚瓜烂熟,不需要过多的赘述,我只提一下一些需要注意的小点

(1)不要做出除以0,对小数取模等谜之操作

(2)注意操作数的类型和用于接收值的变量的类型

优先级 4

+ - 加 - - 减

优先级 5

1.<< - 左移操作符

这里的操作仍然是针对二进制进行的,例如

int a = 4;
a = a << 1;

结果是8,过程如下

a = 4 = (前24位0省略) 00000100

a  << 1即把二进制位向左移动一位

多出来的一位被舍弃,而右边空出来的一位自动填充0,所以a就变成了

(前24位0省略)00001000 = 8

2.>> - 右移操作符

和左移操作符类似,却也有不同之处哦!不可大意

int a = 4;
a = a >> 1;

一口咬定,答案就是2,道理同上,我还是画个图

如果我换成如下代码

int a = -1;
a = a >> 1;

 这时候结果会变成什么呢?

结果还是 -1?

这里不得不提到两种填充方式,对于右移操作符,右边多出来的肯定会舍弃,左边空出来的位置到底该填啥?1.算术右移,填充符号位   2.逻辑右移,填充0

所以我们刚刚在VS中计算还是-1,是因为VS是算术位移,就算移了一格,舍弃一个1又填充一个1,所以还是-1,如果填充的是0,那么结果将大相径庭。 

优先级6

真不是我懒,而是真的没啥说!

优先级 7

这些运算符得到的结果都是真或者假,需要明晰!

优先级 8

& - 按位与

操作的对象仍然是整型的二进制形式,按位与是将两个整型的二进制进行比较,同真为真,一假则假,即如下

int a = 123;
int b = 69;

int c = a & b;

c = 65;

计算如下

a = 123 = (前省)0111 1011

b = 69 = (前省)0100 0101

c = (前省)0 1 0 0 0 0 0 1(有0就为0,都是1才是1)

优先级 9

^ - 按位异或

其意思是,如果二进制位相同则为0,不同则为1,我们还是以上面的代码举例
 

int a = 123;
int b = 69;

int c = a ^ b;

计算过程如下

a = 123 = (前省)0111 1011

b = 69 = (前省)0100 0101

c = (前省)0 0 1 1 1 1 1 0(相同为0,相异为1)

优先级 10

| - 按位或

对应的二进制位只要有1就是1,两个都是0才为0

int a = 123;
int b = 69;

int c = a | b;

 计算过程如下

a = 123 = (前省)0111 1011

b = 69 = (前省)0100 0101

c = (前省)0 1 1 1 1 1 1 1(有真则真,同假则假)

优先级 11

&& - 逻辑与

两边的表达式为真则为真,有假则为假,且逻辑与如果检测到假就直接返回假,不会判断后面的内容!

int ret = 0;

ret = 1 && 1; //1

ret = 0 && 1; //2

ret = 0 && 0; //3

1同真为真,ret = 1;

2有假则假,ret = 0;

3同假为假,ret = 0;

如果代码是这样的

int a = 1;

int ret = 0;

ret = 0 && ++a;

运行后a的值还是1,因为有0的时候就直接返回了,++a被忽略了。

优先级 12

|| - 逻辑或

有真则真,全假则假,道理和逻辑与基本是相同的。

int ret = 0;

ret = 1 || 1; //1

ret = 1 || 0; //2

ret = 0 || 0; //3

1全真为真,ret = 1;

2有真则真,ret = 1;

3全假则假,ret = 0;

如果代码如下

int a = 1;

int ret = 0;

ret = 1 || ++a;

道理也是一样的,在检索到1时就已经返回了1,++a不被执行。

优先级 13
?: - 条件运算符
	int a = 3;
	int b = 0;
	b = a > 5 ? 1 : -1; 

    变量 = 表达式1 ? 表达式2 表达式3
    表达式1为真,把表达式2的值给变量,不然把表达式3的值给变量

其实这和if语句是完全等价的,见如下语句,和以上语句一毛一样

	if (a > 5)
	{
		b = 1;
	}
	else
	{
		b = -1;
	}

如果你用if语句来理解的话,就简单多了!

优先级 14

所有的运算符其实都是一种简化

例如

a = a + 2 -> a += 2;

a = a << 1; -> a <<= 1;

故等价转换即可,略。

优先级 15

,- 逗号表达式

如果你看见这样的东西

(表达式1, 表达式2, 表达式3);

那么整个逗号表达式的值等于一个表达式的值,见如下代码

int a = 0;

a = (++a, ++a, 2  3);

的a应该等于6。

逗号表达式从左至右计算,所以a先+1再+1,但被赋值为6

扩展整型提升
#define _CRT_SECURE_NO_WARNINGS 1

#include 

int main()
{
	//表达式的整型运算一般在CPU内的整型运算器(ALU)中执行,而其中的操作数的字节长度一般是int的字节长度
	//也是CPU的通用寄存器的长度

	//故如果两个空间不满int类型的整数相加,就会发生整型提升,即先转换成int或unsigned int类型再计算

	//转换方式有符号则以符号位填充,无符号则填充0

	char a = 127; //01111111
	char b = 3; //00000011

	char c = a + b;
	//真实的相加是这样的00000000 00000000 00000000 01111111 + 00000000 00000000 00000000 00000011 
	//= 00000000 00000000 00000000 10000010
	//然后被截断 c = 10000010
	//符号位是1,所以原码是补码 - 1 然后按位取反 = 11111110 = -126
	printf("c = %dn", c);

	//在printf时,c是char类型,是以整型输出,则发生整型提升
	//c = 111111110 -> 11111111 11111111 11111111 10000010
	//然后 -1 按位取反 -> 11111111 11111111 11111111 10000001 ->10000000 00000000 00000000 01111110
	//所以c输出的是 -126

	//输出和计算是不同的过程,经历两次整型提升,不要搞混


	//例1
	char i = 0xb6;
	short j = 0xb600;
	int k = 0xb6000000;

	if (i == 0xb6)
		printf("in");
	if (j == 0xb600)
		printf("jn");
	if (k == 0xb6000000)
		printf("kn");

	//例2
	char x = 1;
	char y = 1;
	int d = 10, e = 5;
	printf("%un", sizeof(+x)); //sizeof检索到x被整型提升后是int类型,所以输出了4(如果是!x,输出是1,实际上是错误的,g中是4)
	printf("%un", sizeof(y = d + e)); //这里y只是被赋值了,并没有整型提升,所以输出的还是1

	return 0;
}

//任何一个表达式都有两个属性1.值属性   2.类型属性
//int a = 3, b = 5;
//short c = 5;
//c = a + b; - 对于a+b来说,结果为8,这是值,值需要有一个类型,如果不装到c里面,则是int,如果装到c里,则类型由c决定,为short

Copyright © 2016-2025 www.caominkang.com 曹敏电脑维修网 版权所有 Power by