C语言-操作符是什么?

目录

  • 操作符
    • 进制
      • 四种进制
      • 进制转换
    • 原码 反码 补码
    • 操作符分类
      • 算术操作符
      • 移位操作符
      • 位操作符
      • 赋值操作符
        • 复合赋值符
      • 单目操作符
      • 关系操作符
      • 逻辑操作符
      • 条件表达式
      • 逗号表达式
      • 下标引用操作符
      • 函数调用操作符
      • 结构体成员
    • 表达式求值
      • 隐式类型转换(整型提升)
      • 算术转换
      • 操作符属性
      • 操作符属性

操作符

操作符又称为运算符。作为运算对象的变量或者常量称为操作数
操作符做左侧的操作数称为第一操作数或者左操作数,操作符右边的操作数称为第二操作数或者右操作数
操作符同时对两个操作数进行运算的称为双目操作符,操作符只对一个操作数进行运算的称为单目操作符

进制

在大家使用的电子计算机中所有的数据都是用ON/OFF信号(即1/0)来表示的。而我们比较用于理解的在日常接触的是以10为基数的十进制数,而对计算机来说以2为基数的二进制数则更易于理解。

基数,在表示数值的时候,基数是进位的基准。基数为10的十进制数哦,每逢10或10的倍数就进位。

虽说对于硬件底层的程序而言,二进制数会更加适宜,但是对于我们而言,将所有的数值都用二进制数来表示可就是一件令人头疼的事情了。二进制虽然有很多优点,但是位数过多使得处理和阅读不便也是不可忽略的缺点,所以在写法上还使用了相对书写和便于阅读的八进制数十六进制数

四种进制

  1. 十进制
    十进制中,如果0~9这十个数字用完就进位
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9
    10,11,12,13,14,15,16,17,18,19
    20,21,22,23,24,25,26,27,28,29

  2. 八进制

在八进制中,如果0~7这八个数字用完就进位
0, 1, 2, 3, 4, 5, 6, 7
10,11,12,13,14,15,16,17
20,21,22,23,24,25,26,27

  1. 十六进制

在十六进制中,十六个数分别为0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F,当这十六个数用完时,进位
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
10,11,12,13,14,15,16,17,18,19,1A,1B,1C,1D,1F

  1. 二进制

在二进制中,0和1用于表示所有的数据,当0和1用完进位
00000000,00000001,00000010,00000011,00000100
上述五个二进制数分别表示十进制中:0,1,2,3,4

二进制数规律:

对于一个十进制数
如果为偶数,那么这个数的二进制数末尾数字为0
如果为奇数,那么这个数的二进制数末尾数字为1
(也就是说用一个十进制数除以2做得到的余数就是末尾数字的值)

进制转换

C语言-操作符是什么?_第1张图片

  1. 二进制、八进制、十六进制 转 十进制数

    • 二进制转十进制
      将二进制数101转为十进制数的步骤如下:
      101 = 1 × 2 2 + 0 × 2 1 + 1 × 2 0 = 1 × 4 + 0 × 2 + 1 × 1 = 5 \begin{aligned} 101&= 1 \times 2^2 + 0 \times 2^1 + 1 \times 2^0 \\ &= 1 \times 4 + 0 \times 2 + 1 \times 1\\ &= 5 \\ \end{aligned} 101=1×22+0×21+1×20=1×4+0×2+1×1=5
    • 八进制转十进制
      将八进制数123转为十进制数的步骤如下:
      123 = 1 × 8 2 + 2 × 8 1 + 3 × 8 0 = 1 × 64 + 2 × 8 + 3 × 1 = 83 \begin{aligned} 123&= 1 \times 8^2 + 2 \times 8^1 + 3 \times 8^0 \\ &= 1 \times 64 + 2 \times 8 + 3 \times 1\\ &= 83 \\ \end{aligned} 123=1×82+2×81+3×80=1×64+2×8+3×1=83
    • 十六进制转十进制
      将十六进制数1FD转换为十进制数的步骤如下:
      1 F D = 1 × 1 6 2 + 15 × 1 6 1 + 13 × 1 6 0 = 1 × 256 + 15 × 16 + 13 × 1 = 509 \begin{aligned} 1FD&= 1 \times 16^2 + 15 \times 16^1 + 13 \times 16^0 \\ &= 1 \times 256 + 15 \times 16 + 13 \times 1\\ &= 509 \\ \end{aligned} 1FD=1×162+15×161+13×160=1×256+15×16+13×1=509
  2. 十进制 转 二进制、八进制、十六进制

    • 转 二进制
      将十进制数57转换为二进制数的步骤如下:
      57 / 2 = 28............1 28 / 2 = 14............0 14 / 2 = 7.............0 7 / 2 = 3..............1 3 / 2 = 1..............1 1 / 2 = 0..............1 转 化 为 二 进 制 结 果 为 : 111001 57/2=28............1\\ 28/2=14............0\\ 14/2=7.............0\\ 7 /2=3..............1\\ 3 /2=1..............1\\ 1 /2=0..............1\\ 转化为二进制结果为:111001 57/2=28............128/2=14............014/2=7.............07/2=3..............13/2=1..............11/2=0..............1111001

    • 转 八进制
      将十进制数57转换为八进制数的步骤如下:
      57 / 8 = 7............1 7 / 8 = 0............7 转 化 为 八 进 制 结 果 为 : 71 57/8=7............1\\ 7/8=0............7\\ 转化为八进制结果为:71 57/8=7............17/8=0............771

    • 转 十六进制
      将十进制数57转换为十六进制数的步骤如下:
      57 / 16 = 3............9 3 / 16 = 0............3 转 化 为 十 六 进 制 结 果 为 : 39 57/16=3............9\\ 3/16=0............3\\ 转化为十六进制结果为:39 57/16=3............93/16=0............339

原码 反码 补码

整数的二进制表示形式其实有三种,分别为原码、反码、补码。
原码是我们平时所书写的二进制形式,例如把一个十进制数转换为二进制数,这个二进制数就是原码的形式。
而反码和补码,是通过原码进行转换得到的。
而在内存中,存储的其实是补码。所以对于对二进制位进行操作的操作符,本质上都是对补码进行操作。
我们知道一个有符号数是分正负的,而在二进制中,用最高位表示符号位,正数最高位为0,负数最高位为1。

对于一个有符号数来说

正数:原码、反码、补码相同。
负数:原码、反码、补码不同,原码经过计算可以得到反码和补码。

原码反码补码计算

原码:按照一个数的正负,直接写出其二进制表示形式得到的就是原码。
反码:原码的符号位不变,其余位全部按位取反,得到的就是反码。
补码:在反码的基础上加1,得到的就是补码。

原码求反码计算流程

原码 → 符 号 位 不 变 其 余 位 按 位 取 反 \xrightarrow[]{符号位不变其余位按位取反} 反码 → 反 码 加 一 \xrightarrow[]{反码加一} 补码

反码求原码计算流程

方法1:补码 → 补 码 减 一 \xrightarrow[]{补码减一} 反码 → 符 号 位 不 变 其 余 位 按 位 取 反 \xrightarrow[]{符号位不变其余位按位取反} 原码
方法2:补码 → 符 号 位 不 变 其 余 位 按 位 取 反 \xrightarrow[]{符号位不变其余位按位取反} 反码 → 反 码 加 一 \xrightarrow[]{反码加一} 原码

操作符分类

  1. 算术操作符
  2. 移位操作符
  3. 位操作符
  4. 赋值操作符
  5. 单目操作符
  6. 关系操作符
  7. 逻辑操作符
  8. 条件操作符
  9. 条件操作符
  10. 逗号操作符
  11. 下标引用
  12. 函数调用
  13. 结构体成员

算术操作符

操作符 语法
+ 操作符 a + b
- 操作符 a - b
* 乘法操作符 a * b
/ 除法操作符 a / b
% 取模(求余数)操作符 a % b 余数
  1. 除法操作符(/):求商,两边的操作数都为整数,并且右操作数不能为0,这与数学中是相同的。如果想计算出小数,除号的两端至少有一个操作数是浮点数。
    • 9 / 2的商为 4
    • 9.0 / 2的商为 4.500000(输出为double类型)
      • double类型输出时,默认显示小数点后6位
  2. 取模操作符(%):用于求余数,并且操作符两端都只能是整型。
    • 余数: 9 / 2的余数为 1

移位操作符

上面说过位操作符所操作的数是存储在内存中的补码,所以位移操作符是对补码进行移位操作。

操作符 语法
<< 左移操作符 a << b 将a左移b位,右边空出的位用0填充
>> 右移操作符 a >> b 将a右移b位
  1. 左移操作符(<<)
    a << b 会将a的所有位按位向左移动b位,并在右边空出的位(低位)上补0,如果a为无符号整型,则a << b 的运算结果为 a × 2 b a\times2^b a×2b

    十进制数在左移一位后,值会变成原本的十倍,例如196左移一位变为1960。二进制也是一样,在没有溢出的情况下,左移一位值变为原值的2倍。

  2. 右移操作符(>>)
    a >> b 会将a的所有位按位向右移动b位。

    如果a为无符号整型或者有符号整型的非负数,则运算结果为 a ÷ 2 b a\div2^b a÷2b所得到结果的整数部分。
    如果a为有符号整型的负数形式,位移运算的结果因编译器的不同而会有所不同,在许多编译器中会执行逻辑位移或者算术位移,但是无论使用哪种方式都会降低程序的可移植性所以我们应该或者说不要对负数进行位移

C语言-操作符是什么?_第2张图片

  1. 逻辑位移与算术位移
  • 逻辑右移
    逻辑右移不考虑符号位的情况是0还是1,所有的二进制位都进行位移,负整数右移时,符号位由1变为0,位移的结果为0或整数。

  • 算术右移
    算术右移,所有二进制位进行位移,用符号位来补空位,例如符号位为1,所有空位补1。

位操作符

操作符 语法
& 按位与 a & b 计算a和b的逻辑与
| 按位或 a | b 计算a和b的逻辑或
^ 按位异或 a ^ b 计算a和b的逻辑异或

这些运算符的操作数必须是整型数据类型或者枚举类型。
这些运算符会根据1为真0为假的运算规则对操作数的各二进制位进行逻辑运算。

	int a = 3;
	//正数:原反补相同
	//补码:00000000000000000000000000000011

	int b = -5;
	//原码:10000000000000000000000000000101
	//反码:11111111111111111111111111111010
	//补码:11111111111111111111111111111011
  1. 逻辑与(&)
    两个操作数,对应的二进制位,有0则为0,两个同时为1,才为1。
	int c = a & b; 
	//a-补码:00000000000000000000000000000011
	//b-补码:11111111111111111111111111111011
	// a & b:00000000000000000000000000000011
  1. 逻辑或(|)
    两个操作数,对应的二进制位,有1则为1,两个同时为0,才为0。
	int c = a | b; 
	//a-补码:00000000000000000000000000000011
	//b-补码:11111111111111111111111111111011
	// a | b:11111111111111111111111111111011
  1. 按位异或(^)
    两个操作数,对应的二进制位,相同为0,相异为1。
	int c = a | b; 
	//a-补码:00000000000000000000000000000011
	//b-补码:11111111111111111111111111111011
	// a ^ b:11111111111111111111111111111000

赋值操作符

操作符 语法
= 赋值操作符 a = b 把b的值赋值给a

利用赋值操作符,你可以将你之前不满意的值修改为你满意的值(例如你的工资)

复合赋值符
操作符 语法
+= 加法赋值 a += b a = a + b
-= 减法赋值 a -= b a = a - b
*= 乘法赋值 a *= b a = a * b
/= 除法赋值 a /= b a = a / b
%= 取模赋值 a %= b a = a % b
<<= 左移赋值 a >>= b a = a >> b
>>= 右移赋值 a <<= b a = a << b
&= 位逻辑与赋值 a &= b a = a & b
|= 位逻辑或赋值 a |= b a = a | b
^= 位逻辑异或赋值 a ^= b a = a ^ b

单目操作符

操作符 语法
! 逻辑反 !a 当a本身为0时,!a值为1, 当a本身为1时,!a值为0
- 负值 -a a值为正数时,-a值为负数
+ 正值 +a
& 取地址 &a 取a的地址
sizeof 求类型长度 sizeof(a) 求a(对象、常量、类型名等)的长度
~ 按位取反 ~a 对一个数的二进制位按位取反
– 自减1 a–/–a –a,前置–,先自减1再使用 a–,后置–,先使用再自减1
++ 自加1 a++/++a ++a,前置++,先自加1再使用 a++,后置++,先使用再自加1
* 间接访问操作符 *a 又称为解引用操作符
()强制类型转换 (类型)a 将一个变量的或者常量的类型,强制类型转换为括号内的类型

关系操作符

操作符 语法
> a > b 判断a是否大于b,若a大于b返回1,若a不大于b返回0
>= a >= b 判断a是否大于等于b,若a大于等于b返回1,若a小于b返回0
< a < b 判断a是否小于b,若a小于b返回1,若a不小于b返回0
<= a <= b 判断a是否小于等于b,若a小于等于b返回1,若大于b返回0
!= a != b 判断a是否不等于b,若a不等于b返回1,若a等于b返回0
== a == b 判断a是否等于b,若a等于b返回1,若a不等于b返回0

要注意区分=和== 一个等号为赋值两个等号为判断相等

逻辑操作符

操作符 语法
&& 逻辑与 a && b 如果a和b都不为0,则表达式的结果为1;如果a和b其中一个为0则表达式结果为0
|| 逻辑或 a || b 如果a和b中有一个不为0,则表达式的结果为1;如果a和b都为0,则表达式结果为0

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

要注意区分
逻辑与 和 按位与
逻辑或 和 按位或

  1. 布尔类型
    从C99开始,C语言中引入了布尔类型用于表示真假
    布尔类型中,真为true,假为false

看一段C语言源代码

#define bool  _Bool
#define false 0
#define true  1

从上述代码我们可以知道,布尔类型的本质还是0和1,在编写代码时,使用bool_bool都是允许的。

  1. 短路计算
  • &&逻辑与操作符,在左操作数的结果为0时,操作符右边操作数不再进行计算。
  • ||逻辑或操作符,在左操作数的结果不为0时,不再对右边的操作数进行计算。

条件表达式

操作符 语法
exp1 ? exp2 : exp3 a ? b : c 计算表达式a,若a不为0,则结果为表达式b的结果;若a为0,则结果为表达式c的结果

逗号表达式

操作符 语法
exp1,exp2,exp3,…,expN a,b,c 从左向右依次执行,整个逗号表达式的结果就是最后一个表达式expN的计算结果

下标引用操作符

操作符 语法
[ ] arr[a] 访问数组arr中下标为a的元素

函数调用操作符

操作符 语法
( ) strlen(“abcdef”) 调用strlen函数,求abcdef的字符串长度

结构体成员

操作符 语法
. a . b 表示结构体a中的成员b;a为对象名或者结构体名,b为成员名。
-> a -> b 用指针访问结构体a中的成员b;a为结构体指针,b为成员名

(*p).m可以简写为 p->m

.运算符和->运算符统称为访问运算符。

表达式求值

表达式求值的顺序一般是由操作符的优先级和结合性决定的。

隐式类型转换(整型提升)

在C语言整型算术运算中,通常至少以整型类型的精度进行计算。为了获得这一精度,表达式中的字符或者短整型操作数会在使用之前转换为普通整型,这种转换称为整型提升
当然,如果在同时存在int型和unsigned int型的表达式中,如果int型无法表示出原数据类型的所有数值,就将值转换为unsigned int型。

整型提升 不会改变符号和数值,char类型是否作为符号类型来处理,需要由编译器决定,不同的编译器所运算标准不同。

#include
int main()
{
   //int类型内存中占位为4个字节,32个比特位
   //char类型内存中占位为1个字节,8个比特位

   char a = 3;
   //3在内存中的补码为:00000000000000000000000000000011
   //把一个int类型的3存放在char类型的a中需要进行截断
   //00000011  -a

	char b = 127;
   //127在内存中的补码为:00000000000000000000000001111111
   //01111111  -b

	char c = a + b;
   //表达式中的字符或转整型在使用之前需要转换为普通整型——这种转换称为整型提升
   //在vs2019中默认char类型为signed char也就是有符号字符类型
   //对于有符号类型的整型提升,符号位为什么(0/1),整型提升时的空位就补什么
   //00000000000000000000000000000011  -a   //符号位为0,空位补0
   //00000000000000000000000001111111  -b
   //00000000000000000000000010000010  -a+b
   
   //把a+b之后得到的存储在char类型的c中需要进行截断
   //10000010  -c

	printf("%d\n", c);
   // %d 打印一个十进制的整型
   //c为一个有符号char类型,所以需要进行整型提升
   //10000010  -c     符号位为1 整型提升空位补1
   //11111111111111111111111110000010  -补码 
   //内存中存储的是二进制补码形式,打印显示出来的为原码表示的十进制值
   //11111111111111111111111110000001
   //10000000000000000000000001111110  -原码

	return 0;
}

总结:

在表达式进行运算之前,编译器会自动为表达式中的char类型和short类型操作数进行整型提升,然后在进行运算。
当把一个较大类型存储在一个较小类型中,需要进行截断
不同的编译器对于char类型是否有符号是不同的

算术转换

如果一个双目操作符,甚至多目操作符的几个操作数属于不同的数据类型,那么编译器会对其做数据类型转换。数据类型转换的目的是为了确定通用数据类型,否则不同的数据类型操作数不能将进行运算。这一过程称为普通算术类型转换

类型 排名
long double 1
double 2
float 3
unsigned long int 4
long int 5
unsigned int 6
int 7

上述表格中的类型,如果同时出现在一个操作符的操作数中,在进行算术类型转换时,通常为排名靠后的向排名靠前的类型转换。
但是也要注意,转换需要合理,不然会造成精度的缺失,例如一个浮点型数转换为int型,小数点后面的数会丢失,造成精度丢失。

操作符属性

复杂表达式的求值具有三个影响的因素:

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序

两个相邻的操作符,谁的优先级高先执行谁。
优先级相同,取决于结合性。

C语言-操作符是什么?_第3张图片

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

t | 3 |
| unsigned long int | 4 |
| long int | 5 |
| unsigned int | 6 |
| int | 7 |

上述表格中的类型,如果同时出现在一个操作符的操作数中,在进行算术类型转换时,通常为排名靠后的向排名靠前的类型转换。
但是也要注意,转换需要合理,不然会造成精度的缺失,例如一个浮点型数转换为int型,小数点后面的数会丢失,造成精度丢失。

操作符属性

复杂表达式的求值具有三个影响的因素:

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序

两个相邻的操作符,谁的优先级高先执行谁。
优先级相同,取决于结合性。

[外链图片转存中…(img-xsiyNEIi-1669379888954)]

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

你可能感兴趣的:(C历程,c语言,开发语言,学习方法,程序人生,改行学it)