一、重识C语言
1.1.定义
C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发的计算机编程语言。 C语言能以简易的方式编译、处理低级存储器。C语言是仅产生少量的机器语言以及不需要任何运行环境支持便能运行的高效率程序设计语言。尽管C语言提供了许多低级处理的功能,但仍然保持着跨平台的特性,以一个标准规格写出的C语言程序可在包括类似嵌入式处理器以及超级计算机等作业平台的许多计算机平台上进行编译。
1.2.常见编译器
Clang、GCC、MSVC……
1.3.第一个C语言程序
1.4.强调
C语言代码从主函数(main函数)第一行代码开始执行。在一个工程中main函数有且只有一个。
二、数据类型
2.1. 数据种类
符号 | 名称 |
---|---|
char | 字符数据类型 |
short | 短整型 |
int | 整型 |
long | 长整型 |
longlong | 更长整型 |
float | 单精度浮点整型 |
double | 双精度浮点整型 |
例:创建一个储存字符“a”的代码。
2.2. 各类型的大小
类型 | 大小(单位:字节) |
---|---|
char | 1 |
short | 2 |
int | 4 |
long | 4 |
longlong | 8 |
float | 4 |
double | 8 |
常见计算机中的单位
① bit - 比特位 - 用来存放一个二进制为 - ‘0’ 或 ‘1’;
② byte - 字节 —— 1 byte = 8 bit;
③ Kb ——1 Kb = 1024 byte;
④ Mb —— 1 Mb = 1024 Kb;
⑤ Gb —— 1 Gb = 1024 Mb;
⑥ Tb; ⑦ Pb ……
2.3.各数据类型的打印提示符
提示符 | 用途 |
---|---|
%d | 打印整数 |
%c | 打印字符 |
%s | 打印字符串 |
%p | 打印地址 |
%.nf(n为整数) | 打印浮点数至小数点后n位 |
%u | 打印无符号整数 |
%nd(n为整数) | 右对齐打印,且宽度为n |
%-nd(n为整数) | 左对齐打印,且宽度为n |
2.4. 补充说明
1.类型众多的原因:提高空间利用率。
2.用途:用数据类型创建变量。
三、字符串+转义字符+注释
3.1. 字符串
3.1.1.定义
用双引号括起来的一串字符(“abcdef”)。在C语言中通过字符数组来表示字符串。字符串的结束标志是一个“\0”的转义字符,通常隐藏。
3.1.2.计算字符串长度
语法: strlen("abc");
使用该函数需要引入头文件<string.h>.
注意 "\0"不计入字符串长度
3.2. 转义字符
定义: 转变了原有含义的字符
注意: 转义字符计入字符串长度
原因: \t
和\n
均为转义字符,转变了原本想表达的意思。
解决方案: 在转义字符前加\
便恢复原意。
补充
\ddd
,ddd表示3个八进制数字(如:\130x);
\xdd
,dd表示2个十六进制数字(如:\x300).
3.3. 注释
用途: 用来解释复杂代码或忽略无用代码
注意事项:不允许嵌套注释
三、变量&常量
3.1. 定义
变量: 不变的量;
常量: 可变的量。
3.2. 变量
3.2.1.分类
局部变量: 大括号内部的变量;
全局变量: 大括号外部的变量。
即当局部变量与全局变量名称冲突时,局部变量有限! (不建议局部变量与全局变量同名)
3.2.2.使用
3.2.3.作用域&生命周期
①作用域:
- 局部变量的作用域就是变量所在的局部范围;
- 全局变量的作用域就是整个工程。
②生命周期: 变量创建和销毁之间的时间段
- 局部变量的生命周期是进入局部范围开始,出局部范围结束。
- 全局变量的生命周期是程序的生命周期。
3.3. 常量
3.3.1. 字面常量
3.3.2. const
修饰的常量
const - 不被修饰的 adj.
3.3.3. #define
定义的标识符量
定义某一个量(自定义)。
3.3.4.枚举常量
用于可以一 一列举出的常量
(枚举常量通常从0开始)
四、分支语句和循环语句
4.1. 前言
C语言是结构化的编程语言,它有顺序结构、选择结构和循环结构三种类型;
C语言中;
隔开的就是一个语句
4.2. 分支语局(选择结构)
结构示意图
C语言中,0为假,非0为真
4.2.1. if
语句
① 结构语法:
无分支
if ("表达式")
"语句"
双分支
if ("表达式")
"语句1";
else
"语句2";
多分支
if ("表达式1")
"语句1";
else if ("表达式2")
"语句2";
else
"语句3";
- 例:判断是否成年
强调: if - else 语句只能控制一条语句,若想控制多条语句需要带“ { } ”。
- 例:
表示并列关系不能用 18<age<=26,只能用18<age &&age<=26.
② 悬空 else问题
①处,else之于最近的if相互作用;
②处,该语句无法继续,直接结束。
即,else和与其距离最近的if
起作用。
-
例:判断一个数是否是奇数
-
例:输出1~100之间的奇数
4.2.2. switch
语句
用于多分支的场景
① 结构
强调:
- 必须有停止项;
- 有入口(case),也有出口(break).
- break的位置由实际情况而定。
示例1:
示例2:
②default
子句
(不受位置限制)
结构:
default:
"语句";
break;
- 例:
4.3.循环语句
4.3.1. while
语句
①结构:
while(表达式)
循环语句
②图示:
- 例:
③break的讲解
讲解:
在while循环中,break用于永久破坏循环。
④continue的讲解
讲解:
在while循环中,continue的作用是跳过本次循环continue后的代码,直接到判断部分,判断是否进行下一次循环。
- 例:打印数字字符
补充:getchar 和 putchar
getcahr() —— 得到一个字符;
putchar() —— 输出一个字符。
EOF —— end of file —— 文件结束标志
4.3.2. for
语句
①.结构
for(表达式1(初始化);表达式2(判断部分);表达式3(调整部分))
{"循环语句"}
②.图示
- 例:
③.break 与 continue 的应用
-
示例1:
-
示例2:
continue 在for语句中不跳出“调整部分”,但在while语句中跳出“调整部分”。
④.注意事项
1)不在for循环体内修改循环变量,防止for循环失去控制;
- 若①处的“i++”删除添加到②处,则可正常进行
- 若①处和②处均有调整部分,则会使for语句失去控制
2)for语句的循环控制变量的取值采用“前闭后开区间”写法。
for(i=0; i<=9; i++)
左闭右闭区间
for(i=0; i<10; i++)
左闭右开区间
3)若判断部分省略,则判断部分恒为真。
- 例1:
- 例2:
判断为假,不循环。
4.3.3. do…while
语句
①结构
do
循环语句;
while(表达式);
②图示
- 例:
③break 与 continue 的运用
- 例:
- 例:
循环至少执行一次
5.3.4. 'goto’语句
C语言中提供了可以随意使用的goto语句和标记跳转标号。goto语句最常见的用法就是终止程序在某些深度嵌套的结构的处理过程。
语法:goto "标号";
示例:
实现一个函数:若输入指定字符串则不强制关机;若输入非指定字符串或
不输入,则在60秒后自动关机
4.4. 分支语句和循环语句的综合应用
1.计算n的阶乘。
2.计算1!+2!+3!+ …… +9!+10!
①普通方案
②优化方案
3. 在一个有序数列数列中找出具体的某个数字
4. 编写代码模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录完成;如果三次均输入错误则退出程序)
字符串比较不能用“ == ”只能用strcmp函数比较!
4.5. 循环语句中break
,continue
的作用总结
break:
在while
、for
、do…while
语句中,break
的作用都是直接终止循环语句。
continue:
while语句 —— 终止本次循环,continue
后的代码不再执行,开始下一次循环;
for语句 —— 终止本次循环,continue
后的代码不再执行,但不跳过调整部分,在调整后加入下一次循环;
do…while语句 —— 仅跳过continue
后的代码,但判断部分不跳过。
五、函数
5.1. 定义
子程序:某一大型程序的部分代码,具有一定独立性。
分类:库函数;自定义函数。
5.2. 库函数
为了简化流程而提升效率
库函数的学习途径
① www.cplusplus,com
② MSDN
③ http://en.cppreference.com (英文版)
④ http://zh.cppreference.com (中文版)
常用库函数
①IO函数——输入输出函数:printf;scanf;getchar;push;
②字符串操作函数:strmp;strlen;
③字符操作函数:toupper;
④内存操作函数:memcpy;memcmp;memset;
⑤时间操作函数——日期操作函数:time;
⑥数学函数:sqrt;pow;
⑦其他函数。
库函数使用必须引用相应的头文件
- 例:memset函数
5.3. 自定义函数
5.3.1.结构
函数的返回类型 函数名(函数的参数)
{
函数体(语句项);
}
5.3.2. 示例
①找出两个整数的最大值
②写一个交换两个变量整型的值的函数
用指针使主函数与swap函数产生地址与数值上的联系
5.4. 函数的参数
5.4.1.实际参数(实参)
真实传给函数的参数;都必须有确定的值。
如:常量、变量、表达式、函数……
5.4.2.形式参数(形参)
函数名后括号中的变量;只有在函数调用的过程中才有效,函数调用完后自动销毁。
- 例:
5.5. 函数的调用
5.5.1.传值调用
函数的形参和实参分别占用不同的内存块,对形参的修改不会影响实参。
5.5.2.传址调用
- 把函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式;
- 传址调用可以让函数和函数外部的变量建立起真正的联系;函数内部可以直接改变外部变量。
5.5.3.实例
①写一个函数,列出1~200间的素数
②写一个函数,列举出1000年~2000年的闰年
若不写返回类型会默认为“int型”,但不建议不写——标准化,规范化。
③写一个函数,实现一个整数有序数组的二分查找
数组arr传参,实际传的不是数组本身而是传过去了数组的首元素地址;再以参数的形式传递过去。
函数内部变量改变外部变量需要地址和相应的指针;
&a(取地址) ——> *a(传指针)。
5.6. 函数的嵌套调用和链式访问
函数与函数之间是可以有机合成的
5.6.1.嵌套调用
5.6.2.链式访问
把一个函数的返回值直接作为另一个函数的参数
注意!
printf函数在其他函数里作参数时,打印的时函数里字符的个数;
若在最外侧的时候则打印内容。
5.7. 函数的声明和定义
先定义(或声明)再使用函数;函数的声明一般放在头文件中。
5.8.函数递归
5.8.1.定义
一个函数和过程在其定义或说明中有直接或间接调用自身的一种方法。(自己调用自己)
5.8.2.条件
- 存在限制条件,当满足这个限制条件的时候递归便不再继续;
- 每次递归之后越来越接近这个条件。
5.8.3.实例
①接受一个整型值,按照顺序打印它的每一位
不能死递归,都有跳出条件,每次跳出要逼近条件;
递归层次不能太深。
补充:stackoverflow(栈溢出)
5.8.4. 递归与迭代
实例1:求n的阶乘
方案1:迭代法
方案2:递归法
实例2:求第n个斐波那契数
方案1:递归法
方案2:迭代法
六、数组
6.1. 定义
一组相同类型元素的集合
6.2. 一维数组
6.2.1. 一维数组的创建方式
type_t arr_name [const_n]
type_t 数组的元素类型;const_n 常量表达式,用来指定数组的大小。
arrp[ n ] 变长数组——数组的参数是变量。变长数组在C11标准中才引进。
6.2.2.一维数组的初始化
int a = 10;
初始化;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
完全初始化;
int arr[10] = {1,2,3,4,5}
不完全初始化;
int arr[ ] = {1,2,3,4}; 与 int arr[4] = {1,2,3,4}; 等价!
6.2.3一维数组的使用
- 如:
1)数组是使用下标来访问的,下标是从0开始;
2)数组的大小可以通过计算得到。( int sz = sizeof(arr) / sizeof(arr[0]) )
[ ] 下标引用操作符。
6.2.4. 一维数组在内存中的存储
①一维数组在内存中是连续存放的;
②随着数组下标的增长,地址是由低到高变化的。
- 实例:
%x —— 十六进制打印; %p —— 按地址的格式打印—十六进制打印。
6.3. 二维数组
6.3.1. 二维数组的创建
结果:
6.3.2. 二维数组的初始化
(创建的同时并赋值)
int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
按行排列,①;
int arr[3][4] = {1,2,3,4};
不完全初始化后补0,②;
int arr[3][4] = {{1,2},{3,4},{4,5}};
③;
int arr[ ][4] = {{1,2},{3,4},{4,5}};
只有行数可省略。
①结果:
③结果:
6.3.3. 二维数组的使用
6.3.4. 二维数组的存储形式
- 二维数组在内存中是连续存放的;
- 单个行内部是连续的,跨行间也是连续的。
- 图示:
强调:“arr[2][3]”中,"arr[2]"为函数名! - 实例
6.4. 数组作为函数参数
实例:冒泡排序
①排序思想:两两相邻的元素进行比较并且可能的话进行交换。
确定趟数 = 个数 - 1.
元素个数的计算“int sz = sizeof(arr) / sizeof(aee[0]);”不能在自定义函数中,应当在主函数中,然后以参数的形式传至自定义函数中。
函数在传参时,传递的时数组的首元素地址。
②代码:
6.5. 数组名
首元素的地址
例外情况:
1.sizeof 数组名 —— 其中数组名表示整个数组,计算的是整个数组的大小;
2.& 数组名 —— 表示的是整个数组,取出的是整个数组的地址。
七、操作符&表达式
7.1. 算术操作符
“ +
”,“ -
”,“ *
”(乘),“ /
”(除),“ %
”(取余,取模)
- int a = 9
/
2;
printf(“%d\n”,a); ⇒ a = 4; - float a = 9
/
2;
printf(“%f\n”,a); ⇒ a = 4; - float a 9
/
2.0;
printf(%f\n",a); ⇒ a=4.5; - int a = 9
%
2;
printf(“%d\n”,a); ⇒ a=1.
若要执行小数运算,则式中至少有一个小数;
%
两侧必须是整数。
当出现
%d % %d
会生成%d %d
,两个%
会编译成一个%
,即%% => %
。若要消除这种错误,则应当修改为%d %% %d
.
7.2. 移位操作符
>>
右移,
<<
左移.
移动二进制位
左移操作符:
左边丢弃,右边补0;
右移操作符:
1.算术右移:右边丢弃,左边补原符号位;
2.逻辑右移:右边丢弃,左边补0.
- 如:
2的二进制位:
00000000 00000000 00000000 00000010
左移:
00000000 00000000 00000000 00000100
左移后为4的二进制位
7.3. 位操作符
&
(按位与) ; |
(按位或) ; ^
(按位异或)
按二进制位
7.3.1.&
有0则0,全1才1。
例:3 & 5 = 1
3::00000000 00000000 00000000 00000011;
5:00000000 00000000 00000000 00000101;
1:00000000 00000000 00000000 00000001.
7.3.2. |
有1则1,全0才0.
例:3 | 5 = 7
3: 00000000 00000000 00000000 00000011;
5: 00000000 00000000 00000000 00000101;
7: 00000000 00000000 00000000 00000111;
7.3.3. ^
相同为0,相异为1.
例:3 ^ 5 = 6
3: 00000000 00000000 00000000 00000011;
5: 00000000 00000000 00000000 00000101;
6: 00000000 00000000 00000000 00000110;
4. 用途
实例:交换两个int变量的值,不能使用第三个变量
即a=3,b=5交换之后a=5,b=3.
a ^ a = 0; 0 ^ a = a;b=a ^ a ^ b=a ^ b ^ a
7.4. 赋值操作符
“=” —— 赋值;
“==” —— 判断相等。
7.5. 单目操作符
7.5.1. !
逻辑反操作符——“ !
”
例:“ ! ” 的用途
if ( a )
{ } //如果a为真,操作
if ( !a )
{ } //如果a为假,操作
return 0;
补充:在计算机逻辑中 0 为假,非 0 为真;通常表达为 1。
7.5.2. +
-
" -
"负值;“ +
”正值**
一般“ +
”省略。
7.5.3 sizeof
“ sizeof
”计算统计操作数的类型长度,单位:字节。**
7.5.4. ~
“ ~
”对一个数的二进制位按位取反**
补充:按位取反
如: 0010110 <=(~)=> 1101001
一个二进制整数的表达有三种,分别为原码、反码和补码;但整数在计算机中存储的是补码。且正整数的原码、反码和补码相同!
如:-1
1
为符号位,不改变!
原码:1
000000 0000000 00000000 00000001
反码:1
1111111 11111111 11111111 11111110
补码:1
1111111 11111111 11111111 11111111(补码 = 反码 + 1)包括符号位!
7.5.5. ++
--
前置 ++
(--
):先运算,再使用;
例:
讲解:“a=10”先“++”,则a变为11; 又因为“b=++a”,则“b=11”此时的“++a”表示“10+1”,即“++a=11”;
且此时的a已加,则a已经变为11
即:先给a的值加,再把加后的a的值给b用,此时a的值也发生变换。
后置 ++
(--
):先使用,再运算。
例:
讲解:先使用a的值给b,在给a自身“++”,最后使用改变后的自己。
7.5.6 &
*
%p
&
——取地址;*
——间接访问操作符(解引用操作符);%p
——打印地址。
7.5.7. ( )
("类型")
—— 强制类型转换**
例:强制类型转换
讲解:若不使用会出现报错。因为“int”为整数类型,“3.14”为小数,应使用“float”浮点型才不会出错
7.6. 关系操作符
<;<=;>;>=;!=;==;
7.7. 逻辑操作符
“ && ” —— 与;“ | | ” —— 或.
(与高中真值表类似)
7.8. 条件操作符
AAA ?
BBB :
CCC
若AAA成立,则执行BBB,输出的结果是BBB的结果;
若AAA不成立,则执行CCC,输出的结果是CCC的结果;
例:
普通表达
if ( a> b)
max = a; :
else
max = b;
条件操作符表达
max = a>b ? a : b;
7.9. 逗号表达式
用“ ,
”隔开的一串表达式
例:
逗号表达式是从左向右依次进行的
逗号表达式的结果是表达式中最后一个表达式的结果
上面例子的计算过程及结果
a = b + 2 = 3 + 2 = 5,
c = a - 4 = 5 - 4 = 1,
b = c + 2 = 1 + 2 = 3.
即输出的结果是 3.
7.10. 其他操作符
1. [ ]
下标引用操作符;
2. ( )
函数调用操作符;
3.“ .
”“ ->
” 结构成员。
八、常见关键字
8.1. 种类
auto (自动的); break ; case ; char (字符类型); const (常变量); continue ; default ; do ; double ; else ; enum (枚举); extern (声明外部符号); float ; for ; goto ; if ; int ; long ; register (寄存器关键字); return ; short ; signed (有符号的); sizeof ; static (静态的); struct (结构); switch ; typedef (类型定义); union (联合体); unsigned ; void ; volatile ; while.
特点:①C语言提供,不能自创;②不能作为变量名。
补充:计算机中,数据的储存位置。
强调:“define”、“include”等不是关键字,是预处理指令!
8.2. typedef 类型的重定义
是给类型重命名
A与B等价!
8.3. static 静态的
8.3.1.用途
- 修饰局部变量。改变局部变量的生命周期,本质是改变了其存储类型。
- 修饰全局变量。使修饰的这个变量只能在该源文件使用;本质是全局变量在其他源文件都可以是因为全局变量具有外部链接属性;但被static修饰后,就变成了内部链接属性,其他源文件就不能链接到这个静态的全局变量。
- 修饰函数。使函数只能在自己所在的源文件内部使用,不能在其他源文件内部使用,本质是将函数的外部链接改为内部链接。
8.3.2.static 修饰局部变量
讲解:先运行①,在运行②,然后输出②的结果,结果为:22222 22222;
若将③处改为 “static int a = 1;” 则结果为:23456 7891011.
**补充:存储类型
8.3.3.static 修饰全局变量
源文件A:
源文件B
①处,声明外部符号; (全局变量整个工程都能用)
②处,若改为“static int g_val = 2022;”则文件B无法识别。
8.3.4.static 修饰函数
源文件A
源文件B
①处,若改为“static int Add(int x,int y)”,则源文件B无法识别。
8.4. register 关键字
九、指针
9.1. 内存
9.1.1.内存的编号
32位 —— 32根地址线 —— 物理线 —— 通电 —— 0 / 1;
电信号转换成数字信息,成为1和0组成的二进制序列。
即一个32位内存有 232 个内存单元
9.1.2.一个内存单元的空间大小
①.假设一个内存单元是1bit,则232bit = 536870912 byte = 524288kb = 512Mb = 0.5GB
由此可见,一个内存单元是1bit太浪费
最终规定,一个内存空间的大小为1个字节(1 byte = 8 bit )
9.2.指针变量
9.2.1.定义
存放地址的变量
地址并不唯一确定
9.2.2.指针变量的大小
32位处理器得出的是4;64位处理器得出的是8.
指针的大小是相同的
指针是用来表示地址的,指针需要多大空间取决于地址的存储需要多大空间。
32位——32bit——4byte
64位——64bit——8byte
9.3.指针类型
- 指针类型的意义
①指针类型决定了指针解引用的权限有多大;
②指针类型决定了指针走一步能有多长。
9.4. 野指针
指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)
9.4.1. 成因
①指针未初始化;
②越界访问
③非法访问内存(指向的空间被释放)
9.4.2. 如何避免野指针
①指针初始化
1)当不知道初始化为什么时,初始化为NULL(空指针);
2)明确知道初始化的值。
②小心指针越界;
③指针释放的指向空间失效后,及时释放NULL;
④指针使用之前检查有效性。
9.5. 指针运算
9.5.1. 指针 ± 整数
2. 指针 - 指针
得到的是两个指针间元素的个数。
3. 指针的关系运算
实例:
9.6. 指针和数组
数组名是数组首元素的地址
9.7. 二级指针
指针变量的存放地址
9.8. 指针数组
本质上是一个数组
十、结构体
10.1.定义
**用来描述复杂对象的方法。**结构是一些值的集合;这些值称为成员变量。结构的每个成员可以是不同类型的变量。
- 实例:
S1,S2也是结构体变量,是全局变量;
s 是局部变量。
10.2. 结构体定义和初始化
结构体变量也可当成员变量
结构体嵌套初始化:s = {,{ }, { }, ,} ;
10.3. 结构体成员的访问
通过“ .
”操作符访问的(" ->
"也可以)
如:
s . a . b
或 s -> a -> b
(正推)
(*ps). sb . c
或 ps -> sb . c
(逆推)
“ ->
”用于指针,“ .
”用于普通的
10.4. 结构体传参
- 实例:
补充:函数传参时,选用传址调用的原因
函数传参的时候,参数是需要压栈的;若传递一个结构体对象的时候,结构体过大,参数压栈的系统开销比较大,会导致性能下降。
故结构体传参的时候要传递结构体的地址。参数压栈
栈 —— 数据结构 —— 先进后出,后进先出
十一、文件
11.1. 定义
磁盘上的文件就是文件,该文件的数据是长期的。而内存中的数据是临时的。
11.2. 文件的打开与关闭
11.2.1.文件指针
在计算机中,每一个文件在使用过程中均会开辟一个文件信息区用来存放文件的相关信息。这些信息会存放在一个结构体中。该结构体的类型为FILE
,其指针为FILE *
.
我们可以通过文件指针来找到对应的文件。
11.2.2. 文件的打开与关闭
打开文件语法:
FILE * fopen("文件名",“打开方式”)
;
关闭文件语法:
FILE * fclose(FILE * stream)
。
常用打开方式
打开方式 | 含义 | 如果文件不存在的操作 |
---|---|---|
r (只读) | 为了输入数据,打开一个已经存在的文本文件 | 报错 |
w (只写) | 为了输出数据,打开一个文本文件 | 建立一个新的文件 |
a (追加) | 向文本文件尾部添加数据 | 建立一个新文件 |
- 示例:
11.3. 文件的随机读写
11.3.1. fseek
用途: 根据文件指针的为位置和偏移量来定位文件指针。
语法: int fseek(FILE *stream,"偏移量",起始位置)
11.3.2. ftell
用途: 返回文件指针相对于起始位置的偏移量。
语法: long int ftell(FILE * stream)
11.3.3. rewind
用途: 让文件指针回到起始位置。
语法: void rewind(FILE * stream)
—— writing by Pan Qifan(潘琦藩) ——