文章目录
- 前言
- 一、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的乘积 |
【说明】
- 由于键盘无×号➗号,用*和/代替
- 两个实数相除的结果是双精度实数,两个整数相除的结果为整数,如7/3的结果值为2,舍去小数部分。
- %操作符要求参加运算的运算对象(即操作数)为整数,结果也为整数。如7/3,结果为1,返回的是整除之后的余数。
- 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除 法。
2.移位操作符
2.1移位操作符
1、<< 左移操作符
2、>> 右移操作符
2.1.1左移操作符
我们思考以下代码,printf输出的是什么?也就是在程序中,它是如何移位的?这是移位操作符的重点!
int a = 5; //4byte-32bit=
int b = a << 1;
printf("%d\n", b);
我们知道程序在计算机中是以二进制的方式运行的,所以在这里进行移位的时候,我们也是以二进制的方式来进行移位。
所以:
- 我们得把a转化为二进制,因为a是int类型,所以是4个字节32个比特位。
- 计算机中,整数的二进制有三种表现形式——原码、反码、补码。
- 对于正整数-原码、反码、补码相同,对于负整数-需要计算
那么问题来了,原码,反码,补码怎么计算?
这个也不难。
首先需要明确的是,原码、反码、补码的第一位为符号位,正数符号位为0,负数为1
- 原码—直接按照数字的正负写出的二进制序列。 比如 -1
原码:000000000000000000000000000000001- 反码—原码的符号位不变,其他位按位取反得到的 所以a的
反码:111111111111111111111111111111110- 补码-反码+1
补码:111111111111111111111111111111111
那么,计算机是如何进行移位的?
要想知道这个问题的答案,我们需先知道数字在计算机的存储形式。
计算机存的是补码,我们打印的是原码
所以,移位在计算机中是以补码的形式移动的。
下面我们回到例子中
int a = 5; //4byte-32bit=
int b = a << 1;
printf("%d\n", b);
【如下图】
最后将二进制的数字转化成十进制,答案为10.
如果我们移动的是负数呢?那么这时候又该如何移动呢?
int c = -1;
int d = c << 1;
printf("%d\n", d);
负数也是同样的原理,先把数字转化成二进制,然后计算出补码,计算机用补码进行移动,移动完成后,再转化成补码打印出来。
下面我们再画一个图给大家演示一下。
打印出来的结果:
2.1.2右移操作符
既然左移是二进制移动的,那么同理,右移也是二进制移动的,这里不再细讲,大家可以按照同样的思路来验证一下下面的右移结果
int a = 5;
int b = a >> 1;
//逻辑右移,右边丢弃,左边补0
//算术右移,右边丢弃,左边补原符号位,原来是正数就正数,负数就负数,绝大多数编译器采用,比如vs2013
printf("%d\n", b);
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的补码。
【如图】
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;
}
//00000000000000000000000000000011 3的二进制序列的补码
//11111111111111111111111111111110 -2的补码
//11111111111111111111111111111111 只要有1,就为1,这个为计算完之后内存的补码,回到第一条注释
//11111111111111111111111111111111 计算机内存的补码,1开头为负数,需计算原反补码
//11111111111111111111111111111110 -1
//10000000000000000000000000000001 取反
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
//00000000000000000000000000000011 3的二进制序列的补码
//11111111111111111111111111111110 -2的补码
//11111111111111111111111111111101
return 0;
}
这里不再详解,只需遵循上面所说的技巧与方法便可求解。
3.1.3使用场景
1、不创建第三个变量,实现两个数的交换。
在这个时候我们便可以使用到上面的操作符了
#include <stdio.h>
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; }
这里大家有兴趣的话,可以参照上面的方法,讲两个整数转化成二进制进行计算验证一下。
2、求一个整数存储在内存中的二进制中1的个数。
#include <stdio.h>
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;
}
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*,
//取地址是由低到高取,也就是取首地址
int b = *p;//右值描述的是空间的内容
//*p赋给b,并通过*p的值找到a,把a的值赋给b,当这里写*p的时候,找到的是a里面的值,用的是a空间的值,也就是找到10
//这里因为用的是a的空间,所以下面是赋值给a
*p=20;//*为解引用操作符,*p就是同p里面存的地址,找到它所指的对象
printf("%d\n", a);
return 0;
}
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;
}
5.3操作符~
~表示按位取反,这个也是需要转换成二进制补码的
int main()
{
int a = 0;
//0000000000000000000000000000000
int b = ~a;//按位取反
//0000000000000000000000000000000
//1111111111111111111111111111111
//1111111111111111111111111111110
//1000000000000000000000000000001
//-1
printf("%d\n", b);
return 0 ;
}
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;
}
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;
}
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;
}
9.下标引用、函数调用和结构成员
9.1下标引用
操作数:一个数组名 + 一个索引值
int main()
{
int arr[] = { 1, 2, 3, 4, 5 };
int i = 0;
printf("%d\n", arr[4]);//通过下标,寻找arr数组的元素
return 0;
}
9.2函数调用
( ) 函数调用操作符
void test()
{
printf("hehe\n");
}
int main()
{
test();//不传参,也叫函数调用操作符
return 0;
}
9.3访问一个结构的成员
. 结构体.成员名
-> 结构体指针->成员名
#include <stdio.h>
#include<string.h>
//声明类型
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()
{
struct Book b = {"C语言程序设计",55.5f,"C20190203"};
print2(&b);
//print1(b);
//b.price = 100.0f;
字符串拷贝-strcpy-库函数
//strcpy(b.name, "数据结构");
//print1(b);
//结构成员访问操作符
//. 左边是结构体变量,右边是成员们 结构变量.成员名
//-> 结构体指针->成员名
//(*结构体指针).成员名
return 0;
}
10.表达式求值
10.1隐式类型转换
整型提升
整型提升是C程序设计语言中的一项规定:在表达式计算时,各种整形首先要提升为int类型,如果int类型不足以表示则要提升为unsigned int类型;然后执行表达式的运算。
举个例子
int main()
{
char a = 3;//a是一个字节,只有8bit位
//000000000000000000000000000000000011 3的原码反码补码
//00000011 --a里面存的东西,也就是将最后面的8位截取下来
char b = 127;
//00000000000000000000000000001111111 127的原码反码补码
//01111111 --b里面的东西,也就是将最后面的8位截取下来
//a和b自身都是char类型,自身大小都是一个字节,所以这里计算的时候要进行整型提升
//
//00000000000000000000000000000011
//00000000000000000000000001111111
//00000000000000000000000010000010
char c = a + b;
//放回c中的时候,同样截断,10000010
//高位是1,为负数
//111111111111111111111111111110000010
//100000000000000000000000000001111110
printf("%d\n", c);
return 0;
}
【整型提升的意义】
表达式的整型运算要在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操作符的属性
复杂表达式的求值有三个影响的因素。
- 操作符的优先级
- 操作符的结合性
- 是否控制求值顺序。
两个相邻的操作符先执行哪个?取决于他们的优先级。如果两者的优先级相同,取决于他们的结合性。
操作符优先级(下图来源于网络)
最后
最近学习C语言有点累了,时常感觉前路迷惘,害怕自己不是这块料却非要往这里凑。本文撰述了将近6个小时。参考了谭浩强《C语言设计》(第五版),以及网上的部分资料,加之自己在学习听课时的笔记,拼拼凑凑而成,尽管如此,却也花费了我很多心思。希望能对看到的大家有所帮助!