玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!


文章目录

  • 前言
  • 一、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+b a和b的和
a-b a和b的差
a%b a除以b的余数
a/b a除以b的商
a*b a和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);

【如下图】
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片
最后将二进制的数字转化成十进制,答案为10.
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片
如果我们移动的是负数呢?那么这时候又该如何移动呢?
int c = -1; int d = c << 1; printf("%d\n", d);

负数也是同样的原理,先把数字转化成二进制,然后计算出补码,计算机用补码进行移动,移动完成后,再转化成补码打印出来。
下面我们再画一个图给大家演示一下。
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片
打印出来的结果:
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

2.1.2右移操作符
既然左移是二进制移动的,那么同理,右移也是二进制移动的,这里不再细讲,大家可以按照同样的思路来验证一下下面的右移结果
int a = 5; int b = a >> 1; //逻辑右移,右边丢弃,左边补0 //算术右移,右边丢弃,左边补原符号位,原来是正数就正数,负数就负数,绝大多数编译器采用,比如vs2013 printf("%d\n", b);

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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的补码。
【如图】
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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; } //000000000000000000000000000000113的二进制序列的补码 //11111111111111111111111111111110-2的补码 //11111111111111111111111111111111只要有1,就为1,这个为计算完之后内存的补码,回到第一条注释 //11111111111111111111111111111111计算机内存的补码,1开头为负数,需计算原反补码 //11111111111111111111111111111110-1 //10000000000000000000000000000001取反

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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 //000000000000000000000000000000113的二进制序列的补码 //11111111111111111111111111111110-2的补码 //11111111111111111111111111111101 return 0; }

这里不再详解,只需遵循上面所说的技巧与方法便可求解。
玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

3.1.3使用场景
1、不创建第三个变量,实现两个数的交换。
在这个时候我们便可以使用到上面的操作符了
#include 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; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片
这里大家有兴趣的话,可以参照上面的方法,讲两个整数转化成二进制进行计算验证一下。
2、求一个整数存储在内存中的二进制中1的个数。
#include 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; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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*, //取地址是由低到高取,也就是取首地址 intb = *p; //右值描述的是空间的内容 //*p赋给b,并通过*p的值找到a,把a的值赋给b,当这里写*p的时候,找到的是a里面的值,用的是a空间的值,也就是找到10 //这里因为用的是a的空间,所以下面是赋值给a *p=20; //*为解引用操作符,*p就是同p里面存的地址,找到它所指的对象 printf("%d\n", a); return 0; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

5.3操作符~
~表示按位取反,这个也是需要转换成二进制补码的
int main() { int a = 0; //0000000000000000000000000000000 int b = ~a; //按位取反 //0000000000000000000000000000000 //1111111111111111111111111111111 //1111111111111111111111111111110 //1000000000000000000000000000001 //-1 printf("%d\n", b); return 0 ; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

9.下标引用、函数调用和结构成员 9.1下标引用
操作数:一个数组名 + 一个索引值
int main() { int arr[] = { 1, 2, 3, 4, 5 }; int i = 0; printf("%d\n", arr[4]); //通过下标,寻找arr数组的元素 return 0; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

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

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

9.3访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
#include #include //声明类型 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() { structBook b = { "C语言程序设计",55.5f,"C20190203"}; print2(&b); //print1(b); //b.price = 100.0f; 字符串拷贝-strcpy-库函数 //strcpy(b.name, "数据结构"); //print1(b); //结构成员访问操作符 //.左边是结构体变量,右边是成员们结构变量.成员名 //->结构体指针->成员名 //(*结构体指针).成员名 return 0; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

10.表达式求值 10.1隐式类型转换
整型提升
整型提升是C程序设计语言中的一项规定:在表达式计算时,各种整形首先要提升为int类型,如果int类型不足以表示则要提升为unsigned int类型;然后执行表达式的运算。
举个例子
int main() { char a = 3; //a是一个字节,只有8bit位 //0000000000000000000000000000000000113的原码反码补码 //00000011 --a里面存的东西,也就是将最后面的8位截取下来 char b = 127; //00000000000000000000000000001111111127的原码反码补码 //01111111--b里面的东西,也就是将最后面的8位截取下来 //a和b自身都是char类型,自身大小都是一个字节,所以这里计算的时候要进行整型提升 // //00000000000000000000000000000011 //00000000000000000000000001111111 //00000000000000000000000010000010 char c = a + b; //放回c中的时候,同样截断,10000010 //高位是1,为负数 //111111111111111111111111111110000010 //100000000000000000000000000001111110 printf("%d\n", c); return 0; }

玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

【整型提升的意义】
表达式的整型运算要在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语言系列|【C语言】玩转操作符——操作符的那些东西!
文章图片

最后 最近学习C语言有点累了,时常感觉前路迷惘,害怕自己不是这块料却非要往这里凑。本文撰述了将近6个小时。参考了谭浩强《C语言设计》(第五版),以及网上的部分资料,加之自己在学习听课时的笔记,拼拼凑凑而成,尽管如此,却也花费了我很多心思。 【玩转C语言系列|【C语言】玩转操作符——操作符的那些东西!】希望能对看到的大家有所帮助!

    推荐阅读