CSDN 还是以「 大学生 」 居多,能上大学的都是「 精英 」,那么我们自然要「 精益求精 」,趁着开学季,和我一起打卡学习吧!利用这个时间 「 学好一门语言 」,三年后的你自然「 不能同日而语 」。
那么这里,我整理了「 C语言的基础语法 」大致一览:
直接跳到末尾 参与投票,获取粉丝专属福利。
☀️光天化日学C语言☀️(01)- 第一个 C语言程序
☀️光天化日学C语言☀️(02)- 如何搭建本地环境
☀️光天化日学C语言☀️(03)- 变量
☀️光天化日学C语言☀️(04)- 格式化输出
☀️光天化日学C语言☀️(05)- 格式化输入
☀️光天化日学C语言☀️(06)- 进制转换入门
☀️光天化日学C语言☀️(07)- ASCII码
☀️光天化日学C语言☀️(08)- 常量
☀️光天化日学C语言☀️(09)- 算术运算符
☀️光天化日学C语言☀️(10)- 关系运算符
☀️光天化日学C语言☀️(11)- 逻辑运算符
☀️光天化日学C语言☀️(12)- 类型转换
☀️光天化日学C语言☀️(13)- 位运算概览
☀️光天化日学C语言☀️(14)- 位运算 & 的应用
☀️光天化日学C语言☀️(15)- 位运算 | 的应用
☀️光天化日学C语言☀️(16)- 位运算 ^ 的应用
☀️光天化日学C语言☀️(17)- 位运算 ~ 的应用
☀️光天化日学C语言☀️(18)- 位运算 << 的应用
☀️光天化日学C语言☀️(19)- 位运算 >> 的应用
☀️光天化日学C语言☀️(20)- 赋值运算符
☀️光天化日学C语言☀️(21)- 逗号运算符
☀️光天化日学C语言☀️(22)- 运算符优先级和结合性
☀️光天化日学C语言☀️(23)- 整数的存储
☀️光天化日学C语言☀️(24)- 浮点数的存储
☀️光天化日学C语言☀️(25)- 浮点数的精度问题
☀️光天化日学C语言☀️(26)- if else 语句
☀️光天化日学C语言☀️(27)- 条件运算符
☀️光天化日学C语言☀️(28)- switch case 语句
☀️光天化日学C语言☀️(29)- while 语句
☀️光天化日学C语言☀️(30)- for 语句
☀️光天化日学C语言☀️(31)- break 关键字
☀️光天化日学C语言☀️(32)- continue 关键字
☀️光天化日学C语言☀️(33)- 函数
#include // (1)
int main() // (2)
{
/* 我的第一个 C 程序 */ // (3)
printf("Hello, World! \n"); // (4)
return 0; // (5)
}
这段代码只做了一件事情,就是向屏幕上输出一行字:
Hello, World!
。
( 1 ) (1) (1)stdio.h
是一个头文件 (标准输入输出头文件) ,#include
是一个预处理命令,用来引入头文件。当编译器遇到printf()
函数时,如果没有找到stdio.h
头文件,就会发生编译错误。
( 2 ) (2) (2)main()
作为这个程序的入口函数,代码都是从这个函数开始执行的。
( 3 ) (3) (3) 被/*
和*/
包围起来的代表注释,是给人看到,不进行代码的解析和执行。
( 4 ) (4) (4)printf
代表将内容输出到控制台上。其中\n
代表换行符。
( 5 ) (5) (5) 作为函数的返回值。
#include // (1)
int main() // (2)
{
/* 我的第一个 C 程序 */ // (3)
printf("光天化日学C语言! \n"); // (4)
return 0; // (5)
}
百度网盘下载
CSDN下载
#include
int main() {
printf("光天化日写C语言!\n");
return 0;
}
对于一个变量而言,有三部分组成:
1)变量类型;
2)变量名;
3)变量地址;
int Iloveyou;
1)变量类型
int
表示变量类型,是英文单词 Integer 的缩写,意思是整数。2)变量名
Iloveyou
表示变量名,也可以叫其它名字,例如:WhereIsHeroFrom
、ILoveYou1314
等等。Iloveyou
,用它来存放整数。int Iloveyou
表达了一个语句,要用分号来结束。3)变量地址
Iloveyou
这个变量里: Iloveyou = 520;
=
在数学中叫 “等于号”,例如 1 + 1 = 2
,但在C语言中,这个过程叫做变量的赋值,简称赋值。赋值是指把数据放到内存的过程。 int Iloveyou;
Iloveyou = 520;
int Iloveyou = 520;
Iloveyou
的值变成 520 520 520; int Iloveyou = 520;
Iloveyou = 521;
Iloveyou = 522;
Iloveyou = 523;
int x, y, z = 5;
z
初始化为 5,等价于如下代码: int x;
int y;
int z = 5;
int a, b;
520 = a; // 错误
a = b; // 正确
【例题1】给出如下代码,求输出结果是什么。
#include
int main()
{
int a = 1314, b = 520;
b = a;
a = b;
printf("a=%d b=%d\n", a, b);
return 0;
}
char a = 'a';
short b, c, d = 1314, e, f;
int g = 5201314;
long long h = 123456789;
float i = 4.5;
double j = 4.50000;
love
、Iloveyou
这样的名字,为了表达变量的作用,这就叫 标识符,即 Identifier。int
、char
、long
、int
、unsigned int
等。_aa
,a123
,_
都是合法的变量,?*
、a a
、#
、都是非法的变量;123abc
不是一个合法的变量名;o
和O
不是同一个变量;【例题2】给出一段程序,请回答这段程序的运行结果。
#include
int main()
{
int IloveYou = 0;
ILoveYou = 1314;
ILoveYou = ILoveYou;
ILoveYou = 520;
printf("%d\n", ILoveYou);
return 0;
}
在C语言中,有三个函数可以用来在屏幕上输出数据,它们分别是:
1)puts() :只能输出字符串,并且输出结束后会自动换行;
2)putchar() :只能输出单个字符;
3)printf():可以输出各种类型的数据,作为最灵活、最复杂、最常用的输出函数,可以完全替代全面两者,所以是必须掌握的,今天我们就来全面了解一下这个函数。
printf
前几个章节都有提及,这个函数的命名含义是:Print(打印) 和 Format (格式) ,即 格式化输出。#include
int main()
{
int a = 520;
long long b = 1314;
printf("a is %d, b is %lld!\n", a, b);
return 0;
}
int
而言,我们利用%d
将要输出的内容进行格式化,然后输出,简单的理解就是把%d
替换为对应的变量,%lld
用于对long long
类型的变量进行格式化,所以这段代码的输出为:a is 520, b is 1314!
#include
int main()
{
float f = 1.2345;
double df = 123.45;
printf("f is %.3f, df is %.0lf\n", f, df);
return 0;
}
%f
来对单精度浮点数float
进行格式化;用%lf
来对双精度浮点数进行格式化,并且用.
加 “数字” 来代表要输出的数精确到小数点后几位,这段代码的输出为:f is 1.235, df is 123
#include
int main()
{
char ch = 'A';
printf("%c\n", ch);
return 0;
}
%c
来进行格式化;C语言中的字符是用单引号引起来的,当然,字符这个概念扯得太远,会单独开一个章节来讲,具体可以参考 ASCII 码。\n
,这个符号是一个转义符,它代表的不是两个字符(反斜杠\
和字母n
),而是换行的意思;A
【例题1】第1行输出1个1,第2行输出2个2,第3行输出3个3,第4行输出4个4。
#include
int main()
{
printf("1\n");
printf("22\n");
printf("333\n");
printf("4444\n");
return 0;
}
#include
int main()
{
printf("1\n22\n333\n4444\n");
return 0;
}
%s
进行格式化的即可,代码如下:#include
int main()
{
char str[100] = "I love you!";
printf("%s\n", str);
return 0;
}
I love you!
%
和一个字母,事实上,在百分号和字母之间,还有一些其它的内容。主要包含如下内容:
1)负号:如果有,则按照左对齐输出;
2)数字:指定字段最小宽度,如果不足则用空格填充;
3)小数点:用与将最小字段宽度和精度分开;
4)精度:用于指定字符串重要打印的而最大字符数、浮点数小数点后的位数、整型最小输出的数字数目;
【例题2】给定如下一段代码,求它的输出内容。
#include
int main()
{
double x = 520.1314;
int y = 520;
printf("[%10.5lf]\n", x);
printf("[%-10.5lf]\n", x);
printf("[%10.8d]\n", y);
printf("[%-10.8d]\n", y);
return 0;
}
[ 520.13140]
[520.13140 ]
[ 00000520]
[00000520 ]
#include
int main()
{
char name[100] = "Zhou";
int old = 18;
double meters = 1.7;
char spostfix = 's';
printf("My name is %s, %d years old, %.2lf meter%c.\n",
name, old, meters, spostfix);
return 0;
}
My name is Zhou, 18 years old, 1.70 meters.
在C语言中,有三个函数可以用来在键盘上输入数据,它们分别是:
1)gets() :用于输入一行字符串;
2)getchar() :用于输入单个字符;
3)scanf():可以输入各种类型的数据,作为最灵活、最复杂、最常用的输入函数,虽然无法完全替代前面两者,但是却是必须掌握的,今天我们就来全面了解一下这个函数。
scanf
的函数的命名含义是:Scan(扫描) 和 Format (格式) ,即 格式化输入。#include
int main()
{
int a;
scanf("%d", &a);
printf("%d\n", a);
return 0;
}
1314↙
1314
其中
↙
代表回车,即我们通过键盘输入1314
,按下回车后,在屏幕上输出1314
。
类比输出,我们发现,输入和输出的差别在于:
( 1 ) (1) (1) 函数名不同;
( 2 ) (2) (2) 输入少了换行符\n
;
( 3 ) (3) (3) 输入多了取地址符&
;
#include
int main()
{
int a, b;
scanf("%d", &a);
scanf("%d", &b);
printf("%d %d\n", a, b);
return 0;
}
520↙
1314↙
520 1314
其中
↙
代表回车,即我们通过键盘输入520
,按下回车,再输入1314
,按下回车后,在屏幕上输出520 1314
。
#include
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d %d\n", a, b);
return 0;
}
520 1314↙
520 1314
其中
↙
代表回车,即我们通过键盘输入520
、空格
、1314
,按下回车后,在屏幕上输出520 1314
。
scanf
语句来完成。#include
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d %d\n", a, b);
return 0;
}
520 1314↙
520 1314
其中
↙
代表回车,即我们通过键盘输入520
、n个空格
、1314
,按下回车后,在屏幕上输出520 1314
。
#include
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("%d %d\n", a, b);
return 0;
}
520 1314↙
520 1314
其中
↙
代表回车,即我们通过键盘输入520
、1个空格
、1314
,按下回车后,在屏幕上输出520 1314
。
scanf()
是以回车来结算一次输入的。scanf()
开始读取用户输入的内容,并根据我们定义好的格式化内容从中提取有效数据,只要用户输入的内容和格式化内容匹配,就能够正确提取。#include
int main()
{
int a, b, c, d;
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("%d %d %d %d\n", a, b, c, d);
return 0;
}
1 2 3 4↙
1 2 3 4
1 2 3↙
4↙
1 2 3 4
1 2 3 4 5↙
1 2 3 4
scanf()
,如下:#include
int main()
{
int a, b, c, d, e;
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("%d %d %d %d\n", a, b, c, d);
scanf("%d", &e);
printf("%d\n", e);
return 0;
}
1 2 3 4 5↙
1 2 3 4
5
scanf()
,而是放入了输入缓冲区中,当我们按下回车键,scanf()
才到输入缓冲区中读取数据。如果缓冲区中的数据符合 scanf()
给定的格式要求,那么就读取结束;否则,继续等待用户输入,或者读取失败。【例题1】给定一段代码,如下,并且给出一个输入,请问输出是什么。
#include
int main()
{
int a = 9, b = 8, c = 7, d = 6, e = 5;
scanf("%d %d %d %d", &a, &b, &c, &d);
printf("%d %d %d %d\n", a, b, c, d);
scanf("%d", &e);
printf("%d\n", e);
return 0;
}
1 2b 3 4 5↙
printf
,如下:&
可以不加,如下:#include
int main()
{
char str[100];
scanf("%s", str); // (1)
printf("%s\n", str);
scanf("%s", &str); // (2)
printf("%s\n", str);
return 0;
}
#include
int main()
{
char str[100];
int height;
printf("请大侠输入姓名:");
scanf("%s", str);
printf("请大侠输入身高(cm):");
scanf("%d", &height);
printf("%s大侠,身高%dcm,骨骼惊奇,是百年难得一遇的人才,只要好好学习C语言,日后必成大器!\n", str, height);
return 0;
}
两只鞋子 = 1双鞋子;
二个抓手 = 1双手;
3个月 = 1个季度;
4个季度 = 1年
七进制:7天 = 1周;
十二进制:12瓶啤酒 = 1打;
二十四进制:24小时 = 1天;
六十进制:60秒 = 1分钟;60分钟 = 1小时;
0b
作为前缀,然后跟上0
和1
组成的数字,我们来看一段代码:#include
int main() {
int a = 0b101;
printf("%d\n", a);
return 0;
}
5
%d
代表输出的数是十进制,所以我们需要将二进制转换成十进制以后输出,0b101
的数学表示如下: ( 101 ) 2 (101)_2 (101)2进制 | 零 | 一 | 二 | 三 | 四 | 五 |
---|---|---|---|---|---|---|
二进制 | 0 | 1 | 10 | 11 | 100 | 101 |
十进制 | 0 | 1 | 2 | 3 | 4 | 5 |
101
对应于十进制下的 5。#include
int main() {
int a = 0123;
printf("%d\n", a);
return 0;
}
83
0
,然后跟上0-7
的数字;123
这个八进制数转换成十进制后再输出。而转换结果就是83
,由于这里数字较大,我们已经无法一个一个数出来了,所以需要进行进制转换,关于进制转换,在第四节进制转换初步里再来讲解。0x
或者0X
作为前缀,跟上0-9
、a-f
、A-F
的数字,其中大小写字母的含义相同,分别代表从10
到15
的数字。如下表所示:小写字母 | 大写字母 | 代表数字 |
---|---|---|
a |
A |
10 |
b |
B |
11 |
c |
C |
12 |
d |
D |
13 |
e |
E |
14 |
f |
F |
15 |
#include
int main() {
int a = 0X123;
printf("%d\n", a);
return 0;
}
291
对于 X 进制的数来说,我们定义以下几个概念:
【概念1】对于数字部分从右往左编号为 0 到 n n n,第 i i i 个数字位表示为 d i d_i di,这个数字就是 d n . . . d 1 d 0 d_{n}...d_1d_0 dn...d1d0;
【概念2】每个数字位有一个权值;
【概念3】第 i i i 个数字位的权值为 X i X^i Xi;
0123
,转换成十进制,只需要套用公式:0X123
,转换成十进制,套用同样的公式,如下:291
我们可以通过如下方式,转换成 十六进制。291 除 16 ========== 余 3
18 除 16 =========== 余 2
1 除 16 ============ 余 1
83 除 8 ============ 余 3
10 除 8 ============ 余 2
1 除 8 ============= 余 1
通过这一章,我们学会了:
1)二进制的表示方式为:0b
作为前缀,加上0-1
组成的数字;
2)八进制的表示方式为:0
作为前缀,加上0-7
组成的数字;
3)十六进制的表示方式为:0x
或者0X
作为前缀,加上0-9
、a-f
,A-F
组成的数字;
4)X进制转换成十进制;
5)十进制转换成X进制;
a-z
、A-Z
这样的52个字母以及0-9
的数字还有一些常用的符号(例如?*#@!@#$%^&*()
等)在计算机中存储时也要使用二进制数来表示,具体用哪些二进制数字表示哪个符号,每个人都可以约定自己的一套规则,这就叫编码。0
到9
、标点符号,以及在英语中使用的特殊控制字符。%c
来控制,如下:#include
int main() {
printf("%c\n", '0');
printf("%c\n", 'A');
printf("%c\n", 'a');
printf("%c\n", '$');
return 0;
}
0
A
a
$
%d
来控制,如下:#include
int main() {
printf("%d\n", '0');
printf("%d\n", 'A');
printf("%d\n", 'a');
printf("%d\n", '$');
return 0;
}
48
65
97
36
'0'
的整数编码为48,字符'1'
的整数编码为49,以此类推。#include
int main() {
printf("%c\n", '0' + 5);
printf("%c\n", 'A' + 3);
printf("%c\n", 'a' + 5);
printf("%c\n", '$' + 1);
return 0;
}
5
D
f
%
'0'
向右偏移 5 个单位,就是字符'5'
;同样的,'A'
向右偏移3个单位,就是字符'D'
。【例题1】给出如下代码,给出它的输出结果。
#include
int main() {
printf("%c\n", 'A' - 10);
return 0;
}
'0'
加上1
以后等于'1'
,那么顺理成章可以得出:'0' < '1'
。'a' < 'b'
、'X' < 'Y'
。通过这一章,我们学会了:
1)ASCII 码的表示;
2)ASCII 码的运算;
3)ASCII 码的比较;
进制 | 前缀部分 | 数字部分 | 后缀部分 |
---|---|---|---|
二进制 | 0b |
0-1 |
u 、l 、ll |
八进制 | 0 |
0-7 |
u 、l 、ll |
十进制 | 无 | 0-9 |
u 、l 、ll |
十六进制 | 0x 或0X |
0-9 、a-f 、A-F |
u 、l 、ll |
u
(unsigned
)代表无符号整数,l
(long
)代表长整型,ll
代表long long
。【例题1】说出以下整型常量中,哪些是非法的,为什么非法。
1314
520u
0xFoooooL
0XFeeeul
018888
0987UU
0520
0x4b
1024llul
30ll
030ul
3.1415927
4.5f
.1314
f
后缀代表 float
,用于区分double
。.1314
等价于0.1314
。xey
,如下: 1e9
5.2e000000
5.2e-1
1.1e2
10
的指数部分,所以是支持负数的。 'a'
'Q'
'8'
'?'
'+'
' '
'\n'
代表换行,\t
代表水平制表符(可理解为键盘上的 tab 键),'\\'
代表一个反斜杠,等等;'\ooo'
来代替一个字符,其中一个数字o
代表一个八进制数;也可以用 '\xhh'
来代表一个字符,具体见如下代码:#include
int main() {
char a = 65;
char b = '\101';
char c = '\x41';
printf("%c %c %c\n", a, b, c);
return 0;
}
A A A
101
和十六进制的41
在十进制下都是65
,代表的是大写字母'A'
的ASCII 码值。【例题1】请问如何输出一个单引号?
""
中的。一个字符串包含类似于字符常量的字符:普通字符、转义序列。#include
int main() {
printf( "光天化日学\x43语言!\n" );
return 0;
}
'\x43'
代表'C'
和其它字符组合,变成一个字符串常量。以上代码输出为:光天化日学C语言!
【例题2】如果我想要如下输出结果,请问,代码要怎么写?
"光天化日学C语言!"
""
引起来的字符串,是可以无缝连接的,如下代码:#include
int main() {
printf(
"光天化日学"
"C语言!\n"
);
return 0;
}
光天化日学C语言!
#define
预处理器可以定义一个常量如下:#include
#define TIPS "光天化日学\x43语言!\n"
#define love 1314
int main() {
printf( TIPS );
printf("%d\n", love);
return 0;
}
TIPS
都原文替换为"光天化日学\x43语言!\n"
;将所有love
替换为1314
。const
的用法也非常广泛,而且涉及到很多概念,这里只介绍最简单的用法,后面会开辟一个新的章节专门来讲它的用法。#include
const int love = 1314;
int main() {
printf( "%d\n", love );
return 0;
}
const
,这样就代表它是个常量了,在整个运行过程中都不能被修改。【例题3】下面这段代码会发生什么情况,自己编程试一下吧。
#include
const int love = 1314;
int main() {
love = 520;
printf( "%d\n", love );
return 0;
}
/ | 加法 | 减法 | 乘法 | 除法 |
---|---|---|---|---|
数学 | + | - | × \times × | ÷ |
C语言 | + | - | * | / |
a + b
代表两个操作数相加,代码如下:#include
int main() {
int a = 1, b = 2;
double c = 1.005, d = 1.995;
printf("a + b = %d\n", a + b );
printf("c + d = %.3lf\n", c + d);
printf("a + c = %.3lf\n", a + c);
return 0;
}
a + b = 3
c + d = 3.000
a + c = 2.005
a - b
代表从第一个操作数中减去第二个操作数,代码如下:#include
int main() {
int a = 1, b = 2;
double c = 1.005, d = 1.995;
printf("a - b = %d\n", a - b );
printf("c - d = %.3lf\n", c - d);
printf("a - c = %.3lf\n", a - c);
return 0;
}
a - b = -1
c - d = -0.990
a - c = -0.005
a * b
代表两个操作数相乘,代码如下:#include
int main() {
int a = 1, b = 2;
double c = 1.005, d = 1.995;
printf("a * b = %d\n", a * b);
printf("c * d = %.3lf\n", c * d);
printf("a * c = %.3lf\n", a * c);
return 0;
}
a * b = 2
c * d = 2.005
a * c = 1.005
不同类型的除数和被除数会导致不同类型的运算结果。
1)当 除数 和 被除数 都是整数时,运算结果也是整数;
1.a)如果能整除,结果就是它们相除的商;
1.b)如果不能整除,那么就直接丢掉小数部分,只保留整数部分,即数学上的 取下整;
2)除数和被除数中有一个是小数,那么运算结果也是小数,并且是 double 类型的小数。
#include
int main() {
int a = 6, b = 3, c = 4;
double d = 4;
printf("a / b = %d\n", a / b );
printf("a / c = %d\n", a / c);
printf("a / d = %.3lf\n", a / d);
return 0;
}
a / b = 2
a / c = 1
a / d = 1.500
a
能被整除b
,所以第一行输出它们的商,即 2
;a
不能被整除c
,所以第二行输出它们相除的下整,即 1
;a
和d
中,d
为浮点数,所以相除得到的也是浮点数;#include
int main() {
int a = 5, b = 0;
int c = a / b;
return 0;
}
%
。C语言中的取余运算只能针对整数,也就是说,%
两边都必须是整数,不能出现小数,否则会出现编译错误。5 % 3 = 2
、7 % 2 = 1
。当然,余数可以是正数也可以是负数,由
%
左边的整数决定:
1)如果%
左边是正数,那么余数也是正数;
2)如果%
左边是负数,那么余数也是负数。
#include
int main()
{
printf(
"9%%4=%d\n"
"9%%-4=%d\n"
"-9%%4=%d\n"
"-9%%-4=%d\n",
9%4,
9%-4,
-9%4,
-9%-4
);
return 0;
}
""
引起来的字符串是可以无缝连接的,所以这段代码里面四个字符串相当于一个。而%
在printf
中是用来做格式化的,所以想要输出到屏幕上,需要用%%
。于是,我们得到输出结果如下:9%4=1
9%-4=1
-9%4=-1
-9%-4=-1
x = x + 1;
x++;
++x;
#include
int main()
{
int x = 1;
printf( "x = %d\n", x++ );
printf( "x = %d\n", x );
return 0;
}
x = 1
x = 2
x
在自增前,就已经把值返回了,所以输出的是原值。我们再来看另一种情况:#include
int main()
{
int x = 1;
printf( "x = %d\n", ++x );
printf( "x = %d\n", x );
return 0;
}
x = 2
x = 2
x
先进行了自增,再把值返回,所以输出的是自增后的值。通过这一章,我们学会了:
1)四则运算符;
2)取余运算符;
3)自增和自减运算符;
关系运算符释义 | C语言表示 | 数学表示 |
---|---|---|
大于 | > | > |
大于等于 | >= | ≥ |
等于 | == | = |
不等于 | != | ≠ |
小于 | < | < |
小于等于 | <= | ≤ |
#include
int main() {
printf("%d\n", 1 > 2);
printf("%d\n", 1 < 2);
return 0;
}
0
1
1 > 2
在数学上是不成立的,所以结果为0
;而1 < 2
在数学上是不成立的,所以结果为1
;#include
int main() {
printf("%d\n", 1 > 2 > -1);
return 0;
}
1 > 2
的结果为0
,所以1 > 2 > -1
等价于0 > -1
,显然是成立的,所以输出的结果为:1
【例题1】给出以下代码,问输出的结果是什么。
#include
int main() {
printf("%d\n", 1 < 2 > 1);
printf("%d\n", 3 > 2 > 1);
return 0;
}
!=
和==
的优先级低于>
,<
,>=
,<=
。#include
int main() {
printf("%d\n", 1 < 2 == 1);
return 0;
}
==
优先级低于<
;1 < 2
优先计算,则表达式等价于1 == 1
,成立,输出1
。==
优先级高于<
;2 == 1
优先计算,则表达式等价于1 < 0
,不成立,输出0
。1
==
的优先级低于<
,当然,同学们可以试下 !=
和其它符号的关系。if
语句中,例如:if(a < b) {
// TODO
}
if
语句的时候继续复习关系运算符相关的知识哦~==
和=
搞混,前者是判断相等与否,而后者是赋值。#include
int main() {
int a = 0;
printf("%d\n", a = 0);
printf("%d\n", a == 0);
return 0;
}
0
1
通过这一章,我们学会了:
1)6种关系运算符;
2)关系运算符的嵌套;
3)关系运算符的优先级;
逻辑运算符释义 | 操作数个数 | C语言表示 | 数学表示 |
---|---|---|---|
与 | 二元操作符 | && |
∧ \land ∧ |
或 | 二元操作符 | || |
∨ \lor ∨ |
非 | 一元操作符 | ! |
¬ \lnot ¬ |
对于与运算,参与运算的操作数都为 “真” 时,结果才为 “真”,否则为 “假”。
#include
int main() {
printf("%d\n", 0 && 0); // 0
printf("%d\n", 5 && 0); // 0
printf("%d\n", 0 && 5); // 0
printf("%d\n", 5 && 9); // 1
return 0;
}
&&
运算符自身的运算规则进行运算。对于或运算,参与运算的操作数都为“假”时,结果才为“假”,否则为“真”。
#include
int main() {
printf("%d\n", 0 || 0); // 0
printf("%d\n", 5 || 0); // 1
printf("%d\n", 0 || 5); // 1
printf("%d\n", 5 || 9); // 1
return 0;
}
||
运算符自身的运算规则进行运算。对于非运算,操作数为 “真”,运算结果为 “假”;操作数为 “假”,运算结果为 “真”;
#include
int main() {
printf("%d\n", !0); // 1
printf("%d\n", !5); // 0
return 0;
}
#include
int main() {
int a = !( (5 > 4) && (7 - 8) && (0 - 1) );
printf("%d\n", a);
return 0;
}
(5 > 4)
和(7 - 8)
这两个表达式进行与运算,等价于:1 && 1
,结果为1
。1
和(0 - 1)
继续进行与运算,等价于1 && 1
,结果为1
。1
进行非运算,得到结果为 0
。0
#include
int main() {
int a = !( 1 || 1 && 0 );
printf("%d\n", a);
return 0;
}
0
1 || 1
的两边加上一个括号。#include
int main() {
int a = !( (1 || 1) && 0 );
printf("%d\n", a);
return 0;
}
1
&&
的优先级是比||
要高的,所以在没有任何括号的情况下,&&
会优先计算,简而言之,对于刚才的( 1 || 1 && 0 )
,我们把它等价成( 1 || (1 && 0) )
,这样是不是就好理解了。!
的优先级是最高的,所以这三个符号的优先级排序如下:通过这一章,我们学会了:
1)与运算:有假必假;
2)或运算:有真必真;
3)非运算:非真即假,非假即真;
#include
int main() {
float love = 520;
return 0;
}
520
原本是int
类型的数据,为了赋值给love
,他需要转换成float
类型。#include
int main() {
int loveyou = 11 / 9;
return 0;
}
love / 9
的值明显不是一个整数,但是它需要赋值给int
,所以需要先转换为int
类型以后,才能赋值给变量loveyou
。转换原则如下:
1)数据长度短的向输出长度长的进行转换;
2)精度低的向精度高的进行转换;
float
类型,也要先转换为double
类型,才能进行运算。char
和short
参与运算时,必须先转换成int
类型。#include
#include
const float PI = acos(-1.0); // 3.1415926535...
int main(){
int c1, r = 10;
double c2;
c1 = 2 * PI * r;
c2 = 2 * PI * r;
printf("c1=%d, c2=%lf\n", c1, c2);
return 0;
}
c1=62, c2=62.831855
c1
是int
类型,c2
是double
类型,赋值号右边的内容是计算圆的周长,完全相同,但是就是由于被赋值的变量类型不同,从而导致运算结果截然不同。double
类型。但由于c1
为int
类型,所以赋值运算的结果仍为int
类型,舍去了小数部分,导致数据失真。(type_name) expression
#include
int main(){
long long x = 1 << 32;
printf("%lld\n", x);
return 0;
}
x
中。0
1
是int
类型,所以进行左移32
位时,产生了溢出,所以变成了0
,这里涉及到补码相关的知识,我会在后续章节详细进行讲解。long long
再进行左移运算,如下:#include
int main(){
long long x = (long long)1 << 32;
printf("%lld\n", x);
return 0;
}
4294967296
#include
int main(){
int a = 10;
int b = 3;
double c = a / b;
printf("%lf\n", c);
return 0;
}
3.000000
a
和b
都是int
类型,如果不进行干预,那么a / b
的运算结果也是int
类型,小数部分将被丢弃;虽然是c
是double
类型,可以接收小数部分,但是在赋值之前,小数部分提前就被舍弃了,它只能接收到整数部分,这就导致除法运算的结果失真。#include
int main(){
int a = 10;
int b = 3;
double c = (double) a / b;
printf("%lf\n", c);
return 0;
}
a
或b
其中之一转换成double
,然后再参与运算即可。#include
int main(){
int a = 10;
int b = 3;
double c = (a + 0.0) / b;
printf("%lf\n", c);
return 0;
}
#include
int main(){
int a = 10;
int b = 3;
double c = (a * 1.0) / b;
printf("%lf\n", c);
return 0;
}
double
类型的数,使得整个表达式转换成double
。double
转换成指针,当然,有些强制转换可能直接导致程序崩溃。通过这一章,我们学会了:
1)类型转换;
2)自动类型转换;
3)强制类型转换;
1、101、1100011、100101010101 都是二进制数。
123、423424324、101020102101AF 则不是,因为有 0 和 1 以外的数字位。
二进制加法采用从低到高的位依次相加,当相加的和为2时,则向高位进位。
二进制减法采用从低到高的位依次相减,当遇到 0 减 1 的情况,则向高位借位。
C语言运算符表示 | 含义 | 示例 |
---|---|---|
& |
位与 | x & y |
| |
位或 | x | y |
^ |
异或 | x ^ y |
~ |
按位取反 | x ~ y |
左操作数 | 右操作数 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
#include
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf("%d\n", (a & b) ); // (3)
return 0;
}
0b
作为前缀,表示这是一个二进制数。那么a
的实际值就是 ( 1010 ) 2 (1010)_2 (1010)2。b
的实际值就是 ( 0110 ) 2 (0110)_2 (0110)2;a & b
就是对 ( 1010 ) 2 (1010)_2 (1010)2 和 ( 0110 ) 2 (0110)_2 (0110)2 的每一位做表格中的&
运算。2
左操作数 | 右操作数 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
#include
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a | b) );
return 0;
}
14
左操作数 | 右操作数 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
#include
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a ^ b) );
return 0;
}
12
#include
int main() {
int a = 0b1;
printf("%d\n", ~a );
return 0;
}
C语言运算符表示 | 含义 | 示例 |
---|---|---|
<< |
左移 | x << y |
>> |
右移 | x >> y |
x << y
代表将二进制的 x x x 的末尾添加 y y y 个零,就好比向左移动了 y y y 位。x >> y
代表将二进制的 x x x 从右边开始截掉 y y y 个数,就好比向右移动了 y y y 位。通过这一章,我们学会了:
1)位与 & ;
2)位或 |
3)异或 ^;
4)按位取反 ~;
5)左移 <<;
6)右移 >>;
x & y
。左操作数 | 右操作数 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 0 |
1 | 0 | 0 |
1 | 1 | 1 |
#include
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf("%d\n", (a & b) ); // (3)
return 0;
}
0b
作为前缀,表示这是一个二进制数。那么a
的实际值就是 ( 1010 ) 2 (1010)_2 (1010)2。b
的实际值就是 ( 0110 ) 2 (0110)_2 (0110)2;a & b
就是对 ( 1010 ) 2 (1010)_2 (1010)2 和 ( 0110 ) 2 (0110)_2 (0110)2 的每一位做表格中的&
运算。2
%
来判断的,如下:#include
int main() {
if(5 % 2 == 1) {
printf("5是奇数\n");
}
if(6 % 2 == 0) {
printf("6是偶数\n");
}
return 0;
}
#include
int main() {
if(5 & 1) {
printf("5是奇数\n");
}
if( (6 & 1) == 0 ) {
printf("6是偶数\n");
}
return 0;
}
- | 二进制末尾位 |
---|---|
奇数 | 1 |
偶数 | 0 |
0b1
进行位与,结果为零,则必然这个数的二进制末尾位为0,根据以上表就能得出它是偶数了;否则,就是奇数。if
语句我们还没有实际提到过,所以这里简单提一下,后面会有系统的讲解: if( expr ) {
body }
expr
代表的是一个表达式,表达式的值最后只有 零 或 非零,如果值为非零,才会执行body
中的内容。【例题1】给定一个数,求它的二进制表示的末五位,以十进制输出即可。
0b11111
,这样一来就直接得到末五位的值了。#include
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0b11111) );
return 0;
}
【例题2】如果是想得到末七位、末九位、末十四位、末 K 位,应该如何实现呢?
【例题3】给定一个 32 位整数,要求消除它的末五位。
0xffffffe0
。#include
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0xffffffe0) );
return 0;
}
【例题4】给出一个整数,现在要求将这个整数转换成二进制以后,将末尾连续的1都变成0,输出改变后的数(以十进制输出即可)。
【例题5】请用一句话,判断一个正数是不是2的幂。
x & (x-1)
必然为零。而其他情况则不然。 (x & (x-1)) == 0
通过这一章,我们学会了:
1)用位运算 & 来做奇偶性判定;
2)用位运算 & 获取一个数的末五位,末七位,末K位;
3)用位运算 & 消除某些二进制位;
4)用位运算 & 消除末尾连续 1;
x | y
。左操作数 | 右操作数 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 1 |
#include
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf("%d\n", (a | b) ); // (3)
return 0;
}
0b
作为前缀,表示这是一个二进制数。那么a
的实际值就是 ( 1010 ) 2 (1010)_2 (1010)2。b
的实际值就是 ( 0110 ) 2 (0110)_2 (0110)2;a | b
就是对 ( 1010 ) 2 (1010)_2 (1010)2 和 ( 0110 ) 2 (0110)_2 (0110)2 的每一位做表格中的|
运算。14
【例题1】给定一个数,判断它二进制低位的第 5 位,如果为 0,则将它置为 1。
#include
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x | 0b10000);
return 0;
}
【例题2】给定一个数,判断它二进制低位的第 5 位,如果为 1,则将它置为 0。
#include
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x & 0b11111111111111111111111111101111);
return 0;
}
我们可以配合减法来用。分成以下两步:
1)首先,强行将低位的第5位置成1;
2)然后,强行将低位的第5位去掉;
#include
int main() {
int x;
int a = 0b10000;
scanf("%d", &x);
printf("%d\n", (x | a) - a );
return 0;
}
【例题3】给定一个整数 x x x,将它低位连续的 0 都变成 1。
#include
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x | (x-1) ); // (1)
return 0;
}
x | (x-1)
就是题目所求的 “低位连续零变一” 。【例题4】给定一个整数 x x x,将它低位第一个 0 变成 1。
通过这一章,我们学会了:
1)用位运算 | 来做标记位的设置;
2)用位运算 | 来做标记位的清除;
3)用位运算 | 将低位连续的零变成一;
x ^ y
。左操作数 | 右操作数 | 结果 |
---|---|---|
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
#include
int main() {
int a = 0b1010; // (1)
int b = 0b0110; // (2)
printf("%d\n", (a ^ b) ); // (3)
return 0;
}
0b
作为前缀,表示这是一个二进制数。那么a
的实际值就是 ( 1010 ) 2 (1010)_2 (1010)2。b
的实际值就是 ( 0110 ) 2 (0110)_2 (0110)2;a ^ b
就是对 ( 1010 ) 2 (1010)_2 (1010)2 和 ( 0110 ) 2 (0110)_2 (0110)2 的每一位做表格中的^
运算。12
【例题1】给定一个数,将它的低位数起的第 4 位取反,0 变 1,1 变 0。
0b1000
就能变成 0;如果第 4 位 为 0,则让它异或上 0b1000
就能变成 1,也就是无论如何都是异或上 0b1000
,代码如下:#include
int main() {
int x;
scanf("%d", &x);
printf("%d\n", x ^ 0b1000);
return 0;
}
【例题2】给定两个数 a a a 和 b b b,用异或运算交换它们的值。
#include
int main() {
int a, b;
while (scanf("%d %d", &a, &b) != EOF) {
a = a ^ b; // (1)
b = a ^ b; // (2)
a = a ^ b; // (3)
printf("%d %d\n", a, b);
}
return 0;
}
b
等于a ^ b ^ b
,根据异或的几个性质,我们知道,这时候的b
的值已经变成原先a
的值了。a
等于a ^ b ^ a
,还是根据异或的几个性质,这时候,a
的值已经变成了原先b
的值。a
和b
的交换。【例题3】输入 n n n 个数,其中只有一个数出现了奇数次,其它所有数都出现了偶数次。求这个出现了奇数次的数。
#include
int main() {
int n, x, i, ans;
scanf("%d", &n);
ans = 0;
for(i = 0; i < n; ++i) {
scanf("%d", &x);
ans = (ans ^ x);
}
printf("%d\n", ans);
return 0;
}
【例题4】给定一个 n − 1 n-1 n−1 个数,分别代表 1 到 n n n 的其中 n − 1 n-1 n−1 个,求丢失的那个数。
通过这一章,我们学会了:
1)用位运算 ^ 来做标记位的取反;
2)用位运算 ^ 来做变量交换;
3)用位运算 ^ 找出出现奇数次的数;
4)用位运算 ^ 的加密解密;
~x
。操作数 | 取反结果 |
---|---|
0 | 1 |
1 | 0 |
#include
int main() {
int a = 0b1;
printf("%d\n", ~a );
return 0;
}
~a
代表的是对二进制数 1 进行取反,直观感受应该是 0。-2
~ 00000000 00000000 00000000 00000001
--------------------------------------
11111111 11111111 11111111 11111110
正数的补码是它本身,符号位为 0;负数的补码为正数数值二进制位取反后加一,符号位为一;
-2
的补码计算,需要经过两步: ~ 00000000 00000000 00000000 00000010
--------------------------------------
11111111 11111111 11111111 11111101
11111111 11111111 11111111 11111101
+ 00000000 00000000 00000000 00000001
--------------------------------------
11111111 11111111 11111111 11111110
~1
的结果。int
类型,就有:2^32 = 1 00000000 00000000 00000000 00000000
2^32 - 1 = 11111111 11111111 11111111 11111111
2^32 - 2 = 11111111 11111111 11111111 11111110
...
-2
的二进制表示。【例题1】0 的取反结果为多少呢?
~ 00000000 00000000 00000000 00000000
--------------------------------------
11111111 11111111 11111111 11111111
-1
。int
,分别为unsigned int
和signed int
,我们之前讨论的int
都是signed int
的简称。signed int
而言,最高位表示符号位,所以只有31位能表示数值,能够表示的数值范围是: − 2 31 ≤ x ≤ 2 31 − 1 -2^{31} \le x \le 2^{31}-1 −231≤x≤231−1%d
,如下:#include
int main() {
printf("%d\n", ~0 );
return 0;
}
-1
unsigned int
而言,由于不需要符号位,所以总共有32位表示数值,数值范围为:%u
,如下:#include
int main() {
printf("%u\n", ~0 );
return 0;
}
4294967295
【例题2】给定一个
int
类型的正数 x x x,求 x x x 的相反数(注意:不能用负号)。
~x + 1
。#include
int main() {
int x = 18;
printf("%d\n", ~x + 1 );
return 0;
}
-18
【例题3】给定两个
int
类型的正数 x x x 和 y y y,实现 x − y x - y x−y(注意:不能用减号)。
x - y
其实就可以表示成x + (-y)
,而-y
又可以表示成~y + 1
,所以减法 x - y
就可以用x + ~y + 1
来代替。#include
int main() {
int a = 8;
int b = 17;
printf("%d\n", a + ~b + 1 );
return 0;
}
-9
【例题4】给定两个
int
类型的正数 x x x 和 y y y,实现 x + y x + y x+y(注意:不能用加号)。
x + y
变成x - (-y)
,而-y
又可以替换成 ~y + 1
;x + y
就变成了x - ~y - 1
,不用加号实现了加法运算。#include
int main() {
int x = 18;
int y = 7;
printf("%d\n", x - ~y - 1 );
return 0;
}
25
通过这一章,我们学会了:
1)按位取反运算符;
2)补码的运算;
3)有符号整型和无符号整型;
4)相反数、加法、减法、等于判定的另类解法;
x << y
。其中x
和y
均为整数。x << y
念作:“将 x x x 左移 y y y 位”,这里的位当然就是二进制位了,那么它表示的意思也就是:先将 x x x 用二进制表示,然后再左移 y y y 位,并且在尾部添上 y y y 个零。x << y
的执行结果等价于:#include
int main() {
int x = 3;
int y = 5;
printf("%d\n", x << y);
return 0;
}
96
最常用的就是当 x = 1 x = 1 x=1 时,
1 << y
代表的就是 2 y 2^y 2y,即 2 的幂。
x << y
中,当x
为负数的情况,代码如下:#include
int main() {
printf("%d\n", -1 << 1);
return 0;
}
-2
-1
的补码为:-2
的补码,同样,继续左移 1 位,得到:-4
的补码,以此类推,所以负整数的左移结果同样也是 x × 2 y x \times 2^y x×2y。可以理解成
- (x << y)
和(-x) << y
是等价的。
#include
int main() {
printf("%d\n", 32 << -1); // 16
printf("%d\n", 32 << -2); // 8
printf("%d\n", 32 << -3); // 4
printf("%d\n", 32 << -4); // 2
printf("%d\n", 32 << -5); // 1
printf("%d\n", 32 << -6); // 0
printf("%d\n", 32 << -7); // 0
return 0;
}
[Warning] left shift count is negative [-Wshift-count-negative]
int
类型的数都是 32 位的,最高位代表符号位,那么假设最高位为 1,次高位为 0,左移以后,符号位会变成 0,会产生什么问题呢?#include
int main() {
int x = 0b10000000000000000000000000000001;
printf("%d\n", x); // -2147483647
return 0;
}
-2147483647
#include
int main() {
int x = 0b10000000000000000000000000000001;
printf("%d\n", x << 1);
return 0;
}
0b10
。2
int
整型其实是一个环,溢出以后又会回来,而环的长度正好是 2 32 2^{32} 232,所以 − 2 32 + 2 = 2 -2^{32}+2 = 2 −232+2=2,这个就有点像同余的概念,这两个数是模 2 32 2^{32} 232 同余的。更多关于同余的知识,可以参考我的算法系列文章:夜深人静写算法(三)- 初等数论入门(学生党记得找我开试读)。x & ((1 << y) - 1)
。我们可以用左移运算符来实现标记码,即
1 << k
作为第 k k k 个标记位的标记码,这样就可以通过一句话,实现对标记位置 0、置 1、取反等操作。
【例题1】对于 x x x 这个数,我们希望对它二进制位的第 k k k 位(从0开始,从低到高数)置为 1。
(1 << k)
,那么将 第 k k k 位 置为 1 的语句可以写成:x | (1 << k)
。【例题2】对于 x x x 这个数,我们希望对它二进制位的第 k k k 位(从0开始,从低到高数)置为 0。
(~(1 << k))
,那么将 第 k k k 位 置为 0 的语句可以写成:x & (~(1 << k))
。【例题3】对于 x x x 这个数,我们希望对它二进制位的第 k k k 位(从0开始,从低到高数)取反。
(1 << k)
。那么将 第 k k k 位 取反的语句可以写成:x ^ (1 << k)
。(1 << k)
的二进制表示为:1 加上 k 个 0,那么 (1 << k) - 1
的二进制则代表 k k k 个 1。x | ((1 << k) - 1)
。x & ~((1 << k) - 1)
。x ^ ((1 << k) - 1)
。通过这一章,我们学会了:
1)位运算 << 的用法;
2)用 << 来生成标记位;
3)用 << 来生成掩码;
x >> y
。其中x
和y
均为整数。x >> y
念作:“将 x x x 右移 y y y 位”,这里的位当然就是二进制位了,那么它表示的意思也就是:先将 x x x 用二进制表示,对于正数,右移 y y y 位;对于负数,右移 y y y 位后高位都补上 1。x >> y
的执行结果等价于:#include
int main() {
int x = 0b1010111;
int y = 3;
printf("%d\n", x >> y);
return 0;
}
10
由于除法可能造成不能整除,所以才会有 取下整 这一步运算。
x >> y
中,当x
为负数的情况,代码如下:#include
int main() {
printf("%d\n", -1 >> 1);
return 0;
}
-1
-1
的补码为:-1
的补码,同样,继续右移 1 位,得到:可以理解成
- (x >> y)
和(-x) >> y
是等价的。
【例题1】要求不运行代码,肉眼看出这段代码输出多少。
#include
int main() {
int x = (1 << 31) | (1 << 30) | 1;
int y = (1 << 31) | (1 << 30) | (1 << 29);
printf("%d\n", (x >> 1) / y);
return 0;
}
#include
int main() {
printf("%d\n", 1 >> -1); // 2
printf("%d\n", 1 >> -2); // 4
printf("%d\n", 1 >> -3); // 8
printf("%d\n", 1 >> -4); // 16
printf("%d\n", 1 >> -5); // 32
printf("%d\n", 1 >> -6); // 64
printf("%d\n", 1 >> -7); // 128
return 0;
}
[Warning] right shift count is negative [-Wshift-count-negative]
【例题2】给定一个数 x x x,去掉它的低 k k k 位以后进行输出。
x >> k
。【例题3】获取一个数 x x x 低位连续的 1 并且输出。
(x ^ (x + 1)) >> 1
。【例题4】获取一个数 x x x 的第 k ( 0 ≤ k ≤ 30 ) k(0 \le k \le 30) k(0≤k≤30) 位的值并且输出。
(x >> k) & 1
。通过这一章,我们学会了:
1)位运算 >> 的用法;
2)用 >> 来取低位连续 1;
3)用 >> 取第 k k k 位的值;
=
右边的操作数的值赋值给左边的操作数。a = 10189;
a = a + 5;
=
右边的值。#include
int main() {
int a = 5;
int b = (a = 5);
printf("%d\n", b);
return 0;
}
5
a = 5
的值为5
,从而等价于b = 5
。#include
int main() {
int a = 0;
a = a + 1.5;
printf("%d\n", a);
return 0;
}
1
#include
int main() {
int a, b, c, d = 0;
a = b = c = d = d == 0;
printf("%d\n", a);
return 0;
}
1
#include
int main() {
int a, b, c, d = 0;
a = ( b = (c = ( d = (d == 0) ) ) );
printf("%d\n", a);
return 0;
}
=
的优先级低于关系运算符==
,所以d = d == 0
等价于d = (d == 0)
;而赋值运算符=
的结合性是从右到左,所以a = b = c
等价于a = (b = c)
。 int love;
love = love + 1314;
int love;
love += 1314;
+=
就是复合赋值运算符,类似的复合赋值运算符还有很多,总共分为两大类:算术赋值运算符、位赋值运算符。运算符 | 简称 | 描述 | 举例 |
---|---|---|---|
+= |
加且赋值运算符 | 将 右边操作数 加上 左边操作数 的结果赋值给 左边操作数 | a += b 等价于a = a + b |
-= |
减且赋值运算符 | 将 左边操作数 减去 右边操作数 的结果赋值给 左边操作数 | a -= b 等价于a = a - b |
*= |
乘且赋值运算符 | 将 右边操作数 乘以 左边操作数 的结果赋值给 左边操作数 | a *= b 等价于a = a * b |
/= |
除且赋值运算符 | 将 左边操作数 除以 右边操作数 的结果赋值给 左边操作数 | a /= b 等价于a = a / b |
%= |
求模且赋值运算符 | 求 两个操作数的模,并将结果赋值给 左边操作数 | a %= b 等价于a = a % b |
运算符 | 简称 | 描述 | 举例 |
---|---|---|---|
&= |
按位与且赋值运算符 | 将 左边操作数 按位与上 右边操作数 的结果赋值给 左边操作数 | a &= b 等同于a = a & b |
|= |
按位或且赋值运算符 | 将 左边操作数 按位或上 右边操作数 的结果赋值给 左边操作数 | a |= b 等同于a = a | b |
^= |
按位异或且赋值运算符 | 将 左边操作数 按位异或上 右边操作数 的结果赋值给 左边操作数 | a ^= b 等同于a = a ^ b |
<<= |
左移且赋值运算符 | 将 左边操作数 左移 右边操作数 的位数后的结果赋值给 左边操作数 | a <<= b 等同于a = a << b |
>>= |
右移且赋值运算符 | 将 左边操作数 右移 右边操作数 的位数后的结果赋值给 左边操作数 | a >>= b 等同于a = a >> b |
这样写的好处有三个:
1)前一种形式, e 1 e_1 e1 只计算一次;第二种形式要计算两次。
2)前一种形式,不需要加上圆括号;第二种形式的圆括号不可少。
3)看起来简洁清晰;
a.b.c.d.e.f[ 1024 + g.h.i.j.k.l ] = a.b.c.d.e.f[ 1024 + g.h.i.j.k.l ] + 5
a.b.c.d.e.f[ 1024 + g.h.i.j.k.l ] += 5
(当然,这个例子比较极端,实际编码中千万不要写出这样的代码哦)。通过这一章,我们学会了:
1)赋值运算符;
2)赋值表达式;
简单来说,逗号表达式遵循两点原则:
1)以逗号分隔的表达式单独计算;
2)逗号表达式的值为最后一个表达式的值;
#include
int main() {
int a = 1, b = 2, c = 3, d = 1 << 6, e;
printf("%d\n", a + b + c + d);
return 0;
}
int a = 1, b = 2, c = 3, d = 1 << 6, e
就是逗号表达式。for
结构的括号内的第一个表达式,用于给多个局部变量赋值。1
到 10
的数求立方和的代码,如下:#include
int main() {
int i, s;
for(i = 1, s = 0; i <= 10; ++i) {
s += i*i*i;
}
printf("%d\n", s);
return 0;
}
i = 1, s = 0
就是逗号表达式。for
的内容,会在后面的章节来介绍,暂时只需要知道可以使用逗号表达式来对一些变量赋予初值。int tmp;
tmp = a;
a = b;
b = tmp;
int tmp;
tmp = a, a = b, b = tmp;
#include
int main() {
int x, y, a, b;
a = (1, x = 2, y = 3);
b = 1, x = 9, y = 3;
printf("%d %d\n", a, b);
return 0;
}
a
和b
的的赋值,只差了一个括号,但是结果截然不同。3 1
(1, x = 2, y = 3)
表达式的值为以逗号分隔的最后一个表达式的值,即3
;而在b = 1, x = 9, y = 3
中,由于逗号运算符的优先级很低,导致表达式分成了三部分:b = 1
、x = 9
、y = 3
,所以才有 a = 3 a=3 a=3, b = 1 b=1 b=1。通过这一章,我们学会了:
1)逗号运算符;
2)逗号表达式;
运算符类型 | 运算符举例 | 参考文章 |
---|---|---|
后缀运算符 | [] 下标运算 |
会在数组章节讲解,待更新 |
单目运算符 | (type) 强制转换 |
光天化日学C语言(12)- 类型转换 |
算术运算符 | + 加号 |
光天化日学C语言(09)- 算术运算符 |
移位运算符 | << 左移 |
光天化日学C语言(18)- 位运算 << 的应用 |
关系运算符 | < 小于 |
光天化日学C语言(10)- 关系运算符 |
双目位运算符 | & 位与 |
光天化日学C语言(14)- 位运算 & 的应用 |
双目逻辑运算符 | && |
光天化日学C语言(11)- 逻辑运算符 |
条件运算符 | ? : |
会在if 语句章节讲解,待更新 |
赋值运算符 | <<= 左移后赋值 |
光天化日学C语言(20)- 赋值运算符与赋值表达式 |
逗号运算符 | , 逗号 |
光天化日学C语言(21)- 逗号运算符 |
优先级 | 运算符 | 名称 | 形式 | 举例 |
---|---|---|---|---|
1 | [] |
数组下标 | 数组名[常量表达式] | a[2] |
1 | () |
圆括号 | (表达式) 或 函数名(形参表) | (a+1) |
1 | . |
对象的成员选择 | 对象.成员名 | a.b |
1 | -> |
指针的成员选择 | 指针.成员名 | a->b |
2 | + |
正号 | +表达式 | +5 |
2 | - |
负号 | -表达式 | -5 |
2 | (type) |
强制类型转换 | (数据类型)表达式 | (int)a |
2 | ++ |
自增运算符 | ++变量名 / 变量名++ | ++i |
2 | -- |
自增运算符 | –变量名 / 变量名– | --i |
2 | ! |
逻辑非 | !表达式 | !a[0] |
2 | ~ |
按位取反 | ~表达式 | ~a |
2 | & |
取地址 | &变量名 | &a |
2 | * |
解引用 | *指针变量名 | *a |
2 | sizeof |
取长度 | sizeof(表达式) | sizeof(a) |
3 | * |
乘 | 表达式 * 表达式 | 3 * 5 |
3 | / |
除 | 表达式 / 表达式 | 3 / 5 |
3 | % |
模 | 整型表达式 % 整型非零表达式 | 3 % 5 |
4 | + |
加 | 表达式 + 表达式 | a + b |
4 | - |
减 | 表达式 - 表达式 | a - b |
5 | << |
左移 | 变量<<表达式 | 1<<5 |
5 | >> |
右移 | 变量>>表达式 | x>>1 |
6 | < |
小于 | 表达式<表达式 | 1 < 2 |
6 | <= |
小于等于 | 表达式<=表达式 | 1 <= 2 |
6 | > |
大于 | 表达式>表达式 | 1 > 2 |
6 | >= |
大于等于 | 表达式>=表达式 | 1 >= 2 |
7 | == |
等于 | 表达式==表达式 | 1 == 2 |
7 | != |
不等于 | 表达式!=表达式 | 1 != 2 |
8 | & |
等于 | 表达式&表达式 | 1 & 2 |
9 | ^ |
等于 | 表达式^表达式 | 1 ^ 2 |
10 | | |
等于 | 表达式\表达式 | 1 | 2 |
11 | && |
逻辑与 | 表达式&&表达式 | a && b |
12 | || |
逻辑与 | 表达式|| 表达式 |
a || b |
13 | ?: |
条件运算符 | 表达式1? 表达式2: 表达式3 | a>b?a:b |
14 | = |
赋值 | 变量=表达式 | a = b |
14 | += |
加后赋值 | 变量+=表达式 | a += b |
14 | -= |
减后赋值 | 变量-=表达式 | a -= b |
14 | *= |
乘后赋值 | 变量*=表达式 | a *= b |
14 | /= |
除后赋值 | 变量/=表达式 | a /= b |
14 | %= |
模后赋值 | 变量%=表达式 | a %= b |
14 | >>= |
右移后赋值 | 变量>>=表达式 | a >>= b |
14 | <<= |
左移后赋值 | 变量<<=表达式 | a <<= b |
14 | &= |
位与后赋值 | 变量&=表达式 | a &= b |
14 | ^= |
异或后赋值 | 变量^=表达式 | a ^= b |
14 | |= |
位或后赋值 | 变量|= 表达式 |
a |= b |
15 | , |
逗号运算符 | 表达式1,表达式2,… | a+b,a-b |
结合方向只有 3 个是 从右往左,其余都是 从左往右(比较符合人的直观感受)。
(1)一个是单目运算符;
(2)一个是双目运算符中的 赋值运算符;
(3)一个条件运算符,也就是C语言中唯一的三目运算符。
后缀运算符和单目运算符优先级一般最高,逗号运算符的优先级最低。快速记忆如下:
单目逻辑运算符 > 算术运算符 > 关系运算符 > 双目逻辑运算符 > 赋值运算符
#include
int main() {
int a = 1, b = 2, c = 3;
a <<= b <<= c;
printf("%d\n", a );
return 0;
}
【运行结果】65536
【结果答疑】a <<= b <<= c
的计算方式等价于a = (a << (b << c))
,结果为1 << 16
。
#include
int main() {
int a = 1, b = 2;
printf("%d\n", a > b ? a + b : a - b );
return 0;
}
【运行结果】-1
【结果答疑】条件运算符的优先级较低,低于关系运算符和算术运算符,所以a > b ? a + b : a - b
等价于1 > 2 ? 3 : -1
。
#include
int main() {
int a = 1;
--a && --a;
printf("%d\n", a);
return 0;
}
【运行结果】0
【结果答疑】这个例子是展示逻辑与运算符&&
从左往右计算过程中,一旦遇到 0 就不再进行运算了,所以--a
实际上只执行了一次。
#include
int main() {
int x = 0b010000;
printf("%d\n", x | x - 1 );
return 0;
}
【运行结果】31
【结果答疑】这个例子是是将低位连续的零变成一,但是一般这样的写法会报警告,因为编译程序并不知道你的诉求,到底是想先计算 | 还是先计算-
,由于这个问题我们实际要计算的是x | (x - 1)
,并且减法运算符-
优先级高于位或运算符 | ,所以括号是可以省略的。
#include
int main() {
int a = 0b1010;
int b = 0b0101;
int c = 0b1001;
printf("%d\n", a | b ^ c );
return 0;
}
【运行结果】14
【结果答疑】这个例子表明了异或运算符^
高于位或运算符 | 。
#include
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", a & b == 2);
return 0;
}
【运行结果】0
【结果答疑】延续【例题59】继续看,之前a & b
输出的是2
,那为什么加上等于==
判定后,输出结果反而变成0
了呢?原因是因为==
的优先级高于位与&
,所以相当于进行了a & 0
的操作,结果自然就是0了。
通过这一章,我们学会了:
1)运算符的优先级;
2)运算符的结合性;
所以,如果能够将 符号位 和 数值位 联合起来,让它们共同参与运算,不再加以区分,这样硬件电路就会变得更加简单。
1 - 2
等价于 1 + (-2)
,1 - (-2)
等价于1 + 2
。所以,它们可以合并为一种运算,即只保留加法运算。
【定义】 符号位 为 0 代表 正数,符号位 为 1 代表 负数,数值位 为 真值的绝对值。
真值:+ 00000000 00000000 00000000 00100101
原码: 00000000 00000000 00000000 00100101
真值:- 00000000 00000000 00000000 00100101
原码: 10000000 00000000 00000000 00100101
[ x ] 原 = { x ( 0 ≤ x < 2 n − 1 ) 2 n − 1 − x ( − 2 n − 1 < x ≤ 0 ) [x]_原 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n-1} - x & (-2^{n-1} < x \le 0) \end{cases} [x]原={ x2n−1−x(0≤x<2n−1)(−2n−1<x≤0) 这里 x x x 代表真值,而 n n n 的取值是 8 、 16 、 32 、 64 8、16、32、64 8、16、32、64,我们通常说的整型
int
都是 32位 的,本文就以 n = 32 n = 32 n=32 的情况进行阐述;
【定义】 正数 的 反码 就是它的 原码;负数 的 反码 为 原码 的每一位的 0变1、1变0(即位运算中的按位取反);
真值:+ 00000000 00000000 00000000 00100101
反码: 00000000 00000000 00000000 00100101
真值:- 00000000 00000000 00000000 00100101
反码: 11111111 11111111 11111111 11011010
[ x ] 反 = { x ( 0 ≤ x < 2 n − 1 ) 2 n − 1 + x ( − 2 n − 1 < x ≤ 0 ) [x]_反 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n}-1 + x & (-2^{n-1} < x \le 0) \end{cases} [x]反={ x2n−1+x(0≤x<2n−1)(−2n−1<x≤0) 这里 x x x 代表真值,而 n n n 的取值是 8 、 16 、 32 、 64 8、16、32、64 8、16、32、64,我们通常说的整型
int
都是 32位 的,本文就以 n = 32 n = 32 n=32 的情况进行阐述;
【定义】 正数 的 补码 就是它的 原码;负数 的 补码 为 它的反码加一;
真值:+ 00000000 00000000 00000000 00100101
补码: 00000000 00000000 00000000 00100101
真值:- 00000000 00000000 00000000 00100101
补码: 11111111 11111111 11111111 11011011
[ x ] 补 = { x ( 0 ≤ x < 2 n − 1 ) 2 n + x ( − 2 n − 1 ≤ x < 0 ) [x]_补 = \begin{cases} x & (0 \le x < 2^{n-1})\\ 2^{n} + x & (-2^{n-1} \le x < 0) \end{cases} [x]补={ x2n+x(0≤x<2n−1)(−2n−1≤x<0) 这里 x x x 代表真值,而 n n n 的取值是 8 、 16 、 32 、 64 8、16、32、64 8、16、32、64,我们通常说的整型
int
都是 32位 的,本文就以 n = 32 n = 32 n=32 的情况进行阐述;
对于三种编码方式,总结如下:
1)这三种机器数的最高位均为符号位;
2)当真值为正数时,原码、反码、补码的表示形式相同,符号位用 “0” 表示,数值部分真值相同;
3)当真值为负数时,原码、反码、补码的表示形式不同,但是符号位都用 “1” 表示,数值部分:反码是原码的 “按位取反”,补码是反码加一;
正数
真值:+ 00000000 00000000 00000000 00100101
原码: 00000000 00000000 00000000 00100101
反码: 00000000 00000000 00000000 00100101
补码: 00000000 00000000 00000000 00100101
负数
真值:- 00000000 00000000 00000000 00100101
原码: 10000000 00000000 00000000 00100101
反码: 11111111 11111111 11111111 11011010
补码: 11111111 11111111 11111111 11011011
+1 的原码:00000000 00000000 00000000 00000001
+1 的原码:00000000 00000000 00000000 00000001
----------------------------------------------
+2 的原码:00000000 00000000 00000000 00000010
1 - 2
表示成1 + (-2)
,然后用原码相加得到:+1 的原码:00000000 00000000 00000000 00000001
-2 的原码:10000000 00000000 00000000 00000010
----------------------------------------------
-3 的原码:10000000 00000000 00000000 00000011
1 + (-2) = -3
,计算结果明显是错的,所以为了解决减法问题,引入了反码;1 - 2
表示成1 + (-2)
,情况如下:+1 的反码:00000000 00000000 00000000 00000001
-2 的反码:11111111 11111111 11111111 11111101
----------------------------------------------
-1 的反码:11111111 11111111 11111111 11111110
1 - 1
表示成1 + (-1)
,情况 如下:+1 的反码:00000000 00000000 00000000 00000001
-1 的反码:11111111 11111111 11111111 11111110
---------------------------------------------
-0 的反码:11111111 11111111 11111111 11111111
1)两个正数的补码相加。
2)一正一负两个数相加,且 答案非零 。
+1 的补码:00000000 00000000 00000000 00000001
-2 的补码:11111111 11111111 11111111 11111110
----------------------------------------------
-1 的补码:11111111 11111111 11111111 11111111
3)一正一负两个数相加,且 答案为零。
+1 的补码 00000000 00000000 00000000 00000001
-1 的补码: 11111111 11111111 11111111 11111111
----------------------------------------------
0 的补码:1 00000000 00000000 00000000 00000000
通过这一章,我们学会了:
1)原码的表示形式;
2)反码的表示形式;
3)补码的表示形式;
.
分隔,我们将它称为 十进制表示。例如 0.0 0.0 0.0、 1314.520 1314.520 1314.520、 − 1.234 -1.234 −1.234、 0.0001 0.0001 0.0001 等都是合法的小数,这是最常见的小数形式。aEn 或者 aen
数学 | C语言 |
---|---|
1.5 1.5 1.5 | 1.5 E 1 1.5E1 1.5E1 |
1990 1990 1990 | 1.99 e 3 1.99e3 1.99e3 |
− 0.054 -0.054 −0.054 | − 0.54 e − 1 -0.54e-1 −0.54e−1 |
float
和double
;float
称为单精度浮点型,占 4 个字节;double
称为双精度浮点型,占 8 个字节。printf
对浮点数进行格式化输出,如下表格所示:控制符 | 浮点类型 | 表示形式 |
---|---|---|
%f |
float |
十进制表示 |
%e |
float |
指数表示,输出结果中的 e 小写 |
%E |
float |
指数表示,输出结果中的 E 大写 |
%lf |
double |
十进制表示 |
%le |
double |
指数表示,输出结果中的e 小写 |
%lE |
double |
指数表示,输出结果中的E 大写 |
#include
int main() {
float f = 520.1314f;
double d = 520.1314;
printf("%f\n", f);
printf("%e\n", f);
printf("%E\n", f);
printf("%lf\n", d);
printf("%le\n", d);
printf("%lE\n", d);
return 0;
}
520.131409
5.201314e+02
5.201314E+02
520.131400
5.201314e+02
5.201314E+02
%f
和 %lf
默认保留六位小数,不足六位以 0 补齐,超过六位按四舍五入截断。float
能够表示的范围,从而产生了精度误差,而double
的范围更大一些,所以就能正确表示,所以平时编码过程中,如果对效率要求较高,对精度要求较低,可以采用float
;反之,对效率要求一般,但是对精度要求较高,则需要采用double
。v a l u e value value:代表要表示的浮点数;
s i g n sign sign:代表 v a l u e value value 的正负号,它的取值只能是 0 或 1:取值为 0 是正数,取值为 1 是负数;
b a s e base base:代表基数,或者说进制,它的取值大于等于 2;
f r a c t i o n fraction fraction:代表尾数,或者说精度,是 b a s e base base 进制的小数,并且 1 ≤ f r a c t i o n < b a s e 1 \le fraction \lt base 1≤fraction<base,这意味着,小数点前面只能有一位数字;
e x p o n e n t exponent exponent:代表指数,是一个整数,可正可负,并且为了直观一般采用 十进制 表示。
110011
放入内存。我们将内存中存储的尾数命名为 f f f,真正的尾数命名为 f r a c t i o n fraction fraction,则么它们之间的关系为: f r a c t i o n = 1. f fraction = 1.f fraction=1.ffloat
和double
分配给指数位的比特位不同,所以需要分情况讨论;float
和double
的实际位数来举例说明实际内存中的存储方式。float
还是double
。float
类型,内存分布如下:double
类型,内存分布如下:浮点数类型 | 指数位数 | 指数范围 | 尾数位数 | 尾数范围 |
---|---|---|---|---|
float |
8 8 8 | [ − 2 7 + 1 , 2 7 ] [-2^7+1,2^7] [−27+1,27] | 23 23 23 | [ ( 0 ) 2 , ( 1...1 ⏟ 23 ) 2 ] [(0)_2, (\underbrace{1...1}_{23})_2] [(0)2,(23 1...1)2] |
double |
11 11 11 | [ − 2 10 + 1 , 2 10 ] [-2^{10}+1,2^{10}] [−210+1,210] | 52 52 52 | [ ( 0 ) 2 , ( 1...1 ⏟ 52 ) 2 ] [(0)_2, (\underbrace{1...1}_{52})_2] [(0)2,(52 1...1)2] |
#include
int main() {
int value = 0b01000001011001100000000000000000; // (1)
printf("%f\n", *(float *)(&value) ); // (2)
return 0;
}
运算结果如下:
( 1 ) (1) (1) 第一步,就是把上面那串二进制的 01串 直接拷贝下来,然后在前面加上0b
前缀,代表了 v a l u e value value 这个四字节的内存结构就是这样的;
( 2 ) (2) (2) 第二步,分三个小步骤:
( 2. a ) (2.a) (2.a)&value
代表取value
这个值的地址;
( 2. b ) (2.b) (2.b)(float *)&value
代表将这个地址转换成float
类型;
( 2. c ) (2.c) (2.c)*(float *)&value
代表将这个地址里的值按照float
类型解析得到一个float
数;
14.375000
#include
int main() {
long long value = 0b0100000000101100110000000000000000000000000000000000000000000000; // (1)
printf("%lf\n", *(double *)(&value) ); // (2)
return 0;
}
运算结果如下:
( 1 ) (1) (1) 第一步,就是把上面那串二进制的 01串 直接拷贝下来,然后在前面加上0b
前缀,代表了 v a l u e value value 这个八字节的内存结构就是这样的;
( 2 ) (2) (2) 第二步,分三个小步骤:
( 2. a ) (2.a) (2.a)&value
代表取value
这个值的地址;
( 2. b ) (2.b) (2.b)(double *)&value
代表将这个地址转换成double
类型;
( 2. c ) (2.c) (2.c)*(double *)&value
代表将这个地址里的值按照double
类型解析得到一个double
数;
14.375000
通过这一章,我们学会了:
浮点数的科学计数法和内存存储方式;
float
和double
的尾数部分是有限的,可定无法容纳无限的二进制数,即使能够转换成有限的位数,也可能会超出给定的尾数部分的长度,这时候就必须进行舍弃。这时候,由于和原数并不是完全相等,就出现了精度问题。float
类型,是一个四字节的浮点数,也就是32个比特位,具体内存存储方式如下图所示:double
类型,是一个八字节的浮点数,也就是64个比特位,具体内存存储方式如下图所示:float
的二进制表示,尾数23位,加上一位隐藏的1,总共24位,最后一位可能是精确数字,也可能是近似数字;而其余的 23 位都是精确数字。从二进制的角度看,这种浮点格式的小数,最多有 24 位有效数字,但是能保证的是 23 位;也就是说,整体的精度为 23 ~ 24 位。如果转换成十进制, 2 24 = 16777216 2^{24} = 16777216 224=16777216,一共 8 位;也就是说,最多有 8 位有效数字(十进制),但是能保证的是 7 位,从而得出整体精度为 7 ~ 8 位。对于 double,同理可得,二进制形式的精度为 52 ~ 53 位,十进制形式的精度为 15 ~ 16 位。浮点数类型 | 尾数个数(二进制) | 十进制位数 |
---|---|---|
float |
23 ~ 24 | 7 ~ 8 |
double |
52 ~ 53 | 15 ~ 16 |
#include
int main() {
int ninf = 0b11111111100000000000000000000000;
printf("%f\n", *(float *)&ninf );
return 0;
}
-inf
#include
int main() {
int pinf = 0b01111111100000000000000000000000;
printf("%f\n", *(float *)&pinf );
return 0;
}
inf
#include
int main() {
int nan = 0b11111111100000000000000000001010;
printf("%f\n", *(float *)&nan );
return 0;
}
nan
0.012
的浮点数,规格化的表示应为1.2e-2
。但对于某些过小的数,如1.2e-130
,允许的指数位数不能满足指数的需要,可能就会在尾数前添加前导0,如将其表示为0.00012e-126
。总结如下:
1)当指数 e x p exp exp 的所有二进制位都是 0 时,我们将这样的浮点数称为“非规格化浮点数”;
2)当指数 e x p exp exp 的所有二进制位既不全为 0 也不全为 1 时,我们称之为“规格化浮点数”;
3)当指数 exp 的所有二进制位都是 1 时,作为特殊值对待。
换言之,究竟是规格化浮点数,还是非规格化浮点数,还是特殊值,完全看指数 e x p exp exp。
#define eps 1e-6
bool EQ(double a, double b) {
// EQual
return fabs(a - b) < eps;
}
bool NEQ(double a, double b) {
// NotEQual
return !EQ(a, b);
}
bool GET(double a, double b) {
// GreaterEqualThan
return a > b || EQ(a, b);
}
bool SET(double a, double b) {
// SmallerEqualThan
return a < b || EQ(a, b);
}
bool ST(double a, double b) {
// SmallerThan
return a < b && NEQ(a, b);
}
bool GT(double a, double b) {
// GreaterThan
return a > b && NEQ(a, b);
}
通过这一章,我们学会了:
浮点数的精度及其判定;
a = 6
是一个赋值表达式,而a = 6;
则代表一个赋值语句。#include
int main() {
int a = 1; // (1)
int b = ++a; // (2)
int c = a + b; // (3)
printf("%d\n", a, b, c); // (4)
return 0; // (5)
}
{
和}
括起来,就构成了一个复合语句。复合语句的作用和单个简单语句分开写是一致的,有时候也叫程序块。并且,花括号的后面不需要跟分号。#include
int main() {
{
int a = 1; // (1)
int b = ++a; // (2)
int c = a + b; // (3)
printf("%d\n", a, b, c); // (4)
}
return 0; // (5)
}
if (表达式)
语句1
else
语句2
这个语句在执行时,先计算 表达式 的值:
1)如果 表达式 的值 非零,则执行 语句1;
2)如果 表达式 的值 为零,则执行 语句2;
#include
int main() {
int n;
scanf("%d", &n);
if(n % 2 == 1)
printf("%d 是奇数!", n);
else
printf("%d 是偶数!", n);
return 0;
}
n % 2 == 1
是由算术运算符%
和关系运算符==
组成的表达式,它的含义是 n n n 模 2 的值是否为 1。如果为 1 即表达式成立,则输出 n n n 是奇数,否则输出 n n n 是偶数。#include
int main() {
int n;
scanf("%d", &n);
if(n % 2 == 1)
{
n *= 3;
n -= 4;
}
else
{
n -= 5;
n *= 6;
}
return 0;
}
{
放在条件判断的后面,并且将 else
放在右花括号}
的后面。因为 C语言 对空格和换行不是很敏感,得到代码如下:#include
int main() {
int n;
scanf("%d", &n);
if(n % 2 == 1) {
n *= 3;
n -= 4;
} else {
n -= 5;
n *= 6;
}
return 0;
}
if (表达式)
语句1
else
语句2
if (表达式 != 0)
语句1
else
语句2
#include
int main() {
int n;
scanf("%d", &n);
if(n % 2) {
// (1)
n *= 3;
n -= 4;
} else {
n -= 5;
n *= 6;
}
return 0;
}
n % 2 == 1
等价于n % 2 != 0
,等价于n % 2
。 if (表达式)
语句
#include
int main() {
int n;
scanf("%d", &n);
if(n % 2) {
printf("%d\n", n);
}
return 0;
}
if (表达式1)
语句1
else if(表达式2)
语句2
else if(表达式3)
语句3
else if(...)
...
else
语句n
else if
后的表达式 和 语句 都不会进行计算了。else
表示的则是 “上述条件均不成立” 的情况,当然,如果不需要处理这种情况,也是可以省略的。【例题1】通过输入字符,判断它的类型:
1)如果是数字,输出串 “number”;
2)如果是大写字母,输出串 “upper letter”;
3)如果是小写字母,输出串 “lowwer letter”;
#include
int main(){
char c;
c = getchar();
if(c >= '0' && c <= '9')
printf("number\n");
else if(c >= 'A' && c <= 'Z')
printf("upper letter\n");
else if(c >= 'a' && c <= 'z')
printf("lowwer letter\n");
else
printf("other\n");
return 0;
}
if (表达式1) {
if(表达式2)
语句1
} else
语句n
if
,但是只有一个else
时,这个else
到底是和哪个if
匹配的,我们通过一个例子来说明。代码如下:#include
int main() {
int a = 1, b = 2;
if(a) // (1)
if(a > b) // (2)
printf("a > b\n");
else // (3)
printf("a == 0\n");
return 0;
}
a == 0
#include
int main() {
int a = 1, b = 2;
if(a) // (1)
if(a > b) // (2)
printf("a > b\n");
else // (3)
printf("a == 0\n");
return 0;
}
else
,是匹配的 ( 2 ) (2) (2)的if
,不是匹配 ( 1 ) (1) (1)的if
;else
与最近的前一个没有与else
匹配的if
匹配。所以怕产生歧义的最好办法,就是多加{}
。通过这一章,我们学会了:
1)if else 语句的用法;
2)else if 语句的用法;
if (a > b) {
x = a;
}else {
x = b;
}
表达式1 ? 表达式2 : 表达式3
if else
语句,完全可以用 条件运算符 来实现,如下: x = (a > b) ? a : b;
?
和:
是一个整体,是不能分隔的,不可单独使用。((a > c) ? a : c)
来对 表达式2 进行替换,得到: x = (a > b) ? ((a > c) ? a : c) : b;
(b < c) ? b : c
来替换 表达式3,得到: x = (a > b) ? ((a > c) ? a : c) : ((b < c) ? b : c);
#include
int main() {
int a = 3, b = 4, c = 5;
int x = (a > b) ? ((a > c) ? a : c) : ((b < c) ? b : c);
printf("%d\n", x);
return 0;
}
(a > b)
是否为真,(3 > 5)
结果自然不为真,所以我们走的是后面的分支,即((b < c) ? b : c)
,得到的结果为求b
和c
的最小值,所以为 4,而这也就是整个条件表达式的值。4
#include
int main() {
double a = 0 ? 1 : 1.5;
printf("%lf\n", a);
return 0;
}
1.500000
0 ? 1 : 1.5
的类型是double
,因为如果是整数的话,最后不可能返回一个浮点数。事实上,它的类型是由 表达式2 和 表达式3 决定的,类型转换遵循以下规则:int
,表达式3 是unsigned
,则条件表达式的值返回的就是unsigned
类型的。条件运算符的优先级比较低,仅高于 赋值运算符 和 逗号运算符,所以使用的时候不用担心优先级问题。
#include
int main()