当前位置:网站首页>【C语言】操作符详解
【C语言】操作符详解
2022-07-31 04:36:00 【阿亮joy.】
目录
操作符分类
- 算术操作符 移位操作符 位操作符
- 赋值操作符 单目操作符 关系操作符
- 逻辑操作符 条件操作符 逗号表达式
- 下标引用、函数调用和结构成员
算数操作符
+ - * / %
对于+、-和*这三个算数操作符没什么好讲的,和小学数学里的一样。对于/和%这两个算数操作符,我们需要注意一下几个点:
1. 除了 % 操作符之外,其他的几个操作符可以作用于整数和浮点数。
2. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。
3. % 操作符的两个操作数必须为整数。返回的是整除之后的余数。4. 除法想得到小数的结果,必须保证除数和被除数中至少有一个小数(浮点数)。
5.0 / 2 = 2.5 //浮点数除法
5 / 2 = 2 //整数除法
移位操作符
- << 左移操作符
- >> 右移操作符
注:移位操作符的操作数只能是整数,移动的是存储在内存中的补码。
学习左移操作符之前,我们需要知道数据的二进制的表现形式。二进制有三种表现形式,分别是原码,反码和补码。原码、反码和补码都是有符号位和数值位组成。计算机存储的是数据的原码,打印数据的时候需要数据的补码转换成原码再打印输出 。注意:正整数的原码、反码和补码是相同的,而原码就是直接将十进制的数字转换成二进制就行了。而负整数的反码是在原码的基础上,除了符号位,其他位按位取反,而反码再加上一就等到了负整数的补码了。
例子:
1.左移操作符
移位规则:
左边抛弃、右边补0
代码示例:
#include <stdio.h>
int main()
{
int num = 10;
num << 1;
printf("num = %d\n", num);
return 0;
}
输出结果:
代码示例:
#include <stdio.h>
int main()
{
int num = 10;
num = num << 1; //可简化为num <<= 1;
printf("num = %d\n", num);
return 0;
}
输出结果:
注意:将一个数左移n位得到的结果等于原来的数乘上2的n次方得到的结果。 比如:将10左移3位,得到的结果就是80。
2.右移操作符
移位规则:
首先右移运算分两种:
1. 逻辑移位
左边用0填充,右边丢弃
2. 算术移位
左边用原该值的符号位填充,右边丢弃
注意:C语言并未规定,右移操作符是逻辑右移还是算数右移,这取决于编译器,绝大多数的编译器是算数右移。
代码示例:
#include <stdio.h>
int main()
{
int num = 16;
num >>= 1;
printf("num = %d\n", num);
return 0;
}
输出结果:
如果将num右移n位,且num和2的n次方成倍数关系,那么num右移n位得到结果就是num除以2的n次方得到的结果。但是如果不成倍数关系的话,就要写成补码形式右移n为,再转成原码的形式计算出该数的大小。
例子:
注意:对于移位运算符,不要移动负数位,这个是标准未定义的。
int num = 10;
num>>-1;//error
位操作符
& //按位与
| //按位或
^ //按位异或
注:它们的操作数必须是整数。
真值表
1.位操作符的特点
&按位与: 两个数的补码对应位都为1,得到的结果才为1,否则结果就为0。
| 按位或: 两个数的补码对应位有一个为1,得到的结果就为1,否则结果就位0。
^按位异或:两个数的补码对应位不同,得到的结果为1,否则结果就为0。特例:两个
相同的数按位异或得到的结果为0,一个数与0按位异或得到结果为它本身。
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = a & b;
printf("%d\n", c);
return 0;
}
输出结果:
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = a | b;
printf("%d\n", c);
return 0;
}
输出结果:
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int b = 20;
int c = a | b;
printf("%d\n", c);
return 0;
}
输出结果:
2.一道巧妙的笔试题
不能创建临时变量(第三个变量),实现两个数的交换。
代码示例:
#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和b的值非常大,a+b相加后超出整型的取值范围,将会出现错误。不过还有一种方法能够实现两个数的交换,而已不会出现上述的问题。
代码示例:
#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;
}
输出结果:
3.三道重要的练习题
编写代码实现:求一个整数存储在内存中的二进制中1的个数。
代码示例:
#include <stdio.h>
int main()
{
//本题相当于进制转换
int n = 0;
int sum = 0;
scanf("%d", &n);
while (n)
{
//15 7 3 1
if (n % 2 == 1)
{
sum++;
}
n /= 2;
}
printf("%d\n", sum);
return 0;
}
注意:上面的代码只能求正数的补码有多少个一,不能求负数的补码有多少个一。但是这种方法的思路非常重要,可以用于进制转换。
代码示例:
#include <stdio.h>
int main()
{
int count = 32;
int n = 0;
int sum = 0;
scanf("%d", &n);
//一个整型变量占32个比特位
while (count)
{
if (n & 1 == 1)
{
sum++;
}
n >>= 1;
count--;
}
printf("%d\n", sum);
return 0;
}
输出结果:
代码示例:
#include <stdio.h>
int main()
{
int num = 0;
scanf("%d", &num);
int count = 0;//计数
while (num)
{
count++;
num = num & (num - 1);
}
printf("%d\n", count);
return 0;
}
输出结果:
求两个数二进制中不同位的个数
思路: | |
1. 先将m和n进行按位异或,此时m和n相同的二进制比特位清零,不同的二进制比特位为1 | |
2. 统计异或完成后结果的二进制比特位中有多少个1即可 |
代码示例:
//求两个数二进制中不同位的个数
#include <stdio.h>
int main()
{
int m = 0;
int n = 0;
int sum = 0;//计数器
scanf("%d%d", &m, &n);
int ret = m ^ n;//借助异或来求,相同为0,不相同为1
int count = 32;
while (count)
{
if (ret & 1 == 1)//符合条件的说明ret的最低位为1,也就是说明m和n有一个不同位
{
sum++;
}
count--;
ret >>= 1;//ret右移一位
}
printf("%d", sum);
return 0;
}
//求两个数二进制中不同位的个数
#include <stdio.h>
int main()
{
int m = 0;
int n = 0;
int count = 0;//计数器
scanf("%d%d", &m, &n);
int tmp = m ^ n;//借助异或来求,相同为0,不相同为1
while (tmp)
{
tmp = tmp & (tmp - 1);
count++;
}
printf("%d", count);
return 0;
}
获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列。
代码示例:
#include <stdio.h>
int main()
{
//00000000000000000000000000001010 ->10的补码
//偶数位数字0000000000000011
//奇数位数字0000000000000000
int i = 0;
int n = 0;
scanf("%d", &n);
for (i = 31; i >= 0; i -= 2)
{
//打印偶数位上的数字
printf("%d", (n >> (i)) & 1);
}
printf("\n");
for (i = 30; i >= 0; i -= 2)
{
//打印奇数位上的数字
printf("%d", (n >> (i)) & 1);
}
printf("\n");
return 0;
}
输出结果:
赋值操作符
赋值操作符是一个很棒的操作符,它可以让你得到一个你之前不满意的值。也就是你可以给变量重新赋值。例如:
int weight = 120;//体重
weight = 89;//不满意就赋值
double salary = 10000.0;
salary = 20000.0;//使用赋值操作符赋值。
赋值操作符还可以连续使用,比如:
int a = 10;
int x = 0;
int y = 20;
a = x = y+1;//连续赋值
上面的代码等价于下面的代码,但是下面代码的写法风格更加通俗易懂,也易于调试。
int a = 10;
int x = 0;
int y = 20;
x = y+1;
a = x;
符合操作符
+= -= *= /= %= >>=
<<= &= |= ^=
这些运算符都可以写成复合的效果,比如:
int x = 10;
x = x+10;
<==> x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁。
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型大小(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
!逻辑反操作,能让真变成假,假变成真。
代码示例:
#include <stdio.h>
int main()
{
int flag = 0;
if (!flag)
{
printf("hello\n");
}
return 0;
}
+和-这两个操作符,没什么好讲的了,就是小学的数学知识。在这就不再给大家赘述了。
&取地址操作符和*解引用操作符通常成对出现,&取地址操作符能够取出变量的地址,*解引用操作符对地址(指针)可以访问该指针的内容。同时,需要注意的是&和*是互逆运算。
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int* pa = &a;
printf("%d\n", *&a);
*pa = 20;
printf("%d\n", a);
return 0;
}
sizeof()也是一个操作符,并不是函数。其计算的是操作数类型的大小,单位是字节。
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int arr[10] = { 0 };
printf("%d\n", sizeof(a));
printf("%d\n", sizeof a);
printf("%d\n", sizeof(int));
//printf("%d\n", sizeof int); //error
printf("%d\n", sizeof(arr));
return 0;
}
输出结果:
接下来,我们再来看一下下面的代码的输出结果会是什么?
代码示例:
#include <stdio.h>
int main()
{
short s = 10;
int a = 2;
printf("%d\n", sizeof(s = a + 5));
printf("%d\n", s);
}
输出结果:
sizeof()和数组
代码示例:
#include <stdio.h>
void test1(int arr[])
{
printf("%d\n", sizeof(arr));
}
void test2(char ch[])
{
printf("%d\n", sizeof(ch));
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));
printf("%d\n", sizeof(ch));
test1(arr);
test2(ch);
return 0;
}
输出结果:
~操作符,对数据的补码进行按位取反。注意:符号位也要按位取反。
~操作符很容易理解,但是很多人都不经常用。现在就给出一段代码介绍~操作符的用途。
代码示例:
#include <stdio.h>
int main()
{
int a = 10;
int n = 0;
scanf("%d", &n);
//把a的第n位置为1
a = a | (1 << (n-1));
//00000000000000000000000000001010
//1<<2;
//00000000000000000000000000000100
//==>00000000000000000000000000001110
printf("a=%d\n", a);
//把a的第n位置为0
a = a & ~(1 << (n - 1));
//00000000000000000000000000001110
//11111111111111111111111111111011
//==>00000000000000000000000000001010
printf("a=%d\n", a);
return 0;
}
输出结果:
前置++ 先加加,后使用
前置-- 先减减,后使用
后置++ 先使用,后加加
后置-- 先使用,后减减
代码示例:
#include <stdio.h>
int main()
{
int a = 4;
int b = ++a;//前置++,先++,后使用
// <==> a=a+1;b=a;
//int b = a++;//后置++,先使用,再++
// <==> b=a;a=a+1;
//int b = --a;
// <==> a=a-1;b=a;
//int b = a--;
// <==> b=a;a=a-1;
printf("%d\n", b);
printf("%d\n", a);
return 0;
}
(类型) 可以将其他类型强制转换成你想要的类型。
代码示例:
#include <stdio.h>
int main()
{
double a = 3.14;
int b = (int)a;
printf("a的整数部分为%d\n", b);
printf("a的小数部分为%.2f\n", a - b);
return 0;
}
输出结果:
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等”
这些关系运算符比较简单,没什么可讲的,但是我们要注意一些运算符使用时候的陷阱。最值得注意的是,在写代码的时候,不要将== 和=写错了,否则会出现bug。
逻辑操作符
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与,区分逻辑或和按位或
1&2----->0
00000000000000000000000000000001
00000000000000000000000000000010
==>00000000000000000000000000000000
1&&2---->1
1|2----->3
00000000000000000000000000000001
00000000000000000000000000000010
==>00000000000000000000000000000011
1||2---->1
逻辑与:全真才是真,一假就为假
逻辑或:一真就为真,全假才是假
逻辑与&&和逻辑或||的特点 :逻辑与的第一个表达式为假,后面的表达式都不再计算;逻辑或的第一个表达式为真,后面的表达式都不再计算。因为这个特点,逻辑与&&和逻辑或||也被称为短路操作符。
代码示例:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
输出结果:
代码示例:
#include <stdio.h>
int main()
{
int i = 0, a = 0, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("a = %d\nb = %d\nc = %d\nd = %d\n", a, b, c, d);
return 0;
}
输出结果:
条件表达式
exp1 ? exp2 : exp3
表达式exp1满足,就计算表达式exp2;
不满足,就计算表达式exp3。
代码示例:
#include <stdio.h>
int main()
{
//找出a和b中的较大值
int a = 10;
int b = 20;
int max = (a > b ? a : b);
printf("max = %d\n", max);
return 0;
}
逗号表达式
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开的多个表达式。
逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。
代码示例:
#include <stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d\n", c);
return 0;
}
下标引用、函数调用和结构成员
1. [ ] 下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
2. ( ) 函数调用操作符
接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
代码示例:
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char* str)
{
printf("%s\n", str);
}
int main()
{
test1(); //使用()作为函数调用操作符。
test2("hello bit.");//使用()为函数调用操作符。
return 0;
}
3. 访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
代码示例:
#include <stdio.h>
struct Book
{
char name[30];
int price;
//书:书名+定价
};
int main()
{
struct Book sb = {"如何让富婆对你欲罢不能", 55};
printf("%s %d\n", sb.name, sb.price);//结构体变量.结构体成员名
struct Book* ps = &sb;
printf("%s %d\n", (*ps).name, (*ps).price);
printf("%s %d\n", ps->name, ps->price);//结构体指针->结构体成员名
return 0;
}
表达式求值
表达式求值的顺序一部分是由操作符的优先级和结合性决定。同样,有些表达式的操作数在求值的过程中可能需要转换为其他类型。
1.隐式类型转换
C的整型算术运算总是至少以普通整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称整型提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转换为int或unsigned int,然后才能送入CPU去执行运算。
代码示例:
#include <stdio.h>
int main()
{
char a = 5;//截断
char b = 126;//截断
char c = a + b;//截断
//00000000000000000000000000000101
//00000101 - a
//00000000000000000000000001111110
//01111110 - b
//整型提升
// a和b的值被提升为普通整型,然后再执行加法运算。
// 加法运算完成之后,结果将被截断,然后再存储于c中。
//00000000000000000000000000000101-a
//00000000000000000000000001111110-b
//00000000000000000000000010000011
//10000011 - c
printf("%d\n", c);
//%d 十进制的方式打印有符号整数
//整型提升
//11111111111111111111111110000011 补码
//11111111111111111111111110000010 反码
//10000000000000000000000001111101 原码
//-125
return 0;
}
如何进行整型提升:
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
代码示例:
#include <stdio.h>
int main()
{
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
printf("a\n");
if (b == 0xb600)
printf("b\n");
if (c == 0xb6000000)
printf("c\n");
return 0;
}
输出结果:
代码示例:
#include <stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
输出结果:
2.算数转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
如果某个操作数的类型在上面这个列表中排名较低,那么首先要转换为另外一个操作数的类型后执行运算。注意:算术转换要合理,要不然会有一些潜在的问题。
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
3.操作符的属性
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级
4.问题表达式
//表达式的求值部分由操作符的优先级决定。
//代码1
a*b + c*d + e*f
注释:代码1在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不能决定第三个*比第一个+早执行。
所以表达式的计算机顺序就可能是:
a*b
c*d
a*b + c*d
e*f
a*b + c*d + e*f
或者
a*b
c*d
e*f
a*b + c*d
a*b + c*d + e*f
因为这些表达式中有可能对相同的变量进行了操作,所以不同的运算顺序就有可能导致不同的结果。
//代码2
c + --c;
注释:同上,操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。
总结:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
以上就是本篇博客的全部内容了,如果大家觉得有收获的话,可以点个三连支持一下!谢谢大家!
结语
每一个优秀的人都有一段沉默的时光,那段时光是付出了很多努力却得不到结果的日子,我们把它叫做扎根。
边栏推荐
- idea工程明明有依赖但是文件就是显示没有,Cannot resolve symbol ‘XXX‘
- 问题1:给你1-10的列表,实现列表输出,单数在左边,双数在右边。
- The idea project obviously has dependencies, but the file is not displayed, Cannot resolve symbol 'XXX'
- MySQL数据库必会的增删查改操作(CRUD)
- Redis uses sorted set to cache latest comments
- exsl文件预览,word文件预览网页方法
- RESTful api interface design specification
- [Paper reading] Mastering the game of Go with deep neural networks and tree search
- ERROR 2003 (HY000) Can‘t connect to MySQL server on ‘localhost3306‘ (10061)解决办法
- Learning DAVID Database (1)
猜你喜欢
The BP neural network
Industry-university-research application to build an open source talent ecosystem | 2022 Open Atom Global Open Source Summit Education Sub-Forum was successfully held
ERROR 2003 (HY000) Can‘t connect to MySQL server on ‘localhost3306‘ (10061)
XSS shooting range (3) prompt to win
HCIP Day 10_BGP Route Summary Experiment
专访 | 阿里巴巴首席技术官程立:云+开源共同形成数字世界的可信基础
C language from entry to such as soil, the data store
WeChat applet uses cloud functions to update and add cloud database nested array elements
Learning DAVID Database (1)
重磅 | 基金会为白金、黄金、白银捐赠人授牌
随机推荐
From scratch, a mirror to the end, a pure system builds a grasscutter (Grasscutter)
ERROR 2003 (HY000) Can‘t connect to MySQL server on ‘localhost3306‘ (10061)解决办法
Fusion Cloud Native, Empowering New Milestones | 2022 Open Atom Global Open Source Summit Cloud Native Sub-Forum Successfully Held
Unity Fighter
高等数学---第九章二重积分
type_traits元编程库学习
Safety 20220709
[Swift] Customize the shortcut that pops up by clicking the APP icon
The BP neural network
【论文阅读】Mastering the game of Go with deep neural networks and tree search
递归实现汉诺塔问题
Smartcom Programming Level 4 - Magic Academy Lesson 6
MySQL to revise the root password
qlib架构
Gaussian distribution and its maximum likelihood estimation
扫雷小游戏——C语言
MySQL模糊查询可以使用INSTR替代LIKE
STM32HAL库修改Hal_Delay为us级延时
How Zotero removes auto-generated tags
C# 实现PLC的定时器