目录
[1. 操作符的分类](#1. 操作符的分类)
[2. 二进制和进制转换](#2. 二进制和进制转换)
[3. 原码 反码 补码](#3. 原码 反码 补码)
[4. 移位操作符](#4. 移位操作符)
[4.1 左移操作符 >>](#4.1 左移操作符 >>)
[4.2 右移操作符 >>](#4.2 右移操作符 >>)
[5. 位操作符](#5. 位操作符)
[5.1 按位与 &](#5.1 按位与 &)
[5.2 按位或 |](#5.2 按位或 |)
[5.3 按位异或 ^](#5.3 按位异或 ^)
[5.4 按位取反 ~](#5.4 按位取反 ~)
[练习 整数存储在内存中二进制中1的个数](#练习 整数存储在内存中二进制中1的个数)
[练习 二进制位置置0或者置1](#练习 二进制位置置0或者置1)
[6. 单目操作符](#6. 单目操作符)
[7. 逗号表达式](#7. 逗号表达式)
[8. 下标访问[ ] 函数调用( )](#8. 下标访问[ ] 函数调用( ))
[8.1 [ ] 下标引用操作符](#8.1 [ ] 下标引用操作符)
[8.2 () 函数调用操作符](#8.2 () 函数调用操作符)
[9. 结构成员的访问操作符](#9. 结构成员的访问操作符)
[9.1 结构体](#9.1 结构体)
[9.2 结构体的声明](#9.2 结构体的声明)
[9.3 结构体变量的声明与初始化](#9.3 结构体变量的声明与初始化)
[9.3.1 声明结构体变量](#9.3.1 声明结构体变量)
[9.3.2 初始化结构体变量](#9.3.2 初始化结构体变量)
[9.4 访问结构体成员](#9.4 访问结构体成员)
[10. 操作符的属性: 优先级 结合性](#10. 操作符的属性: 优先级 结合性)
[10.1 优先级](#10.1 优先级)
[10.2 结合性](#10.2 结合性)
[11. 表达式求值](#11. 表达式求值)
[11.1 整型提升](#11.1 整型提升)
[11.2 算数转换](#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进制表示相同 没有符号位 每一位都是数值位
对于整型来说:数据存放在内存中的其实是补码 那为什么呢?
- 加减统一:减法变加法,硬件只需加法器,省成本。
- 零唯一:消灭-0,多存一个数(如-128),不浪费。
- 运算快 :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 既 异或是支持交换律的
cpp#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^b b = a ^ b; // b=a'^b=a^b^b=a a = a ^ b; // a=a'^b=a^b^a=b printf("交换后\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
其实还有更优解 如下
cpp
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
cpp
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main()
{
int a = 13;
//13=00001101
// | 00010000
// 00011101
//这样 就能将第五位置为1 而00010000怎么来呢 我们可以使用左移运算符
//1左移4位 这样就能得到00010000
a |= (1 << 5-1);
printf("a的值为%d", a);
return 0;
}
上述操作就可以将第五位修改为1 使用到了 按位或 和左移运算符 经此启发
我们将第五位修改为0可以通过下面代码实现
cpp
#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
下面具体举例
cpp
#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
而逗号表达式的优势在于哪里呢? 那就是可以精简代码 如下代码
cpp
a = get_val();
count_val(a);
while (a >0)
{
//处理过程
// ~~~~
a = get_val();
count_val(a);
}
当有如上代码时 我们可以用逗号表达式来精简 如下
cpp
while (a = get_val(), count_val(a) ,a >0)
{
//处理过程
// ~~~
}
8. 下标访问[ ] 函数调用( )
8.1 [ ] 下标引用操作符
操作数:一个数组名+一个索引值(下标)
cpp
int arr[10];
arr[8]=8;
[ ]的两个操作数是arr和8
8.2 () 函数调用操作符
cpp
int main()
{
printf("hello");// ()是函数调用操作符
int a=add(3,5); // ()是函数调用操作符
// () 的操作数是函数名和参数 至少有一个函数名 可以不要参数
return 0;
}
9. 结构成员的访问操作符
9.1 结构体
C语言已经提供了内置类型 如 char short int long double等等 但是只有这些内置类型还是不够的 比如我想描述学生 描述一本书 这时单一的内置类型是不行的
描述一个学生需要满足 年龄 学号 身高 体重等
描述一本书需要 满足 作者 出版社 书名 定价等 C语言为了解决这个问题 增加了结构体这种自定义的类型数据 让程序员可以自己创造适合的类型
结构是一些值的集合 这些值被称为成员变量 结构的每个成员变量可以是不同类型的变量 如: 标量 数组 指针 甚至是其他结构体
9.2 结构体的声明
使用**struct
** 关键字定义结构体,语法如下:
cpp
struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
// ...更多成员
};
示例:定义一个表示学生的结构体
cpp
struct Student {
int id; // 学号
char name[20]; // 姓名
float score; // 成绩
};
9.3 结构体变量的声明与初始化
9.3.1 声明结构体变量
cpp
// 方式1:先定义结构体,再声明变量
struct Student stu1, stu2;
// 方式2:定义结构体时直接声明变量
struct Student {
int id;
char name[20];
float score;
} stu3, stu4; // stu3和stu4是全局变量
9.3.2 初始化结构体变量
cpp
// 按成员顺序初始化
struct Student stu1 = {101, "Alice", 90.5};
// 指定成员初始化(C99标准支持)
struct Student stu2 = {.name = "Bob", .score = 85.0f};
通过调试来具体观察初始化

9.4 访问结构体成员
使用 .
运算符 访问成员:
cpp
#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
小的整型类型(如char
、short
等)时,这些值会被自动提升为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
以下代码运算结果是多少呢?
cpp
#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位
cpp
#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类型
结语:
以上就是关于操作符的详细介绍 创作不易 若对您有帮助 请点点关注点点赞 鄙人不胜感激.❀