栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言P5操作符详详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言P5操作符详详解

操作符详解

文章目录
  • 操作符详解
    • 算术操作符
    • 移位操作符
      • 1.左移操作符
      • 2.右移操作符
    • 位操作符
      • 例题
    • 赋值操作符
    • 单目操作符
    • 关系操作符
    • 逻辑操作符
    • 条件操作符
    • 逗号表达式
    • 下标引用操作符,函数调用,结构体成员访问
    • 表达式求值
      • 1.隐式类型转换
      • 2.整形提升
      • 3.算术转换(大小>=int)

算术操作符

算术操作符有+ - * / %
其中加减乘都是双目操作符也即是有两个操作数,除法要注意,两边都是整数就执行整数除法,有一个为浮点数就执行浮点数除法,取模的操作数只能是整数

移位操作符

左移<<
右移 >>
移位只能移动正整数位,不能移动负数位与浮点数位
浮点数不可以进行移位(因为浮点数在内存中的存储方式和整数不同)

1.左移操作符

左移移动的是数字的二进制位
高位丢弃,低位补零。
左移相当于给数字乘上2
还要注意整数在内存中存储的是补码 所以移位操作移动的也是补码。

2.右移操作符

右移操作符涉及到符号位。
右边丢弃,左边分为逻辑右移和算术右移
逻辑右移比较简单,不管符号位,高位直接补零(虽说有这个概念,但是我没有见过有编译器使用逻辑右移)
算术右移
左边补符号位,右边丢弃。(近乎所有的编译器都是用算术右移)

位操作符

位操作符有:&按位与
| 按位或
^按位异或

1.按位与,对两个数的二进制位进行比较,如果同为1,结果则为1,有一个为0,结果就为0.任何数&上-1都为本身
看个例子:(5和1按位与)
00000000 00000000 00000000 00000101(5的补码(正数的原码反码补码相同))
00000000 00000000 00000000 00000001(1的补码)
00000000 00000000 00000000 00000001(结果的补码)

2.按位或,对两个数的二进制进行比较,同为0,结果才为0.有一个为1,结果就为1.任何数|上0都为本身
看个例子:5按位与上8
00000000 00000000 00000000 00000101
00000000 00000000 00000000 00001000
00000000 00000000 00000000 00001101(结果的补码)

按位异或,对二进制位进行操作,两个数二进制位相同位为0,不同为1.所以两个相同数异或结果为0 任何数异或上0都是本身
例子:
00000000 00000000 00000000 00000101
00000000 00000000 00000000 00000111(7的补码)
00000000 00000000 00000000 00000010(结尾为2)

既然了解了位操作符我们来看一下,如何不创建变量交换两个变量的值

#include
int main()
{
    int a = 10;
    int b = 8;
    //数学方法
    a=a+b;
    b=a-b;
    a=a-b;
    //位操作方法
    a=a^b;
    b=a^b;
    a=a^b;
    return 0;
}

讲一下位操作的思路,要注意按位异或符合交换律,a的值位a^b;
a ^ b ^ b = a;
a ^ b ^ a = b;
如此便实现了两个变量的交换,但是日常写代码还是建议用第三个变量进行交换效率高,可读性高。

例题

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

//方法1
#include

int main()
{
    int i = 0;
    int n = 23;
    int count = 0;
    for(i=0;i<32;i++)
    {
        if((n>>i)&1)
        {
            count++;
        }
    }
   printf("%d",count);
    return 0;
}

那么这个方法有没有缺陷呢?当然有,那就是不管这个数字是什么我们都需要循环32次。

下面介绍一个简单方法。

//方法二
#include

int main()
{
    int count = 0;
    int n = 23;
    while(n)
    {
        n=n&(n-1);
        count++;
    }
    printf("%d",count );
    return 0;
}

这样是不就喵喵了很多啊,数字二进制有多少个1,就循环多少次。但是其实很难想到。
使用位操作将二进制位的某一位置1

int a =13;
a=a | (1<<4);//将a的第四位置为1;
//将二进制位的某一位置为0也是同样的道理
a=a & (~(1<<4))//因为任何数与上1都是不变的,所以我
只需要将要置零的那一位的二进制位置为0,其余的二进制位全是1即可
赋值操作符

赋值操作符就简单了很多,
当然还有连续赋值操作符

int a;
a=10;//这就是赋值操作符,给以及创建好的变量附上一个数值
int b =20;
int c = 30;
a=b=c+10;//这就是连续赋值,因为赋值符号是右结合性所以我们从右向左计算

复合赋值符就是

+=
*=
/=
%=
>>=
<<=
....

很简单,a+=b相当于a=a+b;

单目操作符
! //逻辑反操作
- //负值
+ //正值
& //取地址
sizeof //操作数的类型长度(以字节为单位)
~ //对一个数的二进制按位取反
-- //前置、后置--
++ //前置、后置++
* //间接访问操作符(解引用操作符)
(类型) //强制类型转换

单目操作符是指只有一个操作数。
1.逻辑反,就是将真变为假,假变为真。
2.重点说sizeof,我们可以看到sizeof是操作符而并非函数,这里要区别与strlen,strlen是库函数。

int a =10;
sizeof(a);
sizeof a ;//这个计算变量的大小不加括号是可以运行的,
这也说明了sizeof不是函数,因为其不需要函数访问操作符。
sizeof(int);
sizeof int ;//在我们的编译器中这个是跑不过的

另外还有一点,sizeof是不会压栈的这也说明了它不是函数。

关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”

注意关系操作符‘==’只能比较整数,浮点数会有精度损失,字符串使用strcmp库函数
"abc" == "abcdef"这段代码比较的是两个字符串的地址
关系操作符还是比较简单的哈,这里需要着重看的是“ == “和”=“千万不要看错了!!!

逻辑操作符
&& 逻辑与
|| 逻辑或

逻辑操作符要注意短路求值的问题,
逻辑与,如果第一个表达式为假,那么第二个表达式就不会进行判断了,结果为假。
逻辑或,如果第一个表达式为真,那么第二个表达式也就不会判断了,结果为真。
看一下例题:

#include 
int main()
{
int i = 0,a=0,b=2,c =3,d=4;
i = a++ && ++b && d++;
//i = a++||++b||d++;
printf("a = %dn b = %dn c = %dnd = %dn", a, b, c, d);
return 0;
}
//程序输出的结果是什么?

第一个答案为1 2 3 4;
第二个答案为1 3 3 4;
你做对了吗?

条件操作符

(expr1)?(expr2):(expr3);
该表达式的意思是,表达式1成立则返回表达式2的值,表达式1不成立则返回表达式3的值。

逗号表达式

逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
exp1, exp2, exp3, …expN

深入理解一下,既然,逗号表达式内的表达式都会执行,那么我么是不是可以用它来完成do while的任务呢

int a = 0;
do 
{
    printf("%d ",a);
    a++;
}while(a<=100);

while(printf("%d ",a),a++,a<=100);

是不是很有趣呢?

下标引用操作符,函数调用,结构体成员访问

1.下标引用操作符
[ ]
用来操作数组元素的操作符
操作数:一个数组名 + 一个索引值

int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
这时我们想一想,加号也是操作符,那加号的操作数可以交换,那数组的可不可以呢?
9[arr] = 10;//答案是可以的!!
//编译器并不会报错,只能用来引用不可以定义数组哦。

2.()函数调用操作符,他的操作数是数组名和(expr1,expr2),最少一个操作数,即数组名,多可以很多个。剩余的操作数就是传递给函数的参数。
3. 访问一个结构的成员

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

下面我们来看一段代码了解这两个操作符

#include
typedef struct stu
{
    char name[10];
    int ID;
}stu;

void print(stu *ps)
{
    printf("name = %sn ID = %d",ps->name,ps->ID);//此处为->操作符的使用
    //printf("name = %sn ID = %d",(*ps).name,(*ps).ID)
    //注释掉的写法是一样的效果哦
    
}

int main()
{
    struct stu s1 ={ "zhang",2209};
    printf("name = %sn ID = %d",s1.name,s1.ID);//这种就是.操作符的使用。

    print(&s1);

}

这里再使用结构体指针的时候我们还有一个问题,就是在结构体传参的时候,尽量使用地址传递,因为在结构体传参时参数所占内存过大会对栈帧的占用很大,增加了系统开销。
具体原因可以看
这个博客

表达式求值

关于表达式求值我们这里要谈到三个概念,操作符的优先级和结合性以及是否控制求值顺序。

1.隐式类型转换

C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升
简单来说也就是小于int的类型在进行表达式求值的时候会被转换为int类型进行计算

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

看一段代码来理解

#include

int main()
{
	char a = 127;
	char b = 5;
	char c = a + b;
	printf("%d", c);//输出为-124

	return 0;
}

下面来解释一下原理,char类型的a和b在进行加法的时候转换为int类型的这是会发生整型提升。

2.整形提升

整形提升有两种,有符号数,高位补上符号位
无符号数,高位补0,要注意整型提升后这个数本身时不变的。
所以a+b=132其原码为

0000 0000 0000 0000 0000 0000 1000 0100

将该二进制码存入char c时发生了截断,将1000 0100存入了c这时候要注意了最高位的1被看作是符号位,而系统自动认为该二进制码为补码。所以输出的时候就要转换为原码

1000 0100
1111 1100//原码,对应-124

下面这段代码可以更好的理解什么时候需要整型提升

char a=0;
sizeof(a);//1
sizeof(-a);//4
sizeof(+a);//4

不管是,计算,比较,%d打印,只要在表达式中使用时就会发生整型提升。

3.算术转换(大小>=int)

说完了小于int的类型下面来看一下,大于int的类型
例如:int + float 这种类型的运算时候就会将int转换为float在进行运算。
同理int + double也是如此。

关于表达式的求值顺序,相邻操作符优先级相同时就看操作符的求值顺序来决定计算顺序。下面这个表了解一下即可
简单举一个例子:
int c = 10 + 20 +30;这就是操作符优先级相同的时候,我们就要看+号的结合性来决定从左向右计算还是从右向左计算。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p1sNoAyO-1652183416398)(操作符优先级表.jpg)]

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/873480.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号