当前位置:首页 » 《随便一记》 » 正文

【C语言】玩转操作符——操作符的那些东西!_波风张三的博客

20 人参与  2021年12月15日 11:26  分类 : 《随便一记》  评论

点击全文阅读


文章目录

  • 前言
  • 一、C语言中的操作符
  • 二、操作符详细介绍
    • 1.算术操作符
    • 2.移位操作符
      • 2.1移位操作符
        • 2.1.1左移操作符
      • 2.1.2右移操作符
    • 3、位操作符
      • 3.1位操作符的分类
      • 3.1.1按位与&
      • 3.1.2按位或|
      • 3.1.3按位异或^
      • 3.1.3使用场景
    • 4.赋值操作符
    • 5.单目操作符
      • 5.1操作符&、*
      • 5.2 操作符sizeof
      • 5.3操作符~
      • 5.4操作符++ 、--
    • 6.关系操作符
    • 6.逻辑操作符
    • 7.条件操作符
    • 8.逗号表达式
    • 9.下标引用、函数调用和结构成员
      • 9.1下标引用
      • 9.2函数调用
      • 9.3访问一个结构的成员
    • 10.表达式求值
      • 10.1隐式类型转换
      • 10.2 算术转换
      • 10.3操作符的属性
  • 最后


前言

是否能很好的理解的操作符的意思,决定着我们能否很好的理解代码。在一些相对复杂的程序中,不能搞懂操作符,我们将一筹莫展。因此,本文耗费三天的准备才行文,希望对大家有帮助!


提示:以下是本篇文章正文内容

一、C语言中的操作符

C语言提供了以下的操作符(有的地方也称为运算符)

运算操作符+ ,-,*,/,%,++,–
关系操作符<,>,==,>=,<=,!=
逻辑运算符!,&&
位运算符<<,>>,~,^,&
赋值运算符=及其扩展赋值运算符
条件运算符?:
逗号运算符
指针运算符*,&
求字节数运算符sizeof
强制类型转换运算符(类型)
成员运算符. , ->
下标运算符[ ]
其他如函数调用运算符()

下面一一介绍这些操作符的作用与应用

二、操作符详细介绍

1.算术操作符

最常用的算术操作符如下:

    /   %   +  -    *
举例结果
a+ba和b的和
a-ba和b的差
a%ba除以b的余数
a/ba除以b的商
a*ba和b的乘积

【说明】

  1. 由于键盘无×号➗号,用*和/代替
  2. 两个实数相除的结果是双精度实数,两个整数相除的结果为整数,如7/3的结果值为2,舍去小数部分。
  3. %操作符要求参加运算的运算对象(即操作数)为整数,结果也为整数。如7/3,结果为1,返回的是整除之后的余数。
  4. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除 法。

2.移位操作符

2.1移位操作符

1、<< 左移操作符
2、>> 右移操作符

2.1.1左移操作符

我们思考以下代码,printf输出的是什么?也就是在程序中,它是如何移位的?这是移位操作符的重点!

	int a = 5; //4byte-32bit=
	int b = a << 1;
	printf("%d\n", b);

我们知道程序在计算机中是以二进制的方式运行的,所以在这里进行移位的时候,我们也是以二进制的方式来进行移位。

所以:

  1. 我们得把a转化为二进制,因为a是int类型,所以是4个字节32个比特位。
  2. 计算机中,整数的二进制有三种表现形式——原码、反码、补码。
  3. 对于正整数-原码、反码、补码相同,对于负整数-需要计算

那么问题来了,原码,反码,补码怎么计算?
这个也不难。
首先需要明确的是,原码、反码、补码的第一位为符号位,正数符号位为0,负数为1

  1. 原码—直接按照数字的正负写出的二进制序列。 比如 -1
    原码:000000000000000000000000000000001
  2. 反码—原码的符号位不变,其他位按位取反得到的 所以a的
    反码:111111111111111111111111111111110
  3. 补码-反码+1
    补码:111111111111111111111111111111111

那么,计算机是如何进行移位的?
要想知道这个问题的答案,我们需先知道数字在计算机的存储形式。

计算机存的是补码,我们打印的是原码

所以,移位在计算机中是以补码的形式移动的。
下面我们回到例子中

	int a = 5; //4byte-32bit=
	int b = a << 1;
	printf("%d\n", b);

【如下图】

在这里插入图片描述最后将二进制的数字转化成十进制,答案为10.
在这里插入图片描述如果我们移动的是负数呢?那么这时候又该如何移动呢?

	int c = -1;
	int d = c << 1;
	printf("%d\n", d);

负数也是同样的原理,先把数字转化成二进制,然后计算出补码,计算机用补码进行移动,移动完成后,再转化成补码打印出来。
下面我们再画一个图给大家演示一下。
在这里插入图片描述在这里插入图片描述打印出来的结果:
在这里插入图片描述

2.1.2右移操作符

既然左移是二进制移动的,那么同理,右移也是二进制移动的,这里不再细讲,大家可以按照同样的思路来验证一下下面的右移结果

	int a = 5;
	int b = a >> 1;
	//逻辑右移,右边丢弃,左边补0
	//算术右移,右边丢弃,左边补原符号位,原来是正数就正数,负数就负数,绝大多数编译器采用,比如vs2013
	printf("%d\n", b);

在这里插入图片描述

3、位操作符

3.1位操作符的分类

& //按位与
| //按位或
^ //按位异或
注:他们的操作数必须是整数。

3.1.1按位与&

【作用】使参与运算的两数各对应的二进位相与。只有对应的两个二进位均为1时,结果位才为1 ,否则为0

【例子】

int main()
{
	int a = 3;
	int b = -2;
	int c = a&b;
	printf("%d\n", c);
	return 0;
}

同样的,这里要先算出a与b的补码。
【如图】
在这里插入图片描述在这里插入图片描述

3.1.2按位或|

【作用】其功能是参与运算的两数各对应的二进位相或。只要对应的二个二进位有一个为1时,结果位就为1。

【例子】

int main()
{
	int a = 3;
	int b = -2;
	int c = a|b;//计算的时候,内存的补码进行计算,所以需写出a与b的二进制
	printf("%d\n", c);
	return 0;
}

	//00000000000000000000000000000011    3的二进制序列的补码
	//11111111111111111111111111111110    -2的补码
	//11111111111111111111111111111111    只要有1,就为1,这个为计算完之后内存的补码,回到第一条注释

	//11111111111111111111111111111111    计算机内存的补码,1开头为负数,需计算原反补码
	//11111111111111111111111111111110    -1
	//10000000000000000000000000000001    取反

在这里插入图片描述

3.1.3按位异或^

【作用】其功能是参与运算的两数各对应的二进位异或,相同为零,相异为1

int main()
{
		int a = 3;
		int b = -2;
		int c = a^b;  //二进制位异或
		printf("%d\n", c);
		//异或-相同为0,相异为1
		//11111111111111111111111111111101
		//11111111111111111111111111111100
		//10000000000000000000000000000011         -3
	
	
		//00000000000000000000000000000011    3的二进制序列的补码
		//11111111111111111111111111111110    -2的补码
		//11111111111111111111111111111101
	return 0;
}

这里不再详解,只需遵循上面所说的技巧与方法便可求解。

在这里插入图片描述

3.1.3使用场景

1、不创建第三个变量,实现两个数的交换。

在这个时候我们便可以使用到上面的操作符了

#include <stdio.h>
int main()
{
 int a = 39;
 int b = 20;
 printf("交换前a = %d b = %d\n", a, b);
 a = a^b;
 b = a^b;
 a = a^b;
 printf("交换后a = %d b = %d\n", a, b);
 return 0; }

在这里插入图片描述这里大家有兴趣的话,可以参照上面的方法,讲两个整数转化成二进制进行计算验证一下。

2、求一个整数存储在内存中的二进制中1的个数。

#include <stdio.h>
int main()
{
 int a= 2;
 int i = 0;
 int count = 0;//计数
 for(i=0; i<32; i++)
 {
	 if( a& (1 << i) )
	 count++; 
 }
 printf("二进制中1的个数 = %d\n",count);
 return 0; 
 }

在这里插入图片描述

4.赋值操作符

1、赋值符号“=”就是赋值操作符

【作用】将一个数据赋给一个变量

【例子】

int a=3;//把常量3赋给变量a

2、复合的赋值操作符

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

比如:

a+=3 等价于 a=a+3
x*=y+8 等价于 x=x*(y+8)
x%=3 等价于 x=x%3

5.单目操作符

! 逻辑反操作,!a ——含义:如果a为假,则!a为真,如果a为真,则非!a为假
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
– 前置、后置–
++ 前置、后置++
* 间接访问操作符(解引用操作符) (类型) 强制类型转换

【注意】在C的逻辑运算中,以“1”代表“真”,以“0”代表“假”

下面重点讲解一下&、*、sizeof、~、++、–

5.1操作符&、*

取地址(&)操作符与解引用操作符(*)放一块将讲

取地址(&)

1、&后面是个变量。
每个变量对应一块存储空间。每个存储空间有一个编号,也就是地址。
&变量名 , 表示取出这个编,变量名表示取出这个编号所对应的存储空间里的值。

简单来说——&是取地址运算符,&a为变量a的地址。

解引用操作符(*)

提取一个变量的地址。&就提取它的地址,由地址找到a在内存中的空间。*是指针运算符。

举个例子:

int main()
{
	int a = 10;
	int *p = &a;//取地址由*p接受,*p为指针变量,p的类型为 int*,
	//取地址是由低到高取,也就是取首地址
	int  b = *p;//右值描述的是空间的内容
	//*p赋给b,并通过*p的值找到a,把a的值赋给b,当这里写*p的时候,找到的是a里面的值,用的是a空间的值,也就是找到10
	//这里因为用的是a的空间,所以下面是赋值给a
	*p=20;//*为解引用操作符,*p就是同p里面存的地址,找到它所指的对象
	printf("%d\n", a);
	return 0;
}

在这里插入图片描述

5.2 操作符sizeof

sizeof() 是一个判断数据类型或者表达式长度的运算符

int main()
{
	int a = 10;
	printf("%d\n", sizeof(a));//4
	printf("%d\n", sizeof a); //可以省略括号,说明不是函数
	printf("%d\n", sizeof(int));//4.为什么?int的类型用来创建a的,

return 0;
}

在这里插入图片描述

5.3操作符~

~表示按位取反,这个也是需要转换成二进制补码的

int main()
{
	int a = 0;
	//0000000000000000000000000000000
	
	int b = ~a;//按位取反
	//0000000000000000000000000000000
	//1111111111111111111111111111111   
	//1111111111111111111111111111110
	//1000000000000000000000000000001
	//-1
	printf("%d\n", b);
	return 0 ;
}

在这里插入图片描述

5.4操作符++ 、–

int main()
{
	int a = 10;
	printf("a=%d\n", a);
	int b1 = ++a;//前置++,先++,再使用
	int b2 = --a;//前置--,先--,再使用
	int b3= a++;//后置++,先使用再++
	int b4 = a--;//后置--,先使用后--
	printf("b1=%d\n", b1);
	printf("b2=%d\n", b2);
	printf("b3=%d\n", b3);
	printf("b4=%d\n", b4);
	printf("a=%d\n", a);
	return 0;
}

在这里插入图片描述

6.关系操作符

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

关系运算符跟我们平时在数学上的意思差不多,这里值得注意的是==,千万注意,在写的时候不要漏掉

6.逻辑操作符

&& 逻辑与
|| 逻辑或

【注意】这里要区分逻辑与和按位与,逻辑或和按位或
也就是

&与&&
|与||

运算符说明
逻辑与(&&)如果a和b都为真,则结果为真,否则为假
逻辑或如果a和b有一个以上为真,则结果为真,二者都为假时,结果为假

【区别】逻辑与和逻辑或,是用来判断的 按位与和按位或,是用来计算的

int main()
{
	int a = 3;
	int b = 5;
	//int c = a&&b;//逻辑与,只关注真假
	int c = a || b;//逻辑或
	printf("c=%d\n", c);
	return 0;
}

在这里插入图片描述

7.条件操作符

条件操作符的一般形式

表达式1?表达式2:表达式3

【说明】条件运算符由两个符号(?和:)组成,必须一起使用。要求有3个操作对象,称为三目(元)运算符,它是C语言中唯一的一个三目运算符。

那么,这个操作符表达的是什么意思?
我们借助一个例子,求两数中的最大值

if(a>b)
   max=a;
 else
   max=b;

以上代码可以改成

max=(a>b)?a:b;

意思是:

如果(a>b)的条件为真,则表达式的值等于a;否则取值b。

8.逗号表达式

一般形式:

(表达式1,表达式2,表达式3,… ,表达式n)

(1) 逗号表达式的运算过程为:从左往右逐个计算表达式。
(2) 逗号表达式作为一个整体,它的值为最后一个表达式(也即表达式n)的值。
(3) 逗号运算符的优先级别在所有运算符中最低。

如:(3+5,6+8)称为逗号表达式,其求解过程先表达式1,后表达式2,整个表达式值是表达式2的值。(3+5,7+8)的值是15;

int main()
{
	int a = 0;
	int b = 0;
	int c = 0;
	int d=(a = 3,b = 5,b += a,c = b * 5);
	printf("%d\n", d);
	return 0;
}

在这里插入图片描述

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

9.1下标引用

操作数:一个数组名 + 一个索引值

int main()
{
	int arr[] = { 1, 2, 3, 4, 5 };
	int i = 0;
	printf("%d\n", arr[4]);//通过下标,寻找arr数组的元素
	return 0;
}

在这里插入图片描述

9.2函数调用

( ) 函数调用操作符

void test()
{
	printf("hehe\n");
}
int main()
{
	test();//不传参,也叫函数调用操作符
	return 0;
}

在这里插入图片描述

9.3访问一个结构的成员

. 结构体.成员名
-> 结构体指针->成员名


#include <stdio.h>
#include<string.h>
//声明类型
struct Book;
//自定义类型
struct Book
{
	char name[20];
	float price;
	char id[10];
};

void print1(struct Book b)
{
	printf("书名:%s\n", b.name);
	printf("价格:%f\n", b.price);
	printf("书号:%f\n", b.id);
}

void print2(struct Book* pb)
{
	/*printf("书名:%s\n", (*pb).name);
	printf("价格:%f\n", (*pb).price);
	printf("书号:%s\n", (*pb).id);*/
//上下二者等价
	printf("书名:%s\n", pb->name);
	printf("价格:%f\n", pb->price);
	printf("书号:%s\n", pb->id);

}

int main()
{
	struct  Book b = {"C语言程序设计",55.5f,"C20190203"};
	print2(&b);
	//print1(b);
	//b.price = 100.0f;
	字符串拷贝-strcpy-库函数
	//strcpy(b.name, "数据结构");
	//print1(b);

	//结构成员访问操作符
	//.      左边是结构体变量,右边是成员们  结构变量.成员名
	//->       结构体指针->成员名
	//(*结构体指针).成员名
	return 0;
}

在这里插入图片描述

10.表达式求值

10.1隐式类型转换

整型提升

整型提升是C程序设计语言中的一项规定:在表达式计算时,各种整形首先要提升为int类型,如果int类型不足以表示则要提升为unsigned int类型;然后执行表达式的运算。

举个例子

int main()
{

	char a = 3;//a是一个字节,只有8bit位
	//000000000000000000000000000000000011         3的原码反码补码
	//00000011 --a里面存的东西,也就是将最后面的8位截取下来
	char b = 127;
	//00000000000000000000000000001111111     127的原码反码补码
	//01111111  --b里面的东西,也就是将最后面的8位截取下来


	//a和b自身都是char类型,自身大小都是一个字节,所以这里计算的时候要进行整型提升
	//	
	//00000000000000000000000000000011
	//00000000000000000000000001111111
	//00000000000000000000000010000010

	char c = a + b;
	//放回c中的时候,同样截断,10000010
	//高位是1,为负数
	//111111111111111111111111111110000010
	//100000000000000000000000000001111110
	printf("%d\n", c);
	
	return 0;
}

在这里插入图片描述

【整型提升的意义】

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。通用CPU(general-purpose
CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned
int,然后才能送入CPU去执行运算。

                                         ——摘自百度百科(整型提升)

10.2 算术转换

当程序运行时遇到不同类型的数据进行运算,先将二者自动转换成同一种类型,再进行运算

	float a = 10.0;
	double b = 9.5;

在这里,如果进行+、-、*、/,运算的时候,系统会将所有的float型数据都先转化成double,然后进行运算。
这个转换时编译系统自动完成,用户不必过问。

如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。

警告:
但是算术转换要合理,要不然会有一些潜在的问题.
例如:

float f = 3.14;
int num = f;//隐式转换,会有精度丢失

10.3操作符的属性

复杂表达式的求值有三个影响的因素。

  1. 操作符的优先级
  2. 操作符的结合性
  3. 是否控制求值顺序。

两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。

操作符优先级(下图来源于网络)
在这里插入图片描述


最后

最近学习C语言有点累了,时常感觉前路迷惘,害怕自己不是这块料却非要往这里凑。本文撰述了将近6个小时。参考了谭浩强《C语言设计》(第五版),以及网上的部分资料,加之自己在学习听课时的笔记,拼拼凑凑而成,尽管如此,却也花费了我很多心思。

希望能对看到的大家有所帮助!


点击全文阅读


本文链接:http://zhangshiyu.com/post/31591.html

操作  补码  表达式  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1