Chinaunix首页 | 论坛 | 博客
  • 博客访问: 84241
  • 博文数量: 21
  • 博客积分: 371
  • 博客等级: 一等列兵
  • 技术积分: 225
  • 用 户 组: 普通用户
  • 注册时间: 2012-11-15 21:32
文章分类

全部博文(21)

文章存档

2013年(5)

2012年(16)

我的朋友

分类: C/C++

2013-02-03 23:15:19

1. 在C++中,通过基类的引用(或者指针)调用虚函数时,反生动态绑定.引用(或者指针)既可以指向基类对象,也可以指向派生类对象,这一事实是动态绑定的 关键.用引用调用的虚函数在运行时确定,被调用的函数是引用(或者是指针)所指对象的实际类型所定义.(保留字只能在类的内部声明中,定义中不能有)

 2. 基类应该将派生类需要重定义的任意函数定义为虚函数

3. 可以认为protected访问标号是private和public的混合:

       ● 像private成员一样,protected成员不能被类的用户访问

       ● 像public成员一样,protected成员可被该类的派生类访问

       ● 派生类只能通过派生类对象访问其基类的protected成员,派生类对其基类类型对象的protected成员没有特殊的访问权限

4. 派生类中虚函数的声明必须与基类中的定义方式完全匹配,但也有一个例外:返回对基类类型的引用(或指针)的虚函数,派生类中的虚函数可以返回基类函数所返 回类型的派生生类的引用(或指针).(按照我的理解这句话的意思说白了就是:当虚函数在基类中返回基类的引用(或指针),在派生类继承定义时,可以返回基 类或者派生类对象的指针(或引用).而且,一旦函数在基类中声明为虚函数,他就一直为虚函数.派生类无法改变该函数为虚函数这一事实.派生类重载定义虚函 数时,可以使用virtual保留字,但不是必须这样做.

 5. C++中的函数调用默认不使用动态绑定.满足动态绑定的两个条件: ? 只有指定为虚函数的成员函数才能进行动态绑定,成员函数默认为非虚函数,所以不进行动态绑定? 必须通过基类类型的引用或者指针进行函数调用

6. 在某些情况下,希望覆盖虚函数机制并强制函数调用使用虚函数的特定版本.这时可以使用作用域操作符.并且只有成员函数中的代码才应该使用作用域操作符覆盖的虚函数机制

7. 像其他任何函数一样,虚函数也可以有默认实参.通常,如果有用在给定调用中的默认实参值,该值将在编译时确定.如果一个调用省略了具有默认值的实参,则所 有的值由调用该函数的类型定义,与对象的动态类型无关.通过基类的引用或指针调用虚函数时,默认实参为在基类虚函数声明中指定的值,如果通过派生类的指针 或者引用调用虚函数,则默认实参实在派生类的版本中声明的值. 这段话比较难理解,我的理解是: 程序输出:

8. 继承原则:

      ◆ 如果成员在基类中为private,则只有基类和基类的友元可以访问访问该成员.派生类不能访问基类的private成员,也不能使自己的用户能够访问哪些成员

      ◆ 如果是public或者protected,则派生类列表中使用的访问标号决定该成员在派生类中的访问级别: ?

        如果是公有继承,基类成员保持自己的访问级别:基类的public成员为派生类的public,基类的protected成员为派生类的protected成员?

        如果是受保护继承,基类的public和protected成员在派生类中为protected成员? 如果是私有继承,基类的所有成员在派生类中为private成员

9. 继承与组合:前者反映”是一种(Is a)”的关系,后者是”有一个(has a)”的关系.

10. 使用class保留字定义的派生类默认具有private继承,而用struct保留字定义的类默认具有public继承

11. 友元关系不能继承.基类的友元对派生类的成员没有特殊的访问权限.如果基类被授予友元关系,则只有基类具有特殊访问权限,该基类的派生类不能访问授予友元关系的那个类.

 12. 如果基类定义了static成员,则整个继承层次中只有一个这样的成员.无论从基类派生出多少个派生类,每个static成员只有一个实例.如果成员在基类中为private,则派生类不能访问它.

13. 存在从派生类型引用到基类类型引用的自动转换,但是不存在反向转换. 将派生类对象传给希望接受引用的函数时,引用直接绑定到该对象,虽然看起来是在传递对象,但实际上实参是该对象的引用.对象本身并没有复制.并且转换不会 在任何方面改变派生类型对象,该对象仍是派生类对象. 将派生类对象传递给接收基类类型对象的函数时,形参是固定的───在编译和运行时形参都是基类对象

14. 构造函数和赋值控制成员不能继承,每个类定义自己的构造函数和复制控制成员.

15. 构造函数初始化列表为类的基类和成员提供初始值,它并不指定初始化的执行顺序.首先初始化基类,然后根据声明次序初始化派生类成员 16. 如果派生类显示定义了自己复制构造函数或者赋值操作符,则该定义将完全覆盖默认定义,被继承类的复制构造函数和赋值操作符赋值对基类成员以及类自身的成员 进行复制或者赋值.

17. 析构函数的工作与赋值构造函数和赋值操作符不同:编译器总是显示调用派生类对象基类部分的析构函数,因为其本身不会撤销基类的对象成员对象的撤销顺序与构 造函数顺序相反:首先运行派生类析构函数,然后按继承层次依次向上调用各基类析构函数如果删除基类指针,则需要运行基类析构函数并清除基类的成员.如果对 象实际上派生类型的,则没有定义该行为.要保证运行适当的析构函数,基类中的析构函数必须为虚函数,如果基类的析构函数为虚函数,则派生列析构函数也定为 虚函数. 即使析构函数没有工作要做,继承层次的根类也应该定义一个虚析构函数另外,我们一般不要将赋值操作符设置为虚函数(主要是为了避免混淆).

18. 在析构和构造的时候,可能造成类型的不完整性,(主要是在构造和析构期间的对象类型对虚函数的绑定有影响. 如果在构造函数和析构函数中调用虚函数,则运行的是为构造函数或析构函数自身类型定义的版本.

19. 与基类成员同名的派生类成员将屏蔽对基类成员的直接访问.我们在设计派生类的时候,只要可能最好避免与基类成员的名称冲突.

 20. 在基类和派生类中使用同一名字的成员函数,其行为与数据成员一样:在派生类作用域中派生类成员将屏蔽基类成员.即使函数原型不同,基类成员也会被屏蔽.如果派生类不存在该函数时,就考虑基类函数.

21. 派生类不用重定义所继承的每一个基类版本,他可以为重载成员提供Using声明.一个Using声明只能指定一个名字,不能指定形参表,因此,为基类成员 函数名称而做的Using声明将该函数所有重载实例加到派生类的作用域.将所有名字加入作用域后,派生类只需要重定义本类型确实必须定义的那些函数,对其 他的版本可以使用继承定义的.

22. 理解C++中继承层次的关键在于理解如何确定函数调用.确定函数调用遵循一下四个步骤:

      ◆ 首先确定进行函数调用的对象,引用或者指针的静态类型.

      ◆ 在该类中查找函数,如果找不到,就在基类中查找.如此循着累的集成链往上找.

      ◆ 找到了改名字,就立即进行类型检查.

      ◆ 假定调用合法,编译器便生成代码

23. 含有(或继承)一个或多个纯虚函数的类是抽象类.除了作为抽象基类的派生类的对象的组成部分,不能创建抽象类型的对象.

24. 因为派生类对象在赋值给基类对象时会被”切掉”,所以容器与通过继承相关联的类型不能很好的融合.

25. 句柄类:提供到其他类的接口的类,句柄类存储和管理基类指针. 包装了继承层次的句柄有两个重要的设计考虑因素:

      ● 像对任何保存指针的类一样,必须确定对赋值控制做些什么.包装了继承层次的句柄通常表现的像一个只能指针或者像一个

      ● 句柄类决定句柄接口屏蔽还是不屏蔽继承层次,如果不屏蔽继承层次,用户必须了解和使用基本层次中的对象


15.1 面向对象编程:概述
    继承:

    虚函数:virtual

    动态绑定:


15.2 定义基类和派生类
    成员限制符:public private protected

    protected:在子类中可访问,派生类内部可以访问本类对象protected成员,不能访问基类对象protected成员

复制代码
class baseprotectedstring name;};

class item : public base
{
    void test(item &a , base &b)
    {
        a.name; // 可以访问本类对象protected成员

        b.name; // 错误,不能访问基类对象protected成员
    }
}
复制代码

 

    C++允许多重继承,例如 class item : public base1, base2...

    原则上子类重写父类虚函数时声明和定义要于父类完全一致,但有一个例外:虚函数返回值是父类的指针或引用 可以在子类中将返回改成子类的指针或引用:比如父类有虚函数:base *test(); 子类可重写成: item *test();


    声明一个包含派生列表的类(而不实现)是错误的。
    class item: public base;

 

    动态绑定需要符合两个条件:调用函数必须是virtual ;必须要通过指针或引用调用虚函数。  动态绑定时执行函数取决于实际执行的类型,而不取决于指针或引用变量类型。

复制代码
// item 是子类
item b;
base a = b;
a.show(); // 不会动态绑定,a不是指针也不是引用。调用变量a的类方法(a类是base 所以调用base版方法)


base *a = &b;
a->show(); // 动态绑定,调用item版方法,调用实际数据的类方法(实际数据b是item类)


base &a = b;
a.show(); // 动态绑定,调用item版方法,调用实际数据的类方法(实际数据b是item类)
复制代码

    virtual函数版本是在运行时确定,非virtual函数是在编译时确定。

 

    也可以指定执行virtual函数版本如:

item b;
base *a = &b;
a->base::show(); // 指针指定调用base版方法


base &a = b;
a.base::show(); // 引用指定调用base版方法

 

    函数可以设定默认默认参数,默认参数定义的顺序为自右到左。即如果一个参数设定了缺省值时,其右边的参数都要有缺省值

 

    c++三种继承方式:public, private, protected 假设B类继承A类,即B类是A类的直接子类。
   public继承:A的访问属性在B类保持不变。
               A的public-----------B仍是public;       
               A的protected-------B仍是protected;
               A的private----------B无法访问(仍是private);
   protected继承:
               A的public-----------B变成protected;     
               A的protected-------B仍是protected;              
               A的private----------B无法访问(仍是private);
   private继承:
               A的public-----------B无法访问(变成private);     
               A的protected-------B无法访问(变成private);              
               A的private----------B无法访问(仍是private);

 

    派生类可以恢复继承的成员访问级别(只能恢复子类可访问的成员级别),但不能使被恢复成员的级别比他原来的还大。
复制代码
class base
{
    public
           void show(){};
           void show(int i){};

    protected:
           void log(){};          
};

class item : private base
{
    public:
           using base::show; // 可以恢复所有重载版本到子类

           using base::log; // 错误不能使被恢复成员的级别比他原来的还大
}
复制代码

 

    派生类继承基类默认级别是由派生类决定,如果派生类是struct则默认是public,若是class则是private。

    class a : b  // prvate 继承
    struct a : b // public 继承

 

    基类的友元关系是无法被子类继承的,所以要想基类的友元类访问子类的私有成员需要在子类中定义友元关系。


15.3 基类到派生类的转换
    基类对象和派生类之间有单向转换关系。派生类可以转换成基类反过来则不允许。因为基类里的成员派生类中都包含所以转换无错,但派生类中所有对象基类并不全部包含所以转化会失败。
    一个基类引用或指针指向派生类时实际执行的是派生类的代码。
    一个基类对象指向派生类时会发生拷贝赋值操作,用派生类中数据成员初始化或赋值基类对应成员,而方法成员还是使用基类版本。所以这种情况下不会发生动态绑定virtual函数。


15.4 构造函数和复制控制

    缺省情况下派生类创建对象时会先调用基类的默认构造函数,然后再调用自己的构造函数。
    也可以在派生类构造函数中显示调用基类某个构造函数,甚至给基类构造函数传参。调用语法是

class item : public base
{
    public:
           item (int age,string name) : base(age,name),prage(age),prname(name) {}; // 调用基类构造函数并传参, 初始化本类成员

}

 

    派生类只能调用直接基类构造函数。 如果不显示调用基类构造函数则基类一定要有默认构造函数否则会产生编译错误。

    复制构造函数有点不同:子类使用合成复制构造函数则先调用基类默认构造函数再调用子类合成复制构造函数。如果定义了子类的复制构造则一定要显示调用基类赋值构造函数。否则会出现 子类成员是被复制对象副本,而基类成员却未初始化。

class item : public base
{
    public:
           item (cosnt item &it) : base(it) ... {}; // 一定要调用基类复制构造函数base(it)

}

    赋值操作同复制类似,如果派生类定义了自己的赋值操作一定要显示为基类进行赋值

复制代码
class item : public base
{
    public:
           item &operator=(const item &it)
           {
               base:: operator=(it); //
 显示调用基类赋值操作
               
//... 


            }; 
}
复制代码


    析构函数无论如何总是会调用父类的析构函数。析构函数运行顺序和构造函数相反,总是先运行子类析构函数再运行父类析构函数。

复制代码
class one
{
    public: ~one(){ cout << "end one" << endl;};  one{ cout << "init one" << endl;};         
}

class two : public one
{
    public: ~tow(){ cout << "end two" << endl;};  tow(){ cout << "init two" << endl;};         
}

class three : public two
{
    public: ~three(){ cout << "end three" << endl;}; three(){ cout << "init three" << endl;};           
}

three b; // 此时依次输出 "init one"   "init two"   "init three"

one *a = &b;

// 当超过作用域时对象 b 被释放,依次输出 "end three"   "end two"   "end one" 
复制代码

     当定义three *a =&b,  a在回收时不会调用任何方法因为它是指针,只有释放对象b析构才能执行。


    但是有一种情况输出层级和指针有直接关系:动态对象,下面代码只会执行指针对象的析构函数。
    one *a = new three() ;
    delete a ; // 只输出"end one"

    如何才能输出 "end three"   "end two"   "end one"呢? 只要将类 one 中析构函数设置成虚析构函数即可 virtual ~one(){...} 。


    构造函数和赋值函数不要定义成虚函数,因为会让人混淆且没有什么用处。

 

15.5 继承情况下的类作用域
    子类可以定义和父类一样的非虚函数,此时子类会覆盖父类函数。
    和虚函数动态绑定不同,调用版本并不是由指向的数据类型决定,而是由申明变量类型决定。


    如果想调用父类成员需要如此调用

复制代码
itm a;
a.base::show(); // 调用base类的show方法


item *b =&a;
b->base::show();  // 调用base类的show方法


base *k = &a; 
k->base::show(); // 变量类型是base,但实际对象是item 所以需要b->base::show();
复制代码

 

15.6 纯虚函数
    纯虚函数申明很简单 void show()=0;拥有纯虚函数的类无法定义对象,但可以定义指针或引用。假设基类 base 定义了纯虚函数。
    base c ;  // 错误
    base *c = &b ;  // 正确
    base &c = b ;  // 正确


15.7 容器与继承
    容器对象可以定义成存放基类对象,但可以给容器加入子类对象,这时候子类会被转换成基类对象,或者说基类部分会被系统删除。
    可以定义基类指针或引用类型容器,再增加子类指针活引用,这时候会更具实际内容不同执行不同代码(动态绑定)。

 

15.8 句柄类与继承
    我们知道C++中最令人头疼的当属指针,如果您申请了对象却没有释放它,时间一长就会造成系统崩溃,大量的内存溢出使得您的程序的健壮性出现问题而句柄类就是为了能够解决这一问题而出现的,句柄类有点类似于智能指针。
 
    好了,废话不多说,我们来看代码,首先我们来看 head.h文件的代码:

复制代码
#ifndef HEAD_H
#define HEAD_H

#include
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::string;

//基类

class Item_base
{
    public:
    //基类的虚函数,用于智能地复制对象

    virtual Item_base* clone() const
    {
        return new Item_base(*this);
    }
};


//子类

class Bulk_item: public Item_base
{
    //子类的虚函数的重载,用于智能地复制对象

    virtual Bulk_item* clone() const
    {
        return new Bulk_item(*this);
    }
};

//句柄类

class Sales_item
{
public:
    //默认构造函数,用来初始化一个引用计数器(句柄类未绑定任何对象)

    Sales_item(): p(0), use(new size_t(0)) { cout << "Sales_item定义了空句柄" << endl;};
    
    //带有一个参数的,且该参数为基类引用的构造函数

    Sales_item( const Item_base &i): p(i.clone()), use(new size_t( 1 )) { cout << "Sales_item的引用计数器初始化为1" << endl; };
    
    
    
    //复制构造函数,需要注意的是,每复制一次就需要增加引用计数一次

    Sales_item( const Sales_item &i ): p(i.p), use(i.use) { ++*use;};

    void show(){cout<< "user: " << *use << endl;};
    
    //析构函数,析构的时候会判断是否能够释放指针所指向的数据

    ~Sales_item() { decr_use();};
    
    
    //赋值操作符重载

    Sales_item& operator= ( const Sales_item& );
    
    //访问操作符重载

    const Item_base* operator-> () const
    {
        if( p )
        {
            return p;
        }
        else
        {
            cout << "p指针错误" << endl;
        }
    };
    
    //解引用操作符重载

    const Item_base& operator* () const
    {
        if( p )
        {
            return *p;
        }
        else
        {   
            //重载虚函数,用于智能地复制对象

            cout << "p指针错误" << endl;
        }
    };
    
private:
    //两个指针存储着引用计数器以及数据的指针

    Item_base *p;
    size_t *use;
    
    //减少引用

    void decr_use()
    {
        if(*use == 0 && p == 0)
        {
            cout << "空句柄无需释放任何资源"<             return;
        }

        cout << "在 dec_use函数中引用计数减少了,当前计数值为:" << *use - 1 << endl;
        if( --*use == 0 )
        {
            delete p;
            delete use;
            cout << "在 dec_use函数中计数器减为0,释放对象" << endl;
        }
        
    };
};


//赋值操作符重载,每次复制都会增加引用计数

Sales_item& Sales_item::operator= ( const Sales_item &si )
{
    //
这里需要特别注意的就是待复制的对象的计数器需要加1而被赋值的对象需要减1    
    
    
//增加被复制对象的引用计数

    ++*si.use;

    //将即将被赋值的对象的引用计数减1

    decr_use();
    
    
    //复制指针

    p = si.p;
    use = si.use;
    
    //返回

    return *this;
};


#endif //HEAD_H
复制代码

 

    接下来我们来看mail.cc的代码:

复制代码
#include"head.h"

int main()
{
        // 被包装类(实际上包装的是这个对象的副本)

    Bulk_item item;

    Sales_item a(item); // 输出 : Sales_item的引用计数器初始化为1

    a.show(); // 输出 : user:1

    Sales_item b(a);
    a.show(); // 输出 : user:2

    b.show(); // 输出 : user:2

    Sales_item c; // 输出 : Sales_item定义了空句柄
    c.show(); // 输出 : user:0

    c = b; // 输出 : 空句柄无需释放任何资源

    c.show(); // 输出 : user:3
    b.show(); // 输出 : user:3
    a.show(); // 输出 : user:3
}

当main函数执行完毕,c最先被释放:
// 输出 :  在 dec_use函数中引用计数减少了,当前计数值为: 2


b被释放:
// 输出 :  在 dec_use函数中引用计数减少了,当前计数值为: 1


a被释放:
// 输出 :  在 dec_use函数中引用计数减少了,当前计数值为: 0
           在 dec_use函数中计数器减为0,释放对象

此时已经删除了被包装对象(item的副本)

最后item 对象被释放

 

    结论:我们可以看到,句柄类能够很方便并且能够很安全地释放内存,不会导致内存的泄露。

阅读(1966) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~