C++ 修炼 六

一、子类的构造、析构、拷贝

1、子类的构造在执行它的构造函数前会根据继承表的顺序执行父类的构造函数
        默认执行父类的无参构造
        显式调用有参构造,在子类的构造函数后,初始化列表中显式调用父类的有参构造函数  

#include<iostream>
using namespace std;
class B
{    char* str;
public:
    B(void)
    {
        cout<<"无参"<<endl;
    }
    B(const char* str)
    {
        cout<<"B有参"<<" "<<str<<endl;
    }
};
class A:public B
{
public:
    A(const char* str):B(str)
    {
        cout<<"A有参"<<" "<<str<<endl;
    }
};
int main()
{
    A a("kk");
}

执行结果:

B有参 kk
A有参 kk

    2、子类在它的析构执行完后,会根据继承表的顺序逆序执行父类的析构函数
    注意:父类的指针可以指向子类对象,当通过父类指针释放对象时,只会调用父类的析构函数,而这种析构方式有可能造成内存泄漏     Base* p=new A("ff");
    3、当使用子类对象来初始化新的子类对象时,会自动调用缺省的拷贝构造函数,并且会先调用父类缺省的拷贝构造函数
    如果没实现子类的拷贝构造,实现了父类的拷贝构造,会默认调用父类的拷贝构造
    如果只实现子类的拷贝构造,会默认调用父类的无参构造
    如果子类和父类都实现了拷贝构造,需要显式调用父类拷贝构造,否则就会调用父类无参构造
  

    /*A(A& that)           //默认调用父类无参构造
    {
        cout<<"Akaobei"<<endl;
    }*/
    A(A& that):B(that)   //显式调用 父类拷贝构造
    {
        cout<<"Akaobei"<<endl;
    }

二、私有继承、保护继承

1、私有继承
 使用 private 方式继承父类,公开的变成私有的,其它的不变量,这种继承方式防止父类的成员扩散   
 使用 protected 方式继承父类,公开的成员在子类中会变成保护的,其它的不变量,这种继承方式可以有效防止父类的成员扩散   子类以私有或保护方式继承父类,会禁止向上造型(子类的指针或引用不能隐式转换成父类的指针或引用,要想实现多态只能以公开方式继承父类)

三、多重继承、钻石继承、虚继承

 1、多重载继承
    在C++中一个子类可以有多个父类,在继承表中按照顺序继承多个父类中的属性和行为,并按照顺序表中的调用父类的构造函数
    按照从低到高的地址顺序排列父类,子类中会标记每个父类存储位置
    当子类指针转换成父类的隐式指针的时候,编译器会自动计算父类中的内存所在子类中的位置,地址会自动进行偏移计算。

    2、名字冲突   A::num     
    如果父类中有同名的成员,可以正常继承,但如果直接使用会造成歧义,不能直接访问,需要 类名::成员名 进行访问

    3、钻石继承
    假如有一个类A,A中有一个成员整型a,类B继承类A,类C也继承类A,然后类D继承类B和类C
    一个子类继承多个父类,这些父类有一个共同的祖先,这种继承叫钻石继承
    注意:钻石继承不会导致继承错误,但当访问祖先类中的成员时每次需要使用 类名::成员名,重点是这种继承会造成冗余。
    
    B的大小由4变成了8,C的大小由4变成了8,C的大小变成了12

    4、虚继承  virtual     给类加的,在继承的时候
    当进行钻石继承时,祖先类中的内容会有冗余,而进行虚继承后,在子类中的内容只会保留一份。
    注意:但使用虚继承时子类中会多了一些内容(指向从祖先类继承来的成员)。    5、构造函数
    一旦进行了虚继承(钻石继承),祖先类的构造函数只执行一次,由孙子类直接调用,祖先类的有参构造也需要在孙子类中显式调用

#include<iostream>
using namespace std;

class A
{
    int a;
public:
    A(int num)
    {
        cout<<"Ayoucan"<<num<<endl;
    }
};
class B:virtual public A
{
public:
    B(void):A(1)
    {
        cout<<"B"<<endl;
    }
};

class C:virtual public A
{
public:
    C(void):A(2)
    {
        cout<<"C"<<endl;
    }
};
class D:virtual public B,virtual public C
{
public:
    D(void):A(3)
    {
        cout<<"D"<<endl;
    }
};
int main()
{
    D d;
}

6、拷贝构造
    在虚继承(钻石继承)中,祖先的拷贝构造也由孙子类直接调用,子类中不再调用祖先类的拷贝构造,在手动实现的拷贝构造时(深拷贝),祖先类中的内容也由孙子类负责拷贝,同理,赋值构造也一样。

四、虚函数、覆盖、多态

1、虚函数
    类的成员函数前加 virtual 这种函数就叫虚函数
    2、虚函数的特点:可以被覆盖
    子类会覆盖父类的虚函数。父类通过指针调用的是覆盖的版本

#include<iostream>
using namespace std;
class A
{
public:
    void func(void)
    {
        cout<<"A"<<endl;
    }
};
class B:public A
{
public:
    void func(void)
    {
        cout<<"B"<<endl;
    }
};
int main()
{
    /*A* a=new B;
    a->func();*/
    B* b=new B;
    A* p=b;
    b->func(); //调用子类中的函数
    p->func(); //调用父类中的函数
}

执行结果:

B
A
-------------------------------------
父类中加virtual  :virtual void func(void)
B
B

    3、多态
    当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象是谁来决定执行谁的函数,这就是多态
    

#include<iostream>
#include<stdlib.h>
using namespace std;
class A
{
public:
    virtual void func(void)
    {
        cout<<"A"<<endl;
    }
};
class B:public A
{
public:
    void func(void)
    {
        cout<<"B"<<endl;
    }
};
class C:public A
{
public:
    void func(void)
    {
        cout<<"C"<<endl;
    }
};
int main()
{
    srand(time(NULL));
    A* arr[]={new B,new C};
    arr[rand()%2]->func();
}


五、覆盖和多态的条件

        1、覆盖的条件
        必须是成员函数
        必须是虚函数
        必须是父子类之间,可以不同作用域
        函数签名必须相同(参数列表完全一致,const属性也会影响覆盖的结果) 返回值不一样不覆盖
        返回值必须是同类或父子类(子类的返回值要能向父类的隐式转换)
        访问属性(public/private/protected)不会影响覆盖    
class A:public Base

A* a=new A;
Base* p=a;
p->func();   //可以访问子类的私有成员

        常函数属性也会影响覆盖

    2、重载、隐藏、覆盖(重写)的区别
    重载:发生在同一作用域下的同名函数,函数签名不同,构成重载关系   返回值不同,不能构成重载
    覆盖:符合一系列条件。必须是函数
    隐藏:父子类之间的同名成员如果没有形成覆盖,且能通过编译,必定构成隐藏

签名可包含以下内容:

参数 及参数的 类型
一个的返回值及其类型
可能会抛出或传回的异常
该方法在 面向对象程序中的可用性方面的信息(如public、static或prototype)。

    3、多态的条件
        1、父子类之间有函数覆盖关系。
        2、父类的指针或引用指向子类对象
A* a=new A;
Base* p=a;
p->func("hehe");
    在覆盖版本的函数(子类中的函数)中所得到的this指针依然是实际对象(a)的地址,依然能够调用子类中的函数
    4、在构造、析构函数中调用虚函数
    在父类的构造函数中调用虚函数,此时子类还没有创建完成(回顾构造函数的调用过程),因此只能调用父类的虚函数,而不是覆盖本的虚函数
    在父类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,而不是覆盖版本的虚函数

#include<iostream>
using namespace std;

class Base
{
public:
    int num;
    Base(void)
    {
        func();
    }
    virtual void func(void)
    {
        cout<<"Base"<<endl;
    }
    ~Base(void)
    {
        func();
    }
};
class A:public Base
{
public:
    void func(void)
    {
        cout<<"A"<<endl;
    }
};
int main()
{
    A a;    
}

六、纯虚函数和抽象类

   1、纯虚函数   必须覆盖
    在虚函数的声明的后面添加=0,这种虚函数就叫纯虚函数,可以不实现,但如果实现必须在类外(只能在自己类的构造函数、析构函数中调用)    实现也没意义
     virtual 返回值 函数名(参数)=0;
    
    2、抽象类
    成员函数中有纯虚函数,这种类叫抽象类,抽象类不能实例化(不能创建对象)
    抽象类必须被继承且纯虚函数被覆盖后,由子类实例化对象。
    如果继承了抽象类,但没有覆盖纯虚函数,那么子类也将成为抽象类,不能实例化

    3、纯抽象类
    所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类
    这种类一般用来设计接口,这种类在子类被替换后不需要修改或少量的修改即可继续使用
 

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void show(void)=0;
};

class A:public Base
{
public:
	void show(void)
	{
		cout<<"A"<<endl;
	}
};

class B:public Base
{
public:
	void show(void)
	{
		cout<<"B"<<endl;
	}
};

class C:public Base
{
public:
	void show(void)
	{
		cout<<"C"<<endl;
	}
};

enum ClassType{typeA,typeB,typeC};
//工厂类模式
Base* create_object(ClassType type)
{
	switch(type)
	{
		case typeA:return new A;
		case typeB:return new B;
		case typeC:return new C;
		default: return NULL;
	}
}
int main()
{
	Base* p=create_object(typeA);
	p->show();
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值