C语言中最常见的命名规范是什么?

205

C中通常使用哪些命名约定?我知道至少有两种:

  1. GNU / linux / K&R 使用 lower_case_functions
  2. ? name ? 使用 UpperCaseFoo 函数

我这里只谈论C语言。我们的大多数项目是小型嵌入式系统,我们在其中使用C语言。

以下是我计划在下一个项目中使用的命名约定:


C命名约定

Struct              TitleCase
Struct Members      lower_case or lowerCase

Enum                ETitleCase
Enum Members        ALL_CAPS or lowerCase

Public functions    pfx_TitleCase (pfx = two or three letter module prefix)
Private functions   TitleCase
Trivial variables   i,x,n,f etc...
Local variables     lower_case or lowerCase
Global variables    g_lowerCase or g_lower_case (searchable by g_ prefix)

13
我不会强制在全局变量上添加 'g_' 前缀;我会强制使用有意义的名称(例如,将客户端语言环境命名为 client_locale 而不是 cl_lc)。传统的 C 语言不使用驼峰命名法;我曾经在 C 语言中写过驼峰式代码,但它看起来很奇怪(所以我不再这样做了)。尽管如此,这并不是错误的做法——一致性比使用哪种约定更重要。避免使用封装结构指针的 typedef,考虑 C 标准 —— 'FILE *' 的拼写方式就是这样,而不是 FILE_PTR。 - Jonathan Leffler
6
@Jonathan Leffler,使用 g_ 作为全局变量的标识有什么问题吗?在嵌入式系统中,我曾经遇到过通过全局变量和 extern g_somevar 很难追踪模块间依赖关系的问题。我个人认为这通常是一个不好的想法,但这种东西通常出于性能原因而被实现。例如,通过中断设置的全局标志指示数据已经准备就绪。 - JeffV
3
就其价值而言,这种命名约定大多是从PalmOS API约定中借鉴而来的。此外,它类似于O'Reilly的书中使用的约定:“使用C和GNU开发工具编程嵌入式系统”。个人而言,我喜欢在函数名称中使用TitleCase。我正在考虑在内部链接函数中使用lowerCamelCase(我在我的问题中称之为private)。 - JeffV
@Jeff V - 刻薄的回答是“通过注意在函数体中它们没有被定义为局部变量来跟踪全局变量”,但我认为这个答案有一定的价值。如果你有太多的局部变量,以至于无法区分哪些变量是局部的,哪些是全局的,那么你的函数可能太大了,除非你有一个很好的理由不这样做(空间/内存/效率等方面的考虑),否则你应该将其拆分。 - Chris Lutz
3
我完全同意 @Chris Lutz 的观点。在可能的情况下,应将变量保持在最窄的范围内。请注意,实际上我们正在讨论三个作用域:函数本地作用域、模块本地作用域(变量没有外部链接)和具有外部链接的全局变量。在嵌入式系统中通常会有“模块本地”的全局变量。因此,必须小心识别具有外部链接的全局变量,以便将它们最小化并理解模块间的交互。这就是“g_”前缀有所帮助的地方。 - JeffV
我同意使用一些g_符号。这有助于知道所引用的内容是全局的还是其他地方的。 - Gustavo Litovsky
13个回答

195
这里最重要的是一致性。那么我遵循GTK+编码约定,可以概括如下:
  • 所有的宏和常量都用大写字母:MAX_BUFFER_SIZETRACKING_ID_PREFIX
  • 结构体名称和typedef使用驼峰式命名:GtkWidgetTrackingOrder
  • 操作结构体的函数使用经典的C风格:gtk_widget_show()tracking_order_process()
  • 指针不需花哨的定义方式:GtkWidget *fooTrackingOrder *bar
  • 全局变量:不要使用全局变量。它们是邪恶的。
  • 存在的函数但不应直接调用或用途模糊:在开头使用一个或多个下划线:_refrobnicate_data_tables()_destroy_cache()

27
在第六点中,我更喜欢使用static并跳过模块前缀,因此如果gtk_widget_show()是具有文件作用域的函数,它将变为简单的添加了静态存储类的widget_show() - August Karlstrom
40
关于第6点的补充说明:C标准有一些规则,保留以“ _”开头的名称供实现和将来使用。有一些以“ _”开头的例外情况,但在我看来,这不值得去记忆。一个安全的规则是在代码中永远不要使用以“ _”开头的名称。相关的C FAQ条目:http://c-faq.com/~scs/cgi-bin/faqcat.cgi?sec=decl#namespace - jw013
16
#2 更具体地称为 Upper Camel CasePascal Case。 而 Camel Case 或 Lower Camel Case 则在第一个字母使用小写字母。 - Clint Pachl
14
本地多词变量应该怎么命名?是my_var还是myVar? - Dean Gurvitz
27
全局变量:尽量不要使用全局变量,它们有害。除非你在嵌入式项目中工作,且只有1024字节的RAM和8MHz的CPU。 - Kamil
显示剩余3条评论

46

你知道的,我喜欢保持简单明了......所以这是我在C中使用的方式:

  • 普通变量i,n,c等...(只有一个字母。如果一个字母不清楚,那么就将其作为本地变量)
  • 本地变量camelCase
  • 全局变量g_camelCase
  • 常量变量ALL_CAPS
  • 指针变量:在前缀中添加p_。对于全局变量,它将是gp_var,对于本地变量,它将是p_var,对于常量变量,它将是p_VAR。如果使用远程指针,则使用fp_代替p_
  • 结构体ModulePascalCase(模块=完整模块名称,或2-3个字母缩写,但仍然是PascalCase)。
  • 结构体成员变量camelCase
  • 枚举ModulePascalCase
  • 枚举值ALL_CAPS
  • 公共函数ModulePascalCase
  • 私有函数PascalCase
  • PascalCase

我为我的结构体定义了typedef,但对于标记和typedef使用相同的名称。标记并不是通常使用的,而是应该使用typedef。我还在公共模块头文件中向前声明了typedef以进行封装,这样我可以在定义中使用typedef的名称。

完整struct示例:

typedef struct UtilsExampleStruct UtilsExampleStruct;
struct UtilsExampleStruct{
    int var;
    UtilsExampleStruct *p_link;
};

1
我对qt框架一无所知,但你可以按照任何风格格式编写代码。据我所知,没有任何限制。 - SeanRamey
3
是不是指 PascalCasecamelCase 呢? - Michael
2
这种格式类似于Java和C#的命名约定。我建议您使用camelCase来命名函数,并为其添加模块前缀,例如MDL_functionName()。此外,我认为在指针变量前加上p_前缀没有什么好处,因此我会将其删除。 - Osman

41
"

“结构体指针”并不需要命名约定条款来覆盖它们。它们只是struct WhatEver *。不要用聪明而“显而易见”的typedef隐藏指针的事实。这没有任何作用,而且打字更长,破坏了声明和访问之间的平衡。

"

41
同意“不要隐藏指针”的观点,即使这个回答还没有涉及问题的其他方面(至少目前还没有)。 - Jonathan Leffler
4
@unwind,我倾向于同意。然而,有时指针并不打算被外部解引用,它更像是一个句柄提供给消费者使用,而不是他们将要使用的结构体的实际指针。这就是我留下TitleCasePtr的原因。typedef struct { fields } MyStruct,*MyStructPtr; - JeffV
1
我得到的印象是指针类型声明可以减少冗余,特别是在函数签名中,并且“不平衡”声明和访问只在实现文件中显示-客户端不应直接访问字段成员。 - August Karlstrom
1
@AugustKarlstrom 好的。不过我不太明白实现文件有什么“唯一”的,它里面的代码也是吧?我认为问题不只是关于“外部”名称的。所有代码在某个层面上都是“实现”。 - unwind
@unwind 好的,在实现文件中使用 struc WhatEver * - August Karlstrom
显示剩余3条评论

20

首先,C语言没有公有/私有/虚函数。这是C++的概念,它有不同的规定。在C里面,你通常会看到:

  • 常量使用全大写字母
  • 下划线用于结构体或函数名中的单词分隔,在C中很少使用驼峰命名法;
  • 结构体、typedef、联合、成员(联合和结构体)和枚举值通常使用小写字母(根据我的经验),而不是C++/Java/C#等约定的将第一个字母大写,但我想在C中也可能会出现这种情况。

C++更加复杂。我在这里看到了真正的混合。类名使用驼峰命名法或小写字母+下划线(根据我的经验,驼峰命名法更为常见)。结构体很少使用(通常是因为库需要,否则你会使用类)。


3
静态函数可以看作是私有函数;问题中没有提到虚函数。但“在C语言中很少见到驼峰命名法”的说法值得肯定。 - Jonathan Leffler
3
我认为Jeff所指的“public functions”应该是指“外部链接”,而“private functions”则应该是指“内部链接”。 - pmg
1
我见过以k开头的常量,例如:kBufferSize。不确定它的来源是什么。 - JeffV
2
ALL_CAPS 通常也用于枚举值。 - caf
苹果的Core Foundation和微软的Win32是两个主要使用驼峰命名法的C API。我相信还有其他的API也使用这种命名方式,但我经常编写使用这两个API的程序,因此从我的经验来看,在C语言中很少见到不使用驼峰命名法的情况。 - dreamlax
显示剩余3条评论

12

同时使用C#、Java、C、C ++和objective C进行编码,我采用了非常简单和清晰的命名约定来简化我的生活。

首先,它依赖于现代IDE(例如eclipse、Xcode...)的功能,通过悬停或ctrl点击获取快速信息......接受这一点后,我抑制了使用任何前缀、后缀和其他仅由IDE提供的标记。

然后是约定:

  • 任何名称都必须是一个可读的句子,解释你拥有什么。像“这是我的约定”。
  • 然后,有4种方法可以从句子中得出约定:
    1. THIS_IS_MY_CONVENTION 用于宏定义、枚举成员
    2. ThisIsMyConvention用于文件名、对象名(类、结构体、枚举、联合...)、函数名、方法名、typedef
    3. this_is_my_convention全局和局部变量、参数、结构体和联合体元素
    4. thisismyconvention[可选]非常局部和临时变量(例如for()循环索引)

就是这样。

它给出:

class MyClass {
    enum TheEnumeration {
        FIRST_ELEMENT,
        SECOND_ELEMENT,
    }

    int class_variable;

    int MyMethod(int first_param, int second_parameter) {
        int local_variable;
        TheEnumeration local_enum;
        for(int myindex=0, myindex<class_variable, myindex++) {
             localEnum = FIRST_ELEMENT;
        }
    }
}

我是这个的粉丝。 - Lead Vaxeral
只是想指出一个小矛盾。如果局部变量要使用蛇形命名法,那么“myindex”也应该改为“my_index”,“localEnum”应该改为“local_enum”。 - Ekin

11

这是一个(显然)不常用的命名规则,但我发现它很有用:使用驼峰命名法表示模块名,然后下划线,接着使用驼峰命名法表示函数或文件范围名。例如:

Bluetooth_Init()
CommsHub_Update()
Serial_TxBuffer[]

4
不是很不寻常,但非常实用。 - chux - Reinstate Monica

9
我建议不要混合使用驼峰命名法和下划线分隔符(如您提出的用于结构成员)。这会让人感到困惑。你可能会认为,“嘿,我有get_length,所以我应该有make_subset”,然后你会发现它实际上是makeSubset。使用最少惊讶原则,并保持一致。
我发现驼峰命名法对于键入名称(如结构体、typedef 和枚举)非常有用。但仅限于此。对于所有其他内容(函数名称、结构成员名称等),我使用下划线分隔符。

1
是的,任何命名约定的主要问题都在于可预测性和一致性。此外,因为C库本身使用全部小写字母并用下划线分隔,我建议您也这样做,这样您就不必在项目中处理两种不同的命名约定(假设您不编写libc的包装器来使其符合您的命名约定...但那很恶心)。 - Earlz
它还在末尾使用带有“_t”的typedef,但我没有看到任何人建议这样做。实际上,标准库甚至是不一致的:div_t(stdlib.h)是一个结构体,tm(time.h)也是。另外,看一下tm结构体成员,它们都带有tm_前缀,这似乎毫无意义且难看(在我看来)。 - JeffV
1
我发现CamelCase对于输入名称很有用...如果你以大写字母开头,它实际上是PascalCase。 - Tagc
将所有宏都写成大写对我来说听起来像常量,因为即使是C保留的宏也是小写的。但是使用类似函数的宏,您可以传递类型并执行其他奇怪的操作。在这种情况下,我会用库的缩写驼峰命名法前缀,后跟下划线和带有长参数名称的函数名称,以用作文档/消歧(使用IDE自动完成)。对于每个外部化函数库,我都会用全小写的缩写名称作为前缀,并为静态函数提供免费的小写和下划线名称。 - Arkt8

6
你还需要考虑单词的顺序,以使自动命名补全更加容易。
一个好的实践方法是:库名称+模块名称+操作+主题。
如果某部分不相关,则可以跳过它,但至少应该呈现一个模块名称和一个操作。
例如:
函数名:os_task_set_prio、list_get_size、avg_get
定义(通常没有操作部分):OS_TASK_PRIO_MAX

4

当我使用C语言编程时,我会遵循以下约定:

定义 前缀 示例
变量 - 局部 (简单命名) / i, n, a, b...
ii, nn, aa, bb...
iii, nnn, aaa, bbb...
变量 - 局部 (描述性命名) / variable, conection...
变量 - 全局 g_ g_variable, g_connection...
变量 - 常量 c_ c_variable, c_connection...
指针 p_ p_variable , p_connection...
数组 a_ a_variable, a_connection...
结构体 s_ s_variable, s_connection...
联合体 u_ u_variable, u_connection...
枚举 e_ e_variables, e_connection...
结构体 (成员)
联合体 (成员)
枚举 (成员)
m_
m_
m_
m_variable, m_connection...
m_variable, m_connection...
m_variable, m_connection...
结构体 (位域) b_ b_variable, b_connection...
函数 f_ f_variable, f_connection...
o_ o_variable, o_connection...

注意:

除变量外,每个定义都有一个单字母前缀,以便它们可以组合在一起,然后不会被误解。

注意:

由于我用完了字母并坚持只使用小写字母前缀,“o_”前缀用于宏没有匹配到定义的第一个字母(macro),而是匹配到了最后一个字母(macro)。

如果您有任何建议,我很乐意听取。


2
这非常干净。 - drudru
但并不是最好的。最好永远不要使用过长的本地变量名(除了全局变量,这样更难避免多重定义)...我宁愿使用一个字母的本地变量名,比如 c,我总是在函数顶部定义它。这是我添加行注释的两种情况之一,例如 c // 连接b // 字节t // 临时工作缓冲区...现在我的所有代码都更容易阅读了!我的哲学是,代码需要按逻辑段落格式化!就像普通文本一样!在复杂注释的顶部,我也会放一个简单的行注释... - 71GA
这个注释看起来像一个标签,例如 // A:, // B:, // C:... 我正在使用 vim/nvim,所以当我遇到不理解的段落时,我会将鼠标悬停在这个注释/标签上,并按下组合键 ALTGR + 3 (^),这样我就可以得到这个相同标签的前一个位置,而这个位置在函数顶部,有一些以 // A:, // B:, // C:...为前缀的段落解释。这个方法非常完美!我只使用多行注释来注释掉代码,而不是用于注释或函数解释!如果我需要注释单行代码,我使用 /// - 71GA
因此,代码保持紧凑,所有解释都被收集在函数的顶部,并且只需使用组合键即可轻松访问! - 71GA

3
我有一个疑惑:您计划为新项目创建新的命名约定。通常,您应该拥有公司或团队范围内的命名约定。如果您已经有任何形式的命名约定的项目,则不应更改新项目的约定。如果上述约定只是对您现有实践的编码,则很好。它与现有的事实上标准差异越大,从而在新标准中获得关注度的难度就越大。
关于唯一的建议是,我喜欢在类型结尾处使用_t的风格,例如uint32_t和size_t。这对我来说非常C-ish,尽管有人可能会抱怨它只是“反向”匈牙利式。

4
这里的惯例千差万别,不一致,这就是我要记录其中一个惯例的原因,同时也是我提出问题的原因,想了解社区共识是什么。 - JeffV
我理解那种痛苦。但是你现有的规范中一定有一些最受欢迎的子集。你应该从那里开始,而不是在随机的互联网页面上寻找。此外,你应该询问其他开发人员他们认为什么是好的。 - jmucchiello
7
我相信以 _t 结尾的类型名称是 POSIX 标准保留的。 - caf
4
以“_t”结尾的名称被保留。请参见http://www.gnu.org/software/libc/manual/html_node/Reserved-Names.html,“以‘_t’结尾的名称被保留用于附加类型名称。” - Étienne

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接