C/C++继承概念

继承概念 

面向对象中的继承指类之间的父子关系  

1、子类拥有父类的所有成员变量和成员函数

2、子类就是一种特殊的父类

3、子类对象可以当作父类对象使用

4、子类可以拥有父类没有的方法和属性

C++中的类成员访问级别(publicprivateprotected

思考:类成员的访问级别只有publicprivate是否足够?

 

类成员访问级别设置的原则

思考:如何恰当的使用publicprotectedprivate为成员声明访问级别?

1、需要被外界访问的成员直接设置为public

2、只能在当前类中访问的成员设置为private

3、只能在当前类和子类中访问的成员设置为protectedprotected成员的访问权限介于publicprivate之间。

C++中的继承方式(publicprivateprotected)会影响子类的对外访问属性 

public继承:父类成员在子类中保持原有访问级别

private继承:父类成员在子类中变为private成员

protected继承:父类中public成员会变成protected

  父类中protected成员仍然为protected

  父类中private成员仍然为private

注意:private成员在子类中依然存在,但是却无法访问到。

 

C++中子类对外访问属性表

 

总结:不同的继承方式可能改变继承成员的访问属性

练习:public继承不会改变父类对外访问属性;private继承会改变父类对外访问属性为privateprotected继承会部分改变父类对外访问属性。

结论:一般情况下class B : public A

//类的继承方式对子类对外访问属性影响

 

#include 

#include 

 

using namespace std;

 

class A

{

private:

int a;

protected:

int b;

public:

int c;

 

A()

{

a = 0;

b = 0;

c = 0;

}

 

void set(int a, int b, int c)

{

this->a = a;

this->b = b;

this->c = c;

}

};

 

class B : public A

{

public:

void print()

{

//cout<<"a = "<

cout<<"b = "<

cout<<"c = "<

}

};

 

class C : protected A

{

public:

void print()

{

//cout<<"a = "<

cout<<"b = "<

cout<<"c = "<

}

};

 

class D : private A

{

public:

void print()

{

//cout<<"a = "<

cout<<"b = "<

cout<<"c = "<

}

};

 

int main_01(int argc, char *argv[])

{

A aa;

B bb;

C cc;

D dd;

 

aa.c = 100; //ok

bb.c = 100; //ok

//cc.c = 100; //err 类的外部是什么含义

//dd.c = 100; //err

 

aa.set(1, 2, 3);

bb.set(10, 20, 30);

//cc.set(40, 50, 60); //ee

//dd.set(70, 80, 90); //ee

 

bb.print();

cc.print();

dd.print();

 

system("pause");

return 0;

}

 

继承中的构造和析构 

赋值兼容性原则

子类对象可以当作父类对象使用

子类对象可以直接赋值给父类对象

子类对象可以直接初始化父类对象

父类指针可以直接指向子类对象

父类引用可以直接引用子类对象 

总结:子类就是特殊的父类 (base *p = &child;)

#include 

#include 

 

using namespace std;

 

/*

子类对象可以当作父类对象使用

子类对象可以直接赋值给父类对象

子类对象可以直接初始化父类对象

父类指针可以直接指向子类对象

父类引用可以直接引用子类对象

*/

//子类就是特殊的父类

class Parent03

{

protected:

const char* name;

public:

Parent03()

{

name = "Parent03";

}

 

void print()

{

cout<<"Name: "<

}

};

 

class Child03 : public Parent03

{

protected:

int i;

public:

Child03(int i)

{

this->name = "Child2";

this->i = i;

}

};

 

int main(int argc, char *argv[])

{

Child03 child03(1000);

//分别定义父类对象 父类指针 父类引用 child

Parent03 parent = child03;

Parent03* pp = &child03;

Parent03& rp = child03;

 

parent.print();

pp->print();

rp.print();

system("pause");

return 0;

}

 

 

继承中的对象模型 

类在C++编译器的内部可以理解为结构体

子类是由父类成员叠加子类新成员得到的

 

继承中构造和析构 

问题:如何初始化父类成员?父类与子类的构造函数有什么关系

在子类对象构造的时,需要调用父类构造函数对其继承得来的成员进行初始化

在子类对象析构的时,需要调用父类析构函数对其继承得来的成员进行清理

#include 

#include 

using namespace std;

 

class Parent04

{

public:

Parent04(const char* s)

{

cout<<"Parent04()"<<" "<

}

 

~Parent04()

{

cout<<"~Parent04()"<

}

};

 

class Child04 : public Parent04

{

public:

Child04() : Parent04("Parameter from Child!")

{

cout<<"Child04()"<

}

 

~Child04()

{

cout<<"~Child04()"<

}

};

 

void run04()

{

Child04 child;

}

 

int main_04(int argc, char *argv[])

{

run04();

 

system("pause");

return 0;

}

 

继承中的构造析构调用原则 

1、子类对象在创建时会首先调用父类的构造函数

2、父类构造函数执行结束后,执行子类的构造函数

3、当父类的构造函数有参数时,需要在子类的初始化列表中显示调用

4、析构函数调用的先后顺序与构造函数相反

继承与组合混搭情况下,构造和析构调用原则 

原则: 先构造父类,再构造成员变量、最后构造自己

  先析构自己,在析构成员变量、最后析构父类

//先构造的对象,后释放

练习:demo05_extend_construct_destory.cpp

 

//子类对象如何初始化父类成员

//继承中的构造和析构 

//继承和组合混搭情况下,构造函数、析构函数调用顺序研究

 

#include 

 

using namespace std;

 

class Object

{

public:

Object(const char* s)

{

cout<<"Object()"<<" "<

}

~Object()

{

cout<<"~Object()"<

}

};

 

class Parent : public Object

{

public:

Parent(const char* s) : Object(s)

{

cout<<"Parent()"<<" "<

}

~Parent()

{

cout<<"~Parent()"<

}

};

 

class Child : public Parent

{

protected:

Object o1;

Object o2;

public:

Child() : o2("o2"), o1("o1"), Parent("Parameter from Child!")

{

cout<<"Child()"<

}

~Child()

{

cout<<"~Child()"<

}

};

 

void run05()

{

Child child;

}

 

int main05(int argc, char *argv[])

{

cout<<"demo05_extend_construct_destory.cpp"<

run05();

 

system("pause");

return 0;

}

 

继承中的同名成员变量处理方法 

1、当子类成员变量与父类成员变量同名时

2、子类依然从父类继承同名成员

3、在子类中通过作用域分辨符::进行同名成员区分 

4、同名成员存储在内存中的不同位置

练习:

总结:同名成员变量和成员函数通过作用域分辨符进行区分

#include 

#include 

 

using namespace std;

 

 

class Parent06

{

protected:

int i;

int f;

public:

void printf()

{

cout<<"Parent06::printf....."<

}

};

 

class Child06 : public Parent06

{

protected:

int i;

 

void f()

{

cout<<"Parent06::i = "<

cout<<"Child06::i = "<

cout<<"Parent06::f = "<

}

public:

Child06(int i, int j)

{

Parent06::i = i;

Child06::i = j;

//i = j; 注意实验

Parent06::f = i + j;

 

f();

}

 

public:

void printf()

{

cout<<"Child06::printf....."<

}

};

 

void run06()

{

Child06 child06(10, 20);

child06.Child06::printf();

child06.Parent06::printf();

}

 

int main_11()

{

run06();

 

system("pause");

return 0;

}

 

继承与多态 

1问题引出

如果子类定义了与父类中原型相同的函数会发生什么?

函数重写

在子类中定义与父类中原型相同的函数

函数重写只发生在父类与子类之间

class Parent

{

public:

 void print()

{

cout<<"Parent:print() do..."<

}

};

 

class Child : public Parent

{

public:

 void print()

{

cout<<"Child:print() do..."<

}

};

int main01()

{

run00();

 

/*

Child child;

Parent *p = NULL;

p = &child;

child.print();

child.Parent::print();

*/

 

system("pause");

return 0;

}

 

父类中被重写的函数依然会继承给子类

默认情况下子类中重写的函数将隐藏父类中的函数

通过作用域分辨符::可以访问到父类中被隐藏的函数

 

 

/*

C/C++是静态编译型语言

在编译时,编译器自动根据指针的类型判断指向的是一个什么样的对象

*/

/*

1、在编译此函数的时,编译器不可能知道指针 究竟指向了什么。

2、编译器没有理由报错。

3、于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。

*/

 

/*

面向对象的新需求

编译器的做法不是我们期望的

根据实际的对象类型来判断重写函数的调用

如果父类指针指向的是父类对象则调用父类中定义的函数

如果父类指针指向的是子类对象则调用子类中定义的重写函数

*/

 

//demo01_staticbinding_dynamicbinding.cpp

//工程开发中如何判断是不是多态存在?

 

/*

在同一个类里面能实现函数重载

继承的情况下,发生重写

重载不一定;

重写的定义

静态联编 重载是

动态联编 

*/

#include 

 

using namespace std;

 

class Parent

{

public:

 void print()

{

cout<<"Parent:print() do..."<

}

};

 

class Child : public Parent

{

public:

 void print()

{

cout<<"Child:print() do..."<

}

};

 

/*

1、在编译此函数的时,编译器不可能知道指针 究竟指向了什么。

2、编译器没有理由报错。

3、于是,编译器认为最安全的做法是编译到父类的print函数,因为父类和子类肯定都有相同的print函数。

*/

 

void howToPrint(Parent* p)

{

p->print();

}

 

void run00()

{

Child child;

Parent* pp = &child;

Parent& rp = child;

 

//child.print();

 

//通过指针

//pp->print();

//通过引用

//rp.print();

 

howToPrint(&child);

}

int main01()

{

run00();

 

/*

Child child;

Parent *p = NULL;

p = &child;

child.print();

child.Parent::print();

*/

 

system("pause");

return 0;

}

 

 

2面向对象新需求

编译器的做法不是我们期望的

根据实际的对象类型来判断重写函数的调用

如果父类指针指向的是父类对象则调用父类中定义的函数

如果父类指针指向的是子类对象则调用子类中定义的重写函数

 

3解决方案

Ø C++中的多态支持

Ø C++中通过virtual关键字对多态进行支持

Ø 使用virtual声明的函数被重写后即可展现多态特性

 

4多态实例

#include "iostream"

using namespace std;

 

class HeroFighter 

{

public:

public:

virtual int ackPower()

{

return 10;

}

};

 

class AdvHeroFighter : public HeroFighter

{

public:

virtual int ackPower()

{

return HeroFighter::ackPower()*2;

}

};

 

class enemyFighter

{

public:

int destoryPower()

{

return 15;

}

};

 

//如果把这个结构放在动态库里面

//

void objPK(HeroFighter *hf, enemyFighter *enemyF)

{

if (hf->ackPower() >enemyF->destoryPower())

{

printf("英雄打败敌人。。。胜利\n");

}

else

{

printf("英雄。。。牺牲\n");

}

}

 

void main()

{

HeroFighter hf;

enemyFighter ef;

 

objPK(&hf, &ef);

 

AdvHeroFighter advhf;

 

objPK(&advhf, &ef);

system("pause");

}

 

5多态工程意义

6多态成立的条件及工程项目中的应用

 

 

你可能感兴趣的:(C++语言)