为什么在C ++类中的成员变量上使用前缀


150

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

  • 公共成员的m_ memberName(完全使用公共成员)
  • _ 私人成员或所有成员的memberName

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

以我的经验,大多数较大的代码库都无法始终如一地应用这些规则。

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

这个约定是今天在C ++中还是有用的还是仅仅是过时的。特别是因为跨库使用不一致。其他语言没有表明没有成员前缀也可以做到吗?


15
我喜欢 在复杂的代码库中,重要的是要知道哪些var是本地的,哪些不是。我通常在强制使用this->之前使用前缀,我发现这是很多额外的类型输入和可选操作(而命名将迫使您执行此操作)
Joe

5
您从未在Ruby中看到过它,因为@ for属性,以及习惯于直接使用属性而不是直接生成属性的生成器。
史蒂夫·杰索普

6
根据PEP 8,Python中,非公共成员变量必须带有下划线前缀(例如:)self._something = 1
内森·奥斯曼

2
不应该使用编辑器的语法突出显示来识别这些吗?
也是

2
您确实已经看到了this->memberPython代码中的等效代码。在Python中,通常会是self.member并且不仅是约定,而且语言也需要它。
matec '16

Answers:


48

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

_Foo

_L

都是保留字,而

_foo

_l

不是。在其他情况下,不允许在小写字母前加下划线。在我的特定情况下,我发现_L恰好被Visual C ++ 2005保留,并且冲突产生了一些意外的结果。

我对标记局部变量很有用。

以下是有关保留哪些标识符的链接:在C ++标识符中使用下划线的规则什么?


4
实际上,_foo和_l都是在名称空间范围内保留的。

13
但是它们可以作为成员变量名称。我不会在下划线前加上前缀,因为规则太混乱了,过去我已经很烦。
胡安2009年

11
这些不是保留字。它们是保留名称。如果它们是保留字,则根本无法使用它们。因为它们是保留名称,所以可以使用它们,但后果自负。
TonyK,2010年

235

我都赞成前缀做得好

我认为(系统)匈牙利符号是造成前缀出现的大多数“不良说唱”的原因。

在强类型语言中,这种表示形式毫无意义,例如在C ++“ lpsz”中,它告诉您您的字符串是一个以nul终止的字符串的长指针,在以下情况下:分段体系结构是古老的历史,C ++字符串通常是以nul终止的指针char数组,知道“ customerName”是一个字符串真的不是那么困难!

但是,我确实使用前缀来指定变量的用法(实际上是“ Apps Hungarian”,尽管我宁愿避免使用 “ Hungarian”一词,因为它与System Hungarian的关联不良且不公平),这非常节省时间,并且减少错误的方法。

我用:

  • m代表成员
  • c代表常数/唯读
  • p代表指针(pp代表指针)
  • v代表volatile
  • s为静态
  • 我为索引和迭代器
  • 事件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;
    }
}

您可以在这里看到:

  • 成员和参数之间没有混淆
  • 索引/迭代器与项目之间没有混淆
  • 使用一组明确相关的变量(项目列表,指针和索引),避免了通用(模糊)名称(例如“ count”,“ index”)的许多陷阱。
  • 与“ 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中的自动完成功能,键入起来也很容易快捷)

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

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

(如果您不认为您会浪费时间在向上或向下搜索工作,请查找您一年前编写的代码,此后一直没有看过。打开文件,然后不读就跳下一半。了解如何在您不知道某个东西是成员,参数还是局部变量之前,您可以从这一点开始阅读很远的信息,现在跳转到另一个随机位置...这就是我们整天仅浏览其他人的代码时所做的事情或尝试了解如何调用其函数)

'm'前缀还避免了(IMHO)丑陋和冗长的“ this->”表示法,并且避免了不一致的情况(即使您小心了,通常也会混入'this-> data'和相同类别中的“数据”,因为没有任何东西可以使名称的拼写保持一致)。

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

最后一个主要好处是使用Intellisense和自动补全功能。尝试在Windows窗体上使用Intellisense查找事件-您必须滚动浏览数百个神秘的基类方法,而这些方法无需调用即可查找事件。但是,如果每个事件都有一个“ e”前缀,它们将自动在“ e”下的组中列出。因此,前缀可以在智能感知列表中对成员,常量,事件等进行分组,从而更快,更轻松地找到所需的名称。(通常,一个方法可能在其范围内可访问约20-50个值(局部变量,参数,成员,常量,事件)。但是在键入前缀后(我现在想使用索引,所以我键入'i。 ..”),系统仅显示2-5个自动完成选项。“额外键入”

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


反对

那么,缺点是什么?反对前缀的典型参数是:

  • “前缀方案是坏的/邪恶的”。我同意,“ m_lpsz”及其类似词经过深思熟虑,完全没有用。因此,我建议您使用设计合理的符号来满足您的要求,而不是复制不适合您的情况的符号。(使用正确的工具进行作业)。

  • “如果更改某项的用法,则必须将其重命名”。是的,您当然知道了,这就是重构的全部内容,也是为什么IDE具有重构工具可以快速,轻松地完成此工作的原因。即使没有前缀,几乎可以肯定的是,更改变量的用法意味着应该更改其名称。

  • “前缀只是让我感到困惑”。在您学习如何使用之前,每个工具都一样。一旦您的大脑习惯了命名模式,它就会自动过滤掉信息,您根本就不会在乎前缀。但是,在真正变得“流利”之前,必须扎实地使用这样的方案一两个星期。那时很多人都在看旧代码,开始想知道他们如何在没有好的前缀方案的情况下进行管理。

  • “我可以看一下代码来解决这些问题”。是的,但是当您的注意力已经集中在现场时,您不必浪费时间在代码的其他地方或记住每个细节。

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

  • “更多的打字”。真?还有一个完整的角色?还是它-使用IDE自动补全工具,通常会减少键入,因为每个前缀字符都会大大缩小搜索空间。按“ e”,班级中的三个事件将以智能感知方式弹出。按“ c”,将列出五个常数。

  • “我可以使用this->代替m。好吧,可以。但这只是一个更加丑陋和冗长的前缀!只有它带来更大的风险(尤其是在团队中),因为对于编译器来说,它是可选的,因此其用法经常不一致。m另一方面,它是简短,清晰,明确和非可选的,因此使用它来犯错误要困难得多。


6
我的意思是说,匈牙利语符号的问题仅仅是由于西蒙尼被误解了。他写了一个前缀来表示变量的类型,其中他的意思是“类型”,就像“事物”中那样,而不是文字数据类型。后来,Microsoft的平台人员拿起它并提出了lpsz ...,剩下的就是历史了……
VoidPointer

19
对我来说,“ s是静态的”听起来很像匈牙利语的“坏”形式。
jalf

6
@Mehrdad:我认为z在像C ++这样的语言中,这类低级实现细节应该封装在一个类中并不是很有用,但是在C(零终止是一个重要的区别)中,我同意你的看法。IMO我们使用的任何方案都应根据需要进行调整,以最适合我们自己的需要-因此,如果零终止影响您对变量的使用,则声明“ z”为有用的前缀没有错。
杰森·威廉姆斯

14
The most important case is "p" for pointer (because the usage changes from var. to var-> and you have to be much more careful with pointers.我全心全意不同意。如果我使用了错误的指针,它将根本无法编译(不过void*可能是双指针的例外)。整个->结论.足以告诉我这是一个指针。另外,如果您使用自动完成功能,则编辑器可能具有声明工具提示,从而无需为变量信息添加前缀。无论如何,好的答案。
Thomas Eding 2012年

5
赞成进行清晰,全面和有趣的解释,但是在这里很少有人建议如何节省C ++时间。在许多其他语言中,YET仍未使用。

115

我通常不为成员变量使用前缀。

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

这就是我现在使用的。也就是说,当存在歧义时,我会添加this->前缀,但通常不存在歧义,并且我可以直接引用变量名。

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

当然,明显的反驳是“是的,但是一眼就看不到变量是否是类成员”。

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

在实践中,我发现这非常有效。另外,它使我可以轻松地将局部变量提升为类成员(或反之),而不必重命名。

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


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

该标准保留所有以双下划线或下划线大写字母开头的名称。它还保留全局名称空间中以单个下划线开头的所有名称。

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

因此,避免使用下划线会更容易。如果要在变量名称中对作用域进行编码,请使用后缀下划线,或者使用a m_或只是m前缀。


“因此,带下划线前加小写字母的班级成员是合法的,但迟早要对以大写字母开头的标识符执行相同的操作,否则将违反上述规则之一。” -类成员变量不在全局名称空间中,因此前导下划线是安全的,无论其后面是小写还是大写字母。
mbarnett 2011年

3
@mbarnett:不,下划线后跟大写字母通常会保留,而不仅仅是保留全局名称空间中。
jalf

9
惊讶的是,这个答案的投票少于前缀一。
Marson Mao

我同意这个答案,this->如果您需要指定它的成员变量,也可以使用它。
David Morton

而且,您不必记录您的约定即可将代码提供给其他人。每个人都明白什么this->意思。
Caduchon

34

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

class Foo
{
   private:
      int bar_;

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

我也是。我还给访问者/更改者相同的名称。
罗布

4
有趣。首先看起来有点难看,但是我可以看到它是如何带来好处的。
09年

6
我会说它比“ mBar”或“ m_bar”丑陋得多。
sydan 2015年

6
但是后来你有了vector<int> v_;,写作v_.push_back(5)也很丑陋
avim

4
那就是Google C ++风格。
Justme0

20

最近,我倾向于使用m_前缀而不是根本没有前缀,原因并不是很多,以至于它对标记成员变量很重要,但是它避免了歧义,比如说您有如下代码:

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

原因是行不通的,只foo允许一个原因。因此,您的选择是:

  • this->foo = foo;

    我不喜欢它,因为它会导致参数屏蔽,您不再可以使用g++ -Wshadow警告,然后也不能再输入m_。当您使用a int foo;和a 时,仍然会遇到变量和函数之间的命名冲突int foo();

  • foo = foo_; 要么 foo = arg_foo;

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

  • m_foo = foo;

    API文档保持整洁,您不会在成员函数和变量之间产生歧义,其类型也要短于this->。唯一的缺点是它使POD结构难看,但由于POD结构最初没有名称歧义,因此无需将其与它们一起使用。具有唯一的前缀也使一些搜索和替换操作更加容易。

  • foo_ = foo;

    应用程序的大多数优点m_,但是出于美学原因,我拒绝使用它,尾随或开头的下划线只会使变量看起来不完整且不平衡。m_看起来更好。使用m_还可以扩展,因为您可以将其g_用于全局变量和s_静态变量。

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


1
公平地说,您错过了至少两个其他选项,例如(a)foo仅对成员使用全名,而对参数或其他本地/丢弃对象使用单字母或简称,例如int f;或(b)在参数或其他本地名称前加上一些前缀。很好的点子m_和豆荚; 在大多数情况下,我已经独立选择遵循这两个准则。
underscore_d

12

阅读成员函数时,了解谁“拥有”每个变量对于理解变量的含义绝对至关重要。在这样的函数中:

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

...很容易看到苹果和香蕉的来源,但是葡萄,瓜和土豆呢?我们应该看看全局名称空间吗?在类声明中?变量是该对象的成员还是该对象的类的成员?如果不知道这些问题的答案,您将无法理解代码。并且在更长的函数中,即使是局部变量(如苹果和香蕉)的声明也可能在混洗中丢失。

在全局变量,成员变量和静态成员变量(可能分别为g_,m_和s_)之前添加一致的标签可立即弄清情况。

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

这些可能一开始需要一些时间来适应-但是,编程中没有什么呢?有一天甚至{和}对您来说都很奇怪。一旦您习惯了它们,它们就会帮助您更快地理解代码。

(在m_处使用“ this->”是有意义的,但更复杂且在视觉上具有破坏性。我认为它不是标记成员变量的所有使用的理想选择。)

对上述参数的一个可能的反对意见是将参数扩展为类型。知道变量的类型“对理解变量的含义绝对是必不可少的”也可能是真的。如果是这样,为什么不为每个标识其类型的变量名添加前缀呢?按照这种逻辑,您最终会得到匈牙利符号。但是许多人发现匈牙利记述法费力,丑陋且无济于事。

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”)这样的变量的丑陋性却很棒。所以,


感谢您的s_想法。似乎非常有用,而且某种程度上我从未发生过。
克里斯·奥尔森

10

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

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

这是我使用的唯一前缀形式(我非常反对匈牙利表示法),但是多年来,它一直使我处于稳定状态。顺便说一句,我通常不赞成在名称中使用下划线(或在其他任何地方使用下划线),但是对于预处理器宏名称,确实会例外,因为它们通常都是大写的。


5
使用骆驼而不是m_(或_)而不是m_的问题在于当前的驼峰情况,这使得很难读取某些变量名。
马丁·贝克特

1
@尼尔我和你在一起。@mgb:我讨厌以“ _”开头的名字,这只是将来发生错误的邀请。
马丁·约克

1
@Neil:如果您不使用下划线,也不使用驼峰式大写字母,那么您会使用哪种约定?
jalf

2
我的理解是,正是camelCase使“ apData”之类的变量仅使用m令人困惑-它变成了“ mapData”而不是“ m_apData”。我使用_camelCase来保护/私有成员变量,因为它很引人注目
Martin Beckett

10
@MartinBeckett:a在这种情况下,您应该使用大写字母- 否则您的做法不正确。mApDatam前缀,则变量名称为apData)。
白金Azure

8

成员前缀的主要原因是要区分局部的成员函数和具有相同名称的成员变量。如果您将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()或以某种方式修饰成员变量。


1
这就是我加前缀的原因。我认为foo.name()比我认为可读性强得多foo.get_name()
Terrabits

6

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

我发现这样的规则有趣的唯一一点是,当我需要两个名称相同的东西时,例如:

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

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

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


6

一些回应着眼于重构而不是命名约定,以此来提高可读性。我不认为一个可以替代另一个。

我知道有些程序员对使用局部声明不满意。他们喜欢将所有声明放在块的顶部(如C所示),因此他们知道在哪里可以找到它们。我发现,在范围允许的情况下,在首次使用变量的地方声明变量可以减少我花时间回头寻找声明的时间。(即使是小的函数,这对我也是这样。)这使我更容易理解正在查看的代码。

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

我确定我并不是一开始就喜欢这些样式。但是随着时间的流逝,在持续使用它们的环境中工作,我优化了想法以利用它们。我认为,如果一贯使用,目前对他们感到不舒服的许多人也可能会更喜欢它们。


5

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


“在诸如公共和私人成员之间的事物之间”-这真的有多普遍?我不记得看到过它,但是话又说回来,我不去审查代码库或其他内容。
underscore_d

我没有在自己的编码中进行此操作,但我曾在必须根据其代码约定指南进行操作的地方工作。我宁愿不这样做,因为几乎所有的IDE都会以不同的颜色显示私有变量。
Will先生

嗯,我想这只会发生在与我不同的情况下。通常,我使用class所有成员都是private/的es protected或使用struct所有变量都是public(通常也是const)的POD 。因此,我无需怀疑任何给定成员的访问级别。
underscore_d

5

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

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

因此,是的,我认为前缀仍然有用。我希望使用“ _”而不是“ this->”来访问成员。


3
编译器仍然可以解决它...在大多数语言中,局部变量将在较高范围内隐藏它们。这是为了人类阅读代码的(可疑)利益。任何体面的IDE都会以不同的方式突出显示本地人/成员/全局人,因此不需要这种东西
rmeador

1
究竟。当地人将隐藏班级成员。考虑一个设置这些成员的构造函数。通常,将参数命名为与成员相同是有意义的。
肯特·布加亚特

6
为什么会有代码气味?我想说这是完全常见和合理的,尤其是在构造函数方面。
肯特·布加阿特

3
构造函数应(通常)在其初始化列表中设置局部变量。在这里,参数不会遮盖字段名称,但是两者都可以访问-因此您可以编写struct Foo { int x; Foo(int x) : x(x) { ... } };
帕维尔Minaev

2
我认为问题在于当您Foo(int x, bool blee) : x(x) { if (blee) x += bleecount; } // oops, forgot this->愿意将成员变量称为有用的东西,然后提供与其缩写名称匹配的构造函数参数时:Foo(int f) : foo(f) {...}
Steve Jessop

4

其他语言将使用编码约定,但它们往往有所不同。例如,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前缀,因为在此之前加上了用法。指示编译器强制方法中的相同内容。

这并不一定意味着其他方法不好,人们会坚持可行。


3

当您使用大型方法或代码块时,可以方便地立即知道是使用局部变量还是成员。这是为了避免错误并提高清晰度!


3
如果您使用的是大型方法,则为了获得更好的清晰度,请对其进行分解。
09年

4
有很多原因不打破一些大方法。例如,如果您的方法需要保留很多本地状态,则要么必须将大量参数传递到您的从属方法中,要么创建仅用于在这些方法之间传递数据的新类,或者将状态数据声明为父类的成员数据。与单一的冗长的方法(尤其是其逻辑简单的方法)相比,所有这些方法都存在会影响该方法的清晰度或可维护性的问题。
史蒂夫·布罗伯格

3
@sbi:指导方针就是这样;准则,而不是规则。有时您需要大型方法,这些方法在逻辑上不适合于拆分,有时参数名称与成员冲突。
Ed S.

请不要公开您的成员变量。只需使用访问器。括号应告诉读者这是一个成员变量。
jkeys

请注意,gcc(> = 4.6)中有一个警告,可检测到名称冲突:-Wshadow
Caduchon

3

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

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


3

正如其他人已经说过的那样,重要性是要通俗易懂(对要编写的代码库采用适当的命名方式和约定)并保持一致。

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

我对命名非常留恋。我将为归属于类或变量的名称感到痛苦,以至于如果我无法提出自己认为“良好”的名称,我将选择将其命名为无意义的名称,并提供描述其真正含义的注释。是。这样,至少该名称确切地代表了我的意图,仅此而已。通常,在使用了一段时间之后,我发现了名称的真实含义,并且可以回头对其进行适当的修改或重构。

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


3

C ++成员变量上的前缀的最初想法是存储编译器不知道的其他类型信息。因此,例如,您可能有一个固定长度的chars字符串,另一个可变且以'\ 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”表示char指针。换句话说,它们正在复制编译器知道的内容,并使代码难以同时阅读。我认为,这种有害做法应被法规取缔,并受到严厉的处罚。

最后,我要提到两点:

  • 明智地使用C ++功能可以使编译器知道您必须在原始C样式变量中编码的信息。您可以创建仅允许有效操作的类。这应该尽可能实际地完成。
  • 如果你的代码块是这么久,你忘了一个变量的类型是你使用它之前,他们的方式太长。不要使用名称,而要重新组织。

表示变量类型或种类的前缀也值得讨论,但我主要是指表示某项是否是(私有)成员/字段的前缀。当您聪明地应用匈牙利反向符号时(如您的示例),它可能非常方便。我最喜欢的有意义的示例是相对坐标和绝对坐标。当您看到absX = relX时,您可以清楚地看到某些问题。您也可以相应地命名函数:absX = absFromRel(relX,offset);
VoidPointer

注意:aszFred的初始化是有问题的(提供对文字字符串的非常量访问),而arrWilma的初始化甚至不会编译。(您可能打算将arrWilma声明为数组,而不是指针!)但是,没问题,因为您写道它就在您的头上……:-)
Niels Dekker,2009年

糟糕,您绝对正确。孩子们,不要在家尝试。这样做:'const char * aszFred =“嗨,我是一个以空字符结尾的字符串”; char arrWilma [] = {'O','o','p','s'};'
AL Flanagan

3

我们的项目始终使用“它”作为成员数据的前缀,并使用“ the”作为参数的前缀,而本地变量则没有前缀。它有点可爱,但是它被我们系统的早期开发人员采用,因为他们看到当时我们正在使用的一些商业源代码库(XVT或RogueWave-可能两者)都将它用作惯例。因此,您将获得如下内容:

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

我看到对前缀进行范围划分的主要原因(没有其他前缀-我讨厌匈牙利符号)是因为它可以防止编写代码,使您陷入麻烦,因为您认为自己是在引用一个变量,但是实际上是在引用另一个变量在本地范围内定义的相同名称。这也避免了使用变量名来表示相同概念但作用域不同的问题,例如上面的示例。在这种情况下,无论如何,您都必须为参数“ theName”提供一些前缀或其他名称-为什么不制定适用于所有地方的一致规则。

仅仅使用this->还不够好-我们对减少歧义性的兴趣不如减少编码错误的兴趣小,用本地范围的标识符屏蔽名称可能会很麻烦。当然,某些编译器可以选择在较大范围内屏蔽了该名称的情况下引发警告,但是如果您使用的是恰好选择了大量第三方库的那些警告,则这些警告可能会变得很麻烦。偶尔与您自己的变量冲突的未使用变量的名称。

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


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

2

我之所以使用它,是因为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;
}

2

我认为,如果需要前缀来区分类成员与成员函数参数和局部变量,则函数太大或变量命名错误。如果它不适合屏幕显示,那么您可以轻松地看到是什么,进行重构。

考虑到它们经常被声明为远离使用它们的地方,因此我发现全局常数(和全局变量,尽管IMO几乎不需要使用它们)的命名约定是有意义的。但除此之外,我认为没有太多需要。

就是说,我以前在所有私人班级成员的末尾都加了下划线。由于我所有的数据都是私有数据,因此这意味着成员的后缀是下划线。我通常不会在新的代码库中这样做,但是由于作为一名程序员,您主要使用旧代码,所以我仍然会做很多事情。我不确定我对这种习惯的宽容是因为我曾经一直这样做并且仍会定期这样做,还是真的比标记成员变量更有意义。


2
这非常反映了我对这个问题的看法。代码应该可读而不用前缀。也许我们在更现代的语言中没有看到太多的前缀使用,因为他们的用户社区所接受的可读性比您在C ++中有时看到的更多。当然,C ++可以而且应该可读。多年以来,仅编写了许多不可读的C ++。
VoidPointer 2009年


1

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


为何如此?析构函数无法访问在其他函数中声明的局部变量,因此那里没有混淆的空间。此外,堆分配的局部变量不应该存在。而且,堆分配的成员变量应该只存在于RAII类中。
jalf

“堆分配的局部变量不应该存在”有点强。但是如果/当您使用它们时,确保它们被完全释放非常重要,因此针对成员变量和局部变量的严格命名约定有助于确保这一点。
弗兰克斯特

1

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

尽管我从没想到m_表示法有用,但我会在建立标准时赋予McConnell的观点权重。


2
除非他解释下划线的原因,否则不会这样。我是他的“快速开发”书的忠实拥护者,在这里我曾多次推荐过这本书,但对“代码完成”的关注要少得多(我承认,自从首次出版以来我就没有读过)。

1

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

我唯一使用前缀的时间是在签名行上。我会在参数的前面加上_,以便可以围绕它们进行防御性编程。


1

您永远不需要这样的前缀。如果这样的前缀为您提供了任何优势,则通常需要修正您的编码样式,而并不是使您的代码不清楚的前缀。典型的错误变量名称包括“其他”或“ 2”。您不能通过要求它成为其他来解决该问题,而是通过让开发人员考虑该变量在该函数的上下文中在做什么而解决它。也许他的意思是remoteSide或newValue或secondTestListener或该范围内的东西。

这是一个有效的过时现象,至今仍然传播过头。停止为变量加上前缀,并给它们起适当的名称,其清晰程度反映了它们的使用时间。最多可以将5行称为“ i”,而不会引起混淆;超过50行,您需要一个很长的名字。


1

我喜欢变量名只给它们包含的值一个含义,而不必在名称中声明/实现变量。我想知道值的含义,周期。也许我所做的重构工作不仅仅超过平均水平,但我发现以名称的形式嵌入实现方式使得重构比需要的更加繁琐。指示在何处或如何声明对象成员的前缀是特定于实现的。

color = Red;

大多数时候,我不在乎Red是枚举,结构还是其他东西,并且如果函数太大,以至于我不记得是否color是在本地声明的或是成员的,那么可能是时候打破了将功能分解为较小的逻辑单元。

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

通常,我仅在构造函数和初始化程序中使用“ this”。


0

我使用m_作为成员变量只是为了利用Intellisense和相关的IDE功能。在对类的实现进行编码时,我可以键入m_并看到组合了所有m_成员的组合框。

但是,我当然可以没有m_地生活。这只是我的工作风格。


您也可以输入this->
Toast

0

根据《联合攻击战斗机C ++编码标准》(2005年12月):

AV规则67

公共数据和受保护数据应仅在结构中使用,而不能在类中使用。基本原理:类可以通过控制对其数据的访问来保持其不变性。但是,如果某个类是非私有成员,则该类无法控制对其成员的访问。因此,一个类中的所有数据都应该是私有的。

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

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


0

这些约定中的许多约定都是从没有经验丰富的编辑者的时代起。我建议使用适当的IDE,该IDE可以为每种变量着色。到目前为止,颜色比任何前缀都更容易发现。

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.