c笔记10---多文件操作,Makefile,结构体 struct 及结构体变量,重命名 typedef

本文介绍了C语言中多文件操作的步骤,包括如何使用头文件、extern关键字和Makefile。讨论了结构体的声明和使用,以及typedef为数据类型起别名的方法。同时,讲解了数据对齐在结构体中的影响以及如何控制结构体成员的二进制位数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 字节。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值