LinuxC学习总结
一、知识点储备
(一)gcc的编译流程
- 第一步:预处理
将C源文件编译成C文件
头文件展开,注释去掉,宏替换掉,不做语法检查
gcc -E test.c -o test.i - 第二步:编译
将预处理之后C文件编译成汇编文件
gcc -S test.i -o test.s - 第三步:汇编
将汇编文件编译生成机器文件
gcc -c test.s -o test.o - 第四步:链接
将所有的机器文件共同参与链接,生成一个可执行文件
gcc *.o -o App
(二)对于传参时,到底传普通变量,还是传一级指针变量,又或者传二级指针甚至多级指针的选择思路?
看是否需要在子函数内部引起实参本身发生改变。
eg:
int a = 90,b = 89; 子函数中实现ab两个数字的交换
(在子函数中引起实参a,b本身发生改变,因此需要将a,b的地址传过去) func(&a,&b);—>才可以在子函数中通过指针来间接引起实参a,b的交换
int *p = NULL; 子函数中实现给实参p赋值一个连续空间的首地址
(子函数中引起实参p此时会具备有一个首地址,因此需要将&p传过去) func(&p);—> Pointer = &p --> Pointer = p;通过给Pointer赋值就相当于给实参p赋了值。
(三)main函数传参
main函数无参:
int main()
{}
int main(void)
{}
main带参数:
int main(int argc, const chat *argv[ ])
{}
- int argc:代表传递给main 函数的参数的个数,系统自己会计算传递过来的参数个数 const char
- *argv[ ]:代表存储传递给main函数的所有参数的地方
(四)二维数组的图解
二、C高级
(一)函数和指针的结合
函数:存在于内存中的一段二进制代码,函数名代表函数的入口地址,程序运行的时候,会为函数体寻找一片空间,又因为指针变量是用来存储变量地址,故,可以让指针存储函数的地址,将这样的指针称为“函数指针”。
1、指针函数
指针函数:函数返回值类型是指针类型的函数
2、函数指针
- 概念:指向函数的指针(函数的本质是存在内存中的一段二进制代码,因此会占据一片空间,同时该片空间的首地址用函数名来代替)
- 如何定义一个指向整形的指针?
—> int *p - 如何定义一个指向含有5个int类型元素的一维数组?(数组指针)
—> int (*pArr)[5];
(1)函数指针的定义格式
- 定义函数指针方式:
返回值类型 (*pFunc)(形参列表);
- 思考:
void func(int arr[20]); //这里的 [ ] 等价于*
void func(int *arr)
sizeof(arr) = 8;
(2)关键字typedef取别名的用法
- typedef:给已存在的一个数据类型在其作用域内取一个别名
- 用法:typedef 类型名 新类型名;
- 注意:对于基本类型可以按照以上取别名的方式去写,但是对于函数指针类型需要用以下的的方式进行定义。
- eg:
typedef int MYInt;
MyInt a;
- 注意:
针对函数指针类型取别名的写法:
返回值类型 (*新类型名)(形参列表);
(3)函数指针数组的定义
- 函数指针数组:数组元素类型是函数指针类型的数组。
- 定义:
函数指针类型 数组名[元素个数]; //取别名后可以这样定义函数指针数组。
返回值类型 (*数组名[元素个数])(参数表列表);
(4)回调函数
回调函数:把B函数的地址即(函数名或者&函数名)作为A函数的参数,传递给A函数,在A函数中通过使用这个参数来间接调B函数的过程就称为“回调函数”。
(二)动态内存管理
- 针对:堆区
- 申请空间:malloc calloc
- 释放空间:free
- 初始化空间:memset
- 扩容空间:realloc
头文件:#include <stdlib.h>
1、申请空间
(1)void *malloc(size_t size);
- 功能:申请空间在堆区中
- 参数:所需申请空间占据的字节数
- 返回值:成功返回连续空间的首地址,失败返回NULL
(2)void *calloc(size_t nmemb, size_t size);
- 功能:申请空间
- 参数:
参数1:所需申请的元素个数
参数2:元素的大小(字节数) - 返回值:成功返回连续空间的首地址,失败返回NULL
- 注意:calloc在申请完空间之后,会自动将申请的连续空间初始化为’\0’
2、释放空间
void free(void *ptr);
- 功能:释放空间
- 参数:所需释放空间的首地址
- 返回值:无返回值
3、扩容空间
void *realloc(void *ptr, size_t size);
- 功能:用来扩容空间
- 参数:
参数1:所需扩容空间的首地址(malloc的返回值)
参数2:扩容之后总的字节数(旧 + 新) - 返回值:成功返回扩容之后空间的首地址,失败返回NULL
- 注意:扩容之后的空间首地址可能和之前的地址一样,也可不一样!!!
#include <string.h>
4、重置空间
void *memset(void *s, int c, size_t n);
- 功能:用来重置一片内存空间 (当做清空空间居多!)
- 参数:
参数1:所需重置内存空间首地址
参数2:所需重置的字符 —> ’\0’ , 0
参数3:所需重置内存空间的大小—》sizeof( )
(三)结构体
结构体:是一种构造数据类型,目的是为了将一个事物的多重属性表示清楚(属性可能是不同的数据类型,也可以是相同的数据类型)。
1、如何构造一个结构体类型?
struct 结构体名
{
数据类型1 成员1;
数据类型2 成员2;
……
数据类型N 成员N;
};
2、如何定义结构体变量?
struct 结构体名 变量名;
3、如何给结构体成员进行赋值?
变量名= {值1,值2……};
struct 结构体名 变量名 = {值1,值2……};
4、结构体的字节对齐
(1)在申请结构体空间时,会遵循“字节对齐”的原则。
(2)第一点:考虑在范围之内的成员必须为基本数据类型!
- 32OS:系统最多一次性开辟4个字节
怎样确定每一次开辟空间的大小,依赖于成员中占据字节空间最大的那个数据类型,将其作为基准,来进行每一次空间大小的开辟,但是成员有超过上限4字节的数据类型,此时仍按照4字节进行开辟空间。反之,则按照那个最大的类型占据的字节数进行开辟。 - 64OS:系统最多一次性开辟8个字节
怎样确定每一次开辟空间的大小,依赖于成员中占据字节空间最大的那个数据类型,将其作为基准,来进行每一次空间大小的开辟,但是成员有超过上限8字节的数据类型,此时仍按照8字节进行开辟空间。反之,则按照那个最大的类型占据的字节数进行开辟。
(3)第二点:
不管是32OS还是64OS,在存储成员时,均需要保证存储的地址编号和该成员自身的数据类型大小成整数倍的关系,否则往后寻找下一个合适的地址空间进行存储。
(4)案例
typedef struct AA
{
int a;
char b;
short c;
}Type;
printf(“sizeof(Type) = %\n”,sizeof(Type)); ---> 7个字节
typedef struct AA
{
char b;
int a;
short c;
}Type;
printf(“sizeof(Type) = %\n”,sizeof(Type)); ---> 7个字节
5、如何访问结构体中的成员?
方式1:变量名.成员名;
方式2:结构体指针 --> 成员名;
定义结构体数组,实现对于一批数据的输入输出。
(四)共用体
- 共用体:多个成员共用同一片空间,因此开辟内存空间时,会按照占据内存空间最大的数据类型进行开辟。
- 定义格式:
union 共用体名
{
数据类型1 成员1;
……
数据类型N 成员N
};
- 注意:共用体的使用方法和结构体一致。唯一区别是,结构体的成员空间是独立的,而共用体的成员共用同一片空间。
(五)枚举
- 枚举:跟宏比较类似,意味着也是一些常量值,只不过这些常量值是被放在一个集合里面。
- 定义格式
enum 枚举名
{
枚举数值名1,
枚举数值名2,
……
};
(六)大小端
- 大小端:计算机中存储数据的时候到底将低字节或者高字节处的内容存储在低地址还是高地址的问题。
- 大端存储:低字节处的内容存储在高地址处,高字节处的内容存储在低地址处。
- 小端存储:低字节处的内容存储在低地址处,高字节处的内容存储在高地址处。
- 低字节:一串二进制数据的右边
- 低地址:小的地址编号
(七)调试BUG的方式
-
方式1:printf打印 —> 最直接
在自己感觉出错的代码段前后加上部分printf输出内容,然后编译运行来判断问题的出处。 -
方式2:gdb调试工具
流程:
(1)生成带有调试信息环境的可执行文件
方式:gcc -g test.c -o App
(2)进入该可执行文件
方式:gdb App
(3)设置断点 --> 让程序从哪里开始调试
方式:b 行标/函数名
(4)运行
方式: r
(5)单步运行
n: 不进入子函数内部,但不代表不执行子函数
s: 进入子函数内容,并开始执行子函数中每一行代码
(6)找到错误所在的行
响应:看到程序打印出一行文本“Program recived Sigmentation fault…”,上一行就是引起错误的所在行。
(7)结束调试
方式:按下q退出即可。
(八)段错误
在C语言阶段,出现段错误的原因:
- 数组越界
- 操作空指针或者野指针
- 修改常量区内容
(九)递归函数
- 递归函数:重复调用其他函数或者调用自己,但是调用的时候一定要注意结束调用的条件。
- 如何编写一个标准的递归函数?
(1)从哪里开始?
(2)从哪里结束?
(3)每一步干什么? - 阶乘:5! = 120 (5 * 4 * 3 * 2 * 1 = 120)
(十)头文件的书写(多文件编译)
- 格式:条件编译:防止同一个头文件被重复定义
- 创建一个后缀是.h文件。eg: touch test.h
- 打开test.h —> vim test.h
- 编写以下代码:
#ifndef _TEST_H_
#define _TEST_H_
...
1、宏定义
2、结构体定义
3、取别名
4、全局变量的声明
5、函数的声明
6、枚举类型的定义
#endif
- 之前:
书写代码需要一个C文件即可 - 现在:
具备书写一个项目的思想
-项目是什么样的?
会有很对不一样的需求,因此每一个需求可能会交给一个人去完成,因此书写一个完整的项目需要多个人去负责,意味着会有多个.c文件需要进行书写,也需要有一个.h文件作为辅助,进行编译。 - 注意:对于以上需求,需要通过主文件进行调用.c文件
- 综上:1个主文件 + 若干个子文件 + 若干个头文件
(十一)Make工程管理
- 概念:工程管理工具。可以将很多.c文件+.h文件进行统一管理
- 使用:命令行输入make目标名即可
- 如何编写一个Makefile文件?
统一格式
目标:依赖
[Tab]命令表
- 嵌套Makefile:
Make中的一些变量
1、用户自定义变量
格式:
变量名:=值 (定义变量且赋初值,变量名建议大写)
2、自动变量
$< :依赖中的第一个依赖
$^:所有依赖
$@:目标名
3、系统预定义变量
(1)CC:编译器的名称,默认值是cc
用户可以自己赋值为: eg CC :=gcc
(2)CFLAGS:编译器的选项,无默认值
用户可以自己赋值:eg: CFLAGS:= -c -g -Wall
(3)RM:代表删除,默认值为:rm -f
用户可以自己赋值为 eg: RM := -rf
4、环境变量
export :添加一些指定的环境变量到整个系统环境中去
eg:
export 变量1 变量2 变量3
三、C高级练习题
(一)实现指定数字的阶乘
#include <stdio.h>
//功能:实现指定数字的阶乘
int Recursion_Func(int num)
{
//(1)从num开始
//(2)到1结束
//(3)num * Recursion_Func(num-1)
if(1 == num || 0 == num)
{
return 1;
}
return num * Recursion_Func(num-1);
//1 5 * Recursion_Func(4)
//2 5 * 4 * Recursion_Func(3)
//3 5 * 4 * 3 * Recursion_Func(2)
//4 5 * 4 * 3 * 2 * Recursion_Func(1)
//5 5 * 4 * 3 * 2 * 1
}
int main(int argc, const char *argv[])
{
int num;
printf("请输入需要计算的阶乘数字:\n");
scanf("%d",&num);
//调用递归函数实现
printf("%d! = %d\n",num, Recursion_Func(num));
return 0;
}
(二)定义一个学生的结构体,学生有姓名,学号,有三门课程成绩。
假设该班级的学生人数为5人,则:
实现功能:
- 求第一门成绩的平均分
- 找出有两门以上的课程不及格的学生,输出他们的姓名,学号和3门课程的成绩
- 找出平均分在90分以上或者全部全部课程成绩在85分以上的学生。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//宏定义
#define N 20
#define COUNT 3
//五个学生
#define SIZE 5
//定义枚举数据类型,来表示选择的命令以及一些出错原因。
enum VALUE
{
SIZE_ERROR = -4,
NULL_ERROR = -3,
MALLOC_ERROR,
ERROR,
OK,
MALLOC,
INPUT,
OUTPUT,
FIR,
TWO_BAD,
GOOD,
QUIT,
};
//定义描述学生信息的结构体类型
typedef struct student
{
//姓名
char Name[N];
//学号
int ID;
//三门成绩
float Score[COUNT];
}Stu;//Stu是描述学生的结构体数据类型
//菜单函数的实现
void menu()
{
printf("*******班级学生信息*******\n");
printf("1--------------------malloc\n");
printf("2---------------------input\n"