匈牙利标记法是在命名变量是在变量名的前面加上表示变量类型或是作用域的前缀。
1: int value; // non-Hungarian
2: int nValue; // the n prefix denotes an integer
3: double width; // non-Hungarian
4: double dWidth; // the d prefix denotes a double
关于匈牙利标记法在现代程序语言与现代IEDs中的用处有很大的争议。我们相信它的有点依然超过缺点,尽管我们能够找到很多的反对。
一个好处是匈牙利标记法可以通过变量的名字知道这个变量的类型。很多人承认这是一个废弃的优点,因为现在很多的IDE将鼠标置于变量名上,便能够获知变量的类型。但是看一下下面的代码:
1: float applesPerPerson = totalApples / totalPersons;
随意的浏览代码,这个语句很可能不会引起你的注意。但是,这很有可能导致错误。如果totalApples和totalPersons都是整型,编译器在计算 totalApples / totalPersons 使用整型相除,导致小数部分将会丢失。如,totalApples = 5,totalPersons = 3,applesPerPerson的结果将会是1,而不是1.66!
如果使用匈牙利标记法
1: float fApplesPerPerson = nTotalApples / nTotalPersons;
从n前缀的提示我们就能够看出该语句是存在问题的。而且,n前缀也能够提醒你整型相除与溢出的问题。
匈牙利命名法的另一个优点是,让我们能够使用缩写的方法来进行命名。如,bError可以理解成isError,nApples可以理解成numberOfApples。
匈牙利标记法一个被普遍认为的问题是当改变一个变量的类型的时候导致的额外的工作。比如:当你想处理小数部分的时候,想将一个整型的变量改变成double型的变量。如果你没有使用匈牙利标记法,你只要将int改成double就行了,但是如果你采用了匈牙利标记法,并不能单单将int改变成double,你还得将nValue改变成dValue!如果没有及时改变,你的名字将会误导你。
虽然将大量的变量名改掉是一件很烦人的事情,但是我们相信它仍然是好的。因为不同的类型具有不同的行为,通过改变变量的名字你能够检查你的代码,确保新类型下并没有出现任何不适的危险。
如,不使用匈牙利标记法:
1: if (value == 0)
2: // do something
当一个变量改变成double时,你的比较将是不安全的了。当出现问题的时候,你不得不花时间进行调试,如果这个bug被忽视的话,可能会产生更多的问题。
但是如果你使用匈牙利标记法:
1: if (nValue == 0)
2: // do something
1: if (dValue == 0.0)
2: // do something
当看到这时,你也许会说“Hey, wait a second, I shouldn’t be doing naked comparisons with floating point values!”。然后你会进行一定的修正然后再继续执行
(译者注:比较两个浮点型是否相等,更好的使用两者的差值在一个范围内视为相等的吧?
1: const double maxLow = 1e-10;
2: // use [fabs(dValue) < maxLow] instead of [dValue == 0.0]
3: if (fabs(dValue) < maxLow)
4: // do something
)
传统上匈牙利标记法的缺点是组合类型的前缀数量会是人困惑.Wikipedia提供了一个例子: “a_crszkvc30LastNameCol : a constant reference function argument, holding contents of a database column of type varchar(30) called LastName that was part of the table’s primary key”.这样是你的代码变得不容易理解。
Caste Hungarian
不同的团体使用不同的匈牙利命名体系。尽管一些事相同的,但是很多还是有很多有所区别。
我们为每个数据类型使用不同的前缀是过度的(overkill),尤其对与struct和class。而且长的匈牙利命名会适得其反。因此,我们提倡是使用简单的匈牙利命名体系,叫做:caste Hungarian。
变量的前缀由三个部分组成:范围修饰,类型修饰,类型前缀。前两部分也许是不适用的。因此,全部的前缀长度都是合理的,平均前缀长度在2个字符左右。这个体系包含了很多匈牙利标记法的优点,并且将很多缺点排了出去,使得整个体系简单,容易。
Type prefix | Meaning | Example |
---|---|---|
b | boolean | bool bHasEffect; |
c (or none*) | class | Creature cMonster; |
ch | char (used as a char) | char chLetterGrade; |
d | double, long double | double dPi; |
e | enum | Color eColor; |
f | float | float fPercent; |
n | short, int, long char used as an integer |
int nValue; |
s | struct | Rectangle sRect; |
str | C++ string | std::string strName; |
sz | Null-terminated string | char szName[20]; |
The following type modifiers are placed before the prefix if they apply:
Type modifier | Meaning | Example |
---|---|---|
a | array on stack | int anValue[10]; |
p | pointer | int* pnValue; |
pa | dynamic array | int* panValue = new int[10]; |
r | reference | int rnValue; |
u | unsigned | unsigned int unValue; |
The following scope modifiers are placed before the type modifier if they apply:
Scope modifier | Meaning | Example |
---|---|---|
g_ | global variable | int g_nGlobalValue; |
m_ | member of class | int m_nMemberValue; |
s_ | static member of class | int s_nValue; |
一些注意:
1. 这些距离并不是详尽的,它包含了大部分情况。如果你觉得不同类型变量值得具有它自己的前缀,自己赋一个
2. 使用具有具体含义的变量名,能够很好的分别它们。这在struct和class中尤为重要。如Rectangle sWindowRect比Rectangle sWindow要好的多
3. char被用作ASCII字符和integer时,有不同的前缀,能够分清它的用途。
4. float和double具有不同的前缀。
5. typedefs 不适合这个体系
6. 当一个变量是class的引用或指针时,‘c'是被禁用的。
7. 因为整型变量没有被很好的区分,你很容易将一个大的整型变量改变成小的变量类型,而没有改变名字。但是,这样可能会引起溢出的问题。
1: int nIndex; // simple integer type prefix
2: int* pnIndex; // a pointer to an integer
3: int m_nIndex; // an integer variable that is a member of a class
4: int* m_pnIndex; // an pointer to an integer variable that is a member of a class