【C语言】操作符详解(上)

所属专栏:C语言
博主首页:初阳785
代码托管:chuyang785
感谢大家的支持,您的点赞和关注是对我最大的支持!!!
博主也会更加的努力,创作出更优质的博文!!
关注我,关注我,关注我,重要的事情说三遍!!!!!!!!

1.操作符分类

  1. 算术操作符
  2. 移位操作符
  3. 位操作符
  4. 赋值操作符
  5. 单目操作符
  6. 关系操作符
  7. 逻辑操作符
  8. 条件操作符
  9. 逗号表达式
  10. 下标引用、函数调用和结构成员

2.算数操作符

	+  -  *  / %
  1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
  2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
  3. % 操作符的两个操作数==必须为整数。==返回的是整除之后的余数。

具体的代码实操这里就不做过多的讲解,都是一些比较简单的操作。

3.移位操作符

<< 左移操作符
>> 右移操作符

注:移位操作符的操作数只能是整数。
注:移位操作符操作的是二进制位。

这里我们先了解一下整数的二进制的表达形式,主要有三种:原码,反码,补码。

  1. 正整数的原码,反码,补码是相同的。
  2. 负的整数原码,反码,补码是要进行计算的。

首先不管是负整数还是正整数都是可以写出他们的二进制原码的。

  1. 根据正负号直接写出来的二进制序列就是原码。

比如我们的 int a = 1;
我们知道一个int类型的大小时4个字节,一个字节时8个bit位,所以我们表示a的二进制就是:

0 0000000 00000000 00000000 00000001
其中最高位表示符号位(就是黄色代表的那个),我们C语言规定最高位0代表正数,1代表负数

如果要表示int a = -1;就是:
1 0000000 00000000 00000000 00000001
这个个时候就表示-1了。

现在我们来讨论一下他们的原码,反码,补码。
int a = 1;
我们说了正整数的原码,反码,补码是相同的。
00000000 00000000 00000000 00000001 ——原码
00000000 00000000 00000000 00000001 ——反码
00000000 00000000 00000000 00000001 ——补码

但是负数的原码,反码,补码是要计算的。
int a = -1;
10000000 00000000 00000000 00000001 ——原码
111111111 111111111 111111111 111111110 ——反码 (原码的符号位不变,其他按位取反得到的就是反码。按位取反的意思就是把是1的换成0,把是0的换成1)
111111111 111111111 111111111 111111111 ——补码 (反码+1得到的就是补码)
至于为什么是这样的这个是C语言规定的。

注:整数在内存中存储的是补码,所以在进行计算的时候也是用补码进行计算的。所以我们移动的二进制位也是补码。

我们分析下面一串代码:

#include <stdio.h>
int main()
{
	int a = 1;
	int b = a >> 1;
	int d = a << 1;
	printf("%d\n", b);
	printf("%d\n", d);
	return 0;
}

3.1 右移

在这里插入图片描述
所以对于我们的右移不回去的那个位置有两个规定:

  1. 如果是算术右移:那么右边丢掉的,左边补原来的符号位。
  2. 如果是逻辑右移:那么右边丢掉的,左边直接补0。

注:以上的位移操作都是对二进制的补码进行的,只不过上面的a是正整数,它的原反补是相同的。
是负数的话,就要要将其转换成补码来移动,二我们显示屏上显示的是二进制位的原码,所以就得把补码转换成原码,着个转化也很简单,就是补码-1得到反码,在按位取反就得到了原码。

注:C语言没有明确规定使用哪一种方法,但是一般编译器采用的是算术右移

3.2 左移

二对于我们的左移就没那么复杂:
左移的话,左边少的,直接在右边补0就行了。

所以上面的代码的展示就是:0 和 2
在这里插入图片描述

注:我们可能写除a>>-1,也就是说一移动负数个位,这是个错误的写法。

4.位操作符

& 		//按位与
| 		//按位或
^ 		//按位异或

注:他们的操作数必须是整数。
注:这些也是操作的二进制位的补码。
例如:

#include <stdio.h>
int main()
{
	int num1 = 1;
	int num2 = -2;
	int a = num1& num2;
	int b = num1 | num2;
	int c = num1^ num2;
	printf("%d %d %d", a, b, c);
	return 0;
}
  1. 我们的&的规则:对应的二进制位有0则为0,同时为1才是1,两个同时为0也是0。
    我们的num1=1。
    他的原码和补码是相同的的:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    而num1=-2,他们的:
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0——原码(得到这个,但是我们屏幕上展示的是原码,所以要转换成原码的形式
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1——反码
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——补码
    num1&num2:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
    得到:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0——补码
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1——反码
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0——原码
    所以num1&num2=0;

  2. 我们的|对应的规则是:对应的二进制位有1则是1,同时是1则是1,同时是0则是0;
    同样的:
    num1|num2:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
    得到:
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1——补码
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——反码
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1——原码
    所以num1|num2=-1;

  3. 我们的^的规则是:两个二进制位相同则是0,不相同则是1.
    num1^num2:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0
    得到:
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1——补码
    1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——反码
    1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1——原码
    所以num1^num2=-1;

代码展示:
在这里插入图片描述

4.1位操作符发的应用

我们的直接上代码来解释:
说这有个题目,交换两个数,但是不能创建临时变量。
于是我们就可以这样写;

#include <stdio.h>
int main()
{
	 int a = 10;
 	int b = 20;
 	a=a+b;
 	b=a-b;
 	a=a-b;
 	printf("a = %d b = %d\n", a, b);
 	return 0;
}

但是这种写法也是有缺缺陷的,那就是当a=a+b,这个a+b的值超出了int型所能存储的范围,这个时候就有可能会丢失一些数据。就得不到我们想要的结果。

于是我们改进:

#include <stdio.h>
int main()
{
 	int a = 1;
 	int b = 2;
 	a = a^b;
 	b = a^b;
 	a = a^b;
 	printf("a = %d b = %d\n", a, b);
 	return 0;
}
  • a的二进制位:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    b的二进制位:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0

  • a^b得到的二进制位:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1

  • 这个时候a=a^b==0;
    然后b=a^b:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
    得到:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    这个时候b就等于1;

  • 再就是:a=a^b;
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1
    得到:
    0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0
    这个时候a=2

  • 经过上面的过程,我们我们成功的交换了a,b的值。
    这也就说明了我们的异或是支持交换律的

  • 上面的写法就有效的避免了数据溢出的可能,因为异或是不会进位的,只会改变二级制位。

5.赋值操作符

赋值操作符是一个很棒的操作符,他可以让你得到一个你之前不满意的值。也就是你可以给自己重新赋
值。

int weight = 100;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。

赋值操作符可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值

见这些赋值操作符组合一下就可以得到复合赋值符:

+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=

int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁。

6.单目操作符

!           逻辑反操作
-           负值
+           正值
&           取地址
sizeof      操作数的类型长度(以字节为单位)
~           对一个数的二进制按位取反
--          前置、后置--
++          前置、后置++
*           间接访问操作符(解引用操作符)
(类型)       强制类型转换
  1. 这里讲一下!(逻辑反操作),就是把真的看成假的,把假的看成是真的。
#include <stdio.h>
int main()
{
	int a = 1;
	if (!a)
	{
		printf("hehe\n");
	}
	else
	{
		printf("haha\n");
	}
	return 0;
}

就比如这个,本来我们if语句里面的判断是非0为真,为0是假,所以上面打印的因该是hehe。
但是我们加上了!(逻辑反操作)就不一样了,if语句变成了是0则执行,非0不执行,所以上面的执行结果因该是haha。
在这里插入图片描述

  1. +正号,-符号这个就不用多讲了,就是我们数学上的那样了

  2. & *有两个不同的作用。
    第一种:应用于指针。一个是取地址,和解引用操作。
    第二种:&按位与,和指针类型。

int* pa;//pa是个指针。
int b=20;
*pa=10;//解引用操作。
pa=&b;//取地址操作。

int a,b,c;
c=a&b;//按位与。
  1. sizeof
    sizeof不是函数,是操作符。计算的是类型创建变量的大小,单位字节。
    用法也很简单:
int a=10;
printf("%d",sizeof(int));
printf("%d",sizeof(a));

同时sizeof也可以用来计算数组的大小。

int arr[10]={0};
printf("%d",sizeof(arr));//计算的整个数组的大小时40个字节。
  1. ~按位取反
    比如:
int main()
{
	int a = 0;
	printf("%d", ~a);
	return 0;
}

这个结果是什么呢?我们来分析一下:
a的补码:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ——补码
按位取反之后:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 ——补码(取反后还是补码)
我们显示出来时以原码的形式展示的
反码:
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0——反码(-1得到反码)
原码:
1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1——原码(符号位不变,其他取反)
这个时候我们按位取反得到的 数就是-1了。
在这里插入图片描述
我们结合一下上面的知识点:
这里我们给出int a = 13.
此时a的二进制位是:
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1
这个时候我希望把上面画黄色的0改成1,这么办。
我们就想到让
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1按位或上
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0
这个时候及改成了
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1
于是我们就写成:
a | = (1<<4) 就得到我们想要的结果了。

而这个时候我们又想把那个一给改回去,怎么办。我们用到了按位且
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 0 1按位且上
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1
就可以得到
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 0 1
而1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 1 1 1 1则是1<<4后取反
于是就可以写成:a &= (~(1<<4))

  1. ++和–
    ++和–分为;–前置,后置–,++前置,后置++。
int a=1;
int b=a++;//后置++
后置++的特点是先执行后++,也就是上面的执行顺序是先让b=a,然后a=a+1;
这时候a=2,b=1

我们看一下效果:
在这里插入图片描述
而如果改成前置++呢?

int a=1;
int b=++a;//前置++
前置++的特点是,先++后使用
也就是说它的执行顺序是:a=a+1,b=a;
这个时候b=2,a=2;

在这里插入图片描述
而我们–也是一样的。

  1. (类型)强制类型转换
int a=(int)3.1415926
就是把3.1415926强制类型转换成int,此时a就等于3

这里做一个练习:

#include <stdio.h>
void test1(int arr[])
{
 	printf("%d\n", sizeof(arr));//(3)
}
void test2(char ch[])
{
 	printf("%d\n", sizeof(ch));//(4)
}
int main()
{
 	int arr[10] = {0};
 	char ch[10] = {0};
 	printf("%d\n", sizeof(arr));//(1)
 	printf("%d\n", sizeof(ch));//(2)
 	test1(arr);
 	test2(ch);
 	return 0;
}

这个时候(1)(2)(3)(4)分别是多少呢?
(1)和(2)我们可以轻易的得到(1)==40,(2)==10

问题就在于(3)和(4)是多少呢
我们test1和test2都是传的数组过去的,也就是数组的首元素地址,所以我们形参接收的本质上是指针所以我们计算的是指针的大小,所以(3)和(4)的值是4或者8

展示:
在这里插入图片描述
我的电脑是64位的所以指针大小是8。

7.关系操作符

>
>=
<
<=
!=      用于测试“不相等”
==      用于测试“相等”

这些关系运算符比较简单,但是我们要注意一些运算符使用时候的陷阱。
警告:在编程的过程中 = = 和=不小心写错,导致的错误

8.逻辑操作符

&&     	逻辑与——并且
||      逻辑或——或者

区分逻辑与和按位与
区分逻辑或和按位或

1&2----->0
1&&2---->1
1|2----->3
1||2---->1

做一个笔试题:

#include <stdio.h>
int main()
{
    int i = 0,a=0,b=2,c =3,d=4;
    i = a++ && ++b && d++;(1)
    //i = a++||++b||d++;(2) 
    printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
    return 0;
}

这个程序的结果是什么呢?

  • 我们来分析一下(1):
    a=0,而a++是先赋值后++的所以在语句没有结束之前a都是等于0的,0为假所以表达式为假,后面的表达式就不会在执行了。而结束后a加加了。
    所以最终结果就是:1 2 3 4。
    所以我们知道了==&&左边为假后面的就不计算了==。
    在这里插入图片描述

  • 我们来分析一下(2):
    首先是a++||++b,a等于0,b是前置++所以b=3,这个时候是逻辑或,只要出现真后面就不计算
    所以到我们计算的++b的时候表达式为真,跳出,后面的d++就不计算了。
    所以最后的答案是1 3 3 4
    在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_74276498/article/details/130636484
今日推荐