----2.3----、代码行内的空格
对于表达式比较长的 for语句和 if语句,为了紧凑起见可以适当地去
掉一些空格,如for (i=0; i<10; i++)和if ((a<=b) && (c<=d))
----3.1----、命名规则
全局函数的名字应当使用“动词”或者“动词+名词”(动宾词组)类的成员函数应当只使用“动词”,被省略掉的名词就是对象本身。
例如:
DrawBox(); // 全局函数
box->Draw(); // 类的成员函数
----3-2-6----类的数据成员加前缀m_(表示member)
,这样可以避免数据成员与
成员函数的参数同名。
例如:
void Object::SetValue(int width, intheight)
{
m_width = width;
m_height = height;
}
----4.3.1---- 布尔变量与零值比较
不可将布尔变量直接与 TRUE、FALSE或者 1、0进行比较。
根据布尔类型的语义,零值为“假”(记为FALSE),任何非零值都是“真”(记为TRUE)TRUE 的值究竟是什么并没有统一的标准。
假设布尔变量名字为flag,它与零值比较的标准if 语句如下:
if (flag)
// 表示flag 为真
if (!flag) // 表示flag 为假
----4.3.3 ----浮点变量与零值比较
epsilon [ˈɛpsəˌlɑn,-lən]希腊语的第五个字母
if ((x>=-EPSILON) &&(x<=EPSILON))
----5.2---- const 来定义常量比用#define来定义常量有更多的优点:
(1) const常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安
全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会
产生意料不到的错误(边际效应)
。
(2)有些集成化的调试工具可以对const 常量进行调试,
但是不能对宏常量进行调试。
----5.4----不能在类声明中初始化const数据成员。以下用法是错误的,因为类的对象未被创
建时,编译器不知道SIZE 的值是什么。
class A
{...
const int SIZE = 100; // 错误,企图在类声明中初始化const 数据成员
int array[SIZE];
// 错误,未知的SIZE
};
const 数据成员的初始化只能在类构造函数的初始化表中进行,例如
class A
{...
A(int size);
// 构造函数
const int SIZE ;
};
A::A(int size) : SIZE(size)
// 构造函数的初始化表
{
...
}
A a(100); // 对象a 的SIZE 值为100
----6.3---- 创建一个临时对象并返回它
return String(s1 + s2);
这是临时对象的语法,表示“创建一个临时对象并返回它”
。不要以为它与“先创建
一个局部对象 temp并返回它的结果”是等价的,如
String temp(s1 + s2);
return temp;
实质不然,上述代码将发生三件事。首先,temp对象被创建,同时完成初始化;然
后拷贝构造函数把 temp拷贝到保存返回值的外部存储单元中;最后,temp在函数结束
时被销毁(调用析构函数)
。然而“创建一个临时对象并返回它”的过程是不同的,编译
器直接把临时对象创建并初始化在外部存储单元中,省去了拷贝和析构的化费,提高了
效率。
----7.8----malloc 与free 是C++/C 语言的标准库函数,new/delete是 C++的运算符。它们都可
用于申请动态内存和释放内存。
对于非内部数据类型的对象而言,光用maloc/free 无法满足动态对象的要求。对象
在创建的同时要自动执行构造函数,对象在消亡之前要自动执行析构函数。由于
malloc/free是库函数而不是运算符,不在编译器控制权限之内,不能够把执行构造函数
和析构函数的任务强加于malloc/free。
因此 C++语言需要一个能完成动态内存分配和初始化工作的运算符new,以及一个
能完成清理与释放内存工作的运算符delete。注意new/delete 不是库函数。
由于内部数据类型的
“对象”
没有构造与析构的过程,
对它们而言 malloc/free和 new/delete
是等价的。
----7.8----new/delete 使用要点
class Obj{
public:
Obj(void);
Obj(int x);
}
用new创建对象数组,只能使用对象的无参数构造函数
Obj *a = new Obj;
Obj *b = new Obj(2);
Obj *arr = new Obj[100];
Obj *arr = new Obj[100](2); //错误
delete对象数组,别忘了[]
delete []arr;
delete arr; //相当于deletearr[0]
----8.1.2----函数被C 编译器编译后在库中的名字跟 被C++编译器编译后在库中的名字是不同的。C++程序要调用已经被编译后的C 函数,要用extern“C”
假设某个 C函数的声明如下:
void foo(int x, int y);
该函数被 C编译器编译后在库中的名字为_foo,而C++编译器则会产生像_foo_int_int
之类的名字用来支持函数重载和类型安全连接。由于编译后的名字不同,C++程序不能
直接调用 C函数。C++提供了一个C 连接交换指定符号extern“C”来解决这个问题。
例如:
extern “C”
{
void foo(int x, int y);
... // 其它函数
}
或者写成
extern “C”
{
#include “myheader.h”
... // 其它C 头文件
}
这就告诉 C++编译译器,函数foo 是个C 连接,应该到库中找名字_foo而不是找
_foo_int_int。C++编译器开发商已经对C 标准库的头文件作了extern“C”处理,所以我
们可以用#include直接引用这些头文件。
----8.2.1----重载函数实参隐型类型转换
/**********************************************************************
* Compiler: GCC
* Last Update: Mon 23 Apr 201203:43:56 PM CST
************************************************************************/
#include <stdio.h>
void f(int a)
{
}
void f(float a)
{
}
void g(int a)
{
printf("g inta\n");
}
void g(unsigned char a)
{
printf("g inta\n");
}
int main(int argc, char **argv)
{
//f(1.1); //error:call of overloaded ‘f(double)’ is ambiguous
g('a');
return 0;
}
C++实参数型转换,通过类型提升优于 通过 标准转换 (详见C++Primer第4版P234)
对于C++,整数字面值默认为int,浮点字面值默认为double.
1.1既可以通过标准转换转换成float,也可以转换成int型,产生二义性。
而'a'通过类型提升可转换为int型,转换为 unsignedchar属于类型转换,故'a'优先转换为int
运行结果:
g int a
----8.2----成员函数的重载、覆盖与隐藏
/**********************************************************************
* Compiler: GCC
* Last Update: Mon 23 Apr2012 03:43:56 PM CST
************************************************************************/
#include <iostream>
using namespace std;
class Base
{
public:
virtual void f(floatx)
{
cout<< "Base::f(float) " << x << endl;
}
void g(float x)
{
cout<< "Base::g(float) " << x << endl;
}
void h(float x)
{
cout<< "Base::h(float) " << x << endl;
}
};
class Derived : public Base
{
public:
virtual void f(floatx)
{
cout<< "Derived::f(float) " << x << endl;
}
void g(int x)
{
cout<< "Derived::g(int) " << x << endl;
}
void h(float x)
{
cout<< "Derived::h(float) " << x << endl;
}
};
int main(int argc, char* argv[])
{
Derived d;
Base *pb = &d;
Derived *pd = &d;
pb->f(3.14f); //Derived::f(float) 3.14
pd->f(3.14f); //Derived::f(float) 3.14
cout<<"****************************************"<<endl;
// Bad : behaviordepends on type of the pointer
pb->g(3.14f); //Base::g(float) 3.14
pd->g(3.14f); //Derived::g(int) 3 (surprise!)
cout<<"****************************************"<<endl;
// Bad : behaviordepends on type of the pointer
pb->h(3.14f); //Base::h(float) 3.14 (surprise!)
pd->h(3.14f); //Derived::h(float) 3.14
cout<<"****************************************"<<endl;
}
基类类型的指针可以指向派生类型对象(引用也同个道理),所以使用基类类型指针时,不知道指针所指的对象是什么。无论指向的对象是什么,编译器将它当作基类类型对象。当通过该指针调用非虚函数时,无论实际对象是什么,都执行基类类型定义的函数,如果是调用函数,则在运行时根据实际指向的对象去动态调用相应的函数。(详见C++Primer第4版P487)
pd是派生类指针,pd的所有函数调用都只是调用自己的函数,和多态性无关。
pb的函数调用如果有virtual则根据多态性调用派生类的,如果没有virtual则是正常的静态函数调用,还是调用基类的
----隐藏----示例:
/**********************************************************************
* Compiler: GCC
* Last Update: Mon 23 Apr2012 03:43:56 PM CST
************************************************************************/
#include <iostream>
using namespace std;
class Base
{
public:
void f(int x);
};
class Derived : public Base
{
public:
void f(char *str);
};
int main(int argc, char* argv[])
{
Derived *pd = newDerived;
pd->f(10);// error . Base::f(int)被 Derived::f(char*)隐藏了
return 0;
}
----8-3-1----参数缺省值只能出现在函数的声明中,而不能出现在定义体中。
----8.4----如果运算符被重载为全局函数,那么只有一个参数的运算符叫做一元运算符,有两个参数的运算符叫做二元运算符。
如果运算符被重载为类的成员函数,那么一元运算符没有参数,二元运算符只有一个右侧参数,因为对象自己成了左侧参数。
----8.5.1 ----函数内联是如何工作的
对于任何内联函数,编译器在符号表里放入函数的声明(包括名字、参数类型、返回值类型)。如果编译器没有发现内联函数存在错误,那么该函数的代码也被放入符号表里。在调用一个内联函数时,编译器首先检查调用是否正确(进行类型安全检查,或者进行自动类型转换,当然对所有的函数都一样)。如果正确,内联函数的代码就会直接替换函数调用,于是省去了函数调用的开销。这个过程与预处理有显著的不同,因为预处理器不能进行类型安全检查,或者进行自动类型转换。假如内联函数是成员函数,对象的地址(this)会被放在合适的地方,这也是预处理器办不到的。
inline仅仅是对编译器的建议,编译器有权利忽略这个建议。那么编译器是如何决定函数内联与否呢?一般情况下关键性因素包括函数体的大小,是否有局部对象被声明,函数的复杂性等等。
----8.5.2----关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用