4

可以说我有多个功能可以实现相同的目标。这方面的一个例子是为数据类型创建一个“构造函数”。

String new_String();
String new_String(const char *cstr);
String new_String(String s);

显然这不能在 C 中完成,但是对于像这样的函数(以及函数重载有用的情况)是否有命名它们的约定或最佳实践?

像这样的东西?

String new_String();
String new_String_c(const char *cstr);
String new_String_s(String s);

这对我来说感觉很尴尬,不容易阅读。还是这样的?

String new_String();
String new_String_from_cstr(const char *str);
String new_String_copy(String s);

这让我想起了可怕的长 Java 名称,因为这很快就会变得荒谬。

int String_last_index_of_any_characters(String s, char *chars, int length);
4

4 回答 4

3

您可以定义一个带有 void * 初始化参数和枚举的“构造函数”:

enum StringCtor { SC_DEFAULT, SC_C_STR, SC_COPY };

String new_String(enum StringCtor type, const void *arg);

String s1 = new_String(SC_DEFAULT, 0);
String s2 = new_String(SC_C_STR, "hello");
String s3 = new_String(SC_COPY, &s2);

您也可以选择使用 a...而不是 a void *。想法是一样的,但是va_*如果参数应该是字符串或副本,您可以使用宏来提取参数。

如果您只希望 API 具有单个构造函数的外观,但仍希望类型安全,则可以使用上述技术来创建实际的构造函数实现,并使用内联函数和预处理器技巧来赋予单个构造函数的外观,具有类型安全性。

String new_StringImpl(enum StringCtor type, const void *arg);

static inline String new_StringImplDefault () {
    return new_StringImpl(SC_DEFAULT, 0);
}
static inline String new_StringImplCstr (const char *s) {
    return new_StringImpl(SC_C_STR, s);
}
static inline String new_StringImplCopy (String *s) {
    return new_StringImpl(SC_COPY, s);
}

#define new_String_Paste(TYPE) new_String_ ## TYPE
#define new_String_SC_DEFAULT(ARG) new_StringImplDefault()
#define new_String_SC_C_STR(ARG) new_StringImplCstr(ARG)
#define new_String_SC_COPY(ARG) new_StringImplCopy(ARG)

#define new_String(TYPE, ...) new_String_Paste(TYPE)(__VA_ARGS__)

String s1 = new_String(SC_DEFAULT);
String s2 = new_String(SC_C_STR, "hello");
String s3 = new_String(SC_COPY, &s2);

注意使用可变参数宏,SC_DEFAULT不再需要第二个参数。在足够的优化级别上,代码仅转换为对单个实现函数的调用,并具有编译时类型安全检查的好处。因此,以您编写更多代码为代价,您可以为库的用户提供单个构造函数 API 的外观,并具有多个构造函数的所有类型安全性。

于 2012-07-14T22:07:03.907 回答
0

没有标准约定,但如果一个或多或少相似的函数有很多重载,人们通常会参考OpenGL 函数命名约定

@user315052 提供的方法通常不是首选,因为类型安全问题(容易出错、难以更改等)和/或当您要传递多个参数时会带来不便。但有时出于不同的技术原因使用它(内核系统调用就是一个很好的例子)。

于 2012-07-14T22:10:30.327 回答
0

我不认为关于如何处理重载有一个完善的约定。您的第二种方法很好;使用常识使名称保持合理简短但仍易于理解。

在某些情况下,像您的第一种方法(或在 OpenGL 中)这样的系统命名约定可能是一个好主意,但它会使代码不那么自解释;任何阅读您的代码的人都需要先了解您的命名约定。

于 2012-07-14T22:47:16.340 回答
0

现代 C,又名 C11,可以做的比你想象的要多。_Generic它具有类型泛型宏,可以使用新表达式完成类似于 C++ 中的函数重载的事情。

作为库的内部接口,您仍然需要函数声明:

String new_String_from_cstr(const char *str);
String new_String_copy(String s);

现在您的类型泛型宏可能如下所示:

#define new_String(X)                       \
_Generic((X),                               \
         const char*: new_String_from_cstr, \
         String: new_String_copy)(X)

C11 还没有完全实现,但我认为最新的 clang 编译器已经有你需要的东西了。

对于其他编译器(gcc 和堂兄弟),您可以查看P99,它模拟了接近于此的功能。P99 还具有处理接收不同数量参数的宏的技巧。

如果您使用诸如用户界面之类的工具,那么函数本身的命名约定就变得不那么重要了。

于 2012-07-14T22:49:28.273 回答