只要你愿意 开始总比放弃好。 Roman.
愿我们都有自己的目标并正在为其不懈努力。
---------------------------------------------------------------------------------------
一、操作符分类
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
- 下标引用、函数调用和结构成员
***************************************************
二、算术操作符
1.包括:
+ - * / %
2.对于 % 操作符而言,两边都必须是整数
3.除了 % 操作符以外, 其他操作符均可作用于整数和浮点数
***************************************************
三、移位操作符
一) <<左移操作符
1.规则: 左边抛弃,右边补0
2.其实质:是将二进制补码拿起来向左移动,框不变
3.原来的数不会改变
二) >>右移操作符
1.两种右移:
- 算术右移:右边丢弃,左边补原位符号
- 逻辑右移:右边丢弃,左边补0
2.到底是 算术右移 还是 逻辑右移 取决于编译器
3.注:VS2019是算术右移
三)注意/补充
1. 移位操作符的操作数只能是整数
2. 警告:对于移位运算符,不要移动负数位,这个是标准未定义的
3. 注意:移位操作符操作的是二进制位!
1)补充:
整数的二进制表示有三种形式: 原码、反码、补码
正整数的原码、反码、补码都是相同的
负整数的原码、反码、补码是要通过计算获得的
2)如果整数是有符号数,最高位是符号位(0表示正数 1表示负数)
3)负整数:
原码:最高位为1
反码:原码符号位不变!! 其他位按位取反
补码:反码+1
4)整数在内存中存储是以二进制补码形式
5)补码 ---> 原码:
① (补码 - 1) 得到 (反码)--->(反码符号位不变,其他位按位取反) 得到 (原码)
② (补码取反)-- 符号位不变,其他位按位取反 ---> 再 (+ 1)
6)打印的是原码,故需要进行转换
7)注:整型数有4字节,即:32比特位
***************************************************
四、位操作符
1. 包括有:
按位与& 按位或| 按位异或^
2. 注:位操作符的操作数必须是整数
3. 规则:按二进制位进行,又内存中是以二进制补码进行存储的,所以以二进制补码进行运算
4. 区分: 按位与& 是双目操作符, 取地址& 是单目操作符
5. 按位与&: 操作数均为1 才是1
按位或| : 操作数只要存在1 就是1
按位异或^ : 对应的二进制位,相同为0,相异为1
(此处: 1 表示真, 0 表示假)
6. 实例:不创建临时变量,实现两个数交换
//不创建临时变量,实现两个数交换
#include<stdio.h>
void Swap1(int* a, int* b)
{
//方法一:使用加法:需要使用地址改变内容
//该方法会存在一个问题:如果加数过大,相加后会存在溢出现象
*a = *a + *b;
*b = *a - *b;
*a = *a - *b;
}
void Swap2(int a, int b)
{
//方法二:异或^实现:相同为0 相异为1
//根据性质:a^a=0 a^0=a
a = a ^ b;
b = a ^ b;
a = a ^ b;
}
int main()
{
// 定两个数
int a = 0;
int b = 0;
printf("请任意输入两个数:\n");
scanf("%d %d", &a, &b);
printf("交换前:a=%d b=%d\n", a, b);
Swap1(&a, &b);
printf("交换后(加法传地址):a=%d b=%d\n", a, b);
Swap2(a, b);
printf("交换后(异或传原数):a=%d b=%d\n", a, b);
return 0;
}
7. 练习:求一个整数存储在内存中的二进制中 1 的个数
//求一个整数存储在内存中的二进制中 1 的个数
#include<stdio.h>
int Num1(int a)
{
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)
{
//错误!!!
//与0异或(针对二进制):每一位都进行异或--32次 左移操作-针对二进制
/*if (1 == (a ^ 0))
{
count++;
}
a <<= 1;*/
//正确
//与1(只有最右边是1)按位与(针对二进制):每一位都进行异或--32次 右移操作-针对二进制
if (1 == ((a >> i) & 1))
{
count++;
}
}
return count;
}
int main()
{
int a = 0;
printf("请任意输入一个整数:\n");
scanf("%d", &a);
int count = Num1(a);
printf("%d的二进制中1的个数为:\n", a);
printf("%d\n", count);
return 0;
}
***************************************************
五、赋值操作符
1. 复合赋值符:
+= -= *= \= %= >>= <<= &= |= ^=
2.赋值操作符= 可以连续使用, 但是代码可读性较差
其顺序为: 从左至右
***************************************************
六、单目操作符
1. 包括有:
!逻辑反操作符(一般针对真假)
- 负值 + 正值 &取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置 ++ 前置、后置
* 间接访问操作符(解引用操作符 一般与取地址操作符相关时用)
(类型)强制类型转换
2. 补充:
C语言中 C99之前没有表示真假的类型, C99中引入了布尔类型
#include<stdbool.h>
其中库函数定义如下:
#define bool _Bool
#define false 0
#define true 1
所以 可以在引入头文件后使用如下:
_Bool flag1 = false;
bool flag2 = true;
(_Bool 等价于 bool)
3. 注:
如有: int arr[10] = {0} ;
& arr ; //取出的是数组地址,数组地址应该放到 数组指针 中去
(注意:此处要记忆 数组名不表示首元素地址的两种情况 -- sizeof(数组名) &数组名 )
4. sizeof 是操作符(运算符),不是函数
- sizeof 变量 , 变量的括号可以省略
sizeof(类型),类型的括号不可以省略
- sizeof 是计算类型创建的变量所占内存的大小, 单位是 字节
- 举例:
int a = 10;
short b = 0;
printf (" %d\n" , sizeof (b = a + b)); //此时会发生截断 //输出2(是b 的类型)
printf (" %d\n" , b); //输出0
说明: sizeof(表达式)中表达式不参与运算
sizeof 是在编译期间处理的 (test.c --> 编译 --> 链接 --> test.exe)
5. ~ 按位取反:所有位(包括符号位!!)都取反, 但是针对的是补码 , 打印的是 原码
6. 前置 ++ -- : 先++ -- ,后使用
后置 ++ -- : 先使用,后++ --
如:
int a = 2;
int b = a++; // 后置:先使用,后++ : b = a ; a =a ++;
int c = ++a; // 前置: 先++, 后使用: a = a++; b = a;
***************************************************
七、关系操作符
1. 包括有:
> >= < <= != ==
2. 注意:
- == 不能比较两字符串的大小, 比较的是首元素地址
- strcmp()专门用来比较字符串的大小: 对应位置上字符的大小,不是比较长度! 逐位比较
3. 区分: = 赋值 == 等于
***************************************************
八、逻辑操作符
1. 包括有:
逻辑与&& 逻辑或||
2. 注意:
- 逻辑操作符 只关心真假:非0即真 0即假
- 逻辑与&& : 两个均为真才真
- 逻辑或|| : 只要一个真则真
- 逻辑操作符适用于: 均满足 or 只满足一个条件即可
- 逻辑与&& :只要左边为0 右边就不进行计算
- 逻辑或|| : 只要左边为真 右边就不进行计算
3. 补充:
不是有字符串就创建存储空间
如:
char arr [ ] = " abcdef " ; // 没有创建存储空间
char* p = " abcdef " ; // 创建了存储空间 指针指向该字符串
***************************************************
九、条件操作符
exp1 ? exp2 : exp3 ;
含义: 表达式1 是否满足? 满足则执行表达式2, 否则执行表达式3
***************************************************
十、逗号表达式
1. 形式:
exp1,exp2,exp3,... ,expN
即:用逗号表达式隔开的多个表达式
2. 注意:
逗号表达式 从左至右 执行,整个表达式的结果就是最后一个表达式的结果
(注意:逗号表达式前面表达式会影响后面表达式结果)
***************************************************
十一、下标引用、函数调用和结构成员
一)下标引用操作符[ ]
1. 用于数组:
如: arr[4] = 5;
其操作数:arr (数组名)、 4(下标,索引值)
2. 注:
arr[4] == *(arr+4) == *(4+arr) == 4[arr]
二)函数调用操作符( )
1. 函数调用操作符( ): 至少有一个操作数(即:函数名--在不需要传参的情况下)
2. 如:
int ret = Add(3,5);
// 函数调用操作符,其操作数是: Add(函数名)、 3、5(传递的值)
三)访问一个结构的成员
1. 类型:
- . 结构体 . 成员名
- -> 结构体指针 -> 成员名
2. 补充:
- 结构体定义 如下:
struct Stu
{
char name [20];
int age;
float score;
}
修改数组内容:
如:
struct Stu s = { "张三" , 20 ,90.5f }
修改: strcpy (s.name, "张三丰" }; // 需要使用头文件 string.h
*(s.name)= "张三丰" ; // 错误 数组名表示首元素地址,解引用后只是一个字符大小,会溢出
输入:scanf("%s", s.name) ; // name 是数组名,表示首元素地址,所以不用再取地址
***************************************************
十二、表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定的
同样,有些表达式的操作数在求值过程中可能需要转换为其他类型
一)隐式类型转换
1. C的整型算术运算总是至少以缺省整型类型的精度来进行的
2. 为了获得这个精度, 表达式中的 字符和短整型操作数 在使用之前被转换为普通整型,这种转换称为 整型提升
3. 整型提升的意义:
表达式的整型运算要在CPU相应运算器内执行, CPU 内整型运算器(ALU)的操作数的字节长度一般就是 int 的字节长度,同时也是 CPU 的通用寄存器长度。
因此:即使两个 char 类型的相加, 在 CPU 执行时实际上也要先转换为 CPU 内整型操作数的标准长度。
通用 CPU 是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。
所以: 表达式中各种长度可能小于 int 长度的整型值,都必须先转换为 int 或 unsigned int , 然后才能送入 CPU 去执行运算。
4. 如何进行整型提升?
整型提升是按照变量的数据类型的符号来提升的。
- 正数、负数的整型提升:
如:
char c ;
变量 c 的二进制位(补码)中只有 8 个比特位
又 char 为有符号的 char
所以,整型提升(提升到32位)的时候,高位补充符号位(即:8个比特位的最高位)
(正数0 负数1)
- 无符号的整型提升:高位补0
5. 注意:
- char 型 也是整型
- char 到底是 signed char 还是 unsigned char 取决于编译器, 不过绝大多数是 signed char
- 其余, 如 short 、int 是 signed ,即:有符号
6. 实例:
1) 整型存入字符型, 放不下就会发生截断
//截断
#include<stdio.h>
int main()
{
//整型放入字符型:截断
char c1 = 3;
// 二进制:原反补码:00000000000000000000000000000011
// char 类型只有8个比特,发生截断:00000011--补码
char c2 = 127;
// 二进制:原反补码:00000000000000000000000001111111
// char 类型只有8个比特,发生截断:01111111--补码
char c3 = c1 + c2;
//先进行整型提升: 有符号--按最高位进行提升
// c1: 00000000000000000000000000000011 (补码)
// c2: 00000000000000000000000001111111 (补码)
// c3: 00000000000000000000000010000010 (补码)
// 截断: c3-- 10000010 (补码)
printf("%d\n", c3); //整型提升
//整型提升:c3 --11111111111111111111111110000010 (补码)
// c3 反码(补码-1): 11111111111111111111111110000001
// c3 原码: 100000000000000000000000001111110
//所以:打印原码:c3-- -126
return 0;
}
2)整型提升实例1
#include<stdio.h>
int main()
{
char a = 0xb6;
//十六进制,但是 char 是8比特:a: 1011 0110
short b = 0xb600;
// 十六进制,但是 short 是16比特:b:1011 0110 0000 0000
int c = 0xb6000000;
// int 是 32比特 : 1011 0110 0000 0000 0000 0000 0000 0000
if (a == 0xb6)
//注意:此时a 是char型, 0xb6是十六进制整型
// a 进行整型提升:按符号位 11111111 11111111 11111111 1011 0110
printf("a");
if (b == 0xb600)
//注意:此时b 是short型, 0xb6是十六进制整型
// b 进行整型提升:按符号位 11111111 11111111 1011 0110 0000 0000
printf("b");
if (c == 0xb6000000)
//二者均是整型:无需整型提升,相等
//输出
printf("c");
return 0;
}
3)整型提升实例2
#include<stdio.h>
int main()
{
char c = 1;
//截断:00000001
printf("%u\n", sizeof(c));
//char型 1字节
printf("%u\n", sizeof(+c));
//正负运算后: 4字节--进行整型提升
printf("%u\n", sizeof(-c));
//正负运算后: 4字节--进行整型提升
return 0;
}
二)算术转换
1. 如果某个操作符的各个操作数属于不同类型, 那么除非其中一个操作数转换为另一个操作数类型, 否则操作就无法进行。
下面的层次体系称为寻常算术转换。
long double
double
float
unsigned long int
long int
unsigned int
int
如果某个操作数的类型在上面这个列表中排名较低, 那么首先要转换为另一个操作数类型后执行运算(由低到高换算)
2. 算术转换 用于 大于等于 int型
3. 警告: 算术转换要合理,否则会存在潜在问题
如: float 类型 (高)转换为 int 型 (低),会丢失精度
三)操作符的属性
1.复杂表达式求值有三个影响因素:
① 操作符的优先级(相邻!!的两个操作符之间)
附上 :
操作符优先级表格(从上到下优先级逐渐降低 N/A表示无结合性)
操作符 | 描述 | 用法实例 | 结果类型 | 结合性 | 是否控制求值顺序 |
( ) | 聚组 | (表达式) | 与表达式相同 | N/A | 否 |
( ) | 函数调用 | rexp(rexp,... ,rexp) | rexp | L-R | 否 |
[ ] | 下标引用 | rexp [rexp] | lexp | L-R | 否 |
.
|
访问结构成员
|
lexp.member_name
|
lexp
| L-R | 否 |
->
|
访问结构指针成员
|
rexp->member_name
|
lexp
| L-R | 否 |
++
|
后缀自增
|
lexp ++
| rexp | L-R | 否 |
-- | 后缀自减 | lexp -- | rexp | L-R | 否 |
! | 逻辑反 |
! rexp
| rexp | R-L | 否 |
~ | 按位取反 | ~rexp | rexp | R-L | 否 |
+ | 单目,表示正值 | +rexp | rexp | R-L | 否 |
- | 单目,表示负值 | -rexp | rexp | R-L | 否 |
++ | 前缀自增 | ++lexp | rexp | R-L | 否 |
-- | 前缀自减 | --lexp | rexp | R-L | 否 |
* | 间接访问 | *rexp | rexp | R-L | 否 |
& | 取地址 | &lexp | rexp | R-L | 否 |
sizeof | 取其长度,以字节表示 | sizeof rexp sizeof(类型) | rexp | R-L | 否 |
(类型) | 类型转换 | (类型)rexp | rexp | R-L | 否 |
* | 乘法 | rexp * rexp | rexp | L-R | 否 |
/ | 除法 | rexp / rexp | rexp | L-R | 否 |
%
| 整数取余 | rexp % rexp | rexp | L-R | 否 |
+ | 加法 | rexp + rexp | rexp | L-R | 否 |
- | 减法 | rexp - rexp | rexp | L-R | 否 |
<< | 左移位 | rexp << rexp | rexp | L-R | 否 |
>> | 右移位 | rexp >> rexp | rexp | L-R | 否 |
> | 大于 | rexp > rexp | rexp | L-R | 否 |
>= | 大于等于 | rexp >= rexp | rexp | L-R | 否 |
< | 小于 | rexp < rexp | rexp | L-R | 否 |
<= | 小于等于 | rexp <= rexp | rexp | L-R | 否 |
== | 等于 | rexp == rexp | rexp | L-R | 否 |
!= | 不等于 | rexp != rexp | rexp | L-R | 否 |
& | 位与 | rexp & rexp | rexp | L-R | 否 |
^ | 位异或 | rexp ^ rexp | rexp | L-R | 否 |
| | 位或 | rexp | rexp | rexp | L-R | 否 |
&& | 逻辑与 | rexp && rexp | rexp | L-R | 是 |
|| | 逻辑或 | rexp || rexp | rexp | L-R | 是 |
? : | 条件操作符 |
rexp ? rexp : rexp
| rexp | N/A | 是 |
= | 赋值 |
lexp = rexp
| rexp | R-L | 否 |
+= | 以...加 |
lexp += rexp
| rexp | R-L | 否 |
-= | 以...减 |
lexp -= rexp
| rexp | R-L | 否 |
*= | 以...乘 |
lexp *= rexp
| rexp | R-L | 否 |
/= | 以...除 |
lexp /= rexp
| rexp | R-L | 否 |
%= | 以...取模 |
lexp %= rexp
| rexp | R-L | 否 |
<<= | 以...左移 |
lexp <<= rexp
| rexp | R-L | 否 |
>>= | 以...右移 |
lexp >>= rexp
| rexp | R-L | 否 |
&= | 以...与 |
lexp &= rexp
| rexp | R-L | 否 |
^= | 以...异或 |
lexp ^= rexp
| rexp | R-L | 否 |
|= | 以...或 |
lexp |= rexp
| rexp | R-L | 否 |
, | 逗号 |
rexp
,
rexp
| rexp | L-R | 是 |
②操作符的结合性:优先级相同的情况下
③是否控制求值顺序: 逻辑与&& 逻辑或|| 三目操作符? : 逗号表达式, (控制)
2. 注:
表达式的运算顺序无法唯一确定
若无法唯一确定计算表路径,错误
如1:
// 表达式的求值部分由操作符的优先级(相邻两个操作符)决定。a * b + c * d + e * f// 注释:代码 在计算的时候,由于 * 比 + 的优先级高,只能保证, * 的计算是比 + 早,但是优先级并不能决定第三个* 比第一个 + 早执行。
如2:int fun (){static int count = 1 ; // 静态变量不销毁,记忆性return ++ count ;}int main (){int answer ;answer = fun () - fun () * fun (); //调用顺序未知printf ( "%d\n" , answer ); // 输出多少?return 0 ;}//注释:该代码同样有问题:// 静态变量不销毁,存在记忆性,而函数的调用顺序未知