操作符详解(C 语言)

news2024/11/22 8:59:38

目录

  • 一、操作符的分类
  • 二、算数操作符
    • 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;
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2018 年 NLP 的 10 个令人兴奋的想法

一、说明 这篇文章收集了有影响力的 10 个想法&#xff0c;我们将来可能会看到更多。 对于每个想法&#xff0c;我们将重点介绍 1-2 篇执行良好的论文。为了保持列表简洁&#xff0c;这里没有涵盖所有相关工作。该列表必然是主观的&#xff0c;涵盖主要与迁移学习和泛化相关的想…

老照片修复工作流教程:用 ComfyUI 轻松还原历史记忆

你是否有过这样的遗憾&#xff1f; 那些珍贵的老照片因为时间的流逝&#xff0c;早已失去了当年的色彩&#xff0c;变得模糊、褪色&#xff0c;甚至破损&#xff1f; 今天带你了解如何使用 ComfyUI 的老照片修复工作流&#xff0c;通过简单的几步操作&#xff0c;在短短十几秒…

三亚旅游微信小程序的设计与实现

详细视频演示 请联系我获取更详细的演示视频 项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不…

【深度学习代码调试1】环境配置篇(上) -- 安装PyTorch(安利方法:移除所有国内源,使用默认源)

【深度学习代码调试1】环境配置篇 -- 安装TensorFlow和PyTorch 写在最前面1. 创建新的Conda环境2. 安装PyTorch及相关库&#xff08;可以直接跳到2.3安装方法&#xff09;2.1 检查CUDA版本2.2 解决安装过程中常见问题2.2.1 超时问题&#xff08;这个不是最终解决方案&#xff0…

AUTOSAR_EXP_ARAComAPI的5章笔记(13)

☞返回总目录 5.4.7 事件&#xff08;Events&#xff09; 在骨架侧&#xff0c;服务实现负责通知事件的发生。如 5.4.2 RadarService Skeleton Class 所示&#xff0c;骨架为每个事件提供一个事件包装类的成员。骨架的事件包装类与代理的事件包装类看起来明显不同。 在骨架端…

论文阅读:On determining the hinterlands of China‘s foreign trade container ports

集装箱港口腹地的边界线&#xff0c;只要存在&#xff0c;就可以作为未来港口发展和基础设施规划的参考点。在早期划定中国港口腹地的努力中&#xff0c;要么考虑的港口数量有限&#xff0c;要么仅根据港口总吞吐量划定腹地。因此&#xff0c;这些研究都没有让我们清楚地了解共…

快速理解AUTOSAR CP的软件架构层次以及各层的作用

在 AUTOSAR CP 的架构中&#xff0c;软件分为 应用层 (App)、运行时环境 (RTE) 和 基础软件层 (BSW) 三个主要层级。下面是每一层的主要功能与简单的代码示例来展示它们之间的关系。 1. 概述 应用层 (App)&#xff1a;包含应用程序代码&#xff0c;主要实现业务逻辑。应用层通…

DeepFM模型代码详解

直到看到这篇文章&#xff0c;我才搞明白类别特征怎么做lookup的&#xff0c;也看明白了代码逻辑。如果你看完没懂&#xff0c;私信留下wx&#xff0c;给你讲懂。 1、Deepfm 的原理&#xff0c;DeepFM 是一个模型还是代表了一类模型&#xff0c;DeepFM 对 FM 做了什么样的改进…

【时时三省】(C语言基础)函数介绍strcat

山不在高&#xff0c;有仙则名。水不在深&#xff0c;有龙则灵。 ----CSDN 时时三省 strcat 字符串追加 示例&#xff1a; 比如我要把world加到hello后面去 就可以用这个 还有一种方法是这样 这两个代码的意思是一样的 只是写法不一样 写的时候要注意这些 •源字符串必须…

DAB-DETR: DYNAMIC ANCHOR BOXES ARE BETTER QUERIES FOR DETR论文笔记

原文链接 [2201.12329] DAB-DETR: Dynamic Anchor Boxes are Better Queries for DETR (arxiv.org)https://arxiv.org/abs/2201.12329 原文笔记 在本文中&#xff0c;我们提出了一种新的查询公式&#xff0c;使用动态锚框进行DETR (DEtection TRansformer)&#xff0c;并对查…

探索人工智能在数学教育上的应用——使用大规模语言模型解决数学问题的潜力和挑战

概述 论文地址&#xff1a;https://arxiv.org/abs/2402.00157 数学推理是人类智能的重要组成部分&#xff0c;人工智能界不断寻求应对数学挑战的方法&#xff0c;而在这一过程中&#xff0c;人工智能的能力需要进一步提高。从文本理解到图像解读&#xff0c;从表格分析到符号操…

大数据|MapReduce编程原理与应用

在大数据时代的浪潮中&#xff0c;MapReduce作为一种高效处理海量数据的编程模型&#xff0c;自其诞生以来便成为了数据处理领域的基石。本文旨在深入探讨MapReduce的基本原理、典型应用以及其在未来技术发展趋势中的展望&#xff0c;帮助读者更好地理解并应用这一关键技术。 一…

数制转换及交换机

数制转换 非位置化数字系统&#xff1a;罗马数字 位置化数字系统&#xff1a;二进制&#xff0c;八进制&#xff0c;十进制&#xff0c;十六进制 十进制数&#xff1a; 符号&#xff1a;2 2 2位置&#xff1a;2 1 0位权&#xff1a;该数字的真实大小 该位置上的数基数的位置…

如何设计开发RTSP直播播放器?

技术背景 我们在对接RTSP直播播放器相关技术诉求的时候&#xff0c;好多开发者&#xff0c;除了选用成熟的RTSP播放器外&#xff0c;还想知其然知其所以然&#xff0c;对RTSP播放器的整体开发有个基础的了解&#xff0c;方便方案之作和技术延伸。本文抛砖引玉&#xff0c;做个…

所有程序员的白嫖圣地-github

内容汇总 认识github页面如何从github下载资源git配置如何通过github管理工程代码 有人问github怎么用&#xff0c;几分钟了解这个每个程序员都在用的白嫖圣地。 打开github主界面&#xff0c;映入眼帘的是Home面板&#xff0c;它的作用是显示我们关注的人、点赞的项目等更新…

一款非常有用且高效的国产的Linux运维面板:1Panel介绍

1Panel介绍 一、1panel介绍二、1panel的安装1、不同系统安装2、安装日志3、访问地址 三、1panel的卸载1、停止服务2、卸载服务3、清理残留文件4、清除日志文件5、验证卸载是否成功 四、1panel的功能介绍1、服务器资源使用情况快速监控2、文件管理器简单易用3、创建和管理网站轻…

【Linux操作系统】进程等待

目录 一、什么是进程等待&#xff1f;二、为什么要进行等待&#xff1f;三、进程等待方法1.wait函数2.waitpid3.status阻塞等待和非阻塞等待&#xff08;轮询等待&#xff09;1.阻塞等待2.非阻塞等待 四、代码举例 一、什么是进程等待&#xff1f; "进程等待"是指一…

基于springboot摄影跟拍预定管理系统

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 系统展示 【2024最新】基于JavaSpringBootVueMySQL的&#xff0c;前后端分离。 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;…

Finops成本优化企业实践-可规划篇

引言&#xff1a;本篇假设我们要在云上新增一个应用&#xff0c;讨论其在单体、failover、DR、集群模式下的成本规划。 假设该应用base on Linux&#xff0c;硬件要求是8cores、64G mem的云主机&#xff0c;并搭配500g内存&#xff0c;至少部署在一台云主机上。我们有开发、测…

Java项目: 基于SpringBoot+mysql+maven+vue林业产品推荐系统(含源码+数据库+毕业论文)

一、项目简介 本项目是一套基于SpringBootmybatismavenvue林业产品推荐系统 包含&#xff1a;项目源码、数据库脚本等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xff0c;eclipse或者idea 确保可以运行&#xff01; 该系统功能完善、界面美观、操…