操作符详解(C 语言)

目录

  • 一、操作符的分类
  • 二、算数操作符
    • 1. 除法操作符
    • 2. 取余操作符
  • 三、位移操作符
    • 1. 进制
    • 2. 原码、反码和补码
    • 3. 左移操作符(<<)和右移操作符(>>)
  • 四、位操作符
    • 1. 按位与 &
    • 2. 按位或 |
    • 3. 按位异或 ^
    • 4. 按位取反 ~
  • 五、单目操作符
    • 1. 逻辑非(!)
    • 2. 自增操作符(++)
    • 3. 自减操作符(--)
    • 4. 取地址操作符(&)
    • 5. 解引用操作符(\*)
    • 6. 正号(+)和负号(-)
    • 7. sizeof 操作符
    • 8. 强制类型转换操作符——(类型)
  • 六、逗号表达式
  • 七、下标引用操作符([])和函数调用操作符(())
  • 八、结构成员访问操作符(.)
    • 1. 结构体简介
    • 2. 使用结构体成员访问操作符对结构体变量进行访问
  • 九、操作符的优先级和结合性
  • 十、表达式求值
    • 1. 整型提升
    • 2. 算数转换
    • 3. 问题表达式解析

一、操作符的分类

• 算术操作符: + 、- 、* 、/ 、%
• 移位操作符: << 、>>
• 位操作符: & 、| 、^
• 赋值操作符:= 、+= 、 -= 、 *= 、 /= 、%= 、<<= 、>>= 、&= 、|= 、^=
• 单⽬操作符: !、++、–、&、*、+、-、~ 、sizeof、(类型)
• 关系操作符: > 、>= 、< 、<= 、 == 、 !=
• 逻辑操作符: && 、||
• 条件操作符: ? :
• 逗号表达式: ,
• 下标引⽤: []
• 函数调⽤: ()

以上操作符有些初始 C 语言的时候已经介绍过了,这次复习一下。

二、算数操作符

加法、减法和乘法操作符就不过多叙述,这里主要介绍除法和取余操作符。

1. 除法操作符

当两个操作对象有一个是浮点数时,执行浮点数的除法,也就是带小数的。当两个操作对象都是整数时,结果为商舍弃余数。如:5 / 3 = 1,余数 2 舍弃;而 5.0 / 3 = 1.6666。如下代码:
在这里插入图片描述

2. 取余操作符

首先,取余操作符的两个操作对象必须为整数,然后其运算结果为第一个操作对象除以第二个操作对象的余数。如:5 % 3 = 2,本来是商 1 余 2,这里只取余数。如下代码:
在这里插入图片描述

三、位移操作符

位移操作符是作用于整数的二进制位数的。在对操作符进行说明之前,需要补充一下进制和原码、反码、补码的相关知识。

1. 进制

我们日常生活中使用的数都是采用十进制,如:10、20、99等。但是在计算机中,数据都是采用二进制进行存储的。其实不同的进制之间除了表达方式不同,其实质并没有差异。如:二进制 1111 和十进制 15,都表示数值 15,就是表达形式不同。

(1)其他进制转十进制
其他进制转 10 进制,只要每位乘以相应的权重即可,如:二进制 1111 转十进制
在这里插入图片描述
(2)十进制转其他进制
除以相应进制数取余,然后逆序输出。如:十进制 15 转二进制,
15 / 2 商 7 余 1
7 / 2 商 3 余 1
3 / 2 商 1 余 1
1 / 2 商 0 余1

然后倒着输出余数,就是二进制 1111。如下是一个函数,接受一个整数输出其二进制数:

// 输出整数的二进制
// 采用递归的方法
void binary(int n)
{
	if (n > 0)
	{
		int remain = n % 2;
		binary(n / 2);
		printf("%d", remain);
	}
}

采用尾递归的方法更加方便逆序输出。

2. 原码、反码和补码

计算机中数据的存储都是采用二进制的形式,而二进制又分为原码,反码和补码。而正整数的这三种形式相同,如:int a = 10
原码:00000000000000000000000000001010
反码:00000000000000000000000000001010
补码:00000000000000000000000000001010

而负整数的反码和补码需要计算,反码是原码符号位不变,其余位取反;而补码是反码加 1,如:int b = -10;
原码:10000000000000000000000000001010
反码:111111111111111111111111111111110101
补码:111111111111111111111111111111110110

从上述 10 和 -10 的原码,不难得出最高位为符号位,且 1 表示负数,0 表示非负数。当然这是有符号整数,无符号整数的最高位仍参与计算,因为无符号整数没有负数。现在的 int 类型大多为 32 位,所需上述使用的是 32 为二进制数。

3. 左移操作符(<<)和右移操作符(>>)

顾名思义,左移操作符把被操作对象的二进制数左移,右移操作符把被操作对象的二进制数右移。且左移和右移操作符均针对整数的二进制补码进行操作。

(1)左移操作符(<<)
表达式 5<<1 的意思是把整数 5 的二进制数左移一位,如下:
在这里插入图片描述
左边超出的位数去掉,右边缺少的位数补 0,则左移一位后的结果为:
00000000000000000000000000001010
结果为十进制的 10,相当于原来的两倍。其实也很好理解,如:十进制 10 向左移动一位结果为 100,是原来的 10 倍。所以得出结论,当该正整数左移 n 位结果不超过该类型的范围时,其结果为原来的 2 的 n 次方倍。

左移负整数时,移动的是该负整数的补码,但是所得结果是该负整数的原码,这时就要进行计算。如:-5<<1
原码:10000000000000000000000000000101
反码:111111111111111111111111111111111010
补码:111111111111111111111111111111111011
左移一位:11111111111111111111111111110110

上述左移一位的结果仍是补码,这里要通过补码计算出原码,有两种方法:
(1)补码取反加 1
(2)补码减 1 取反

得出结果的原码:10000000000000000000000000001010,也就是 10 进制的 -10。

结论:当一个正整数左移 n 位时,其结果若不超出该类型的范围,那么结果是原数的 2 的 n 次方倍。但是对于负整数来说不一定。

(2)右移操作符(>>)
右移操作符和左移操作符类似,但是右移操作符分为:算数右移和逻辑右移。算数右移右边丢弃,左边补符号位;而逻辑右移左边补 0。但是大多情况下编译器使用的都是算数右移。

如:5>>1
补码:00000000000000000000000000000101
右移:00000000000000000000000000000010
结果:2

如:-5>>1
原码:10000000000000000000000000000101
反码:111111111111111111111111111111111010
补码:111111111111111111111111111111111011
右移:111111111111111111111111111111111101
原码:10000000000000000000000000000011
结果:-3

结论:对于正整数来说,右移 n 位,相当于除以 2 的 n 次方(取整数部分)。对于负整数来说不一定。

四、位操作符

位操作符有:
(1)按位与 &
(2)按位或 |
(3)按位异或 ^
(4)按位取反 ~

它们也是作用于整数的二进制数。

1. 按位与 &

如表达式 5 & 8 就是把两个数的 32 为二进制展开,一一对应,对应位均为 1 则为 1,否则为 0。如:
8:00000000000000000000000000001000
5:00000000000000000000000000000101
  00000000000000000000000000000000
结果为 0。

2. 按位或 |

和按位与类似,但是对应位上只要有 1 则为 1,否则为 0。如: 5 | 8
8:00000000000000000000000000001000
5:00000000000000000000000000000101
  00000000000000000000000000001101
结果为 13。

3. 按位异或 ^

按位异或是对应位上相同为 0,相异为 1。如:5 ^ 8
8:00000000000000000000000000001000
5:00000000000000000000000000000101
  00000000000000000000000000001101
结果为 13。

4. 按位取反 ~

按位取反是把操作数的每一个二进制数都取反,包括符号位。如:~5
补码:00000000000000000000000000000101
取反:11111111111111111111111111111010
去反后得到的是补码,计算结果需要原码,所以进行计算:
原码:10000000000000000000000000000110,结果为 -6

五、单目操作符

单⽬操作符有这些:
!、++、–、&、*、+、-、~ 、sizeof、(类型)

1. 逻辑非(!)

把操作对象的逻辑取反,真变假,假变真。如:
在这里插入图片描述

2. 自增操作符(++)

自增操作符分为前置和后置。前置自增操作符先对操作数加 1,再使用。而后置自增操作符先使用操作数,然后再对操作数假 1。如:
前置:
int a = 5;
int b = ++a;
相当于
int a = 5;
a = a + 1;
int b = a;

后置:
int a = 5;
int b = a++;
相当于
int a = 5;
int b = a;
a = a + 1;

3. 自减操作符(–)

和自增操作符类似。前置自减操作符先对操作数减 1,然后使用。后置自减操作符,先使用操作数,然后再让操作数加 1。

4. 取地址操作符(&)

取出操作对象的地址,使用格式 %p 进行输出。如:
在这里插入图片描述
当然也可以使用指针变量。

5. 解引用操作符(*)

解引用操作符作用于指针变量,对该指针进行解引用找到其指向的对象。被解引用的指针必须是有效的,否则可能导致程序崩溃。

6. 正号(+)和负号(-)

正号几乎不使用,没啥用。符号就是把操作数取负,正数变负数,负数变正数。

7. sizeof 操作符

sizeof 操作符计算操作对象的大小,单位字节。当计算对象是类型时,必须加括号,当计算类型是一个变量时,括号可加可不加。如下:
在这里插入图片描述

对变量和对该变量的类型使用 sizeof 操作符的结果是一样的。

8. 强制类型转换操作符——(类型)

强制类型转换操作符把操作对象强制转换为需要的类型,如:int a = (int)3.14,该表达式把 double 值 3.14 强制类型转换为 int 值。在编写程序的过程中应该尽量不要使用强制类型转换。

六、逗号表达式

逗号表达式是用逗号隔开的多个表达式:
expr1, expr2, expr3, …, exprn
逗号表达式从左向右求值依次对每个表达式进行求值,但逗号表达式的最终结果为最右边表达式的值。如下代码:

int a = 2, b = 3;
int c = (a++, ++b);

由于赋值运算符的优先级要高于逗号表达式,所以在使用时需要加上括号。先计算a++,a 的值为 3,然后计算++b,b 的值为 4,所以逗号表达式的值为 4,赋值给 c。
在这里插入图片描述

七、下标引用操作符([])和函数调用操作符(())

下标引用操作符主要是给数组使用的,其有两个操作对象 —— 数组名和下标。作用为:获取该数组的该下标处的元素。

函数调用操作的操作对象至少有一个,即函数名和任意参数。其作用为调用该函数。

八、结构成员访问操作符(.)

1. 结构体简介

结构体是一种自定义类型,它可以包含多种不同类型。比如我们想要记录一个学生的信息:姓名、性别、年龄。就可以声明如下结构类型:

// 学生结构体声明
struct Stu {
	char name[20];  // 姓名
	char sex[5];  // 性别
	int age;  // 年龄
};

上述代码只是一个类型的声明,就是告诉编译器有这么一个类型,它里面包含什么信息。然后我们就可以像创建 int 变量一样创建该类型的变量。

// 创建 stuct Stu 变量并初始化
struct Stu Lihua = { "李华", "男", 18 };

前面的 struct 不能省略。如果声明放在一个函数中,那么该结构体就是局部结构体,只能在该函数中创建该结构体变量。如果在所有函数之外声明该结构体,那么该结构体就是全局结构体,可以在全局创建该结构体变量。

2. 使用结构体成员访问操作符对结构体变量进行访问

我们可以直接使用成员访问运算符来访问上述创建的 Lihua 变量的每个成员,如:
Lihua.name
Lihua.sex
Lihua.age
使用它们就和使用原来的类型一样,下面使用 printf() 函数显示其信息:
在这里插入图片描述
也可以定义一个打印函数,这样每次需要打印信息的时候调用一下该函数就好了,参数传递有两种形式,值传递和址传递。如下:

// 打印结构体 struct Stu
void PrintStu1(struct Stu s);  // 值传递
void PrintStu2(struct Stu* ps);  // 址传递

值传递:被调函数中的形参是主调函数中实参的副本(也就是重新创建了一个结构体变量,把实参的值拷贝过来了而已)。其缺点是,当结构体所占空间较大时,会造成时间和空间的损失;优点是不会修改实参。

址传递:把结构体的地址传递给了被调函数,被调函数通过该指针使用指向结构体成员运算符(->)来对主调函数的实参进行访问。这样只传递了一个指针,大大提高了运行效率。缺点是:可以修改实参(可以加上 const 防止被修改)。

下面是两个函数的函数定义:

// 打印结构体 struct Stu
void PrintStu1(struct Stu s)  // 值传递
{
	printf("姓名:%s\n", s.name);
	printf("性别:%s\n", s.sex);
	printf("年龄:%d\n", s.age);
}

void PrintStu2(struct Stu* ps)  // 址传递
{
	printf("姓名:%s\n", ps->name);
	printf("性别:%s\n", ps->sex);
	printf("年龄:%d\n", ps->age);
}

九、操作符的优先级和结合性

众所周知先乘除后加减,这是因为乘除的优先级比加减高,所以在同一个表达式中出现不同的操作符时,根据优先级进行先后计算。但是当优先级相同时,就需要根据操作符的结合型进行计算。如:3+4*5+10+6
上述表达式先计算 4*5,因为乘法的优先级高于加法,然后计算 3 + 4*5,因为加法操作符的结合性是从左到右,然后 + 10,再 + 6。

下面是常用操作符的优先级表:
在这里插入图片描述
上面这张图是比特 C 语言课程的课件哈,有兴趣的小伙伴可以了解一下比特,作者并没有打广告,个人觉得比特 C 语言的课程讲的很好。

十、表达式求值

1. 整型提升

当 char、short等短整型进行算数运算时,它们的值就会被提升为 int 类型,然后参与算数运算,该行为被称为整型提升。

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

整型提升的规则:有符号整型提升补符号位,无符号整型提升补 0。

如:
char a = -1;
short b = 2;
a + b;
进行 a + b 时,先对 a 和 b 进行整形提升
a:11111111
提升:111111111111111111111111111111111 结果为 -1
b:0000000000000010
提升:00000000000000000000000000000010 结果为 2
提升后相加:1

2. 算数转换

当操作符来两边出现不同类型的对象时,那么其中一个操作数就需要转换为另一个操作数的类型,否则无法进行计算。一般按照下面的顺序由低向高转换:
(1)long double
(2)double
(3)float
(4)unsigned long
(5)long
(6)unsigned
(7)int

3. 问题表达式解析

虽然我们已经学习了以上种种与表达式求值有关的知识,但是仍然有不少表达式我们是无法求出确切的值,而且我们在编写代码的时候也尽量不要编写这些代码。

(1)类型1
int i = 1;
(i++) + (i++) + (i++);
该表达式只能确定后置递增运算符在加法之前,但是不能确定先算计算几个递增运算符,我可以先计算三个 i++,然后 i 的值为 4,然后相加得出表达式的结果为 12,;我也可以先计算前面两个 i++ 然后 i 的值为 3,前面两个 i 相加得 6,再加最后一个 i++,表达式结果为 9,i 的值最终为 4。

所以尽量不要编写上述类型的代码。

(2)类型2

#include <stdio.h>
int fun()
{
 static int count = 1;
 return ++count;
}
int main()
{
 int answer;
 answer = fun() - fun() * fun();
 printf( "%d\n", answer);//输出多少? 
 return 0;
}

虽然大多数编译器上面求得该表达式的结果相同,但是我们只能确定乘法在加法之前,但是却不能得出三个函数的调用顺序,我可以先调用第一个函数,也可以先调用第二个。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/893142.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

大衍数列——考研408考试科目之数据算法——未来之窗学习通

一、大衍数列 中国古代文献中&#xff0c;曾记载过“大衍数列”, 主要用于解释中国传统文化中的太极衍生原理。 它的前几项是&#xff1a;0、2、4、8、12、18、24、32、40、50 … 其规律是&#xff1a;对偶数项&#xff0c;是序号平方再除2&#xff0c;奇数项&#xff0c;是…

有源滤波器(三)

这个连接方法很可以&#xff0c;正好解决了最近没有转接器的问题&#xff1a;

javaweb-xml映射文件编写sql语句

可以使用注解的方式&#xff0c;也可以使用xml映射的方式&#xff0c;一般简单sql语句使用注解&#xff0c;复杂的使用xml映射。

【PhpSpreadsheet】ThinkPHP5+PhpSpreadsheet实现批量导出数据

目录 前言 一、安装 二、API使用 三、完整实例 四、效果图 前言 为什么使用PhpSpreadsheet&#xff1f; 由于PHPExcel不再维护&#xff0c;所以建议使用PhpSpreadsheet来导出exlcel&#xff0c;但是PhpSpreadsheet由于是个新的类库&#xff0c;所以只支持PHP7.1及以上的版…

【文心智能体 | AI大师工坊】如何使用智能体插件,完成一款购物类智能体的开发,来体验一下我的智能体『科技君Tom』

目录 1.1、智能体运行效果1.2、创作灵感来源智能体平台拥有个人化且人性化的大致框架&#xff0c;可以让小白也能搭建出一个智能体其次是拥有丰富的插件&#xff0c;可以更加快速的得到自己想要的效果~ 1.3、如何制作智能体常见问题与解决方案关于人设与回复逻辑插件使用模型的…

【黑马redis高级篇】持久化

//来源[01,05]分布式缓存 除了黑马&#xff0c;还参考了别的。 目录 1.单点redis问题及解决方案2.为什么需要持久化&#xff1f;3.Redis持久化有哪些方式呢&#xff1f;为什么我们需要重点学RDB和AOF&#xff1f;4.RDB4.1 定义4.2 触发方式4.2.1手动触发save4.2.2被动触发bgsa…

文件IO(Linux文件IO,目录操作函数)

前言 本文介绍Linux系统下自带的文件IO的函数。 Linux文件IO相关函数 open函数 #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode)…

2024ideaUI切换和svn与git的切换

2024的UI实在很不舒服&#xff0c;隐藏了很多按键&#xff1b; 第一步&#xff1a; 视图 -》 外观 -》 工具栏选出来&#xff1b; 结果出来&#xff1a; 运行的按键和设置的按钮 第二步 点击设置的按钮&#xff0c;选择最后一个&#xff0c;重启就行 结果 舒服&#xff01; s…

双通道音频功率放大电路D2822M兼容TDA2822,全封装输出功率0.11W,用于音频产品

在某客户的便携式音频产品中&#xff0c;客户想在确保其产品的性能的前提下&#xff0c;为产品方案寻找一颗国产备份料。客户产品之前使用的是TDA2822&#xff0c;在了解客户的电路设计以及该产品的电气特性后&#xff0c;给客户寻找了一款可兼容相同电路设计使用的国产厂牌芯谷…

2010年国赛高教杯数学建模C题输油管的布置解题全过程文档及程序

2010年国赛高教杯数学建模 C题 输油管的布置 某油田计划在铁路线一侧建造两家炼油厂&#xff0c;同时在铁路线上增建一个车站&#xff0c;用来运送成品油。由于这种模式具有一定的普遍性&#xff0c;油田设计院希望建立管线建设费用最省的一般数学模型与方法。   1. 针对两炼…

Python零基础01——Python的由来,Python的特点、优缺点有哪些?

文章目录 Python的由来Python的特点Python的优缺点什么是编译器 Python的由来 1989年圣诞节期间&#xff0c;在阿姆斯特丹为打发圣诞节的无趣&#xff0c;决定开发一款新的脚本解释语言&#xff0c;作为ABC语言的一种继承&#xff0c;然后他就这么做了&#xff0c;并实现了&am…

Python中的help()函数:追踪错误并提供解决方案

引言 在Python编程中&#xff0c;help()函数是一个非常有用的内置工具&#xff0c;它能够提供关于模块、关键字、属性和方法等的详细信息。对于初学者来说&#xff0c;help()函数可以帮助他们理解代码的工作原理&#xff0c;快速查找文档&#xff0c;以及解决编程过程中遇到的…

<Linux> 线程安全

目录 文章目录 一、Linux线程互斥 1. 进程线程间的互斥相关背景概念 2. 互斥量mutex 3. 互斥量接口 初始化互斥量 动静态分配 销毁互斥量 互斥量加锁 互斥量解锁 4. 互斥量实现原理 5. 简单封装互斥量 二、可重入与线程安全 1. 概念 1.1 可重入 1.2 线程安全 2. 常见的线程不…

LLama 3.2 1B 和 3B:体型虽小,但威力强大!

MetaAI 刚刚推出了 Llama-3.2&#xff0c;这是一套新的模型&#xff0c;其中包括两个令人印象深刻的轻量级大型语言模型 (LLM)&#xff0c;分别具有 10 亿 (1B) 和 30 亿 (3B) 个参数&#xff0c;以及更大的视觉语言模型 (VLM)&#xff0c;分别具有 11B 和 90B 个参数。 取决于…

IT监控平台可视化:多维度展示助力运维效率提升

在信息化时代&#xff0c;IT设备的稳定性与业务的连续性紧密相连&#xff0c;任何细微的故障都可能给企业带来巨大的损失。因此&#xff0c;IT运维团队面临着前所未有的挑战&#xff0c;他们需要迅速、准确地识别和解决问题&#xff0c;以确保业务的平稳运行。而IT监控平台的可…

Django学习- ORM基础操作_创建数据

ORM操作&#xff1a; 管理器对象&#xff1a; 创建数据&#xff1a; Django shell 想要操作模型对象&#xff0c;首先我们需要把它引进Django shell中 >>> from bookstore.models import Book >>> b1 Book.objects.create(titleAI, pub清华大学出版社, pr…

Java | Leetcode Java题解之第478题在圆内随机生成点

题目&#xff1a; 题解&#xff1a; class Solution {Random random;double xc, yc, r;public Solution(double radius, double x_center, double y_center) {random new Random();xc x_center;yc y_center;r radius;}public double[] randPoint() {double u random.next…

基于机器学习与深度学习的贷款批准预测

1.项目背景 该数据集源自Kaggle的“Playground Series - Season 4, Episode 10”竞赛&#xff0c;是通过在贷款批准预测数据集上训练的深度学习模型生成的数据&#xff0c;旨在使用借款人信息预测贷款批准结果&#xff0c;它通过模拟真实贷款审批场景&#xff0c;帮助金融机构…

【Linux系统编程】环境基础开发工具使用

目录 1、Linux软件包管理器yum 1.1 什么是软件包 1.2 安装软件 1.3 查看软件包 1.4 卸载软件 2、Linux编辑器-vim 2.1 vim的概念 2.2 vim的基本操作 2.3 vim的配置 3、Linux编译器-gcc/g 3.1 gcc编译的过程​编辑​编辑​编辑 3.2 详解链接 动态链接 静态链接 4…

深度解析LMS(Least Mean Squares)算法

目录 一、引言二、LMS算法简介三、LMS算法的工作原理四、LMS算法的特点五、LMS算法的应用场景六、LMS算法的局限性七、总结八、进一步探讨 一、引言 自适应滤波器是一种动态调整其参数以适应变化环境的信号处理工具&#xff0c;广泛应用于噪声消除、信道均衡和系统识别等领域。…