1. 一个程序分装在两个 .c 文件里面(同一个文件夹),需要增加一个 .h 文件,然后两个 .c 文件都 include 那个 .h 头文件。
而且一个 .h 头文件只能链接一个 .c 文件。
头文件里面的东西在程序运行的时候,是不存在的,所以,函数声明可以放在头文件里,变量声明不可以。
编写头文件的时候,要使用条件编译,避免头文件被多次编译。
一个加法程序如下:
主函数 code.c 文件如下:
#include <stdio.h>
#include "add.h" // code.c 和 add.c 在同一个文件夹,所以用 “”,而不是 < >
extern int result; // 全局变量,在 add.c 已经定义了,此处只是再次调用而已,所以要加 extern
int main()
{
add(7, 3); // 因为它无返回值,所以只能在此单独调用,不能写在 printf 里面。
printf("%d\n", result);
return 0;
}
加法函数 add.c 内容如下:
#include <stdio.h> // 如果下面需要调用这个库,则在这需要声明,与主函数是否有无关
#include "add.h" // 记得加入这个头文件
int result; // 定义全局变量,除了此处不加 extern,其他都得加。注意:此处不能用 static 修饰 result,否则作用域变小,main函数无法调用。
void add(int value, int value1) // void无返回值
{
result = value + value1;
}
连接函数 add.h 如下:
#ifndef __ADD_H__ // 如果add.h未被定义,则执行下面的语句(这里是#ifndef,而不是#ifdef),否则不执行。这句话可以防止同一个 .h 文件,被多次编译。
#define __ADD_H__ // 定义 add.h 或者给 add.h 起别名
int add(int, int); // 把add.c里面的声明部分提取出来,记得末尾有分号
#endif
(所有 .h 头文件都用这种格式进行条件控制)
编译的时候,需要把 code.c 和 add.c 都编译才可以,即在gcc里如下输入:
gcc code.c add.c
2. 多个文件实际操作的时候,我们是单个文件编译,最后把编译后的文件加在一起。
那么在编译的时候,需要加 -c 命令,生成 .o 目标文件,如下:
gcc -c code.c 生成:code.o
gcc -c add.c 生成:add.o
3. Makefile代替上面逐步编译过程:
新建Makefile命令如下:vi Makefile
进入以后,Makefile代码如下:
a.out : code.c add.c // 冒号中间可以用空格
gcc -c code.c // 开头一定要用 tab 键,不要用空格键
gcc -c add.c
gcc code.o add.o
clean : // 负责清理运行产生的中间文件
*.o a.out
代码还可以改为:(严格注意这里的缩进)
a.out : code.o add.o // 当执行到 code.o 的时候,make 就向下找 code.o 语句执行,也就是第三、四行,然后回到此行执行 add.o ,以此类推。
gcc code.o add.o // 最后执行这一条
code.o : code.c
gcc -c code.c
add.o : add.c
gcc -c add.c
clean :
rm *.o a.out
保存运行,在gcc里面直接输入:make
这样就把 code.o 和 add.o 都执行出来;
如果只想编译 code.c,在gcc里输入:make code.o
这样就只运行 code.o
最后在gcc里输入:make clean
清理中间文件
4. struct 结构体,把多个不同类型的变量合成一个整体,且结构体里面的变量声明语句并没有生成新的变量。
struct student{ 变量声明语句 }; // 声明结构体
结构体可以当作数据类型来使用,并可以声明结构体变量:
struct student student; // 用结构体数据类型 struct student 定义一个 student 结构体变量,这里两个 student 不一样。
另一种写法:
struct{ 变量声明语句 } student; // 这里的 student 是结构体变量
这种写法很局限,没办法写入头文件,无法被其他文件使用。
一般结构体写在函数之外,进行全局声明,也可以写在头文件里面。
5. typedef 专门给数据类型起别名,
typedef struct student stu; // 把 struct student 重命名为 stu;
和上面的宏定义预处理 #define 不一样,
首先:typedef 是 c 语言语句,而 #define 不是;
其次:typedef 后面有分号,#define 没有;
最后:新起的名字的先后顺序不一样。
6. 可以把上两条改写为一个:(以后按这种方式声明结构体)
typedef struct { // 此处不需要再在 struct 后面加 student
变量声明语句;
} stu;
stu student1 = {,,,}; // 初始化结构体,和变量声明语句里的类型一一对应
举例:(struct.c)
typedef struct
{
int age;
float height;
} stu;
int main()
{
stu student = {24, 1.65f}; // 键盘输入:scanf("%d", student.age);
printf("%d\n", student.age);
***
}
也可以把结构体初始化为数组:stu student[3] = {{}, {}, {}};
结构体变量可以作形参,但是如果结构体很大的话,这个形参就很大。
我们用指针变量代替结构体变量:
void scan(stu *student)
{
scanf("%d", &(student -> age)); // 详细解释请看下面 print() 函数,这里若使用 &student.age 则编译不通过
***
}
void print(const stu *student) // 如果这个函数不需要返回值,则加上 const 防止参数被修改
{
printf("%d\n", (*student).age); // 一般不用这个方法,如果用,注意:这里的“*student”必须加括号,否则编译错误
printf("%d\n", student -> age); // 通常用这个方法,“->”前后可以加空格
***
}
int main()
{
stu student;
scan(&student);
print(&student);
}
7. 变量地址必须是变量大小的整数倍,这叫做数据对齐。(特例:double 变量地址只需要是 4 的倍数即可)
数据对齐会导致结构体中,不同变量之间有空隙,所以结构体变量的大小并不是其中每一个子变量大小之和。
举例:
typedef struct
{
char i; // 1字节
char j; // 1字节
int k; // 4字节
}
结构体占用空间顺序为:
首先根据最大的变量类型 int,确定,这个空间以 4 字节为单位,实行补齐政策;
其次前面两个 char 占用两个字节,而最小单位为 4 字节,所以需要补两个 0,满足 4 个字节;
最后,加上一个 int,4 个字节;
这个结构体共占用 8 字节:CCXX IIII
typedef struct
{
char i;
int k[2];
char j;
}
这个结构体空间占用顺序为:
首先根据 int,可以确定还是以 4 个字节为单位,不考虑数组;
第一个和最后一个 char 都需要补三个 0 ,满足 4 字节;
所以,共占用 16 个字节:CXXX IIII IIII CXXX
8. 控制结构体成员占用二进制位数,这里的数字是二进制位数,而非字节数;
typedef struct
{
short k:2;
char i:1;
char j:1;
int w:3;
int s:8;
}
根据 int 可以确定这个结构体以 4 字节为最小单位,冒号后面最大为 32,超过 32 编译会报错;
这里有个简便理解办法,因为后面的数字已经把结构体二进制化了,只要后面所有数字之和不超过 32 就是 4 字节,否则就是 8 字节,以此类推;
这个结构体占 4 字节。
而且一个 .h 头文件只能链接一个 .c 文件。
头文件里面的东西在程序运行的时候,是不存在的,所以,函数声明可以放在头文件里,变量声明不可以。
编写头文件的时候,要使用条件编译,避免头文件被多次编译。
一个加法程序如下:
主函数 code.c 文件如下:
#include <stdio.h>
#include "add.h" // code.c 和 add.c 在同一个文件夹,所以用 “”,而不是 < >
extern int result; // 全局变量,在 add.c 已经定义了,此处只是再次调用而已,所以要加 extern
int main()
{
add(7, 3); // 因为它无返回值,所以只能在此单独调用,不能写在 printf 里面。
printf("%d\n", result);
return 0;
}
加法函数 add.c 内容如下:
#include <stdio.h> // 如果下面需要调用这个库,则在这需要声明,与主函数是否有无关
#include "add.h" // 记得加入这个头文件
int result; // 定义全局变量,除了此处不加 extern,其他都得加。注意:此处不能用 static 修饰 result,否则作用域变小,main函数无法调用。
void add(int value, int value1) // void无返回值
{
result = value + value1;
}
连接函数 add.h 如下:
#ifndef __ADD_H__ // 如果add.h未被定义,则执行下面的语句(这里是#ifndef,而不是#ifdef),否则不执行。这句话可以防止同一个 .h 文件,被多次编译。
#define __ADD_H__ // 定义 add.h 或者给 add.h 起别名
int add(int, int); // 把add.c里面的声明部分提取出来,记得末尾有分号
#endif
(所有 .h 头文件都用这种格式进行条件控制)
编译的时候,需要把 code.c 和 add.c 都编译才可以,即在gcc里如下输入:
gcc code.c add.c
2. 多个文件实际操作的时候,我们是单个文件编译,最后把编译后的文件加在一起。
那么在编译的时候,需要加 -c 命令,生成 .o 目标文件,如下:
gcc -c code.c 生成:code.o
gcc -c add.c 生成:add.o
3. Makefile代替上面逐步编译过程:
新建Makefile命令如下:vi Makefile
进入以后,Makefile代码如下:
a.out : code.c add.c // 冒号中间可以用空格
gcc -c code.c // 开头一定要用 tab 键,不要用空格键
gcc -c add.c
gcc code.o add.o
clean : // 负责清理运行产生的中间文件
*.o a.out
代码还可以改为:(严格注意这里的缩进)
a.out : code.o add.o // 当执行到 code.o 的时候,make 就向下找 code.o 语句执行,也就是第三、四行,然后回到此行执行 add.o ,以此类推。
gcc code.o add.o // 最后执行这一条
code.o : code.c
gcc -c code.c
add.o : add.c
gcc -c add.c
clean :
rm *.o a.out
保存运行,在gcc里面直接输入:make
这样就把 code.o 和 add.o 都执行出来;
如果只想编译 code.c,在gcc里输入:make code.o
这样就只运行 code.o
最后在gcc里输入:make clean
清理中间文件
4. struct 结构体,把多个不同类型的变量合成一个整体,且结构体里面的变量声明语句并没有生成新的变量。
struct student{ 变量声明语句 }; // 声明结构体
结构体可以当作数据类型来使用,并可以声明结构体变量:
struct student student; // 用结构体数据类型 struct student 定义一个 student 结构体变量,这里两个 student 不一样。
另一种写法:
struct{ 变量声明语句 } student; // 这里的 student 是结构体变量
这种写法很局限,没办法写入头文件,无法被其他文件使用。
一般结构体写在函数之外,进行全局声明,也可以写在头文件里面。
5. typedef 专门给数据类型起别名,
typedef struct student stu; // 把 struct student 重命名为 stu;
和上面的宏定义预处理 #define 不一样,
首先:typedef 是 c 语言语句,而 #define 不是;
其次:typedef 后面有分号,#define 没有;
最后:新起的名字的先后顺序不一样。
6. 可以把上两条改写为一个:(以后按这种方式声明结构体)
typedef struct { // 此处不需要再在 struct 后面加 student
变量声明语句;
} stu;
stu student1 = {,,,}; // 初始化结构体,和变量声明语句里的类型一一对应
举例:(struct.c)
typedef struct
{
int age;
float height;
} stu;
int main()
{
stu student = {24, 1.65f}; // 键盘输入:scanf("%d", student.age);
printf("%d\n", student.age);
***
}
也可以把结构体初始化为数组:stu student[3] = {{}, {}, {}};
结构体变量可以作形参,但是如果结构体很大的话,这个形参就很大。
我们用指针变量代替结构体变量:
void scan(stu *student)
{
scanf("%d", &(student -> age)); // 详细解释请看下面 print() 函数,这里若使用 &student.age 则编译不通过
***
}
void print(const stu *student) // 如果这个函数不需要返回值,则加上 const 防止参数被修改
{
printf("%d\n", (*student).age); // 一般不用这个方法,如果用,注意:这里的“*student”必须加括号,否则编译错误
printf("%d\n", student -> age); // 通常用这个方法,“->”前后可以加空格
***
}
int main()
{
stu student;
scan(&student);
print(&student);
}
7. 变量地址必须是变量大小的整数倍,这叫做数据对齐。(特例:double 变量地址只需要是 4 的倍数即可)
数据对齐会导致结构体中,不同变量之间有空隙,所以结构体变量的大小并不是其中每一个子变量大小之和。
举例:
typedef struct
{
char i; // 1字节
char j; // 1字节
int k; // 4字节
}
结构体占用空间顺序为:
首先根据最大的变量类型 int,确定,这个空间以 4 字节为单位,实行补齐政策;
其次前面两个 char 占用两个字节,而最小单位为 4 字节,所以需要补两个 0,满足 4 个字节;
最后,加上一个 int,4 个字节;
这个结构体共占用 8 字节:CCXX IIII
typedef struct
{
char i;
int k[2];
char j;
}
这个结构体空间占用顺序为:
首先根据 int,可以确定还是以 4 个字节为单位,不考虑数组;
第一个和最后一个 char 都需要补三个 0 ,满足 4 字节;
所以,共占用 16 个字节:CXXX IIII IIII CXXX
8. 控制结构体成员占用二进制位数,这里的数字是二进制位数,而非字节数;
typedef struct
{
short k:2;
char i:1;
char j:1;
int w:3;
int s:8;
}
根据 int 可以确定这个结构体以 4 字节为最小单位,冒号后面最大为 32,超过 32 编译会报错;
这里有个简便理解办法,因为后面的数字已经把结构体二进制化了,只要后面所有数字之和不超过 32 就是 4 字节,否则就是 8 字节,以此类推;
这个结构体占 4 字节。