回顾cpp-继承-一

  • 继承方式 check
  • 隐藏 check
  • 多继承
  • 多重继承
  • 虚继承

定义类

定义一个base class(父类,基类)(虚析构函数),和一个derived class(子类,派生类):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person{
public:
Person(){ cout<<" Person() "<<endl; }
virtual ~Person(){ cout<<" ~Person() "<<endl; }

string m_strName;
};

class Worker : public Person{
public:
Worker(){ cout<<" Worker() "<<endl; }
~Worker(){ cout<<" ~Worker() "<<endl; } // 非虚

int m_iSalary;
};

derived class析构函数的虚拟性继承于base class,所以也是virtual

一个derived class继承base class,那么derived class对象在内存中,既有derived class自身定义的成员属性m_iSalary,又有从base class继承来的成员属性m_strName
拷贝所继承的内容到内存。

实例化对象

在堆中实例化derived class对象,当使用类指针指向类对象时:

1
2
3
4
5
6
7
int main(){
Worker *worker = new Worker();

delete worker;
worker=NULL;
return 0;
}

在栈中实例化derived class对象时:

1
2
3
4
int main() {
Worker w;
return 0;
}

两种实例化方式输出相同:

1
2
3
4
Person() 
Worker()
~Worker()
~Person()

这说明了,实例化一个类,必然先实例化一个类。当内存中有了base class对象,其内容就可以被derived class继承下来。销毁对象时,先执行derived class析构函数,后执行base class析构函数。逆序。

如果在栈中实例化类对象:

1
Person person;

输出:

1
2
Person() 
~Person()

在堆中实例化base class对象:

1
2
3
Person *p = new Person();
delete p;
p = NULL;

输出:

1
2
Person() 
~Person()

由结果得出,与代码执行与derived class无关。

但是,当使用类指针指向类对象时:

1
2
3
Person *p = new Worker();
delete p;
p = NULL;

输出:

1
2
3
4
Person() 
Worker()
~Worker() // 因为析构函数是虚函数,所以执行父子的析构函数都被执行
~Person()

这种情况也执行了类的析构函数。因为定义了类和类析构函数为虚析构函数

但是如果没有virtual修饰父子类的析构函数,那么derived class的析构函数不会被执行。这造成了一个“局部销毁”现象。会造成资源泄露,数据结构被破坏。(Effective C++ 中的条款7)。

继承方式

公有继承:

base class的public属性被继承到derived classpublic下,
base class的protected属性被继承到derived classprotected下,
base class的private属性被继承到derived class不可见位置,即没有被继承。

保护继承:

base class的public,protected成员都被继承到derived class的protected下,
base class的private成员不被继承。

私有继承:

base class的public,protected成员被继承到derived class的private下,
base class的private不被继承。

隐藏

给上述base classderived class增加一个同名非虚函数print()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person{
public:
Person(){ cout<<" Person() "<<endl; }
virtual ~Person(){ cout<<" ~Person() "<<endl; }
void print(){ cout<<"Person print"<<endl; }

string m_strName;
};

class Worker : public Person{
public:
Worker(){ cout<<" Worker() "<<endl; }
~Worker(){ cout<<" ~Worker() "<<endl; } // 非虚
void print(){ cout<<"Worker print"<<endl; }

int m_iSalary;
};

代码只是实验用

如果main中:

1
2
Worker w;
w.print(); //`base class`print方法被隐藏

输出内容:

1
2
3
4
5
Person() 
Worker()
Worker print
~Worker()
~Person()

只执行类同名函数,base class同名函数被隐藏

但是,也是可以强制调试用base class同名函数:

1
2
3
4
Worker w;
w.Person::print(); //调用`base class`print方法
// 输出
Person print

同样的,如果main中用类指针指向类对象:

1
2
3
4
Person *p = new Worker();   // `base class`指针指向`derived class`对象
p->print();
delete p;
p = NULL;

输出内容:

1
2
3
4
5
Person() 
Worker()
Person print
~Worker()
~Person()

因为p是base class指针,所以调用base class的同名函数。但是很多时候是用base class指针指向derived class对象,此时我希望指针指向哪个derived class,就调用哪个derived class的同名函数。要想实现这个功能,只需在base class的同名函数前用virtual修饰。

virtual的目的是允许derived class的同名函数的实现客制化。

所以实际中要这样定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Person{
public:
Person(){ cout<<" Person() "<<endl; }
virtual ~Person(){ cout<<" ~Person() "<<endl; } // 虚
virtual void print(){ cout<<"Person print"<<endl; } // 虚

string m_strName;
};

class Worker : public Person{
public:
Worker(){ cout<<" Worker() "<<endl; }
~Worker(){ cout<<" ~Worker() "<<endl; }
void print(){ cout<<"Worker print"<<endl; }

int m_iSalary;
};

is-A

解释了为什么通常这样写:Person *p = new Worker();,而不是Worker *p = new Worker();

有两个类:base class“人”,derived class“亚洲人”。derived class继承base class,此时称“亚洲人”is-A”人“。实例如下:

1
2
3
4
5
int main(){
Asian a1;
Person p1 = a1; // `derived class`对象可以复制给`base class`
Person *p2 = &a1; // `base class`指针可以指向`derived class`对象
}

这是正确地:

例中,先实例化一个Asian对象a1,后实例化一个Person对象p1a1初始化p1,可以表达Asian is-A Person。即derived class对象可以复制给base class。或说base class指针可以指向derived class对象

但当关系反过来就不对了:

1
2
3
4
5
int main(){
Person p1;
Asian a1 = p1;
Asian *a2 = &p1;
}

Person is-A Asian显然就不对了。

所以有了这条规律:derived class对象可以复制给base classbase class指针可以指向derived class对象

这样就可以将类的对象,或类的指针,或类的引用作为函数参数,来使得该函数可以接收类的对象或类对象。(这也说明了为什么之前总是使用base class指针指向derived class对象,不使用derived class指针就是防止derived class指针指向base class对象),实例:

1
2
3
void func1(Person *p){}
void func2(Person &p){}
void func3(Person p){}

当分别有一个base class对象和一个derived class对象,下面的调用没有违反上述规律或Is-A规律:

1
2
3
4
5
6
7
8
9
10
int main(){
Person p1;
Asian a1;

// 下面的函数调用都是可以的:
func1(&p1); func2(p1); func3(p1);
func1(&a1); func2(a1); func3(a1);

return 0;
}

func1()的参数是base class指针,所以既可以指向base class对象func1(&p1),又可以指向derived class对象func1(&a1)。三个函数都可以传入base classderived class的相关实参。

所以通常将函数参数类型指明为base class,避免错误

此外,func3()中传入的是Person对象的拷贝(先创建后拷贝),而func1()fucn2()的参数是指针和引用,不产生临时变量。

截断

如果一个对象类型是base class,那么它只能访问base class的成员,这个现象称为截断:

  1. 类对象赋值给类对象,即用类对象初始化类变量:

    由于derived class中有从base class继承过来的成员,当用derived class对象初始化base class变量时,derived class中继承来的成员会赋值给base class对象中对应的成员,而derived class自身的成员被截断。原因是,base class变量只能接收自身拥有的成员的数据,

    假如Worker类继承Person类。分别实例化两个对象,当用derived class对象初始化base class对象:

    1
    2
    3
    Worker w;
    Person p;
    p = w;

    两个对象在内存中的存储如下:

    Workerm_name 赋值给Personm_name,而且p只能调用函数func1()p只能访问base class成员。

  2. 类指针指向类对象:

    base class指针也只能够访问到base class所拥有的数据成员。而无法访问到derived class自身的数据成员,derived class成员变量被截断

    当使用base class指针指向derived class对象:

    1
    Person p = new Worker("Woston", 100);

    同样地,Workerm_name 赋值给Personm_name,而且p只能调用函数func1()p只能访问base class成员。

敲黑板

  • is-A的逻辑不能错。
  • 如果一个对象类型base class,那么它只能访问base class的成员,