操作符详解—c语言

目录

1. 操作符分类:

2. 算术操作符

3. 移位操作符 

3.1 左移操作符

3.2 右移操作符

4. 位操作符 

5. 赋值操作符 

6. 单目操作符

6.1 单目操作符介绍 

7. 关系操作符

8. 逻辑操作符 

9. 条件操作符 

10. 逗号表达式 

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

12. 表达式求值 

12.1 隐式类型转换 

12.2 算术转换 

12.3 操作符的属性 


1. 操作符分类:

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

2. 算术操作符

                                  +   -  *  /  %

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

3. 移位操作符 

<< 左移操作符  将二进制序列向左移动
>> 右移操作符  将二进制序列向右移动

说起移位操作符就要说到二进制了

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

正整数的原码反码补码都相同

而负整数的反码与补码是要计算的

符号位 0 - 表示正数

            1 - 表示负数

整数在内存中存储的都是二进制的补码

               int a = 5
原码 000000000000000000000000000000000101
反码 000000000000000000000000000000000101
补码 000000000000000000000000000000000101
                               int a = -5
原码 10000000000000000000000000000101
反码 11111111111111111111111111111010 (原码的符号位不变,其他位取反的就是补码)
补码 11111111111111111111111111111011  (反码+1就是补码)

3.1 左移操作符

左移:就是将二进制序列左边丢弃,右边补0

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

 程序运行结果

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

程序运行结果

注意:编译器打印出来的值都是原码 

负数的原码 反码 补码的转换规则

3.2 右移操作符

右移分两种

算术右移:右边丢弃,左边补原符号位

逻辑右移:右边丢弃,左边补0 

到底是算术右移还是逻辑右移是取决于编译器的

注意:对于移位运算符,不要移动负数位,这个是标准未定义的  

4. 位操作符 

& //按(二进制)位与  (对应的二进制位只要有0就为0,只有全1才为1)
//按(二进制)位或     (对应的二进制位只要有1就为1,只有全0才为0)
^ //按(二进制)位异或 (对应的二进制位相同的为0,相异的为1)
注:他们的操作数必须是整数

int main()
{
	int a = 3;
	int b = -5;
	int c = a & b;
	int d = a | b;
	int e = a ^ b;
	printf("%d\n", c);
	printf("%d\n", d);
	printf("%d\n", e);

	return 0;
}
00000000000000000000000000000011  -> 3的补码
11111111111111111111111111111011  -> -5的补码

(再次提醒,编译器打印出来的是原码,而正数原码反码补码相同)
00000000000000000000000000000011  -> 3&-5的结果 结果是3

(补码)
11111111111111111111111111111011  -> 3|-5的结果
10000000000000000000000000000101  (原码) 结果是-5

(补码)
11111111111111111111111111111000  -> 3^-5的结果
10000000000000000000000000001000  (原码) 结果是-8

程序运行结果

一道变态的面试题:

不能创建临时变量(第三个变量),实现两个数的交换

#include <stdio.h>
int main()
{
   int a = 10;
   int b = 20;
   a = a^b;
   b = a^b;
   a = a^b;
   printf("a = %d b = %d\n", a, b);
   return 0;
}

初学者没指望会,知道有这么一种方法即可。

5. 赋值操作符 

赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋值。

int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值

复合赋值符 

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

比如:int x += 2 ;  //x = x+2

6. 单目操作符

6.1 单目操作符介绍 

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

C语言中0表示假,非0表示真

int num =10;
if(num)
{
   printf("hehe\n);
}

//!num 就是假 if语句不进入

1.sizeof(数组名),数组名不是数组首元素的地址,数组名表示整个数组,计算的是整个数组的大小.除这种情况之外,所有的数组名都表示数组首元素的地址。 

 sizeof是一个操作符,不是函数,计算类型创建的变量所占内存的大小,单位是字节 。

sizeof()中的表达式不参与计算 

一道有关sizeof的恶心的题目

#include <stdio.h>
int i;
int main()
{
    i--;
    if (i > sizeof(i))
    {
        printf(">\n");
    }
    else
    {
        printf("<\n");
    }
    return 0; 
}

问:程序会输出什么内容?

解释:首先全局变量如果没被初始化,会被默认设置成0,所以此时i的值就是0;而sizeof操作符,它的返回值是size_t的,size_t代表的是无符号的数,这里这个表达式就要(i > sizeof(i))进行算术转换,编译器会自动将左侧i自动转换为无符号整形的数据,-1对应的无符号整形是一个非常大的数字,超过4或者8,故最后的结果是打印出来“>”。

& 取地址和 * 间接访问操作符(解引用操作符)

int main()
{
	//& 取地址操作符
	//* 解引用操作符(间接访问操作符)

	int a = 10;
	int* pa = &a;
	*pa = 20;//* - 解引用操作符
	//
	//*&a ==> a;
	return 0;
}

7. 关系操作符

> >= < <= !=  ==

没什么好说的 

8. 逻辑操作符 

&&   逻辑与
||      逻辑或

逻辑与 只有左右两个表达式全为真,整个表达式才为真

逻辑或 只有左右两个表达式全为假,整个表达式才为假 

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

分析:i = a++ || ++b || d++; a++先使用a,再++,ay一·开始是0,往下走,++b,先b++,在使用,而整个表达式的值已经为真,则后面的d++没有执行,故程序运行结果是1 ,3 ,3 ,4。

9. 条件操作符 

exp1 ? exp2 : exp3

如果语句1为真,表达式结果就为语句2,反之则为语句3 

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

10. 逗号表达式 

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

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

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

1. [ ] 下标引用操作符

操作数:一个数组名 + 一个索引值

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

2. ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

注意:可以没有参数

3. 访问一个结构的成员
 .  结构体.成员名
-> 结构体指针 ->成员名 

#include <stdio.h>
struct Stu
{
   char name[10];
   int age;
   char sex[5];
   double score;

};

void set_age1(struct Stu stu)
{
   stu.age = 18;
}

void set_age2(struct Stu* pStu)
{
   pStu->age = 18;//结构成员访问
}

int main()
{
   struct Stu stu;
   struct Stu* pStu = &stu;//结构成员访问
   stu.age = 20;//结构成员访问
   set_age1(stu);
   pStu->age = 20;//结构成员访问
   set_age2(pStu);
   return 0;
}

12. 表达式求值 

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

12.1 隐式类型转换 

C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。


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

char a,b,c;
...
a = b + c;

b和c的值被提升为普通整型,然后再执行加法运算。

加法运算完成之后,结果将被截断,然后再存储于a中。

如何进行整体提升呢?

整形提升是按照变量的数据类型的符号位来提升的

//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0

举两个例子 

int main()
{
	char c1 = 3;
	//首先要进行整型提升
	//00000000000000000000000000000011
	//00000011 - c1
	char c2 = 127;
	//首先要进行整型提升
	//00000000000000000000000001111111
	//01111111 - c2
	

	char c3 = c1 + c2;
	//00000000000000000000000000000011
	//00000000000000000000000001111111
	//00000000000000000000000010000010
	// 这里要发生截断
	//10000010 - c3
	// 此时c1是字符型,但我们是要以整型形式打印,所以还要进行整型提升。
	//11111111111111111111111110000010 补码
	//11111111111111111111111110000001 反码 
	//10000000000000000000000001111110 原码
	//-126
	
	printf("%d\n", c3);//
	
	return 0;
}
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;
}

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

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

c只要参与表达式运算,就会发生整形提升,表达式 +c ,就会发生提升,所以 sizeof(+c) 是4个字节.
表达式 -c 也会发生整形提升,所以 sizeof(-c) 是4个字节,但是 sizeof(c) ,就是1个字节. 

12.2 算术转换 

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

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

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

12.3 操作符的属性 

复杂表达式的求值有三个影响的因素。
1. 操作符的优先级(一定是相邻操作符)
2. 操作符的结合性
3. 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

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

比如,该代码在几种编译环境下得出的结果都不相同。

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

  • 37
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 28
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

早点睡觉1.0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值