目录
这节课我们详细来看操作符
一.操作符分类
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用、函数调用和结构成员
二.算数操作符
+ - * / %
我们详细来看一下除于和取余
int main()
{
操作符(运算符)
int ret = 10 / 3;
//备注:对于除法操作符来说,两边的操作数都是整数,执行的是整数除法
//如果想计算出小数,除号的两端至少有一个操作数是浮点数
double ret2 = 10.0 / 3;
printf("%d\n", ret);
printf("%.1lf\n", ret2);
int ret = 10 % 3;//取模(取余),计算的是整除之后的余数,取模操作符的两边的操作数只能是整数
printf("%d\n", ret);
return 0;
}
注意:
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。
三.移位操作符
1.二进制的存储
整数的2进制表示形式,其实有3种
原码——反码——补码
内存中存储的起始是:补码的二进制
所以在参与移位的时候,移动后都是补码
12 - 数值
2进制:1100
8进制:14
10进制:12
16进制:c
按照一个数的正负,直接写出它的二进制表示形式得到的就是原码,整型占4个字节(32bit)
负数的第一位是符号位,如果是1就是负数,是0就是正数
正数的原码、反码、补码是相同的
负数的原码、反码、补码要经过计算的
反码是原码的符号位不变,其他位按位取反,就是反码
补码是反码+1
我们分别写出10和-10的三码
int main()
{
int a = 10;
//00000000000000000000000000001010 - 原码
//00000000000000000000000000001010 - 反码
//00000000000000000000000000001010 - 补码
int b = -10;
//10000000000000000000000000001010 - 原码
//11111111111111111111111111110101 - 反码
//11111111111111111111111111110110 - 补码
return 0;
}
由补码变到原码,反过来倒着回来计算,减一取反,按照原逻辑,不求反码了,直接取反加一就是原码,看一个图
2.左移操作符
内存中存的是补码的二进制,所以在参与位移的时候,位移的是补码,打印出数值的时候必须是原码
左移:左边抛弃一个,右边补0
而操作的是位移的效果,但是不改变值
当为负数也是一样的,去位移补码,左边抛弃一个,右边补0,算出原码,得出结果打印
int main()
{
int a = 10;
//00000000000000000000000000001010 - 补码
int b = a << 1;
//00000000000000000000000000010100 - 补码
//因为是正数,所以原码补码相同直接打印
printf("%d\n", b);//20
printf("%d\n", a);//这里a还是10,就是位移操作不改变a的值
return 0;
}
int main()
{
int a = -10;
//10000000000000000000000000001010 - 原码
//11111111111111111111111111110101 - 反码
//11111111111111111111111111110110 - 补码(进行位移)
int b = a << 1;
//11111111111111111111111111101100 - b的补码(位移过的)
//10000000000000000000000000010011
//10000000000000000000000000010100 - b的原码
printf("b=%d\n", b);//-20
printf("a=%d\n", a);//这里a还是-10,就是位移操作不改变a的值
return 0;
}
当负数的时候,h2我们二进制最前面的一位是符号位,0为正,1为负
特别:我们打印的时候是打印的原码,我们内存存储的补码,所以我们求出来补码后,先去看是正数还是负数,正数补码原码相同直接打印,负数的补码化为原码再打印
3.右移操作符
1. 逻辑右移
左边用0填充,右边抛弃一个
2. 算术右移(多见)
右边的用原来的符号位上的值进行填充,右边抛弃一个
负数:1 正数:0
注意规范,位移后面跟正数,往哪就是往哪,别来负的移动
int main()
{
int a = -1;
//10000000000000000000000000000001 - 原码
//11111111111111111111111111111110 - 反码
//11111111111111111111111111111111 - 补码
int b = a >> 1;
//操作完成后,这里是补码,记得化为原码进行打印,别忘记了
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
四.位操作符
1.& 按2进制位与
对应的二进制位有0,则为0,两个同时为1,才为1
int main()
{
int a = 3;
//00000000000000000000000000000011 - 原码反码补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 -补码
//
int c = a & b;
//00000000000000000000000000000011 - a的补码
//11111111111111111111111111111011 - b的补码
//00000000000000000000000000000011 - 按位与
//这里也是补码,但是是正数,三个码一样,我们就直接打印就好啦
printf("%d\n", c); //原码打印出来就是按位与出来的值
return 0;
}
2.| 按2进制位或
对应的二进制位有1则为1,两个同时为0则为0
int main()
{
int a = 3;
//00000000000000000000000000000011 - 原码反码补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
//
int c = a | b;
//00000000000000000000000000000011 - a的补码
//11111111111111111111111111111011 - b的补码
//11111111111111111111111111111011 - 按位或(求出来的也是补码)看是正数还是负数去再计算还是直接打印
//一看第一位是符号位是负数,就需要把原码求出来,如果是正数的话,三个码相同就不用求了,直接打印
printf("%d\n", c); //打印出来需要再求原码(取反加一)就是按位或出来的值
return 0;
}
3. ^ 按2进制位异或
对应的二进制位:相同为0,相异为1
int main()
{
int a = 3;
//00000000000000000000000000000011 - 原码反码补码
int b = -5;
//10000000000000000000000000000101 - 原码
//11111111111111111111111111111010 - 反码
//11111111111111111111111111111011 - 补码
//
int c = a ^ b;
//00000000000000000000000000000011 - a的补码
//11111111111111111111111111111011 - b的补码
//11111111111111111111111111111000 - 按位异或
//这里求出来,一看第一位是负数,这里是我们的补码,我们就需要求出来原码,再进行打印他的值
printf("%d\n", c); //这里原码打印出来就是按位异或的值
return 0;
}
4.深度理解
不能创建临时变量(第三个变量),实现两个整数的交换
(1)正常创建变量的交换
int main()
{
int a = 3;
int b = 5;
int tmp = 0;//临时变量
printf("%d %d\n", a, b);
tmp = a;
a = b;
b = tmp;
printf("%d %d\n", a, b);
return 0;
}
(2)算数逻辑交换
int main()
{
int a = 3;
int b = 5;
printf("%d %d\n", a, b);
a = a + b;
b = a - b;
a = a - b;
printf("%d %d\n", a, b);
return 0;
}
(3)位操作符交换
a^ a = 0;
0 ^ a = a;
int main()
{
int a = 3;
int b = 5;
printf("%d %d\n", 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("%d %d\n", a, b);
return 0;
}
五.赋值操作符
赋值操作符,就是给你赋值很简单,你可以给你自己重新赋值
int main()
{
int a = 0;
int b = 0;
int c = 0, d = 0;
printf("%d\n", c);
return 0;
}
也可以连续的赋值,但是可读性很差,容易让人误解,我们就不连续的写,分开去写,好理解
复合赋值操作符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
其实用法都是一样的,就是简化我们的代码,都是一样的用法,一样的写法
int main()
{
int a = 10;
a = a + 5;
a += 5;
int b = 12;
b = b >> 1;
b >>= 1;
return 0;
}
六.单目操作符
1.分类
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、 后置--
++ 前置、 后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
2.逻辑反操作符
C语言中0表示假,非0表示真
int main()
{
int flag = 0;
if (flag)//flag如果为真,去执行下面语句
{
printf("hehe\n");
}
if (!flag)
{
printf("haha\n"); //打印的haha
}
printf("%d\n", flag); //0
printf("%d\n", !flag); //1
return 0;
}
3.布尔类型(补充)
c99中添加的,用来表示真假的类型
#include <stdbool.h>
别让了头文件哈
int main()
{
_Bool flag = false;
if (flag)
{
printf("hehe\n"); //这里就啥都不打印,_bool这个函数就是判断真假,true就是真,false就是假
}
return 0;
}
下面就是函数里面的用法,判断闰年,然后主函数里面判断打印就好了,true就是真返回!0,fales就是假返回0
bool is_leap_year(int y)
{
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0))
return true;
else
return false;
}
4.正值负值
打印出来-10和10
int main()
{
int a = -10;
printf("%d\n", a);
printf("%d\n", -a);
return 0;
}
打印出来都是-10
int main()
{
int a = -10;
printf("%d\n", a);
printf("%d\n", +a);
return 0;
}
5.强制类型转换(补充)
不看你全面啥的符号,就只看你的值,当补码的时候,第一位的符号位都不给你看了,全给你当成值来看,所以直接是正数给你打印了,unsigned打印的时候用%u
int main()
{
unsigned int num = -10;
//记住补码是放到内存里面的
//100000000000000000001010 - 原码
//111111111111111111110101 - 反码
//111111111111111111110110 - 补码
//补码是放在内存里面的,我们打印的时候用原码打印,正数直接,负数再化为原码,但是我们加上unsigned后,第一位不是符号位了
//所以我们就以为这里是个正数,补码当原码直接打印了,所以打印出来的值很大很大
printf("%u\n", num);
return 0;
}
第一位本来是符号位去判断正负,加上强制类型转换,第一位也变为数值,直接就当成正数打印了
6.取地址
这个讲过很多次了,就是取地址和指针的合用
int main()
{
int a = 10;
printf("%p\n", &a); //打印地址用%p
int* pa = &a; //用整型指针来放a的地址
char ch = 'w'; //字符类型
char*pc = &ch;
char arr[10] = { 0 };
char* p2 = arr; //数组名就是首元素地址
char* p3 = &arr[0]; //这个就是用小标表示的数组第一个元素然后取地址
char* p = "abcdef";//当是字符串的时候,就是穿的字符串第一个元素的地址,就是a的地址
printf("%p\n", p); //所以这个打印的时候就是a的地址
printf("%c\n", *p); //指针解引用打印出来就是a,字符串就不用取地址符号,直接传的就是第一个元素的地址
return 0;
}
下面就是最简单的指针,和解引用,pa里面放的是a的地址,加上*就是解引用,找到a的值
int main()
{
int a = 10; //int *就是指针变量
int* pa = &a; //把a的地址放到指针变量pa里面,想找到a的值,就用*解引用,直接pa,就是a的地址
*pa = 20;//解引用操作
printf("%d\n", a);
return 0;
}
7.sizeof
是关键字也是操作符,计算一个变量的大小,函数调用的时候,要写(),但是sizeof后边的括号可以省略,说明sizeof不是函数
int main()
{
int a = 10;
printf("%d\n", sizeof(a));//可以是变量
printf("%d\n", sizeof a);//不带括号也可以
printf("%d\n", sizeof(int));//也可以是类型
//但是int不加括号是不行的
int arr[10] = {0};
printf("%d\n", sizeof arr);//可以是数组名
printf("%d\n", sizeof(arr));//带括号也可以
printf("%d\n", sizeof(int[10]));//也可以是类型
int a = 10;
short s = 5;
printf("%d\n", sizeof(s = a + 3));//2, 因为我定义的时候就是short,就是两个直接,你里面怎么计算,我都是两个字节
printf("%d\n", s);//5,虽然我上面sizeof里面计算了,但是内部的表达式不参加计算,所以里面的不带到下面,我的s还是5
return 0;
}
我们来看它和数组
我们看好我们的sizeof计算的时候,主函数里面的求得是数组,整个数组大小
我们外面调用函数,外面传过去得是数组名也就是首元素地址,所以计算的就是一个指针的大小
#include <stdio.h>
void test1(int arr[])
{
printf("%zd\n", sizeof(arr));//4
//所以这里接受的时候也是地址,也是个指针,所以我们打印就是第一个元素的大小,就是4个字节,是整型指针才是4个字节
}
void test2(char ch[])
{
printf("%zd\n", sizeof(ch));//4
//所以这里接受的时候也是地址,也是个指针,所以我们打印就是第一个元素的大小,就是4个字节,是字符指针才是4个字节,虽然字符是一个字节
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%zd\n", sizeof(arr));//40
//这个打印的就是整个数组的大小,是40个字节
printf("%zd\n", sizeof(ch));//10
//这个也是打印整个数组的大小,ch类型是一个字节,所以这里数组一个是10个字节
test1(arr);
//函数调用后,是数组名是首元素的地址,我们传过去就是第一个元素的地址
test2(ch);
//函数调用后,是数组名是首元素的地址,我们传过去就是第一个元素的地址
return 0;
}
打印数组的时候也是一样的,我们用指针接收和用数组接收都是一样道理,数组名是首元素地址
void test1(int arr[], int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void test2(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);//arr[i] --> *(arr+i)
}
printf("\n");
}
//用指针和用数组都是一样的效果,数组名就是首元素的地址,所以我们函数接受的时候用指针和数组都是一样的
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int sz = sizeof(arr) / sizeof(arr[0]);
test1(arr, sz);
test2(arr, sz);
printf("%s\n", arr);
//这个是错误的
//%s 是打印字符串的
return 0;
}
8.按位取反
00000000000000000000000000000000 - 原码反码补码
进行按位取反
11111111111111111111111111111111 - 补码是全1
然后打印出来原码就是-1,记得打印都是原码,就看是正数还是负数,还需要取反加一不需要了
int main()
{
int a = 0;
printf("%d\n", ~a);//打印出来-1
return 0;
}
9.小综合练习
把a的二进制中的第五位改为1
int a = 9;
//00000000000000000000000000001001 原码反码补码
//00000000000000000000000000010000 先把第五位按位或上去1,其他按位或上去0,保持不变,就把第五位改成了1,其他不变
//上面这个数字就是我们要跟a按位或的数,怎么来,就是 1<<4 得到
//00000000000000000000000000011001 这就是我们想要的结果
a |= (1<<4);
再把改后的第五位再回来为0
//把a的二进制中的第5位改回来,变成0
//00000000000000000000000000011001
//11111111111111111111111111101111 先把第五位按位与上去0,其他位按位与上去1,保持不变,就把第五位改成了0,其他不变
//上面这个数字就是我们要跟a按位与的数,怎么来,就是把(1<<4)按位取反得到得到的
//00000000000000000000000000001001 这就是我们想要的结果a &= (~(1 << 4));
完整的代码
#include <stdio.h>
int main()
{
int a = 9;
//把a的二进制中第5位改成1
//00000000000000000000000000001001
//00000000000000000000000000010000 先把第五位按位或上去1,其他按位或上去0,保持不变,就把第五位改成了1,其他不变
//上面这个数字就是我们要跟a按位或的数,怎么来,就是 1<<4 得到
//00000000000000000000000000011001 这就是我们想要的结果
//把a的二进制中第5位改成1
a |= (1<<4);
printf("%d\n", a);
//把a的二进制中的第5位改回来,变成0
//00000000000000000000000000011001
//11111111111111111111111111101111 先把第五位按位与上去0,其他位按位与上去1,保持不变,就把第五位改成了0,其他不变
//上面这个数字就是我们要跟a按位与的数,怎么来,就是把(1<<4)按位取反得到得到的
//00000000000000000000000000001001 这就是我们想要的结果
a &= (~(1 << 4));
printf("%d\n", a);
return 0;
}
10.++ --
我们先讲前置后置++ --,对于这个我们考虑清楚谁在前面,我们先计算谁,先给谁赋值就好了
void test(int b)
{
printf("b = %d\n", b);
}
int main()
{
int a = 10;
int b = a++;//后置++,先使用,再++
int b = a;a=a+1;
int b = ++a;//前置++,先++,后使用
a=a+1,b=a;
printf("%d\n", a);//11
printf("%d\n", b);//11
//函数里面也是一样的道理,都是看谁在前面,先去把操作了,再去加加或者减减
int a = 10;
test(a++);//10
test(++a);//11
printf("%d\n", ++a);//11
printf("%d\n", a);//11
printf("%d\n", a++);//10
printf("%d\n", a);//11
return 0;
}
--也是一样的道理,看前置还是后置,看是不是先使用
int main()
{
int a = 10;
printf("%d\n", --a);//9
printf("%d\n", a);//9
return 0;
}
但是具有副作用,会改变自己本来的值
int main()
{
//1
int a = 10;
int b = ++a;//b=11 a=11
//这两个的值,虽然都是加一,但是原来的值会发生变化
//2
int a = 10;
int b = a + 1;//b=11 a=10
return 0;
}
11.强制类型转换
就在里面加上()带上类型,强制转换
int main()
{
int a = (int)3.14;
printf("%d\n", a);
return 0;
}
调用时间函数的时候也是一样的,强制类型转换,本来是time_t类型的,我们强制转换
int main()
{
//time_t;
srand((unsigned int)time(NULL));
return 0;
}
结束语
还有很多内容没有讲完,我们下一节课继续保持关注,重点掌握移位操作符和位操作符