操作符
1、操作符的分类
- 算数操作符:+、-、*、/、%
- 移位操作符:<<、>>
- 位操作符:&、|、^
- 赋值操作符:=、+=、-=、*=、/=、%=、<<=、>>=、&=、|=、^=
- 单目操作符:!、++、--、&、*、+(正)、-(负)、~、sizeof、(类型)
- 关系操作符:>、>=、<、<=、==、!=、
- 逻辑操作符:&&、||
- 条件操作符:?、:、
- 逗号表达式:,
- 下标引用:[ ]
- 函数调用:( )
- 结构成员访问:. 、->
2、二进制和进制转换
所谓二进制就是数值的一种表示形式,根据十进制数值表示特点容易得到:
- 二进制中满2进1.
- 二进制序列只由0,1构成
2.1 二进制转十进制
十进制中位数的权重
类似的二进制转十进制只需要将每一位上的数字乘以它所对应的权重再依次相加即可,例如:1010
2.1.1 十进制转二进制
除2取余法
2.2 二进制转八进制和十六进制
2.2.1 二进制转八进制
由十进制数字的特征容易得到:
- 八进制中满8进1
- 八进制序列只由0~7构成
而0~7的数字,各自写成2进制,最多有3个二进制位就够了,如图
所以在二进制转八进制时,从二进制序列的右边低位开始向左每三个二进制为换算成一个八进制位,如果不够三个则直接换算即可。
如图2进制的01101011,换算成8进制为:0153,0开头的数字会被当做是8进制。
2.2.2 二进制转十六进制
由十进制数字的特征容易得到:
- 十六进制中满16进1.
- 十六进制序列只由0~15构成。
特别地,在16进制中为了表示简单方便10~15的数字分别用a ~ f 的字母表示。
同样的0~9,a ~ f 的数字各自写成2进制,最多有4个2进制位就够了,如图:
同理,在2进制转16进制时,从2进制序列中右边地位开始向左每4个2进制为会换算成一个16进制,如果不够4个2进制位则直接换算,如:
2进制的0101101011,换算成16进制为0x16b,16进制表示时前面要加上0x
3、原码,反码,补码
整数的2进制表示方式有三种,分别是:原码、反码和补码。
有符号整数的三种表示方式均有符号位和数值位两部分,2进制序列中,最高的一位被当作符号位,剩余的都是数值位,符号位上0表示正,1表示负。
正整数的原码、反码、补码都相同,而负整数的原码、反码、补码都各不相同。
- 原码:直接将数值按照正负数的形式翻译成2进制得到的就是原码。
- 反码:将原码的符号位不变,其他位依次按位取反得到的就是反码。
- 补码:反码加1就得到补码。
int main()
{
int a = 10;
//原码:00000000000000000000000000001010
//反码:00000000000000000000000000001010
//补码:00000000000000000000000000001010
int b = -10;
//原码:10000000000000000000000000001010
//反码:11111111111111111111111111110101
//补码:11111111111111111111111111110110
return 0;
}
特别地,补码得到原码也可以使用:取反加1的操作
对于整形来说:数据存放内存中的其实是补码。
原因是:
在计算机系统中,使用补码可以将符号位和数值域统一处理;同时,加法和减法也可以统一处理(CPU只有加法器),此外,补码和原码相互转换,其运算过程相同,不需要额外的硬件电路。
4、移位操作符
// <<左移操作符
// >>右移操作符
移位操作符的操作数只能是整数
int main()
{
int n=10;
int m=n<<1.54;//错误
return 0;
}
4.1 左移操作符
规则:左边抛弃,右边补0
int main()
{
int n=10;
int m=n<<1;
printf("%d",n);
printf("%d",m);
return 0;
}
4.2 右移操作符
右移操作分为两种:
- 逻辑右移:左边用0填充,右边丢弃
- 算术右移:左边用原该值的符号位填充,右边丢弃。
int main()
{
int n=-1;
int m=n>>1;
printf("%d",n);
printf("%d",m);
return 0;
}
1、逻辑右移1位
2.算数右移1位
注意:
- 对于移位操作符,不能移动负数位,这个是标准未定义的
int num=10;
int n=num<<-1;//错了
- 移位操作是对于补码进行移位操作,对于负整数而言需将移位后的补码转换位原码后才可知道移位后表示的数值
5、位操作符
//&(按位与)运算规则:只有两个数的二进制同时为1,结果才为1否则为0
//|(按位或)参加运算的两个数只要两个数中的一个为1,结果就为1
//^(按位异或)如果操作数的对应位相同,则结果位为0;
// 如果操作数的对应位不同,则结果位为1
//~(按位取反)包括符号位依次取反
注:它们的操作数必须是整数
5.1 代码展示:
#include<stdio.h>
int main()
{
int a=3;
int b=-1;
printf("%d",a&b);//3
printf("%d",a|b);//-1
printf("%d",a^b);//-4
printf("%d",~0);//-1
return 0;
}
代码的解读:
3的补码0000000000000000000000000000011
-1的原码1000000000000000000000000000001
-1的反码1111111111111111111111111111110
-1的补码1111111111111111111111111111111
3 & -1(只有两个数的二进制同时为1,结果才为1否则为0)
0000000000000000000000000000011
1111111111111111111111111111111
= 0000000000000000000000000000011(因为是正数得到的补码与原码相同解码后为3)
3 | -1(参加运算的两个数只要两个数中的一个为1,结果就为1)
0000000000000000000000000000011
1111111111111111111111111111111
= 1111111111111111111111111111111(补码)
1000000000000000000000000000001(原码解码后为-1)
3 ^ -1(如果操作数的对应位相同,则结果位为0;
如果操作数的对应位不同,则结果位为1)
0000000000000000000000000000011
1111111111111111111111111111111
= 1111111111111111111111111111100(补码)
= 1000000000000000000000000000100(原码解码后为-4)
0的补码:
00000000000000000000000000000000
~ 0 (包括符号位依次取反):
11111111111111111111111111111111(补码)
10000000000000000000000000000001(原码解码后为-1)
5.2 位操作符的应用
题型1:
不创建第三个变量实现两个数的交换
特别公式:
int a=8;//(a可以是任意值)
a^a=0;
a^0=a;
代码展示:
int main()
{
int a = 2, b = 6;
a = a ^ b;
b = a ^ b;//直接带入:b=(a^b)^b =a^(b^b) =a^0 =a
a = a ^ b;//同理:a=a^(a^b) =(a^a)^b =0^b =b
printf("%d\n", a);//6
printf("%d\n", b);//2
return 0;
}
题型二:
编写代码实现:求一个整数存储在内存中的二进制中1的个数
#include<stdio.h>
int main()
{
int a = -5;
int i = 0;
int count = 0;
for (i = 0; i < 32; i++)
{
if (a & (1 << i))
count++;
}
printf("%d", count);//31
return 0;
}
题型三:
编写代码将13的二进制序列中的第5位修改为1,然后再改为0
int main()
{
int num = 13;
int i = 0;
num = num | (1 << 4);
printf("%d\n", num);
num = num ^(1 << 4);
printf("%d\n", num);
return 0;
}
6、单目操作符
C语言中还有一些操作符只有一个操作数,被称为单目操作符。
6.1 ++ 和- -
++ 是一种自增操作符,又分为前置 ++和后置 ++ ,- - 是一种自减操作符,也分为前置 - - 和后置 - -。
6.1.1 前置++
int main()
{
int a = 10;
int b = ++a;
printf("a=%d,b=%d", a, b);
//结果:a=11,b=11
return 0;
}
计算口诀:先加1,后使用
a原来是10,先+1变成了11,再赋值给b,b得到的也是11,所以计算结果是a和b都是11,相当于这样的代码:
int main()
{
int a = 10;
a = a + 1;
int b = a;
printf("a=%d,b=%d",a,b);
return 0;
}
6.1.2 后置++
int main()
{
int a = 10;
int b = a++;
printf("a=%d,b=%d", a, b);
return 0;
}
计算口诀:先使用,再+1
a原来是10,先将a赋值给b然后a再+1,所以计算结果是a是11,b是10。相当于这样的代码:
int main()
{
int a = 10;
int b =a;
a = a + 1;
printf("a=%d,b=%d", a, b);
return 0;
}
6.1.3 前置 - -
int main()
{
int a = 10;
int b =--a;
printf("a=%d,b=%d", a, b);
return 0;
}
计算口诀:先-1,后使用
a原来是10,先-1变成了9,再赋值给b,b得到的也是9,所以计算结果是a和b都是9。
6.1.4 后置- -
int main()
{
int a = 10;
int b =a--;
printf("a=%d,b=%d", a, b);
return 0;
}
计算口诀:先使用,后-1
a原来是10,先将a赋值给b然后a再-1,所以计算结果是a是9,b是10。
6.2 +和-
这里的+是正号,-是负号,都是单目操作符。
运算符+对正负值没有影响,是一个完全可以省略的操作符,但是写了也不会报错。
int a=10;//等价于 int a=+10;
运算符 - 用来改变一个值的正负号,负数的前面加上-就会得到正数,正数的前面加上 - 就会变成负数。
6.3 强制类型转换
在操作符中还有一种特殊的操作符是强制类型转换,语法形式如下:
(类型)
代码示例:
int a=3.14;
//a的类型是整形,但是3.14的类型是双精度浮点型此时编译器会报错
此时只需要使用强制类型转换就可以消除这个警报,代码如下:
int a=(int)3.14;
``` `
6.4 sizeof操作符
sizef 是一个关键字也是一个操作符,专门是用来计算其操作数的类型长度的,单位是字节。
sizef 操作符的操作数可以是类型,也可以是变量或表达式。
sizeof(类型)
sizeof(表达式)
sizeof 表达式
sizeof 的操作数如果不是类型,是表达式的时候,可以省略后面的括号的。
sizeof后面的表达式是不真实参与运算的,根据表达式的类型来得出大小,代码举例:
int main()
{
int a = 10;
short b = 0;
printf("%zd\n", sizeof(a));//4
printf("%zd\n", sizeof(b = a + 7));//2
return 0;
}
第一句,sizeof 计算表达式时只看表达式的类型,此时表达式a 的类型是 int 类型,而int 类型的长度大小是4个字节所以 sizeof 的返回值是4。
第二句,此时 a为 int 类型7是整形,所以两者之和也一定是int类型,但是b是short类型长度大小只有2个字节,所以当4个字节的数据赋给2个字节的表达式时会发生截断,所以表达式 b=a+7本质上由b说了算(长度类型小的),而b的类型是short(2字节),所以sizeof的返回值是2。
sizeof的计算结果是 sizeof_t 类型的。
注
sizeof 运算符的返回值,C语言只规定是无符号整数,并没有规定具体类型,而是留给系统自己决定, sizeof 究竟返回什么类型,不同的系统中返回值可能是 unsigned int ,也有可能是unsigned long ,甚至是unsigned long long ,对应的 printf() 的占位符分别是 %u,%lu,%llu。这样不利于程序的可移植性。
对此,C语言提供了一个解决方法,创造了一个类型别名 sizeof_t ,用来统一表示 sizeof 的返回类型,(对应当前系统的sizeof的返回类型,可能是 unsigned int ,也可能是 unsigned long long )。
int main()
{
int a = 0;
printf("%zd\n", sizeof(a));
printf("%zd\n", sizeof a);
printf("%zd\n", sizeof(int));
printf("%zd\n", sizeof(3+3.5));
return 0;
}
6.5 取地址操作符(&)
6.5.1 内存单元
我们知道计算机CPU上在处理数据的时候,需要的数据是从内存中读取的,处理后的数据也会放到内存中。为了高效地对整片内存进行管理内存会被划分为一个个内存单元,每个内存单元的大小取一个字节。
而每个内存单元也都有一个编号,有了这个编号,CPU就可以快速找到一个内存空间。在计算机中我们把内存单元的编号叫做地址。
6.5.2 取地址操作符
在C语言中创建变量 就是向内存申请空间,比如:
int main()
{
int a = 10;
return 0;
}
此时,a是一个整形变量创建a时要向内存申请4个字节的空间,内存会以这样的形式给它(每次申请时给a的内存单元的地址都会有所变化,下列是一种情况):
如果我们想获得a的地址可以使用&(取地址操作符),代码如下:
int main()
{
int a = 10;
printf("%p", &a);
return;
}
此时屏幕上不会打印出内存给a 4个内存单元的地址,而是打印出4个字节中地址较小的那一个(如上图情况只会打印 0×006FFD70),但是我们只要知道了第一个字节的地址,顺藤摸瓜访问到4个字节的数据也是可行的。
6.6 解引用操作符(*)
我们将地址用指针变量保存起来,未来是要用的,那么怎么用呢?
在现实生活中,我们使用地址要找到房间,在房间里可以拿走或存放物品。
C语言中也是一样的,我们只要拿到了地址(指针),就可以通过地址(指针)找到其所指向的对象,这里就要使用到解引用操作符(*)。
int main()
{
int a = 10;
int* p = &a;//存放a的地址
printf("%d\n", *p);//打印a
*p = 100;//将100赋值给a
printf("%d\n", *p);//再打印a
return 0;
}
打印结果如下:
7、逗号表达式
逗号表达式就是用逗号隔开的多个表达式,表达式从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
类型一
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);
printf("%d", c);//13
return 0;
}
类型二
int a=0,b=0,c=0,d=0;
if(a=b+1,c=a/2,d>0)//影响判断结果的只有d,但要坚持依次计算
{
//语句
}
为了减少阅读失误,逗号表达式要坚持从左到右以此计算,因为之前的表达式的结果可能会影响最后表达式的结果
8、下标访问[],函数调用()
8.1 [ ]下标引用操作符
操作数:一个数组名 + 一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符
[ ]的两个操作符是arr和9。
8.2 函数调用操作符
接受一个或多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
void text1()
{
printf("hehehehe\n");
}
void text2(const char*str)
{
printf("%s\n", str);
}
int main()
{
text1();
text2("hello world");
return 0;
}
9、结构成员访问操作符
9.1 结构体
C语言已经提供了内置类型,如:char、short、int、long、float、double等,但是只有单一内置类型描述对象是不够的,比如描述一件食品需要名字、保质期、生产日期、生产地址、配料等。C语言为了解决这个问题,增加了结构体这种自定义的数据类型,让程序员可以创造出适合的类型。
注:
结构是一些值的集合,这些值称位成员变量。结构的每个成员都可以是不同类型的变量,如:标量、数组、指针,甚至是其他结构体。
9.1.1 结构的声明
struct tag
{
member - list;
}variable-list;
- tag:声明的结构体的名称
- member-list:成员变量
- variable-list: 定义的结构体变量
描述一个学生:
struct student
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};
9.1.2 结构体变量的定义和初始化
代码1:变量的定义
struct point
{
int x;
int y;
}p1;//声明类型的同时定义变量p1
struct point p2;//定义结构体变量p2
代码2:初始化
struct student
{
char name[20];//名字
int age;//年龄
};
struct student p1 = { "zhangsan",20 };//初始化
struct student p1 = { .age = 20,.name = "zhangsan" };//按指定顺序初始化
//结构体嵌套初始化(如下)
struct student
{
char name[20];//名字
int age;//年龄
};//结构体类型1
struct point
{
int x;
int y;
};//结构体类型2
struct class
{
int people;
struct student p1;
struct point p3;
}k1 = { 20,{"zhangsan",20},{15,13} };//嵌套初始化
struct class k2={40,{"lisi",13},{15,16}};//嵌套初始化
9.2 结构成员访问操作符
9.2.1 结构体成员的直接访问
结构体成员的直接访问是通过(.)访问的。点操作符接受两个操作数。如下所示:
struct student
{
char name[20];//名字
int age;//年龄
};
#include<stdio.h>
int main()
{
struct student p1 = { "zhangsan",20 };
printf("名字是:%s 年龄是:%d", p1.name, p1.age);
return 0;
}
使用方式:结构体变量名 . 成员名
9.2.2 结构体成员的间接访问
结构体成员的直接访问是通过 -> 访问的。->操作符接受两个操作数。如下所示:
#include<stdio.h>
struct stu
{
char name [20];
int age;
};
int main()
{
struct stu p1 = {"zhangsan",18};
struct stu* p = &p1;//结构体指针
printf("%s\n",p->name);//间接访问
return 0;
}
使用方式:结构体指针 -> 成员名
10、操作符的属性:优先级,结合性
C语言的操作符有两个重要属性:优先级,结合性,这两个属性决定了表达式求值的计算顺序。
10.1 优先级
优先级指的是,如果一个表达式包含多个运算符 ,那个运算符应该优先执行。各种运算符的优先级是不一样的。
3 + 4 * 5
如上表达式所示,表达式3 + 4 * 5中有加法运算符也有乘法运算符。由于乘法运算符的优先级高于加法所以会先计算4 * 5,而不是先计算3 + 4。
10.2 结合性
如果两个优先级相同,优先级没办法确定先计算哪个了,这时候就看结合性了,则根据运算符是左结合还是右结合,决定执行顺序。大部分运算符是左结合(从左到右执行),少数运算符是右结合(从右到左执行),比如赋值运算符(=)。
5 * 6 / 2
如上面表达式所示,* 和 /的优先级相同,他们都是左结合运算符,所以从左到右执行,先计算5 * 6,再计算6 / 2。
运算符的优先级顺序很多,下面是部分运算符的优先级顺序(按照优先级从高到低排列),建议大概记住这些运算符的优先级就行,其他操作符在使用的时候查看下面表格就可以了。
- 圆括号: ( )
- 自增运算符: ++,自减运算符 : - -
- 单目运算符
- 乘法 * ,除法 /
- 加法 + ,减法 -
- 关系运算符( > 、< 等)
- 赋值运算符 =
由于圆括号的优先级最高,可以使用它改变其他运算符的优先级。
11、表达式求值
11.1 整形提升
C语言中整形算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整形提升的意义:
表达式的整形运算要在CPU的相应运算器内执行,CPU内整形运算器的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。因此,即使两个char类型的相加,在CPU执行时实际上也要先转化为CPU内整形操作数的标准长度。通用CPU是难以实现两个8比特字节直接相加运算。所以,表达式中各种长度可能小于int长度的整形值,都首先转化为int或unsigned int ,然后才能送入CPU去执行计算。
如何进行整形提升:
- 有符号整数提升是按照变量的数据类型的符号位来提升的。
- 无符号整形提升,高位补0。
示例:
int main()
{
char a = -1;//char == signed char
char b = 1;
char c = a + b;
printf("%d",c);
return 0;
}
//负数的整型提升:
char a=-1;
变量a的二进制序列(补码)中只有8个比特位
11111111
因为char==signed char所以提升时高位补符号位1
11111111111111111111111111111111
//正数的整型提升:
char b=1;
变量b的二进制序列(补码)中只有8个比特位
00000001
因为char==signed char所以提升时高位补符号位0
00000000000000000000000000000001
补充:
- 当以%d的形式打印 c 时此时 c 中存储的数据也需要整型提升
- char 究竟时 unsigned char 还是 signed char 这个是不确定的,是取决于编译器实现的,但是在常见的编译器上 char== signed char。
11.2 算术转换
如果某个操作符的各个操作数属于不同的类型,那么除非其中一个操作数的类型转换为另一个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算数转换。
1 long double
2 double
3 float
4 unsigned long int
5 long int
6 unsigned int
7 int
如果某个操作数的类型在上面的列表中排名靠后,那么首先要转换为另一个操作数的类型后再执行运算。比如 int 类型的变量与 double 类型的变量进行计算时 int 类型的变量首先要转换为 double 类型,而当有比 int 类型长度更短的类型如 char 或 short 参与运算时应该先进行整型提升将 char类型或short类型先转换为 int类型,再考虑是否进行算术转换。
11.3 问题表达式解析
11.3.1 表达式1:
a*b + c*d + e*f
表达式在执行时由于*比+优先级高,只能保证*号的计算比+早但是优先级不能决定第三个*比第一个+早执行。
所以表达式的的计算顺序可以是:
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
11.3.2 表达式2:
c+ --c
操作符的优先级只能保证 - - 的运算比 + 早但我们无法得知第一个 c 的数据是在执行了 --c 之前取值还是之后取值,所以结果是不可预测的,是有歧义的。
11.3.3 表达式3:
int main()
{
int i = 10;
i = i-- - --i * (i = -3) * i++ + ++i;
printf("i=%d", i);
return 0;
}
表达式3在不同编译器中有不同的结果,是一个非法表达式。
11.3.4 表达式4:
int fun(void)
{
static int count = 1;
return ++count;
}
int main()
{
int answer = fun() - fun() * fun();
printf("%d", answer);
return 0;
}
表达式4虽然在大多数编译器上求的的结果是相同的但其依然是一个问题代码。
上述代码中由操作符的优先级可以得到:乘法的计算顺序早于减法,但是函数调用的先后顺序无法通过操作符的优先级确定,所以会有下面两种不同的计算结果:
- answer=2- 3 * 4 (-10)
- answer=4 - 2 * 3 ( -2 )
11.3.5 表达式5
int main()
{
int i = 1;
int ret = (++i) + (++i) + (++i);
printf("%d\n", ret);
printf("%d\n", i);
return 0;
}
11.4 总结
即使有了操作符的优先级和结合性,我们写出的表达式依然有可能不能通过操作符的属性确定唯一的计算路径,这个表达式就是存在潜在风险的,我们可以尝试通过拆分语句或者加括号的方式使其逻辑畅通,容易理解。
12、算术操作符
在写代码的时候,一定会涉及到运算。C语言中为了方便运算,提供了一系列操作符,其中有一组操作符叫:算术操作符。如下所示
+(执行加法)
-(执行减法)
*(执行乘法)
/(执行除法)
%(求模运算)
这些操作符都需要两个操作数,故都为双目操作符。
注:操作符也可被称作运算符。
12.1 四则运算(+、-、*、/)
代码举例:
int main()
{
int x = 4 + 22;//执行加法
int y = 61 - 23;//执行减法
int z = 23 * 2;//执行乘法
int j = 6 / 3;//执行除法
printf("%d\n", x);
printf("%d\n", y);
printf("%d\n", z);
printf("%d\n", j);
return 0;
}
特别的:对于 / 来讲
除号( / )两端如果是整数,执行的是整数除法,得到的结果也是整数。
int main()
{
float a = 6 / 4;
int b = 6 / 4;
printf("%f\n", a);//输出1.000000
printf("%d\n", b);//输出1
return 0;
}
如上面所示,尽管变量a的类型是单精度浮点型但是计算结果依然是1.0,原因在于C语言中的整数除法是整除,只会返回整数部分丢弃小数部分。
如果希望得到的返回值是一个浮点数,那么两个操作数必须有一个是浮点型。
int main()
{
float a = 6.0 / 4;
printf("%f\n", a);//结果是1.50000
return 0;
}
12.2 求模运算(%)
运算符%表示求模运算,即返回两个整数相除的余数。这个操作符只能用于整数,不能用于浮点数
int main()
{
int a = 6 % 3;
int b = 9 % 4;
printf("%d\n", a);//0
printf("%d\n", b);//1
return 0;
}
负数求模的规则是,结果的正负号由第一个运算数的正负号决定
int main()
{
int a = -7 % 3;
int b = 7 % -3;
int c = -7 % -3;
printf("%d\n", a);// -1
printf("%d\n", b);// 1
printf("%d\n", c);// -1
return 0;
}
13、赋值操作符:=和复合赋值
在变量创建的时候给一个初始值叫做初始化,在变量创建好后再给一个值,这就叫做作赋值。
int main()
{
int a = 5;//初始化
a = 6;//赋值
return 0;
}
赋值操作符=是一个随时可以给变量赋值的操作符。
13.1 连续赋值
赋值操作符也可以连续赋值:
int main()
{
int a = 5;
int b = 4;
int c = 3;
b =c=a+b;
return 0;
}
连续赋值的表达式虽然简洁但是不易理解,建议赋值时分开来写。
13.2 复合赋值符
在写代码时,我们经常可能对一个数进行自增或者自减的操作:
int main()
{
int a = 0;
int b = 8;
int c = 3;
int d = 9;
a = a + 3;
b = b - 6;
c = c * 2;
d = d / 3;
return 0;
}
C语言提供了复合赋值符,方便我们编写代码,所以以上代码可以写成:
int main()
{
int a = 0;
int b = 8;
int c = 3;
int d = 9;
a +=3;
b -= 6;
c *= 2;
d /= 3;
return 0;
}
14、关系操作符
C语言用于比较的表达式,称为关系表达式,里面使用的运算符就称为“关系运算符”,主要有下面六个:
> //大于运算符
< //小于运算符
>=//大于等于运算符
<=//小于等于运算符
==//等于运算符
!=//不相等运算符
代码举例:
int main()
{
int a = 0, b = 0;
a == b;
a >= b;
a <= b;
a > b;
a < b;
a != b;
return 0;
}
关系表达式通常返回0或1,表示真假。
C语言中,0表示假,所有非0值表示真。比如,23>20返回1,20>23返回0.
关系表达式常用于 if 或 while 结构。
if (a >= 2)
{
printf("验证结束");
}
else
printf("运行结束");
注意:相等运算符 == 与赋值运算符 = 是两个不一样的运算符,不要混淆。有时候,可能不小心写出下面的代码,它可以运行,但是运行结果却与我们想象的不同:
if (a = 3)
{
...
}
上面代码原意是 x==3,但是不小心写成x=3 这个式子将3赋值给x,它的返回值是3,所以if判断总是为真。
为了防止这中情况的发生,我们可以把变量写再等式的右边:
if(3==x)
这样的话,如果将==误写为=,就表示将变量赋值给常量编译器就会报错。
另一个需要注意的是:多个关系运算符不易连用如:
if(i<x<j)
此时,如果我们输入一个x,编译器首先会将 i 与x比较也就是判断 i<x 是否成立,若是则返回1否则返回0,这就导致之后与 j 进行对比的不是1就是0。也就是说表达式(i<x<j)不能保证变量 x 会在 i 与 j 之间
解决办法是使用逻辑操作符:
if( i<x && x<j )
15、条件操作符 ?、:、
条件操作符也叫做三目操作符,“三目”表示需要接受3个操作数,形式如下:
exp1?exp2:exp3
条件操作符的计算逻辑是,首先判断exp1是否为真,若为真则exp2计算,计算的结果就是整个表达式的结果;如果exp1为假则exp3计算,计算结果就是整个表达式的结果。
一个小代码举例:
int main()
{
int a = 0, b = 0;
scanf("%d %d", &a, &b);
if (a > b)
a = 10;
else
a = -10;
printf("%d", a);
return 0;
}
将其用条件操作符(三目操作符)优化一下如下:
int main()
{
int a = 0; int b = 0;
scanf("%d %d",&a,&b);
a = a > b ? 10 : -10;
printf("%d", a);
}
练习:使用条件表达式实现找两个数的较大值:
int main()
{
int a = 0;
int b = 0;
int c = a > b ? a : b;
scanf("%d %d",&a,&b);
printf("%d", c);
return 0;
}
16、逻辑操作符
逻辑操作符提供逻辑判断功能,用于构造更复杂的表达式,主要有下面三个操作符:
!//逻辑取反
&&//逻辑与
||//逻辑或
注:C语言中0表示假,非0表示真
16.1 逻辑取反操作符!
比如,我们有一个变量 flag ,如果 flag 为假我们要做一个什么事情,就可以这样写代码:
int main()
{
int flag = 0;
if (!flag)
{
printf("哈哈");
}
return 0;
}
16.2 逻辑与操作符
&&就是逻辑与操作符,也是并且的意思,&&是一个双目操作符,使用方式是a&&b,&&表达式两边都为真的时候,整个表达式才为真,只要有一个为假,则整个表达式为假。
比如:写一段代码要求:当输入月份为3到5月时输出春天
int main()
{
int month = 0;
scanf("%d", &month);
if (month >= 3 && month <= 5)
printf("春天\n");
return 0;
}
这里表达的意思是month既要大于等于3也要小于等于5,必须同时满足。
16.3 逻辑或运算符
|| 就是逻辑或运算符,也就是或者的意思,|| 也是一个双目运算符,使用的方式是 a||b ,||两边只要有一个为真,整个表达式就为真,两边的表达式都为假的时候表达式才为假。
比如:我们说一年中月份是12月,1月或者2月就是冬天,我们来用代码实现:
int main()
{
int month = 0;
scanf("%d",&month);
if (month == 12 || month == 1 || month == 2)
{
printf("是冬天\n");
}
return 0;
}
16.4 练习
输入一个年份year,判断year是否为闰年
闰年的判断规则:
- 能被4整除但不能被100整除
- 能被400整除
//代码1
int main()
{
int year = 0;
scanf("%d",&year);
if (year % 4 == 0 && year % 100 != 0)
{
printf("是闰年");
}
else if (year % 400 == 0)
{
printf("是闰年");
}
else
printf("不是闰年");
return 0;
}
//代码2
int main()
{
int year = 0;
scanf("%d",&year);
if (year % 400 == 0 || (year % 4 == 0 && year % 100 != 0))
{
printf("是闰年");
}
else
{
printf("不是闰年");
}
return 0;
}
16.5短路
C语言逻辑运算符还有一个特点,它总是先对左侧的表达式求值,在对右边的表达式求值,这个顺序是保证的。
如果左边的表达式满足逻辑运算符的条件,就不再对右边的表达式求值。这种情况称之为短路。
例:
对于逻辑与(&&)来讲
if(month >= 3 && month <= 5)
表达式&&操作符右边是 month>=3 ,右操作数是 month<=5,当左操作数 month>=3 的结果为0时,即使不判断 month<=5,整个表达式的结果也是0(不是春季)。
所以对与&&操作符来说,左边操作数为0时,右边操作数就不再执行。
对于逻辑或(||)来讲
if(month==12||month==1||month||month==2)
如果month==12,则不用再判断month是否为1或2,整个表达式的结果也仍然为1(是冬季),相同的若month为1则首先判断month是否为12,再判断month 是否为1此时整个表达式会返回1而不会去判断month是否为2.
所以,当 | | 操作符的左操作数结果为1时,就不会再执行右操作数。
像这样仅仅根据左操作数的结果就能知道整个表达式的结果,不用再对右操作数进行计算的运算称为短路求值。