概览
项目地址
https://github.com/impact-eintr/LinuxC
c的历史
- 1960 原型A语言->ALGOL语言
- 1963 CPL语言
- 1967 BCPL
- 1970 B语言
- 1973 C语言
C语言特点
- 基础性语言
- 语法简洁 紧凑 方便 灵活(得益于指针)
- 运算符 数据结构丰富
- 结构化 模块化编程
- 移植性好 执行效率高
- 允许直接对硬件操作
学习建议
- 概念的正确性
- 动手能力
- 主动阅读优秀的程序段
- 大量练习,编程是技术不是理论
学习思路
- 基本概念
- 数据类型 运算符 表达式
- 输入输出
- 流程控制
- 数组
- 指针
- 函数
- 构造类型
- 动态内存管理
- 常用库函数
- 调试工具和调试技巧
环境搭建与”Hello world”
环境
- 当前测试环境是安装了基于
archlinux
的manjarolinux
发行版的物理机,大家自己搭建linux环境的话推荐试用或租用云服务器或者尝试WSL
gcc
版本是 10.2.0- 编辑器使用
vim
(推荐vim配置vimplus)“Hello world”
1
2
3
4
5
6
7
int main(void){
printf("hello world\n");
exit(0);
}
gcc 编译c的源文件过程
1 | gcc -v |
C源文件->预处理->编译->汇编->链接->可执行文件
完整过程
- 预处理
1
gcc -E hello.c > hello.i
- 编译
1
gcc -S hello.i
- 汇编
1
gcc -c hello.s
- 链接->可执行文件
1
gcc hello.o -o hello
或者
1 | gcc hello.c -o hello |
又或者
1 | make hello |
执行
1 | ./hello |
基本概念
怎么写代码
头文件的重要性
在c中,如果没有出现函数原型,就默认函数的返回值是int
1 |
|
1 | hello.c: 在函数‘main’中: |
- 正确写法
1
2
3
4
5
6
7
int main()
{
int *num = (int *)malloc(sizeof(int));
return 0;
}
数据类型 运算符 表达式
- 基本类型
- 数值类型
- short
- int
- long
- float
- double
- 字符类型
- 数值类型
- 构造类型
- 数组
- 结构体 struct
- 共用体 union
- 枚举类型 enum
- 指针类型
- 空类型 void
1 | 254 -> unsigned int -> 32bit |
类型转换
1 | int i; |
bool
1 |
|
浮点型的失真问题
1 | int func(float f) { |
char
在iso c中 char
有无符号是未定义行为
0
1 | 0(整形) '0'(字符常量) "0"(字符串常量) '\0'(字符常量) |
输入输出
数据类型要和后续代码中所使用的输入输出要相匹配(小心自相矛盾)
1 |
|
正确
1 |
|
常量与变量
常量:在程序执行过程中值不会变化的量
- 整形常量 1 890
- 实型常量 1.2 3.14
- 字符常量 ‘\t’ ‘\n’ ‘\0’ ‘\015’(8进制) ‘\x7f’ ‘\018’(错误的表示!!)
- 字符串常量 “” “a” “abXYZ” “abc\n\021\010”(a b c \n \021 \0 1 8)
- 标识常量
宏的用法
1
2
3
4
5
6
7
8
9
10
11
12
// 正确写法
//#define ADD (2+3)
int main() {
printf("%f\n", PI);
printf("%d\n", ADD * ADD);
}
1 | # 6 "pi.c" |
1 |
|
1 |
|
在程序的预处理阶段,占编译时间,不占运行时间(没有函数调用的消耗),但不检查语法(比较危险)
变量:用来保存一些特定内容,并且在程序执行过程中值会随时变化
[存储类型] 数据类型 标识符 = 值
TYPE NAME = VALUE
标识符:由字母数字下划线组成,不能以数字开头的标识序列,尽量做到见名生意
数据类型:基本数据类型+构造类型
存储类型:auto static register extern
auto 默认 自动分配空间
register 建议型 寄存器类型 只能定义局部变量,不能定义全局变量,大小有限制,只能定义32位大小的数据类型,比如double就不可以。因为寄存器没有地址,所以一个register类型的变量无法打印出地址查看或使用。
static 静态型 自动初始化为0值或空值 并且static变量的值有继承性。另外常用来修饰一个变量或者函数(防止当前函数对外扩展)
1 |
|
- extern 说明型 意味着不能改变被说明的量的值或类型 可以用来扩展外部变量的作用域
- 变量的生命周期和作用范围
- 全局变量和局部变量
- 局部变量和局部变量
1 |
|
1 |
|
1 |
|
表达式
- 表达式与语句的区别
- 运算符部分
- 每个运算符所需要的操作数个数
- 结合性
- 优先级
- 运算符的特殊用法
- 位运算的重要意义
逻辑运算符的短路性
1 |
|
sizeof
1 |
|
位运算
- | 按位或
- & 按位与
- ^ 按位异或
- ~ 按位取反
应用
将操作数中的第n位置1 其他位不变 num = num | 1 << n;
将操作数中的第n位置0 其他位不变 num = num & ~(1<<n);
测试第n位: if(num & (1<<n))
从一个指定宽度的数中取出其中的某几位???
IO (标准IO,文件IO)
- 格式化输入输函数:scanf,printf
- 字符输入输出函数:getchar,putchar
- 字符串输入输出函数:gets(!),puts
- printf
- format:“%[修饰符]格式字符”
变长参数
1 | int main() { |
刷新缓冲区
1 | int main() { |
正确写法
1 |
|
- scanf
1 | int main() { |
scanf 在使用
%s
的时候要特别小心
1 |
|
scanf 在循环中使用的时候要特别小心
1 | int main() { |
处理换行
format:抑制符 *
%s 的使用是比较危险的,因为不知道存储空间的大小
1 | int main() { |
- 字符输入输出函数:getchar, putchar
- 字符串输入输出函数:gets(!),puts
- gets:也是十分危险的,可以使用fgets,getline来替代
流程控制
- 顺序:语句逐句执行
- 选择:出现了一种以上的情况
- 循环:在某个条件成立的情况下,重复的执行某个动作
- 关键字:if-else switch-case,while ,do-while,for, if-goto,continue, break
- 慎用goto 实现的是无条件的跳转 ,且不能跨函数跳转
数组
构造类型 连续存放
一维数组
定义:[存储类型] 数据类型 标识符[下标]
初始化
- static
1 | static int a[10]; |
- {}
1 | int a[3] = {1, 2, 3}; |
元素引用
- arr[i]
- arr+i
数组名
一个常量
1 |
|
数组越界
c对数组不进行越界检查,需要格外小心
练习
1 |
|
1 |
|
1 |
|
二维数组
[存储类型] 数据类型 标识符[行下标][列下标]
1 | int main() { |
深入理解二维数组
a[2][3] = b[3] + c[3]
a[0] = b[0]
a[1] = c[0]
字符数组
定义以及初始化
[存储类型] char 标识符[]
注意部分初始化的时候,剩余部分会自动初始化为’\0’
IO
scanf 无法获取带有分隔符的字符串(\t
, \n
,
)
常用函数
- strlen & sizeof
- strcpy & strncpy
- strcat & strncat
- strcmp & strncmp
单词统计
1 |
|
指针
- 变量与地址
- 指针与指针变量
- 直接访问与间接访问
- 空指针与野指针
- 空类型
- 定于与初始化的书写规则
- 指针运算
- 指针与数组
- 指针与一维数组
- 指针与二维数组
- 指针与字符数组
- 指针常量,常量指针
- 指针数组和数组指针
- 多级指针
64位环境 指针类型占用8个字节
32位环境 指针类型占用4个字节
变量与地址
变量对某块内存的抽象表示
指针 == 地址 变量名 == 抽象出来的某块空间的别名
指针与指针变量
1 | int i = 1; |
直接访问与间接访问
1 | i = 1; |
空指针与野指针
空类型
1 | char *s = "hello"; |
定义与初始化的写法
指针运算
& * 关系运算 ++ –
指针与数组
指针与一维数组
1 |
|
p++
!= p+1
1 |
|
指针与二维数组
1 |
|
1 |
|
const与指针
1 | const float pi = 3.14; // 常量化变量 |
先看到指针就是指针 先看到常量就是常量
- 常量指针 指向的内存不能通过这个指针修改
1 | const int* p; |
- 指针常量 指向的位置不能变 可以通过这个指针修改内存的值
1 |
|
指针数组与数组指针
指针数组
1 | int *arr[3] |
指针数组排序
1 |
|
数组指针
1 | int a[2][3] = {{1, 2, 3},{ 4, 5, 6}}; |
1 |
|
多级指针
没啥好说的
函数
函数的定义
1 |
|
函数的传参
- 值传递
- 地址传递
- 全局变量
函数的调用
- 嵌套
1 |
|
- 递归
1 |
|
1 |
|
函数与数组
1 |
|
1 |
|
函数的指针
- 指针函数
1 |
|
函数指针
类型 (*指针名)(形参)
函数指针数组
类型 (*数组名[下标]) (形参)
指向指针函数的函数指针数组
1 | int *(*funcp[N])(int) |
实际例子
1 | int pthread_create(pthread_t *restrict thread, |
构造类型
结构体
产生及意义
描述复杂的数据类型
类型描述
1 | struct node_st{ |
嵌套定义
1 | struct day { |
定义变量 初始化以及成员引用
- 结构体 .
- 结构体指针 ->
1 |
|
占用内存空间大小
addr % sizeof(type)
不能整除的话就要继续往下偏移
1 |
|
共用体
产生及意义
N选一 多个成员共用一块空间 取最大的成员的类型大小作为共用体的类型大小
类型描述
1 | union test_un{ |
嵌套定义
同结构体 可以互相嵌套
定义变量 初始化以及成员引用
成员引用:
- u.成员名
- up->成员名
32位的无符号数的高16位和低16位相加
1 |
|
另一种写法
1 |
|
枚举
1 | enum 标识符{ |
1 | enum dar { |
1 | enum status { |
typedef
typedef type typename
1 | typedef int INT |
typedef 和 define 的区别
1 |
|
数组
1 | typedef int ARR[6]; // int [6] 改名为 ARR |
结构体
1 | typedef struct { |
函数
1 | typedef int *FUNC(int) |
函数指针
1 | typedef int* (*FUNCP)(int) |
动态内存管理
- malloc
- calloc
- realloc
- free
谁申请谁释放
1 |
|
动态数组
1 |
|
内存申请与函数传值
1 |
|
free的理解
1 |
|
- free代表着变量p不再拥有原来指向内存空间的引用权限
- free后最好马上将指针置NULL
Makefile
工程管理 依赖管理
- makefile(用户自定义 更高优先级)
- Makefile(默认)
1 | mytool:main.o tool1.o tool2.o |
1 | OBJS=main.o tool1.o tool2.o |
$^ 表示在上一句依赖关系中被依赖的所有文件
$@ 表示在上一句依赖关系中依赖项的目标文件
1 | CFLAGS=-Wall -g -c |
% 表示同一个名字
1 | CFLAGS=-Wall -g -c |