哎呦 资料合集
链接:https://pan.quark.cn/s/770d9387db5f
一、函数库是什么?------ 代码的"工具箱"
函数库是一组具有相近功能或操作同一数据结构的函数集合。
简单来说,函数库就是我们编程时可以随时取用的"工具箱"。当我们需要某个功能时,不必每次都从零开始"造轮子",而是直接从工具箱里拿出封装好的工具来使用。
1. 系统自带的工具箱:标准C库
最典型的例子就是C语言的 <string.h>
头文件,它提供了一系列字符串处理函数。
【代码案例:字符串处理函数实战】
让我们编写一个简单的程序,演示几个常用的字符串函数:strcpy
(拷贝), strcat
(拼接), strlen
(长度), 和 strcmp
(比较)。
// file: string_demo.c
#include <stdio.h>
#include <string.h>
int main() {
char str1[50] = "Hello";
char str2[50] = "World";
char str3[50];
// 1. strlen: 获取字符串长度
printf("Length of str1: %zu\n", strlen(str1));
// 2. strcpy: 拷贝 str1 到 str3
strcpy(str3, str1);
printf("After strcpy, str3 is: %s\n", str3);
// 3. strcat: 将 " " 和 str2 拼接到 str3 后面
strcat(str3, " ");
strcat(str3, str2);
printf("After strcat, str3 is: %s\n", str3);
// 4. strcmp: 比较 str1 和 "Hello"
if (strcmp(str1, "Hello") == 0) {
printf("str1 is equal to \"Hello\"\n");
} else {
printf("str1 is not equal to \"Hello\"\n");
}
return 0;
}
编译与运行:
在Linux终端中,我们使用GCC编译器来编译这个程序。
$ gcc string_demo.c -o string_demo
$ ./string_demo
运行结果:
Length of str1: 5
After strcpy, str3 is: Hello
After strcat, str3 is: Hello World
str1 is equal to "Hello"
这个例子直观地展示了如何直接调用标准库中的函数来完成复杂操作,极大地提高了开发效率。
2. 自定义的工具箱:创建自己的排序库
除了使用系统库,我们也可以创建自己的函数库。假设我们经常需要用到排序算法,就可以把它们封装成一个自定义的排序库。
【代码案例:封装一个简单的排序库】
我们将创建一个包含冒泡排序和选择排序的库。
第1步:创建头文件
my_sort.h
// file: my_sort.h
#ifndef MY_SORT_H
#define MY_SORT_H
// 打印数组的辅助函数
void print_array(int arr[], int size);
// 冒泡排序
void bubble_sort(int arr[], int size);
// 选择排序
void select_sort(int arr[], int size);
#endif // MY_SORT_H
第2步:创建源文件
my_sort.c
// file: my_sort.c
#include "my_sort.h"
#include <stdio.h>
void print_array(int arr[], int size) {
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
void bubble_sort(int arr[], int size) {
for (int i = 0; i < size - 1; i++) {
for (int j = 0; j < size - 1 - i; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
void select_sort(int arr[], int size) {
int min_idx;
for (int i = 0; i < size - 1; i++) {
min_idx = i;
for (int j = i + 1; j < size; j++) {
if (arr[j] < arr[min_idx]) {
min_idx = j;
}
}
if (min_idx != i) {
int temp = arr[min_idx];
arr[min_idx] = arr[i];
arr[i] = temp;
}
}
}
第3步:创建主程序
main.c
来使用这个库
// file: main.c
#include <stdio.h>
#include "my_sort.h" // 引入我们自己的库
int main() {
int arr1[] = {64, 34, 25, 12, 22, 11, 90};
int n1 = sizeof(arr1) / sizeof(arr1[0]);
printf("Original array for bubble sort: \n");
print_array(arr1, n1);
bubble_sort(arr1, n1);
printf("Sorted array with bubble sort: \n");
print_array(arr1, n1);
printf("\n---------------------------------\n\n");
int arr2[] = {5, 2, 8, 3, 9, 4, 1};
int n2 = sizeof(arr2) / sizeof(arr2[0]);
printf("Original array for select sort: \n");
print_array(arr2, n2);
select_sort(arr2, n2);
printf("Sorted array with select sort: \n");
print_array(arr2, n2);
return 0;
}
编译与运行:
在编译时,需要将主程序和库的源文件一起编译。
$ gcc main.c my_sort.c -o sort_test
$ ./sort_test
运行结果:
Original array for bubble sort:
64 34 25 12 22 11 90
Sorted array with bubble sort:
11 12 22 25 34 64 90
---------------------------------
Original array for select sort:
5 2 8 3 9 4 1
Sorted array with select sort:
1 2 3 4 5 8 9
通过这个例子,我们成功创建并使用了一个自定义库。这就是代码复用 和程序积累最直接的体现。
二、函数库的发布形式:源码 vs. 二进制
笔记中提到了两种发布形式,各有优劣:
- 源码形式 :就像我们上面的
my_sort.c
,直接提供源代码。
- 优点:透明,便于学习和修改。
- 缺点:暴露核心算法,每次使用都需要重新编译,浪费时间。
- 二进制形式 :将库编译成一个二进制文件(如Linux下的
.so
动态库或.a
静态库)。
- 优点:保护知识产权,无需编译,链接速度快。
- 缺点:无法查看源码,不便学习和二次开发。
商业公司和操作系统通常以二进制形式提供库,以保护其核心技术并提高效率。
三、动手实践:在Ubuntu中探寻标准C库
我们的程序能够运行,离不开标准C库(libc
)的支持。它在哪里?我们的程序是如何找到它的?
1. 查找标准C库的位置
在大多数64位的Ubuntu系统中,标准C库位于 /usr/lib/x86_64-linux-gnu/
目录下。我们可以用 ls
命令来验证。
【命令行操作】
$ ls -l /usr/lib/x86_64-linux-gnu/libc*
运行结果(示例):
-rw-r--r-- 1 root root 2216360 Mar 27 05:40 /usr/lib/x86_64-linux-gnu/libc.a
-rw-r--r-- 1 root root 338 Mar 27 05:40 /usr/lib/x86_64-linux-gnu/libc.so
lrwxrwxrwx 1 root root 12 Mar 27 05:40 /usr/lib/x86_64-linux-gnu/libc.so.6 -> libc-2.35.so
从结果中我们可以看到:
libc.a
:静态库文件。libc.so.6
:动态库文件,它是一个软链接 (类似Windows的快捷方式),指向了实际的库文件libc-2.35.so
。使用版本号(如.6
)是为了更好地进行版本管理。
2. 验证程序对动态库的依赖
我们可以使用 ldd
(List Dynamic Dependencies) 命令来查看一个可执行文件依赖哪些动态库。
【代码案例:验证
hello world
程序的依赖】
先创建一个简单的 hello.c
。
// file: hello.c
#include <stdio.h>
int main() {
printf("Hello, Library!\n");
return 0;
}
编译并使用
ldd
查看:
$ gcc hello.c -o hello
$ ldd hello
运行结果:
linux-vdso.so.1 (0x00007ffc1b7f8000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f1a3b200000)
/lib64/ld-linux-x86-64.so.2 (0x00007f1a3b42a000)
关键信息是第二行:libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6
。 这明确地告诉我们,hello
这个程序在运行时,需要加载 /lib/x86_64-linux-gnu/libc.so.6
这个动态库。这也证明了,即使是简单的 printf
,也离不开标准C库的支持。
四、GCC的"智能":自动链接标准库
细心的你可能发现了,我们在编译 string_demo.c
和 hello.c
时,并没有用任何特殊参数来告诉编译器去链接标准C库。这是因为 GCC 默认会自动链接标准C库。这是C语言生态系统为了方便开发者而做的一个约定,因为几乎所有的C程序都需要它。
- 动态库 (.so):在程序运行时才被加载到内存。多个程序可以共享同一个库的实例,节省内存。系统更新库后,所有依赖它的程序都能受益。
- 静态库 (.a):在编译时,库的代码被完整地复制到可执行文件中。程序体积更大,但可以独立运行,不依赖系统环境中的库文件。
默认情况下,GCC优先使用动态链接。
五、总结
通过今天的实践,我们把课堂笔记上的理论知识转化为了看得见、摸得着的操作:
- 函数库是代码的集合 :我们不仅使用了系统自带的
string.h
,还亲手创建了一个my_sort
库。 - 函数库提升效率:通过代码复用和程序积累,让我们能站在巨人的肩膀上编程。
- 两种发布形式:源码(便于学习)和二进制(保护代码、提高效率)。
- 定位与验证 :我们学会了在Linux下使用
ls
和ldd
命令来查找和验证程序所依赖的动态库。
