C 语言是全世界最流行的编程语言之一,在过去几年中长期霸占语言热度排行榜前 3 名(TIOBE)
一、认识 C 语言
1.1. 标准
- 目前最新的标准是:
ISO/IEC 9899:2011
,简称为C11
标准 - 还有部分开发人员在使用
ISO/IEC 9899:1999
,简称为C99
标准
1.2. 用途
基本上大部分的计算机语言都是基于 C 语言进行扩展的,它的性能极其优越,其他编程语言无法取代
- 操作系统(Linux、Unix 等操作系统就是利用 C 语言编写的)
- 数据库开发
- 高性能服务器开发
- 嵌入式开发
- 游戏开发
- ……
1.3. 开发工具
- 记事本(txt)
- 功能单一、体验差、易出错、开发效率低
- IDE(Integrated Development Environment):集成开发环境
- 智能提示、高亮识别、语法检测、开发效率高等强大功能
- 常见的 C 语言 IDE:Visual Studio、Qt Creator、CLion、Dev-C++、Xcode(Mac 操作系统)
二、程序的结构
任何一个 C 语言程序都由一个或多个函数构成,每个函数都由自己的功能,所以以后编写的 C 语言代码,基本上都是写在函数中。
2.1. main 函数
每一个函数都有自己的名称(标识符),并且每一个函数的名称都是唯一的。
大部分程序运行时都有一个入口,而这个入口就是main
函数,C 语言就是这样,如果没有 main 函数,程序就无法运行。
2.2. Hello World
1 |
|
printf
:将双引号中的文字显示到屏幕上。
2.3. 语法须知
- C 语言源代码文件的扩展名是
.c
; - 每一条语句后面都要以分号
;
结尾; - 括号都是成对出现的,小括号
()
,中括号[]
,大/花括号{}
; - 区分大小写;
- 代码中的符号必须是英文符号(注释、字符、字符串等内容除外,其实所有编程语言代码中的符号都是只支持英文符号的,除了易语言)。
三、计算机关联
3.1. 计算机 0 和 1 由来
计算机是由电路机演变而来的,电路的逻辑只有 0 和 1 两种状态,0 表示低电平,1 表示高电平,因此现在的电子计算机只能识别 0 和 1。
- 除了电子计算机、还有光子计算机、量子计算机、生物计算机、纳米计算机等高精端计算机(这些高端计算机不是只能识别 0 和 1 的)
- 我们平时说的计算机,就是指的电子计算机
3.2. 计算机如何识别 C 语言代码
我们编写好的代码交给计算机后,计算机是怎么识别出的?
上面提到计算机只能识别 0 和 1,要想计算机能够正常识别代码,就需要经过以下流程:
源代码(.c) -> 编译(compile) -> 编译文件(.o) -> 链接(link) -> 可执行文件(*.exe)
- 编译:将 C 语言源代码文件编译成目标文件(二进制文件),由编译器完成;
- 链接:将所有的目标文件以及所需要的库文件合并成一个可执行文件,由链接器完成。
3.3. 常见的 C 语言编译器(已内置链接器)
- MSVC:微软出品(用在 Windows 中)
- GCC:GNU Compiler Collection 缩写,GNU 出品
- MinGW:Minimalist GNU for windows 的缩写,GNU 出品
- LLVM:常用在苹果开发工具中
- ……
同一分源代码,用不同的编译器编译出来的目标文件是不一样的,主要体现在体积、格式、运行效率等。
不同操作系统最后链接产生的可执行文件(应用程序)格式也是不同的:
- Windows:PE 格式(常以
.exe
作为文件扩展名) - Linux:ELF 格式
- Mac:Mach-O 格式
3.4. 什么是 BUG?
- BUG 一般指的是程序漏洞、缺陷、错误,程序出现 Bug 后可能会导致程序无法正常运行;
- 来源:很早之前一位计算机科学家在调试设备时出现了故障,后来发现是有只飞蛾跑进了设备内部,卡住了机器的电路,导致机器无法运行,于是后来就把程序故障成为 Bug(臭虫),排除程序故障排叫做 DeBug。
3.5. 硬盘和内存
在 CPU、硬盘、内存 3 者之中,对我们开发者来说,最需要关注的内存,因为内存在程序运行过程中才执行的,而程序是运行在内存中的
四、注释
4.1. 什么是注释?
- 注释常用来解释某段代码的具体含义、作用
- 注释并不会被当做正常代码进行编译
4.2. 注释有 2 种书写格式
- 单行注释
- 多行注释
1 | // 导入C语言基础库(这是一段单行注释) |
4.3. 写注释的好处:
- 方便回忆,检查旧代码
- 方便程序员之间团队协作,提高开发效率
- 方便旧项目的交接
五、变量
变量的作用,可以存储程序运行过程中会变化的数据。
5.1. 如何声明一个变量?
格式:
1 | 变量类型 变量名; |
示例:
1 | int year; |
- 变量类型决定了变量能够存储什么类型的数据(上面的示例:int 类型决定了变量能够存储整数)。
5.2. 如何给变量赋值?
赋值:将数据交给变量去存储
1 | 变量名 = 数据; |
- 这个等号表示赋值,会把右边的数据赋值给左边的变量
1 | int month = 12 |
在变量声明完成后,直接通过变量名访问变量(不需要再带上变量类型(也不能带上变量类型))
变量可以被多次赋值,新值会覆盖旧值
1 | int num = 10; |
- 变量的第一次赋值,一般叫做初始化(init)
1 | // 声明一个变量,未初始化 |
- 变量声明的同时也可以进行初始化(开发中大部分操作)
1 | // 声明变量num,并对变量进行初始化,变量初始值为10 |
- 变量在未初始化之前,他的值是不确定的
- 变量在使用之前必须先进行初始化
- 可以同时声明多个同类型的变量
1 | int year, month, day = 30; |
- 可以将一个变量的值赋给另一个变量
1 | int num1 = 10; |
5.3. 变量的作用域
变量的作用域,就是指变量的作用范围,有效使用范围
- 从声明变量的那条语句开始,直到变量所在的大括号结束
- 在同一个作用域内,不允许有同名的变量
六、字符串
字符串由若干个字符组成的一串数据。
- 字符串用双引号包裹
1 | printf("Hello World"); |
- 如果字符串中出现了变量名,是不会去访问变量的,使用字符串时,会保留原来的字符内容
1 | int day = 30; |
- 字符串中的
%d
,是整数的占位符,到时会用对应的整数取代占位符,生成一个新的字符串
七、标识符
标识符,由开发者自定义的一些名称(比如变量名、函数名等)。
7.1. 标识符的命名规则如下:
- 不限长度
- 可以使用数字、下划线、英文字符
- 可以使用\u 及\U 转义记号指定的 Unicode 字符(C99 开始)
- 不能以数字开头
- 不能使用关键字(关键字,也叫做保留字,是编程语言内部已经定义好的一些有特殊含义的符号)
7.2. 命名规范:
- 小驼峰(第一个单词的首字母小写,其他单词的首字母大写),
myNameAndAge
- 大驼峰(所有单词的首字母大写),
MyNameAndAge
- 下划线连接,
my_name_and_age
,也有大写MY_NAME_AND_AGE
八、字面量
顾名思义,就是一个固定值。
1 | int age = 20; |
上面的代码中,等号右边的固定值都叫做字面量。
九、进制
什么是进制?进制是一种数字的表示形式;是一种带进位的计数方法。
计算机领域常见的进制有:十进制、二进制、八进制、十六进制。
9.1. 十进制(逢十进一)
由十个数字符号构成所有的数值:0、1、2、3、4、5、6、7、8、9
9.2. 八进制(逢八进一)
由八个数字符号构成所有的数值:0、1、2、3、4、5、6、7
9.3. 二进制(逢二进一)
由两个数字符号构成所有的数值:0、1
9.4. 十六进制(逢十六进一)
由十六个数字及字母符号构成所有的数值:0、1、2、3、4、5、6、7、8、9、A、B、C、D、E、F。
- A、B、C、D、E、F 分别表示十、十一、十二、十三、十四、十五
- 也可以用小写字母表示:a、b、c、c、e、f
同一个数可以用不同的进制来表示:比如三十三
- 十进制:33
- 二进制:100001
- 八进制:41
- 十六进制:21
9.5. 基数
进制中有个叫“基数”(或者底数)的概念
- 基数是指可以使用的数字符号的数目
- 十进制的基数是十、二进制的基数是二、八进制的基数是八、十六进制的基数是十六
9.6. 进制转换(任意 R 进制 -> 十进制)
一个 R 进制表示的数转为十进制:$ k_3 k_2 k_1 k_0.s_1 s_2 s_3 $
公式:$$ (k_3 _ R^3) + (k_2 _ R^2) + (k_1 _ R^1) + (k_0 _ R^0) + (s_1 _ R^{-1}) + (s_2 _ R^{-2}) + (s_3 * R^{-3})$$
二进制 -> 十进制
- $$(1101.011)_2 = (1 * 2^3) + (1 * 2^2) + (0 * 2^1) + (1 * 2^0) + (0 * 2^{-1}) + (1 * 2^{-2}) + (1 * 2^{-3}) = 13.375$$
八进制 -> 十进制
- $$(537.34)_8 = (5 * 8^2) + (3 * 8^1) + (7 * 8^0) + (3 * 8^{-1}) + (4 * 8^{-2}) = 351.4375$$
十六进制 -> 十进制
- $$(4DA.CB)_16 = (4 * 16^2) + (13 * 16^1) + (10 * 16^0) + (12 * 16^{-1}) + (11 * 16^{-2}) \approx 1242.79$$
9.7. 十进制 -> 任意 R 进制
十进制数转换成 R 进制数,须将整数部分和小数部分分别转换
- 整数转换(除 R 取余法)
- 用十进制数的整数部分除以 R、取余数作为转换后的 R 进制数据的整数部分最低位数字;
- 再用所得的商除以 R、取余数作为转换后的 R 进制数据的高一位数字;
- 重复上面第二次的操作,一直到商为 0 结束。
求 115 的二进制:$$115 = (1110011)_2$$
- 小数转换(乘 R 取整法)
- 十进制数的小数部分乘以 R、取乘积的整数部分作为转换后 R 进制数据的小数点后第一位数字;
- 再用所得的乘积的小数部分乘以 R、然后取新乘积的整数部分作为转换后的 R 进制小数的低一位数字;
- 重复上面第二次的操作,一直到乘积的小数部分为 0 为止,或者一直到已得到要求的精度数位为止。
求 0.625 的二进制:$$0.625 \approx (0.101)_2$$
- 小数转换(整数退位法)
- 0.321 换成二进制数,保留 7 位
$$0.321 * 2^7 = 41.088$$ - 取整数 41 $$41 = (101001)_2$$
- 只有 6 位但要求保留 7 位,退位,在前面补 0,所以是 0.0101001
- 0.321 换成二进制数,保留 7 位
9.8. 二进制与十六进制之间的转换
4 位二进制数恰好有 16 种组合状态,也就是说,1 位十六进制数与 4 位二进制数是一一对应的
计算:
- $$(111010110.11)_2 = (0001 1101 0110.1100)2 = (1D6.C){16}$$
- $$(4AF8B.E)_{16} = (0100 1010 1111 1000 1011.1110)_2 = (1001010111110001011.111)_2$$
9.9. 二进制与八进制之间的转换
3 位二进制数恰好有 8 种组合状态,也就是说,1 位八进制数与 3 位二进制数是一一对应的
计算:
- $$(111010110.11)_2 = (011 010 110.110)_2 = (326.6)_8$$
- $$(2476.4)_8 = (010 100 111 110.100)_2 = (10100111110.1)_2$$
9.10. 代码中的进制书写形式
C 语言标准规定
- 默认是十进制,比如 89,表示【八十九】
- 以 0 开头的是八进制,比如 024,表示【二十】
- 以 0x 或 0X 开头的是十六进制,比如 0x2B,表示【四十三】
C 语言标准并不支持二进制的书写形式
- 但是部分编译器支持,比如 GCC 规定以 0b 或 0B 开头的是二进制
1 | int i1 = 20; // 十进制 |
十、内存
10.1. 存储单位
字节(Byte,B)是一种基本存储单位(在计算机领域用来计量存储容量)。
单位换算:
1 字 = 2B(字的英文是 word,1 字等于 2 字节)
- 1KB = 1024B(千字节)
- 1MB = 1024KB(兆字节)
- 1GB = 1024MB(千兆字节)
- 1TB = 1024GB(太字节)
- 1PB = 1024TB(拍字节)
- 1EB = 1024PB, 1ZB = 1024EB, 1YB = 1024ZB,
- 1BB = 1024YB, 1NB = 1024BB, 1DB = 1024NB,
10.2. 内存地址
4GB 的内存一共有多少字节?
4GB = 41024MB = 410241024KB = 4102410241024B = 4294967296B = 2^32B
内存中的每一个字节都有自己的编号
- 上面图示是 4GB 内存空间
- 如果把内存比作是酒店,
- 那么字节就是酒店的房间,字节的编号就是房间号
- 相邻的 2 个房间,房号是连续的,那么相邻的 2 个字节,编号是连续的
- 房间是拿来住人的,存放物品的,那么字节是拿来存储数据的
- 每一个字节的编号,也叫做字节的内存地址
10.3. 变量的存储空间
变量是用来存储数据的,凡是数据,或多或少会占用一定的存储空间。
变量的数据是存储在硬盘还是内存中呢?
- 内存。因为变量是用来存储程序运行过程中会变化的数据
一个变量会占用多大的存储空间呢?(占用多少字节)
- 取决于变量所存储数据的类型(也就是变量类型)
- 比如 C 语言标准规定,一个 int 类型的变量至少占用 2 个字节的内存空间
- 在 Win64 API 中,一个 int 类型的变量占用 4 个字节的内存空间
在 C 语言中,怎么查看变量占用了多少内存空间呢?
10.4. sizeof 运算符
使用sizeof
可以获得某一数据类型的大小(所占多少字节的内存空间)。
1 | int age = 20; |
10.5. 变量的内存地址
1 | int a = 10; |
- 变量的内存地址:变量首字节(地址值最小的那个字节)的内存地址
- 越晚定义的变量,内存地址越小
十一、位(Bit)
- 由于计算机只能识别 0 和 1,所以每一个字节中的数据都是以二进制形式存储的
- 一个字节包含 8 个二进制位,位是计算机领域最小存储单位,每一个二进制存储一个 0 或 1
- 1Byte = 8bit, 简写: 1B = 8b
11.1. 有符号数的二进制表示方法
int
类型属于有符号整数类型
- 可以表示正数、负数
1 | int a = 20; |
- 有符号数的二进制有 3 种表示方法:源码、反码、补码
- 三种表示方法均有符号位和数值位两部分
- 符号位:最高位作为符号位,用 0 表示“正”,用 1 表示“负”
- 数值位:三种表示方法各不相同
11.2. 原码
- 最高位:作为符号位,用 0 表示“正”,用 1 表示“负”
- 其余位:作为数值位,表示绝对值的大小(所以原码又称为带符号的绝对值)
11.3. 反码
- 正数的反码:和原码一样
- 负数的反码:原码保持符号位不变,数值位按位取反(0 变 1,1 变 0),得到反码
11.4. 补码
- 正数的补码:和原码一样
- 负数的补码:反码的末位加 1,得到补码
在计算机中,数值一律用补码来表示和存储。
比如,下面 2 个变量在内存中存储的二进制数值是(假设int
类型的变量占用 4 字节)
1 | int a = 20; |
- a: 0000 0000 0000 0000 0000 0000 0001 0100
- b: 1111 1111 1111 1111 1111 1111 1110 1100
十二、大小端模式
大小端模式:决定了多字节数据的字节存储顺序
大端模式:高低低高
- 高字节放在低地址,低字节放在高地址
小端模式:高高低低
- 高字节放在高地址,低字节放在低地址
不同 CPU 架构的模式不一样,比如 x86 架构师小端模式,有些 CPU 架构师大端模式,目前比较常见的是小端模式。
十三、char 类型
char
类型属于字符类型。
- 字符用单引号
- 打印的占位符:
%c
1 | char c1 = 'k'; |
- 一个
char
类型的变量占用 1 个字节的内存,所以它只能存储 1 个单字节字符 - 26 个英文字母(a ~ z,A ~ Z),10 个阿拉伯数字(0~9)都是单字节字符
下面的代码有问题:
1 | // 不能存储超过1个单字节字符 |
13.1. 字符的存储细节
计算机中的数据都是以二进制形式存储的,字符数据也不例外。每一个字符都会被转化成对应的整数值进行存储。
在 1967 年,美国发布了 ASCII 码表,里面规定了 128 个单字节字符对应的整数值(ASCII 码值)。
- 数字 0 对应的码值是 48
- 大写字母 A 对应的码值是 65
- 小写字母 a 对应的码值是 97
1 | int i = 97; |
十四、ASCII 码表
ASCII 被译为“美国信息交换标准码”,是一种标准的单字节字符编码方案。
共 128 个字符,码值范围:0~127(也就是 0x00 ~ 0x7F)
33 个是控制字符或通信专用字符,码值范围:0~31,127
- 控制字符:LF(换行)、DEL(删除)、BS(退格)等
- 通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等
95 个可显示字符,码值范围:32~126
- 48 ~ 57:十个阿拉伯数字(0~9)
- 65 ~ 90:26 个大写英文字母(A~Z)
- 97 ~ 122:26 个小写英文字母(a~z)
- 其余为标点符号、运算字符等
没有必要记住所有字符码值,只需要记住常用的两个就行了:
- 小写字母 a 的 ASCII 码值:97
- 小写字母的 ASCII 码值 = 大写字母 ASCII 码值 + 32
- 大写字母的 ASCII 码值 = 小写字母 ASCII 码值 - 32
- 阿拉伯数字 0 的 ASCII 码值:48
注意使用细节:
1 | char c1 = 'a'; |
大小写字母之间的转换
1 | char c1 = 'n'; |
14.1. 数字整数、数字字符在内存中的区别
1 | int i = 9; |
- 变量 i 在内存中利用 4 个字节存储数值 9
- 变量 c 在内存中利用 1 个字节存储数值 57
疑问:中文、日文、韩文等非英文字符是如何存储在计算机中的?
可以肯定的是,必然是以二进制的形式存储在计算机中的,只是每个字符对应的二进制数值取决于具体的编码方案(GBK、UTF-8、Unicode 等),
GBK 主要支持的是 CJK 汉字字符(C-中国、J-日本、K-朝鲜),而 UTF-8 支持几乎世界上所有的字符。
14.2. 转义序列
转义序列,一般也叫作转义字符,是一些有特殊含义的字符
1 | printf("我叫'idbeny',在\"1024星球\"等你"); // 输出:我叫'idbeny',在"1024星球"等你 |
十五、scanf 函数
printf
函数的功能是:输出(output),将数据显示在屏幕上scanf
函数的功能是:输入(input),读取数据(比如读取通过键盘输入的数据)
1 | int age = 10; |
- scanf 函数开始执行后,会等待用户输入
- 程序会卡在 scanf 函数,不会执行 scanf 函数后面的代码
- 当用户敲 Enter 键(回车键)时,表示输入完毕
- 程序才会开始执行 scanf 函数后面的代码
15.1. 匹配的细节
1 | int i = 10; |
总结:
- 从左到右开始字符匹配,当中途匹配失败时,将结束匹配
- 当尝试把输入的数字赋值给字符变量时,scanf 把输入的数字当成是数字字符来处理,而不是把输入的数字当成字符的 ASCII 码值来处理
15.2. scanf 函数的空白字符
空白字符包括:空格(’ ‘)、Tab(’\t’)、Enter(’\n’)
如果在输入整数的开头,有一段任意长(长度 ≥0)连续空白字符,是不需要与格式化字符串中的字符进行匹配的。
1
2
3
4
5
6
7int age = 10;
scanf("%d", &age);
printf("age is %d", age);
/*
输入: 30
输出:age is 30
*/在格式化字符串中,任意长(长度 ≥0)连续空白字符,能匹配输入的任意长(长度 ≥0)连续空白字符
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15int age = 10;
scanf(" %d", &age); // %d左边有空格
printf("age is %d", age);
/*
输入: 30
输出:age is 30
*/
int age = 20;
scanf("%d ", &age); // %d右边有空格
printf("age is %d", age);
/*
输入: 30
输出:age is 30
*/