Participants:LF,HZP,CPP,ZY<!----><o:p></o:p>
Date:<!----><st1:chsdate isrocdate="False" month="9" islunardate="False" day="16" w:st="on" year="2008">08-09-16</st1:chsdate> 7:20PM<o:p></o:p>
Recorder: CPP,ZY<o:p></o:p>
参考文献:<o:p></o:p>
1、《effective C++》2nd Edition,Scott Meyers etc.<o:p></o:p>
2、《C++程序设计教程》,钱能<o:p></o:p>
3、《高质量C++C编程指南》,林锐<o:p></o:p>
4、http://keith.ecjtu.com/article.asp?id=319<o:p></o:p>
<o:p> </o:p>应大家的要求,今天晚上开始了我们的第一次讨论会。<o:p></o:p>
主要是针对C++里面的一小撮问题展开的,这里我给出讨论的概要:<o:p></o:p>
1、关于优先级与结合性;(这里要重点“批”一下HZP和ZY,正号和加号都分不清的家伙…)(顶)<o:p></o:p>
2、#define \inline\const(顺便涉及到inline 和 virtual的连用问题);<o:p></o:p>
3、const的作用(包括修饰类的成员函数,成员函数返回值,成员函数的参数列表,数据成员);<o:p></o:p>
4、重载、覆盖(重写/改写,实现多态)以及隐藏的区别;<o:p></o:p>
5、构造函数和析构函数<o:p></o:p>
6、关于虚拟函数、虚基类及多继承<o:p></o:p>
1、优先级口诀:(除了标明是右结合外,都是左结合)<o:p></o:p>
括号成员第一;//[]、()<o:p></o:p>
全体单目第二;//比如++、--、+(正号)、-(负号)、指针运算符* & 右结合<o:p></o:p>
乘除余第三;//取余 左结合<o:p></o:p>
移位五,关系六;<o:p></o:p>
等于不等排第七;<o:p></o:p>
位与亦或和位或;//&、 ^、|<o:p></o:p>
逻辑或跟与;//&&、||<o:p></o:p>
条件高于赋值;//注意的是赋值运算符很多,包括= 、*=、 /=、 +=、 -= 、|=、 <<=和>>= 二者都是右结合<o:p></o:p>
逗号排最后。<o:p></o:p>
上面是C中的规则,而C++由于引入了一些新的运算符,因此,有些出入,如表1:
表<st1:chmetcnv tcsc="0" hasspace="True" sourcevalue="1" numbertype="1" negative="False" unitname="C" w:st="on">1 C</st1:chmetcnv>++ 运算符优先级列表<o:p></o:p>
见两个例子:<o:p></o:p>
(1) int x=1,y=0;<o:p></o:p>
!x&&x+y&&++y;<o:p></o:p>
加括号确定优先级的方法
当多个优先级不同的运算符在一起时,为了不混淆,可以先加上括号,这样就分出层次了,相同层次的考虑结合性问题,当确定下来先算那块时,再往这块里面深入。例如上面的例子,我们可以这样加上括号:从左向右看,由于!比&&优先级高,所以有(!x),又由于&&比+优先级低,所以有(x+y),而++优先级高于&&,所以(++y)。这样整个式子就变成了:(!x)&&(x+y)&&(++y),最外层的是两个&&运算,由于&&的结合性是从左至右,所以上式可看成:A&&B&&C,先计算A,再计算B,最后算C.由于x=1,则!x就为假,后面的就不需要再算了,整个语句的值为假。执行完后,y的值没变,还是0.
所以碰到不清楚先算谁后算谁时,先加个括号看看,就明白了先后次序。<o:p></o:p>
(2)给语句c=a>b?a:b;加括号。此语句有三个运算符:=、>、? :,应该怎样加括号呢?<o:p></o:p>
第一种方案:c=((a>b)?a:b);
第二种方案:c=(a>(b?a:b));
第三种方案:(c=a)>(b?a:b);
应该是那一种呢?按照运算符优先级的高低顺序,>优先级高于=,所以不可能把(c=a)括起来。而>优先级高于? :运算符。所以也不可能把(b?a:b)括起来。因此,第一种答案正确。<o:p></o:p>
<o:p> </o:p>
2、尽量以const和inline取代#define<o:p></o:p>
尽量以编译器取代预处理器或许更好,因为#define通常不被视为语言本身的一部分。<o:p></o:p>
#define导致的结果就是程序内所使用的名称并未出现于符号表之中。可以改用常量来声明。<o:p></o:p>
若是需要一个class专属常量,即将这个常量的scope局限于class 之内,必须让它成为一个member,而为了确保这个常量至多只有一份实体,则必须让他成为一个static member,例如:
class GamePlayer{<o:p></o:p>
private:
static const int NUM;//仅仅是个声明而非定义<o:p></o:p>
…<o:p></o:p>
};<o:p></o:p>
必须在类定义文件中定义该类成员:const int GamePlayer::NUM=5;<o:p></o:p>
另一个误用#define指令的常见例子是,以它来实现宏——看起来像函数,却又不会带来函数调用所需的成本。经典例子就是计算两数的最大值:<o:p></o:p>
#define max(a,b) ((a)>(b)?(a) : (b))<o:p></o:p>
即使加上了小括号,还是会发生一个可怕的动作:<o:p></o:p>
int a=5,b=0;<o:p></o:p>
max(++a,b);//a被累加两次<o:p></o:p>
max(++a,b+10);//a被累加一次<o:p></o:p>
这时,我们可以使用inline函数,既可以得到宏带来的高效率以及函数带来的可预期行为和类型检验。例如:<o:p></o:p>
inline int max(int a, int b) {return a>b?a:b; }<o:p></o:p>
这与前述宏并不完全相同,因为这个版本的max只接受int类型参数,不过,template可以修正这一问题,这里by reference相比by value可以获取更高的效率。<o:p></o:p>
template<class T><o:p></o:p>
inline const T& max(const T& a, const T&b)<o:p></o:p>
{return a>b?a:b; }<o:p></o:p>
3、const的作用<o:p></o:p>
(1)修饰类的成员函数时:<o:p></o:p>
即在成员函数声明时将const置于成员函数参数列表后分号前,代表它不能对类的数据成员进行修改,但是有一个例外,就是当数据成员前有mutable修饰时,它是可以被该函数修改的;<o:p></o:p>
(2)修饰成员函数返回值及函数参数时:<o:p></o:p>
意即被修饰的量是不可被修改的,前者意味着在成员函数返回后得到的值不可被更改,后者意味着不能在函数体内对参数进行变动,只能读取它;<o:p></o:p>
(3)对于类中的const常量,它只在某个对象生存期内是常量,而对于整个类而言却是可变的,因为类可以创建多个对象,不同的对象其const数据成员的值可以不同。<o:p></o:p>
不能在类声明中初始化const数据成员,只能在类构造函数的初始化表中进行,例如:<o:p></o:p>
class A<o:p></o:p>
{…<o:p></o:p>
const int SIZE=100;//错误,企图在类声明中初始化const数据成员<o:p></o:p>
int array[SIZE];//错误,位置的SIZE<o:p></o:p>
};<o:p></o:p>
应该是:<o:p></o:p>
class A <o:p></o:p>
{<o:p></o:p>
A(int size);<o:p></o:p>
const int SIZE;<o:p></o:p>
};<o:p></o:p>
A::A(int size):SIZE(size){<o:p></o:p>
…<o:p></o:p>
}<o:p></o:p>
若要建立在整个类中都恒定的常量,需要用枚举常量来实现,例如:<o:p></o:p>
class A{<o:p></o:p>
enum{SIZE1=100,SIZE2=200};//<o:p></o:p>
int array1[SIZE1];<o:p></o:p>
int array2[SIZE2];<o:p></o:p>
};<o:p></o:p>
枚举常量不会占用对象的存储空间,它们在编译时被全部求值。枚举常量的缺点是:它的隐含数据类型是整数,其最大值有限,且不能表示浮点数.(PI=3.14159)<o:p></o:p>
<o:p> </o:p>
4、重载(overload)、覆盖(override)以及隐藏<o:p></o:p>
本来是讨论多态的,但是我们又讲到了重载这个概念,对于一个类中的成员函数,其被重载的特征:<o:p></o:p>
(1)相同的范围(同一个类中);<o:p></o:p>
(2)函数名相同,参数列表不同,返回值类型可相同也可不同。<o:p></o:p>
覆盖是指派生类函数覆盖基类函数,其特征:<o:p></o:p>
(1)不同范围;<o:p></o:p>
(2)函数名字相同,参数列表相同,<o:p></o:p>
(3)基类必须要有virtual关键字。<o:p></o:p>
除覆盖外,所有同名的基类函数与子类函数都属于隐藏,下面是一个例子,讲得比较清楚,也点出了问题的本质:<o:p></o:p>