CLang Basement Map

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: 表示编译时使用最高级优化

认识局部和全局

变量的作用范围

  1. 局部变量–仅作用于函数内部逻辑区域, 离开则非法
  2. 全局变量–不属于哪一个函数内部, 属于整个源程序

存储类型

  • auto: 自动变量, 可省略不写, 生存周期上属于动态存储
  • stati: 静态变量, 编译初期赋初值,存在默认值,生存周期上属于静态存储
  • register: 寄存器变量,
  • extern: 外部变量, 将函数内的变量提升至外部, 可供该函数执行后的函数使用

Poniter

  • 指针和const
1
2
const int *p;
int const *p;

指的是:p指向的内存单元不能改写,即(*p)++是不允许的, p++是可以的,说明p可指向其他内存单元。

1
int *const p;

指的是:p是一个指向int的const指针, (*p)可以改写, 但是p不能改写

1
int const * const 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基本思想分析现象->假设错误原因->产生新的现象验证假设

  • gdb版本查看
1
2
3
gdb -v

# GNU gdb (GDB) Red Hat Enterprise Linux 7.6.1-120.el7
  • gdb常用命令
命令 说明
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与进程解绑

CLang gdb cmd

  • 启动调试
 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

CLang gdb break cmd

  • 观察点调试

CLang gdb watch cmd

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>
  • 静态内存分配:编译时分配
  1. malloc() 按照字节大小为任何类型分配内存
1
2
3
4
// tsdlib.h --> size_t ==> unsigned int
// malloc 分配num字节的存储内存空间 返回第一个字节指针
// 如果num==0 则会返回NULL,而NULL可以认为是void*
void *malloc(size_t num);
  1. calloc() 分配一组对象
1
2
3
4
5
// num 分配对象的个数
// size 每个对象的大小,单位依旧是字节
// 成功分配内存,将清空所有分配的内存,返回指向第一个字节的指针
// 分配失败则返回,获取num\size为0,返回NULL
void *calloc(size_t num, size_t size);
  1. 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,原内存不会被修改
  1. free() 释放分配动态内存池中可用内存的malloc\calloc\realloc申请的内存块
1
2
3
// 如果ptr==NULL,不会有任何操作
void free(void *ptr);
// 动态分配内存后,必须将其释放

内存操作

  • 内存设置为指定值
  • 内存拷贝或者转移
  1. memset 将所有字节的内存块设置为指定的值
1
2
3
4
void *memset(void *dest, int c, size_t count);
// dest 指向待初始化的内存块
// c是设置的值 被视为char 只能使用低位字节 即值区间是0~255
// count是从dest开始设置的字节数
  1. memcpy 缓冲区间拷贝数据 逐字节的拷贝数据
1
2
3
4
5
void *memcpy(void *dest, void *src, size_t count);
// dest 目标内存块
// src 源内存块
// count 待拷贝的字节数
// dest和src存在内存重叠,memcpy会出现异常
  1. memmove 将字节从一个内存块拷贝至另一个内存

会处理重叠的内存块,建议使用memmove()替换memcpy()

1
void *memmove(void *dest, void *src, size_t count);

CPP

基础

  • sizeof: 计算一个类型/对象所占用的内存大小。

拷贝构造函数

使用同一个类型的实例化对象创建新的同类对象.

场景:

  1. 使用对象创建新的对象
  2. 复制对象作为参数传递

何时创建?

  1. 不显式定义,编译器会默认定义一个
  2. 类型带有动态分配内存的指针变量, 必须显式构造拷贝构造函数
 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;
}