| 网站首页 | 业界新闻 | 小组 | 威客 | 人才 | 下载频道 | 博客 | 代码贴 | 在线编程 | 编程论坛
欢迎加入我们,一同切磋技术
用户名:   
 
密 码:  
共有 7116 人关注过本帖, 9 人收藏
标题:[分享]c++综合技术应用文章合集^^
只看楼主 加入收藏
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏(9)
 问题点数:0 回复次数:27 
[分享]c++综合技术应用文章合集^^
整理了想各个地方收集的c++综合性文章,分享给广大c++学习者作为参考。将不断更新。

希望支持

目录:

1 关于编程风格的讨论 (2楼)
2 C++代码优化方法 (3楼)
3 Microsoft Visual C++ 浮点优化 (4楼)
4 Effective C++ 的52个条款列表 (5楼)
5 C/C++中的整型常识 (6楼)
6 C++的底层机制 (7楼)
7 C++的中抽象 (8楼)
8 C++实用技巧两则 (9楼)
9 C++之运算符重载 (10楼)
10 C++中函数指针数组的使用 (11楼)
11 C++中结构体的的慨念和使用方法 (12楼)
12 C++中数组和指针类型的关系 (13楼)
13 C++中用函数模板实现和优化抽象操作 (14楼)
14 浅析c/c++中的指针 (15楼)
15 理解复杂的C/C++声明 (16楼)
16 BJ大师防止类被继承的探讨 (25楼)

----------------------------------------------------------------------------
多多顶啊!

[此贴子已经被作者于2007-6-2 0:20:48编辑过]

搜索更多相关主题的帖子: 综合技术 应用 分享 
2007-05-05 11:44
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

关于编程风格的讨论
**软件公司软件开发规范**(这只是其中一类较好的编程规范,可以借鉴)

在公司团队协作开发的情况下,编程时应该强调的一个重要方面是程序的易读性,在保证软件的速度等性能指标能满足用户需求的情况下,能让其他程序员容易读懂你的程序。一套鲜明的编程风格,可以让协作者、后继者和自己一目了然,在很短的时间内看清程序的结构,理解设计的思路。大大的提高代码的可读性、可重用性、程序健壮性、可移植性和可维护性。
制定本编程规范的目的是为了提高公司的软件开发效率及所开发的软件的可维护性,提高软件的质量。本规范由程序风格、命名规则、注释规范、程序健壮性、可移植性、错误处理以及软件的模块化规范等部分组成。

一、程序风格:
1、严格采用阶梯层次组织程序代码:
各层次缩进的分格采用VC的缺省风格,即每层次缩进为4格,括号位于下一行。要求相匹配的大括号在同一列,对继行则要求再缩进4格。例如:
void main()
{
......
long lI; //循环变量
long lSum;//用来记录和
float fAvg;//用来求平均值
......
//对数进行累加。
for( lI=0;lI<10;lI++)
{
lSum=lSum+lI;
...... }
//求平均值。
fAvg=lSum/10.0;
......
}

2、提示信息字符串的位置
在程序中需要给出的提示字符串,为了支持多种语言的开发,除了一些给调试用的临时信息外,其他所有的提示信息必须定义在资源中。
3、对变量的定义,尽量位于函数的开始位置。
二、命名规则:
1、变量名的命名规则
①、变量的命名规则要求用“匈牙利法则”。即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的缩写,尽量避免用中文的拼音,要求单词的第一个字母应大写。
即: 变量名=变量类型+变量的英文意思(或缩写)
对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。
见下表:
bool(BOOL) 用b开头 bIsParent
byte(BYTE) 用by开头 byFlag
short(int) 用n开头 nStepCount
long(LONG) 用l开头 lSum
char(CHAR) 用c开头 cCount
float(FLOAT) 用f开头 fAvg
double(DOUBLE) 用d开头 dDeta
void(VOID) 用v开头 vVariant
unsigned short(WORD) 用w开头 wCount
unsigned long(DWORD) 用dw开头 dwBroad
HANDLE(HINSTANCE) 用h开头 hHandle
DWORD 用dw开头 dwWord
LPCSTR(LPCTSTR) 用str开头 strString
用0结尾的字符串 用sz开头 szFileName

对未给出的变量类型要求提出并给出命名建议给技术委员会。

②、指针变量命名的基本原则为:
对一重指针变量的基本原则为:
“p”+变量类型前缀+命名
如一个float*型应该表示为pfStat
对多重指针变量的基本规则为:
二重指针: “pp”+变量类型前缀+命名
三重指针: “ppp”+变量类型前缀+命名
......
③、全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)
④、静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即: 变量名=s_+变量类型+变量的英文意思(或缩写)
⑤、成员变量用m_开头,如一个长型成员变量定义为m_lCount;即:变量名=m_+变量类型+变量的英文意思(或缩写)
⑥、对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。并且要求用大写。
如:enum cmEMDAYS
{
EMDAYS_MONDAY;
EMDAYS_TUESDAY;
……
};
⑦、对struct、union、class变量的命名要求定义的类型用大写。并要加上前缀,其内部变量的命名规则与变量命名规则一致。
结构一般用S开头
如:struct ScmNPoint
{
int nX;//点的X位置
int nY; //点的Y位置
};
联合体一般用U开头
如: union UcmLPoint
{
long lX;
long lY;
}
类一般用C开头
如:
class CcmFPoint
{
public:
float fPoint;
};
对一般的结构应该定义为类模板,为以后的扩展性考虑
如:
template <class TYPE>
class CcmTVector3d
{
public:
TYPE x,y,z;
};
⑧、对常量(包括错误的编码)命名,要求常量名用大写,常量名用英文表达其意思。
如:#define CM_FILE_NOT_FOUND CMMAKEHR(0X20B) 其中CM表示类别。
⑨、对const 的变量要求在变量的命名规则前加入c_,即:c_+变量命名规则;例如:
const char* c_szFileName;
2、 函数的命名规范:
函数的命名应该尽量用英文表达出函数完成的功能。遵循动宾结构的命名法则,函数名中动词在前,并在命名前加入函数的前缀,函数名的长度不得少于8个字母。
例如:
long cmGetDeviceCount(……);
3、函数参数规范:
①、 参数名称的命名参照变量命名规范。
②、 为了提高程序的运行效率,减少参数占用的堆栈,传递大结构的参数,一律采用指针或引用方式传递。
③、 为了便于其他程序员识别某个指针参数是入口参数还是出口参数,同时便于编译器检查错误,应该在入口参数前加入const标志。如:
……cmCopyString(const char * c_szSource, char * szDest)
4、引出函数规范:
对于从动态库引出作为二次开发函数公开的函数,为了能与其他函数以及Windows的函数区分,采用类别前缀+基本命名规则的方法命名。例如:在对动态库中引出的一个图象编辑的函数定义为 imgFunctionname(其中img为image缩写)。
现给出三种库的命名前缀:
①、 对通用函数库,采用cm为前缀。
②、 对三维函数库,采用vr为前缀。
③、 对图象函数库,采用img为前缀。
对宏定义,结果代码用同样的前缀。
5、文件名(包括动态库、组件、控件、工程文件等)的命名规范:
文件名的命名要求表达出文件的内容,要求文件名的长度不得少于5个字母,严禁使用象file1,myfile之类的文件名。
三、注释规范:
1、函数头的注释
对于函数,应该从“功能”,“参数”,“返回值”、“主要思路”、“调用方法”、“日期”六个方面用如下格式注释:
//程序说明开始
//================================================================//
// 功能: 从一个String 中删除另一个String。
// 参数: strByDelete,strToDelete
// (入口) strByDelete: 被删除的字符串(原来的字符串)
// (出口) strToDelete: 要从上个字符串中删除的字符串。
// 返回: 找到并删除返回1,否则返回0。(对返回值有错误编码的要// 求列出错误编码)。
// 主要思路:本算法主要采用循环比较的方法来从strByDelete中找到
// 与strToDelete相匹配的字符串,对多匹配strByDelete
// 中有多个strToDelete子串)的情况没有处理。请参阅:
// 书名......
// 调用方法:......
// 日期:起始日期,如:2000/8/21.9:40--2000/8/23.21:45
//================================================================//
函数名(……)
//程序说明结束
①、 对于某些函数,其部分参数为传入值,而部分参数为传出值,所以对参数要详细说明该参数是入口参数,还是出口参数,对于某些意义不明确的参数还要做详细说明(例如:以角度作为参数时,要说明该角度参数是以弧度(PI),还是以度为单位),对既是入口又是出口的变量应该在入口和出口处同时标明。等等。
②、 函数的注释应该放置在函数的头文件中,在实现文件中的该函数的实现部分应该同时放置该注释。
③、 在注释中应该详细说明函数的主要实现思路、特别要注明自己的一些想法,如果有必要则应该写明对想法产生的来由。对一些模仿的函数应该注释上函数的出处。
④、 在注释中详细注明函数的适当调用方法,对于返回值的处理方法等。在注释中要强调调用时的危险方面,可能出错的地方。
⑤、 对日期的注释要求记录从开始写函数到结束函数的测试之间的日期。
⑥、 对函数注释开始到函数命名之间应该有一组用来标识的特殊字符串。
如果算法比较复杂,或算法中的变量定义与位置有关,则要求对变量的定义进行图解。对难以理解的算法能图解尽量图解。
2、变量的注释:
对于变量的注释紧跟在变量的后面说明变量


Fight  to win  or  die...
2007-05-05 11:45
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++代码优化方法
在C++层次进行优化,比在汇编层次优化具有更好的移植性,应该是优化中的首选做法。

确定浮点型变量和表达式是 float 型

为了让编译器产生更好的代码(比如说产生3DNow! 或SSE指令的代码),必须确定浮点型变量和表达式是 float 型的。要特别注意的是,以 ";F"; 或 ";f"; 为后缀(比如:3.14f)的浮点常量才是 float 型,否则默认是 double 型。为了避免 float 型参数自动转化为 double,请在函数声明时使用 float。

使用32位的数据类型

编译器有很多种,但它们都包含的典型的32位类型是:int,signed,signed int,unsigned,unsigned int,long,signed long,long int,signed long int,unsigned long,unsigned long int。尽量使用32位的数据类型,因为它们比16位的数据甚至8位的数据更有效率。

明智使用有符号整型变量

在很多情况下,你需要考虑整型变量是有符号还是无符号类型的。比如,保存一个人的体重数据时不可能出现负数,所以不需要使用有符号类型。但是,如果是要保存温度数据,就必须使用到有符号的变量。

在许多地方,考虑是否使用有符号的变量是必要的。在一些情况下,有符号的运算比较快;但在一些情况下却相反。比如:整型到浮点转化时,使用大于16位的有符号整型比较快。因为x86构架中提供了从有符号整型转化到浮点型的指令,但没有提供从无符号整型转化到浮点的指令。看看编译器产生的汇编代码,不好的代码:

编译前 编译后

double x; mov [foo + 4], 0

unsigned int i; mov eax, i

x = i; mov [foo], eax

flid qword ptr [foo]

fstp qword ptr [x]


上面的代码比较慢。不仅因为指令数目比较多,而且由于指令不能配对造成的FLID指令被延迟执行。最好用以下代码代替,推荐的代码:

编译前 编译后

double x; fild dword ptr [i]

int i; fstp qword ptr [x]

x = i;


在整数运算中计算商和余数时,使用无符号类型比较快。以下这段典型的代码是编译器产生的32位整型数除以4的代码,不好的代码 推荐的代码

编译前 编译后

int i; mov eax, i

i = i / 4; cdq

and edx, 3

add eax, edx

sar eax, 2

mov i, eax


编译前 编译后

unsigned int i; shr i, 2

i = i / 4;


总结:

无符号类型用于:

除法和余数

循环计数

数组下标

有符号类型用于:

整型到浮点的转化

while VS. for

在编程中,我们常常需要用到无限循环,常用的两种方法是while (1) 和 for (;;)。这两种方法效果完全一样,但那一种更好呢?然我们看看它们编译后的代码:

编译前 编译后

while (1); mov eax,1

test eax,eax

je foo+23h

jmp foo+18h


编译前 编译后

for (;;); jmp foo+23h


for (;;)指令少,不占用寄存器,而且没有判断跳转,比while (1)好。

使用数组型代替指针型

使用指针会使编译器很难优化它。因为缺乏有效的指针代码优化的方法,编译器总是假设指针可以访问内存的任意地方,包括分配给其他变量的储存空间。所以为了编译器产生优化得更好的代码,要避免在不必要的地方使用指针。一个典型的例子是访问存放在数组中的数据。C++ 允许使用操作符 [] 或指针来访问数组,使用数组型代码会让优化器减少产生不安全代码的可能性。比如,x[0] 和x[2] 不可能是同一个内存地址,但 *p 和 *q 可能。强烈建议使用数组型,因为这样可能会有意料之外的性能提升。

不好的代码 推荐的代码

typedef struct

{

float x,y,z,w;

} VERTEX;

typedef struct

{

float m[4][4];

} MATRIX;

void XForm(float* res, const float* v, const float* m, int nNumVerts)

{

float dp;

int i;

 const VERTEX* vv = (VERTEX *)v;

 for (i = 0; i <; nNumVerts; i++)

{

dp = vv->;x * *m ++;

dp += vv->;y * *m ++;

dp += vv->;z * *m ++;

dp += vv->;w * *m ++;

*res ++ = dp;// 写入转换了的 x

dp = vv->;x * *m ++;

dp += vv->;y * *m ++;

dp += vv->;z * *m ++;

dp += vv->;w * *m ++;

*res ++ = dp; // 写入转换了的 y

dp = vv->;x * *m ++;

dp += vv->;y * *m ++;

dp += vv->;z * *m ++;

dp += vv->;w * *m ++;

*res ++ = dp;// 写入转换了的 z

dp = vv->;x * *m ++;

dp += vv->;y * *m ++;

dp += vv->;z * *m ++;

dp += vv->;w * *m ++;

*res ++ = dp;// 写入转换了的 w

vv ++;  // 下一个矢量

m -= 16;

}

}

typedef struct

{

float x,y,z,w;

} VERTEX;

typedef struct

{

float m[4][4];

} MATRIX;

void XForm (float* res, const float* v, const float* m, int nNumVerts)

{

int i;

const VERTEX* vv = (VERTEX*)v;

const MATRIX* mm = (MATRIX*)m;

VERTEX* rr = (VERTEX*)res;

for (i = 0; i <; nNumVerts; i++)

{

rr->;x = vv->;x * mm->;m[0][0] + vv->;y * mm->;m[0][1]

+ vv->;z * mm->;m[0][2] + vv->;w * mm->;m[0][3];

rr->;y = vv->;x * mm->;m[1][0] + vv->;y * mm->;m[1][1]

+ vv->;z * mm->;m[1][2] + vv->;w * mm->;m[1][3];

rr->;z = vv->;x * mm->;m[2][0] + vv->;y * mm->;m[2][1]

+ vv->;z * mm->;m[2][2] + vv->;w * mm->;m[2][3];

rr->;w = vv->;x * mm->;m[3][0] + vv->;y * mm->;m[3][1]

+ vv->;z * mm->;m[3][2] + vv->;w * mm->;m[3][3];

}

}


注意: 源代码的转化是与编译器的代码发生器相结合的。从源代码层次很难控制产生的机器码。依靠编译器和特殊的源代码,有可能指针型代码编译成的机器码比同等条件下的数组型代码运行速度更快。明智的做法是在源代码转化后检查性能是否真正提高了,再选择使用指针型还是数组型。


充分分解小的循环

要充分利用CPU的指令缓存,就要充分分解小的循环。特别是当循环体本身很小的时候,分解循环可以提高性能。BTW:很多编译器并不能自动分解循环。

不好的代码 推荐的代码

// 3D转化:把矢量 V 和 4x4 矩阵 M 相乘

for (i = 0; i <; 4; i ++)

{

r[i] = 0;

for (j = 0; j <; 4; j ++)

{

r[i] += M[j][i]*V[j];

}

}

r[0] = M[0][0]*V[0] + M[1][0]*V[1] + M[2][0]*V[2] + M[3][0]*V[3];

r[1] = M[0][1]*V[0] + M[1][1]*V[1] + M[2][1]*V[2] + M[3][1]*V[3];

r[2] = M[0][2]*V[0] + M[1][2]*V[1] + M[2][2]*V[2] + M[3][2]*V[3];

r[3] = M[0][3]*V[0] + M[1][3]*V[1] + M[2][3]*V[2] + M[3][3]*v[3];


避免没有必要的读写依赖

当数据保存到内存时存在读写依赖,即数据必须在正确写入后才能再次读取。虽然AMD Athlon等CPU有加速读写依赖延迟的硬件,允许在要保存的数据被写入内存前读取出来,但是,如果避免了读写依赖并把数据保存在内部寄存器中,速度会更快。在一段很长的又互相依赖的代码链中,避免读写依赖显得尤其重要。如果读写依赖发生在操作数组时,许多编译器不能自动优化代码以避免读写依赖。所以推荐程序员手动去消除读写依赖,举例来说,引进一个可以保存在寄存器中的临时变量。这样可以有很大的性能提升。下面一段代码是一个例子:

不好的代码 推荐的代码

float x[VECLEN], y[VECLEN], z[VECLEN



Fight  to win  or  die...
2007-05-05 11:45
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

Microsoft Visual C++ 浮点优化

Eric Fleegal

Microsoft Corporation

适用于:Microsoft Visual C++

C++ 中的浮点代码优化
C++ 优化编译器不仅能够将源代码转换为机器码,而且能够对机器指令进行适当的排列以便改善性 能和/或减小大小。遗憾的是,许多常用的优化在应用于浮点计算时未必安全。在下面的求和算法 [1] 中,可以看到这方面的一个恰当的示例:

float KahanSum( const float A[], int n )

{

float sum=0, C=0, Y, T;

for (int i=0; i<n; i++)

{

Y = A[i] - C;

T = sum + Y;

C = T - sum - Y;

sum = T;

}

return sum;

}


该函数将数组向量 A 中的 n 个浮点值相加。在循环体中,算法计算 一个“修正”值,然后将其应用于求和的下一步。与简单的求和相比,该方法大大减小了累积性舍入 误差,同时保持了 O(n) 时间复杂性。

一个不完善的 C++ 编译器可能假设浮点算法遵循与实数算法相同的代数规则。这样的编译器可能 继而错误地断定

C = T - sum - Y ==> (sum+Y)-sum-Y ==> 0;

也就是说,C 得到的值总是常量零。如果随后将该常量值传播到后续表达式中,循环体将化简为简 单的求和。更准确地说,就是

Y = A[i] - C ==> Y = A[i]

T = sum + Y ==> T = sum + A[i]

sum = T ==> sum = sum + A[i]


因此,对于不完善的编译器而言,KahanSum 函数的逻辑转换将是:

float KahanSum( const float A[], int n )

{

float sum=0; // C, Y & T are now unused

for (int i=0; i<n; i++)

sum = sum + A[i];

return sum;

}


尽管转换后的算法更快,但它根本没有准确表达程序员的意图。精心设计的误差修正已经 被完全消除,只剩下一个具有所有其关联误差的简单的直接求和算法。

当然,完善的 C++ 编译器知道实数算法的代数规则通常并不适用于浮点算法。然而,即使是完善 的 C++ 编译器,也可能错误地解释程序员的意图。

考虑一种常见的优化措施,它试图在寄存器中存放尽可能多的值(称为“登记”值)。在 KahanSum 示例中,这一优化可能试图登记变量 C、Y 和 T,因为这些变量仅在循环体内使用。如果寄存器精度为 52 位(双精度)而不是 23 位(单精度),这一优化可以有效地将 C、Y 和 T 的类 型提升为 double。如果没有以同样的方式登记 sum 变量,则它仍将编 码为单精度。这会将 KahanSum 的语义转换为下面的语义

float KahanSum( const float A[], int n )

{

float sum=0;

double C=0, Y, T; // now held in-register

for (int i=0; i<n; i++)

{

Y = A[i] - C;

T = sum + Y;

C = T - sum - Y;

sum = (float) T;

}

return sum;

}


尽管现在 Y、T 和 C 以更高的精度进行计算,但新的编码可能产生精确性较低的结果,具体取决 于 A[] 中的值。因而,即使看起来无害的优化也可能具有消极的后果。

这些种类的优化问题并不局限于“棘手”的浮点代码。即使是简单的浮点算法,在经过错误的优化 后也可能失败。考虑一个简单的直接求和算法:

float Sum( const float A[], int n )

{

float sum=0;

for (int i=0; i<n; i++)

sum = sum + A[i];

return sum;

}


因为一些浮点单元能够同时执行多个运算,所以编译器可能选择采用标量简化 优化。这一 优化有效地将简单的 Sum 函数从上述形式转换为以下形式:

float Sum( const float A[], int n )

{

int n4 = n-n%4; // or n4=n4&(~3)

int i;

float sum=0, sum1=0, sum2=0, sum3=0;

for (i=0; i<n4; i+=4)

{

sum = sum + A[i];

sum1 = sum1 + A[i+1];

sum2 = sum2 + A[i+2];

sum3 = sum3 + A[i+3];

}

sum = sum + sum1 + sum2 + sum3;

for (; i<n; i++)

sum = sum + A[i];

return sum;

}


该函数现在保持了四个独立的求和运算,它们可以在每个步骤同时处理。尽管优化后的函数现在要 快得多,但优化结果可能与非优化结果完全不同。在进行这一变化时,编译器采用了具有结合律的浮 点加法;即以下两个表达式等效:(a+b)+c == a+(b+c)。然而,对于浮点数而言,结合律并不总是适 用。现在,转换后的函数不是按以下方法求和:

sum = A[0]+A[1]+A[2]+...+A[n-1]


而是按以下方法计算结果:

sum = (A[0]+A[4]+A[8]+...)

+(A[1]+A[5]+A[9]+...)

+(A[2]+A[6]+A[10]+...)

+(A[3]+A[7]+A[11]+...)

+...


对于 A[] 的某些值而言,不同的加法运算顺序可能产生意外的结果。更为复杂的是,某些程序员 可能选择预先针对此类优化做准备,并相应地对这些优化进行补偿。在此情况下,程序可以按不同的 顺序构建数组 A,以便优化的 sum 产生预期的结果。而且,在许多情况 下,优化结果的精确性可能“足够严密”。当优化提供了令人信服的速度优点时,尤其如此。例如, 视频游戏要求具有尽可能快的速度,但通常并不要求进行高度精确的浮点计算。因此,编译器制造商 必须为程序员提供一种机制,以便控制速度和精确性之间经常背离的目标。

某些编译器通过为每种类型的优化单独提供“开关”在速度和精确性之间进行折衷。这使开发人员 可以禁用可能为其特定应用程序的浮点精确性带来变化的优化。尽管该解决方案可能提供对编译器的 高度控制,但它也会带来其他一些问题:

? 通常很难搞清楚需要启用或禁用哪些开关。

? 禁用任一优化都可能对非浮点代码的性能带来不利影响。

? 每个附加的开关都会引起许多新的开关组合;组合数目将很快变得难以控制。


因此,尽管为每种优化提供单独的开关看起来似乎很有吸引力,但使用此类编译器可能非常麻烦并 且不可靠。

许多 C++ 编译器提供了“一致性”浮点模型(通过 /Op 或 /fltconsistency 开关),从而使开 发人员能够创建符合严格浮点语义的程序。采用该模型时,可以防止编译器对浮点计算使用大多数优 化,同时允许其对非浮点代码使用这些优化。但是,该一致性模型具有一个缺点。为了在不同的 FPU 体系结构中返回可预测的结果,几乎所有 /Op 实现都将中间表达式舍入到用户指定的精度;例如,考 虑下面的表达式:

float a, b, c, d, e;

. . .

a = b*c + d*e;


为了在使用 /Op 开关时产生一致的且可重复的结果,该表达式的计算方式按如下方式实现:

float x = b*c;

float y = d*e;

a = x+y;


现在,最终结果在计算该表达式的每一步 中都产生了单精度舍入误差。尽管这种解释在严 格意义上并未破坏任何 C++ 语义规则,但它几乎肯定不是计算浮点表达式的最佳方法。通常,以 尽可能高的可行精度计算中间结果 更为可取。例如,以如下所示的较高精度计算表达式 a=b*c+d*e 将会更好:

double x = b*c;

double y = d*e;

double z = x+y;

a = (float)z;


或者,采用以下方式会更好:

long double x = b*c;

long double y = d*e

long double z = x+y;

a = (float)z;


在以较高精度计算中间结果时,最终结果显然会更为精确。具有讽刺意味的是,如果采用一致性模 型,则当用户试图通过禁用不安全的优化来减少误差时,出现误差的可能性却恰恰增加了。因此,一 致性模型不仅严重降低了效率,同时还无法对精确性的提高提供任何保证。对于认真的数值程序员而 言,这看起来不像是一个很好的折衷,这也是该模型没有被广泛接受的主要原因。

从版本 8.0 (Visual C++?2005) 开始,Microsoft C++ 编译器提供了一种更好的选择。它使程序 员可以选择以下三种常规浮点模式之一:fp:precise、fp:fast 和 fp:strict。

? 在 fp:precise 模式下,仅对浮点代码执行安全优化,并且与 /Op 不同,以最 高可行 精度一致性地执行中间计算。

? fp:fast 模式放松了浮点规则,允许以牺牲精确性为代价进行更为积极的优化。

? fp:strict 模式提供了 fp:precise 的所有常规正确性,同时启用了 fp- exception 语义,并禁止在存在 FPU 环境更改(例如,对寄存器精度、舍入方向的更改等等)时进行 非法转换。



Fight  to win  or  die...
2007-05-05 11:45
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

Effective C++ 的52个条款列表

第一章 从C转向C++

条款1:尽量用const和inline而不用#define

条款2:尽量用而不用

条款3:尽量用new和delete而不用malloc和free

条款4:尽量使用c++风格的注释

第二章 内存管理
条款5:对应的new和delete要采用相同的形式

条款6:析构函数里对指针成员调用delete

条款7:预先准备好内存不够的情况

条款8 写operator new和operator delete时要遵循常规

条款9 避免隐藏标准形式的new

条款10 如果写了operator new就要同时写operator delete

第三章 构造函数,析构函数和赋值操作符

条款11 为需要动态分配内存的类声明一个拷贝构造函数和一个赋值操作符

条款12 尽量使用初始化而不要在构造函数里赋值

条款13 初始化列表中成员列出的顺序和它们在类中声明的顺序相同

条款14 确定基类有虚析构函数

条款15 让operator=返回this的引用

条款16 在operator=中对所有数据成员赋值

条款17 在operator=中检查给自己赋值的情况

第四章 类和函数:设计与声明

条款18 争取使类的接口完整并且最小

条款19 分清成员函数,非成员函数和友元函数

条款20 避免public接口出现数据成员

条款21 尽可能使用const

条款22 尽量用“传引用”而不用“传值”

条款23 必须返回一个对象时不要试图返回一个引用

条款24 在函数重载和设定参数缺省值间慎重选择

条款25 避免对指针和数字类型重载

条款26 当心潜在的二义性

条款27 如果不想使用隐式生成的函数就要显式地禁止它

条款28 划分全局名字空间

第五章 类和函数 实现

条款29 避免返回内部数据的句柄

条款30 避免这样的成员函数:其返回值是指向成员的非const指针或引用,但成员的访问级比这个函数要低

条款31 千万不要返回局部对象的引用,也不要返回函数内部用new初始化的指针的引用

条款32 尽可能地推迟变量的定义

条款33 明智地使用内联

条款34 将文件间的编译依赖性降至最低

第六章 继承和面向对象设计

条款35 使公有继承体现 是一个 的含义

条款36 区分接口继承和实现继承

条款37 决不要重新定义继承而来的非虚函数

条款38 决不要重新定义继承而来的缺省参数值

条款39 避免 向下转换 继承层次

条款40 通过分层来体现 有一个 或 用...来实现

条款41 区分继承和模板

条款42 明智地使用私有继承

条款43 明智地使用多继承

条款44 说你想说的;理解你所说的

第七章 杂项

条款45 弄清C++在幕后为你所写、所调用的函数

条款46 宁可编译和链接时出错,也不要运行时出错

条款47 确保非局部静态对象在使用前被初始化

条款48 重视编译器警告

条款49 熟悉标准库

条款50 提高对C++的认识


Fight  to win  or  die...
2007-05-05 11:46
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C/C++中的整型常识

很多人对C/C++中的整型不太了解,导致代码移植的时候出现问题,在此总结一下:

a. C/C++对整型长度的规定是为了执行效率,将int定义为机器字长可以取得最大的执行速度;

b. C/C++中整型包括:int, char 和 enum, C++中还包含bool类型,C99中bool是一个宏,实际为_Bool;

c. C 和 C++ 对 enum 的规定有所不同,这里不描述;

d. 修饰整型正负的有 signed 和 unsigned,对于 int 默认为 signed;

e. 修饰 int 大小的有 short 和 long, 部分编译器还扩展了一些更长的整型,比如 long long 和 __int64, C99中增加了long long和unsigned long long;

f. int 的长度 与 机器字长相同, 16位的编译器上int长16位,32位的编译器上int长32位;

g. short int 的长度 小于等于 int 的长度,注意她们可能长度相等,这取决于编译器;

h. long int 的长度 大于等于 int 的长度,注意她们可能长度相等,这取决于编译器;

i. char 的长度应当可以包容得下一个字符,大部分系统中就是一个字节,而有的系统中可能是4个字节,因为这些系统中一个字符需要四个字节来描述;

j. char 的正负取决于编译器,而编译器的决定取决于操作系统,在不同的编译器中char可能等同于signed char,也可能等同于unsigned char;

总结:

a. 出于效率考虑,应该尽量使用int和unsigned int;

b. 当需要指定容量的整型时,不应该直接使用short、int、long等,因为在不同的编译器上她们的容量不相同。此时应该定义她们相应的宏或类型,比如在VC++6.0中,可以如下定义:

typedef unsigned char UBYTE;

typedef signed char SBYTE;

typedef unsigned short int UWORD;

typedef signed short int SWORD;

typedef unsigned int UDWORD;

typedef signed int SDWORD;

typedef unsigned __int64 UQWORD;

typedef signed __int64 SQWORD;

然后在代码中使用 UBYTE、SBYTE、UWORD 等,这样当代码移植的时候只需要修改相应的类型即可。

定义自己的类型虽然在代码移植的时候只需要修改一处即可,但仍然属于源代码级别的修改,所以 C++ 2.0 中将这些类型定义在模板中,可以做到代码移植时无需修改代码。

c. 在定义char时,一定要加上 signed 或 unsigned,因为她的正负在不同的编译器上并不相同。

d. 不要想当然的以为char是1字节长,因为她的长度在不同的编译器上并不相同


Fight  to win  or  die...
2007-05-05 11:46
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++的底层机制

c++为我们所提供的各种存取控制仅仅是在编译阶段给我们的限制,也就是说是编译器确保了你在完成任务之前的正确行为,如果你的行为不正确,那么你休想构造出任何可执行程序来。

但如果真正到了产生可执行代码阶段,无论是c,c++,还是pascal,大家都一样,你认为c和c++编译器产生的机器代码会有所不同吗,你认为c++产生的机器代码会有访问限制吗?那么你错了。什么const,private,统统没有(const变量或许会放入只读数据段),它不会再给你任何的限制,你可以利用一切内存修改工具或者是自己写一个程序对某一进程空间的某一变量进行修改,不管它在你的印象中是private,还是public,对于此时的你来说都一样,想怎样便怎样。

另外,你也不要为c++所提供的什么晚期捆绑等机制大呼神奇,它也仅仅是在所产生的代码中多加了几条而已,它远没有你想象的那么智能,所有的工作都是编译器帮你完成,真正到了执行的时候,计算机会完全按照编译器产生的代码一丝不苟的执行。

(以下的反汇编代码均来自visial c++ 7.0)

一.让我们从变量开始-----并非你想象的那么简单

变量是什么,变量就是一个在程序执行过程中可以改变的量。换一个角度,变量是一块内存区域的名字,它就代表这块内存区域,当我们对变量进行修改的时候,会引起内存区域中内容的改变。但是你若是学习过汇编或是计算机组成原理,那么你就会清楚对于一块内存区域来说,根本就不存在什么名字,它所仅有的标志就是他的地址,因此我们若想修改一块内存区域的内容,只有知道他的地址方能实现。看来所谓的变量一说只不过是编译器给我们进行的一种抽象,让我们不必去了解更多的细节,降低我们的思维跨度而已。例如下面这条语句:

int a=10;

按照我们的思维习惯来讲,就是“存在一个变量a,它的值是10”,一切都显得那么的自然。我们不必去在乎什么所谓的地址以及其他的一些细节。然而在这条语句的底层实现中,a已经不能算是一个变量了,它仅仅是一个标记,代表一个地址的标记:

mov dword ptr[a],0Ah;

怎么样,这条语句不像上面那条易于接受吧,因为它需要了解更多的细节,你几乎不能得到编译器的任何帮助,一切思维上的跨越必须由你自己完成。这条语句应该解释为“把10写入以a为地址的内存区域”。你说什么?a有些像指针?对,的确像,但还不是,只不过他们的过程似乎是类似的。这里所说的跨越实际上就是从一个现实问题到具体地址以及内存区域的跨越。

二.引用:你可以拥有引用,但编译器仅拥有指针(地址)

看过了第一条,你一定对编译器的工作有了一定的了解,实际上编译器就是程序员与底层之间的一个转换层,它把一个高级语言代码转换为低级语言代码,一个编译器完成的转换跨度越大,那么它也就会越复杂,因为程序员的工作都由他代为完成了。C++编译器必然比汇编编译器复杂就是这个道理。如果我问你引用和指针是一样的吗?你或许会说当然不一样了,指针容易产生不安全的因素,引用却不会,真的不会吗?我们来看下面这段代码:

int *e=new int(10);

int &f=*e;

delete e;

f=30;

你认为上面这段代码怎么样,我感觉就不很安全,它和指针有相同的隐患。因为它所引用的内存区域就不合法。

我个人认为,所谓的引用其实就是一种指针,只不过二者的接口并不相同,引用的接口有一定的限制。指针可以一对多,而引用却只能一对一,即&refer不能被改变,但却并不能说一对一就是安全的,只不过危险的系数降低罢了。引用比指针更容易控制。

Ok,下面来说说指针,曾经有过汇编经验的人一定会说,恩,指针的某些地方有些像汇编,尤其是那个“*”,怎么就那么像汇编中的“[]”啊。的确,它也涵盖了一个寻址的过程。看来指针的确是个比较低级的东西。然而引用却并不那么直接,虽然程序员用起来方便安全了许多。但是你要清楚,只有你可以拥有引用,编译器可没有这个工具,计算机并不认识这个东西。因此,它的底层机制实际上是和指针一样的。不要相信只有一块内存拷贝,不要认为引用可以为你节省一个指针的空间,因为这一切不会发生,编译器还是会把引用解释为指针。不管你相不相信,请看下面这段代码:

int& b=a;

lea eax,[a];

mov dword ptr[b],eax;把a的地址赋给地址为b的一块内存

b=50;

mov eax,dword ptr[b];

mov dword ptr[eax],32h;

int *d=&a;

lea eax,[a];

mov dword ptr[d],eax

*d=60;

mov eax,dword ptr[d]

mov dword ptr[eax],3ch;

以上的代码均来自具体的编译器,怎么样,相信了吧,好,让我再来做一个或许不怎么恰当的比拟,你一定编过有关线性表和栈的程序吧,线性表是一个非常灵活的数据结构,在他上面有许多的操作,然而栈呢,它是一个限制性操作的线性表,它的底层操作实际上是由线性表操作实现的。就好比stack与vector的关系,因此指针和引用的关系就好比线性表和栈的关系,引用也就是受限的指针,它对外的接口和指针虽然并不一样,但底层是相同的。

下面再来看看引用的一个重要用途,作为函数的参数传递的时候是怎样的情形:

void swapr(int &a, int &b);

void swapr(int* a, int *b);

int a=10;

int b=20;

swapr(a, b);

lea eax,[a];

push eax; //把a的地址压入堆栈

lea ecx,[b];

push ecx;

call swapr;

swapr(&a, &b);

lea eax,[a];

push eax;

lea ecx,[b];

push ecx;

call swapr;

怎么样,用引用和指针传递参数无论是在效率上还是在空间上都是完全一样的,如果妄想不传入地址就修改实参的值,简直就是天方夜谭,这就说明引用的本质就是指针。毕竟它们的行为都太相似了,如果不是这样,你还有什么方法去实现引用吗?记住,引用只不过是编译器为你提供的一个有用且安全的工具,对于机器代码可无法表示它,它把指针一对多的缺点去除,禁止了你的不安全的操作。但回到问题的本源,他们没有任何区别。


Fight  to win  or  die...
2007-05-05 11:46
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++的中抽象

在C++中,以类、虚函数等为代表的数据抽象功能一直是C++的核心和难点。我认为C++的抽象应该是指:从我们需要解决的问题出发,在与该问题相关的一组关联对象中提取出主要的或共有的部分――说简单一点,就是用相同的行为来操作不同的对象。

从提出问题到找出与该问题相关的对象,这是一个互动的、反复的过程。在对相关对象的抽象中,随着认识的深入,我们可能会修改最初的目标,而最初目标的修改又可能使一组新的相关对象被加入进来。如:假设现在要设计一个基于广域网的邮件服务器,首先可能需要通过socket对底层协议进行封装,为高层的pop3、smtp协议提供一组标准的接口。开始为了使问题简化我们可能计划只封装TCP/IP协议,不过基于以下两点我们有理由修改最初的需求:

1、 pop3、smtp需要的底层接口很简单。除了连接,仅需要发送、接收一块数据的功能

2、 用socket进行网络编程,大多数常见协议间的差别很小,有许多都仅仅只是初始化和连接不同而已我们只需要做很小的努力就可以兼容大多数常用协议(如:ATM、Ipx、红外线协议等)。

现在决定修改需求,除了TCP/IP协议,还要支持一些其他的的常用协议。通过对最初目标的修改,除了TCP/IP协议对象,又会有一组相关的协议对象被加入进来。我们可以很容易从这组相关对象中提出共有的部分,将他抽象到另一个公共对象中。当然,根据具体应用环境不同,这可能并不是最佳方案。

C++中常规的抽象是在一组相互间有“血缘”关系的类中展开的。如:

Class Parent

{

virtual ~Parent(){};

virtual void GetValue(){ .... };

virtual void Test(){ ... };

};

class child1 : public parent

{

virtual ~child1(){};

virtual void GetValue(){...};

virtual void Test(){ ... } const;

};

class child2 : public parent

{

virtual ~child2(){};

virtual void GetValue(){...};

virtual void Test(){ ... } ;

};

(顺便说一句,child1::Test() const 不是基类 parent::Test() 的重载。)

由上可总结出C++中抽象的一些基本特点:

1、被抽象对象必须直接或间接派生至某个类对象

2、如果你不用没有类型安全的操作,如:向下转型操作或强制类型转化操作(像COM那样)。那么派生类中需要抽象的动作必须在某个基类中出现。

3、 基类的析构函数必须是一个虚函数

上述特点一般而言不会影响我们的抽象,但在一些特殊情况下就很难说了。比如:

假设为某个项目进行二次开发,到手的资料可能就是一堆dll、一堆头文件和一堆文档。这些dll里输出了很多的类,其中有一大堆都是离散的、毫无关系的类对象。经过一段时间的开发,你可能发现为了分别操作这些对象,程序中充满了switch...case.../if....else....语句。更扰人的是其实这些对象完全可以从某个基类派生,有些操作完全可以定义成virtual function。但在不能修改source code 的情况下(其实就算有源代码这样的修改也不可行)如何对这组对象进行抽象呢?

还有一些例子,比如:在MFC中,假设我们从Cdialog派生一组对话框类,如果我们在某个派生类中定义了一个自己的virtual function。那么除了重新在Cdialog和派生类之间再派生一个类层次,我们无法从外部以抽象的方式直接调用这个虚函数。但为了一个派生类和一个virtual function就添加一个类层次,这也太.....

将以上特例总结一下:C++中进行抽象的一组类之间必须有“血缘”关系。但在实际应用中我们有

时候有必要对一组离散的、没有关系的类对象(如来自不同的类库或者根本就没有virtual function)进行一些抽象操作――可能因为工作关系,我接触这种情况的机会比较多。传统的C++没有直接提供这方面的支持。在实际应用中我经常使用如下方法:

#include <list>

class parent

{

public:

virtual ~parent(){};

virtual void DoSomething( void ) const = 0;

};

template< typename T >

class child : public parent

{

public:

virtual ~child()

{

delete tu;

}

child( ):

{

tu = new T;

}

void DoSomething( void ) const

{

tu->InitObj();

}

private:

T *tu;

};

class test

{

public:

void InitObj( void )

{

::MessageBox( NULL, "Test", "test...ok!", MB_OK );

}

};

int main()

{

using namespace std;

list< parent* > plist;

parent *par = new child<test>();

plist.push_back( par );

}

以上方法用模板的方式来产生对象的代理。优点是完全未损失C++类型安全检查的特性,class object的一般普通成员函数就可以进行抽象调用了。缺点是调用的函数名被事先确定了――但这往往是不能接受的。为了改进这一点我在后来的使用中引入了member function pointer。代码如下:

#include<list>

class parent

{

public:

virtual ~parent(){};

virtual void do1( void ) const = 0;

virtual int do2( char* ) const = 0;

};

template< typename T >

class child : public parent

{

typedef void (T::*PFUN1)( void );

typedef int (T::*PFUN2)( char* );

public:

virtual ~child()

{

delete tu;

}

//////////////////////////////////////

child( PFUN1 p1 ):

fun1(p1), fun2(NULL)

{

tu = new T;

}

//------------------------------------

child( PFUN2 p2 ):

fun1(NULL), fun2(p2)

{

tu = new T;

}

//-------------------------------------

child( PFUN1 p1, PFUN2 p2 ):

fun1(p1), fun2(p2)

{

tu = new T;

}

////////////////////////////////////////

int do2( char *pch ) const

{

return fun2?(tu->*fun2)( pch ) : -1;

}

void do1( void ) const

{

fun1?(tu->*fun1)() : -1;

}

private:

T *tu;

PFUN1 fun1;

PFUN2 fun2;

};

class test

{

public:

void test1( void )

{

::MessageBox( NULL, "Test", "test...ok!", MB_OK );

}

};

int main()

{

using namespace std;

list< parent* > plist;

parent *par = new child<test>( test::test1 );

plist.push_back( par );

}

在这个例子中我只引用了两种类型的member function pointer:

typedef void (T::*PFUN1)( void );

typedef int (T::*PFUN2)( char* );

按上面的方法很容易扩展到其他函数类型。Construct child( PFUN1 p1, PFUN2 p2 )只是为了说明一个class object可以注册多种方法。更好的做法可能是将函数注册功能独立成一个函数。

总体来说以上方法只能作为一个特例来看。我们总是应该以常规的C++的方式进行抽象。C++中关于抽象的一些限制并不是我们进行抽象的阻碍。我们应该把它们看作“桥上的栏杆”,他们可以保证我们的逻辑思维沿着正确地方向前进!


Fight  to win  or  die...
2007-05-05 11:48
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++实用技巧两则

在准标准C++中,有关缺省变量值的限制非常模糊。基于此,很多编译器允许开发人员将缺省变量值包含在函数声明,指向函数的指针和引用,成员函数的指针,以及typedef声明中。

请看一下以下的程序:

struct A

{void func(int x=5) {}

};

void g(int n=12)

{

}

// 根据C++标准,不能在以下声明中使用缺省变量值。

void (*pf)(inti=120);

void (A::*pmf)(int j=50);

typedef void (*PF)(inti=100);

// 函数的引用

typedef void (&PRF)(inti=100);

int main()

{pf=g;

PF pf2=g;

pmf=&A::func;

A a;

//这些调用使用了哪些缺省值?

pf();

pf2();

(a.*pmf)();

}

A::func()和g()具有缺省变量值,这是合理的。然而,指针pmf,pf以及typedef PF也定义了缺省的变量值。根据C++标准,这是不规范的。

这一代码的实际使用中,其中的一个问题是这些声明中提供的缺省值与A::func()和g()函数提供的值不一致。也就是说,很多编译器将这些代码作为非标准的扩展。当调用g()函数时,我的编译将120作为pf的缺省值;然而,对于pf2,它使用100作为它的缺省值。

作为一种规则,应该避免使用指向函数的指针,成员函数的指针,以及typedef命名的缺省变量值。即使你的编译器接受了它们,在更高版本中它也可能不被接受。而且,这些代码也会降低程序的灵巧性,也会给那些无法判别哪些编译器接收何种缺省变量的开发人员带来误导。在使用这些缺省变量值的合法代码中,我的建议是添加一些必要的注释,以说明需要哪些缺省变量值。


Fight  to win  or  die...
2007-05-05 11:49
aipb2007
Rank: 8Rank: 8
来 自:CQU
等 级:贵宾
威 望:40
帖 子:2879
专家分:7
注 册:2007-3-18
收藏
得分:0 

C++之运算符重载

有了C++语言,你就可以重载函数和运算符。重载是一种应用,它在同一范围中为一个给定函数名称提供了多种定义。委托编译器依据调用该函数的参量选择合适的函数或运算符的版本。例如:

double max(double d1,double d2)

{

return (di>d2)?d1:d2;

}
int max (int e1,int e2)

{

return (e1>e2)?e1:e2;

}

作为一个重载函数,函数max在程序中使用如下:

main()

{

int e=nax(12,8);

double d=max(123.4,12.3);

return e+(int)d;

}

在第一个例子中,要求出两个整型变量的最大值,故调用函数(int,int)。然而,在第二种情况下,两个参量是浮点型,因此调用的函数是max(double,double)。

重载函数之间的区别在于带有不同初始值的参量类型。因而对一个给定类型的参量以及对于该类型的引用,在重载的意义上来说是完全相同的。它们被看成是相同的,因为它们采用了相同的初始值。例如:max(double,double)和(double&,double &)是完全相同的,说明两个这样的函数会引起错误。出于相同的原因,用修饰符const和volatile进行修饰的函数参量类型同基本类型,在重载的意义上看没有什么不同。然而重载函数的机制可以区分由const或volatile修饰的引用以及基本类型的引用。指向const和volatile对象的指针和指向其基本类型的指针在重载意义上是不同的。

一组重载函数是否是可接受的如下限制:

·该组重载函数中任何两个都必须有不同的参量表。

·具有相同类型参量表、仅在返回值类型上不同的重载函数会引起错误。

·成员函数的重载不能仅基于一个说明为静态的,另一个说明为非静态的。

·typedef说明并未定义新的类型,它们仅为已存在的类型引入了一个同义词。它们不能影响重载机制。

·枚举类型是一些可区分的类型,故可以区分重载函数。

·从区分重载函数的意义上说,类型“数组”和“指针”是相同的。对于一维数组来说是正确的。

了解了函数重载,下面将具体的介绍运算符重载

注意,下面的规则约束着重载运算符如何实现,但它们并不适用于new和delete运算符。

·运算符必须要么是成员函数,或者带有某个类的参量,或者是枚举类型的参量、或者带有某个类的引用、或某个枚举的类型的引用。

·运算符要遵守它们同内部类型一起使用所指定的优先原则、分组及操作数的个数。因此,无法表达把2及3加到一个Point对象中的含义,除了把2加到X坐标中,把3加到Y坐标中。

·单目运算符说明为成员函数不带参量;如果说明为全局函数,要带一个参量。双目运算符说明为成员函数只带一个参量;如果说明为全局函数,要带两个参量。

·所有的重载运算符除了赋值(operator=)外均可被派生类继承。

·重载运算符的成员函数的第一个参量总是激活该运算符的对象的类类型参量(运算符被定义的类,或者定义了运算符的类的派生类)。对于第一个参量也不支持转换。

任何运算符的意义都可能被完全地改变了,这包括取地址(&)、赋值(=)、函数调用运算符的含义。同理,内部的类型可由于使用了运算符重载而改变。例如:下面四条语句在完成求值以后完全等同:

s=s+1;

s+=1;

s++;

++s;

对于重载了运算符的类类型来说,这种确信是靠不住的,而且,对于在基本类型中使用这些运算符的隐含条件,对于重载的运算符来说是放松了。例如:加法/赋值操纵符,在应用于基本类型时,要求其左操作数是l值的;但此运算符重载以后就没有这种要求了。

下面给出可重载的单目运算符:

运算符 名称
! 逻辑非
& 取地址
~ 求补
* 指针间接引用
+ 单目加
++ 增1
- 单目取反
-- 减1

如果说明一个单目运算符为一个非静态成员,必须按如下形式进行说明:

ret-type operator op()

其中ret-type是返回类型,而op是上表中的某个运算符。

说明一个单目运算符为全局函数,必须按如下形式说明:

ret-type operator op(arg)

其中ret-type和op的含义同成员运算符函数中的描述,而arg是对其操作的类类型参量。

注意:对于单目运算符的返回值没有限制。例如,对于一个逻辑非运算符,返回一个必要的值是合适的。但这一点不是必须的。

双目运算符

下表给出了可被重载的运算符。

运算符 名称
, 逗号
!= 不等

% 取模
%= 取模/赋值
& 按位和
&& 逻辑和
&= 按位和/赋值
* 乘法
*= 乘法/赋值
+ 加法
+= 加法/赋值
- 减法
-= 减法/赋值
-> 成员选择
->* 指向成员的指针选择
/ 除法
/= 除法/赋值
< 小于
<< 左移
<<= 左移/赋值
<= 小于等于
= 赋值
== 等于
> 大于

[此贴子已经被作者于2007-5-5 11:54:11编辑过]


Fight  to win  or  die...
2007-05-05 11:51
快速回复:[分享]c++综合技术应用文章合集^^
数据加载中...
 
   



关于我们 | 广告合作 | 编程中国 | 清除Cookies | TOP | 手机版

编程中国 版权所有,并保留所有权利。
Powered by Discuz, Processed in 0.020077 second(s), 9 queries.
Copyright©2004-2024, BCCN.NET, All Rights Reserved