C++继承与多态入门

继承

单继承

class Derived:<public/protected/private> Base{

…// 派生类成员数据

…//派生类成员函数

}

基类共有成员私有成员保护成员
公有派生类公共成员不可访问成员保护成员
保护派生类保护成员不可访问成员保护成员
私有派生类私有成员不可访问成员保护成员

派生类与基类同名成员访问

派生类成员会默认覆盖基类成员,即访问基类和派生类的同名成员会默认访问派生类的成员

如果想要访问基类成员,可以通过显式规则访问:

基类名::成员

赋值兼容原则

公有派生情况下,派生类可以作为基类对象来使用:

  • 派生类对象可以直接赋值给基类对象

  • 基类对象的引用可以直接引用一个派生类对象

  • 基类对象指针可以直接指向一个派生类对象

即,所有的派生类都是基类,但是基类不一定是派生类

(当一个派生类对象赋值给基类对象时,赋值的只是基类所拥有派生类的一部分,称作派生类对象的“切片”)

单继承的构造与析构

派生类构造函数:

DerivedFunction(argList):BaseFunction(argList),Obj1(argList)…Objn(argList){

…//初始化自定义成员

}

例如:

class Circle{
public:
    int x,y,r;

    Circle(int x,int y,int r){
    this->x=x;
    this->y=y;
    this->r=r;
	}

	~Circle()
}

class ColorCircle:public Circle{
    char* color;

    ColorCircle(int x,int y,int r,cahr*color):Circle(x,y,r){
		this->color=color;
    }
    /*或者可以这么调用基类构造函数
    ColorCircle(int x,int y,int r,char* color):x(x),y(y),r(r){
    	this->color=color;
    }
}

构造函数与析构函数调用顺序:

  1. 构造函数调用顺序:

    • 先调用基类构造函数
    • 再调用类中对象成员的构造函数
    • 最后调用派生类构造函数
  2. 析构函数调用顺序:

    • 先调用派生类析构函数

    • 在调用类中对象成员的析构函数

    • 最后调用基类析构函数

多态

**动态绑定:**编译器可以自动根据情况确定使用基类函数还是派生类函数

编译时的多态

  1. 函数重载
  2. 运算符重载

运行时的多态

  • 虚函数

二元运算符重载

基本数据类型运算符本身已经隐含重载,比如“+”可以用作整型、浮点型等不同数据类型的加法

如果直接把运算符用在类对象上,由于本身没有关于自定义类的重载的运算符,因此需要在代码中手动重载

class Complex{

    double re, im;

    public:

        Complex(double r=0.0, double i=0.0): re(r), im(i){}
    	Complex add(Complex c){
        	Complex t;
        	t.re=re+c.re;t.im=im+c.im;
        	return t;
        }
}


int main(){
    Complex c1(1,2),c2(3,4);
    Complex c3=c1.add(c2);
    return 0;
}
//这是一般情况下两个类加法最直接想到的方法
//但是实际上c1+c2这种方法更符合我们的使用习惯,此时就需要进行运算符重载
class Complex{

    double re, im;

    public:

        Complex(double r=0.0, double i=0.0): re(r), im(i){}
    	Complex operator+(Complex c){
            //在重载某一个运算符时要在运算符前加上关键字:operator
            //ReturnType operator(argList){}
            complex t;
            t.re=re+c.re;
            t.im=im+c.im;
            return t;
        }
}

int main(){
    Complex c1(1,2),c2(3,4);
    Complex c3=c1+c2;
    return 0;
}

//通过这种方法看起来好像实现了运算符重载,但事实上并没有,这里的 + 是Complex成员函数 operator+ 的隐式调用
//即,c3=c1+c2...(隐式调用)  <==>  c3=c1.operator+(c2)...(显式调用)
//如果 + 前面的变量不是Complex类型就仍然无法进行运算,仍旧报错
/* 
例如:
c3 = c2+27;	···········正确
c3 = 27+c2; ···········错误
*/

用友元函数实现重载:这样当使用"+"时,由于编译器自带 operator+ 函数没有参数类型为我们自定义类类型的重载,便会来调用类中自定义 operator+ 函数

class Complex{

    double re, im;

    public:

        Complex(double r=0.0, double i=0.0): re(r), im(i){}
    	friend Complex operator+(Complex c1,Complex c2){
            //称 Complex operator+(Complex c1,Complex c2) 为运算符重载函数
            complex t;
            t.re=c1.re+c2.re;
            t.im=c1.im+c2.im;
            return t;
        }
}

int main(){
    Complex c1(1,2);
    Complex c3=c1+27;	·······正确
    Complex c3=27+c1;	·······正确
    return 0;
}

操作符"<<"的重载

ostream& operator<<(Complex& a,Complex& b){
    a<<b.re<<"+"<<b.im<<"i"<<endl;
    return a;
}

一元运算符重载

class Counter{
    int value;
public:
    Counter(value=0){
        this->value=value;
    }
    
    Counter++();
    ++Counter();
    cout<<Counter;		········这三种重载又如何实现呢
}

可以参照 + 的重载办法,用 friend 重载

friend Counter operator++(Counter &obj){
    //通过引用可以直接改变值,不然只是改变了复制进来的形参的值
    obj.value+=1;
    return obj;
}

实际上这种重载只是重载了 ++Counter
而Counter++的重载就需要增加占位参数

friend Counter operator++(Counter &obj,int){
    Counter tmp=obj;
    obj.value++;
    return tmp;
}
// 这里添加临时变量是考虑了Counter++本身的特性:值自增,但是返回的是自增前的值

也可以直接定义为成员函数

Counter operator++(){
    value++;
}

Counter operator++(int){
    tmp=*this;
    value++;
    return tmp;
}

两种重载函数的比较:

  • 多数情况下,运算符可以重载为类的成员函数,也可以重载为友元函数。但是两种方式也各有特点:
    • 一般情况下,单目运算符重载为类的成员函数;双目运算符重载为类的友元函数;
    • 有些运算符不能重载为类的友元函数:=,(),[],->
    • 类型转换函数只能定义为类的 成员函数;
    • 若一个运算符需要修改对象的状态,则重载为成员函数比较好;
    • 若运算符所需要的操作数(尤其是第一个操作数) 希望有隐式类型转换,则只能选择友元函数;
    • 若运算符是成员函数,最左边的操作数必须是运算符类的类对象(或者类对象的引用)。如果左边操作数必须是一 个不同类的对象,或者是基本数据类型,则必须重载为友元函数;
    • 当需要重载运算符的元素具有交换性(就如前面的c2+27 <==> 27+c2)时,重载为友元函数.

缺省的赋值运算符重载

如果没有为类重载赋值运算符,那么编译器会生成一个缺省的赋值运算符函数,其作用是通过为拷贝的方式将原对象复制到目的对象。

赋值运算符与拷贝构造函数异同------------------------------------------
相同点不同点
将一个对象数据成员复制到另一个对象拷贝构造函数是要初始化一个新对象,赋值运算符是要改变一个已经存在的对象

虚函数与多态性

重载成员函数的两种方式(静态多态):

  • 同一个类内重载:以参数特征区分重载函数

  • 派生类中重载成员函数:

    ​ 调用时通过 “类名::函数名” 调用

**虚函数(动态多态):**函数调用与函数体的匹配在运行时确定

class Base{
public:
	void print(){
	cout<<"I'm base"<<endl;
    }
}


class Derived:public Base{
public:
    void print(){
        cout<<"I'm derived"<<endl;
    }
}


int main(){
    Base base,*p;
    Derived derived;
    
    p=&base;
    p->print();		//···········调用的是基类的print函数
    
    p=&derived;
    p->print();		//···········调用的是基类的print函数
    
    return 0;
}

通过基类引用或指针访问的对象,派生类对象的属性是不可见的,访问的全部是基类的属性

可以将基类函数定义为虚函数,这样在利用基类引用或指针访问对象时,可以通过其引用或对象类型来自动确定访问基类或者派生类对象

class Base{
public:
	virtual void print(){
	cout<<"I'm base"<<endl;
    }
}


class Derived:public Base{
public:
    void print(){
        cout<<"I'm derived"<<endl;
    }
}


int main(){
    Base base,*p;
    Derived derived;
    
    p=&base;
    p->print();		//···········调用的是基类的print函数
    
    p=&derived;
    p->print();		//···········调用的是派生类的print函数
    
    return 0;
}
  • 在基类中用virtual关键字声明的成员函数即为虚函教。

  • 虚函数可以在一个或多个派生类中被重新定义,但要求在重定义时虚函数的原型(包括返回值类型、函数名、参数列表)必须完全相同。

基类函数具有虚特性条件:

  • 在基类中用virtual将函数说明为虚函数。

  • 在公有派生类中原型一致地重 载该虚函数。

  • 定义基类弓|用或指针,使其引用或指向派生类对象。当通过该引用或指针调用虚函数时,该函数将体现出虚特性来。

  • C++中,基类必须指出希望派生类重定义那些函数。定义为virtual的函数基类期待派生类重新定义的,基类希望派生类继承的函数不能定义为虚函数。

注意事项:

  • 在派生类中重载虚函数时必须与基类中的函数原型相同,否则该函数将丢失虚特性。

  • 仅返回类型不同,其他相同。C+ +编译器认为这种情况是不允许的。

  • 函数原型不同,仅函数名相同。C++编译器认为这是一般的函数重载,此时虚特性丢失。

纯虚函数与抽象类

基类表示抽象的概念,提供公共接口,只需要有说明而不需要实现,即纯虚函数,具体实现在基类中实现。

定义纯虚函数语法:

virtual type FunctionName(argList)=0

  • 将一个函数说明为纯虚函数,就要求任何派生类都定义自己的实现。

  • 拥有纯虚函数的类被称为抽象类。抽象类不能被实例化,只能作为基类被使用。

  • 抽象类的派生类需要实现纯虚函数,否则该派生类也是一个抽象类。

  • 当抽象类的所有函数成员都是纯虚函数时, 这个类被称为接口类

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值