- 由于之前的文章附加内容时电脑卡死,中间不小心删除了一些内容,而且因为内容海量,根本没办法知道删除了哪些内容,所以从模板文章重新梳理了一版出来。
零、人物简介
??欢迎大家踊跃评论,优秀的评论更能引起大家的共鸣,评论的点赞前三名,将获得 《夜深人静写算法》 和 《C语言入门100例》的 一折优惠券(二选一) ,三天为限,记得主动联系博主。 
- 第一位登场的就是今后会一直教我们C语言的老师 —— 光天。
 - 第二位登场的则是今后会和大家一起学习C语言的没什么资质的小白程序猿 —— 化日。

一、C语言简介

- C语言是一种高级语言,运行效率仅次于汇编,支持跨平台,所以被广泛的应用于软件开发、系统开发、嵌入式系统、游戏开发等场景。
二、第一个C语言程序

1、编程环境
-
(
1
)
(1)
(1) 百度搜索 “c语言在线编译”,如图四-1-1所示:

图四-1-1
-
(
2
)
(2)
(2) 任意选择一个在线编译工具,我选择的是菜鸟工具,如图四-1-2所示:
图四-1-2
2、写代码

- 先给出代码,然后根据行尾的标号,一行一行进行解释;
#include <stdio.h>
int main()
{
printf("Hello, World! \n");
return 0;
}
这段代码只做了一件事情,就是向屏幕上输出一行字: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) 作为函数的返回值。 
- 你可能对 头文件、预处理命令、函数、换行符、返回值 这些都没有概念,没有关系,刚开始我们不去理解这些概念,你只需要知道:通过改一些代码以后,能够看到想要看到的结果 就行。
3、修改代码
- 我们把 Hello, World 改成 光天化日学C语言 后,再来看看效果:
#include <stdio.h>
int main()
{
printf("光天化日学C语言! \n");
return 0;
}

三、编译运行

1、编译
- 编译就是把高级语言变成计算机可以识别的二进制语言,因为计算机只认识 1 和 0,你需要把一大堆复杂的语言词法、语法全部转换成 0 和 1。
2、运行
- 运行就是执行可执行程序啦。就是我们通常 Windows 上的双击 exe 干的事情。

四、为什么要搭建本地环境

- 1)联网:在线编译环境毕竟涉及到联网,如果没有网的情况下,我们就不能写代码了,真是听者伤心,闻者流泪啊;
- 2)定制化:写代码是一辈子的事情,界面当然要搞得赏心悦目才能持久,本地环境可以配置字体和背景,支持定制化,觉得什么界面好看就配成什么样的;
- 3)代码补全:字体高亮,代码补全 这些好用的功能,能够帮助你减少很多不必要编码错误;
- 4)多文件:当代码量比较大以后,涉及到多个文件时,在线编译环境就无能为力了;

五、下载 Dev C++
- Dev C++ 是一个轻量级的 C/C++ 集成编译环境,正因为是轻量级,所以还是有很多不太好用的地方,不过不用担心,对于教学来说已经足够了。
- 相比 Visual Studio 20XX 来说,安装快了不少,所以我打算用这个工具来进行后续文章的讲解。
- 可以选择以下任何一个链接进行下载,下载后解压出 DevCpp_v6.5.exe 即可。
百度网盘下载
CSDN下载
六、安装 Dev C++
1、语言选择
- 双击 DevCpp 的 exe 文件,会跳出如下对话框,初学者建议直接用中文。如图五-1所示:

图五-1
2、我接受

图五-2
3、下一步

图五-3
4、选择安装位置
- 选择一个你钟意的安装路径,点击安装,如图五-4所示:

图五-4
5、看他装完
- 看他安装完,大概 7 秒左右,如图五-5-1所示:

图五-5-1
图五-5-2
七、配置
1、选择语言
- 选择一个你钟意的语言,推荐用中文,强我国威,壮我河山!点击 Next,如图六-1所示:

图六-1
2、选择配色
- 选择一个你看着舒服的配色方案,推荐 VS Code,如图六-1所示:

图六-1
八、写一段代码
1、新建文件
- 点击界面左上角的 【新建】 按钮,选择【源代码】菜单栏,如图七-1所示;
图七-1
2、写代码
- 把我们第一章中写过的代码,写到这个文件中。建议自己一行一行写哦,复制粘贴 和 自己敲出来的感觉是不一样的。
#include <stdio.h>
int main() {
printf("光天化日写C语言!\n");
return 0;
}
3、保存文件
- 点击菜单栏的【保存】按钮,或者 Ctrl + S 快捷键保存文件。

图七-3
4、编译运行
- 点击菜单栏的【编译运行】或者 F11 按钮,就会跳出一个控制台,如图七-4所示:

图七-4
九、变量

1、变量的定义
对于一个变量而言,有三部分组成: ??1)变量类型; ??2)变量名; ??3)变量地址;
int Iloveyou;
1)变量类型
int 表示变量类型,是英文单词 Integer 的缩写,意思是整数。

2)变量名
Iloveyou 表示变量名,也可以叫其它名字,例如:WhereIsHeroFrom 、ILoveYou1314 等等。- 这个语句的含义是:在内存中找一块区域,命名为
Iloveyou ,用它来存放整数。 - 需要注意的是,最后有一个分号,
int Iloveyou 表达了一个语句,要用分号来结束。需要注意的是,最后有一个分号,int Iloveyou 表达了一个语句,要用分号来结束。

3)变量地址

2、变量的赋值
- C语言中可以用以下语句把
520
520
520 这个整数存储到
Iloveyou 这个变量里:
Iloveyou = 520;

= 在数学中叫 “等于号”,例如 1 + 1 = 2 ,但在C语言中,这个过程叫做变量的赋值,简称赋值。赋值是指把数据放到内存的过程。
3、变量的初始化
int Iloveyou;
Iloveyou = 520;
int Iloveyou = 520;

- 两段代码的执行结果相同,都是把
Iloveyou 的值变成
520
520
520;

4、变量的由来
int Iloveyou = 520;
Iloveyou = 521;
Iloveyou = 522;
Iloveyou = 523;
- 代码执行完毕以后,它的值以最后一次赋值为准,正因为可以不断修改,是可变的,所以才叫变量。

- 简单总结一下就是:数据是放在内存中的,变量是给这块内存起的名字,有了变量就可以找到并使用这份数据。
5、多变量的定义
- 如果几个变量的类型一致,我们可以写在一行上进行定义,如下:
int x, y, z = 5;
- 这段代码代表一次性定义了三个整型类型的变量,并且将
z 初始化为 5,等价于如下代码:
int x;
int y;
int z = 5;
6、变量间的赋值

int a, b;
520 = a;
a = b;

【例题1】给出如下代码,求输出结果是什么。
#include <stdio.h>
int main()
{
int a = 1314, b = 520;
b = a;
a = b;
printf("a=%d b=%d\n", a, b);
return 0;
}
十、数据类型

- 接下来我们展开来讲一下变量类型,更加确切的讲,应该叫数据类型,C语言中有如下一些系统内置数据类型。
1、内置数据类型

- 从上面这个表,我们可以看到,有表示字符的,有表示整数的,也有表示浮点数的。

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;
2、数据的大小

- 字节是计算机中的一种基本单位,英文名为 Byte,计算机中所有的数据都是由字节组成的。
- 我们通常在计算机中看到的文件单位 B 、K、M 、G、T 和字节的关系如下:

- 一个字节在计算机里面是有 8 个位组成,一个位有 0 和 1 两种状态,所以一个字节能表示的状态数就是
2
8
=
256
2^8 = 256
28=256。如图四-2-1,代表的是一个字节的状态,白色代表0,灰色代表1,它的二进制表示就是
(
00001101
)
2
(00001101)_2
(00001101)2?。
图四-2-1
3、整数的表示范围
- 这样一来,上面提到的几种整数类型,能够表示的整数就显而易见了,假设字节数为
n
n
n,那么能够表示的整数个数就是能够表示的状态个数,即:
2
8
n
2^{8n}
28n 。
- 由于我们需要表示负数 和 零,实际的每种整数数据类型能够表示的数字范围如下表所示:


十一、变量名
1、标识符
- 定义变量时,我们使用了诸如
love 、Iloveyou 这样的名字,为了表达变量的作用,这就叫 标识符,即 Identifier。 - 标识符就是程序员自己起的名字,除了变量名,后面还会讲到函数名、常量名、宏名、结构体名等,它们都是标识符。
2、关键字
- 关键字(Keywords)是由C语言规定的具有特定意义的字符串,通常也称为保留字,例如
int 、char 、long 、int 、unsigned int 等。 - 程序自己定义的标识符不能与关键字相同,否则会出现错误。
- 后续会对各个关键字进行一一讲解。
3、命名规则
-
(
1
)
(1)
(1) 必须由字母、数字 或者下划线构成,如
_aa ,a123 ,_ 都是合法的变量,?* 、a a 、# 、都是非法的变量; -
(
2
)
(2)
(2) 不能以数字开头,如
123abc 不是一个合法的变量名; -
(
3
)
(3)
(3) 大小写敏感,即大小写看成不同,即
o 和O 不是同一个变量; -
(
4
)
(4)
(4) 不能将变量名和C语言的语法保留关键字同名;
-
(
5
)
(5)
(5) C语言虽然不限制标识符的长度,但是它受到 编译器 和 操作系统 的限制。例如在某个编译器中规定标识符前 256 位有效,当两个标识符前 256 位相同时,则被认为是同一个标识符。
-
(
6
)
(6)
(6) 标识符命名时还是最好遵循 min-length-max-infomation 的原则,即以最小的长度表达最全的信息,不过这个是规范上的,语言层面是不会做过多的限制的。

【例题2】给出一段程序,请回答这段程序的运行结果。
#include <stdio.h>
int main()
{
int IloveYou = 0;
ILoveYou = 1314;
ILoveYou = ILoveYou;
ILoveYou = 520;
printf("%d\n", ILoveYou);
return 0;
}
十二、概念简介
1、输出的含义

2、标准输出

在C语言中,有三个函数可以用来在屏幕上输出数据,它们分别是: ??1)puts() :只能输出字符串,并且输出结束后会自动换行; ??2)putchar() :只能输出单个字符; ??3)printf():可以输出各种类型的数据,作为最灵活、最复杂、最常用的输出函数,可以完全替代全面两者,所以是必须掌握的,今天我们就来全面了解一下这个函数。
3、格式化

- 我们在进行输出的时候,对于小数而言,可能需要输出小数点后一位,亦或是两位,这个计算机自己是不知道规则的,需要写代码的人告诉它,这个告诉它如何输出的过程就被称为格式化。
十三、格式化输出
printf 前几个章节都有提及,这个函数的命名含义是:Print(打印) 和 Format (格式) ,即 格式化输出。

1、数据类型格式化
1)整数
#include <stdio.h>
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!

2)浮点数
#include <stdio.h>
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

- 另外,单精度 和 双精度 的区别就是双精度的精度更高一点,也就是能够表示的小数的范围更加精准,这个会在介绍浮点数的存储方式时详细介绍。
3)字符
#include <stdio.h>
int main()
{
char ch = 'A';
printf("%c\n", ch);
return 0;
}
- 对于字符而言,我们利用
%c 来进行格式化;C语言中的字符是用单引号引起来的,当然,字符这个概念扯得太远,会单独开一个章节来讲,具体可以参考 ASCII 码。 - 顺便我们来解释一下一直出现但是我闭口不提的换行符
\n ,这个符号是一个转义符,它代表的不是两个字符(反斜杠\ 和字母n ),而是换行的意思; - 这段代码的输出就是一个字符 A;
A
- 我们通过一个例题来理解这个换行符的含义;

【例题1】第1行输出1个1,第2行输出2个2,第3行输出3个3,第4行输出4个4。
#include <stdio.h>
int main()
{
printf("1\n");
printf("22\n");
printf("333\n");
printf("4444\n");
return 0;
}
#include <stdio.h>
int main()
{
printf("1\n22\n333\n4444\n");
return 0;
}

4)字符串
- 字符串,是由多个字符组合而成,用双引号引起来,这一章我不打算讲得太细,只需要知道用
%s 进行格式化的即可,代码如下:
#include <stdio.h>
int main()
{
char str[100] = "I love you!";
printf("%s\n", str);
return 0;
}
I love you!

2、对齐格式化
- 我们发现,上文中所有的格式化,都有一个
% 和一个字母,事实上,在百分号和字母之间,还有一些其它的内容。
主要包含如下内容: ??1)负号:如果有,则按照左对齐输出; ??2)数字:指定字段最小宽度,如果不足则用空格填充; ??3)小数点:用与将最小字段宽度和精度分开; ??4)精度:用于指定字符串重要打印的而最大字符数、浮点数小数点后的位数、整型最小输出的数字数目;

【例题2】给定如下一段代码,求它的输出内容。
#include <stdio.h>
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 <stdio.h>
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.

十四、输入概念简介
1、输入的含义

2、标准输入

在C语言中,有三个函数可以用来在键盘上输入数据,它们分别是: ??1)gets() :用于输入一行字符串; ??2)getchar() :用于输入单个字符; ??3)scanf():可以输入各种类型的数据,作为最灵活、最复杂、最常用的输入函数,虽然无法完全替代前面两者,但是却是必须掌握的,今天我们就来全面了解一下这个函数。
3、格式化

- 我们在进行输入的时候,其实都是一个字符串,但是这个字符串被输入后有可能当成整数来用,也有可能还是字符串,这个计算机自己是不知道规则的,需要写代码的人告诉它,这个告诉它如何输入的过程就被称为格式化。
十五、整数的格式化输入
scanf 的函数的命名含义是:Scan(扫描) 和 Format (格式) ,即 格式化输入。- 和输出一样,输入的时候,也根据数据类型的不同,分为 整数、浮点数、字符、字符串等等。
- 但是这里会有很多问题,拿整数的输入为例,我们一个一个来看。
1、单个数据的输入
#include <stdio.h>
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) 输入多了取地址符& ;

- 我们会在后面指针的章节来围绕对这个符号进行展开的。
2、多个数据的输入
#include <stdio.h>
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 <stdio.h>
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 语句来完成。
3、空格免疫
- 然后我们来看下,对于输入的数据之间有一个空格和多个空格的情况,代码如下:
#include <stdio.h>
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 。
- 也就是说,虽然文中要求是1个空格,但是我们输入多个也不影响我们输入,再来看下一种情况:
#include <stdio.h>
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 。
- 也就是说,虽然文中要求多个空格,但是我们输入1个也不影响我们输入。

4、回车结算
- 通过以上的几个例子,我们发现,
scanf() 是以回车来结算一次输入的。 - 用户每次按下回车键,计算机就会认为完成一次输入操作,
scanf() 开始读取用户输入的内容,并根据我们定义好的格式化内容从中提取有效数据,只要用户输入的内容和格式化内容匹配,就能够正确提取。

十六、输入缓冲区
#include <stdio.h>
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、少输入
- 我们尝试少输入1个数,按下回车后,发现程序并没有任何的输出,当我们再次输入下一个数的时候,产生了正确的输出,如下:
1 2 3↙
4↙
1 2 3 4
2、多输入
- 我们尝试多输入1个数,按下回车后,发现输出了前四个我们输入的数,如下:
1 2 3 4 5↙
1 2 3 4
3、再次尝试
- 我们增加一行代码,就是在输出四个数以后,再调用一次
scanf() ,如下:
#include <stdio.h>
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 <stdio.h>
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 ,如下:

1、字符串的输入
- 关于字符串,后面在讲完数组以后,还会着重讲,也有很多匹配算法是应用于字符串上的,也是一个很重要的内容,所以这里不作太多介绍,只需要记住,字符串输入时
& 可以不加,如下:
#include <stdio.h>
int main()
{
char str[100];
scanf("%s", str);
printf("%s\n", str);
scanf("%s", &str);
printf("%s\n", str);
return 0;
}
-
(
1
)
(1)
(1) 和
(
2
)
(2)
(2) 的方式都是可以的,但是我们一般采用
(
1
)
(1)
(1) 的方式;
2、做个简单的游戏吧

- 这是一个算命游戏,要求根据输入的姓名,得到这个人的算命信息。
- 我们先来看看效果:

#include <stdio.h>
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;
}
十八、何为进制

- 进制也就是 进位计数制 的简称,是人为定义的带进位的计数方法。
- 对于任何一种进制 —— X进制,表示每一个数位上的数运算时都是逢 X 进一位。
- 例如:十进制是逢十进一,十六进制是逢十六进一,二进制就是逢二进一,八进制是逢八进一,以此类推,X进制就是 逢X进一。
- 如图三-1所示,代表的则是十进制的进位演示:

图三-1
十九、常用进制
1、二进制

两只鞋子 = 1双鞋子; 二个抓手 = 1双手;
2、三进制
3个月 = 1个季度;
3、四进制
- 好了接下来,你能举出四进制的例子吗?

4个季度 = 1年
5、十进制
- 当然,现实生活中遇到的最多的数字都是十进制表示。例如:0、1、2、3、… 、9、10、…
4、其它进制

七进制:7天 = 1周; 十二进制:12瓶啤酒 = 1打; 二十四进制:24小时 = 1天; 六十进制:60秒 = 1分钟;60分钟 = 1小时;
二十、计算机中的进制
1、二进制
- C语言中,我们如果想表示一个二进制数,可以用
0b 作为前缀,然后跟上0 和1 组成的数字,我们来看一段代码:
#include <stdio.h>
int main() {
int a = 0b101;
printf("%d\n", a);
return 0;
}
5
- 因为
%d 代表输出的数是十进制,所以我们需要将二进制转换成十进制以后输出,0b101 的数学表示如下:
(
101
)
2
(101)_2
(101)2? - 它在十进制下的值为 5。
- 因为数字比较小,所以我们可以简单列出二进制和十进制的对应关系如下:
进制 | 零 | 一 | 二 | 三 | 四 | 五 |
---|
二进制 | 0 | 1 | 10 | 11 | 100 | 101 | 十进制 | 0 | 1 | 2 | 3 | 4 | 5 |
2、八进制
#include <stdio.h>
int main() {
int a = 0123;
printf("%d\n", a);
return 0;
}

83
- 为什么呢?参考二进制的表示法,八进制的表示法是前缀1个
0 ,然后跟上0-7 的数字; - 换言之,我们需要把
123 这个八进制数转换成十进制后再输出。而转换结果就是83 ,由于这里数字较大,我们已经无法一个一个数出来了,所以需要进行进制转换,关于进制转换,在第四节进制转换初步里再来讲解。
3、十六进制
- 同样的,对于十六进制数,表示方式为:以
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 <stdio.h>
int main() {
int a = 0X123;
printf("%d\n", a);
return 0;
}
291

二十一、进制转换初步
1、X进制 转 十进制
对于 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?...d1?d0?; ??【概念2】每个数字位有一个权值; ??【概念3】第
i
i
i 个数字位的权值为
X
i
X^i
Xi;
- 基于以上几个概念, X进制 转 十进制的值为 每一位数字 和 它的权值的乘积的累加和,如下:
-
∑
i
=
0
n
X
i
d
i
\sum_{i=0}^{n} X^id_i
i=0∑n?Xidi?
-
∑
\sum
∑ 是个求和符号,不必惊慌!
- 举个例子,对于上文提到的八进制的数
0123 ,转换成十进制,只需要套用公式: -
∑
i
=
0
n
X
i
d
i
=
∑
i
=
0
2
8
i
d
i
=
8
2
×
1
+
8
1
×
2
+
8
0
×
3
=
64
+
16
+
3
=
83
\begin{aligned}\sum_{i=0}^{n} X^id_i &= \sum_{i=0}^{2} 8^id_i \\ &= 8^2 \times 1 + 8^1 \times 2 + 8^0 \times 3 \\ &= 64 + 16 + 3 \\ &= 83\end{aligned}
i=0∑n?Xidi??=i=0∑2?8idi?=82×1+81×2+80×3=64+16+3=83?
- 再如,上文提到的十六进制数
0X123 ,转换成十进制,套用同样的公式,如下: -
∑
i
=
0
n
X
i
d
i
=
∑
i
=
0
2
1
6
i
d
i
=
1
6
2
×
1
+
1
6
1
×
2
+
1
6
0
×
3
=
256
+
32
+
3
=
291
\begin{aligned}\sum_{i=0}^{n} X^id_i &= \sum_{i=0}^{2} 16^id_i \\ &= 16^2 \times 1 + 16^1 \times 2 + 16^0 \times 3 \\ &= 256 + 32 + 3 \\ &= 291\end{aligned}
i=0∑n?Xidi??=i=0∑2?16idi?=162×1+161×2+160×3=256+32+3=291?
2、十进制 转 X进制
- 对于 十进制 转 X进制 的问题,我们可以这么来考虑:
- 从 X进制 转 十进制 的原理可知,任何一个十进制数字都是由 X进制 的幂的倍数累加而成。所以,一个数一定有
X
0
X^0
X0 这部分,而这部分,可以通过原数除上
X
X
X 的余数得到。然后我们把原数除上
X
X
X 后得到的数,肯定又有
X
0
X^0
X0 的部分,就这样重复的试除,直到得到的商为 零 时结束,过程中的余数,逆序一下就是对应进制的数了。
- 还是一上文的例子来说,对于
291 我们可以通过如下方式,转换成 十六进制。
291 除 16 ========== 余 3
18 除 16 =========== 余 2
1 除 16 ============ 余 1
- 而对于十进制的83,我们可以通过如下方式,转换成 八进制。
83 除 8 ============ 余 3
10 除 8 ============ 余 2
1 除 8 ============= 余 1
- 那么,等我们后面学习了循环语句以后,就可以教大家如何用计算机来实现进制转换了,目前阶段只需要了解下进制转换的基本原理即可。
二十二、ASCII 码简介

1、ASCII 码的定义
- ASCII 码(即 American Standard Code for Information Interchange),翻译过来是美国信息交换标准代码。
- 我们一般念成 ask 2 马。

2、ASCII 码的起源
- 它是一套编码系统。
- 由于计算机用 高电平 和 低电平 分别表示 1 和 0,所以,在计算机中所有的数据在存储和运算时都要使用二进制数表示,例如,像
a-z 、A-Z 这样的52个字母以及0-9 的数字还有一些常用的符号(例如?*#@!@#$%^&*() 等)在计算机中存储时也要使用二进制数来表示,具体用哪些二进制数字表示哪个符号,每个人都可以约定自己的一套规则,这就叫编码。 - 即 一个数字 和 一个字符 的一一映射。
- 为了通信而不造成混淆,所以需要所有人都使用相同的规则。
3、ASCII 码的表示方式

- 标准ASCII 码,使用 7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字
0 到9 、标点符号,以及在英语中使用的特殊控制字符。 - 简单的就可以认为:一个数字对应一个字符。具体如下表所示:

二十三、ASCII 码的输出

- ASCII 码对应的字符用单引号括起来,并且是可以按照两种方式来输出的,分别为:字符形式 和 整数形式。
1、字符
#include <stdio.h>
int main() {
printf("%c\n", '0');
printf("%c\n", 'A');
printf("%c\n", 'a');
printf("%c\n", '$');
return 0;
}
0
A
a
$

2、整数
#include <stdio.h>
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,以此类推。 
二十四、ASCII 码的运算
- 既然当成了整数,那么就可以进行简单的四则运算了。
- 我们简单来看下下面这段代码:
#include <stdio.h>
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 <stdio.h>
int main() {
printf("%c\n", 'A' - 10);
return 0;
}
- 建议先想想,然后再敲代码看看结果,是否和你想的一致。
二十五、ASCII 码的比较
- ASCII 码既然可以和整数无缝切换,那么自然也可以进行比较了。
- 通过上一节,我们了解到了
'0' 加上1 以后等于'1' ,那么顺理成章可以得出:'0' < '1' 。 - 同样可以知道:
'a' < 'b' 、'X' < 'Y' 。 - 那么,我们再来看个问题。

【例题2】请问 'a' < 'A' 还是 'a' > 'A' 。 
二十六、常量简介

- C语言中的常量,主要分为以下几种类型:

二十七、数值常量
- 数值常量分为整数和浮点数,整数一般称为整型常量,浮点数则称为实型常量。
1、整型常量
- 整型常量分为二进制、八进制、十进制 和 十六进制。
- 每个整型常量分为三部分:前缀部分、数字部分、后缀部分。
- 如下表所示:
 - 关于前缀这部分,在 光天化日学C语言(06)- 进制转换入门 已经讲到过,就不再累述了。
- 这里着重提一下后缀,
u (unsigned )代表无符号整数,l (long )代表长整型,ll 代表long long 。

- 换言之,无符号整型就是非负整数。
- 待时机成熟,我会对整数的存储结构进行一个非常详细的介绍。

【例题1】说出以下整型常量中,哪些是非法的,为什么非法。
1314
520u
0xFoooooL
0XFeeeul
018888
0987UU
0520
0x4b
1024llul
30ll
030ul
2、实型常量
1)小数形式
- 小数形式由三部分组成:整数部分、小数点、小数部分。例如:
3.1415927
4.5f
.1314
- 其中
f 后缀代表 float ,用于区分double 。 .1314 等价于0.1314 。
2)指数形式
1e9
5.2e000000
5.2e-1
1.1e2
- 它表示的数值是:
-
x
×
1
0
y
x \times 10^{y}
x×10y
- 其中
y
y
y 代表的是数字
10 的指数部分,所以是支持负数的。
二十八、字符常量
- 字符常量可以是一个普通的字符、一个转义序列,或一个通用的字符。
- 每个字符都对应一个 ASCII 码值。
1)普通字符
'a'
'Q'
'8'
'?'
'+'
' '
2)转义字符
- 转义字符是用引号引起来,并且内容为 斜杠 + 字符,例如我们之前遇到的用
'\n' 代表换行,\t 代表水平制表符(可理解为键盘上的 tab 键),'\\' 代表一个反斜杠,等等; - 当然还可以用
'\ooo' 来代替一个字符,其中一个数字o 代表一个八进制数;也可以用 '\xhh' 来代表一个字符,具体见如下代码:
#include <stdio.h>
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】请问如何输出一个单引号?
二十九、字符串常量
- 字符串常量,又称为字符串字面值,是括在双引号
"" 中的。一个字符串包含类似于字符常量的字符:普通字符、转义序列。
1、单个字符串常量
#include <stdio.h>
int main() {
printf( "光天化日学\x43语言!\n" );
return 0;
}
- 我们可以用转义的
'\x43' 代表'C' 和其它字符组合,变成一个字符串常量。以上代码输出为:
光天化日学C语言!

【例题2】如果我想要如下输出结果,请问,代码要怎么写?
"光天化日学C语言!"
2、字符串常量分行
- 两个用
"" 引起来的字符串,是可以无缝连接的,如下代码:
#include <stdio.h>
int main() {
printf(
"光天化日学"
"C语言!\n"
);
return 0;
}
光天化日学C语言!
三十、符号常量
1、#define
- 利用
#define 预处理器可以定义一个常量如下:
#include <stdio.h>
#define TIPS "光天化日学\x43语言!\n"
#define love 1314
int main() {
printf( TIPS );
printf("%d\n", love);
return 0;
}
- 以上这段代码,会将所有
TIPS 都原文替换为"光天化日学\x43语言!\n" ;将所有love 替换为1314 。

2、const
const 的用法也非常广泛,而且涉及到很多概念,这里只介绍最简单的用法,后面会开辟一个新的章节专门来讲它的用法。
#include <stdio.h>
const int love = 1314;
int main() {
printf( "%d\n", love );
return 0;
}
- 我们可以在普通变量定义前加上
const ,这样就代表它是个常量了,在整个运行过程中都不能被修改。

【例题3】下面这段代码会发生什么情况,自己编程试一下吧。
#include <stdio.h>
const int love = 1314;
int main() {
love = 520;
printf( "%d\n", love );
return 0;
}
三十一、算术运算符

- 算术运算符主要包含以下几个:
- 1)四则运算符,也就是数学上所说的加减乘除;
- 2)取余符号;
- 3)自增和自减。
 - 那么接下来让我们一个一个来看看吧。
1、四则运算符
- 数学上的加减乘除和C语言的加减乘除的含义类似,但是符号表示方法不尽相同,对比如下:
/ | 加法 | 减法 | 乘法 | 除法 |
---|
数学 | + | - |
×
\times
× | ÷ | C语言 | + | - | * | / |
1)加法
#include <stdio.h>
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

2)减法
a - b 代表从第一个操作数中减去第二个操作数,代码如下:
#include <stdio.h>
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

3)乘法
#include <stdio.h>
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

4)除法

不同类型的除数和被除数会导致不同类型的运算结果。 ??1)当 除数 和 被除数 都是整数时,运算结果也是整数; ????1.a)如果能整除,结果就是它们相除的商; ????1.b)如果不能整除,那么就直接丢掉小数部分,只保留整数部分,即数学上的 取下整; ??2)除数和被除数中有一个是小数,那么运算结果也是小数,并且是 double 类型的小数。
#include <stdio.h>
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 <stdio.h>
int main() {
int a = 5, b = 0;
int c = a / b;
return 0;
}
- 这里会触发一个异常,即 除零错。这种情况在 C语言中是不允许的,但是由于变量的值只有在运行时才会确定,编译器是没办法帮你把这个错误找出来的,平时写代码的时候一定要注意。

2、取余符号
- 取余,也就是求余数,使用的运算符是
% 。C语言中的取余运算只能针对整数,也就是说,% 两边都必须是整数,不能出现小数,否则会出现编译错误。 - 例如:
5 % 3 = 2 、7 % 2 = 1 。
当然,余数可以是正数也可以是负数,由% 左边的整数决定: ??1)如果% 左边是正数,那么余数也是正数; ??2)如果% 左边是负数,那么余数也是负数。
#include <stdio.h>
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;
}
- 在 光天化日学C语言(08)- 常量 这一章中,我们提到的两个用
"" 引起来的字符串是可以无缝连接的,所以这段代码里面四个字符串相当于一个。而% 在printf 中是用来做格式化的,所以想要输出到屏幕上,需要用%% 。于是,我们得到输出结果如下:
9%4=1
9%-4=1
-9%4=-1
-9%-4=-1
3、自增和自减

x = x + 1;
x++;
++x;
#include <stdio.h>
int main()
{
int x = 1;
printf( "x = %d\n", x++ );
printf( "x = %d\n", x );
return 0;
}
x = 1
x = 2
- 这是因为
x 在自增前,就已经把值返回了,所以输出的是原值。我们再来看另一种情况:
#include <stdio.h>
int main()
{
int x = 1;
printf( "x = %d\n", ++x );
printf( "x = %d\n", x );
return 0;
}
x = 2
x = 2
- 这是因为
x 先进行了自增,再把值返回,所以输出的是自增后的值。 - 当然,自减也是同样的道理,大家可以自己写代码实践一下。

三十二、关系运算符

1、概览
- 关系运算符是用来判断符号两边的数据的大小关系的。
- C语言中的关系运算符主要有六个,如下:

2、表示方式
- C语言中的关系运算符和数学中的含义相同,但是表示方法略有不同,区别如下:
关系运算符释义 | C语言表示 | 数学表示 |
---|
大于 | > | > | 大于等于 | >= | ≥ | 等于 | == | = | 不等于 | != | ≠ | 小于 | < | < | 小于等于 | <= | ≤ |
- 关系运算符的两边可以是变量、数值 或 表达式,例如:
1)变量
2)数值
3)表达式
三十三、关系运算符的应用
1、运算结果
- 关系运算符的运算结果只有 0 或 1。当条件成立时结果为 1,条件不成立结果为 0。
- 我们来看一段代码,如下:
#include <stdio.h>
int main() {
printf("%d\n", 1 > 2);
printf("%d\n", 1 < 2);
return 0;
}
0
1
- 原因就是
1 > 2 在数学上是不成立的,所以结果为0 ;而1 < 2 在数学上是不成立的,所以结果为1 ;
2、运算符嵌套
- 关系运算符是允许嵌套使用的,即运算的结果可以继续作为关系运算符的运算参数,例如以下代码:
#include <stdio.h>
int main() {
printf("%d\n", 1 > 2 > -1);
return 0;
}
- 输出结果是多少呢?
 - 由于
1 > 2 的结果为0 ,所以1 > 2 > -1 等价于0 > -1 ,显然是成立的,所以输出的结果为:
1

- 有关于结合性的内容,会在运算符的内容都讲完后,就运算符优先级和运算符结合性进行一个统一讲解,现在这个阶段,你只需要知道,关系运算符都是左结合,即存在多个运算符,有没有括号的情况下,一律从左往右计算。

【例题1】给出以下代码,问输出的结果是什么。
#include <stdio.h>
int main() {
printf("%d\n", 1 < 2 > 1);
printf("%d\n", 3 > 2 > 1);
return 0;
}
3、运算符优先级
!= 和== 的优先级低于> ,< ,>= ,<= 。- 优先级是什么呢?
- 看个例子就能明白。
#include <stdio.h>
int main() {
printf("%d\n", 1 < 2 == 1);
return 0;
}
- 我们可以做出两种假设:
- 假设1:
== 优先级低于< ;1 < 2 优先计算,则表达式等价于1 == 1 ,成立,输出1 。 - 假设2:
== 优先级高于< ;2 == 1 优先计算,则表达式等价于1 < 0 ,不成立,输出0 。 - 实际上,这段代码的结果为:
1
- 即
== 的优先级低于< ,当然,同学们可以试下 != 和其它符号的关系。  - 另外,关系表达式会进场用在条件判断
if 语句中,例如:
if(a < b) {
}
- 我们会在将
if 语句的时候继续复习关系运算符相关的知识哦~ - 不过写到这里,这个打字时真的卡!
4、== 和 =
- 初学者最容易犯的错是把
== 和= 搞混,前者是判断相等与否,而后者是赋值。 - 看一段代码,就能知道:
#include <stdio.h>
int main() {
int a = 0;
printf("%d\n", a = 0);
printf("%d\n", a == 0);
return 0;
}
0
1
三十四、逻辑运算符

1、概览
- 逻辑运算符是用来做逻辑运算的,也就是我们数学中常说的 “与或非”。
- C语言中的逻辑运算符主要有三个,如下:

2、表示方式
- C语言中的逻辑运算符和数学中的含义类似,但是表示方法截然不同,对应关系如下:
逻辑运算符释义 | 操作数个数 | C语言表示 | 数学表示 |
---|
与 | 二元操作符 | && |
∧
\land
∧ | 或 | 二元操作符 | || |
∨
\lor
∨ | 非 | 一元操作符 | ! |
?
\lnot
? |
- 二元操作符的操作数是跟在符号两边的,而一元操作符的操作数则是跟在符号右边的。
- 逻辑运算符的操作数可以是变量、数值 或 表达式。例如:
1)变量
2)数值
3)表达式
- a + b && c + d
- a + b || c + d
- !(a + b)
三十五、逻辑运算符的应用
1、运算结果

1)与运算(&&)
对于与运算,参与运算的操作数都为 “真” 时,结果才为 “真”,否则为 “假”。
#include <stdio.h>
int main() {
printf("%d\n", 0 && 0);
printf("%d\n", 5 && 0);
printf("%d\n", 0 && 5);
printf("%d\n", 5 && 9);
return 0;
}
- 注释中的内容,就是实际输出的内容。
- 我们发现,无论操作数原本是什么,程序只关心它是 “零” 还是 “非零”。然后根据
&& 运算符自身的运算规则进行运算。

2)或运算(||)
对于或运算,参与运算的操作数都为“假”时,结果才为“假”,否则为“真”。
#include <stdio.h>
int main() {
printf("%d\n", 0 || 0);
printf("%d\n", 5 || 0);
printf("%d\n", 0 || 5);
printf("%d\n", 5 || 9);
return 0;
}
- 注释中的内容,就是实际输出的内容。
- 我们同样发现,无论操作数原本是什么,程序只关心它是 “零” 还是 “非零”。然后根据
|| 运算符自身的运算规则进行运算。

3)非运算(!)
对于非运算,操作数为 “真”,运算结果为 “假”;操作数为 “假”,运算结果为 “真”;
#include <stdio.h>
int main() {
printf("%d\n", !0);
printf("%d\n", !5);
return 0;
}
- 注释中的内容,就是实际输出的内容。
- 八个字概括:非真即假,非假即真。
2、运算符嵌套
- 和 关系运算符 一样,逻辑运算符也是可以支持嵌套的,即运算结果可以继续作为逻辑运算符的操作数,例如如下代码:
#include <stdio.h>
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
3、运算符优先级
- 接下来,我们看下三个运算符混合运用的情况,对于如下代码:
#include <stdio.h>
int main() {
int a = !( 1 || 1 && 0 );
printf("%d\n", a);
return 0;
}

0

- 我们再来看个例子,区别只是在
1 || 1 的两边加上一个括号。
#include <stdio.h>
int main() {
int a = !( (1 || 1) && 0 );
printf("%d\n", a);
return 0;
}
1
- 这是为什么呢?
- 因为
&& 的优先级是比|| 要高的,所以在没有任何括号的情况下,&& 会优先计算,简而言之,对于刚才的( 1 || 1 && 0 ) ,我们把它等价成( 1 || (1 && 0) ) ,这样是不是就好理解了。 - 用类似的方法,我们可以得到
! 的优先级是最高的,所以这三个符号的优先级排序如下: -
∣
∣
?
<
?
&
&
?
<
?
!
|| \ < \ \&\& \ < \ !
∣∣?<?&&?<?!

- 当然,后面的章节,我们会对 算术运算符、关系运算符、逻辑运算符 等等所有的运算符的优先级 和 结合性 进行一个梳理,尽情期待 ~~

通过这一章,我们学会了: ??1)与运算:有假必假; ??2)或运算:有真必真; ??3)非运算:非真即假,非假即真;
- 希望对你有帮助哦 ~ 祝大家早日成为 C 语言大神!
课后习题

三十六、再谈二进制

1、二进制数值表示
- 例如,在计算机中,我们可以用单纯的 0 和 1 来表示数字。
1、101、1100011、100101010101 都是二进制数。 123、423424324、101020102101AF 则不是,因为有 0 和 1 以外的数字位。
- 一般为了不产生二义性,我们会在数字的右下角写上它的进制,例如:
-
101
0
(
10
)
1010_{(10)}
1010(10)?
- 代表的是十进制下的 1010,也就是十进制下的 “一千零一十”。
-
101
0
(
2
)
1010_{(2)}
1010(2)?
- 代表的是二进制下的 1010,也就是十进制下的 “十”。

2、二进制加法
二进制加法采用从低到高的位依次相加,当相加的和为2时,则向高位进位。
- 例如,在二进制中,加法如下:
1
(
2
)
+
1
(
2
)
=
1
0
(
2
)
1
(
2
)
+
0
(
2
)
=
1
(
2
)
0
(
2
)
+
1
(
2
)
=
1
(
2
)
0
(
2
)
+
0
(
2
)
=
0
(
2
)
1_{(2)} + 1_{(2)} = 10_{(2)} \\ 1_{(2)} + 0_{(2)} = 1_{(2)} \\ 0_{(2)} + 1_{(2)} = 1_{(2)} \\ 0_{(2)} + 0_{(2)} = 0_{(2)}
1(2)?+1(2)?=10(2)?1(2)?+0(2)?=1(2)?0(2)?+1(2)?=1(2)?0(2)?+0(2)?=0(2)?
3、二进制减法
二进制减法采用从低到高的位依次相减,当遇到 0 减 1 的情况,则向高位借位。
- 例如,在二进制中:减法如下:
1
(
2
)
?
1
(
2
)
=
0
(
2
)
1
(
2
)
?
0
(
2
)
=
1
(
2
)
1
0
(
2
)
?
1
(
2
)
=
1
(
2
)
0
(
2
)
?
0
(
2
)
=
0
(
2
)
1_{(2)} - 1_{(2)} = 0_{(2)} \\ 1_{(2)} - 0_{(2)} = 1_{(2)} \\ 10_{(2)} - 1_{(2)} = 1_{(2)} \\ 0_{(2)} - 0_{(2)} = 0_{(2)}
1(2)??1(2)?=0(2)?1(2)??0(2)?=1(2)?10(2)??1(2)?=1(2)?0(2)??0(2)?=0(2)?
- 而我们今天要讲的位运算正是基于二进制展开的。
三十七、位运算简介
- 位运算可以理解成对二进制数字上的每一个位进行操作的运算。
- 位运算分为 布尔位运算符 和 移位位运算符。
- 布尔位运算符又分为 位与(&)、位或(|)、异或(^)、按位取反(~);移位位运算符分为 左移(<<) 和 右移(>>)。
- 如图所示:
 - 接下来几天,每天都会更新一篇,对每个位运算符的详细解读,并且配有例题。

- 光天化日学C语言(14)- 位运算 & 的应用 (已更新)
- 光天化日学C语言(15)- 位运算 | 的应用 (待更新)
- 光天化日学C语言(16)- 位运算 ^ 的应用 (待更新)
- 光天化日学C语言(17)- 位运算 ~ 的应用 (待更新)
- 光天化日学C语言(18)- 位运算 << 的应用 (待更新)
- 光天化日学C语言(19)- 位运算 >> 的应用 (待更新)
三十八、位运算概览
- 今天,我们先来对位运算进行一个初步的介绍。后面会对每个运算符的应用做详细介绍,包括刷题的时候如何运用位运算来加速等等。
1、布尔位运算
C语言运算符表示 | 含义 | 示例 |
---|
& | 位与 | x & y | | | 位或 | x | y | ^ | 异或 | x ^ y | ~ | 按位取反 | x ~ y |
1)位与
- 位与就是对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共
2
2
=
4
2^2 = 4
22=4 种情况。

#include <stdio.h>
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a & b) );
return 0;
}
-
(
1
)
(1)
(1) 在C语言中,以
0b 作为前缀,表示这是一个二进制数。那么a 的实际值就是
(
1010
)
2
(1010)_2
(1010)2?。 -
(
2
)
(2)
(2) 同样的,
b 的实际值就是
(
0110
)
2
(0110)_2
(0110)2?; -
(
3
)
(3)
(3) 那么这里
a & b 就是对
(
1010
)
2
(1010)_2
(1010)2? 和
(
0110
)
2
(0110)_2
(0110)2? 的每一位做表格中的& 运算。 - 所以最后输出结果为:
2
- 因为输出的是十进制数,它的二进制表示为:
(
0010
)
2
(0010)_2
(0010)2?。
- 注意:这里的 前导零 可有可无,作者写上前导零只是为了对齐以及让读者更加清楚位与的运算方式。
2)位或

#include <stdio.h>
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a | b) );
return 0;
}
14
- 即二进制下的
(
1110
)
2
(1110)_2
(1110)2? 。
3)异或

#include <stdio.h>
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a ^ b) );
return 0;
}
12
- 即二进制下的
(
1100
)
2
(1100)_2
(1100)2? 。
4)按位取反
- 按位取反其实就是 0 变 1, 1 变 0。
- 同样,我们来看一段程序。
#include <stdio.h>
int main() {
int a = 0b1;
printf("%d\n", ~a );
return 0;
}
- 这里我想卖个关子,同学们可以自己试一下运行结果。
- 至于为什么会输出这个结果,我会在 光天化日学C语言(17)- 位运算 ~ 的应用 (待更新) 中进行详细讲解,敬请期待。
2、移位位运算
C语言运算符表示 | 含义 | 示例 |
---|
<< | 左移 | x << y | >> | 右移 | x >> y |
1)左移
- 其中
x << y 代表将二进制的
x
x
x 的末尾添加
y
y
y 个零,就好比向左移动了
y
y
y 位。 - 比如
(
1011
)
2
(1011)_2
(1011)2? 左移三位的结果为:
(
1011000
)
2
(1011000)_2
(1011000)2?。
2)右移
- 其中
x >> y 代表将二进制的
x
x
x 从右边开始截掉
y
y
y 个数,就好比向右移动了
y
y
y 位。 - 比如
(
101111
)
2
(101111)_2
(101111)2? 右移三位的结果为:
(
101
)
2
(101)_2
(101)2?。

通过这一章,我们学会了: ??1)位与 & ; ??2)位或 | ??3)异或 ^; ??4)按位取反 ~; ??5)左移 <<; ??6)右移 >>;
- 希望对你有帮助哦 ~ 祝大家早日成为 C 语言大神!
课后习题

三十九、位与运算符
- 位与运算符是一个二元的位运算符,也就是有两个操作数,表示为
x & y 。 - 位与运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共
2
2
=
4
2^2 = 4
22=4 种情况。
- 通过这个表,我们得出一些结论:
- 1)无论是 0 或 1,只要位与上 1,还是它本身;
- 2)无论是 0 或 1,只要位与上 0,就变成 0;

#include <stdio.h>
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a & b) );
return 0;
}
-
(
1
)
(1)
(1) 在C语言中,以
0b 作为前缀,表示这是一个二进制数。那么a 的实际值就是
(
1010
)
2
(1010)_2
(1010)2?。 -
(
2
)
(2)
(2) 同样的,
b 的实际值就是
(
0110
)
2
(0110)_2
(0110)2?; -
(
3
)
(3)
(3) 那么这里
a & b 就是对
(
1010
)
2
(1010)_2
(1010)2? 和
(
0110
)
2
(0110)_2
(0110)2? 的每一位做表格中的& 运算。 - 所以最后输出结果为:
2
- 因为输出的是十进制数,它的二进制表示为:
(
0010
)
2
(0010)_2
(0010)2?。
- 注意:这里的 前导零 可有可无,作者写上前导零只是为了对齐以及让读者更加清楚位与的运算方式。
四十、位与运算符的应用
1、奇偶性判定
- 我们判断一个数是奇数还是偶数,往往是通过取模
% 来判断的,如下:
#include <stdio.h>
int main() {
if(5 % 2 == 1) {
printf("5是奇数\n");
}
if(6 % 2 == 0) {
printf("6是偶数\n");
}
return 0;
}
#include <stdio.h>
int main() {
if(5 & 1) {
printf("5是奇数\n");
}
if( (6 & 1) == 0 ) {
printf("6是偶数\n");
}
return 0;
}
- 哇,好神奇!
- 这是利用了奇数和偶数分别的二进制数的特性,如下表所示:
- 所以,我们对任何一个数,通过将它和
0b1 进行位与,结果为零,则必然这个数的二进制末尾位为0,根据以上表就能得出它是偶数了;否则,就是奇数。 - 注意,由于
if 语句我们还没有实际提到过,所以这里简单提一下,后面会有系统的讲解:
if( expr ) { body }
- 对于以上语句,
expr 代表的是一个表达式,表达式的值最后只有 零 或 非零,如果值为非零,才会执行body 中的内容。
2、取末五位
【例题1】给定一个数,求它的二进制表示的末五位,以十进制输出即可。

- 这个问题的核心就是:我们只需要末五位,剩下的位我们是不需要的,所以可以将给定的数 位与上
0b11111 ,这样一来就直接得到末五位的值了。 - 代码实现如下:
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0b11111) );
return 0;
}

【例题2】如果是想得到末七位、末九位、末十四位、末 K 位,应该如何实现呢?
3、消除末尾五位
【例题3】给定一个 32 位整数,要求消除它的末五位。
- 还是根据位与的性质,消除末五位的含义,有两层:
- 1)末五位,要全变成零;
- 2)剩下的位不变;
- 那么,根据位运算的性质,我们需要数,它的高27位都为1,低五位都为 0,则这个数就是:
-
(
11111111111111111111111111100000
)
2
(11111111111111111111111111100000)_2
(11111111111111111111111111100000)2?
- 但是如果要这么写,代码不疯掉,人也会疯掉,所以一般我们把它转成十六进制,每四个二进制位可以转成一个十六进制数,所以得到十六进制数为
0xffffffe0 。 - 代码实现如下:
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0xffffffe0) );
return 0;
}

4、消除末尾连续1

【例题4】给出一个整数,现在要求将这个整数转换成二进制以后,将末尾连续的1都变成0,输出改变后的数(以十进制输出即可)。
- 我们知道,这个数的二进制表示形式一定是:
-
.
.
.
0
11...11
?
k
...0\underbrace{11...11}_{\rm k}
...0k
11...11??
- 如果,我们把这个二进制数加上1,得到的就是:
-
.
.
.
1
00...00
?
k
...1\underbrace{00...00}_{\rm k}
...1k
00...00??
- 我们把这两个数进行位与运算,得到:
-
.
.
.
0
00...00
?
k
...0\underbrace{00...00}_{\rm k}
...0k
00...00??
- 所以,你学会了吗?

通过这一章,我们学会了: ??1)用位运算 & 来做奇偶性判定; ??2)用位运算 & 获取一个数的末五位,末七位,末K位; ??3)用位运算 & 消除某些二进制位; ??4)用位运算 & 消除末尾连续 1;
- 希望对你有帮助哦 ~ 祝大家早日成为 C 语言大神!
课后习题

四十一、位与运算符
- 位与运算符是一个二元的位运算符,也就是有两个操作数,表示为
x & y 。 - 位与运算会对操作数的每一位按照如下表格进行运算,对于每一位只有 0 或 1 两种情况,所以组合出来总共
2
2
=
4
2^2 = 4
22=4 种情况。
- 通过这个表,我们得出一些结论:
- 1)无论是 0 或 1,只要位与上 1,还是它本身;
- 2)无论是 0 或 1,只要位与上 0,就变成 0;

#include <stdio.h>
int main() {
int a = 0b1010;
int b = 0b0110;
printf("%d\n", (a & b) );
return 0;
}
-
(
1
)
(1)
(1) 在C语言中,以
0b 作为前缀,表示这是一个二进制数。那么a 的实际值就是
(
1010
)
2
(1010)_2
(1010)2?。 -
(
2
)
(2)
(2) 同样的,
b 的实际值就是
(
0110
)
2
(0110)_2
(0110)2?; -
(
3
)
(3)
(3) 那么这里
a & b 就是对
(
1010
)
2
(1010)_2
(1010)2? 和
(
0110
)
2
(0110)_2
(0110)2? 的每一位做表格中的& 运算。 - 所以最后输出结果为:
2
- 因为输出的是十进制数,它的二进制表示为:
(
0010
)
2
(0010)_2
(0010)2?。
- 注意:这里的 前导零 可有可无,作者写上前导零只是为了对齐以及让读者更加清楚位与的运算方式。
四十二、位与运算符的应用
1、奇偶性判定
- 我们判断一个数是奇数还是偶数,往往是通过取模
% 来判断的,如下:
#include <stdio.h>
int main() {
if(5 % 2 == 1) {
printf("5是奇数\n");
}
if(6 % 2 == 0) {
printf("6是偶数\n");
}
return 0;
}
#include <stdio.h>
int main() {
if(5 & 1) {
printf("5是奇数\n");
}
if( (6 & 1) == 0 ) {
printf("6是偶数\n");
}
return 0;
}
- 哇,好神奇!
- 这是利用了奇数和偶数分别的二进制数的特性,如下表所示:
- 所以,我们对任何一个数,通过将它和
0b1 进行位与,结果为零,则必然这个数的二进制末尾位为0,根据以上表就能得出它是偶数了;否则,就是奇数。 - 注意,由于
if 语句我们还没有实际提到过,所以这里简单提一下,后面会有系统的讲解:
if( expr ) { body }
- 对于以上语句,
expr 代表的是一个表达式,表达式的值最后只有 零 或 非零,如果值为非零,才会执行body 中的内容。
2、取末五位
【例题1】给定一个数,求它的二进制表示的末五位,以十进制输出即可。

- 这个问题的核心就是:我们只需要末五位,剩下的位我们是不需要的,所以可以将给定的数 位与上
0b11111 ,这样一来就直接得到末五位的值了。 - 代码实现如下:
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0b11111) );
return 0;
}

【例题2】如果是想得到末七位、末九位、末十四位、末 K 位,应该如何实现呢?
3、消除末尾五位
【例题3】给定一个 32 位整数,要求消除它的末五位。
- 还是根据位与的性质,消除末五位的含义,有两层:
- 1)末五位,要全变成零;
- 2)剩下的位不变;
- 那么,根据位运算的性质,我们需要数,它的高27位都为1,低五位都为 0,则这个数就是:
-
(
11111111111111111111111111100000
)
2
(11111111111111111111111111100000)_2
(11111111111111111111111111100000)2?
- 但是如果要这么写,代码不疯掉,人也会疯掉,所以一般我们把它转成十六进制,每四个二进制位可以转成一个十六进制数,所以得到十六进制数为
0xffffffe0 。 - 代码实现如下:
#include <stdio.h>
int main() {
int x;
scanf("%d", &x);
printf("%d\n", (x & 0xffffffe0) );
return 0;
}

4、消除末尾连续1

【例题4】给出一个整数,现在要求将这个整数转换成二进制以后,将末尾连续的1都变成0,输出改变后的数(以十进制输出即可)。
- 我们知道,这个数的二进制表示形式一定是:
-
.
.
.
0
11...11
?
k
...0\underbrace{11...11}_{\rm k}
...0k
11...11??
- 如果,我们把这个二进制数加上1,得到的就是:
-
.
.
.
1
00...00
?
k
...1\underbrace{00...00}_{\rm k}
...1k
00...00??
- 我们把这两个数进行位与运算,得到:
-
.
.
.
0
00...00
?
k
...0\underbrace{00...00}_{\rm k}
...0k
00...00??
- 所以,你学会了吗?

通过这一章,我们学会了: ??1)用位运算 & 来做奇偶性判定; ??2)用位运算 & 获取一个数的末五位,末七位,末K位; ??3)用位运算 & 消除某些二进制位; ??4)用位运算 & 消除末尾连续 1;
- 希望对你有帮助哦 ~ 祝大家早日成为 C 语言大神!
课后习题

|