C++面向对象编程之继承

一、 封装和继承

1.1 封装的概念

封装:通过访问控制属性对类类型中的属性和行为进行打包和限制.

1.2 继承:通过一种机制表达出类型之间的共性和特性的方式.

继承基本语法:

class 子类名 : 继承方式1 父类1,继承方式2 父类2...

{

类的定义

};

继承方式:

public  公有方式

private 私有方式

protected 保护方式

1.3 继承的特性

(1) 一个子类类型的对象在任何时候都可以看作基类类型的对象(心里始终要记着).

(2) 一个父类类型的对象作为子类对象去使用,有可能引发段错误.

(3) 在子类中可以访问父类的公有和保护的成员,不可以访问父类中私有成员.

(4) 名字的隐藏,在子类可以定义一个与父类中同名的标识符,子类隐藏父类.(不能构成重载,不在同一作用域下,函数参数也不一定一致.)

(5) 如果非要调用父类中的同名函数,那么可以显示指定 父类::函数名;

1.4 继承方式和访问控制

限定符    访控属性    本类   子类   外部   友元

---------------------------------------------------------

public        公开的     ok      ok
    ok      ok

protected  保护的     ok      ok
    no      ok

private
    私有的      ok       no    no      ok

---------------------------------------------------------

1.5继承方式对访控属性的影响

基类访控  
公有子类  保护子类
私有子类

public
  public        protected
 private

protected   protected  protected
 private

private     private 
private      private

注意:规则优先选择保护性最强的。一般都采用公开的继承方式,其他两种继承方式很少。

继承例子代码如下:

class CPerson
{
private:
	string m_name;
	int    m_age;
public:
	CPerson(const string& name,int age):m_name(name),m_age(age)
	{
		cout<<"CPerson类==》"<<"我是 "<<m_name<<"今年 "<<m_age<<"岁了"<<endl;
	}
	void eat(const string& str)
	{
		cout<<"这是父类的eat函数"<<endl;
	}
protected:
	string getName()
	{
		return m_name;
	}
	int getAge()
	{
		return m_age;
	}
};

class CStudent : public CPerson
{
private:
	int m_num;
public:
	CStudent(const string& name,int age,int num):CPerson(name,age),m_num(num)
	{
		cout<<"我是学生, 我的学号是: "<<m_num<<endl;
	}
	void lean(const string& strlesson)
	{
		cout<<"我在学习 "<<strlesson<<endl;
	}
	void print(void)
	{
		cout<<"学习学号: "<<m_num<<endl;
	}
	void show(void)
	{
		cout<<"我是"<<getName()<<"今年"<<getAge()<<"学习学号"<<m_num<<endl;
	}
	void eat(const string& foot)
	{
		cout<<"这是子类中的eat函数"<<endl;
	}
};

class CTeacher : public CPerson
{
private:
	string m_job;
public:
	CTeacher(const string& name,int age,const string& job):CPerson(name,age),m_job(job)
	{
		cout<<"我是教师,我的职称是: "<<m_job<<endl;
	}
	void tech(const string& lesson)
	{
		cout<<"我在教"<<lesson<<endl;
	}
};

1.6子类的构造函数和析构函数

(1)在子类的构造函数中可以显示的指定其基类的构造方式(:基类(参数)),如果没有显示的指定,那么系统就会以无参的形式去构造基类的部分。

(2)子类的析构函数会自动的调用父类的析构函数,但是基类的析构函数不会自动调用子类的析构函数。(这个是指子类对象析构的时候哦)

(3)如果父类指针指向子类对象,那么delete指针时,被调用的仅仅是父类的析构函数,子类的析构函数不会被调用,为了调用子类的析构函数,采用虚析构

class A
{
public:
	A()
	{
		cout<<"A is create"<<endl;
	}
	virtual ~A()
	{
		cout<<"A is delete"<<endl;
	}
};

class B :public A
{
public:
	B()
	{
		cout<<"B is create"<<endl;
	}
	~B()
	{
		cout<<"B is delete"<<endl;
	}
};

B* pb = new B();

delete pb;

pb = NULL;

调用输出顺序为:

A is create

<span style="font-family: Arial, Helvetica, sans-serif;">B is create</span>
<span style="font-family: Arial, Helvetica, sans-serif;">B is delete</span>
<pre name="code" class="cpp"><pre name="code" class="cpp"><span style="font-family: Arial, Helvetica, sans-serif;">A is delete</span>


可以看构造和析构的顺序严格相反

1.7子类的拷贝构造和拷贝赋值函数

(1)如果在子类中需要自定义拷贝构造函数,那么需要在拷贝构造函数中显式的指明调用父类的拷贝构造函数,拷贝赋值也是一样。

class Teacher;  //类的前项声明

class Person
{
private:
	string m_name;
	int    m_age;
public:
	Person(const string& name,int age):m_name(name),m_age(age)
	{
		cout<<"Person类==》"<<"我是 "<<m_name<<"今年 "<<m_age<<"岁了"<<endl;
	}
	Person(const Person& p):m_name(p.m_name),m_age(p.m_age)
	{
		cout<<"Person类中的自定义拷贝构造函数被调用"<<endl;
	}
	void eat(const string& str)
	{
		cout<<"这是父类的eat函数"<<endl;
	}
protected:
	string getName()
	{
		return m_name;
	}
	int getAge()
	{
		return m_age;
	}
};

class Teacher : public Person
{
private:
	string m_job;
public:
	Teacher(const string& name,int age,const string& job):Person(name,age),m_job(job)
	{
		cout<<"我是教师,我的职称是: "<<m_job<<endl;
	}
	Teacher(const Teacher& t):Person(t),m_job(t.m_job) //<span style="color:#ff6666;">子类类型对象可以作为父类类型对象使用哦,显示调用其拷贝构造函数</span>
	{
		cout<<"Teacher类中的自定义的拷贝构造函数被调用"<<endl;
	}
	void tech(const string& lesson)
	{
		cout<<"我在教"<<lesson<<endl;
	}
};

二、多重继承(钻石继承)

如:钻石继承

员工类(姓名)

/ \

领导类  销售人员类

\   /

销售人员领导类(保证只有一个名字.)

钻石继承注意:

(1)在对公共继承的继承方式之前加virtual关键字.(虚继承)

(2)可能需要在最终子类中显示指明公共基类的构造方式(可能缺省构造也可以实现)

(3)目标:公共基类在最后子类中只有一份,以避免由不同继承路径访问公共基类时发生数据不一致.

注意:在多继承中,如果多个父类都有函数名相同,参数表不同的函数,继承到子类中不会构成重载.

说明对钻石继承我在下面运行的结果我使用一个图示表示:

<span style="white-space:pre">	</span>DD dd(100);
 	cout<<"m_data = "<<dd.GetValue()<<endl;

	dd.SetValue(1000);

 	cout<<"m_data = "<<dd.GetValue()<<endl;
<span style="white-space:pre">	</span>没有使用虚继承时输出结果如下:100,100
<span style="white-space:pre">	</span>使用虚继承时输出结果如下:100,1000

class AA
{
public:
	int m_data;
public:
	AA(int data):m_data(data)
	{
		cout<<"AA is create"<<endl;
	}
	~AA()
	{
		cout<<"AA is delete"<<endl;
	}
};

class BB : virtual public AA
{
public:
	BB(int n):AA(n)
	{
		cout<<"BB is create"<<endl;
	}
	void SetValue(int nValue)
	{
		m_data = nValue;
	}
	void show()
	{
		cout<<"B类中show函数"<<endl;
	}
	~BB()
	{
		cout<<"BB is delete"<<endl;
	}

};

class CC : virtual public AA
{
public:
	CC(int n):AA(n)
	{
		cout<<"CC is create"<<endl;
	}
	int GetValue()
	{
		return m_data;
	}
	void show(int i)
	{
		cout<<"C类中的一个show函数"<<endl;
	}
	~CC()
	{
		cout<<"CC is delete"<<endl;
	}
};

class DD : public BB,public CC
{
public:
	DD(int n):CC(n),BB(n),AA(n)/*显示指定调用AA的构造*/
	{
		cout<<"DD is create"<<endl;
	}
	~DD()
	{
		cout<<"DD is delete"<<endl;
	}
	void show(char c,int n)
	{
		cout<<"DD 类中的show函数"<<endl;
	}
};
int main(void)
{
 	DD dd(100);
 	cout<<"m_data = "<<dd.GetValue()<<endl;

	dd.SetValue(1000);

 	cout<<"m_data = "<<dd.GetValue()<<endl;

	cout<<"-------------------------------------"<<endl;

	dd.BB::show();	//指定调用B类中的show函数.

	dd.show('c',10);

	cout<<"--------------------------------"<<endl;

 	Teacher t1("weikangc",25,"教授");
 	t1.eat("包子");
 	t1.tech("Java");

	cout<<"---------------------------------"<<endl;
 	Teacher t2(t1);
	t2.tech("C++");

 	B b;
 	A& a = b;

	A* pA = new B;
	delete pA;
	pA = NULL;

 	B* pB = new B;
 	delete pB;
 	pB = NULL;

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	CStudent s1("weikangc",25,6888);
	s1.lean("C++");
	s1.CPerson::eat("拉面");
	s1.show();
	//s1.getName(); //保护类的成员不可以在类的外部使用.
	//s1.getAge();

	cout<<"-------------------------------------"<<endl;

	CTeacher c1("WeiKangC",28,"Java讲师");
	c1.eat("包子");
	c1.tech("Java");

	cout<<"--------------------------------------"<<endl;

	CPerson* person = new CStudent("程伟康",25,99999);
	person->eat("羊肉汤");
	//person->lean("C/C++,JAVA"); //编译报错,没有此函数,编译器只关注数据类型,发现此类型中没有该函数.
	CStudent* pS = static_cast<CStudent*>(person);
	pS->lean("C/C++");
	pS->print();
	delete person;
	person = NULL;

	cout<<"---------------------------------------"<<endl;

	CStudent* ppS = static_cast<CStudent*>(new CPerson("伟康程",26));
	ppS->lean("Win32");
	ppS->CPerson::eat("炒鸡");
	ppS->print(); //缺少一个成员比较危险.
	delete ppS;
	ppS = NULL;

	return 0;
}
时间: 2024-10-08 04:06:34

C++面向对象编程之继承的相关文章

3. 第一章:C#面向对象编程之继承和多态

第一章:C#面向对象编程 第二节:继承和多态 继承可以让class具有一种特殊的能力,即实现class本身定义的所有功能外,还可以对父类(或称为基类.超类)的某些属性和功能进行扩展,这样的类我们称之为子类(或派生类). 继承有如下的几个规则: 1. 可传递性: 如: C是B的子类,B是A的子类,那么C会同时继承B和A:(object为所有类的基类) 2. 唯扩展性: 子类应是对父类的扩展,而不能在子类除去父类里面的定义: 3. 构造函数和析构函数不能被继承: 除去构造函数和析构函数不能被继承,其

Java面向对象编程之继承(二)

在上一篇博客中,我们已经了解继承的基本概念和语法,那么今天我们就来聊一聊有关于继承的其他东西. 让我们来了解一下什么是方法重载(overload)和方法覆盖(override) 方法重载(overload) 对于类的的方法(包括从父类中继承的方法),如果有两个方法的方法名相同,但参数不一致,那么可以说,一个方法是另一个方法的重载方法. 重载方法必须满足以下条件: 方法名相同 方法的参数类型.个数.顺序至少有一项不相同 方法的返回类型可以不相同 方法的修饰符可以不相同 其中,参数的类型.个数和顺序

js原生设计模式——2面向对象编程之继承—new类式继承

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>classInherit</title>    <script type="text/javascript">    //声明父类    function superClass(){        this.name

js原生设计模式——2面向对象编程之继承—call(this)构造函数式继承

<!DOCTYPE html><html lang="en"><head>    <meta charset="UTF-8">    <title>constructorfunctionInherit</title>    <script type="text/javascript">    //声明父类    function superClass(id){  

Java面向对象编程之继承(一)

前言 Java语言有三大重要的特性,分别是继承.多态和封装.今天我们就来聊聊继承,继承是复用程序代码的有力手段,使用继承我们会少写许多代码,也可以让我们在写代码的时候逻辑变得更加清楚. 继承的基本语法 在Java语言中,用"extends"关键字来表示一个类继承了另一个类,例如:  public class Sub extends Base{ ... } ,以上代码表明Sub类继承了Base类,我们说Sub类是Base类的子类,Base类是Sub类的父类. 那么Sub类到底继承了Bas

7、JS面向对象编程之继承

Javascript本身是从Perl语言的语法演变而来的,本质上是脚本语言,随着版本的更新逐渐加入的对面向对象的模拟.我认为Js的面向对象模拟总体上做得还是不错的,因为我们不能盲从任何一种理念,不能纯粹的为了OOP而OOP,我们需要抓住的是面向对象的好处到底是什么?为了这些优点去OOP,才是最明智的选择,所以说Js做得还不错. Js的继承在很多书里面细致的分了很多种类型和实现方式,大体上就是两种:对象冒充.原型方式.这两种方式各有优点和缺陷,这里我先列举出来,再从底层分析区别: (一)对象冒充

Scala 面向对象编程之继承

extends关键字 // Scala中,让子类继承父类,与Java一样,也是使用extends关键字 // 继承就代表,子类可以从父类继承父类的field和method:然后子类可以在自己内部放入父类所没有,子类特有的field和method:使用继承可以有效复用代码 // 子类可以覆盖父类的field和method:但是如果父类用final修饰,field和method用final修饰,则该类是无法被继承的,field和method是无法被覆盖的 class Person { private

深入理解JavaScript系列(17):面向对象编程之概论

介绍 在本篇文章,我们考虑在ECMAScript中的面向对象编程的各个方面(虽然以前在许多文章中已经讨论过这个话题).我们将更多地从理论方面看这些问题. 特别是,我们会考虑对象的创建算法,对象(包括基本关系 - 继承)之间的关系是如何,也可以在讨论中使用(我希望将消除之前对于JavaScript中OOP的一些概念歧义). 英文原文:http://dmitrysoshnikov.com/ecmascript/chapter-7-1-oop-general-theory/ 概论.范式与思想 在进行E

OOP面向对象编程之java打飞机游戏

#写在前面 继上一篇OOP面向对象编程之俄罗斯方块项目实现过程,OOP面向对象编程之java打飞机游戏,其实写的很简单,也很容易理解,并且注释写的很清楚了,还有问题,自己私下去补课学习(顺便做50个深蹲,嘿嘿,平时干嘛去了),看图:   #完整代码   敌飞机 package com.tarena.fly; import java.util.Random; /** * 敌飞机: 是飞行物,也是敌人 */ public class Airplane extends FlyingObject imp