您的位置:首页 > 编程语言 > C语言/C++

讨论记录之C++细节

2008-09-17 22:32 239 查看
Participants:LF,HZP,CPP,ZY
Date:08-09-16 7:20PM
Recorder: CPP,ZY
参考文献:
1、《effective C++》2nd Edition,Scott Meyers etc.
2、《C++程序设计教程》,钱能
3、《高质量C++C编程指南》,林锐
4、http://keith.ecjtu.com/article.asp?id=319
应大家的要求,今天晚上开始了我们的第一次讨论会。
主要是针对C++里面的一小撮问题展开的,这里我给出讨论的概要:
1、关于优先级与结合性;(这里要重点“批”一下HZP和ZY,正号和加号都分不清的家伙…)(顶)
2、#define /inline/const(顺便涉及到inline 和 virtual的连用问题);
3、const的作用(包括修饰类的成员函数,成员函数返回值,成员函数的参数列表,数据成员);
4、重载、覆盖(重写/改写,实现多态)以及隐藏的区别;
5、构造函数和析构函数
6、关于虚拟函数、虚基类及多继承
1[/b]、优先级口诀[/b]:(除了标明是右结合外,都是左结合)
括号成员第一;//[]、()
全体单目第二;//比如++、--、+(正号)、-(负号)、指针运算符* & 右结合
乘除余第三;//取余 左结合
移位五,关系六;
等于不等排第七;
位与亦或和位或;//&、 ^、|
逻辑或跟与;//&&、||
条件高于赋值;//注意的是赋值运算符很多,包括= 、*=、 /=、 +=、 -= 、|=、 <<=和>>= 二者都是右结合
逗号排最后。
上面是C中的规则,而C++由于引入了一些新的运算符,因此,有些出入,如表1:



表1 C++ 运算符优先级列表
见两个例子:
(1) int x=1,y=0;
!x&&x+y&&++y;
加括号确定优先级的方法
  当多个优先级不同的运算符在一起时,为了不混淆,可以先加上括号,这样就分出层次了,相同层次的考虑结合性问题,当确定下来先算那块时,再往这块里面深入。例如上面的例子,我们可以这样加上括号:从左向右看,由于!比&&优先级高,所以有(!x),又由于&&比+优先级低,所以有(x+y),而++优先级高于&&,所以(++y)。这样整个式子就变成了:(!x)&&(x+y)&&(++y),最外层的是两个&&运算,由于&&的结合性是从左至右,所以上式可看成:A&&B&&C,先计算A,再计算B,最后算C.由于x=1,则!x就为假,后面的就不需要再算了,整个语句的值为假。执行完后,y的值没变,还是0.
  所以碰到不清楚先算谁后算谁时,先加个括号看看,就明白了先后次序。
(2)给语句c=a>b?a:b;加括号。此语句有三个运算符:=、>、? :,应该怎样加括号呢?
第一种方案:c=((a>b)?a:b);
  第二种方案:c=(a>(b?a:b));
  第三种方案:(c=a)>(b?a:b);
  应该是那一种呢?按照运算符优先级的高低顺序,>优先级高于=,所以不可能把(c=a)括起来。而>优先级高于? :运算符。所以也不可能把(b?a:b)括起来。因此,第一种答案正确。

2[/b]、尽量以[/b]const[/b]和[/b]inline[/b]取代[/b]#define[/b]
尽量以编译器取代预处理器或许更好,因为#define通常不被视为语言本身的一部分。
#define导致的结果就是程序内所使用的名称并未出现于符号表之中。可以改用常量来声明。
若是需要一个class专属常量,即将这个常量的scope局限于class 之内,必须让它成为一个member,而为了确保这个常量至多只有一份实体,则必须让他成为一个static member,例如:
class GamePlayer{
private:
static const int NUM;//仅仅是个声明而非定义

};
必须在类定义文件中定义该类成员:const int GamePlayer::NUM=5;
另一个误用#define指令的常见例子是,以它来实现宏——看起来像函数,却又不会带来函数调用所需的成本。经典例子就是计算两数的最大值:
#define max(a,b) ((a)>(b)?(a) : (b))
即使加上了小括号,还是会发生一个可怕的动作:
int a=5,b=0;
max(++a,b);//a被累加两次
max(++a,b+10);//a被累加一次
这时,我们可以使用inline函数,既可以得到宏带来的高效率以及函数带来的可预期行为和类型检验。例如:
inline int max(int a, int b) {return a>b?a:b; }
这与前述宏并不完全相同,因为这个版本的max只接受int类型参数,不过,template可以修正这一问题,这里by reference相比by value可以获取更高的效率。
template<class T>
inline const T& max(const T& a, const T&b)
{return a>b?a:b; }

3[/b]、[/b]const[/b]的作用[/b][/b]
(1)修饰类的成员函数时:
即在成员函数声明时将const置于成员函数参数列表后分号前,代表它不能对类的数据成员进行修改,但是有一个例外,就是当数据成员前有mutable修饰时,它是可以被该函数修改的;
(2)修饰成员函数返回值及函数参数时:
意即被修饰的量是不可被修改的,前者意味着在成员函数返回后得到的值不可被更改,后者意味着不能在函数体内对参数进行变动,只能读取它;
(3)对于类中的const常量,它只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。
不能在类声明中初始化const数据成员,只能在类构造函数的初始化表中进行,例如:
class A
{…
const int SIZE=100;//错误,企图在类声明中初始化const数据成员
int array[SIZE];//错误,位置的SIZE
};
应该是:
class A
{
A(int size);
const int SIZE;
};
A::A(int size):SIZE(size){

}
若要建立在整个类中都恒定的常量,需要用枚举常量来实现,例如:
class A{
enum{SIZE1=100,SIZE2=200};//
int array1[SIZE1];
int array2[SIZE2];
};
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数.(PI=3.14159)

4[/b]、重载[/b](overload)[/b]、覆盖[/b](override)[/b]以及隐藏[/b][/b]
本来是讨论多态的,但是我们又讲到了重载这个概念,对于一个类中的成员函数,其被重载的特征:
(1)相同的范围(同一个类中);
(2)函数名相同,参数列表不同,返回值类型可相同也可不同。
覆盖是指派生类函数覆盖基类函数,其特征:
(1)不同范围;
(2)函数名字相同,参数列表相同,
(3)基类必须要有virtual关键字。
除覆盖外,所有同名的基类函数与子类函数都属于隐藏,下面是一个例子,讲得比较清楚,也点出了问题的本质:

class Base

{

public:

Base();

virtual ~Base();

public:

virtual void f(float x)

{

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:

Derived();

virtual ~Derived();

public:

virtual void f(float x)

{

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;

}

};

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 调用派生类函数

pb->g(3.14f); // Base g(float)3.14 (!) 调用基类函数

pd->g(3.14f); // Derived g(int)3 调用派生类函数

pb->h(3.14f); // Base h(float)3.14 (!) 调用基类函数

pd->h(3.14f); // Derived h(float)3.14 调用派生类函数



总结:[/b][/b]
当基类函数和子类函数间的关系为“覆盖”时,是根据对象类型来调用相应的函数;[/b][/b]
而当基类函数和子类函数间的关系为“隐藏”时,是根据指针类型来调用相应的函数。[/b][/b]

5[/b]、构造函数和析构函数[/b][/b]
几乎每个类都会有一个或多个构造函数,一个析构函数。前者用来建立和初始化对象,只要对象建立,它马上被调用,给对象分配空间和初始化。
如果一个类没有专门定义构造函数,那么C++就仅仅创建对象而不作任何初始化。
(1) 假设一个类的构造函数一个都未提供,则C++提供一个默认的构造函数,该默认构造函数是个无参构造函数,它仅负责创建对象,而不做任何初始化工作;
(2) 只要一个类定义了一个构造函数(不一定是无参构造函数),C++就不再提供默认的构造函数,也就是说,如果为类定义了一个带参数的构造函数,还想要无参构造函数,则必须自己定义。
(3) 与变量定义类似,在用默认构造函数创建对象时,如果创建的是全局对象或静态对象,则对象的位模式全为0,否则,对象是随机的。
(4) 如果类内动态分配内存,需为此类声明一个copy constructor和assignment运算符,请看图1:
Class String{
public:
String(const char *value);
~String();

private:
char *data;
};
Assignment运算符:
这个class之中并无assignment运算符或是copy constructor,这样子会带来严
重后果,假设我们定义了两个对象,形式如下:
String a(“Hello”);
String b(“World”);
对象a内有一个指针,指向一块内含字符串“Hello”的内存。对象b也是内含了一个指针,指向一块内含字符串“World”的内存。现在执行assignment动作:b=a;
由于自定义的operator=并不存在,所以C++产生一个默认的assignment运算符并调用它。这个默认的assignment运算符对着a的menmbers执行一个member一个member的逐次赋值动作,将内容赋给b的members。对指针(a.data和b.data)而言,其实就只是一个bit一个bit的逐次拷贝动作。Assignment后的结果就如图2所示:



a
data
H e l l o /0
b
data
World /0

图1



a
data
H e l l o /0
b
data
World /0
图2
这样的状况至少存在两个问题,第一,b原先所指的内存并没有被释放掉;它永远遗失了,这是一个典型的memory leak问题,第二,a和b内含的指针如今指向相同的字符串,因此,当其中一个离开生存空间时,其destructor会删除内存,而此内存目前仍被另一个指针所指,例如:
String a(“Hello”);//定义并构造a
{
String b(“World”);//打开新的作用域

b=a; //执行默认的operator=,b的内存泄漏了
} //作用域借宿,调用b的destructor
String c=a; //c.data没有定义!因为a.data已被删除!
Copy constructor:
只要程序中有pass-by-value的动作,就会调用copy constructor,例如:
Void doNothing(String localString){}
String s=”The truth is out there”;
doNothing(s);
由于localString是以by value的方式传递,它必须使用缺省的copy constructor,以s为本,进行初始化,因此localString有一个“s所含指针”副本,当doNothing完成任务时,localString退离其scope,于是它的destructor被调用,后果就是:s内含的指针指向一块已被localString删除的内存。(在一个已被删除内存的指针身上再施行delete动作,其结果未定义)
总结:[/b][/b]
如果类拥有任何指针,需撰写自己的[/b]copy constructor[/b]和[/b]assignment operator[/b],在这两个函数中将指针所指之数据结构做一份复制品,使每个对象拥有属于自己的一份拷贝。(即深拷贝和浅拷贝问题,就是资源是否也被复制问题)[/b][/b]
堆内存并不是唯一需要拷贝构造函数的资源,但它是最常用的一个。打开文件,占有硬件设备服务等也需要深拷贝,它们也是析构函数必须返还的资源类型。因此一个很好的经验是:如果你的类需要一个自定义的析构函数,则它也需要一个拷贝构造函数。[/b]

6[/b]、关于虚拟函数、虚基类及多继承[/b][/b]
可以参考这里:
http://hi.baidu.com/abby_tang/blog/item/4435dbcbd1c74440f21fe7be.html

全文由 ZY 完成
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: