C语言教程笔记
十二. 运算符与表达式
1. 表达式
1.1 什么是表达式?
运算符往往需要与运算对象一起使用才有意义。例如:
5 + 10
其中,5与10是 运算对象 ,+为 运算符 。
在C语言中,一个或多个运算对象与零个或多个运算符组成表达式。下面几个都是表达式:
100
5 + 10
a / b
a * 10 / b + c
如上的例子可以看出,100也是一个正确的表达式,虽然它只有运算对象而没有运算符。
1.2 表达式的结果
printf("%d\n", 5 + 10);
运算符与运算对象进行运算操作必然产生一个结果,因此每个表达式都有一个结果。例如上面的表达 式 5 + 10 的结果为15。
值15被传入printf后,在控制台上打印出字符15。
1.3 表达式语句
表达式后面加上分号构成表达式语句。
C语言中表达式不能单独存在,必须以表达式语句的形式存在。
例如:
100;
5 + 10;
a / b;
a * 10 / b + c;
那么下面的表达式5+10呢?它为什么可以没有分号。
printf("%d\n", 5 + 10);
因为 5 + 10 是一个子表达式,函数名加上括号运算符,构成了一个函数调用表达式。 所以, 5 + 10 为函数调用表达式的子表达式,而函数调用表达式后面加了分号了。
当然函数调用表达式也有一个表达式结果,这个结果就是函数的返回值了。
2. 加法运算符 +
+ 为加法运算符,它左右边各需要一个运算对象,构成 加法运算符表达式 。
表达式结果:加法表达式的结果为左右表达式结果相加。
int a, b;
a = 200;
b = 100;
a + b; // 表达式结果为300
3. 减法运算符 -
- 为减法运算符,它左右边各需要一个运算对象,构成 减法运算符表达式 。
表达式结果:减法表达式的结果为左右表达式结果相减。
int a, b;
a = 200;
b = 100;
a - b; // 表达式结果为100
4. 符号运算符 +、-
+ 和 - 为符号运算符。
+ 用于标明一个表达式的正负
- 用于改变一个表达式的正负。
与加减运算符不同的是,它仅需要在运算符右边有一个运算对象。
表达式结果:
+ 号表达式的结果为右边运算对象的值。
- 号表达式的结果为右边运算对象值的相反数。
+100;
// 表达式结果为100 -100;
// 表达式结果为-100
用于改变表达式的正负:
int a = 100; -a;
// 表达式结果为-100
请注意:+不能改变表达式的正负,请使用负负得正。
int a = -100;
+a; // 表达式结果为-100
-a; // 表达式结果为100
5. 乘法运算符 *
*为乘法运算符,它左右边各需要一个运算对象,构成乘法运算符表达式 。
表达式结果:乘法表达式的结果为左右表达式结果相乘。 * 是乘法运算符,注意不要误用为字母x。
int a, b;
a = 200;
b = 100;
a * b; // 表达式结果为20000
6. 除法运算符 /
/ 为除法运算符,它左右边各需要一个运算对象,构成 除法运算符表达式 。
表达式结果:除法表达式的结果为左运算对象除以右运算对象。
int a, b;
a = 200;
b = 100;
a / b; // 表达式结果为2
6.1 整型无法整除问题
对于除法,请特别注意整型无法整除时,出现浮点类型数据的问题。
int a, b, c;
a = 5;
b = 2;
c = a / b;
a 的值为 5 , b 的值为 2 。那么 5 除以 2 ,应该等于 2.5 。
但是,使用 int 来接收 2.5 好像不太对,那我们把 c 改为 float 或 double 。
int a, b;
a = 5;
b = 2;
float c;
c = a / b;
printf("%f\n", c);
为什么结果依然不对呢?
在C语言里面,整型与整型运算的结果,依然是一个整型。结果的小数部分被丢弃,这一过程被称作截 断。a / b后,结果仍然是一个整型,数值已经被截断了,这时候,我们再赋给一个浮点型的c。也只能 是整数2转成浮点2.0了,没有起到效果。
那我们将a,b,c都改为float,这样就不会出现截断了。a,b将会进行浮点运算,结果也是一个浮点数 类型float。
#include <stdio.h>
int main()
{
int a, b;
a = 5;
b = 2;
float c;
c = a / b;
printf("%f\n", c);
return 0;
}
7. 求余运算符 %
% 为求余运算符,它左右边各需要一个运算对象,构成 求余运算符表达式 。 表达式结果:求余表达式的结果为左运算对象除以右运算对象的余数。 例如:10%3,10除以3,等于3余1。
int a, b;
a = 10;
b = 3;
a % b; // 表达式结果为1
8. 赋值运算符 =
a = 100; // 赋值表达式语句
= 为赋值运算符,它左右各需要一个运算对象,构成 赋值运算符表达式 。
赋值表达式既然也是一个表达式,类似于上面提到的各种表达式,赋值表达式也有一个运算结果。赋值表达式的结果为等号右边的运算对象的值。例如,赋值表达式 a = 100; 的结果为100。除了获得表达式 结果以外,赋值表达式还会将等号右边运算对象的值,传递给左边的运算对象。
8.1 赋值与初始化的区别
int a = 100; // =表示初始化,不是赋值运算符
a = a + 150; // 赋值运算符,将右边的表达式结果赋值给左边的变量a。
在上面的代码中,第一行代码声明了一个变量a。请注意,声明变量时并将它初始化为100所用的 = 号, 不是赋值运算符,而是变量初始化。虽然初始化和赋值很像,但是 = 号左边并不是一个单纯的变量,而是变量的声明。你可以借此来区别它们。
8.2 求一个赋值表达式的结果
让我们来求一下上面赋值表达式的结果吧:
表达式 a = a + 50 是一个复合表达式,先求得子表达式 a + 50 的结果,为150。再求表达式 a = 150 的 结果,即 150 。另外,赋值表达式除了计算出表达式的结果外,还会将 150 赋值给变量 a 。
8.3 不能给常量赋值
最后,给常量赋值是错误的,因为常量不能被更改。下面几种赋值是错误的:
"Hello" = "HelloWorld";
'a' = 'b';
100 = 200;
9. 自增、自减运算符
++ 为指自增运算符, - - 为自减运算符。它们仅需要运算符左边或右边有一个运算对象即可。
运算符置于左边被称作前缀模式。
例如: ++i --i 。 运算符置于右边被称作后缀模式。例如: i++ i- - 。
9.1 前缀模式
#include <stdio.h>
int main()
{
int a, b;
a = 10;
b = 10;
printf("%d %d\n", ++a, --b);
printf("%d %d\n", a, b);
return 0;
}
前缀模式表达式结果:
++ 与右运算对象构成前缀自增表达式,表达式结果为运算对象值加1。 - - 与右运算对象构成前缀自减表达式,表达式结果为运算对象值减1。
与赋值表达式类似,自增、自减表达式除了计算出表达式结果,也会对运算对象本身产生操作。将运算对象进行自增或自减。
前缀模式对运算对象的操作:
自增表达式:将运算对象加1。
自减表达式:将运算对象减1。
因此printf函数输出的值为11和9。其后,再打印a,b的值,可以发现它们分别被加1和减1了。
9.2 后缀模式
#include <stdio.h>
int main()
{
int a,b;
a = 10;
b = 10;
printf("%d %d\n",a++,b--);
printf("%d %d\n",a,b);
return 0;
}
后缀模式表达式结果:
++ 与左运算对象构成后缀自增表达式,表达式结果为运算对象值。 - - 与左运算对象构成后缀自减表达式,表达式结果为运算对象值。
和前缀模式一样,后缀模式也会对运算对象本身产生操作。将运算对象进行自增或自减。
后缀模式对运算对象的操作:
自增表达式:将运算对象加1。
自减表达式:将运算对象减1。
因此printf函数输出的值为10和10。其后,再打印a,b的值,可以发现它们分别被加1和减1了。
10. 自增、自减表达式何时操作运算对象
#include <stdio.h>
int main()
{
int a, b;
a = 1;
b = a++ + a++ + a++;
printf("%d %d", a, b);
return 0;
}
同一段代码,居然在不同的编译器下出现了不同的结果。
vs执行编译的结果为4 3。
gcc编译执行的结果为4 6。
我们可以看出变量a被后缀自加了3次,在下一行打印a的时候,结果都是4。但是,两种编译器对于何时 操作运算对象本身却有不同理解。
10.1 Visual Studio的表达式求值过程
- 第一个a++:a的值为1,后缀自加表达式结果为1,此外不做其他操作。
- 第二个a++:a的值为1,后缀自加表达式结果为1,此外不做其他操作。
- 第三个a++:a的值为1,后缀自加表达式结果为1,此外不做其他操作。
- 执行上面三次后缀自加累积的对运算对象的操作a + 3 = 4。
最终:b的值为表达式结果相加1 + 1 + 1 = 3,a的值为4。
10.2 GCC的表达式求值过程
- 第一个a++:a的值为1,后缀自加表达式结果为1,求值完成后立即操作运算对象。a的值变为2。
- 第二个a++:a的值为2,后缀自加表达式结果为2,求值完成后立即操作运算对象。a的值变为3。
- 第三个a++:a的值为3,后缀自加表达式结果为3,求值完成后立即操作运算对象。a的值变为4。
最终:b的值为表达式结果相加1 + 2 + 3 = 6,a的值为4。
10.3 表达式操作运算对象最晚时机
某些表达式除了求表达式结果以外,还会对运算对象产生操作。例如:赋值和自增、自减运算符。而对 运算对象产生操作的时机却在不同编译器里,有不同的发生时机。
在这个例子里,GCC每完成一个子表达式的求值,即对运算对象发生操作。而在VS里面,所有对运算 对象的操作,累积到所有子表达式求值完成后进行。
对运算对象的操作没有固定的发生时机,只有一个最晚时机。
表达式操作运算对象最晚时机为完整表达式求值结束后,进入下一步之前。完整表达式即它不是任何一 个表达式的子表达式。例如: b = a++ + a++ + a++; 为一个完整表达式,而 a++ 为它的一个子表达式。
因此,编译器只要保证在这个表达式结束并进入下一步操作之前,完对运算对象的操作即可。
前缀模式和后缀模式都会受到影响。例如: b = ++a + ++a + ++a; 在不同编译器下,也可能出现不同的 运算结果。
为了避免写出这种在不同编译器下可能造成不同结果的代码。请不要在一个表达式中,重复对一个变量 进行自增或自减。
11. 一元运算符,二元运算符
一元运算符:只有一个运算对象的运算符。例如:自增运算符 ++ ,自减运算符 – 。
二元运算符:有两个运算对象的运算符。例如:赋值运算符 = ,加法运算符 + 。
12. 运算符优先级
C语言的四则运算符优先级和数学中的一致。先算括号里面的,先乘除后加减。
#include <stdio.h>
int main()
{
int a, b;
a = 10 * 2 + 4 * 3;
b = 10 * (2 + 4) * 3;
printf("%d %d", a, b);
return 0;
}
优先级顺序从高到低如下表所示:
优先级 | 运算符 |
---|---|
1 | () |
2 | ! ++ - - |
3 | * / % |
4 | + - |
5 | < <= > >= |
6 | == != |
7 | && |
8 | 逻辑运算符或 |
9 | = += -= *= /= %= |
下图是运算符优先级表格:
运算符优先级表格
点个赞吧♥
十三. 类型转换
类型转换,这次教你永不出错的办法
在编码过程中,我们肯定会遇到一些类型相互转化的问题。这一节当中,我们就来讨论类型的相互转化。
1. 判断数据类型的小技巧
在开始之前,先介绍一个小技巧,用于判断某一个数据对象的类型。
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
char c;
short s;
int n;
long l;
float f;
double d;
// 将整型赋值给指针类型
p = c;
p = s;
p = n;
p = l;
p = f;
p = d;
return 0;
}
首先,我们定义一个整型指针变量 p 。注意,这里的 int 后面加了一个 * ,说明它是一个整型指针变 量。我们先不管什么是指针变量,大家只要知道,指针变量和整型、浮点这些数据类型是不能相互赋值的。
由于它们无法相互赋值,所以编译器会提示报错信息。
从上到下,依次是赋值运算符无法将char、short、int、long、float、double转化为整型指针变 量 int * 。
错误提示与我们定义的变量类型一致,说明这个提示是准确的。好的,那下面我们就可以通过这个小技巧来研究一下,类型与类型之间进行运算,到底会发生什么样的变化呢?
2. 同类型之间进行运算
我们将一步一步探究运算会怎样改变数据对象的类型。首先,我们先了解同类型之间的数据对象进行运 算,将会产生怎样的结果。
2.1 有符号整型同类型
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p; // 各式各样的类型
char c;
short s;
int n;
long l;
p = c + c; // char + char = int
p = s + s; // short + short = int
p = n + n; // int + int = int
p = l + l; // long + long = long
return 0;
}
c + c,char同类型运算,结果是一个int类型。
s + s,short同类型运算,结果是一个int类型。
n + n,int同类型运算,结果是一个int类型。
l + l,long同类型运算,结果是一个long类型。
在C语言中,高级别的数据类型能表示的数据范围大于或等于低级的数据类型。
类型级别:
char < short < int < long
有符号整型类型同类型运算中:
比int低级的类型,都会转换成int,比int高级的类型不变。
2.2 无符号整型同类型
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
unsigned char uc;
unsigned short us;
unsigned int un;
unsigned long ul;
p = uc + uc; // unsigned char + unsigned char = int
p = us + us; // unsigned short + unsigned short = int
p = un + un; // unsigned int + unsigned int = unsigned int
p = ul + ul; // unsigned long + unsigned long = unsigned long
return 0;
}
uc + uc,unsigned char同类型运算,结果是一个int类型。
us + us,unsigned short同类型运算,结果是一个int类型。
un + un,unsigned int同类型运算,结果是一个unsigned int类型。
ul + ul,unsigned long同类型运算,结果是一个unsigned long类型。
类型级别:
int < unsigned int < unsigned long
无符号整型类型同类型运算中:
比int低级的类型,都会转换成int,比int高级的类型不变。
2.3 浮点同类型
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
float f;
double df;
p = f + f; // float + float = float
p = df + df; // double + double = double
return 0;
}
f + f,float同类型运算,结果是一个float类型。
df + df,double同类型运算,结果是一个double类型。
类型级别:
float < double
浮点类型同类型运算中:
类型不变
2.4 同类型运算的结果
上面探究了同类型之间运算结果的类型。 对于整型,级别低于int的类型会转换成int。而比int高级的类型则不发生变化。 对于浮点,不发生变化。
3.不类型之间进行运算
3.1 有符号整型不同类型
#include <stdio.h>
int main()
{ // 一个整型指针变量p
int* p;
// 各式各样的类型
char c;
short s;
int n;
long l;
p = c + s; // char + short = int
p = c + n; // char + int = int
p = c + l; // char + long = long
p = n + l; // int + long = long
return 0;
}
c + s,char类型与short类型运算,结果是一个int类型。
c + n,char类型与int类型运算,结果是一个int类型。
c + l,char类型与long类型运算,结果是一个long类型。
n + l,int类型与long类型运算,结果是一个long类型。
类型级别:
char < short < int < long
有符号整型不同类型运算中:
若运算符两边类型均低于int或等于int,那么结果为int。
若有高于int的,那么结果为高于int的等级最高的类型。
3.2 无符号整型不同类型
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
unsigned char uc;
unsigned short us;
unsigned int un;
unsigned long ul;
p = uc + us; // unsigned char + unsigned short = int
p = uc + un; // unsigned char + unsigned int = unsigned int
p = uc + ul; // unsigned char + unsigned long = unsigned long
p = un + ul; // unsigned int + unsigned long = unsigned long
return 0;
}
uc + us,unsigned char类型与unsigned short类型运算,结果是一个int类型。
uc + un,unsigned char类型与unsigned int类型运算,结果是一个unsigned int类型。
uc + ul,unsigned char类型与unsigned long类型运算,结果是一个unsigned long类型。
un + ul,unsigned int类型与unsigned long类型运算,结果是一个unsigned long类型。
类型级别:
int < unsigned int < unsigned long
无符号整型不同类型运算中:(同上)
若运算符两边类型均低于int或等于int,那么结果为int。
若有高于int的,那么结果为高于int的等级最高的类型。
3.3 混合整型类型
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
char c;
short s;
int n;
long l;
unsigned char uc;
unsigned short us;
unsigned int un;
unsigned long ul;
p = c + uc; // char + unsigned char = int
p = s + us; // short + unsigned short = int
p = c + n; // char + int = int
p = c + un; // char + unsigned int = unsigned int
p = n + un; // int + unsigned int = unsigned int
p = n + ul; // int + unsigned long = unsigned long
return 0;
}
c + uc,char类型与unsigned char类型运算,结果是一个int类型。
s + us,short类型与unsigned short类型运算,结果是一个int类型。
c + n,char类型与int类型运算,结果是一个int类型。
c + un,char类型与unsigned int类型运算,结果是一个unsigned int类型。
n + un,int类型与unsigned int类型运算,结果是一个unsigned int类型。
n + ul,int类型与unsigned long类型运算,结果是一个unsigned long类型。
类型级别:
int < unsigned int < long < unsigned long
混合整型类型运算中:(同上)
若运算符两边类型均低于int或等于int,那么结果为int。
若有高于int的,那么结果为高于int的等级最高的类型。
从结果看来,同一种类型的无符号等级要高于有符号等级。
3.4 浮点不同类型
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
float f;
double df;
p = f + df;
return 0;
}
f + df,float类型与double类型运算,结果是一个double类型。
类型级别:
float < double
浮点不同类型运算中:
结果为运算符两边级别最高的类型。
3.5 浮点整型混合
#include <stdio.h>
int main()
{
// 一个整型指针变量p
int* p;
// 各式各样的类型
char c;
short s;
int n;
long l;
unsigned char uc;
unsigned short us;
unsigned int un;
unsigned long ul;
float f;
double df;
p = c + f; // char + float = float
p = s + f; // short + float = float
p = n + f; // int + float = float
p = l + f; // long + float = float
p = uc + f; // unsigned char + float = float
p = us + f; // unsigned short + float = float
p = un + f; // unsigned int + float = float
p = ul + f; // unsigned long + float = float
p = c + df; // char + double = double
p = s + df; // short + double = double
p = n + df; // int + double = double
p = l + df; // long + double = double
p = uc + df; // char + double = double
p = us + df; // short + double = double
p = un + df; // int + double = double
p = ul + df; // long + double = double
return 0;
}
类型级别: 整型类型 < float < double
浮点与整型类型混合运算中: 结果为运算符两边等级最高的类型。
4. 自动类型转换
上面的代码,让我们看到了类型经过运算后,结果的变化。那么造成这种变化的原因是什么呢?
这个原因,我们称之为自动类型转换。C语言会将运算符两边的类型,先经过自动类型转换后,再进行 运算。
那么为什么有时候转换为int,有时候又是long,有时候是float,又有时候是double了呢?
我们总结一下上面观察到的各种转换现象:
运算类型 | 结论 |
---|---|
有符号整型同类型 | 比int低级的类型,都会转换成int,比int高级的类型不变 |
无符号整型同类型 | 比int低级的类型,都会转换成int,比int高级的类型不变 |
浮点同类型 | 类型不变 |
有符号整型不同类型 | 若运算符两边类型均低于int或等于int,那么结果为int。若有高于int的,那么结果为高于int的等级最高的类型。 |
无符号整型不同类型 | 若运算符两边类型均低于int或等于int,那么结果为int。若有高于int的,那么结果为高于int的等级最高的类型。 |
混合整型不同类型 | 若运算符两边类型均低于int或等于int,那么结果为int。若有高于int的,那么结果为高于int的等级最高的类型。 |
浮点不同类型 | 结果为高于int的级别最高的类型。 |
浮点整型混合 | 结果为高于int的等级最高的类型。 |
综合所有情形,我们可以得到以下结论:
整型之间进行运算:若运算符两边类型均低于int或等于int,那么结果为int。若有高于int的,那么结果 为高于int的等级最高的类型。
整型与浮点进行运算:结果为运算符两边等级最高的类型。
类型级别从低到高依次为:
int < unsigned int < long < unsigned long < float < double。
char,short,unsigned char,unsigned short总是会被转换为int。
5. 整型运算的注意点
整型与整型运算,是不会出现浮点类型的。也就是说,运算结果将丢失小数部分。
#include <stdio.h>
int main()
{
int n1, n2;
n1 = 5;
n2 = 2;
printf("%d\n", n1 / n2);
}
除号运算符两边均为 int , int 与 int 运算,结果是 int 。那我们必须在运算符两边,设置一个浮点型 才行, float 和 double 都可以。根据上面的转换规则,运算符两边均会转换为浮点型进行运算,结果也 是一个浮点型。这样就能保留小数部分了。
#include <stdio.h>
int main()
{
int n;
float f;
n = 5;
f = 2;
printf("%f\n", n / f);
}
那我们再思考一下,对于字面常量来说,是不是也存在这种问题呢?
#include <stdio.h>
int main()
{
printf("%d\n", 5 / 2); // int与int运算,结果为int
printf("%f\n", 5 / 2.0); // int与double运算,结果为double
printf("%f\n", 5.0 / 2.0); // double与double运算,结果为double
}
5 / 2,int与int运算,结果为int 5 / 2.0,
int与double运算,结果为double 5.0 / 2.0,
double与double运算,结果为double
确实如此,字面常量也有这个问题。至于字面常量是什么类型,我们可以用上面的小技巧判断一下。
常量的类型可以参考文章变量与常量。
6. 强制类型转换
如果我不想修改上面代码,依旧保持 n1 或 n2 为整型,但是计算结果想要得到浮点型怎么办?
好像以目前我们了解的知识,是无法解答的。因为整型与整型运算是无法得到浮点型的。因此,我们引 入一个新的知识点,强制类型转换。
使用公式:
(类型)需要转换的数据对象
#include <stdio.h>
int main()
{
int n1, n2;
n1 = 5;
n2 = 2;
printf("%f\n", (float)n1 / n2);
printf("%f\n", (double)n1 / n2);
return 0;
}
在上面的代码中,我们把 n1 先强制转换为了 float型 , float 再除以 int 。那么结果就是 float 类型 了。 n2 先转换为 double ,再与 int 运算,结果为 double 。
请注意,强制类型转换,并不能影响n1和n2变量原本的类型。它们只是改变了运算时的临时数据对象的 类型。
7. 赋值造成的类型转换
#include <stdio.h>
int main()
{
int n;
char c = 123;
n = c;
printf("%d %d", c, n);
return 0;
}
上面的代码中,n是int型,c是字符型。我们把char型变量c值给了int型变量n,结果正常。
#include <stdio.h>
int main()
{
int n = 123456;
char c;
c = n;
printf("%d %d", c, n);
return 0;
}
如果反过来,把整型赋值给字符型呢?结果是整型变量n存储的数值遭到了丢失。毕竟,字符型的最大 范围是-128到127。
小的整型类型可以赋值给大的,大的整型类型请勿赋值给小的。 除非你有这种特殊需求。
十四. 关系运算符与逻辑运算符
1. 关系运算符
1.1 大于 > 和小于 <
#include <stdio.h>
int main()
{
printf("%d\n", 1 > 2);
printf("%d\n", 1 < 2);
return 0;
}
表达式结果:
表达式关系成立,表达式结果为真。在C语言中,用1表示真。
表达式关系不成立,表达式结果为假。在C语言中,用0表示假。
例如:
1 < 2,表达式关系成立,表达式结果为真,即1。
1 > 2,表达式关系不成立,表达式结果为假,即0。
1.2 大于等于 >= 和 小于等于 <=
如果希望可以取值到N点,即N点为实心,那么可以使用大于等于或小于等于运算符。
#include <stdio.h>
int main()
{
printf("%d\n", 1 >= 1);
printf("%d\n", 1 <= 1);
return 0;
}
因此以下两个表达式均为真。
1 >= 1 //为真 1 <= 1 //为真
下面是一些关系运算符示例:
大于等于:
10 >= 2,真
10 >= 10,真
2 >= 10,假
小于等于:
10 <= 2,假
10 <= 10,真
2 <= 10,真
2. 等于 == 和 不等于 !=
等于运算符以及不等于运算符,用于单纯地判断运算符两边的值是否相等。 由于 = 等号已经被作为赋值运算符,所以相等在C语言里面用 == 来表示,而不相等则使用 != 。
#include <stdio.h>
int main()
{
printf("%d\n", 10 == 10);
printf("%d\n", 10 != 10);
printf("%d\n", 10 == 12);
printf("%d\n", 10 != 12);
return 0;
}
10 == 10,10等于10,表达式结果为真。
10 != 10,10不等于10,表达式结果为假。
10 == 12,10等于12,表达式结果为假。
10 != 12,10不等于12,表达式结果为真。
3. 逻辑运算符
我们来看看在C语言里面,怎样表达数轴上的这两个条件。
我们能否使用表达式 2 <= x <=10 来表示呢?
我们使用数值0来检查一下,0不在这个区间内,按理说表达式结果应当为假。
让我们用表达式求值的方法来求一下这个表达式的值。
2 <= 0 <= 10
先计算表达式 2<=0 ,表达式结果为假,即0。
0 <= 10
0 <= 10 ,表达式结果为真,即1。
结果与我们预料的不一致,区间不能这样来表示。 我们想要的是让x,同时满足大于等于2且小于等于10这两个条件,而不是想用它直接求表达式结果。
我们需要结合两个子表达式的结果:
第一个数轴区间:x <= 2 或 x >= 10。
由于数值只要符合任意一个条件就满足要求,所以这里我们用或。
第二个数轴区间:x >= 2 与 x <= 10。
由于数值要同时符合两个条件才能满足要求,所以这里我们用与。 因此,这里必须引入新的运算符,来表达这种情况。
3.1 逻辑或 || 和 逻辑与 &&
逻辑或运算符写法为 ||, 两条竖线。
逻辑与运算符写法为 &&。
第一个数轴区间:(x <= 2) || (x >= 10)。
第二个数轴区间:(x >= 2) && (x <= 10)。
我们在上面加了 () ,确保两边完成之后,再进行 与运算 、 或运算 。然而,事实上,这里不加括号也是可以的。因为 || 和 && 的运算符优先级比关系运算符 >= 、 <= 低,所以按照优先级也会是先计算两边再 进行与或操作。
但是,为了着重一般会加上括号。
接下来,我们再用数值0带入第二个区间,验算一下结果是否为假吧。
(0 >= 2) && (0 <= 10)
0 >= 2 表达式的结果为假, 0 <= 10 表达式结果为真。
逻辑与运算&&,符要求两边均为真,整个表达式结果才为真。
因此表达式结果为假,符合我们的预期。
3.2 逻辑非 !
使用逻辑非运算符,我们可以对当前的结果,取它的反向。
例如,2 != 3,为真,但是如果加上逻辑非之后呢。
#include <stdio.h>
int main()
{
printf("%d\n", 2 != 3);
printf("%d\n", !(2 != 3)); // 尽量使用多一些的括号清晰地表达意图
return 0;
}
2 != 3,2不等于3,表达式结果为真。
!(2 != 3),将结果真取反向,表达式结果为假。
那我们再看看上面的区间,如果被逻辑非之后呢?
(x <= 2) || (x >= 10) 加上逻辑非,!( (x <= 2) || (x >= 10) )
为(x > 2) &&(x <10)
(x >= 2) && (x <= 10)加上逻辑非,!(x >= 2) && (x <= 10)
为(x < 2) || (x >10)
4.运算符优先级
int a;
int i = 0;
a = 9 / 3 + 1 * -2 && ++i || !6;
点个赞吧♥
图片来源:你好编程