C语言初阶——操作符part1

目录

一.操作符分类

 二.算数操作符

三.移位操作符

1.二进制的存储

2.左移操作符

3.右移操作符

四.位操作符

1.&  按2进制位与

2.|  按2进制位或

3. ^  按2进制位异或

4.深度理解

(1)正常创建变量的交换

(2)算数逻辑交换

(3)位操作符交换

五.赋值操作符

六.单目操作符

1.分类

2.逻辑反操作符

3.布尔类型(补充)

4.正值负值

5.强制类型转换(补充)

6.取地址

7.sizeof

 8.按位取反

9.小综合练习

10.++  -- 

11.强制类型转换

结束语


这节课我们详细来看操作符

一.操作符分类

算术操作符

移位操作符

位操作符

赋值操作符

单目操作符

关系操作符

逻辑操作符

条件操作符

逗号表达式

下标引用、函数调用和结构成员

 二.算数操作符

+   -   *   /   %   

我们详细来看一下除于和取余

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;
}

由补码变到原码,反过来倒着回来计算,减一取反,按照原逻辑,不求反码了,直接取反加一就是原码,看一个图

e32c53d34522447fb2beb7baf2a07ff1.png

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;
}

7cf6dfad03af4128b8b5c49406cae38a.png

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填充,右边抛弃一个

2d85103e54cf45a5b38754d52ee69e0f.png

2. 算术右移(多见)

右边的用原来的符号位上的值进行填充,右边抛弃一个

e4b6344d2152446ba6133bd3b3863e32.png

负数: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;
}

结束语

还有很多内容没有讲完,我们下一节课继续保持关注,重点掌握移位操作符和位操作符

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值