命名约定 - C++和C#变量中的下划线

191

在类字段中经常看到_var变量名,下划线是什么意思?是否有关于所有这些特殊命名约定的参考资料?


3
可能是在C++标识符中使用下划线的规则是什么?的重复问题。 - R Samuel Klatchko
20
部分不恰当的企业编码准则建议在成员变量中添加“疣”,以区分它们与局部变量。这是因为有一种信念,认为类和函数必然会变得过于臃肿,你没有模糊的线索,就无法追踪其中的区别。建议采用这样的约定的人往往是那些最需要它们的人。 - Mike Seymour
28
@Mike - 你的概括性陈述并不准确。我发现使用这种约定可以更轻松地扫描方法并对类有一个良好的理解。 - ChaosPandion
18
在C++中,避免使用前置下划线。在很多情况下(例如在全局作用域中,在大写字母后面等),前置下划线被保留给实现,并且您实际上可能会有一些宏覆盖它们。因此,如果您需要使用下划线,请改为使用尾随下划线。 - sbi
@MikeSeymour 这太猛了,哈哈。 - Fantastic Mr Fox
显示剩余4条评论
19个回答

142

下划线仅仅是一种惯例,没有其他意义。因此,其使用方式对每个人来说都有些不同。以下是我对两种编程语言的理解:

在 C++ 中,下划线通常表示私有成员变量。

在 C# 中,我通常只看到它被用于为公共属性定义底层私有成员变量。其他私有成员变量则不会带有下划线。然而,随着自动属性的出现,这种用法已经大大减少了。

之前:

private string _name;
public string Name
{
    get { return this._name; }
    set { this._name = value; }
}

之后:

public string Name { get; set; }

12
除了那些希望使其属性不可变的团队外。 - ChaosPandion
16
以下划线开头的标识符是保留给编译器的。使用下划线前缀 可能会 与编译器符号冲突。 - Thomas Matthews
12
@Thomas Matthews - 不是用C#。 - EMP
13
我假设你所说的“不可变”的意思实际上是“只读”,如果是这样,那么可以使用public string Name { get; private set; }。确实,它并不完全是不可变的,但已经足够了。 - jdmichal
9
在课堂环境中,以下划线开头且后面紧跟一个大写字母的标识符在C++中是保留的。但_var并不是保留的。 - Tyler McHenry
显示剩余3条评论

118

在C++中最好不要在任何变量名或参数名之前使用下划线

以下划线或双下划线开头的名称是保留给C++实现者的。带下划线的名称被保留供库使用。

如果您查看C++编程标准,您将看到在第一页上它说:

"不要过度立法命名,但是要使用一致的命名约定:只有两个必须做的事情:a) 永远不要使用“欺诈性名称”,即以下划线开头或包含双下划线的名称;" (p2, C++编程规范,Herb Sutter和Andrei Alexandrescu)

更具体地,ISO工作草案规定了实际规则:(链接)

此外,某些标识符为C++实现所保留,并且不应用于其他用途;无需进行诊断。(a)每个包含双下划线__或以下划线后跟大写字母开头的标识符都保留供实现使用。 (b)以下划线开头的每个标识符都保留供实现在全局命名空间中用作名称。

最好不要以下划线开头一个符号,以防意外进入上述限制之一。

您可以自己看到为什么在开发软件时这种下划线的使用可能会灾难性:

尝试编译像这样的简单helloWorld.cpp程序:

g++ -E helloWorld.cpp
你将看到背景中发生的一切。这是一个片段:
   ios_base::iostate __err = ios_base::iostate(ios_base::goodbit);
   try
     {
       __streambuf_type* __sb = this->rdbuf();
       if (__sb)
  {
    if (__sb->pubsync() == -1)
      __err |= ios_base::badbit;
    else
      __ret = 0;
  }

你可以看到有多少名称以双下划线开头!

另外,如果你查看虚成员函数,你会发现 *_vptr 是为虚表生成的指针,当你在类中使用一个或多个虚成员函数时,它会自动创建!但这是另外一个故事...

如果你使用下划线,你可能会遇到冲突问题,而且直到太晚才知道导致这个问题的原因。


9
不以全局或文件范围命名并以下划线和小写字母开头的名称不是完全可以使用吗?我经常这样做,因为对于特定情况非常清晰,例如:void A :: set_size(int _size){size = _size;}。你在像这样琐碎的用途中怎么做呢? - tukra
7
非全局/文件作用域下,可以使用下划线后跟小写字母。我认为标准中没有说不允许这样做。下划线后缀的问题在于有时候我已经用它作为成员变量了。因此,我可能会有一个函数'int size();'、成员变量'int size_';和参数'int _size'。这种方式通常很好地涵盖了各种情况,而且我还没有看到更好的替代方案。对于与STL一起工作的泛型,无法使用大写函数名。C#人喜欢的'this.size'风格对我来说似乎容易出错,除非你总是将其用于成员访问,但那样很丑陋。 - tukra
11
我理解为什么有些人不想使用下划线和小写字母,因为他们可能会难以确定哪些是合法的。尽管有些作者建议不要使用它,但C++标准允许使用它。对我来说,既然它完全合法且未被保留,我不认为有任何理由考虑它是否存在风险。感谢您的反馈。 - tukra
3
@tukra说:“这并不是完全合法的。C++标准仅允许您在局部范围内使用一个下划线后跟一个小写字母。祝好运! :-)” - Constantinos Glynos
7
抱歉,我以为我们已经过了需要重复资格的阶段。在除文件作用域之外的每个作用域中,以下划线开头并后跟小写字母的C++标识符是允许的。在函数、函数原型、类、结构体、联合体、枚举以及命名空间内部,它是安全和合法的。如果你遵循将所有代码放在命名空间中的常规做法,那么你会完全安全。 - tukra
显示剩余10条评论

52

实际上,_var 这种约定来自 VB,而不是 C# 或 C++(m_... 是另一回事)。

这是为了解决在声明属性时 VB 的大小写不敏感性问题。

例如,在 VB 中不可能编写如下代码,因为它会将 userUser 视为相同的标识符。

Private user As String

Public Property User As String
  Get
    Return user
  End Get
  Set(ByVal Value As String)
    user = value
  End Set
End Property

因此,为了克服这个问题,一些人使用了一个惯例,在私有字段前添加“_”来表示:

Private _user As String

Public Property User As String
  Get
    Return _user
  End Get
  Set(ByVal Value As String)
    _user = value
  End Set
End Property

由于许多惯例适用于 .Net,并为了在 C# 和 VB.NET 之间保持一些统一性,它们使用相同的命名约定。

我找到了我所说的参考资料:

http://10rem.net/articles/net-naming-conventions-and-programming-standards---best-practices

以首字母小写的驼峰式命名法。在 VB.NET 中,永远要指示“Protected”或“Private”,不要使用“Dim”。不建议使用“m_”,也不建议使用只有大小写区别的变量名,特别是在使用受保护的变量时,这会违反兼容性并使您的编程生涯备受痛苦,因为您必须为成员命名与访问器/变量名不同的内容。在所有项目中,下划线开头实际上是唯一具有争议的一个。个人而言,我更喜欢它,因为对于我的私有变量,我不必用“this.”限定变量名来区分构造函数或其他地方的参数,这样做是很重要的,因为 VB.NET 是不区分大小写的,因此您的访问器属性通常与您的私有成员变量具有相同的名称,除了下划线。至于“m_”,这真的只是关于美学方面的问题。我(和许多其他人)认为 m_ 很丑,因为它看起来像变量名中有一个洞。这几乎令人反感。我过去经常在 VB6 中使用它,但那仅仅是因为变量无法以下划线开头。我很高兴看到它消失了。Microsoft 建议不要使用 m_(也不要使用下划线),尽管他们在他们的代码中都使用了这两种方式。同时,以直接“m”作为前缀是行不通的。当然,由于他们主要使用 C# 编码,所以可以拥有仅在属性上区分大小写的私有成员。VB 用户必须做一些其他的事情。与其试图提出适用于每种语言的特殊情况,我建议对所有支持它的语言使用下划线作为前缀。如果我想让我的类完全符合 CLS,我可以在任何 C# 受保护的成员变量上省略前缀。不过,在实践中,我从不担心这个问题,因为我将所有潜在的受保护成员变量都保持为私有,并提供受保护的访问器和变量。为什么:简而言之,这个约定很简单(只有一个字符),易于阅读(您的眼睛不会被其他开头的字符分散注意力),并成功避免了过程级变量和类级属性之间的命名冲突。


11

_var没有实际意义,只是为了更容易地区分变量为私有成员变量。

在C++中,使用_var惯例是不好的形式,因为前面带下划线的标识符有规则。_var被保留作为全局标识符,而_Var(下划线+大写字母)则随时保留。这就是为什么在C++中,你会看到人们使用var_惯例。


6
第一个评论者(R Samuel Klatchko)提到了:C++标识符中使用下划线的规则是什么?,它回答了有关C++中下划线的问题。一般来说,您不应该使用前导下划线,因为它被保留给编译器的实现者。您看到的带有_var的代码可能是遗留代码,或者是由于成长环境使用旧的命名系统而编写的代码,这个系统不反对前导下划线。

正如其他答案所述,它曾经用于标识C++类成员变量。然而,就装饰器或语法而言,它没有特殊的含义。因此,如果您想使用它,它将被编译。

至于C#讨论,我会留给其他人。


4
你可以创建自己的编码规范。只需为团队编写清晰的文档。
使用 _field 可以帮助 Intelilsense 通过输入 _ 来过滤所有类变量。
我通常遵循 Brad Adams Guidelines,但它建议不要使用下划线。

3
使用 C#,Microsoft 框架设计准则 建议不使用下划线字符作为公共成员的标识符。对于私有成员,可以使用下划线。实际上,Jeffrey Richter(通常在准则中被引用)为实例使用 m_,为私有静态成员使用“s_”。
个人而言,我只使用下划线来标记我的私有成员。“m_”和“s_”属于匈牙利命名法,它不仅在.NET中不受欢迎,而且可能非常冗长。我发现具有许多成员的类在按字母顺序快速浏览时会很困难(想象一下10个变量都以“m_”开头)。

由于匈牙利命名法表示类型,我认为你不能真正说m/s接近它。 - Jerry Nixon
@JerryNixon-MSFT 当涉及到匈牙利标记法时,我们是否将变量数据类型和变量可见性类型视为相同的东西? - Necro
那么你是不是只用下划线 _ 来区分静态成员和实例成员呢? - Rekshino

3

旧问题,新答案(C#)。

C#中下划线的另一个用法是与ASP NET Core的DI(依赖注入)一起使用。类的私有readonly变量在构造时被分配给注入的接口,应该以下划线开头。我想这是否对于类的每个私有成员都使用下划线存在争议(尽管Microsoft本身遵循此规则),但这个是确定的。

private readonly ILogger<MyService> _logger;

public MyService(ILogger<MyService> logger)
{
    _logger = logger;
}

编辑:

微软现在已经采用下划线来表示类的所有私有成员。


2
微软的C#命名标准要求变量和参数使用小驼峰形式,例如:paramName。该标准还要求字段遵循同样的形式,但这可能会导致代码不清晰,因此许多团队要求在字段前添加下划线前缀以提高可读性,例如:_fieldName。

1

在C#中使用它有完全合法的理由:如果代码还需要从VB.NET进行扩展。(否则,我不会这样做。)

由于VB.NET不区分大小写,因此没有简单的方法可以访问此代码中受保护的field成员:

public class CSharpClass
{
    protected int field;
    public int Field { get { return field; } }
}

例如,这将访问属性的getter,而不是字段:

Public Class VBClass
    Inherits CSharpClass

    Function Test() As Integer
        Return Field
    End Function

End Class

天哪,我甚至不能把 field 写成小写字母 - VS 2010 一直在更正它。

为了使其在 VB.NET 的派生类中易于访问,必须想出另一种命名约定。在前面添加下划线可能是最不侵入性且最“历史接受”的方法。


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