🤔️深入浅出地讲讲,C语言中,如何引用自定义文件中的函数

2023年 9月 21日 33.8k 0

前言

在我们用C语言编程的时候,会遇到这么一个问题。

A文件用到了一个功能,B文件也用到了这个功能,那对于初学者来说,就只能将A文件实现这个功能的代码粘贴一份到B文件。之后如果C文件D文件,也需要这个功能,也只能粘贴代码了。不得不说,这很麻烦。

举个实际的例子,用C语言作数据结构练习的时候,需要输出线性表这个功能:

void printList(int *data, int length)
{
  for (int i = 0; i < length; i++)
  {
    printf("%d ", data[i]);
  }
  printf("n");
};

这个功能的使用频率是非常高的,在每个文件都复制这样一份代码是很不划算的一个做法。

可不可以将这个代码放在一个文件里,然后其他文件要用到这个函数,直接引用就好了,没必要直接复制代码。

当然是可以的,C语言提供了这一功能。

从其他文件中引入自定义函数

假设现在有个index.c文件, 这个文件的就是要将data数组中的内容打印出来

#include 

void printList(int *data, int length)
{
  for (int i = 0; i < length; i++)
  {
    printf("%d ", data[i]);
  }
  printf("n");
};

int main()
{
  int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};
  printList(data, 10);
  return 0;
}

现在编译这个文件:

image.png

没有任何提示就是不报错了,

-o 表示编译后的文件名是index,如果不指定文件名,就会默认a.out

运行一下:

image.png

正确输出,没有问题

index.c中,负责打印的功能是函数printList,现在将printList放到util文件中去:

//uitl.c

#include 
int printList(struct List *L)
{
  for (int i = 0; i length; i++)
  {
    printf("%dn", L->data[i]);
  }

  return 0;
};

index.c文件只剩下main函数了,除此之外,还需要保留printList的函数声明:

函数声明就是含有函数返回类型,函数名称,函数的参数;

函数的定义就是带有函数体的函数声明

util.c中的printList函数就是一个函数定义

//index.c
#include 

int printList(struct List *L);

int main()
{
  int data[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0};

  struct List list;
  list.data = data;
  list.length = 10;

  printList(&list);
  return 0;
}

这样就可以了,我们来编译下index.c文件

image.png

报错了。它说printList这个标识符未定义(undefined symbols: printList),也就是说只有声明,但找不到uitl.c中的printList函数的定义。

这说明gcc命令并不会帮我们自动寻找util.c文件,需要我们手动告诉gcc
image.png

在命令行中加入了util.c后,编译就不报错了。咱们来执行一下:
image.png

运行成功

大致的过程就是这样,只是成熟的开发一般不这么干。想想,如果有数十个函数需要从其他文件中引入,那就需要在每个文件的前面加上数十个函数声明吗,这也很累人啊。

头文件

我们可以将函数声明放在一个头文件里面,然后再头文件引入头文件就可以了。
头文件就是以.h结尾的文件,里面主要放一些函数声明。
现在创建一个util.h头文件

// util.h
void printList(int *data, int length);

然后在index.c引入这个头文件:

//index.c

#include 
#include "util.h"

void printList(int *data, int length)
{
  for (int i = 0; i < length; i++)
  {
    printf("%d ", data[i]);
  }
  printf("n");
};

这样就可以了,编译命令和上面一样,需要告诉gcc,去util中找函数定义,不然又会出现undefined symbols: printList这样的错误

你们注意到没有,index.c中还有一个头文件,stdio.h,这里面其实也放着大量的函数声明。stdio大致意思是standard I/OI/O就是input/ouput的缩写,整体意思就是标准输入输出流

当我们需要在屏幕上输出内容,或者需要在从键盘输入的时候,这个头文件的引入就是必不可少的。

头文件原理

在C文件编译之前,其实还有一个预处理的过程,对于头文件来说,预处理会将头文件里面的内容插入到#include所在的地方。我们来看看:

image.png

-E 的作用就是将预处理之后,立即停止翻译过程,不进行后续的编译过程,并且显示预处理的内容

image.png
这是index.c文件的预处理输出截图,因为有stdio.h头文件,所以内容很多,我只截了其中一部分。

重点关注输出内容的后面,后面有我们的源代码,源代码上面就是util.h的内容了。
看到这里,相信大家对头文件的作用理解更深刻了吧

链接

在gcc经过对文件的预处理,编译之后,会得到一个目标文件(通常以.o后缀结尾)。

编译的作用是对C语言源文件的内容进行语法分析,进而生成对应的机器指令。但是,对有些函数来说,它的解析是无法完成的。因为它们可能并不是在当前源文件内定义的。也就是说目标文件中还没有函数的定义,需要在链接这一步才会去找函数的定义。

所以这个目标文件不能直接执行,还需要经过链接这一步。

下面将之前的编译命令拆开来看:

image.png

-c 表示仅输出目标文件

现将index.c文件编译成目标文件index.o

目标文件是二进制文件,里面就是一大堆的0和1。用编辑器打开,大概长这样:
image.png

编辑器对二进制文件做了显示的优化,将二进制用十六进制表示,并且右边会显示二进制对应的字符内容

image.png

然后将util.c编译成util.o

image.png
最后将两个目标文件链接起来

image.png

运行没问题,搞定

总结

这篇文章讲了如何在C语言中,引用其他文件中自己编写的函数,过程中,串讲了一些编译和链接的概念。思路清晰,例子详细,是个不错的文章。

本篇例子运行机器是Mac,window系统可能看起来不同,但其中原理是一样的

相关文章

JavaScript2024新功能:Object.groupBy、正则表达式v标志
PHP trim 函数对多字节字符的使用和限制
新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
为React 19做准备:WordPress 6.6用户指南
如何删除WordPress中的所有评论

发布评论