当前位置: 首页 > news >正文

C语言----操作符详解(万字详解)

目录

1. 操作符的分类

2. 二进制和进制转换

3. 原码 反码 补码

4. 移位操作符

4.1 左移操作符 >>

4.2 右移操作符 >>

5. 位操作符

5.1 按位与 &

5.2 按位或 |

5.3 按位异或 ^

5.4 按位取反 ~

练习 整数存储在内存中二进制中1的个数

练习 二进制位置置0或者置1

6.  单目操作符

7.  逗号表达式

8. 下标访问[  ]   函数调用( )

8.1 [ ] 下标引用操作符

8.2 () 函数调用操作符

9. 结构成员的访问操作符

9.1 结构体

9.2 结构体的声明

9.3 结构体变量的声明与初始化

9.3.1 声明结构体变量

9.3.2 初始化结构体变量

9.4 访问结构体成员

10. 操作符的属性: 优先级  结合性

10.1  优先级

10.2  结合性

11. 表达式求值

11.1 整型提升

11.2 算数转换


1. 操作符的分类

类型具体操作符
算术操作符+(加法)、-(减法)、*(乘法)、/(除法)、%(取模 / 取余)
移位操作符<<(左移)、>>(右移)
位操作符&(按位与)、(按位或)、^(按位异或)
赋值操作符=(简单赋值)、+=(加法赋值)、-=(减法赋值)、*=(乘法赋值)、/=(除法赋值)、%=(取模赋值)、<<=(左移赋值)、>>=(右移赋值)、&=(按位与赋值)、=(按位或赋值)、^=(按位异或赋值)
单目操作符!(逻辑非)、++(自增)、--(自减)、&(取地址)、*(指针取值 / 解引用)、+(正号)、-(负号)、~(按位取反)、sizeof(获取字节数)、(类型)(类型转换)
关系操作符>(大于)、>=(大于等于)、<(小于)、<=(小于等于)、==(等于)、!=(不等于)
逻辑操作符&&(逻辑与)、(逻辑或)
条件操作符? :(三元条件)
逗号表达式操作符,(逗号)
下标引用操作符[ ](方括号)
函数调用操作符( )(圆括号)
结构成员访问操作符.(结构体变量成员访问)、->(结构体指针成员访问)

上面的操作符  我们在数据类型和变量中以及学过了一部分 今天我们继续具体介绍我们没有学过的操作符 但那之前 我们得学习一下二进制 因为有些操作符与二进制有关

2. 二进制和进制转换

在以前学数学时 我们也听说过2进制 10进制 那他们是什么意思呢 ?

其实2进制 8进制 10进制 16进制 都是值的不同表现形式 只不过我们平常使用值的时候都默认了10进制方式

15的2进制        1111

15的8进制        17

15的10进制      15

15的16进制       F

在十进制中的 152  其中2是个位  5是十位  1是百位 相当于权重

123相当于3乘以10的0次方+2乘以10的1次方+1乘以10的2次方  往后依次在次方上按顺序+1

而理解了十进制之后 其实其他进制也是相同理解

比如2进制的1101   从左到右分别是2的0  1  2  3次方 

那么就相当于1✖2的0次方+1✖2的1次方+0✖2的2次方+1✖2的3次方 既1+2+4+8=13

因此二进制的1101实际上就是13  也就是10进制的13

再举个8进制的例子 8进制的17是多少呢?

7✖8的0次方+1✖8的1次方=7+8=15    既15的8进制写法就是17

而最后我们就来讲解以下16进制 

首先我们知道2进制数字是由0 1组成  8进制是由0~7组成  10进制由0~9组成  因为分别满2 8 10会进一位

而16进制呢? 因为0~9不够 因此我们使用ABCDEF(小写也适用)来分别表示10 11 12 13 14 15 这样就可以表示完了

比如F  就是F✖16的0次方=15✖1=15

而2进制如何转10进制呢   刚才已经讲过了 权重✖权重值即可

10进制如何转换成2进制呢 如下图

通过10进制数字 除以2 得到的余数 从下往上写就是转换出的2进制

而下面在计算机上试着看看相同数字 不同进制时的值是多少

可以看出来数字都是161  而10进制的值就是161  8进制的值是113  16进制的值是353

而下面我们来说说2进制 转8进制和16进制的方法

8进制数字是0~7的数字 各自写成二进制每3个二进制位就足够了  比如7的二进制位是111  所以2进制转8进制的时候 从2进制序列中 右边低位开始向左依次 每3个2进制位换算一个8进制位 剩余不够3个2进制位则直接换算

比如2进制位的01101011 换算成8进制就是0153  这是8进制表示方法 0开头的数字

16进制的数字每一位是0~9 a~f的数字 各自写成2进制 最多有4个2进制位就足够了 比如f的二进制位是1111 所以2进制转16进制的 时候 从2进制序列的右边低位开始向左依次 每4个2进制位换算一个16进制位 剩余不够4个2进制位则直接换算 

比如2进制的01101011 换成16进制表示就是0x6b  这是16进制表示方法 0x开头的数字

同理 8进制位和16进制位 转2进制只需要反着来1个8进制位转换成3个2进制位  1个16进制位转换成4个2进制位即可

3. 原码 反码 补码

整数的2进制表示方法有三种  原码  反码  补码

有符号的三种表示方法均有符号位和数值位两部分  2进制序列中 最高位被当作符号位 剩余的都是数值位 

符号位都是用0表示"正'   用1表示"负' 剩余的都是数值位

而无符号的整数 全部都是数值位

有符号的整数既 signed int  而无符号的整数既 unsigned int 

而他们的原码反码补码怎么算呢?

原码: 直接将数值按照正负数的形式翻译成二进制得到的就是原码

反码: 原码的符号位不变  其他位依次按位取反就可以得到反码

补码:  反码+1就得到补码

: 补码得到原码也是使用:  取反   +1  的操作

举例如下:

无符号整型的三种 2进制表示相同  没有符号位  每一位都是数值位

对于整型来说:数据存放在内存中的其实是补码  那为什么呢?

  1. 加减统一:减法变加法,硬件只需加法器,省成本。
  2. 零唯一:消灭-0,多存一个数(如-128),不浪费。
  3. 运算快:CPU直接算,无需额外处理符号位。
    本质:用“补数”思想(如钟表倒拨=正拨)实现高效计算。

4. 移位操作符

<<  左移操作符     >>  右移操作符   

注:移位操作符的操作数只能是整数 不能是小数和负数

我将通过具体的例子来解释

4.1 左移操作符 >>

从图中 我们就了解到了左移操作符的用法  并且知道了左移操作符并不会改变操作数的值 

而为什么左移操作符移动一位后 值会变大2呗呢  那是因为左移一位之后 二进制数都变大了两倍 因此整体也变大两倍

同理 移动2位 则变大了四倍 如图

注: 并不是所有的数左移一位都变大两倍 

同理 负数也满足此规则

具体操作过程如下图

4.2 右移操作符 >>

首先 右移操作符分为两类 

1 逻辑右移  :  左边用0填充,右边丢弃

2 算术右移  :  左边用原该值的符号位填充,右边丢弃

注: 右移具体是什么右移取决于编译器 而大部分编译器都是采用 算术右移

验证:

逻辑右移用0填充 而算数右移中整数是0开头 也会用0填充 因此我会选择负数 负数的符号位为1 这样就可以判断出来

可以看出来 的确是-5 因此判断出来是补的原本的数值位 因此我的编译器采用的是算数右移

快去试试你的编译器吧

警告: 对于移位操作符来说 不能移动负数位 这个是标准未定义的 

5. 位操作符

位操作符有  ( 双目操作符 )

&            // 按位与            区别于 && 逻辑与 -- 并且

|             // 按位或               区别于 | | 逻辑或 -- 或者

^            // 按位异或                     

~            // 按位取反                       

注: 位指的是二进制位

下面我将用具体的例子来讲解

5.1 按位与 &

这就是 按位与 & 

特点: 按照对应的二进制进行与运算 只要有0则为0   两个同时为1 才为1

5.2 按位或 |

这就是 按位或 |

特点: | 是按位或 是对应的二进制位或运算 只要有1就是1 两个为0才是0

5.3 按位异或 ^

这就是 按位异或 ^

特点:异或 ^ 对应的二进制位 相同为0 相异为1   

5.4 按位取反 ~

这就是 按位取反 ~

特点:按位取反  ( 原来是0变成1 原来是1变成0 )

按位异或 ^ 有什么作用呢?

当我们需要交换两个数的值 但不能创建新变量(第三个变量)时  ^ 就能发挥作用了 

而上述代码是如何完成交换的呢 这就是 ^ 的作用了

首先我们知道 异或: 相同为0,相异为1

a^a=0    0^a=a

3^3=0   3^3^5=5   

而3^5^3的值依旧是5  既 异或是支持交换律的

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int main()
{int a = 10;int b = 20;printf("a=%d\n", a);printf("b=%d\n", b);//我们使用a'来作为中间变量理解 代入既可理解 ^a = a ^ b; // a'=a^bb = a ^ b; // b=a'^b=a^b^b=aa = a ^ b; // a=a'^b=a^b^a=bprintf("交换后\n");printf("a=%d\n", a);printf("b=%d\n", b);return 0;
}

而相同为0 是不可能会进位的 这样也避免的值溢出的问题

练习 整数存储在内存中二进制中1的个数

思路: 和十进制整数求相似  12343%10可以得到个位 在/10 可以得到1234 这样依次既可以计算 同理二进制则%2  /2

输入15时 得出4  我们知道15的补码是00000000000000000000000000001111确实有4位1

但当我们输入负数时呢

因此以上代码对负数是不友好的 只能计算二进制正整数中1的个数

因此我们做以下更改

首先我们得知道  

//  100000000000000000000000000001111  -   n

//  000000000000000000000000000000001  - 1

//  000000000000000000000000000000001   -  n按位与1 既n&1

无论前面的数是什么  按位与1时 只用管最后一位是不是1 如果是1 那结果也为1   不是1那就是0 

可以看出 代码运行无误   15的进制补码中有4个1     -1的补码中有32个1

其实还有更优解 如下

int count_n(int n)
{int i = 0;int count = 0;while (n){n = n & (n - 1);count++;}return count;
}
int main()
{int n=0 ;while (scanf("%d", &n) == 1){int c = count_n(n);printf("%d\n", c);}return 0;
}

代码解释:

例如15的二进制是1111    14是1110

1111&1110=1110       既n=n&(n-1) 继续往复

1110&1101=1100

1100&1011=1000

1000&0111=0000

一共执行了4次 最后为0 而正好15的二进制数里有4个1  因此可以使用while循环和n=n&(n-1)来实现目的

练习 二进制位置置0或者置1

例如把改变13的第5位修改为1  然后在修改为0

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int main()
{int a = 13;//13=00001101// | 00010000//   00011101//这样 就能将第五位置为1  而00010000怎么来呢  我们可以使用左移运算符//1左移4位 这样就能得到00010000a |= (1 << 5-1);printf("a的值为%d", a);return 0;
}

上述操作就可以将第五位修改为1  使用到了 按位或 和左移运算符 经此启发 

我们将第五位修改为0可以通过下面代码实现

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int main()
{int a = 13;//13=00001101// | 00010000  按位与 |//   00011101// & 11101111  按位或 &//   00001101//这样通过 & 就可以实现第五位转换为0 //而11101111怎么获得呢? 可以发现它就是00010000的反码//既 ~(1<<5-1)a |= (1 << 5-1);printf("a的值为%d\n", a);a &= ~(1 << 5 - 1);printf("a的值为%d\n", a);return 0;
}

可以看出运行结果的确正确 如下:

其中 我们运用到了 按位或 按位取反 左移运算符等

6.  单目操作符

单目操作符我们已经在数据类型和变量中学过了  如下

其中 只有 * 和 & 没有具体讲解 这一块将在之后的指针章节中具体介绍

7. 逗号表达式

首先逗号表达式是什么呢?

逗号表达式就是用逗号隔开的多个表达式

特点: 从左往右依次执行 整个表达式的结果是最后一个表达式的结果

整体结果是expN

下面具体举例 

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int main()
{int a = 13;int b = 14;int c = 15;int d = (a > b, a = b + 10, c, b = a + 1);printf("%d", d);return 0;
}

代码运行结果是什么呢?   如下

结果是25  因为从左往右计算 a=b+10=24  b=a+1=24+1=25

逗号表达式最后一个式子的值为结果 因此d的值是25

而逗号表达式的优势在于哪里呢?  那就是可以精简代码  如下代码

a = get_val();
count_val(a);
while (a >0)
{//处理过程// ~~~~a = get_val();count_val(a);
}

当有如上代码时 我们可以用逗号表达式来精简 如下

while (a = get_val(), count_val(a) ,a >0)
{//处理过程// ~~~
}

8. 下标访问[  ]   函数调用( )

8.1 [ ] 下标引用操作符

操作数:一个数组名+一个索引值(下标)

int arr[10];
arr[8]=8;
[ ]的两个操作数是arr和8

8.2 () 函数调用操作符

int main()
{
printf("hello");// ()是函数调用操作符
int a=add(3,5); // ()是函数调用操作符
// () 的操作数是函数名和参数  至少有一个函数名 可以不要参数
return 0;
}

9. 结构成员的访问操作符

9.1 结构体

C语言已经提供了内置类型  如 char short int long double等等  但是只有这些内置类型还是不够的  比如我想描述学生 描述一本书  这时单一的内置类型是不行的

描述一个学生需要满足 年龄 学号 身高 体重等

描述一本书需要 满足   作者 出版社 书名 定价等  C语言为了解决这个问题 增加了结构体这种自定义的类型数据  让程序员可以自己创造适合的类型

结构是一些值的集合  这些值被称为成员变量  结构的每个成员变量可以是不同类型的变量  如: 标量  数组  指针 甚至是其他结构体 

9.2 结构体的声明

使用 struct 关键字定义结构体,语法如下:

struct 结构体名 {数据类型 成员1;数据类型 成员2;// ...更多成员
};

示例:定义一个表示学生的结构体

struct Student {int id;         // 学号char name[20];  // 姓名float score;    // 成绩
};

9.3 结构体变量的声明与初始化

9.3.1 声明结构体变量

// 方式1:先定义结构体,再声明变量
struct Student stu1, stu2;// 方式2:定义结构体时直接声明变量
struct Student   {int id;char name[20];float score;} stu3, stu4;  // stu3和stu4是全局变量

9.3.2 初始化结构体变量

// 按成员顺序初始化
struct Student stu1 = {101, "Alice", 90.5};// 指定成员初始化(C99标准支持)
struct Student stu2 = {.name = "Bob", .score = 85.0f};

通过调试来具体观察初始化

9.4 访问结构体成员

使用 . 运算符 访问成员:

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<string.h>
struct Student {int id;         // 学号char name[20];  // 姓名float score;    // 成绩		score	90.5000000	float};
struct Student stu1, stu2;
int main()
{struct Student stu1 = { 101, "Alice", 90.5 };struct Student stu2 = { .name = "Bob", .score = 85.0f };stu1.id = 1001;strcpy(stu1.name, "Tom");  // 字符串需用strcpy赋值stu1.score = 92.5;printf("ID: %d, Name: %s, Score: %.1f\n",  stu1.id, stu1.name, stu1.score);return 0;
}

运行结果如下:

结构体指针与 -> 运算符

如何通过指针操作结构体,需用 -> 访问成员   稍后在指针章节在讲

10. 操作符的属性: 优先级  结合性

C语言的操作符有两个重要的属性: 优先级和结合性 这两个属性决定了表达式求值的计算顺序

10.1 优先级

优先级指的是 如果一个表达式包含多个运算符 哪个运算符应该优先执行 各种运算符的优先级是不一样的

3+4*5; //*优先级更高 因此先算4*5 后算+

10.2 结合性

如果两个算数符优先级相同 优先级没有办法确定先算哪一个时 这时候就看结合性了 根据运算符是左结合 还是右结合 决定执行顺序 大部分运算符是左结合(既从左往右计算)  少数运算符是从右往左计算 比如说赋值运算符( = )

比如 6/3*2  假如先算3*2那结果就是1  若先算6/3那结果就是4

因此结合性的作用在无法确定优先性时就会体现出来  上面的例子就是左结合起了作用

11. 表达式求值

11.1 整型提升

基本概念:

整型提升是指当表达式中使用比int小的整型类型(如charshort等)时,这些值会被自动提升为int类型(如果int能够表示原类型的所有值)或unsigned int类型(如果不能)。

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

总结:

意义说明
提高运算效率CPU 更擅长处理 int 大小的数据,减少额外指令开销
防止数据溢出提升到 int 后,中间结果不易溢出
统一运算规则避免混合类型运算的歧义,保证可移植性
符合语言标准C/C++ 标准要求整型提升,确保行为一致
简化编译器实现减少特殊情况处理,优化代码生成

整型提升虽然是一个隐式的过程,但它在底层优化、防止错误、保证可移植性等方面起着重要作用。理解整型提升有助于编写更健壮、高效的代码,并避免一些微妙的类型相关错误。

如何进行整型提升呢?

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

2.无符号整数提升 高位补0

以下代码运算结果是多少呢?

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int main() {char a = 20;char b = 120;char c = a + b;printf("%d\n", c);return 0;
}

结果如下:

为什么 得出c的值是-116 而不是140呢 原因如下 让我们利用整型提升的知识来讲解

注:char类型占8位  short类型占16位 int类型占32位

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
int main() {char a = 20;//截断后存储到a中//00010100 - a //00000000000000000000000000010100    a整型提升char b = 120;//01111000 - b //00000000000000000000000001111000    b整型提升char c = a + b;// 00010100- a// 00000000000000000000000000010100// 01111000- b// 00000000000000000000000001111000// // 00000000000000000000000000010100// 00000000000000000000000001111000// 00000000000000000000000010001100 - 整型提升后相加// 10001100 - C 通过整型提升得到补码printf("%d\n", c);//%d-以10进制的形式,打印一个有符号的整型(int)//11111111111111111111111110001100 - 补码//10000000000000000000000001110011//10000000000000000000000001110100 - 原码//不难看出值是-116 return 0;
}

11.2 算数转换

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

如下:

100和55.5f是无法进行直接加法运算的 因此需要将100转换为flort类型 

结语:

以上就是关于操作符的详细介绍 创作不易 若对您有帮助 请点点关注点点赞  鄙人不胜感激.❀

http://www.xdnf.cn/news/190621.html

相关文章:

  • python 线程池顺序执行
  • 二叉树的所有路径(回溯算法基础)
  • 深度学习---Pytorch概览
  • 3D模型文件格式之《DAE格式介绍》
  • [LeetCode 438/567] 找到字符串中所有字母异位词/字符串的排列(滑动窗口)
  • tsconfig.json的配置项介绍
  • 云原生周刊:Kubernetes v1.33 正式发布
  • 用JavaScript构建3D程序
  • 2025系统架构师---论微服务架构及其应用
  • Linux中的系统延时任务和定时任务与时间同步服务和构建时间同步服务器
  • 老电脑优化全知道(包括软件和硬件优化)
  • 【爬虫】一文掌握 adb 的各种指令(adb备忘清单)
  • 【Mybatis】Mybatis基础
  • 集合框架篇-java集合家族汇总
  • 【3D基础】深入解析OBJ与MTL文件格式:Blender导出模型示例及3D开发应用
  • 【KWDB 创作者计划】_企业数据管理的利刃:技术剖析与应用实践
  • CMake:设置编译C++的版本
  • 【北京】昌平区某附小v3700存储双控故障维修案例
  • 分布式链路追踪理论
  • 【Axure视频教程】手电筒效果
  • 【题解-Acwing】867. 分解质因数
  • 【蒸馏(5)】DistillBEV代码分析
  • FPGA-DDS信号发生器
  • 3D架构图软件 iCraft Editor 正式发布 @icraft/player-react 前端组件, 轻松嵌入3D架构图到您的项目
  • 数据可视化
  • 【C++教程】三目运算符
  • Day8 鼠标控制与32位模式切换
  • AIGC重构元宇宙:从内容生成到沉浸式体验的技术革命
  • 临床试验概述:从定义到实践的关键要素
  • R 语言科研绘图第 43 期 --- 桑基图-冲击