目录
一、算术操作符
+ - * / %
- 除了"%"(取模)操作符之外,其他的几个操作符可以作用于整数和浮点数。
- 对于"/"操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除 法。
- "%"操作符的两个操作数必须为整数。返回的是整除之后的余数。
如下:
int main() {
int a = 100;
int b = 12;
//除了"%"(取模)操作符之外,其他的几个操作符可以作用于整数和浮点数。
int mod = a % b;
double div1 = a / b;
double div2 = a / 12.0;
//int mod = a % c; err
printf("%d\n", mod); // 结果: 4
//对于"/"操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除 法。
printf("%lf\n", div2); // 结果 : 8.333333
printf("%lf\n", div1);// 结果 : 8.000000
return 0;
}
二、移位操作符
<< 左移操作符
>> 右移操作符
注意: 位移操作符并不会对变量本身进行改变
左移操作符位移规则:在内存中(补码) 左边抛弃,右边补0
在上图起到了乘2的效果。
int main() {
int a = 5;
int b = a << 1;
printf("%d\n", b); // 结果为:10
printf("%d\n", a);// 结果为:5
a = a << 1;
printf("%d\n", a); //结果为 : 10
return 0;
}
右移操作符 移位规则:
首先右移运算分两种:
逻辑移位: 左边用0填充,右边丢弃
算术移位: 左边用原该值的符号位填充,右边丢弃
假设我们的变量为 : int a = -1;
此时我们变量在内存中的存储是:11111111 11111111 11111111 11111111(详细可看原、反、补码片段)
注意 :不论是左移还是右移,移动位数不能是负数! 位数为负数是标准未定义的!!
如果这样些的后果是出现bug,当然一个程序猿想写bug谁都拦不住...
三、位操作符
& //按二进制位与
| //按二进制位或
^ //按二进制位异或
注:他们的操作数必须是整数。
3.1、&(按二进制位与)
在内存二进制中进行同位对比,同位同为1,则返回 1
例如此时有两个整形变量 : int a = 5; int b = 6;
他们在内存中存储的是补码 :a : 00000000 00000000 00000000 00000101
b : 00000000 00000000 00000000 00000110
如果此时有 int c = a & b;
为了直观展示 我们现在只关注内存后8位
😎:在理解上可以与逻辑与相结合:全为真才是真,即全为1才是1。
3.2、|(按二进制位或)
在内存二进制中进行同位对比,同位有一位为1,则返回 1
我们还是 举上面栗子
例如此时有两个整形变量 : int a = 5; int b = 6;
他们在内存中存储的是补码 :a : 00000000 00000000 00000000 00000101
b : 00000000 00000000 00000000 00000110
如果此时有 int c = a | b;
为了直观展示 我们现在只关注内存后8位
😎:在理解上可以与逻辑或相结合:全为真才是真,即全为1才是1。
3.2、^(按二进制位异或)
在内存二进制中进行同位对比,同位相同为0,相异为1
我们还是 举上上面栗子
例如此时有两个整形变量 : int a = 5; int b = 6;
他们在内存中存储的是补码 :a : 00000000 00000000 00000000 00000101
b : 00000000 00000000 00000000 00000110
如果此时有 int c = a ^ b;
为了直观展示 我们现在只关注内存后8位
这里有个小知识:
- 变量本身异或本身结果为0
- 0与任何数异或为任何数本身
一道经典的面试题:
不能创建临时变量(第三个变量),实现两个数的交换。
我们就使用异或的特性来进行...🫳🫳
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
a = a^b;
b = a^b; //这里的表达式是 b = a ^ b ^ b
a = a^b;//这里的表达式是 b = a ^ a ^ b
printf("a = %d b = %d\n", a, b);
return 0;
}c
通过我们上面的小知识可以知道 变量本身异或本身结果为0,那么在b变量异或是时候其实就是a与0在异或,再结合我们上面的小知识 0与任何数异或为任何数本身 那么此时我们就完成了把变量a赋值给变量b了。(包拿捏的😎)
四、赋值操作符
= += -= *= /= %= >>= <<= &= |= ^=
这个不多说,百闻不如一见,百见不如一干
五、单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置-
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
5.1、"!" 逻辑操作符
在变量前面加"!",把变量变为假(在C语言中数字0表示假,非0表示真即使是负数),如果变量本身为"0"变为真(非0)。
需注意的是"!"操作符并不会的变量本身进行修改。
举个栗子
int a = 100;
printf("%d\n", !a);//运行结果为: 0
printf("%d\n", a);//运行结果为: 100
int b = 0;
printf("%d\n", !b);//运行结果为: 1
printf("%d\n", b);//运行结果为: 0
5.2sizeof() 操作数的类型长度(以字节为单位)
sizeof() 操作符 : 用于计算括号内的 变量类型 占用空间的大小 返回的是无类型整数,并且在括号中sizeof只关注最后的类型是什么,不会进行运算
一定要记住:计算的是: 变量类型 占用空间的大小(重要的事情说三遍)
变量类型 占用空间的大小(重要的事情说三遍)
变量类型 占用空间的大小(重要的事情说三遍)
举个栗子:
int main() {
int a = 5;
long long b = 0;
int arr[5] = { 0 };
printf("%u\n", sizeof(a) ); //结果为 :4
printf("%u\n", sizeof(int));//结果为 :4
printf("%u\n", sizeof(b = a + 100));//结果为 :8
printf("%u\n", sizeof(long long));//结果为 :8
printf("%d\n", b);//结果为 :0
printf("%u\n", sizeof(arr));//结果为 :20
printf("%u\n", sizeof(int [5]));//结果为 :20
return 0;
}
无论你在括号内传递的是什么、写的是什么、无论是否有具体变量和有具体的值,sizeof只会对计算最终的变量占用空间的大小。
计算数组若看不懂可以看不才写的关于数组的博客:【C语言】数组
在深入学习指针和数组的内存使用详情前,我们需要深入了解一下sizeof操作符的使用
5.2.1 sizeof操作符是计算括号内最终结果的类型大小
int main() {
int a = 0;
int sz1 = sizeof(a);
int sz2 = sizeof(int);
printf("sz1 = %d\n", sz1);
printf("sz2 = %d\n", sz2);
return 0;
}
运行结果:
- sz1计算的是a变量 占用空间的大小,得出的结果是4
- sz2计算的是 int类型 占用空间的大小,得出的结果也是4
由此可以证明 sizeof 计算的就是 类型大小
5.2.2 sizeof操作符括号内如果是复杂表达式,sizeof计算的就是表达式最终的变量类型占用空间的大小
int main() {
int a = 0;
char b = 12;
short c = 0;
int sz1 = sizeof(short);
int sz2 = sizeof(c = a + b);
printf("sz1 = %d\n", sz1);
printf("sz2 = %d\n", sz2);
return 0;
}
运行结果:
- 在sz2中,我们看表达式: c = a + b。在正常计算中,我们会涉及整形提升,a + b会提升成为4个字节运算,然后赋值给c,c也需要进行整形提升,提升到4个字节来接收,但是sizeof的结果还是占用2个字节,说明在复杂表达式中,sizeof计算的就是表达式最终的变量类型占用空间的大小
在sizeof操作符内不会进行运算赋值,不管是多复杂或多简单的表达式在sizeof内只会判断其最终类型占用空间大小后,返回结果
举个栗子:
int main() {
int a = 10;
int b = 20;
int c = 0;
printf("%d\n", sizeof(c = a + b));
printf("%d\n\n", c);
printf("%d\n", sizeof(c = 123));
printf("%d\n", c);
return 0;
}
运行结果:
总结:sizeof操作符具体实现方法是使用宏实现的,所以在编译阶段sizeof处理内容时,并不知道变量a、b等的值是什么,只会找到其最终变量后判断这个变量类型占用空间多少个字节。
5.3、"~"按位取反
对一个数的二进制按位取反
把该数所有二进制中的数字,"1"变为"0","0"变为"1",包括符号项。
需注意的是"~"操作符并不会的变量本身进行修改。
例:
#include <stdio.h>
int main() {
int b = 0;
printf("%d\n", ~b);//运行结果为: -1
printf("%d\n", b);//运行结果为: 0
return 0;
}
整数a = 0
a 二进制 : 00000000 00000000 00000000 00000000 //32位(至于为什么是32为,先记住后面讲)
~a 二进制:11111111 11111111 11111111 11111111 //32位数字
第一个数字也就是标了橙色的数字就为符号位,"1"为负、 "0"为正
5.3.1 原、反、补码
一个整数的二进制表示有3种: 原码、反码、补码
整数在内存中存储的是补码
上面~a为啥二进制是:11111111 11111111 11111111 11111111 却得到数字-1呢
关于-1的原码、反码、补码的计算:
10000000 00000000 00000000 00000001(原码)
11111111 11111111 11111111 11111110 (反码)
11111111 11111111 11111111 11111111 (补码)
原码转反码:符号位不变,其他按位取反
反码转补码:反码+1
换过来也一样的
补码转反码:补码-1
反码转原码:符号位不变,其他按位取反
注意:正数也有原码、反码、补码,但是正数的原码、反码、补码都相同
5.4、"++"前置加加/后置加加
自增操作符,无论是使用前置加加还是后置加加,都会对变量本身进行加1。只要了自增操作符,就会进行自增。
前置加加,先加后用
后置加加,先用后加
例:
int a = 10;
int b = ++a;//前置加加,先加后用,即 b = 11
int c = 8;
int d = c++;//后置++,先用后加,即 d = 8
prinf("a = %d , b = %d , c = %d , d = %d", a, b, c, d);
//结果输出: a = 11 , b = 11, c = 9 , d = 8
printf("c = %d", ++c);
//结果输出:c = 10;
5.5、"--"前置减减/后置减减
自减操作符,无论是使用前置减减还是后置减减,都会对变量本身进行减1。只要了自减操作符,就会进行自减。
与"++"同理:前置--,先减后用
后置--,先用后减
例:
int a = 10;
int b = --a;//前置减减,先减后用,即b = 9
int c = 10;
int d = c--//后置--,先用后减,即 d = 10
prinf("a = %d , b = %d , c = %d , d = %d", a, b, c, d);
//结果输出: a = 9 , b = 9, c = 9 , d = 10
printf("c = %d", --c);
//结果输出:c = 8;
5.6 关系运算符
> >= < <= != ==
这个也不多说,纸上得来终觉浅、绝知此事要躬行
5.7逻辑操作符
&& 逻辑与
|| 逻辑或
5.7.1 "||" 逻辑或
逻辑或 : 作用就是比较两个表达式是否满足需求,只要有一个表达式为真即为真 。并且在逻辑或中的判断语句中,只要有一个为真就结束语句判断直接返回真,不管后面有没有运算。
举个栗子
int main() {
int a = 10;
int b = 10;
if (a - b != 0 || a - b == 0) {
prinyf("逻辑或\n");
}
return 0;
}
在if语句中 第一个表达式为假 第二个表达式为真 但是通过逻辑或的作用 整个结果为真 进入if语句。
再举个栗子
int main() {
int a = 10;
int b = 10;
int c = 0;
int d = 0;
if (( c = a + b )|| (d = a + b) ) {
printf("c = %d d = %d\n", c, d);//结果为 : c = 20 d = 0
}
return 0;
}
在if表达式中:第一个表达式为真,那么逻辑或就结束后面语句的运行,直接返回结果为真进入if语句。我们在打印后的结果中也可以看出 , 变量c是进行了赋值 ,变量d是维持了原状。
5.7.2 "&&" 逻辑与
逻辑与 : 作用就是比较两个表达式是否满足需求,要求两个表达式为,才为真 。并且在逻辑与中的判断语句中,只要有一个为假就结束语句判断直接返回假,不管后面有没有运算。
举个栗子
int main() {
int a = 10;
int b = 10;
if (a - b != 0 && a - b == 0) {
printf("hehe\n");
}
else {
printf("逻辑与\n");
}
return 0;
}
结果打印:逻辑与。 在if语句中 第一个表达式为假 第二个表达式为真 通过逻辑与判断 结果为假 进入else语句。
再举个栗子
int main() {
int a = 10;
int b = 10;
int c = 20;
int d = 20;
if ((c = a - b) && (d = a - b)) {
printf("xixi\n");
}
else {
printf("c = %d d = %d\n", c, d);//结果为 : c = 0 d = 20
}
return 0;
}
在if表达式中:第一个表达式为假,那么逻辑与就结束后面语句的运行,直接返回结果为假进入else语句。我们在打印后的结果中也可以看出 , 变量c是进行了赋值 ,变量d是维持了原状。
5.8、条件操作符
exp1 ? exp2 : exp3
条件操作符 可以翻译中文助于理解: 条件1是为真吗?真返回条件2:否则返回条件3
是一个可以替代小型 if-else
if (a > 5)
b = 3;
else
b = -3;
上面的if - else就可以替换为 : a > 5 ? b = 3 : b = -3;
5.9逗号表达式
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。 逗号表达式,从左向右依次执行。整个表达式的结果是最 后一个表达式的结果。
逗号表达式是一些题目的坑点,大家要仔细观察
举个栗子:问c是多少
int a = 1;
int b = 2;
int c = (a>b, a=b+10, a, b=a+1);//逗号表达式
我们来分析一下,我们知道逗号表达式返回的是最后一个值的结果。那么 a > b没有改变变量,进入第二个表达式,这时候a被改变为了12,进入到最后一个表达式 b 被改变为了13,那么 c 的赋值表达式就变为了 int c = b; 那么c最终被赋值为: 13。
5.10 下标引用、函数调用和结构成员
1.[ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
其实[]下标引用操作符与 *(?+?)是一样的,如上面的查找数组数组最后一位具体值,我们用指针可以访问是:*(arr + 9)。这也是可以查找到数组最后一位,那么与我们 arr[9] 的作用是一样的,其实在底层 arr[9] 最终也是转换成 *(arr + 9)。所以无论是指针还是数组我们想要访问对应的值都可以使用下标引用操作符
举个栗子
int main() {
char* str = "abcd";
printf("%c\n", str[2]); // 结果为: c
printf("%c\n", *(str + 2)); // 结果为: c
return 0;
}
2.( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数 的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1();
//实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
3.访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
六、(类型)强制类型转换
用途与定义:当要赋的值与定义的类型不一样时,使用强制类型转换,把要赋的值强制转换为与强转类型内的类型相同
需要注意的是强制类型转换可能会导致整形提升 或者 截断。在整型提升后可能会因类型区间进行裁切。以至于强制类型转换后得不到我们想要的内容和效果。
例如:
int a = 3.14;//把浮点型赋值给整形会有警告
//整形 浮点型
int b = (int)3.14;//把浮点型强制转换为整形后再赋给整形
以下代码会进行截断
#include <stdio.h>
int main() {
long long a = 0xffffffffff;
int c = (int)a;
printf("%d\n", c);
// 打印结果为: -1
return 0;
}
long long 类型可以存放的整形空间远比 int 类型多,但是把long long 类型变量 a 强制转换成int类型 在内存中存储的内容(补码)
a:00000000 00000000 00000000 11111111 11111111 11111111 11111111 11111111
int 类型变量 b 原有空间为:00000000 00000000 00000000 00000000
强行把long long类型转成int类型后 a变量在内存中的过程
补码:00000000 00000000 00000000 11111111 (截断) 11111111 11111111 11111111 11111111
(int)a : 11111111 11111111 11111111 11111111
即:a变量被赋值为 :11111111 11111111 11111111 11111111(补码)
11111111 11111111 11111111 11111110(反码)
10000000 00000000 00000000 00000001(源码)
这里涉及算数转换详细可以看不才文章(对整形提升感兴趣也可以跳转):【C语言】表达式求值
七、操作符的属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
以上就是本章所有内容。若有勘误请私信不才。万分感激💖💖 若有帮助不要吝啬点赞哟~~💖💖
ps:表情包来自网络,侵删🌹