继承、重载、多态学习总结

上次的总结是类与对象,这次是最近学的对象的初始化、复制和销毁,运算符重载,组合与继承,虚函数与多态,面向对象程序设计的特点:封装、继承、多态就都学完了。
  第七章对象的初始化、复制和销毁:
一、对象初始化
  不同的初始化形式,对于类类型的对象来说,意味着要调用不同的构造函数。对象初始化有4种:默认初始化、直接初始化、拷贝初始化、列表初始化。
1、默认初始化:
  定义对象时没有指定初值,调用类中的默认构造函数,对象被默认初始化。
例如:
Student name; //调用Student()
Student name[2]; //调用两次Student(),初始化每个数组元素
2、直接初始化:
  初始值在圆括号“()”中,可以提供多个初始值,根据初始值类型和个数直接调用最匹配的构造函数。
例如:
Student name(mingzi);//调用和mingzi类型匹配的Student(string)
3、拷贝初始化:
  用赋值号“=”初始化一个对象时,执行拷贝初始化,编译器用赋值号右边的初始值创建一个对象,复制给新创建的对象。赋值号右边的初始值只能有一个,调用与初始值类型匹配的构造函数。
例如:
Student name = mingzi; //用mingzi调用Student(string),复制给name
4、列表初始化:
  用花括号“{}”中的初始值构造对象,调用相应的构造函数,与直接初始化类似。
例如:
Student number{123};
  花括号也可以是初始值列表,用来初始化数组的每个元素,此时对每个值调用构造函数,创建数组元素,如果初始值的个数少于数组大小,对后面的元素调用默认构造函数来初始化
例如:
Student number[3] = {1,2}; //用1和2调用两次Student(int)创建nubmer[0]和number[1],number[2]则调用Student()
二、,默认构造函数
  通常需要为类定义一个默认构造函数,如果类没有定义任何构造函数,编译器会在需要时自动合成一个默认构造函数,一旦定义了构造函数,即使不是默认构造函数,编译器也不再合成,当对象被默认初始化或值初始化时自动执行默认构造函数。

class X{
	int m,n;
public:
	X(){m=0;n=0;}//默认构造函数
	X(int x,int y){m=x;n=y;}//构造函数
};
int main()
{
	X x1;//调用默认构造函数X()
	X x2(1,2)//调用构造函数X(int,int)
}

三、析构函数
  对象离开作用域时析构函数被自动调用,析构函数包括函数体和隐式的析构部分。首先执行函数体,然后执行析构部分销毁成员,而构造函数却相反,析构函数销毁成员时按初始化的逆序销毁,销毁方式完全依赖于成员类型,销毁类类型的成员需要执行成员自己的析构函数。
四、拷贝构造函数
浅复制:用已有对象去初始化另一个同类型的对象
  X one;
  X two(one); //用one初始化同类型对象two
  用one 初始化two 时需要构造函数X(X&),称为拷贝构造函数
  如果在类中没有定义这样的构造函数,编译器会自动合成一个,默认的行为是逐个成员复制
  X two(one)就是用one 中的每个成员分别去初始化two 的每个对应的成员
  类中包含指针或引用成员,浅复制就不恰当了。

class X {
	int m;
	int& r;
	int* p;
public:
	X(int mm=0):m(mm),r(m),p(&m){}
	void changep(){*p = 10;}
	void changer(){ r = 5;}
};
int main()
{
	X a;
	X b(a);
	b.changep();
	b.changer();
}

深复制:

class X {
	int m;
	int& r;
	int* p;
public:
	X(int mm=0):m(mm),r(m),p(&m){}
	X(const X&a):m(a.m),r(m),p(&m){}
	void changep(){*p = 10;}
	void changer(){ r = 5;}
};
int main()
{
	X a;//X(int):a.m=0;a.r=a.m,a.p=&a.m
	X b(a);//X(const X&):b.m=0;b.r=b.m,b.p=&b.m
	b.changep();//b.m=10
	B.changer();//b.m=5 不会影响a
}

在这里插入图片描述
浅复制实现方式是简单赋值,深复制是申请资源以后简单赋值(已知对象创建)
五、拷贝赋值运算符
  类可以通过重载赋值运算符控制其对象如何赋值,类X的赋值运算符要定义为类X的成员函数,X& operator=(const X&);
  如果类中没有重载operator=(),编译器将在需要时自动合成一个,其行为是按成员赋值,对于复杂的类,尤其是包含指针成员时,应该显式地创建operator=(),编写拷贝赋值运算符时,要切记两点:1、如果一个对象给自身赋值,赋值运算符必须能正确工作。2、大多赋值运算符组合了析构函数和拷贝构造函数的工作。
  第八章运算符重载:
  重载运算符声明为友元,以便访问私有数据成员
  运算符重载的实质:运算符重载仅仅提供了一种语法上的方便,是以另外一种方式调用函数。
  不能滥用运算符重载
  只有至少一个操作数是用户自定义类型时,才可能调用重载的运算符,只有在能使类的代码更易读、使类对象的操作方式更符合一般习惯时,才重载运算符,运算符重载不会改变内置类型的表达式中运算符的含义。
  不建议重载的特殊运算符:逻辑与(&&)、逻辑或(||)、逗号运算符(,)、取地址运算符(&)
  可以重载的运算符:
可以重载的运算符
  对于类类型的参数,如果仅仅只是读参数的值,而不改变参数,应该作为const引用来传递,当运算符函数是类的成员函数时,就将其定义为const成员函数。
  输入输出运算符的函数原型:
  istream& operator>>(istream&, type&);
  ostream& operator<<(ostream&, const type&);
  第九章组合与继承:
一、组合(包含):将一个类的对象作为另一个类的成员
  成员对象是组合对象的一部分,随着组合对象的创建而创建,随着组合对象的撤销而撤销,成员对象不作为独立元素对外部展现。
例如:Car和Engine的组合关系:汽车中包含引擎,引擎是汽车的一部分,对外展现的只是汽车的整体功能

class Engine{
public:
	void fire(void){...}	
	void stall(void){...}
};
class Car {
public:
	void run() { engine.fire();	} 
	void stop() { engine.stall();}
private:
	Engine engine;	
};
int main()
 {
 	Car benz;		
  	benz.run();	
  	benz.stop();
}

创建包含对象成员的组合对象时,会执行成员类的构造函数初始化对象成员,如果成员对象所属的类不存在默认构造函数,会引起编译错误,成员对象的初始化使用初始化列表语法。
当组合对象被撤销时,会执行其析构函数,成员对象的析构函数也会被执行。
析构函数的执行次序和构造函数相反。
二、复用类:当一个类的功能与所需类的功能类似,应用组合,提供新接口

class XCircle {
public:
	XCircle(); //构造函数
	void Xdraw();	//绘制圆形
	double calc_area();	//计算面积
	double calc_perimeter();//计算周长
	void zoom(double factor); //按比例缩放
};
class Circle {
public:
	Circle() : xc() {}
	void draw(){xc.Xdraw();}		
	double area() {return xc.calc_area();}
	double perimeter(){return xc.calc_perimeter();}
 void scale(double factor) {xc.zoom(factor);}
private:
	XCircle xc;
};

三、继承:在已有类的基础上创建新类的过程
class 派生类名 : 基类名表
{
  数据成员和成员函数声明
};
基类名表 构成:访问控制 基类名1, 访问控制 基类名2 ,… , 访问控制 基类名n
访问控制 表示派生类对基类的继承方式,使用关键字:
  public公有继承
  private私有继承
  protected 保护继承
派生类不能直接使用基类的私有成员
派生类的生成过程经历了三个步骤:
  1、吸收基类成员(全部吸收(构造函数、析构函数除外),但不一定可见)
  2、改造基类成员
  3、添加派生类新成员
为了帮助我更好的理解继承,我自己打了下面代码进行了调试

class Person{
    string name;
    int age;
public:
    Person(){};
    Person(string n, int a):name(n),age(a){};
    string getname(){return name;}
    int getage(){return age;}
    bool operator < (const Person &p) const {
        return name<p.name&&age<p.age;
    }
    bool operator == (const Person &p) const {
        return name==p.name&&age==p.age;
    }
    friend ostream &operator << (ostream &os,const Person &p){
        os<<p.name<<" "<<p.age<<endl;
        return os;
    }
    friend istream &operator >> (istream &is,Person &p){
        is>>p.name;
        if(p.name=="end")
        return is;
        is>>p.age;
    }
};

class Personop{
    Person person;
    vector<Person> vp;
    vector<Person>::iterator pit;
public:
    void push(Person p){return vp.push_back(p);}
    void addp();
    void sendp();
};

void Personop::addp(){
    while(1)
    {
    	cin>>person;
        if(person.getname()=="end") break;
        push(person);
    }
};

void Personop::sendp(){
        for(pit=vp.begin();pit!=vp.end();pit++)
        cout<<*pit;
};

class Student:public Person{
public:
    Student(){};
    Student(string na,int ag):Person(na,ag){};
};

class Studentop:public Personop{
public:
    void adds(){addp();}
    void sends(){sendp();}

};

基类与派生类关系:
在这里插入图片描述
  第十章虚函数与多态:
一、多态:指一个名字,多种语义或界面相同,多种实现。
  冠以关键字 virtual 的成员函数称为虚函数,实现运行时多态的关键首先是要说明虚函数,另外,必须用基类指针调用派生类的不同实现版本。
  通过基类指针,只能访问从基类继承的成员
注意:
  1、一个虚函数,在派生类层界面相同的重载函数都保持虚特性
  2、虚函数必须是类的成员函数
  3、虚函数可以是另一个类的友元
  4、析构函数可以是虚函数,但构造函数不能是虚函数
  5、在派生类中重载基类的虚函数要求函数名、返回类型、参数个数、参数类型和顺序完全相同(仅仅返回类型不同,C++认为是错误重载,函数原型不同,仅函数名相同,丢失虚特性 )
二、纯虚函数
  纯虚函数是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去做。
  纯虚函数是一个在基类中说明的虚函数,在基类中没有定义,要求任何派生类都定义自己的版本。
  纯虚函数为各派生类提供一个公共界面。
  纯虚函数说明形式:
  virtual 类型 函数名(参数表)= 0 ;
例如:virtual void draw ( ) = 0 ;// 纯虚函数
一个具有纯虚函数的基类称为抽象类。
下面是自己测试的多态代码和截图(加了virtual后输出的不同)

 class  Base
{
public :
    Base(char xx)  { x = xx; }
     void who()  { cout << "Base class: " << x << "\n" ; }
protected:
    char x;
};

class  First_d:public  Base
{
public :
    First_d(char xx, char yy):Base(xx){ y = yy; }
    void who()  { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected:
    char y;
} ;

class  Second_d : public  First_d
{
public :
    Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
    void who()  { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected:
    char z;
} ;
int main()
{
    Base  B_obj( 'A' ) ;
    First_d F_obj( 'T', 'O' ) ;
    Second_d S_obj( 'E', 'N', 'D' ) ;
    Base  * p ;
    p = & B_obj ;
    p -> who() ;
    p = &F_obj ;
    p -> who() ;
    p = &S_obj ;
    p -> who() ;
}

在这里插入图片描述

class  Base
{
public :
    Base(char xx)  { x = xx; }
    virtual void who()  { cout << "Base class: " << x << "\n" ; }
protected:
    char x;
};

class  First_d:public  Base
{
public :
    First_d(char xx, char yy):Base(xx){ y = yy; }
    void who()  { cout << "First derived class: "<< x << ", " << y << "\n" ; }
protected:
    char y;
} ;

class  Second_d : public  First_d
{
public :
    Second_d( char xx, char yy, char zz ) : First_d( xx, yy ) { z = zz; }
    void who()  { cout << "Second derived class: "<< x << ", " << y << ", " << z << "\n" ; }
protected:
    char z;
} ;
int main()
{
    Base  B_obj( 'A' ) ;
    First_d F_obj( 'T', 'O' ) ;
    Second_d S_obj( 'E', 'N', 'D' ) ;
    Base  * p ;
    p = & B_obj ;
    p -> who() ;
    p = &F_obj ;
    p -> who() ;
    p = &S_obj ;
    p -> who() ;
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值