makefile
gcc
gdb
makefile
1. 分文件编程
1.1 源文件:.c结尾的文件
包含main函数的.c
包含子函数的.c
1.2 头文件:.h结尾的文件
头文件、宏定义、typedef 、结构体、共用体、枚举、函数声明
include引用时“”和<>的区别:
<>去系统目录查找头文件
“”先从当前目录下查找,如果没有再去系统目录查找头文件。
位置查看:ctrl+鼠标点击stdio.h,退出alt+左或右箭头
系统默认搜索路径:/usr/include
2. 编译工具
gcc
1)预处理
展开头文件,删除注释,替换宏定义,不检查语法错误
gcc -E xx.c -io xx.i
- 编译
检查语法错误,有错就报错,没有问题就转换成汇编语言,生成汇编文件
gcc -S xx.i -o xx.s
3)汇编 将汇编文件转换成不可执行的二进制文件
gcc -c xx.s -o xx.o
4)链接 链接库文件 生成可执行的二进制文件
gcc xx.o -o xx
gcc xx.c --> ./a.out
gcc xx.c -o xx -->./xx
makefile:
gcc xx.o -o xx
gcc -c xx.c -o xx.o
gdb
gcc -g xx.c 生成 a.out
gdb a.out
r: 运行代码
l: 查看文件
b 行号:添加断点
info b:查看断点情况
d num(断点编号):删除断点
p 变量名:查看变量的值
s/n: 单步运行,s会进入子函数顺序执行;单步跳过,n不会进入子函数
c: 执行到下一个断点的位置
help:帮助
q: 退出
步骤:代码写好后,可以先通过gcc -g 进行编译,没有错误后可以通过gdb a.out进行调试
,输入l查看文件内容,通过b设置断点,输入r运行到断点,然后通过n或s可以进行单步调试,q退出
make
make:工程管理器
make工程管理器就是一个“自动编译管理器”这里的“自动”是指它能构根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefile文件文件的内容来执行大量的编译工作。
- c 2.c 3.c 4.c 5.c
makefile:
gcc xx.o -o xx
gcc -c xx.c -o xx.o
3. 格式
目标:依赖
<Tab>命令
依赖(面粉)--->命令(加工)--->目标(馒头)
gcc xx.o -o xx 目标:xx 依赖:xx.o
gcc -c xx.c -o xx.o 目标:xx.o 依赖:xx.c
makefile的写法:
.PHONY:clean 避免生成同名文件
aa: 只有目标没有依赖,叫伪目标
运行程序:
make
./test
运行新增的功能:
make aa
也可以运行其他命令:
aa:
touch b.c
4. make管理多个文件
gcc -c main.c -o main.o 目标:main.o 依赖:main.c
gcc -c add.c -o add.o 目标:add.o 依赖:add.c
gcc main.o add.o -o sum 目标:sum 依赖:main.o add.o
5. makefile变量
自定义变量
自己定义的变量:一般用大写表示变量名,取变量的值用 $(变量名)
= 递归方式展开
:= 直接赋值(当前的值是什么就立即赋值)
+= 追加新的值
?= 判断之前是否定义,如果定义了,不重新赋值,否则赋值
预定义变量
RM 文件删除程序的名称,默认值为rm -f
CC C编译器的名称,默认值为cc。
CPP C预编译器的名称,默认值为$(CC) –E。
CFLAGS C编译器的选项,无默认值。
OBJS 生成的二进制文件或目标文件,自己定义
自动变量
$< 第一个依赖的名称
$@ 目标文件的完整名称
$^ 所有不重复的依赖文件,以空格分开
简化版Makefile写法:用 %.c 和 %.o 去替换所有的 .c 和 .o 文件
指针函数
本质是函数,返回值是指针
格式:
数据类型 * 函数名(形参)
{
函数体;
return 地址;//失败返回NULL
}
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
char *fun()
{
//char a[]="hello";//hello在栈区,函数调用结束,空间释放
//char *p="hello"; //hello存放在常量区
char *p=(char *)malloc(100);
strcpy(p,"hello"); //存放在堆区
return p;
}
int main(int argc, char const *argv[])
{
char *p=fun();
printf("%s\n",p);
free(p);
p=NULL;
return 0;
}
函数指针
本质是指针,指向函数
格式:
数据类型 (*指针名)(参数列表)
参数列表:和指向的函数参数列表一致
数据类型:和指向的函数返回值一致
#include<stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int test(int (*p)(int,int),int a,int b)
{
printf("%d\n",p(a,b));
return 0;
}
int main(int argc, char const *argv[])
{
// int (*p)(int,int);
// p=add;
// printf("%d\n",p(5,2));
// p=sub;
// printf("%d\n",p(5,2));
test(add,5,2);
test(sub,5,2);
return 0;
}
只需要一个test函数,可以实现不同的功能,这个特点叫"多态"
函数指针数组
本质是数组,存放函数指针
格式:
数据类型 (*数组名[元素个数])(参数列表)
int (*arr[2])(int,int)={函数名};
#include<stdio.h>
int add(int a,int b)
{
return a+b;
}
int sub(int a,int b)
{
return a-b;
}
int main(int argc, char const *argv[])
{
int (*arr[2])(int,int)={add,sub};
// arr[0]=add;arr[1]=sub;
for(int i=0;i<2;i++)
printf("%d \n",arr[i](5,3));
return 0;
}
- 封装函数实现求一个int类型数据的二进制有几位1
a) 一个整型数 //int a;
b) 一个指向整型的指针 //int *p=&a;
c)一个指向指针的指针,它指向的指针是一个指向一个整型数//int **q=&p;
d)一个有10个整型数的数组 //int a[10]
e)一个有10个指针的数组,该指针是指向一个整型数的 //int *a[10];
f)一个指向有10个整型数数组的指针//int (*p)[10]
g)一个指向函数的指针, 该函数有一个整型参数并返回一个整型数//int (*p)(int);
h)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数
//int (*arr[10])(int)
条件编译
根据宏是否定义
#define 宏名
#ifdef 宏名
/*code1*/
#else
/*code2*/
#endif
执行顺序:判断宏名是否定义,如果定义就编译code1,否则编译code2
根据宏值
#define 宏名 值
#if 宏名
/*code1*/
#else
/*code2*/
#endif
执行顺序:判断宏的值是否为0,如果不为0编译code1,否则编译code2
防止头文件重复编译
放在头文件中:
#ifndef 宏名
#define 宏名
/*code*/
#endif