C语言接口设计技巧(新手必看)
本节介绍 C 语言接口设计中常用的一些方法和技巧。
下表给出了设计对称接口时常用的术语,这些术语通常作为函数名的前缀或后缀存在。
首先,函数接口应尽量避免返回 void 类型。若分析标准 C 库提供的接口,就会发现仅有少量函数的返回值为 void 类型,常见的便是 free(),而绝大多数的标准库函数是有返回值的。
某些函数通过返回一些特殊的值来表明出错状态,而某些函数的返回值纯粹是为了方便使用。比如 memcpy()、strcpy() 等复制内存或字符串的函数,会返回传入的目标地址值,这样可以方便调用者书写代码。
实质上,对 C 程序来讲,从函数中返回一个值对程序性能的影响微乎其微。因此,我们将函数中已经计算好的值返回,可避免调用者再次计算这些值。
比如,BSD 系统(包括 macOS)提供了 stpcpy() 和 stpncpy() 函数,这两个函数的功能与 strcpy() 和 strncpy() 一样,但返回值不同。stpcpy() 和 stpncpy() 函数会返回目标缓冲区中指向 \0 终止字符的指针。这样,当我们需要将多个字符串依次串接到同一个缓冲区中时,调用 stpcpy() 要比调用 strcpy() 高效很多,可以避免每次串接操作均从目标缓冲区的头部开始数起,如下面的代码所示:
其次,应选择最合适的参数类型。比如当涉及长度、大小等参数时,应使用 size_t 类型。
因为习惯,很多人可能会选择 int 或 unsigned int 类型来表示长度或大小等参数。现在应摒弃这种做法,因为 64 位系统已经很常见了,一个文件的长度可以轻松超过 int 或 unsigned int 类型的最大存储值。而 size_t 类型是无符号整型,其定义随底层架构的不同而不同(基本等同于 uintptr_t 类型),它的最大存储值对应操作系统可处理的最大文件长度,故而比 int 或 unsigned int 类型更加安全。
再次,对指针类参数和返回值,一定要恰当使用 const 修饰词。虽然对 C 代码来讲,const 指针可以随意被强制转换为非 const 指针,但它仍然可以在一定程度上帮助我们书写更好的代码,原因有两个:
最后,应谨慎使用 bool 类型的返回值或参数。当一个函数在执行中可能出现多种错误时,只能用于表示成功或失败的 bool 型返回值就不够用了。
另外,我们经常使用 bool 型参数来传递一个标志,当一个标志不够用时,就必须使用更多的 bool 型参数;而如果我们使用 int 或 unsigned 类型的参数作为标志,则可以通过标志位来传递多达 32 个标志,这将在扩展这一接口时带来极大的便利。
接口对称设计
C语言的接口都是成对出现的,代表着一个操作以及对应的反操作。比如 malloc() 的反操作是 free()。下表给出了设计对称接口时常用的术语,这些术语通常作为函数名的前缀或后缀存在。
操作 | 反操作 | 含义或用途 |
---|---|---|
new | delete | 创建/销毁一个新对象,通常意味着存在内存分配;如 variant_new 和 variant_delete |
create | destroy | 创建/销毁一个新对象,通常意味着存在内存分配;如 create_window 和 destroy_window |
init | deinit/terminate/cleanup | 初始化/清理一个对象;如 array_init 和 array_cleanup |
open | close | 打开/关闭一个对象;如 fopen 和 fclose |
connect | disconnect | 连接/断开;如 connect_to_host 和 disconnect_from_host |
read | write | 读取/写入;如 fread 和 fwrite |
alloc | free | 分配/释放;如 malloc 和 free |
ref | unref | 引用/反引用;如 variant_ref 和 variant_unref |
get | set | 获取/设置;如 get_cookie 和 set_cookie |
attach | detach | 附着/脱离;如 attach_data 和 detach_data |
接口的参数和返回值
有关接口的参数和返回值,下面给出一些值得遵循的建议。首先,函数接口应尽量避免返回 void 类型。若分析标准 C 库提供的接口,就会发现仅有少量函数的返回值为 void 类型,常见的便是 free(),而绝大多数的标准库函数是有返回值的。
某些函数通过返回一些特殊的值来表明出错状态,而某些函数的返回值纯粹是为了方便使用。比如 memcpy()、strcpy() 等复制内存或字符串的函数,会返回传入的目标地址值,这样可以方便调用者书写代码。
实质上,对 C 程序来讲,从函数中返回一个值对程序性能的影响微乎其微。因此,我们将函数中已经计算好的值返回,可避免调用者再次计算这些值。
比如,BSD 系统(包括 macOS)提供了 stpcpy() 和 stpncpy() 函数,这两个函数的功能与 strcpy() 和 strncpy() 一样,但返回值不同。stpcpy() 和 stpncpy() 函数会返回目标缓冲区中指向 \0 终止字符的指针。这样,当我们需要将多个字符串依次串接到同一个缓冲区中时,调用 stpcpy() 要比调用 strcpy() 高效很多,可以避免每次串接操作均从目标缓冲区的头部开始数起,如下面的代码所示:
#define _GNU_SOURCE #include <string.h> #include <stdio.h> int main(void) { /* 代码将在buffer中构造“foobar” */ char buffer[20]; char *to = buffer; to = stpcpy(to, "foo"); to = stpcpy(to, "bar"); printf("%s\n", buffer); return 0; }
其次,应选择最合适的参数类型。比如当涉及长度、大小等参数时,应使用 size_t 类型。
因为习惯,很多人可能会选择 int 或 unsigned int 类型来表示长度或大小等参数。现在应摒弃这种做法,因为 64 位系统已经很常见了,一个文件的长度可以轻松超过 int 或 unsigned int 类型的最大存储值。而 size_t 类型是无符号整型,其定义随底层架构的不同而不同(基本等同于 uintptr_t 类型),它的最大存储值对应操作系统可处理的最大文件长度,故而比 int 或 unsigned int 类型更加安全。
再次,对指针类参数和返回值,一定要恰当使用 const 修饰词。虽然对 C 代码来讲,const 指针可以随意被强制转换为非 const 指针,但它仍然可以在一定程度上帮助我们书写更好的代码,原因有两个:
- 第一,为指针参数使用 const 修饰词,通常表明对应的接口不会修改该指针指向的缓冲区内容;
- 第二,为返回的指针使用 const 修饰词,通常意在强调调用者不应该通过该指针值修改对应的缓冲区内容。
最后,应谨慎使用 bool 类型的返回值或参数。当一个函数在执行中可能出现多种错误时,只能用于表示成功或失败的 bool 型返回值就不够用了。
另外,我们经常使用 bool 型参数来传递一个标志,当一个标志不够用时,就必须使用更多的 bool 型参数;而如果我们使用 int 或 unsigned 类型的参数作为标志,则可以通过标志位来传递多达 32 个标志,这将在扩展这一接口时带来极大的便利。