C的思维导图
目标
- C的基本语法和编写习惯–输出思维导图
- 编译, make文件和链接–熟练使用Makefile
- 寻找和预防bug–熟练使用gdb
- 防御性编程实践–从问题预设思考编程
- 使C的代码崩溃–形成崩溃式的代码思考方式
- 编写基本的Unix系统软件–进入Unix/Linux的世界
技能
- 阅读和编写代码能力
- 专注细节
- 定位差异
- 规划和调试
环境配置
minGW-64
+vscode
minGW
是GCC对windows操作系统的移植版,可以在windows环境下享受GCC编译器的功能。
vscode
是强大的IDE, 开源免费且支持工具包众多,很好支持C语言的开发,测试等。
关于环境,google折腾。
Centos
+vim
涉及到Linux
环境下的测试与开发,通过搭建VM环境或者租借云服务器。
我是用VM搭建了Centos 7 minimal
的linux环境, 远程通过MobaXterm
。
vim
的搭建是简单改造PowerVim。附上链接:PowerVim
参考书籍
《Learn C The Hard Way》
《Learn C The Hard Way 中文版》
C语言基础
hello-c
1
2
3
4
5
6
7
8
9
10
11
12
|
/* ************************************************************************
> File Name: ex1.c
> Author: hanzi_zhu
> Created Time: Thu 15 Sep 2022 09:56:26 AM EDT
> Description: The first C code
*********************************************************************** */
int main(int argc, char* argv[]){
puts("Hello C");
return 0;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
# 编译和运行
make ex1
# cc ex1.c -o ex1
CFLAGS="-Wall" make ex1
# cc -Wall ex1.c -o ex1
# ex1.c: In function ‘main’:
# ex1.c:9:5: warning: implicit declaration of function ‘puts’ [-Wimplicit-function-declaration]
# puts("Hello C");
# ^
# 应清除所有warning和error
# 此处“implicit declaration”是因为没有导入“puts”的引入声明,隐式声明
# 代码首行增加 #include <stdio.h>
# 关于make编译参数
# -Wall:选项可以打印出编译时所有的错误或者警告信息, 编译必须带上
# CFLAGS 表示用于 C 编译器的选项,CXXFLAGS 表示用于 C++ 编译器的选项
# -O0: 表示编译时没有优化
# -O1: 表示编译时使用默认优化
# -O2: 表示编译时使用二级优化
# -O3: 表示编译时使用最高级优化
|
认识局部和全局
变量的作用范围
:
- 局部变量–仅作用于函数内部逻辑区域, 离开则非法
- 全局变量–不属于哪一个函数内部, 属于整个源程序
存储类型
- auto: 自动变量, 可省略不写, 生存周期上属于动态存储
- stati: 静态变量, 编译初期赋初值,存在默认值,生存周期上属于静态存储
- register: 寄存器变量,
- extern: 外部变量, 将函数内的变量提升至外部, 可供该函数执行后的函数使用
Poniter
1
2
|
const int *p;
int const *p;
|
指的是:p指向的内存单元
不能改写,即(*p)++
是不允许的, p++
是可以的,说明p可指向其他内存单元。
指的是:p是一个指向int的const指针, (*p)
可以改写, 但是p不能改写
指的是: p和*p都能被改写
多用const
对指针进显式限制,同时能够检查唯一性。
Makefile
1
2
3
4
|
CFLAGS=-Wall -g
clean:
rm -f ex1
|
1
2
3
4
5
6
|
# 清除原有ex1
make clean
# rm -f ex1
# 重新编译
make ex1
# cc -Wall -g ex1.c -o ex1
|
GDB-Debug
Debug是较好的学习的方式。
开发和测试全部在Linux环境
DEBUG基本思想:分析现象->假设错误原因->产生新的现象验证假设
1
2
3
|
gdb -v
# GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
|
命令 |
说明 |
break xxx/ b xxx |
设置断点 xxx断点位置 行号或者函数名称 |
backtrace / bt |
转存当前调用栈的回溯信息 |
info break\info watch |
查看断点信息,监视断点 |
run / r |
运行程序 |
continue / c |
断点后可继续执行程序,直到下一个断点或者程序结束 |
next / n |
一行一行的执行程序 ,当碰到函数会单步跳过 step over |
step / s |
下一行,当碰到函数时候会单步进入 step in |
print xxx / p xxx |
打印指定变量,xxx是变量名称 |
list / l |
enter键可以看后续代码 列出接下来10行代码 加入“-”列出之前的10行源代码 |
finish |
退出上一个gdb的状态,继续回到调试运行状态中 |
quit / q |
退出gdb |
clean |
清除一处断点 |
cd\pwd\make |
shell命令一致 |
shell |
快速启动shell,便于在调试过程中做别的事情 |
attach pid |
将GDB绑定在一个正在运行进程上,对其继续调试 |
detach |
将GDB与进程解绑 |
1
2
3
4
5
6
7
8
9
10
11
|
# -g 选项 才能生成的可执行文件才能用于gdb进行源码级测试
# 作用:在可执行文件中加入源码的信息,将执行文件机器指令和源代码行号映射
# 所以,gdb调试必须保证能找到源代码文件
gcc -g demo.c -o demo
# 或者通过make斤进行编译
make demo
# 调试可执行文件,会出现提示信息
gdb demo
# 启动调试不显示调试信息
gdb demo [--slient|--quit|-q]
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
display sum
# 取消跟踪 undisplay 变量编号
# 打断点 break
b line_num
b func_name
# 当前行号的前面或后面的offset行停住。offset为自然数
break +offset
break -offset
# 连续执行 continue
c
# 查看断点
i breakpoints
# 删除断点
delete breakpoints 断点编号
# delete breakpoints 删除所有断点
# 暂时禁用断点
disable breakpoints 断点编号
# 启用断点
enable 断点编号
# 条件断点
break 源码行号 if 条件变量 条件
# break 9 if sum != 0
|
Data & Algorithm
数据的组织方式包含了存储方式和访问方式,二者密不可分。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
// stack.c
// 栈的简单操作--DFS 深度优先搜索
// Created By CodeMax At 2022/07/06/ 19:33
// FILO
#include <stdio.h>
char stack[512];
int top = 0;
void push(char c) { stack[top++] = c; }
char pop(void) { return stack[--top]; }
int is_empty(void) { return top == 0; }
int main(void)
{
push('a');
push('b');
push('c');
while (!is_empty()) {
putchar(pop());
putchar(' ');
}
return 0;
}
// c b a
|
管理内存[memory]
What a C programmer should know about memory
类型转换
- 问题1: 类型隐式转换原理是什么?为什么char short int long float等能够隐式转换?
- 问题2: 强制转换的原理是什么?为什么编译器隐式转换不能替代强制转换?
内存分配和释放
- 动态内存分配:运行时分配,使用<stdlib.h>,<malloc.h>
- 静态内存分配:编译时分配
- malloc() 按照字节大小为任何类型分配内存
1
2
3
4
|
// tsdlib.h --> size_t ==> unsigned int
// malloc 分配num字节的存储内存空间 返回第一个字节指针
// 如果num==0 则会返回NULL,而NULL可以认为是void*
void *malloc(size_t num);
|
- calloc() 分配一组对象
1
2
3
4
5
|
// num 分配对象的个数
// size 每个对象的大小,单位依旧是字节
// 成功分配内存,将清空所有分配的内存,返回指向第一个字节的指针
// 分配失败则返回,获取num\size为0,返回NULL
void *calloc(size_t num, size_t size);
|
- realloc() 修改已分配内存块的大小
1
2
3
4
5
6
7
|
// ptr 指向原内存块的指针
// size 新内存块大小(字节单位)
void *realloc(void *ptr, size_t size);
// 1. 存在足够内存空间扩大ptr指向内存块,realloc会分配额外的内存并返回ptr
// 2. 没有足够内存空间,realloc会重新分配size大小内存并拷贝原有ptr存储数据到新地址,释放原有ptr指向内存
// 3. ptr如果为NULL, realloc相当于malloc,分配size字节内存块并返回其地址
// 4. 如果size==0或者内存已不足用于扩大或者再分配,都会返回NULL,原内存不会被修改
|
- free() 释放分配动态内存池中可用内存的malloc\calloc\realloc申请的内存块
1
2
3
|
// 如果ptr==NULL,不会有任何操作
void free(void *ptr);
// 动态分配内存后,必须将其释放
|
内存操作
- memset 将所有字节的内存块设置为指定的值
1
2
3
4
|
void *memset(void *dest, int c, size_t count);
// dest 指向待初始化的内存块
// c是设置的值 被视为char 只能使用低位字节 即值区间是0~255
// count是从dest开始设置的字节数
|
- memcpy 缓冲区间拷贝数据 逐字节的拷贝数据
1
2
3
4
5
|
void *memcpy(void *dest, void *src, size_t count);
// dest 目标内存块
// src 源内存块
// count 待拷贝的字节数
// dest和src存在内存重叠,memcpy会出现异常
|
- memmove 将字节从一个内存块拷贝至另一个内存
会处理重叠的内存块,建议使用memmove()替换memcpy()
1
|
void *memmove(void *dest, void *src, size_t count);
|
CPP
基础
sizeof
: 计算一个类型/对象所占用的内存大小。
类
拷贝构造函数
使用同一个类型的实例化对象创建新的同类对象.
场景:
- 使用对象创建新的对象
- 复制对象作为参数传递
何时创建?
- 不显式定义,编译器会默认定义一个
- 类型带有动态分配内存的指针变量, 必须显式构造拷贝构造函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
|
#include <iostream>
using namespace std;
class Line {
public:
void setLength(double len);
double getLength(void);
double getLength2(void);
Line(double len);
Line(const Line &o); // 拷贝构造函数
~Line(); // 析构函数会跟随每一个该类型对象(包括拷贝对象)
private:
double length;
int *ptr;
};
Line::Line(double len):length(len*2) {
ptr = new int;
*ptr = len;
cout << "Object is being created" << endl;
}
Line::Line(const Line &b):length(b.length+1) {
// length = b.length;
ptr = new int;
*ptr = *b.ptr;
cout << "Object is being copy created" << endl;
}
Line::~Line() {
cout << "Object is being deleted" << endl;
delete ptr;
}
void Line::setLength(double len) {
length = len;
}
double Line::getLength(void) {
return length;
}
double Line::getLength2(void) {
return *ptr;
}
void display(Line j) {
cout << "Line 大小1: " << j.getLength() << endl;
cout << "Line 大小2: " << j.getLength2() << endl;
}
int main() {
Line line(6.0); // create line
Line line1 = line; // copy-create line1 line.length+1=13=line1.length
display(line); // copy-create display-obj-line, then delete display-obj-line line.length+1=13
display(line1); // copy-create display-obj-line1 then delete display-obj-line1 line1.length+1=14
// then delete line1
// then delete line
return 0;
}
// Object is being created
// Object is being copy created
// Object is being copy created
// Line 大小1: 13
// Line 大小2: 6
// Object is being deleted
// Object is being copy created
// Line 大小1: 14
// Line 大小2: 6
// Object is being deleted
// Object is being deleted
// Object is being deleted
|
友元函数/类
逃逸类限制的无限制的操作
可以访问类的 private 和 protected 成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
|
#include <iostream>
using namespace std;
class Box {
double width;
public:
friend void printWidth(Box box); // 友元函数 printWidth
friend class BigBox; // 友元类 BigBox
void setWidth(double wid);
};
class BigBox {
public:
void print(double width, Box &box) {
box.setWidth(width);
// BigBox作为Box友元类,可以直接访问Box对象的任何权限成员
cout << "Width of friend class box: " << box.width << endl;
}
};
void Box::setWidth(double wid) {
width = wid;
}
// 独立函数定义
void printWidth(Box box) {
// printWidth作为Box友元函数,可直接访问Box对象的任何权限成员
cout << "Width of friend func box: " << box.width << endl;
}
int main() {
Box box;
BigBox big;
box.setWidth(10.0);
printWidth(box);
big.print(20.0, box);
return 0;
}
|
继承
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
#include <iostream>
using namespace std;
class Shape {
public:
void setWidth(int w) {
width = w;
}
void setHeight(int h) {
height = h;
}
protected:
int width;
int height;
};
class PainCost {
public:
int getCost(int area) {
return area * 70;
}
};
class Rectangle: public Shape,public PainCost {
// accessType BaseClass1, accessType BaseClass1
// accessType 基类的权限会在派生类继承的时候修改基类的权限,权限趋向限制
public:
int getArea() {
return (width * height);
}
};
int main() {
Rectangle Rect;
int area;
Rect.setWidth(5);
Rect.setHeight(7);
area = Rect.getArea();
cout << "Total area of rectangle: " << area << endl;
cout << "Total paint cost of rectangle: $" << Rect.getCost(area) << endl;
return 0;
}
|
重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
#include <iostream>
using namespace std;
// 重载函数
class PrintData {
public:
void print(int i) {
cout << "Integer: " << i << endl;
}
void print(double f) {
cout << "Float: " << f << endl;
}
void print(const char *c) {
cout << "String: " << c << endl;
}
};
class Box {
public:
double getVolume() {
return length * breadth * height;
}
void setLength(double len) {
length = len;
}
void setBreadth(double bre) {
breadth = bre;
}
void setHeight(double hei) {
height = hei;
}
// 运算符重载
// operator符号(const 类& var)
// 运算符的重新定义
Box operator+(const Box& b) {
Box box;
box.length = this->length + b.length;
box.breadth = this->breadth + b.breadth;
box.height = this->length + b.length;
return box;
}
private:
double length;
double breadth;
double height;
};
int main() {
PrintData pd;
pd.print(5);
pd.print(500.123);
const char *c = "Hello c++";
pd.print(c);
return 0;
}
|