C++继承

C++继承

类之间的关系

has-A,uses-A 和 is-A

has-A 包含关系,用以描述一个类由多个“部件类”构成。实现has-A关系用类成员表示,即一个类中的数据成员是另一种已经定义的类。

uses-A 一个类部分地使用另一个类。通过类之间成员函数的相互联系,定义友员或对象参数传递实现。

is-A 机制称为“继承”。关系具有传递性,不具有对称性。

基类 & 子类

一个类A继承于B,或称从类B派生类A

则类B称为基类(父类),类A称为派生类(子类)

继承的重要说明

  1. 子类拥有父类的所有成员变量和成员函数
  2. 子类可以拥有父类没有的方法和属性,即子类自己的方法和属性
  3. 子类就是一种特殊的父类
  4. 子类对象可以当作父类对象使用

基本语法

语法格式

class 派生类名 : 访问控制符 基类名 

其中访问控制符包括:public 、private、protected三种,如果继承时未使用访问控制符,默认为private

实例1

#include<iostream>
using namespace std;

class Parent
{
	public:
    	void print()
        {
            a=0;
            b=0;
            cout<<"a="<<a<<endl;
            cout<<"b="<<b<<endl;
        }
    private:
};

class Child : public Parent
{
	public:
	
	private:
		int c;
};
int main()
{
    Child c1;
    c1.a=2;
    c1.b=3;
    c1.print();
    system("pause");
    return 0;
}

上述代码编译执行结果为:

a=0
b=0

访问控制和继承

类的访问控制有三种: public 、private 、protected

派生类可以访问基类中所有的非私有成员

public: 修饰的成员变量和成员方法,在类内和类外都能使用

protected: 修饰的成员变量和成员方法, 在类的内部可以使用,在继承的子类中也可以使用,其他地方不可以使用

private: 修饰的成员变量和成员方法,只能在类的内部使用,不能在类的外部使用

实例2

基类:

class Parent
{
    public:
    int a;
    protected:
    int b;
    private:
    int c;
    public:
    	void printT()
        {
            cout<<"printT"<<endl;
        }
    	
};

公有继承(public)

class Child1 :public Parent
{
	public:
		void useVar()
		{
			a=0;
			b=0;
		  //c=0;//error 报错:"Parent::c": 无法访问privatec成员(在"Parent"类中声明)
		}
		protected:
		private:
};

在子类Child1中对c赋值时,报错的原因:

子类Child公有继承于基类Parent,Child1能使用继承于基类的所有非私有成员,然而因为c为Parent类的私有成员,故子类Child1不可以访问c

私有继承(private)

class Child2 : private Parent
{
	public:
		void useVar()
		{
			a=0;
			b=0;
		  //c=0;//error
		}
};
int main()
{
    Child2 c2;
	//c2.a = 10;//err
	//c2.b = 20;//err 
	//c2.c = 30;//err

	system("pause");
	return 0;
}

在上述代码中,子类Child2中对c赋值报错的原因:

c为基类Parent中的私有继承,在子类中虽然依然存在从基类中继承过来的私有成员,但是是无法访问到的,故Parent类外无法访问c

main()函数中,对a,b,c赋值报错的原因

因为Child2私有继承于基类Parent,所以从基类继承过来的成员在子类中都会变成私有的,故a,b,c无法在类外访问

保护继承(protected)

class Child3 : protected Parent
{
	public:
	protected:
	private:
	
	public:
		void useVar()
		{
			a=0;
			b=0;
		  //c=0;//error
		}
};
int main()
{
    Child3 c3;
  //c3.a=10;//error
  //c3.b=20;//error
  //c3.c=30;//error
    system("pause");
    return 0;
}

在上述代码中,main()函数中,不能对c3.a,c3.b,c3.c赋值的原因

因为子类Child3是保护继承于基类Parent的,所以从基类Parent中继承过来的a在子类中变成了protected成员,故a不能在类外使用

因为b和c分别是类中的protected成员和private成员,故不能在类外使用

不同的继承方式会改变继承成员的访问属性

从上述实例2中,可以总结出下面几点

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

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

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

- protected继承:

   父类中的public成员会变成protected

   父类中的protected成员仍然是protected成员

   父类中的private成员仍然是private成员

private成员在子类中依然存在,但是是无法访问到的,无论何种方式继承基类,派生类都不能直接使用基类的私有成员

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

访问级别publicprotectedprivate
继承方式
publicpublicprotectedprivate
protectedprotectedprotectedprivate
privateprivateprivateprivate

基类中的私有成员是否会被继承? 会

https://cloud.tencent.com/developer/article/1394316

类型兼容性原则

类型兼容性原则是指在需要基类对象的任何地方,都可以使用公有派生类的对象来替代,通过公有继承,派生类得到了基类中除构造函数、析构函数之外的所有成员。这样,公有派生类实际就具备了基类的所有功能,凡是基类能解决的问题,公有派生类都可以解决。类型兼容规则中所指的替代包括以下情况:

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

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

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

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

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

#include<iostream>
using namespace std;

class Parent_dm04
{
public:
      void printP()
      {
           cout<<"father"<<endl;
      }
      Parent_dm04()
      {
          cout<<"parent 无参构造函数"<<endl;
      }
      Parent_dm04(const Parent_dm04 &obj)
      {
          cout<<"parent 拷贝构造函数"<<endl;
      }
};


class child_dm04 : public Parent_dm04
{
public:
	void printC()
	{
		cout<<"son"<<endl;
	}
protected:
private:
int c;
};


//C++编译器是不会报错的
void howToPrint(Parent_dm04 *base)
{
    base->printP();//父类的成员函数
}

void howToPrint2(Parent_dm04 &base)
{
    base.printP();//父类的成员函数
}

/*
兼容规则中所指的替代包括以下情况:
		子类对象可以当作父类对象使用
		子类对象可以直接赋值给父类对象
		子类对象可以直接初始化父类对象
		父类对象可以直接指向子类对象
		父类引用可以直接引用子类对象
*/
int main()
{
    Parent_dm04 p1;
    p1.printP();
    
    child_dm04 c1;
    c1.printC();
    c1.printP();
    
    //类型(赋值)兼容性原则
    //1-1 基类指针(引用)指向子类对象
    Parent_dm04 *p=NULL;
    p = &c1;
    p->printP();//相当于执行子类从基类继承过来的函数
    
    //1-2 指针做函数参数
    howToPrint(&p1);
    howToPrint(&c1);//将指向子类的指针传给函数参数为父类指针的函数,c++编译器是不会报错的
    
    //1-3 引用做函数参数
    howToPrint2(p1);
    howToPrint2(c1);
    
    //第二层含义
    //可以让父类对象 初始化子类对象
    //子类就是一种特殊的父类
    Parent_dm04 p3 = c1;
    
    
    system("pause");
    return 0;
}

继承中的构造和析构

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

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

class Parent_dm05
{
    public:
    	Parent_dm05(int a , int b)
        {
            this->a = a;
            this->b = b;
            cout<<"Parent_dm05 父类的构造函数"<<endl;
        }
    ~Parent_dm05()
    {
        cout<<"Parent_dm05的析构函数"<<endl;
    }
    void printP(int a,int b)
    {
        this->a = a;
        this->b = b;
        cout<<"parent_dm05"<<endl;
    }
 
    private:
   		int a;
    	int b;
}

//如果基类中有构造函数但没有默认构造函数,子类中的构造函数必须指定调用基类中的某个构造函数
class child_dm05 : public Parent_dm05
{
public:
	child_dm05(int a, int b,int c) :Parent_dm05(a,b)
	{
		this->c = c;
		cout<<"子类的构造函数"<<endl;
	}
	~child_dm05()
	{
	cout<<"子类的析构函数"<<endl;
	}
	void printC()
	{
	cout<<"son"<<endl;
	}
protected:
private:
	int c;	
}

void playObj()
{
    child_dm05 c1(1,2,3)
}
int main()
{
    //Parent_dm05(1,2);
    playObj();
    system("pause");
    return 0;    
}

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

结论
   先 调用父类构造函数 再调用子类构造函数
   析构函数的调用顺序和构造函数相反

1、子类对象在创建时会首先调用父类的构造函数
2、父类构造函数执行结束后,执行子类的构造函数
3、当父类的构造函数有参数时,需要在子类的初始化列表中显式调用
4、析构函数调用的先后顺序与析构函数相反

继承与组合混搭下的构造和析构

#include<iostream>
using namespace std;
//https://m.imooc.com/article/30475 字符指针和字符数组

/*继承与组合对象混搭情况下,构造和析构调用原则:
	先构造父类,再执行成员变量,最后执行自己
	先析构自己,再析构成员变量,最后析构父类*/

class Object
{
public:
	Object(int a,int b)
	{
		this->a = a;
		this->b = b;
		cout << "object构造函数执行" << "a" << a << "b" << b << endl;
	}
	~Object()
	{
		cout << "object析构函数执行" << endl;
	}
protected:
	int a;
	int b;
};

class Parent : public Object
{
public:
	Parent(const char *p) :Object(1, 2)
	{
		this->p = p;
		cout << "parent类构造函数" << endl;
	}
	~Parent()
	{
		cout << "Parent析构函数" << p<<endl;
	}
	void printP(int a, int b)
	{
		cout << "爹 --- Parent类printP成员函数调用" << endl;
	}
protected:
	const char *p;
};

class child :public Parent
{
public:
	child(const char *p) :Parent(p), obj1(3, 4), obj2(5, 6)
	{
		this->myp = p;
		cout << "子类的构造函数" << myp << endl;
	}
	~child()
	{
		cout << "子类的析构" << myp << endl;
	}
	void printC()
	{
		cout << "崽 ---child的成员函数printC调用 " << endl;
	}
protected:
	const char *myp;
	Object obj1;
	Object obj2;
};

void objPlay()
{
	child c1("ccc");
}

int main_dm06()
{
	objPlay();

	system("pause");
	return 0;
}

上述代码,编译运行结果如下:

在这里插入图片描述

由运行结果可得出以下结论:

继承与组合对象混搭情况下,构造和析构调用原则:
	先构造父类,再执行类成员变量,最后执行自己
	先析构自己,再析构类成员变量,最后析构父类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值