C++学习笔记

1.二义性和虚基类(转)
 定义基类CBase,并定义CBase的派生类CDerived1CDerived2,在定义CDerived1CDerived2的派生类CDerived12,观察二义性。 
   
   
代码如下:
/************************************************************************
* 二义性问题
***********************************************************************
*/

//基类
class CBase
{
    
int a;
public:
    
int x;
    
void func();
}
;

//派生类1
class CDerive1:public CBase
{
    
int b;
public:
    
int y;
    
void func1();
}
;

//派生类2
class CDerive2:public CBase
{
    
int c;
public:
    
int z;
    
void func2();
}
;

//子派生类
class CDerive12:CDerive1,CDerive2
{
    
int d;
public:
    
int yz;
    
int func12()
    
{
        x
=10;    //error C2385: 'CDerived12::x' is ambiguous
        func();    //error C2385: 'CDerived12::func' is ambiguous
    }

}
;

void main()
{
    CDerived12 obj;
}
    
    
说明:
对于x=10;语句提示的警告如下:
warning C4385: could be the 'x' in base 'CBase' of base 'CDerive1' of class 'CDerive12' or the 'x' in base 'CBase' of base 'CDerive2' of class 'CDerive12'
 
对于func();语句提示的警告如下:
warning C4385: could be the 'func' in base 'CBase' of base 'CDerive1' of class 'CDerive12' or the 'func' in base 'CBase' of base 'CDerive2' of class 'CDerive12'
 
解析:
当一个基类被一个派生类间接继承多次时,或者说在多条继承链路有公共的基类,那么该基类就会存在多个备份,系统无法分辨对基类成员的引用是通过哪个派生类继承来的。于是编译器对于这种不确定性问题发出错误信息。
上图的例子,4个类的继承关系如下:
  
   
    
   
所以,在函数func12中访问x或者func函数时,编译器就不知道是CDerive1CBase继承来的,还是CDerive2CBase继承来的。 
要注意二义性(ambiguity)检查是在访问控制或类型检查之前进行的,因此当不同基类成员中具有相同名字时就会出现二义性,即使只有一个名字可以被派生类访问或只有一个名字的类型与要求相符。
 
解决二义性问题的方法:
1.利用范围运算符指明所要调用的成员的类属范围;
2.在派生类中重新定义一个与基类中同名的成员,使该成员隐蔽基类的同名成员;
3.将公共基类说明为虚基类,避免在派生类中保留多个基类的备份,而只保存一个实例
 
虚基类:
虚基类的定义:在定义派生类时,要在基类描述前加关键字virtual。这称为虚基类机制
 
引入虚基类的原因:
防止二义性;
使派生类中只有公共基类的一个数据副本;

2.关于虚函数:
关于虚函数:
1)虚函数和非虚函数调用方式有什么不同
    非虚成员函数是静态确定的,换句话说,该成员函数在编译时就会被静态地选择。
    然而,虚成员函数是动态确定的,换句话说,成员函数在运行时才被动态地选择,该选择基于对象的类型,而不是指向该对象的指针或引用的类型。这被称作“动态绑定”。
2)虚函数和重载有什么不同
    虚函数看来于函数重载有些共通之处,但是函数重载在编译期间就可以确定下来我们要使用的函数,是可预测的;而虚函数在运行时刻才能确定到具体的函数,是不可预测的,对于虚函数这一特性有一个专用术语----晚绑定,运用虚函数这种方法叫做函数覆盖。
3)虚函数不可能是内联的,原因看起来似乎也很明显:
   (1)虚函数是在运行时机制而内联函数特性是一个编译时的机制;
   (2)声明一个内联的虚函数会使程序在执行的时间的产生多个函数拷贝,这将导致大量的空间的浪费

3.关于内联函数:
(1)什么是内联函数?
内联函数是指那些定义在类体内的成员函数,即该函数的函数体放在类体内。

(2)为什么要引入内联函数?
当然,引入内联函数的主要目的是:解决程序中函数调用的效率问题。另外,前面我们讲到了宏,里面有这么一个例子:
#define ABS(x) ((x)>0? (x):-(x))
当++i出现时,宏就会歪曲我们的意思,换句话说就是:宏的定义很容易产生二意性。
  
我们可以看到宏有一些难以避免的问题,怎么解决呢?前面我们已经尽力替换了。

下面我们用内联函数来解决这些问题。

(3)为什么inline能取代宏?
1、 inline 定义的类的内联函数,函数的代码被放入符号表中,在使用时直接进行替换,(像宏一样展开),没有了调用的开销,效率也很高。
2、 很明显,类的内联函数也是一个真正的函数,编译器在调用一个内联函数时,会首先检查它的参数的类型,保证调用正确。然后进行一系列的相关检查,就像对待任何一个真正的函数一样。这样就消除了它的隐患和局限性。
3、 inline 可以作为某个类的成员函数,当然就可以在其中使用所在类的保护成员及私有成员。

(4)内联函数和宏的区别?
内联函数和宏的区别在于,宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。而且内联函数是真正的函数,只是在需要用到的时候,内联函数像宏一样的展开,所以取消了函数的参数压栈,减少了调用的开销。你可以象调用函数一样来调用内联函数,而不必担心会产生于处理宏的一些问题。内联函数与带参数的宏定义进行下比较,它们的代码效率是一样,但是内联欢函数要优于宏定义,因为内联函数遵循的类型和作用域规则,它与一般函数更相近,在一些编译器中,一旦关上内联扩展,将与一般函数一样进行调用,比较方便。
(5)内联函数的优缺点?
我们可以把它作为一般的函数一样调用,但是由于内联函数在需要的时候,会像宏一样展开,所以执行速度确比一般函数的执行速度要快。当然,内联函数也有一定的局限性。就是函数中的执行代码不能太多了,如果,内联函数的函数体过大,一般的编译器会放弃内联方式,而采用普通的方式调用函数。(换句话说就是,你使用内联函数,只不过是向编译器提出一个申请,编译器可以拒绝你的申请)这样,内联函数就和普通函数执行效率一样了。

4.关于构造函数和析构函数:
     构造函数和析构函数是负责对象的创建和撤销的特殊成员函数。构造函数的作用是创建对象时进行初始化,析构函数的作用是释放对象时清理现场,二者作用相反,名称也正好相反。构造函数和析构函数之所以称为特殊的成员函数,是因为二者都没有类型说明符且程序中不能直接调用,在创建和撤销对象时由系统调用自动执行。

1) 构造函数

   构造函数的名称必须同类名一致,以便系统能识别为构造函数。当使用声明语句创建一个对象时隐含调用相应类的构造函数,为对象分配空间和初始化。
    构造函数的特点:
   造函数不能指定返回类型,函数体中不允许有返回值。
   1.构造函数的说明可以在类体内,也可以在类体外,放在类体外的构造函数名前要加上"类名::"。
   2.构造函数可以有一个或多个参数,也可以没有参数,当对象初始化时,需要定义带参数的构造函数。
   3.构造函数可以重载,一个类可以定义多个参数个数不同的构造函数。
   4.如果一个类没有定义任何构造函数,那么C++就自动建立一个默认的构造函数,仅创建对象而不作任何初始化。
   5.默认构造函数是空函数,无参数,不能重载。

2 )拷贝构造函数

   当用一个已知对象初始化另一个对象时,系统将自动调用拷贝构造函数进行对象之间的值的拷贝。拷贝构造函数实际上也是一种构造函数,其名称与类名相同。
   拷贝构造函数的特点如下:
   1.拷贝函数不能指定返回类型,函数体中不允许有返回值。
   2.拷贝函数的说明可以在类体内,也可以在类体外。放在类体外的拷贝函数名前要加上"类名::"。
   3.拷贝构造函数只有一个参数,并且该参数是所在类的对象的引用。
   4.拷贝构造函数的格式为: <类名>::<拷贝构造函数名>(const <类名> & <引用名>)。
   5.如果一个类没有定义任何拷贝构造函数,那么C++就自动建立一个默认拷贝构造函数。该函数的功能是将已知对象的所有数据成员的值拷贝给相应对象的所有数据成员。

3 )析构函数

   析构函数的名称与类名相同且前面加有非运算符"~",表明其功能和构造函数相反。
   析构函数的特点如下:
   1.拷贝函数不能指定返回类型,函数体中不允许有返回值。
   2.析构函数的说明可以在类体内,也可以在类体外。放在类体外的析构函数名前要加上"类名::"。
   3.析构函数没有参数,因此析构函数不能重载,一个类只能定义一个析构函数。
   4.如果一个类没有定义任何析构函数,那么C++就自动建立一个默认的析构函数,只执行清理任务。
   5.默认析构函数是空函数,无参数,不能重载。
   6.析构函数的调用次序和构造函数的调用次序正好相反。
   在以下情况下,析构函数会自动被调用:
   ⑴如果一个对象被定义在一个函数体内,当这个函数结束时,该对象的析构函数被自动调用。
   ⑵若使用new运算符动态创建一个对象,在使用delete运算符释放时,delete将会自动调用析构函数。


构造函数可以重载,析构函数不能重载(没有参数)

5.关于#define与const
从功能上讲都是对常量定义提供支持的一种机制,但是#define和const比起来还是有很多缺陷。

1) 在C++中采用const来定义常量,这样程序在编译的时候,提供安全类型检查,而define只是简单的进行了一个字符串替换。

2)很多集成开发环境都提供对const常量的调试,没有多define的调试。

3)const 定义的常量,都内存中会分配相应大小的空间,而define没有空间分配,只是在程序与处理的时候被替换掉。很有点类似word软件中的replace功能。

6.关于static:
C中static函数只有在其所在的文件内有效。
c++类静态数据成员与类静态成员函数:
1)类的静态成员是属于类的而不是属于哪一个对象的,所以静态成员的使用应该是类名称加域区分符加成员名称的。
2)静态成员函数的特性类似于静态成员的使用,同样与对象无关,调用方法为类名称加域区分符加成员函数名称。
3)静态成员函数由于与对象无关系,所以在其中是不能对类的普通成员进行直接操作的,静态成员函数与普通成员函数的差别就在于缺少this指针。

默认static成员会被编译器用0值初始化。静态成员不能在构造函数中初始化,因为静态成员是所有对象共享的,而构造函数是每一个对象产生都会被调用,静态函数初始化一般在CPP文件中,也就是在类的外部,而写法一般是:
返回值     类名::变量名=初值.有点象是定义变量.

静态成员是以static type 变量名,作为格式,在内存中,只存储一份,各个位置均可对其进行修改的成员。
  在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。因此,静态成员是类的所有对象中共享的成员,而不是某个对象的成员。
        使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。静态数据成员的值对每个对象都是一样,但它的值是可以更新的。只要对静态数据成员的值更新一次,保证所有对象存取更新后的相同的值,这样可以提高时间效率。
1)静态数据成员在定义或说明时前面加关键字static。
2)静态成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式如下:
<数据类型><类名>::<静态数据成员名>=<值>
这表明:
                (1) 初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆
                (2) 初始化时不加该成员的访问权限控制符private,public等。
                (3) 初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员。
3)静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
4)引用静态数据成员时,采用如下格式:
<类名>::<静态成员名>

静态成员的使用范围
1)用来保存对象的个数。
2)作为一个标记,标记一些动作是否发生,比如:文件的打开状态,打印机的使用状态,等等。
3)存储链表的第一个或者最后一个成员的内存地址

静态数据,只能在外部初始化,静态函数只能处理静态数据成员。常量在构造函数初始化时进行。如:
class A
{
public:
A():PI(3.14159){}
static void Count(){ icount++; }


private:
static int icount;
const double PI;
};

A::icount = 0;
A obj1;
A::Count;

*构造函数非公有,可以限制某些形式的类对象的创建。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值