【问题标题】:Why use prefixes on member variables in C++ classes为什么在 C++ 类的成员变量上使用前缀
【发布时间】:2010-11-16 17:20:29
【问题描述】:

许多 C++ 代码使用语法约定来标记成员变量。常见的例子包括

  • m_memberName 用于公共成员(完全使用公共成员)
  • _memberName 用于私人成员或所有成员

其他人试图在使用成员变量时强制使用 this->member

根据我的经验,大多数大型代码库无法始终如一地应用此类规则。

在其他语言中,这些约定远没有那么普遍。我只是偶尔在 Java 或 C# 代码中看到它。我想我从未在 Ruby 或 Python 代码中看到过它。因此,更现代的语言似乎有一种趋势,即不对成员变量使用特殊标记。

这个约定在今天的 C++ 中是否仍然有用,或者它只是一个不合时宜的东西。特别是因为它在库中的使用如此不一致。其他语言不是表明没有成员前缀也可以吗?

【问题讨论】:

  • 我更喜欢它;在复杂的代码库中,了解哪些 var 是本地的,哪些不是本地的可能很重要。我通常使用前缀而不是强制 this-> 我发现这是很多额外的输入和可选的(而命名会迫使你这样做)
  • 你在 Ruby 中从未见过它,因为 @for 属性,以及生成访问器而不是直接使用属性的习惯用法。
  • 根据PEP 8,非公共成员变量在Python中要加下划线前缀(例如:self._something = 1)。
  • 不应该使用编辑器的语法高亮来识别这些吗?
  • 您确实已经在 Python 代码中看到了相当于 this->member 的内容。在 Python 中,它通常是 self.member,它不仅是一种约定,而且是语言所要求的。

标签: c++ coding-style naming-conventions


【解决方案1】:

我完全赞成做好前缀

我认为(系统)匈牙利符号是造成前缀获得的大部分“坏名声”的原因。

这种表示法在强类型语言中基本上没有意义,例如在 C++ "lpsz" 中告诉你你的字符串是一个指向 nul 终止字符串的长指针,当:分段架构是古老的历史,C++ 字符串按照惯例是指向 nul 终止字符数组的指针,这并不是那么困难要知道“customerName”是一个字符串!

但是,我确实使用前缀来指定变量的用法(本质上是“Apps Hungarian”,尽管我更喜欢避免使用 Hungarian 这个术语,因为它与 System Hungarian 有不良和不公平的关联),这是一种非常方便的省时减少错误的方法。

我用:

  • 会员米
  • c 用于常量/只读
  • p 代表指针(pp 代表指向指针的指针)
  • v 表示易失性
  • s 用于静态
  • i 用于索引和迭代器
  • e 用于事件

在我希望明确类型的地方,我使用标准后缀(例如 List、ComboBox 等)。

这使程序员在看到/使用变量时就知道变量的用法。可以说最重要的情况是指针的“p”(因为用法从 var. 更改为 var-> 并且您必须更加小心指针 - NULL、指针算术等),但所有其他情况都非常方便。

例如,您可以在单个函数中以多种方式使用相同的变量名:(这里是 C++ 示例,但它同样适用于多种语言)

MyClass::MyClass(int numItems)
{
    mNumItems = numItems;
    for (int iItem = 0; iItem < mNumItems; iItem++)
    {
        Item *pItem = new Item();
        itemList[iItem] = pItem;
    }
}

你可以在这里看到:

  • 成员和参数之间没有混淆
  • 索引/迭代器和项目之间没有混淆
  • 使用一组明确相关的变量(项目列表、指针和索引),避免“计数”、“索引”等通用(模糊)名称的许多缺陷。
  • 与“itemIndex”和“itemPtr”等替代方法相比,前缀减少了输入(更短,并且更适合自动完成)

“iName”迭代器的另一个优点是我从不使用错误索引对数组进行索引,如果我将循环复制到另一个循环中,我不必重构循环索引​​变量之一。

比较这个不切实际的简单例子:

for (int i = 0; i < 100; i++)
    for (int j = 0; j < 5; j++)
        list[i].score += other[j].score;

(这很难阅读,并且经常导致在意为“j”的地方使用“i”)

与:

for (int iCompany = 0; iCompany < numCompanies; iCompany++)
    for (int iUser = 0; iUser < numUsers; iUser++)
       companyList[iCompany].score += userList[iUser].score;

(更具可读性,并且消除了对索引的所有混淆。在现代 IDE 中具有自动完成功能,这也可以快速轻松地键入)

下一个好处是代码 sn-ps 不需要任何上下文即可理解。我可以将两行代码复制到电子邮件或文档中,任何阅读该 sn-p 的人都可以分辨所有成员、常量、指针、索引等之间的区别。我不必添加“哦,并且是小心,因为 'data' 是指向指针的指针”,因为它被称为 'ppData'。

出于同样的原因,我不必为了理解它而将视线从一行代码上移开。我不必搜索代码来查找“数据”是本地、参数、成员还是常量。我不必将手移到鼠标上,因此我可以将指针悬停在“数据”上,然后等待工具提示(有时从未出现)弹出。因此,程序员可以显着更快地阅读和理解代码,因为他们不会浪费时间上下搜索或等待。

(如果您认为自己不会浪费时间上下搜索来解决问题,请查找一些您一年前编写但尚未查看的代码 自从。打开文件并向下跳转大约一半而不读取它。 在你不知道之前,看看你能从这一点读多远 某事是成员、参数或本地。现在跳到另一个随机 位置......这是我们单身时整天都在做的事情 单步执行别人的代码或试图了解如何 调用他们的函数)

“m”前缀还避免了(恕我直言)丑陋和冗长的“this->”符号,以及它所保证的不一致(即使你很小心,你通常最终也会混合使用 'this-> data' 和 'data' 在同一个类中,因为没有强制名称的拼写一致)。

'this' 表示法旨在解决 歧义 - 但是为什么有人会故意编写可能有歧义的代码呢?歧义迟早会导致错误。在某些语言中,“this”不能用于静态成员,因此您必须在编码风格中引入“特殊情况”。我更喜欢有一个适用于任何地方的单一简单编码规则 - 明确、明确和一致。

最后一个主要好处是 Intellisense 和自动完成功能。尝试在 Windows 窗体上使用 Intellisense 来查找事件 - 您必须滚动浏览数百个您永远不需要调用来查找事件的神秘基类方法。但是,如果每个事件都有一个“e”前缀,它们将自动列在“e”下的组中。因此,前缀可以对智能感知列表中的成员、常量、事件等进行分组,从而更快、更容易地找到所需的名称。 (通常,一个方法可能有大约 20-50 个值(locals、params、members、consts、events)可以在其范围内访问。但是在输入前缀之后(我现在想使用索引,所以我输入 'i. ..'),我只看到了 2-5 个自动完成选项。将“额外输入”的人归因于前缀和有意义的名称大大减少了搜索空间并显着加快了开发速度)

我是一个懒惰的程序员,上面的约定为我节省了很多工作。我可以更快地编写代码,而且我犯的错误也少得多,因为我知道应该如何使用每个变量。


反对意见

那么,有什么缺点呢?反对前缀的典型论据是:

  • “前缀方案是坏的/邪恶的”。我同意“m_lpsz”及其同类产品经过深思熟虑并且完全没用。这就是为什么我建议使用设计良好的符号来支持您的要求,而不是复制不适合您的上下文的内容。 (为工作使用正确的工具)。

  • “如果我改变了某些东西的用法,我必须重命名它”。是的,你当然知道,这就是重构的全部意义所在,以及为什么 IDE 有重构工具可以快速、轻松地完成这项工作。即使没有前缀,更改变量的用法几乎肯定意味着应该更改其名称。

  • “前缀让我很困惑”。就像每个工具一样,直到你学会如何使用它。一旦你的大脑习惯了命名模式,它就会自动过滤掉信息,你不会真的介意前缀的存在。但是你必须在你真正变得“流利”之前牢固地使用这样的方案一两个星期。这就是很多人看到旧代码并开始怀疑他们是如何管理没有一个好的前缀方案的时候。

  • “我可以看看代码来解决这个问题”。是的,但是当答案就在您的注意力已经集中的位置时,您无需浪费时间查看代码的其他地方或记住其中的每一个小细节。

  • (部分)可以通过等待工具提示在我的变量上弹出来找到该信息。是的。在支持的情况下,对于某些类型的前缀,当您的代码干净地编译时,等待之后,您可以通读描述并找到前缀将立即传达的信息。我觉得前缀是一种更简单、更可靠、更高效的方法。

  • “打字更多”。真的吗?还要一整个角色?或者是 - 使用 IDE 自动完成工具,它通常会减少输入,因为每个前缀字符都会显着缩小搜索空间。按“e”,您班级中的三个事件会以智能方式弹出。按“c”,列出五个常量。

  • “我可以使用this-&gt; 代替m。嗯,是的,你可以。但这只是一个更丑陋、更冗长的前缀!只有它会带来更大的风险(尤其是在团队中),因为对于编译器来说它是可选,因此它的使用经常是不一致的。另一方面,m 简短、清晰、明确且不是可选的,因此使用它更难出错。

【讨论】:

  • 我的意思是读到匈牙利符号的问题只是由于西蒙尼被误解了。他写了一个前缀应该用来表示一个变量的类型,他的意思是“类型”,比如“某种事物”而不是文字数据类型。后来微软的平台人员把它捡起来并想出了 lpsz……剩下的就是历史了……
  • "s is for static" 在我看来很像匈牙利语的“坏”形式。
  • @Mehrdad:我不认为z 在像 C++ 这样的语言中经常有用,因为这种低级实现细节应该封装在一个类中,但是在 C 中(其中零-终止是一个重要的区别)我同意你的看法。 IMO 我们使用的任何方案都应根据需要进行调整以最适合我们自己的需求 - 因此,如果零终止会影响您对变量的使用,那么将“z”声明为有用的前缀并没有错。
  • The most important case is "p" for pointer (because the usage changes from var. to var-&gt; and you have to be much more careful with pointers. 我完全不同意。如果我使用错误的指针,它根本不会编译(void* 可能是双指针的例外)。整个-&gt; 超过. 足以告诉我它是一个指针。此外,如果您使用自动完成,您的编辑器可能有声明工具提示,无需为变量信息添加前缀。无论如何,很好的答案。
  • 赞成清晰、全面和有趣的解释,但是这里几乎没有说明这如何在 C++ 中节省时间 YET 在许多其他语言中仍然很大程度上未使用。
【解决方案2】:

我一般不为成员变量使用前缀。

我曾经使用m 前缀,直到有人指出“C++ 已经有一个用于成员访问的标准前缀:this-&gt;

所以这就是我现在使用的。也就是有歧义的时候我加了this-&gt;前缀,但是一般情况下是没有歧义的,直接引用变量名就可以了。

对我来说,这是两全其美的。我有一个前缀,我可以在需要时使用它,并且我可以随时将其省略。

当然,明显的反击是“是的,但是你不能一眼看出变量是否是类成员”。

对此我说“那又怎样?如果你需要知道这一点,你的类可能有太多的状态。或者函数太大太复杂”。

在实践中,我发现这非常有效。作为一个额外的好处,它允许我轻松地将局部变量提升为类成员(或相反),而无需重命名它。

最重要的是,它是一致的!我不需要做任何特别的事情或记住任何惯例来保持一致性。


顺便说一句,您不应该为您的班级成员使用前导下划线。您会很不自在地接近实现保留的名称。

标准保留所有以双下划线或下划线后跟大写字母开头的名称。它还在全局命名空间中保留所有以下划线开头的名称​​

因此,具有前导下划线后跟小写字母的类成员是合法的,但迟早您将对以大写开头的标识符执行相同操作,否则会违反上述规则之一.

因此,避免前导下划线会更容易。如果要在变量名中编码范围,请使用后缀下划线或 m_ 或仅使用 m 前缀。

【讨论】:

  • "所以类成员的前导下划线后跟一个小写字母是合法的,但迟早你会对以大写开头的标识符做同样的事情,否则违反上述规则之一。” -- 类成员变量不在全局命名空间中,因此前导下划线是安全的,无论其后跟小写字母还是大写字母。
  • @mbarnett:不,下划线后跟大写是保留的一般情况下,而不仅仅是在全局命名空间中。
  • 惊讶于这个答案的票数小于前缀一。
  • 我同意这个答案,如果您需要指定它的成员变量,请使用this-&gt;,或者不要,这也很好。
  • 此外,您不必记录您的约定即可将您的代码提供给其他人。大家都明白this-&gt;是什么意思。
【解决方案3】:

您必须小心使用前导下划线。保留单词中大写字母前的前导下划线。 例如:

_Foo

_L

都是保留字,而

_foo

_l

不是。在其他情况下,不允许在小写字母前使用前导下划线。在我的具体情况下,我发现 _L 恰好被 Visual C++ 2005 保留,冲突产生了一些意想不到的结果。

我不知道标记局部变量有多大用处。

这是一个关于保留哪些标识符的链接: What are the rules about using an underscore in a C++ identifier?

【讨论】:

  • 其实 _foo 和 _l 都保留在命名空间范围内。
  • 但是它们可以作为成员变量名。我不前缀下划线,因为规则太混乱,我过去被烧毁了。
  • 这些不是保留字。它们是保留名称。如果它们是保留字,则根本无法使用它们。因为它们是保留名称,所以您可以使用它们,但风险自负。
【解决方案4】:

我更喜欢后缀下划线,比如:

class Foo
{
   private:
      int bar_;

   public:
      int bar() { return bar_; }
};

【讨论】:

  • 我也是。我也给访问器/突变器取了相同的名字。
  • 有趣。一开始看起来有点难看,但我可以看到它有什么好处。
  • 我会说它比“mBar”或“m_bar”丑多了。
  • 但是你有vector&lt;int&gt; v_;,写v_.push_back(5)也很丑
  • 那是 Google C++ 风格。
【解决方案5】:

最近我更喜欢 m_ 前缀而不是根本没有前缀,原因并不是标记成员变量很重要,而是它避免了歧义,比如你有这样的代码:

void set_foo(int foo) { foo = foo; }

原因不起作用,只允许一个foo。所以你的选择是:

  • this-&gt;foo = foo;

    我不喜欢它,因为它会导致参数阴影,你不能再使用g++ -Wshadow 警告,它也比m_ 输入更长。当您有 int foo;int foo(); 时,您仍然会遇到变量和函数之间的命名冲突。

  • foo = foo_;foo = arg_foo;

    已经使用了一段时间,但它使参数列表变得丑陋,文档不应该处理实现中的名称歧义。这里也存在变量和函数的命名冲突。

  • m_foo = foo;

    API 文档保持简洁,成员函数和变量之间不会有歧义,并且键入起来比 this-&gt; 更短。唯一的缺点是它使 POD 结构变得丑陋,但由于 POD 结构一开始就不会受到名称歧义的影响,因此不需要与它们一起使用。拥有唯一的前缀还可以让一些搜索和替换操作更容易。

  • foo_ = foo;

    m_ 的大部分优点都适用,但出于美学原因我拒绝它,尾随或前导下划线只会使变量看起来不完整且不平衡。 m_ 看起来更好。使用m_ 也更具可扩展性,因为您可以将g_ 用于全局变量,将s_ 用于静态变量。

PS:您在 Python 或 Ruby 中看不到 m_ 的原因是因为两种语言都强制使用自己的前缀,Ruby 使用 @ 作为成员变量,而 Python 需要 self.

【讨论】:

  • 公平地说,您至少错过了 2 个其他选项,例如(a) 仅对成员使用像 foo 这样的全名,而对参数或其他本地/一次性使用单字母或短名称,例如 int f;或 (b) 在 parameters 或其他本地人前面加上一些东西。不过,关于m_ 和豆荚的好点;在大多数情况下,我已经独立地倾向于遵循这两个准则。
  • @underscore_d 参数名称是类的公共接口的一部分。那应该是您添加奇怪命名约定的最后一个地方。此外,单字母变量名称很糟糕,应该不惜一切代价避免使用非常少数例外(i 在循环中)。
【解决方案6】:

在通读成员函数时,知道谁“拥有”每个变量对于理解变量的含义是绝对必要的。在这样的函数中:

void Foo::bar( int apples )
{
    int bananas = apples + grapes;
    melons = grapes * bananas;
    spuds += melons;
}

...很容易看出苹果和香蕉的来源,但是葡萄、甜瓜和土豆呢?我们应该查看全局命名空间吗?在类声明中?变量是该对象的成员还是该对象的类的成员?不知道这些问题的答案,就无法理解代码。而在更长的函数中,即使是像 apples 和bananas 这样的局部变量的声明也可能会在 shuffle 中丢失。

为全局变量、成员变量和静态成员变量(可能分别为 g_、m_ 和 s_)添加一致的标签可以立即阐明情况。

void Foo::bar( int apples )
{
    int bananas = apples + g_grapes;
    m_melons = g_grapes * bananas;
    s_spuds += m_melons;
}

一开始这些可能需要一些时间来适应——但是,在编程中什么不是呢?曾经有一天,你甚至觉得 { 和 } 都很奇怪。一旦你习惯了它们,它们会帮助你更快地理解代码。

(使用 "this->" 代替 m_ 是有道理的,但更冗长且视觉上具有破坏性。我不认为它是标记所有成员变量使用的好选择。)

对上述论点的一个可能的反对意见是将论点扩展到类型。知道变量的类型“对于理解变量的含义是绝对必要的”也可能是真的。如果是这样,为什么不给每个变量名添加一个前缀来标识它的类型呢?使用这种逻辑,您最终会使用匈牙利符号。但很多人觉得匈牙利符号费力、丑陋且无用。

void Foo::bar( int iApples )
{
    int iBananas = iApples + g_fGrapes;
    m_fMelons = g_fGrapes * iBananas;
    s_dSpuds += m_fMelons;
}

匈牙利语确实告诉我们一些关于代码的新内容。我们现在明白了 Foo::bar() 函数中有几个隐式类型转换。现在代码的问题是匈牙利前缀添加的信息的价值相对于视觉成本来说很小。 C++ 类型系统包含许多功能,可帮助类型很好地协同工作或引发编译器警告或错误。编译器帮助我们处理类型——我们不需要符号来这样做。我们可以很容易地推断出 Foo::bar() 中的变量可能是数字的,如果这就是我们所知道的,那对于获得对该函数的一般理解来说已经足够了。因此,知道每个变量的精确类型的价值相对较低。然而,像“s_dSpuds”(或什至只是“dSpuds”)这样的变量的丑陋是非常好的。因此,成本收益分析拒绝使用匈牙利符号,而 g_、s_ 和 m_ 的收益在许多程序员眼中超过了成本。

【讨论】:

  • 感谢 s_ 的想法。似乎很有用,但不知怎的,我从来没有想过。
【解决方案7】:

我不能说它有多广泛,但就我个人而言,我总是(并且一直)在我的成员变量前面加上“m”。例如:

class Person {
   .... 
   private:
       std::string mName;
};

这是我使用的唯一前缀形式(我非常反对匈牙利符号),但多年来它一直让我受益匪浅。顺便说一句,我通常讨厌在名称(或其他任何地方)中使用下划线,但对预处理器宏名称例外,因为它们通常都是大写的。

【讨论】:

  • 使用 m 而不是 m_(或 _)的问题在于当前的驼峰式大小写方式使得某些变量名难以阅读。
  • @Neil 我和你在一起。 @mgb:我讨厌以 '_' 开头的名字,这只是对未来出错的邀请。
  • @Neil:如果您不使用下划线,也不使用驼峰式,那么您使用哪种约定?
  • 我的理解是camelCase使得只使用m来表示'apData'之类的变量令人困惑——它变成了'mapData'而不是'm_apData'。我将 _camelCase 用于受保护/私有成员变量,因为它很突出
  • @MartinBeckett:在那种情况下,你应该将a 大写——否则你就没有做对。 mApDatam前缀,那么变量名就是apData)。
【解决方案8】:

成员前缀主要是为了区分成员函数local和同名成员变量。如果您使用带有事物名称的 getter,这将很有用。

考虑:

class person
{
public:
    person(const std::string& full_name)
        : full_name_(full_name)
    {}

    const std::string& full_name() const { return full_name_; }
private:
    std::string full_name_;
};

在这种情况下,成员变量不能称为 full_name。您需要将成员函数重命名为 get_full_name() 或以某种方式装饰成员变量。

【讨论】:

  • 这就是我加前缀的原因。我认为foo.name()foo.get_name() 更具可读性。
【解决方案9】:

我不认为一种语法比另一种更有价值。正如您所提到的,这一切都归结为源文件的一致性。

我觉得这些规则有趣的唯一一点是当我需要两个命名相同的东西时,例如:

void myFunc(int index){
  this->index = index;
}

void myFunc(int index){
  m_index = index;
}

我用它来区分两者。此外,当我包装调用时,例如来自 Windows Dll 的调用,来自 Dll 的 RecvPacket(...) 可能会包装在我的代码中的 RecvPacket(...) 中。在这些特殊情况下,使用像“_”这样的前缀可能会使两者看起来很相似,很容易识别哪个是哪个,但对于编译器来说却不同

【讨论】:

    【解决方案10】:

    一些响应侧重于重构,而不是命名约定,以此来提高可读性。我不觉得一个可以替代另一个。

    我认识一些不喜欢使用局部声明的程序员;他们更喜欢将所有声明放在块的顶部(如在 C 中),因此他们知道在哪里可以找到它们。我发现,在作用域允许的情况下,在首次使用它们的位置声明变量会减少我向后看以查找声明的时间。 (对我来说即使是小函数也是如此。)这让我更容易理解我正在查看的代码。

    我希望这与成员命名约定的关系足够清楚:当成员统一前缀时,我根本不必回头;我知道该声明甚至不会在源文件中找到。

    我确定我一开始并不是更喜欢这些样式。然而,随着时间的推移,在始终使用它们的环境中工作,我优化了自己的思维以利用它们。我认为,如果一直使用它们,许多目前对它们感到不舒服的人也可能会更喜欢它们。

    【讨论】:

      【解决方案11】:

      这些约定就是这样。大多数商店都使用代码约定来简化代码的可读性,因此任何人都可以轻松查看一段代码并快速解读公共成员和私人成员等内容。

      【讨论】:

      • “公共成员和私人成员之间” - 这真的很常见吗?我不记得看过它,但话又说回来,我不会到处查看代码库或任何东西。
      • 我不会在我自己的编码中这样做,但我曾在我们必须根据他们的代码约定指南这样做的地方工作。我宁愿不这样做,因为几乎所有 IDE 都会以不同的颜色显示私有变量。
      • 嗯,我猜它只发生在与我不同的情况下。通常我使用classes,其所有成员都是private/protected,或POD structs,所有变量都是public(通常还有const)。所以,我永远不需要怀疑任何给定成员的访问级别。
      【解决方案12】:

      其他人试图强制使用 this->member 每当一个成员 使用变量

      那通常是因为没有前缀。编译器需要足够的信息来解析有问题的变量,无论是由于前缀的唯一名称,还是通过 this 关键字。

      所以,是的,我认为前缀仍然有用。一方面,我更愿意输入“_”来访问成员,而不是“this->”。

      【讨论】:

      • 编译器无论如何都可以解决它......在大多数语言中,局部变量将隐藏在更高范围内的变量。这是为了人类阅读代码的(可疑)利益。任何体面的 IDE 都会以不同的方式突出显示本地/成员/全局,因此不需要这种东西
      • 没错。当地人会隐藏班级成员。考虑一个设置这些成员的构造函数。通常将参数命名为与成员相同的名称是有意义的。
      • 为什么会有代码味道?我想说这是非常普遍和合理的,尤其是在构造函数方面。
      • 构造函数应该(通常)在其初始化列表中设置局部变量。在那里,参数不会影响字段名称,但两者都可以访问 - 所以你可以写struct Foo { int x; Foo(int x) : x(x) { ... } };
      • 我认为当你这样做时会出现问题Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this-&gt; 我更喜欢调用我的成员变量一些有用的东西,然后给与它们匹配的构造函数参数缩写名称:Foo(int f) : foo(f) {...}
      【解决方案13】:

      其他语言将使用编码约定,只是它们往往有所不同。例如,C# 可能有两种人们倾向于使用的不同风格,一种是 C++ 方法(_variable、mVariable 或其他前缀,如匈牙利表示法),另一种是我所说的 StyleCop 方法。

      private int privateMember;
      public int PublicMember;
      
      public int Function(int parameter)
      {
        // StyleCop enforces using this. for class members.
        this.privateMember = parameter;
      }
      

      最终,它变成了人们所知道的,并且看起来最好的。我个人认为没有匈牙利符号的代码更具可读性,但是如果附加了匈牙利符号,则使用智能感知查找变量会变得更容易。

      在我上面的示例中,您不需要为成员变量添加 m 前缀,因为在您的用法中添加了这个前缀。在编译器强制执行的方法中表示相同的东西。

      这并不一定意味着其他方法不好,人们坚持有效的方法。

      【讨论】:

        【解决方案14】:

        当你有一个大的方法或代码块时,很方便立即知道你是使用局部变量还是成员。这是为了避免错误并提高清晰度!

        【讨论】:

        • 如果你有一个大方法,为了更清楚,把它分解。
        • 有很多理由不分解一些大方法。例如,如果您的方法需要保留大量本地状态,您要么必须将大量参数传递到您的从属方法中,要么创建仅用于在这些方法之间传递数据的新类,或者将状态数据声明为父类的成员数据。与单个冗长的方法(尤其是逻辑简单的方法)相比,所有这些都存在会影响方法的清晰度或可维护性的问题。
        • @sbi:指南就是这样;指导方针,而不是规则。有时您需要在逻辑上不适合拆分的大型方法,有时参数名称会与成员发生冲突。
        • 请不要公开您的成员变量。只需使用访问器。括号应该告诉读者它是一个成员变量。
        • 请注意,在 gcc (>= 4.6) 中有一个警告以检测名称冲突:-Wshadow
        【解决方案15】:

        IMO,这是个人的。我根本没有添加任何前缀。无论如何,如果代码是公开的,我认为它最好有一些前缀,这样它可以更具可读性。

        大公司经常使用它自己的所谓“开发者规则”。
        顺便说一句,我看到的最有趣但最聪明的是 DRY KISS(不要重复自己。保持简单,愚蠢)。 :-)

        【讨论】:

          【解决方案16】:

          正如其他人已经说过的,重要的是口语化(根据您编写的代码库调整命名样式和约定)并保持一致。

          多年来,我一直致力于使用“this->”约定以及对成员变量使用后缀下划线表示法的大型代码库。多年来,我还从事过一些较小的项目,其中一些没有任何命名成员变量的约定,而另一些则有不同的命名成员变量的约定。在那些较小的项目中,我一直发现那些缺乏任何约定的项目最难快速投入和理解。

          我对命名非常保守。我会为归于类或变量的名称而苦恼,以至于如果我不能想出我认为“好”的东西,我会选择将其命名为无意义的名称,并提供描述其真正含义的评论是。那样的话,至少这个名字的意思正是我想要的意思——不多也不少。并且经常,在使用了一段时间后,我会发现真正应该是什么名字,并且可以返回并适当地修改或重构。

          最后一点是关于 IDE 执行工作的主题——这一切都很好,但 IDE 通常在我执行最紧急工作的环境中不可用。有时,此时唯一可用的就是“vi”的副本。此外,我还看到很多情况下 IDE 代码完成会传播愚蠢,例如名称拼写错误。因此,我宁愿不必依赖 IDE 拐杖。

          【讨论】:

            【解决方案17】:

            C++ 成员变量前缀的最初想法是存储编译器不知道的附加类型信息。例如,您可以有一个固定长度的字符的字符串,另一个是可变的并以“\0”结尾。对于编译器来说,它们都是char *,但是如果你试图从一个复制到另一个,你会遇到很大的麻烦。所以,在我的脑海中,

            char *aszFred = "Hi I'm a null-terminated string";
            char *arrWilma = {'O', 'o', 'p', 's'};

            其中“asz”表示此变量为“ascii 字符串(以零结尾),“arr”表示此变量为字符数组。

            然后魔法发生了。编译器会对这个语句非常满意:

            strcpy(arrWilma, aszFred);

            但作为人类,你可以看着它说“嘿,那些变量不是真正的同一类型,我不能那样做”。

            不幸的是,很多地方都使用标准,例如“m_”代表成员变量,“i”代表整数(无论如何使用),“cp”代表字符指针。换句话说,他们在复制编译器所知道的内容,同时使代码难以阅读。我认为这种有害的做法应该被法律禁止并受到严厉的惩罚。

            最后,我要提两点:

            • 明智地使用 C++ 功能使编译器能够了解您必须在原始 C 样式变量中编码的信息。您可以创建只允许有效操作的类。应尽可能做到这一点。
            • 如果您的代码块太长以至于您在使用变量之前忘记了它是什么类型,那么它们方式 太长了。不要使用名称,重新组织。

            【讨论】:

            • 表示变量类型或种类的前缀也值得讨论,但我主要指的是表示某事物是否为(私有)成员/字段的前缀。您提到的反向匈牙利符号在智能应用时会非常方便(如您的示例中)。我最喜欢的有意义的例子是相对坐标和绝对坐标。当您看到 absX = relX 时,您可以清楚地看到可能有问题。您也可以相应地命名函数:absX = absFromRel(relX, offset);
            • 注意:aszFred 的初始化是有问题的(提供对文字字符串的非常量访问),并且 arrWilma 的初始化甚至无法编译。 (您可能打算将 arrWilma 声明为数组,而不是指针!)不过没问题,正如您所写的那样,它只是在您的脑海中...... :-)
            • 糟糕,你说得对。孩子们,不要在家里尝试。这样做:'const char *aszFred = "嗨,我是一个以空字符结尾的字符串"; char arrWilma[] = {'O', 'o', 'p', 's'};'
            【解决方案18】:

            我们的项目一直使用“its”作为成员数据的前缀,“the”作为参数的前缀,本地没有前缀。它有点可爱,但它被我们系统的早期开发人员采用,因为他们认为它被我们当时使用的一些商业源库(XVT 或 RogueWave - 可能两者兼有)用作约定。所以你会得到这样的东西:

            void
            MyClass::SetName(const RWCString &theName)
            {
               itsName = theName;
            }
            

            我看到范围前缀(没有其他——我讨厌匈牙利表示法)的一个重要原因是,它可以防止你在编写代码时遇到麻烦,因为你认为你指的是一个变量,但实际上你指的是一个变量到本地范围中定义的另一个同名变量。它还避免了使用变量名称来表示相同概念但具有不同范围的问题,如上面的示例。在这种情况下,无论如何,您都必须为参数“theName”提供一些前缀或不同的名称 - 为什么不制定一个适用于所有地方的一致规则。

            仅使用 this-> 还不够好 - 我们对减少歧义的兴趣不如减少编码错误的兴趣,并且使用本地范围的标识符屏蔽名称可能会很痛苦。诚然,某些编译器可能会选择在您在更大范围内掩盖名称的情况下发出警告,但如果您正在使用碰巧选择的大量第三方库,这些警告可能会变得令人讨厌偶尔与您自己的冲突的未使用变量的名称。

            至于 its/the 本身 - 老实说,我发现它比下划线更容易打字(作为一名触控打字员,我尽可能避免使用下划线 - 过多地延伸主行),而且我发现它比神秘的更具可读性下划线。

            【讨论】:

            • 这是我听过的最直观的解决方案,学习曲线最快。我希望口语能够更灵活地处理所有这些问题,这样我们就不必考虑提出新技术来解决代码中的歧义。
            【解决方案19】:

            我使用它是因为 VC++ 的 Intellisense 无法判断在访问类外时何时显示私有成员。唯一的指示是 Intellisense 列表中字段图标上的一个小“锁定”符号。它只是更容易识别私有成员(字段)。老实说,这也是 C# 的一种习惯。

            class Person {
               std::string m_Name;
            public:
               std::string Name() { return m_Name; }
               void SetName(std::string name) { m_Name = name; }
            };
            
            int main() {
              Person *p = new Person();
              p->Name(); // valid
              p->m_Name; // invalid, compiler throws error. but intellisense doesn't know this..
              return 1;
            }
            

            【讨论】:

              【解决方案20】:

              我认为,如果你需要前缀来区分类成员与成员函数参数和局部变量,要么函数太大,要么变量命名错误。如果它不适合屏幕以便您可以轻松查看是什么,请重构。

              鉴于它们经常被声明为远离使用它们的地方,我发现全局常量(和全局变量,尽管 IMO 很少需要使用它们)的命名约定是有意义的。但除此之外,我认为没有太大必要。

              也就是说,我曾经在所有私有类成员的末尾加上下划线。由于我所有的数据都是私有的,这意味着成员有一个尾随下划线。我通常不再在新代码库中这样做了,但是由于作为一名程序员,你主要使用旧代码,所以我仍然经常这样做。我不确定我对这种习惯的容忍度是否来自于我过去总是这样做并且仍然经常这样做的事实,或者它是否真的比标记成员变量更有意义。

              【讨论】:

              • 这非常反映了我对这个问题的感受。代码应该是可读的,而不需要使用前缀。也许我们在更现代的语言中看不到这么多前缀的使用,因为他们的用户社区对可读性的接受程度比你有时在 C++ 中看到的要多。当然,C++ 可以而且应该是可读的。只是这些年写了很多不可读的C++。
              【解决方案21】:

              在 python 中,前导双下划线用于模拟私有成员。更多详情见this answer

              【讨论】:

                【解决方案22】:

                由于内存管理,区分成员变量和局部变量很有用。广义上讲,堆分配的成员变量应该在析构函数中销毁,而堆分配的局部变量应该在该范围内销毁。对成员变量应用命名约定有助于正确的内存管理。

                【讨论】:

                • 怎么回事?析构函数无法访问在其他函数中声明的局部变量,因此这里没有混淆的余地。此外,堆分配的局部变量不应该存在。而且堆分配的成员变量几乎应该只存在于 RAII 类中。
                • “堆分配的局部变量不应该存在”有点强。但是,如果/当您使用它们时,确保它们被正确地释放是非常重要的,因此成员变量与局部变量的严格命名约定有助于确保这一点。
                【解决方案23】:

                Code Complete 建议成员变量使用 m_varname。

                虽然我从未认为 m_ 符号有用,但我会在制定标准时给予 McConnell 的意见权重。

                【讨论】:

                • 除非他解释了为什么下划线。我是他的“快速开发”书的忠实粉丝,我在这里推荐了很多次,但更不喜欢“代码完成”(我承认自从它第一次出版以来我还没有读过)。跨度>
                【解决方案24】:

                我几乎从不在变量名前使用前缀。如果您使用的是足够好的 IDE,您应该能够轻松地重构和查找参考。我使用非常清晰的名称,并且不怕使用长变量名。使用这种哲学,我从来没有遇到过范围问题。

                我唯一一次使用前缀是在签名行。我会在方法的参数前面加上 _,这样我就可以围绕它们进行防御性编程。

                【讨论】:

                  【解决方案25】:

                  您永远不需要这样的前缀。如果这样的前缀为您提供了任何优势,那么您的编码风格通常需要修复,而不是前缀让您的代码不清晰。典型的错误变量名称包括“其他”或“2”。您不能通过要求它是 mOther 来解决这个问题,而是通过让开发人员考虑该变量在该函数的上下文中在那里执行的操作来解决它。也许他的意思是 remoteSide,或者 newValue,或者 secondTestListener 或者那个范围内的东西。

                  这是一种有效的时代错误,但仍然传播得太远。停止为您的变量添加前缀,并为它们提供正确的名称,其清晰性反映了它们的使用时间。最多 5 行,您可以将其称为“i”而不会混淆;超过 50 行你需要一个很长的名字。

                  【讨论】:

                    【解决方案26】:

                    我喜欢变量名称只为它们包含的值赋予含义,而将它们的声明/实现方式放在名称之外。我想知道这个值是什么意思,句号。也许我已经完成了超过平均数量的重构,但我发现在名称中嵌入某些实现方式会使重构变得比需要的更乏味。指示对象成员在何处或如何声明的前缀是特定于实现的。

                    color = Red;
                    

                    大多数时候,我不在乎 Red 是枚举、结构还是其他什么,如果函数太大以至于我不记得 color 是在本地声明的还是成员,它可能是是时候将函数分解为更小的逻辑单元了。

                    如果您的圈复杂度如此之大,以至于如果没有嵌入在事物名称中的特定于实现的线索,您就无法跟踪代码中发生的事情,那么您很可能需要降低函数/方法的复杂性.

                    大多数情况下,我只在构造函数和初始化程序中使用“this”。

                    【讨论】:

                      【解决方案27】:

                      我使用 m_ 作为成员变量只是为了利用 Intellisense 和相关的 IDE 功能。当我编写一个类的实现时,我可以键入 m_ 并查看所有 m_ 成员组合在一起的组合框。

                      当然,没有 m_ 的我也可以毫无问题地生活。这只是我的工作风格。

                      【讨论】:

                      • 你也可以输入this-&gt;
                      【解决方案28】:

                      根据 JOINT STRIKE FIGHTER AIR VEHICLE C++ 编码标准(2005 年 12 月):

                      反病毒规则 67

                      公共和受保护的数据只能用于 结构——不是类。理由:一个类能够保持它的 通过控制对其数据的访问来保持不变。但是,一个类不能 如果这些成员是非私人的,则控制对其成员的访问。因此所有 类中的数据应该是私有的。

                      因此,“m”前缀变得无用,因为所有数据都应该是私有的。

                      但是在指针之前使用 p 前缀是一个好习惯,因为它是一个危险的变量。

                      【讨论】:

                        【解决方案29】:

                        其中许多约定都来自没有成熟编辑的时代。我建议使用适当的 IDE,它允许您为每种变量着色。颜色比任何前缀都更容易识别。

                        如果您需要获取有关变量的更多详细信息,任何现代 IDE 都应该能够通过将插入符号或光标移到该变量上来向您显示它。而且,如果您以错误的方式使用变量(例如带有 . 运算符的指针),无论如何都会出错。

                        【讨论】:

                          猜你喜欢
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2015-01-29
                          • 1970-01-01
                          • 2014-05-09
                          • 1970-01-01
                          • 1970-01-01
                          相关资源
                          最近更新 更多