C语言—— [操作符详解] 零基础也照样拿捏


前言

别把生活过的一团糟
详解C的操作符


操作符

1.算数操作符

• +
• -
• *
• /
• %
(取模/取余)

如何用 / 得到一个浮点数?

  • 变量类型:浮点数类型
  • / 两边的操作数至少一个浮点数
#include<stdio.h>
int main()
{
	float a = 5 / 2.0;
	printf("%d", a);
}

取模操作符 % 两边的操作数必须都为整型


2.移位操作符

• <<
• >>(移动二进制位)

基本概念理解

  • 按二进制位取反

  • 整数的二进制表示形式(三种)

  • 整数在内存中的存储

代码理解

<< 左移操作符:左边丢弃,右边补0

#include<stdio.h>
int main()
{
	int a = 2;
	int b = a << 1;//左移操作符
	
	printf("%d\n", b); //b = ?
	return 0;
}

分析:
    1、 局部变量 a 的类型 - int
    2、 int类型所占空间大小 - 4byte == 32bit
    3、 2 的二进制表示
     

>> 右移操作符:通常采用算数右移
        1) 算数右移: 右边丢弃,左边补原符号位(0 / 1)
        2) 逻辑右移: 右边丢弃,左边补0

#include<stdio.h>
int main
{
	int a = -1;
	int b = a >> 1;//右移操作符

	printf("%d\n", b); //b = ?
	return 0;
}

分析:
在这里插入图片描述

注意:
   i. << 和 >> 并不改变 a 的值
   ii. 只是把 a >> 1的结果存入变量 b 中


3.位操作符

按二进制位
• &
• |
• ^

代码理解

& 按位与:真真 - 真,假假 - 假,真假 - 假
     1 1 - 1, 0 0 - 0, 1 0 - 0
| 按位或: 真真- 真, 假假 - 假,真假 - 真,
     1 1 - 1, 0 0 - 0, 1 0 - 1

int main()
{
	int a = 3;
	int b = 5;
	int c = a & b;
	int d = a | b;
	//a:00000000000000000000000000000011 - 3
	//b:00000000000000000000000000000101 - 5
	//c:00000000000000000000000000000001 - 1
	//d:00000000000000000000000000000111 - 7
	return 0;
}

^ 按位异或:规则:相同取0 , 不同取1
      性质:a ^ a = 0 (相同取0)
         0 ^ a = a (不同取1)
- 是1的地方还是1 - 不变


交换两个变量的值,不使用第三个变量

#include<stdio.h>
//交换两个变量的值,不使用第三个变量
int main()
{
	int a = 3;
	int b = 5;
	a = a ^ b;
	b = a ^ b;//(3 ^ 5 ^ 5 == 3 ^ 0 == 3) - 成功交换
	//此时,b == 3; a == 3 ^ 5;
	a = a ^ b;//(3 ^ 5 ^ 3 == 5 ^ 0 == 5)
	printf("a = %d, b = %d", a , b);
}

4.赋值操作符

• =
• +=
• -=
• *=
• /=
• &=
• |=
• ^=
•>>=

代码理解

复合赋值符的含义

int main()
{
	int a = 0;
	a += 1;
	a = a + 1;//语义相同
	return 0;
}

连续赋值

int main()
{
	int a = 0, x = 1, y = 2;
	
	a = x = y + 1;//连续赋值(不推荐)
	//理解(同语义)
	x = y + 1;
	a = x;// 这样写更清晰,易于调试
	return 0;
}

=  赋值
== 判断相等
比较两个字符串时不能用 ==


5.单目操作符

只有一个操作数
• !
• -
• +
• &
• Sizeof
• ~
• - -
• ++
• *
• (类型
)

代码理解

!逻辑反操作

#include<stddio.h>
int main()
{
	//规定:0为假,非 0 为真
	int a = 0;
	printf("a = %d", !a);//打印出 a = 1
	if (a)
	{
		printf("不打印\n");
	}
	if (!a)
	{
		printf("打印\n");
	}
	return 0;
}

–      负值
+      正值
&      取地址
*       间接访问操作符 (解引用操作符)
(类型)  强制类型转换

#include<stdio.h>
int main()
{
	int a = 0;
	a = +5;//一般 + 省略
	a = -5;//表负值
	
	int* pa = &a;//&取地址
	*pa = 5;//解引用操作符 - 通过 a 的地址找到 a 
	
	a = (int)3.14;//强制把3.14浮点型转化成整型

	return 0;
}

sizeof   计算类型 / 变量所占内存大小 (单位:byte)

基本理解

#include<stdio.h>
int main()
{
	//sizeof计算类型 / 变量大小
	printf("%d\n",sizeof(a));
	printf("%d\n",sizeof a);//计算变量大小时()可省略 - 说明sizeof不是函数,是关键字
	printf("%d\n",sizeof(int))//计算类型大小不能省略()
	
	//sizeof 计算数组大小
	int arr[10] = { 0 };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(int [10]));
}

深层理解

int main()
{
	short s = 5;
	int a = 2;
	printf("%d\n", sizeof(s = a + 2));//sizeof() - ()中的表达式不参与运算 
	//翻译 + 链接 - 运行
	//表达式的计算发生在运行程序中 - 但翻译时sizeof(s = a + 2)直接翻译成了 2
	//打印结果是 2
	//类型由 s 决定
	//a + 2的值并没有赋给 s
	a + 3;//值属性 + 类型属性
	printf("%d\n", s);//s == 5;
	return 0;
}
#include<stdio.h>
void test_1(int arr[])
{
	//数组传参传首元素地址
	printf("1:%d\n", sizeof(arr));// 4或8(32位系统/64位系统)
}
void test_2(char ch[])
{
	printf("2:%d\n",sizeof(ch));// 4或8
}
int main()
{
	int arr[10] = { 0 };
	char ch[10] = { 0 };
	printf("%d  %d\n", sizeof(arr), sizeof(ch));//40  10
	test_1(arr);
	test_2(ch);
}

- -    前置、后置
++    前置、后置

前置:先++,后使用
后置:先使用,后++

#include<stdio.h>
int main()
{
	int a = 10;
	int b = a++;//b = 10(a的值赋给 b后,a++,a = 11)
	printf("a = %d, b = %d", a, b);//a = 11, b = 10

	b = ++a;// b = 12(a++,a = 12后,把a的值赋给b)
	printf("a = %d, b = %d", a, b);//a =12, b = 12

	printf("%d", a--);//打印a = 12后(先使用), a--,a = 11
	printf("%d", a);//a = 11
	
	return 0;
}

~   按位取反(二进制位)

基本理解

int main()
{
	int a = -1;
	printf("%d", ~a);
	
	// a:11111111111111111111111111111111(-1在内存中存储的补码)
	//~a:00000000000000000000000000000000
	
	return 0;
}

操作一个数的二进制位

#include<stdio.h>
int main()
{
	int a = 13;
	//00000000000000000000000000001101
	//a 的二进制第五位换成1
	a = a | (1 << 4);
	printf("%d\n", a);
	
	//a 换回去
	a = a & ~(a << 4);
	printf("%d\n", a);

	return 0;
}

6.逻辑操作符

• &&    逻辑与
• ||    逻辑或

逻辑与: 同时满足为真
逻辑或: 至少一个满足为真

代码理解

#include<stdio.h>
int main()
{
	int a = 0;
	int b = 5;//0为真,非0为假
	int c = 0;
	
	c = a && b;
	printf("%d\n", c);//c == 0
	
	c = a || b;       
	printf("%d\n", c);//c == 1
}
#include<stdio.h>
int main()
{
	int i = 0, a = 0,b = 2, c = 3, d = 4;
	i = a++ && ++ b && ++d;
	printf("a = %d  b = %d  c = %d  d = %d\n", a, b, c , d);//a = 1, b = 2, c =3, d = 4
	//分析:
	//1、判断a == 0为假后,a++, a == 1
	//2、a 为假,后面表达式不在执行

	i = a++ || ++b || ++d;
	printf("a = %d  b = %d  c = %d  d = %d\n", a, b, c , d);//a = 2, b = 2, c = 3, d = 4
	//分析:
	//1、判断a == 1为真后,a++, a == 2
	//2、a 为真,后面表达式不在执行
	
	a = 0;
	i = a++ || ++b || ++d;
	printf("a = %d  b = %d  c = %d  d = %d\n", a, b, c , d);//a = 1, b = 3, c = 3, d = 4
	//分析:
	//1、判断a == 0为真假,a++, a == 1
	//2、b++, b == 3
	//3、b == 3为真,后面表达式不在执行
	return 0;
}

7.条件操作符

三目操作符
• exp1 ? exp2 : exp3

规则:
   Exp1 – 真 – exp2计算 – 结果为exp2
   Exp1 – 假 – exp3计算 – 结果为exp3

   


8.逗号表达式

• exp1 , exp2 , exp3 ,… ,expN

规则:
    从左向右依次计算
最后的结果可能受前面计算的影响

    整个表达式的结果是 - 最后一个表达式的结果
    


9.下标引用操作符

• [ ]

10.函数调用操作符

• ( )
在这里插入图片描述

11.结构成员访问操作符

• .
• ->

#include<stdio.h>
struct student//创建结构体类型
{
	char name[20];
	int age;
	char school_name[20];
};
int main()
{
	struct studen Y = {"Eunoiay", 19, "XXX大学"};
	printf("%s %d\n", Y.nmae, Y.age);//访问结构体成员变量
	
	struct studen* p = &Y;
	printf("%s %d\n",p->name, p->age);//访问结构体成员变量
}

12.操作符的属性

• 操作符的优先级
• 操作符的结合性
• 操作符是否控制执行顺序

优先级:大类分为15个等级,小类自上而下递减
    控制相邻两个操作符执行顺序

结合性:控制操作符L – R 和 从R – L 的顺序

控制执行顺序的操作符( 4个 ):

  • &&   逻辑与
  • ||    逻辑或
  • :?     条件操作符
  • ,     逗号
优先级操作符描述结合性
1( )聚合N / A
( )函数调用L — R
[ ]下标引用
.访问结构成员
->访问结构指针成员
2后置++后缀, 自增
后置--后缀, 自减
逻辑反R — L
~按位取反
+表正值
-表负值
前置++前缀, 自增
前置--前缀, 自减
*间接访问
&取地址
sizeof长度运算
(类型)强制类型转换
3*L — R
/
%取模 / 取余
4+
-
5<<左移位
>>右移位
6>大于
>=大于等于
<小于
<=小于等于
7==等于
!=不等于
8&按位与
9^按位异或
10|按位或
11&&逻辑与
12||逻辑或
13?:条件操作符N / A
14=赋值R — L
+=复合赋值
-=
*=
/=
%=
<<=
>>=
&=
^=
|=
15,逗号L — R

13.表达式求值

1. 求值顺序 – 操作符优先级和结合性

  • 两个相邻操作符的执行顺序,取决于他们的优先级。若两者优先级相同,则取决于结合性
  • 除此之外,由编译器自行决定(不违反控制执行顺序的操作符的前提下)

2.求值过程可能需要转换类型

  • 常见 – 隐式类型转换

隐式类型转换:
         1、整形提升
                 i. 表达式中char / short 类型的操作数在使用前转换为int类型 - 为获得精度
                 ii. 按照变量数据类型的符号位提升 (0 / 1)
                 iii. 无符号 - 高位补0

         2、寻常算数转换
                 i. 某个操作符的操作数属于不同的类型时,其中一个操作数进行寻常算数转换
                 ii. 向精度更高(浮点型),长度更长的方向转换
                 iii. 无符号类型可以存放的正数范围比有符号类型 中的范围大一倍 (signed二进制第一位为符号位)unsigned 向 signed转换可能会丢失信息
                 iv. 整型数如果转换为signed不会丢失信息,就会转换为signed,否则转换为unsigned。
                

整型提升

1.为什么要整型提升?
整型操作器 (ALU) - CUP中执行整型运算的器件
  一般,ALU的操作数的字节长度 == int的字节长度 == CPU通用寄存器的长度
  因此,CPU难以直接进行(char / short)1 / 2byte的运算
  所以,表达式中各种长度 小于 int长度的整型值,都必须先转换为int或unsigned int,才能送入CPU执行运算。

2.如何进行整型提升?

  • 表达式中char / short 类型的操作数在使用前转换为int类型 - 为获得精度
  • 按照变量数据类型的符号位提升 (0 / 1)
  • 无符号 - 高位补0

3.代码理解

#include<stdio.h>
int main()
{
	char a = 3;
	char b = 127;
	char c = a + b;
	//1. char类型长度 1 byte = 8 bit
	//2. a: 00000011
	//   b: 01111111
	//3. 整型提升
	//   a:00000000000000000000000000000011
	//   b:00000000000000000000000001111111
	//   c:00000000000000000000000010000010
	//4. 截断
	//   c:10000010(第一位是符号位)
	printf("%c\n", c);
	//10000010 - ASCII码值无对应的符号
	printf("%d\n", c);//注意:%d的格式打印
	//5. %d的格式打印 - 整型提升
	//   c:11111111111111111111111110000010(补码)
	//     11111111111111111111111110000001(反码)补码 - 1
	//     10000000000000000000000001111110(原码)反码符号位不变,其他位取反
	//   c = -126
	return 0;
}
int main()
{
	//0x表示后面的数以16进制计算
	char a = 0xb6;      //b6 == 182
	short b = 0xb600;   //b600 == 46592
	int c = 0xb6000000; //b6000000 == 3053453312
	
	if (a == 0xbb6)     //char 类型和 int 类型比较 -- 整型提升
		printf("a");
	if (b == 0xb600)    //short类型和 int 类型比较 -- 整型提升
		printf("b");
	if (c == 0xb6000000)
		printf("c");
					    //代码运行结果为c
}

寻常算数转换

1.为什么要算数转换?
  某个操作符的操作数属于不同的类型时,其中一个操作数进行寻常算数转换

2.如何进行算数转换?

  • 向精度更高(浮点型),长度更长的方向转换
  • 无符号类型可以存放的正数范围比 有符号类型 中的范围大一倍 (signed二进制第一位为符号位)unsigned 向 signed转换可能会丢失信息
  • 整型数如果转换为signed不会丢失信息,就会转换为signed,否则转换为unsigned。

3.代码理解
在这里插入图片描述


14.问题表达式

有不确定因素,有歧义

1 * 2 + 3 * 4 + 5 * 6 的运算顺序

a、相邻操作符:* + *
b、确定的顺序:2 + 12 + 5 * 6
c、不确定的顺序:14 + 5 * 6 == 44
          2 + 12 + 30 == 44
在这里插入图片描述

int c = 2; c - ++c中 操作数c的值的获取顺序
a、相邻操作符:- ++
b、确定的顺序:c - 3
c、不确定的顺序:2 - 3 == -1
          3 - 3 == 0

问题代码

《C和指针》中证明表达式的求值顺序只是部分由操作符的优先级决定的程序

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

《C和指针》中不同编译器下的值

编译器
-128Tandy 6000 Xenix 3.2
-95Think C 5.02(Macintosh)
-86IBM PowerPC AIX 3.2.5
-85Sun Sparc cc(K&C编译器)
-63gcc,HP_UX 9.0,Power C 2.0.0
4Sun Sparc acc(K&C编译器)
21Turbo C/C++ 4.5
22FreeBSD 2.1 R
30Dec Alpha OSF1 2.0
36Dec VAX/VMS
42Microsoft C 5.1

问题代码

Linux环境:ret == 10, i == 4
VS2019环境:ret == 12, i == 4

#include <stdio.h>
int main()
{
 int i = 1;
 int ret = (++i) + (++i) + (++i);
 //相邻操作符:(++)   优先级只控制相邻两个操作符
 //                  先执行() 和 ++ 都一样
 //                  因此(++i) + 相当于 ++i +
 //					 原式:++i + ++i + ++i
 //                  相邻操作符:++ + ++
 //                  确定的顺序:先++i  ++i(两次确定)
 //                不确定的顺序:3 + 3 + ++i(Linux环境)
 //  			               再++i后,4 + 4 + 4(VS2019)
 //Linux环境: 3 + 3 + 4 = 10,i = 4 ++两次,赋值,++,赋值
 //VS2019环境:4 + 4 + 4 = 12,i = 4 ++三次,赋值
 
 
 printf("%d\n", ret);
 printf("%d\n", i);
 return 0;
}

问题代码 - 函数调用顺序不确定

操作符仅决定了运算的执行顺序,函数调用的顺序并没有规则加以限定

int fun()
{
     static int count = 1;//生命周期同全局变量 - 不自动销毁
     return ++count;
}
int main()
{
     int answer;
     answer = fun() - fun() * fun();
     
     printf( "%d\n", answer);
     return 0;
}

在这里插入图片描述


共勉!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值