码迷,mamicode.com
首页 > 编程语言 > 详细

Standard C++ Episode 5

时间:2015-08-21 07:04:41      阅读:258      评论:0      收藏:0      [点我收藏+]

标签:

继承与多态
一、继承的基本概念
人类:姓名、年龄、吃饭
学生是人:学号、学习
教师是人:工资、讲课
    人类      - 基类,共性
   /    \       派生V^继承
学生     教师 - 子类,个性
二、继承的语法
class class_name : [继承表] {};
形如:
class 子类名 : 继承方式1 基类1, 继承方式2 基类2, ... {
  ...
};
继承方式:
公有继承 - public - 最常用方式
私有继承 - private - 缺省方式
保护继承 - protected - 特殊的私有继承
三、公有继承
1.通过继承,在基类中定义的任何成员,也都成为了子类的成员,但是基类的私有成员,子类虽然拥有却不能直接访问。
2.基类中的保护成员,可以被子类直接访问,但不能在无关的类和全局域中被访问。
3.任何一个子类对象中都包含着它的基类子对象。如果在子类的构造函数中没有明确指明其基类子对象如何被构造,系统将采用无参的方式构造该子对象。如果在初始化表中指明了基类子对象的构造方式,就调用相应的构造函数构造该子对象。
4.子类对象的构造和析构顺序
按照继承表的顺序依次构造每个基类子对象->按照声明的顺序依次构造每个成员变量->执行子类构造函数体中的代码
析构的过程与构造严格相反
5.一个子类对象在任何都可以被视为它的基类对象——IsA。
任何时候,一个子类对象的指针或者引用,都可以被隐式地转换为它的基类类型的指针或者引用,但是反过来,将基类类型的指针或者引用转换为它的子类类型必须显示地(static_cast)完成。
Student s (...);
Human* h = &s; //Student* 转换为 Human* 可以 隐式转换 OK !
6.在子类中定义的任何和基类成员同名的标识符,都可以将基类中的该成员隐藏起来。通过作用域限定操作符“::”,可对该成员解隐藏,也可以使用using关键字将指定命名空间的标志符引入当前作用域的形式,引入被隐藏的同名标志符。
(p.s.重载是编译期的概念。编译结束后重名的都被换名了。)
四、继承方式对访控属性的影响
class A {
X:
   void foo (void) { ... }
};
class B : Y A {
  void bar (void) {
    foo (); // 仅需考虑X
  }
};
int main (void) {
  B b (...);
  b.foo(); // 不仅要考虑X还要考虑Y
}
class C : Z B {
  void fun (void) {
    foo(); // 不仅要考虑X还要考虑Y
  }
};
当通过一个子类对象(B)访问它从基类(A)中继承过来的成员(foo)的时候,需要考虑子类(B)从基类(A)继承时所采用的继承方式。
1.访控属性
关键字    属性 基类 子类 外部 友员
public    公有  OK   OK   OK   OK
protected 保护  OK   OK   NO   OK
private   私有  OK   NO   NO   OK
2.基类的成员被继承到子类中以后,其访控属性会因不同的继承方式而异。
基类 公有继承 保护继承 私有继承
公有 公有     保护     私有
保护 保护     保护     私有
私有 私有     私有     私有
五、子类的构造函数和析构函数
1.子类隐式地调用基类的构造函数
在子类的构造函数中没有显示地指明其基类部分如何构造,隐式地调用基类的无参构造函数。如果子类没有定义任何构造函数,其缺省无参构造函数同样会隐式地调用基类的无参构造函数。
2.子类显式地调用基类的构造函数
在子类构造函数的初始化表中指明其基类部分的构造方式。
class A {
public:
  A (void) : m_data (0) {}
  A (int data) : m_data (data) {}
private:
  int m_data;
};
class B : public A {
public:
  B (int data) : A (data) {}
};
3.继承链的构造和初始化顺序
class A { ... };
class B : public A { ... };
class C : public B { ... };
C c (...);
构造:A->B->C
析构:C->B->A
任何时候子类中基类子对象的构造都要先于子类构造函数中的代码。
4.delete一个指向子类对象的基类指针,实际被执行的基类的析构函数,基类的析构函数不会调用子类析构函数,因此子类所特有的资源将形成内存泄漏。
Human* p = new Student (...);
delete p; // ->Human::~Human()
delete static_cast<Student*> (p);
六、子类的拷贝构造和拷贝赋值

子类的缺省拷贝构造和拷贝赋值除了复制子类的特有部分以外,还会复制其基类部分。如果需要自己定义子类的拷贝构造和拷贝赋值,一定不要忘记在复制子类特有部分的同时,也要复制其基类部分,否则将无法得到完整意义上的对象副本。

 1 /*
 2  * 子类的拷贝构造和拷贝赋值函数练习
 3  *
 4  * 子类的缺省拷贝构造和拷贝赋值除了复制子类的特有部分以外,还会复制其基类部分。
 5  * 如果需要自己定义子类的拷贝构造和拷贝赋值,一定不要忘记在复制子类特有部分的同时,也要复制其基类部分,否则将无法得到完整意义上的对象副本 
 6  */
 7 #include <iostream>
 8 using namespace std;
 9 class Human {
10 public:
11     Human (const string& name, int age) :
12         m_name (name), m_age (age) {}
13     void who (void) const {
14         cout << m_name << "" << m_age << endl;
15     }
16     void eat (const string& food) const {
17         cout << "我在吃" << food << endl;
18     }
19 protected:
20     string m_name;
21     int m_age;
22 };
23 class Student : public Human {
24 public:
25     Student (const string& name, int age, int no) :
26         Human (name, age), m_no (no) {}
27     Student (const Student& that) :
28         Human (that), m_no (that.m_no) {}
29     Student& operator= (const Student& that) {
30         if (&that != this) {
31             Human::operator= (that);//OK
32             //(*this).Human::operator=(that);//OK
33             //*this = that;//ERROR
34                         //(*this).operator=(that);//ERROR
35             m_no = that.m_no;
36         }
37         return *this;
38     }
39     void learn (const string& lesson) const {
40         cout << "我(" << m_name << "" << m_age
41             << "" << m_no << ")在学" << lesson
42             << endl;
43     }
44     using Human::eat;
45     void eat (void) const {
46         cout << "我绝食!" << endl;
47     }
48 //    int eat;
49 private:
50     int m_no;
51 };
52 int main (void) {
53     Student s1 ("张飞", 25, 1001);
54     s1.who ();
55     s1.eat ("包子");
56     s1.learn ("C++");
57     Human* h1 = &s1;
58     h1 -> who ();
59     h1 -> eat ("KFC");
60 //    h1 -> learn ("C");
61     Student* ps = static_cast<Student*> (h1);
62     ps -> learn ("C");
63     Student s2 = s1;
64     s2.who ();
65     s2.learn ("英语");
66     Student s3 ("赵云", 20, 1002);
67     s3 = s2;
68     s3.who ();
69     s3.learn ("数学");
70     return 0;
71 }

 

 

七、私有继承和保护继承
用于防止或者限制基类中的公有接口被从子类中扩散。
class DCT {
public:
  void codec (void) { ... }
};
class Jpeg : protected DCT { // Jpeg Has A DCT,实现继承
public:
  void render (void) {
    codec (...);
  }
};
Jpeg jpeg;
jpeg.codec (...); // ERROR !



class Jpeg2000 : public Jpeg {
public:
  void render (void) {
    codec (...); // OK !
  }

};


八、多重继承
从多于一个基类中派生子类。
电话 媒体播放器 计算机
       \          |           /
            智能手机  
1.多重继承的语法和语义与单继承并没有本质的区别,只是子类对象中包含了更多的基类子对象。它们在内存中按照继承表的先后顺序从低地址到高地址依次排列。

2.子类对象的指针可以被隐式地转换为任何一个基类类型的指针。无论是隐式转换,还是静态转换,编译器都能保证特定类型的基类指针指向相应类型基类子对象。但是重解释类型转换(reinterpret<目标类型>源类型),无法保证这一点。

3.尽量防止名字冲突,避免不必要的标识符形成隐藏。

 1     /* 
 2      * 多重继承例程  
 3      */  
 4     #include <iostream>  
 5     using namespace std;  
 6     class Phone {  
 7     public:  
 8         Phone (const string& numb) : m_numb (numb) {}  
 9         void call (const string& numb) {  
10             cout << m_numb << "致电" << numb << endl;  
11         }  
12         void foo (void) {  
13             cout << "Phone::foo" << endl;  
14         }  
15     private:  
16         string m_numb;  
17     };  
18     class Player {  
19     public:  
20         Player (const string& media) : m_media (media){}  
21         void play (const string& clip) {  
22             cout << m_media << "播放器播放" << clip  
23                 << endl;  
24         }  
25         void foo (int data) {  
26             cout << "Player::foo" << endl;  
27         }  
28     private:  
29         string m_media;  
30     };  
31     class Computer {  
32     public:  
33         Computer (const string& os) : m_os (os) {}  
34         void run (const string& prog) {  
35             cout << "" << m_os << "上运行" << prog  
36                 << endl;  
37         }  
38     private:  
39         string m_os;  
40     };  
41     class SmartPhone : public Phone, public Player,  
42         public Computer {  
43     public:  
44         SmartPhone (const string& numb,  
45             const string& media, const string& os) :  
46             Phone (numb), Player (media),  
47             Computer (os) {}  
48         using Phone::foo;  
49         using Player::foo;  
50     };  
51     int main (void) {  
52         SmartPhone sp ("13929081672", "MP3", "Android");  
53         sp.call ("18592880888");  
54         sp.play ("High歌");  
55         sp.run ("愤怒的小鸟");  
56         Phone* p1 =  &sp;//派生类类型的指针或引用可以隐式转换为基类类型指针或引用  
57         Player* p2 = &sp;  
58         Computer* p3 = &sp;  
59         cout << "&sp:" << &sp << endl
60              << "p1:"<< p1 << endl
61              << "p2:"<< p2 << endl
62              << "p3:"<< p3 << endl
63              << endl;
64         p1 = reinterpret_cast<Phone*> (&sp);
65         p2 = reinterpret_cast<Player*> (&sp);
66         p3 = reinterpret_cast<Computer*>(&sp);
67         cout << &sp <<  << p1 <<   << p2 <<  << p3 << endl;
68         sp.foo ();
69         sp.foo (100);
70 
71         return 0;
72     } 

 

4.钻石继承和虚继承
1)钻石继承
     A
   /     \
  B    C
   \     /
     D
class A { ... };
class B : public A { ... };
class C : public A { ... };
class D : public B,public C{ ...};
在最终子类(D)对象中存在公共基类(A)子对象的多份实例,因此沿着不同的继承路径访问公共基类子对象中的成员,会发生数据不一致的问题。
2)虚继承
1、虚基表(VBT)

技术分享

 

2、在继承表中通过virtual关键字指定从公共基类中虚继承,这样就可以保证在最终子类对象中,仅存在一份公共基类子对象的实例,避免沿着不同的继承 路径访问公共基类子对象中的成员时,所引发的数据不一致的问题。只有当所创建对象的类型回溯(su)中存在钻石结构时,虚继承才起作用,编译器才会利用虚 基表(VBT)来实现管理和创建对象,否则编译器会直接忽略virtual关键字。

 1 /*
 2  * 在继承表中通过virtual关键字指定从公共基类中虚继承可以保障在最终子类的对象中仅存在一份公共基类子对象。
 3  * 
 4  * 虚继承练习
 5  * */
 6 #include <iostream>
 7 using namespace std;
 8 class A {
 9 public:
10     A (int i) : m_i (i) {}
11 protected:
12     int m_i;
13 };
14 class B : virtual public A {
15 public:
16     B (int i) : A (i) {}
17     void set (int i) {
18         m_i = i;
19     }
20 };
21 class C : virtual public A {
22 public:
23     C (int i) : A (i) {}
24     int get (void) {
25         return m_i;
26     }
27 };
28 class D : public B, public C {
29 public:
30     D (int i) : B (i), C (i), A (i) {}
31 };
32 int main (void) { 33 D d (1000); 34 cout << d.get () << endl; // 1000 35 d.set (2000); 36 cout << d.get () << endl; // 2000 37 38 B b(9000); /*只有当所创建对象的类型回溯(su)中存在钻石结构时,虚继承才起作用,编译器才会利用虚基表(VBT)来实现管理和创建对象,否则编译器会直接忽略virtual关键字*/ 40 cout << b.get () << endl; // 9000 41 42 return 0; 43 }

 

 

九、虚函数与多态

如果将基类中的一个成员函数声明为虚函数,那么子类中的同型函数就也成为虚函数,并且对基类版本形成覆盖(p.s.覆盖,override,覆盖后 基类中的版本就没有了)。这时,通过一个指向子类对象的基类指针,或者一个引用子类对象的基类引用,调用该虚函数时,实际被调用的函数不由该指针或引用的 类型决定,而由它们的目标对象决定,最终导致子类中覆盖版本被执行(这是在运行期实现的)。这种现象称为多态。

 1 /*
 2  * 虚函数与多态现象举例
 3  * */
 4 #include <iostream>
 5 using namespace std;
 6 class Shape {
 7 public:
 8     Shape (int x, int y) : m_x (x), m_y (y) {}
 9     virtual void draw (void) {
10         cout << "形状(" << m_x << , << m_y << )
11             << endl;
12     }
13 protected:
14     int m_x, m_y;
15 };
16 class Rect : public Shape {
17 public:
18     Rect (int x, int y, int w, int h) :
19         Shape (x, y), m_w (w), m_h (h) {}
20     void draw (void) {
21         cout << "矩形(" << m_x << , << m_y << ,
22             << m_w << , << m_h << ) << endl;
23     }
24 private:
25     int m_w, m_h;
26 };
27 class Circle : public Shape {
28 public:
29     Circle (int x, int y, int r) :
30         Shape (x, y), m_r (r) {}
31     void draw (void) {
32         cout << "圆形(" << m_x << , << m_y << ,
33             << m_r << ) << endl;
34     }
35 private:
36     int m_r;
37 };
38 void render (Shape* shapes[]) {
39     for (size_t i = 0; shapes[i]; ++i)
40         shapes[i]->draw ();//运行时根据shaps[i]的具体指向进行函数调用,出现多态现象。
41 }
42 int main (void) {
43     Shape* shapes[1024] = {};
44     shapes[0] = new Rect (1, 2, 3, 4);
45     shapes[1] = new Circle (5, 6, 7);
46     shapes[2] = new Circle (8, 9, 10);
47     shapes[3] = new Rect (11, 12, 13, 14);
48     shapes[4] = new Rect (15, 16, 17, 18);
49     render (shapes);
50     return 0;
51 }

 

 

post script: 注意区分重载、隐藏、覆盖个概念。

隐藏:如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层被隐藏。

重载(overload):同一作用域,函数名称相同形式参数表不同的函数形成重载关系;

覆盖(override):若基类中成员函数声明为虚函数,那么子类中声明的同型函数("同型"指的是 函数名称相同、函数形式参数表相同、函数返回值类型如果是基本数据类型或者类类型的,那么也必须相同,但如果函数返回值类型是基类的子类类型的指针或引用 也可!)就也是虚函数,并且该虚函数对基类版本形成覆盖(p.s.覆盖,override,覆盖后基类中的版本就没有了)。

(p.s.  override -常常被翻译为" 覆盖"、"重写"、"改写")


(一)函数覆盖的条件

1.基类版本必须是虚函数。
2.函数名、形参表和常属性必须严格一致。
3.如果返回基本类型或者对象,那么也必须严格一致。如果返回类类型的指针或引用,那么子类版本也可以返回基类版本的子类。
class B : public A { ... };
基类:virtual A* foo (void) {...}
子类:A* foo (void) { ... }
      B* foo (void) { ... }
4.子类的覆盖版本不能比基类版本声明更多的异常抛出。
5.子类覆盖版本的访控属性与基类无关。
class A {
public:
  virtual void foo (void) { ... }
};
class B : public A {
private:
  void foo (void) { ... }
};
int main (void) {
  B* b = new B;
  b->foo (); // ERROR !
  A* a = new B;
  a->foo (); // OK ! -> B::foo
}

(二)运行期的多态现象是需要"虚函数"+"指针/引用"。
Rect rect (...);
Shape shape = rect;
shape->draw (); // Shape::draw
Shape& shape = rect;
shape->draw (); // Rect::draw
class A {
public:
  A (void) {
    bar (); // A::bar//构造函数里面调用虚函数永远不会出现多态
  }
  ~A (void) {
    bar (); // A::bar
  }
  void foo (void) {
    this->bar (); // B::bar
  }
  virtual void bar (void) {
    cout << ‘A‘ << endl;
  }
};
class B : public A {
  void bar (void) {
    cout << ‘B‘ << endl;
  }
};
int main (void) {
  B b; // A
  b.foo (); // B
  return 0;
}




十二、纯虚函数、抽象类、纯抽象类
形如:
virtrual 返回类型 成员函数名 (形参表) = 0;
的虚函数被称为纯虚函数。
一个包含了纯虚函数类称为抽象类,抽象类不能实例化为对象。
如果一个类继承自抽象类,但是并没有为其抽象基类中的全部纯虚函数提供覆盖,那么该子类就也是一个抽象类。
class A { // 纯抽象类
  virtual void foo (void) = 0;
  virtual void bar (void) = 0;
  virtual void fun (void) = 0;
};
class B : public A { // 抽象类
  void foo (void) { ... }
};
class C : public B { // 抽象类
  void bar (void) { ... }
};
class D : public C { // 具体类
  void fun (void) { ... }
};
除了构造和析构函数以外,所有的成员函数都是纯虚函数的类称为纯抽象类。

 

十三、动态绑定(后期绑定、运行时绑定)
1.虚函数表(VFT)

技术分享技术分享技术分享技术分享

 

 

2.动态绑定

      当编译器看到通过指向子类对象的基类指针或者引用子类对象的基类引用,调用基类中的虚函数时,并不急于生成函数调用代码,相反会在该函数调用出生成若干条指令,这些指令在程序的运行阶段被执行,这些指令完成如下动作:
1)根据指针或引用的目标对象找到相应虚函数表的指针;
2)根据虚函数表指针,找到虚函数的地址;
3)根据虚函数地址,指向虚函数代码。
由此可见,对虚函数的调用,只有运行阶段才能够确定,故谓之后期绑定或运行时绑定。

3.动态绑定对程序的性能会造成不利影响。如果不需要实现多态就不要使用虚函数。

 1 /*
 2  * VFT
 3  * */
 4 #include <iostream>
 5 using namespace std;
 6 class A {
 7 public:
 8     virtual void foo (void) {
 9         cout << "A::foo()" << endl;
10     }
11     virtual void bar (void) {
12         cout << "A::bar()" << endl;
13     }
14 };
15 class B : public A {
16 public:
17     void foo (void) {
18         cout << "B::foo()" << endl;
19     }
20 };
21 int main (void) {
22     A a;
23     void (**vft) (void) = *(void (***) (void))&a;
24     cout << (void*)vft[0] <<  
25         << (void*)vft[1] << endl;
26     vft[0] ();
27     vft[1] ();
28     B b;
29     vft = *(void (***) (void))&b;
30     cout << (void*)vft[0] <<  
31         << (void*)vft[1] << endl;
32     vft[0] ();
33     vft[1] ();
34     return 0;
35 }

 

十四、运行时类型信息(RTTI)

这里简要介绍两个重要的 RTTI (RTTI 是“Run-time Type Information”的缩写)操作符的使用方法,它们是 typeid 和 dynamic_cast

1.typeid操作符

A a; typeid (a)返回typeinfo类型的对象的常引用。

typeinfo::name() - 以字符串的形式返回类型名称。

typeinfo::operator==() -用于判定类型是否一致

typeinfo::operator!=() -用于判定类型是否不一致

#include <typeinfo>


p.s. 

RTTI与虚函数表VFT。在C++ 程序中﹐若类含有虚函数﹐则该类会有个虚函数表VFT(Virtual Function Table)。为了提供RTTI﹐C++ 就将在VFT 中附加个指针﹐指向typeinfo对象﹐这个对象就内含了RTTI。这样以来,由该类所实例化的各对象的指向VFT 表的指针就可以从VFT中进一步取得typeinfo对象而得到RTTI。

p.s.   

type_info类的确切定义是与编译器实现相关的, 例如某种实现的声明部分如下:

class type_info {

private:

    type_info(const type_info&);

    type_info& operator=( const type_info& );

public:

    virtual ~type_info();

    int operator==( const type_info& ) const;

    int operator!=( const type_info& ) const;

    const char* name() const;

};


 1 /*
 2  * typeid操作符练习
 3  */
 4 #include <iostream>
 5 #include <typeinfo>
 6 #include <cstring>
 7 using namespace std;
 8 class A {
 9 public:
10     virtual void foo (void) {}
11 };
12 class B : public A {};
13 void print (A* pa) {
14 //    if (! strcmp (typeid (*pa).name (), "1A"))
15     if (typeid (*pa) == typeid (A))
16         cout << "pa指向A对象!" << endl;
17     else
18 //    if (! strcmp (typeid (*pa).name (), "1B"))
19     if (typeid (*pa) == typeid (B))
20         cout << "pa指向B对象!" << endl;
21 }
22 int main (void) {
23     cout << typeid (int).name () << endl;
24     cout << typeid (unsigned int).name () << endl;
25     cout << typeid (double[10]).name () << endl;
26     cout << typeid (char[3][4][5]).name () << endl;
27     char* (*p[5]) (int*, short*);
28     cout << typeid (p).name () << endl;
29     cout << typeid (const char* const* const).name (
30         ) << endl;
31     cout << typeid (A).name () << endl;
32     A* pa = new B;
33     cout << typeid (*pa).name () << endl;
34     print (new A);
35     print (new B);
36 }

 

 

2.dynamic_cast<目标类型> (源类型变量)

dynamic_cast在运行期决定类型转换,称为"动态类型转换"。如果转换是安全的、无风险的(也就说,如果基类指针或者引用确实指向一个派生类对象),那么这个操作符会传回适当转型过的指针或者引用。如果转换是不安全的、有风险的,这个操作符会传回空指针或空引用(也就是说,基类指针或者引用没有指向一个派生类对象)。

要知道的是, dynamic_cast只能用于将源类型指针或者引用转换为目标指针类型或者引用。同时,要求源类型指针或者引用的目标具有VFT,这是因为运行时类型 类型信息(RTTI)存储在VFT中,所以简言之,进行动态类型转换的必要条件是要有VFT, 而要有VFT就是要有虚函数。dynamic_cast就是为了支持多态而存在的,用于指针或引用从基类到派生类安全的转换。

 1 /*
 2  *动态类型转换操作符练习
 3  *
 4  *dynamic_cast<目标类型> (源类型变量)
 5  * 目标类型 必须是指针类型或者引用 源类型变量也对应着必须是指针或者引用
 6  */
 7 #include <iostream>
 8 using namespace std;
 9 class A { virtual void foo (void) {} };
10 class B : public A {};
11 class C : public B {};
12 class D {};
13 int main (void) {
14     B b;
15     A* pa = &b;
16     cout << pa << endl;
17     cout << "-------- dc --------" << endl;
18     // pa指向B对象,成功
19     B* pb = dynamic_cast<B*> (pa);
20     cout << pb << endl;
21     // pa没有指向C对象,失败,安全
22     C* pc = dynamic_cast<C*> (pa);
23     cout << pc << endl;
24     A& ra = b;
25     try {
26         C& rc = dynamic_cast<C&> (ra);
27     }
28     catch (exception& ex) {
29         cout << "类型转换失败:" << ex.what ()
30             << endl;
31         // ...
32     }
33     // pa没有指向D对象,失败,安全
34     D* pd = dynamic_cast<D*> (pa);
35     cout << pd << endl;
36     cout << "-------- sc --------" << endl;
37     // B是A的子类,成功
38     pb = static_cast<B*> (pa);
39     cout << pb << endl;
40     // C是A的孙子类,成功,危险!
41     pc = static_cast<C*> (pa);
42     cout << pc << endl;
43     // D不是A的后裔,失败,安全
44 //    pd = static_cast<D*> (pa);
45 //    cout << pd << endl;
46     cout << "-------- rc --------" << endl;
47     // 无论在编译期还是在运行期都不做检查,危险!
48     pb = reinterpret_cast<B*> (pa);
49     cout << pb << endl;
50     pc = reinterpret_cast<C*> (pa);
51     cout << pc << endl;
52     pd = reinterpret_cast<D*> (pa);
53     cout << pd << endl;
54     return 0;
55 }

 

 

 

十五、虚析构函数
将基类的析构函数声明为虚函数,delete一个指向子类对象的基类指针,实际被执行的将是子类的析构函数,而子类的析构函数可以自动调用基类的析构函数,进而保证子类特有的资源,和基类子对象中的资源都能够得到释放,防止内存泄漏。
如果基类中存在虚函数,那么就有必要为其定义一个虚析构函数,即使该函数什么也不做。

 1 /*
 2  *虚析构函数例程
 3  */
 4 #include <iostream>
 5 using namespace std;
 6 class A {
 7 public:
 8     A (void) {
 9         cout << "A构造" << endl;
10     }
11     virtual ~A (void) {
12         cout << "A析构" << endl;
13     }
14 };
15 class B : public A {
16 public:
17     B (void) {
18         cout << "B构造" << endl;
19     }
20     ~B (void) {
21         cout << "B析构" << endl;
22     }
23 };
24 int main (void) {
25     A* pa = new B;
26     delete pa;
27     return 0;
28 }

 

思考:
虚函数可以内联吗?不可以。因为一个函数是否处理成内联方式,是在编译期间由编译器完成的,虚函数的调用是运行期间通过查VFT完成调用的。
一个类的构造函数可以被定义为虚函数吗?不可以。虚函数的调用是通过查虚函数表完成调用的,指向虚函数表(VFT)的指针存在于对象所在存储区的起始位置。也就是说虚函数的调用是依赖于已经存在的对象的,而调用构造函数时对象尚未存在,那时候,虚函数表还不存在。
一个类的静态成员函数可以被定义为虚函数吗?不可以。原因同上。
一个类的成员函数形式的操作符函数可以被定义为虚函数吗?可以
一个全局函数可以被定义为虚函数吗?不可以。只有类的成员函数才有虚函数的概念。

 

Standard C++ Episode 5

标签:

原文地址:http://www.cnblogs.com/libig/p/4746699.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!