3.C++深入理解 面向对象部分2

C++深入理解 面向对象部分

一、面向对象深入部分

  接下来接续深入 c++ 面向对象部分知识。

1. 继承和派生

1.1 继承和派生

  继承:在定义一个新的类 B 时,如果该类与某个已有的类 A 相似(指的是 B 拥有 A 的全部特点),那么就可以把 A 作为一个基类,把 B 作为基类的一个派生类(也称子类)。派生类是通过基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。派生类一旦定义,就可以使用,不依赖基类。
  派生类拥有基类的全部成员函数,不论 private, protected, public 。在派生类的各个成员函数中,不能访问基类的 private 成员。写法为

class 派生类名:public 基类名{};

class Student{
	private:
		string Name;
		int age;
	public:
		bool IsThreeGood(){};
		void setName(const string & name);
		{Name = name;}
		// ...
};

class UndergraduateStudent:public Student{
	private:
		int Department;
	public:
		bool IsThreeGood(){......}; // 覆盖
		bool CanBaoYan(){......};
};  // 派生类写法:类名 puclic 基类名

class GraduateStudent:public Student{
	private:
		int Department;
		char mentorName[20];
	public:
		int CountSalary(){};
};

  派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。派生类对象中,包含基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。例如,

#include <iostream>
#include <string>
using namespace std;

class Student{
    private:
        string name;
        string id;
        char gender; // "F"代表女,"M"代表男
        int age;
    public:
        void printInfo();
        void setInfo(const string & name_, const string & id_,
                     int age_, char gender_);
        string getName(){return name;}
};
void Student::printInfo(){
    cout << "name:" << name << endl;
    cout << "id:" << id << endl;
    cout << "gender:" << gender << endl;
    cout << "age:" << age << endl;
}
void Student::setInfo(const string & name_, const string & id_,
                     int age_, char gender_){
    name = name_;
    id = id_;
    age = age_;
    gender = gender_;
}

class UndergraduateStudent:public Student{  // 本科生类,继承了Student类
    private:
        string department; // 学生所属的系的名称
    public:
        void qualifiedForBaoyan(){ // 给予保研资格
            cout << "qulified for baoyan" << endl;
        }
        void printInfo(){
            // 不写的话,就会当作派生类自己的函数
            Student::printInfo(); // 调用基类的 printInfo
            cout << "Department:" << department << endl;
        }
        void setInfo(const string & name_, const string & id_,
            int age_, char gender_, const string & department_){
            Student::setInfo(name_, id_, age_, gender_); // 调用基类的 setInfo
            department = department_;
        }
};

int main(){
    UndergraduateStudent s2;
    s2.setInfo("Harry Potter", "118829212", 19, 'M', "Computer Science");
    cout << s2.getName() << " ";
    s2.qualifiedForBaoyan();
    s2.printInfo();
    return 0;
}

视频里有两个函数没有写,所以运行出来有问题,后来我自己补充上运行得到了结果,如下,
在这里插入图片描述

1.2 继承关系和复合关系

  继承是“是”的关系,复合是“有”的关系。举个例子,要写一个圆和点的类。圆里面有点,所以要写成复合关系,如下

class Point{
	double x, y;
	friend class Circle; // 因为 x,y是私有的
}

class Circle{
	double r;
	Point center;  // 复合类
}

  复合关系的使用:比如有一个业主和狗管理信息情况,那就只能用复合关系来写,其中每个主人最多有10条狗,所以定义如下,

// 为“狗”类写一个“业主”类的对象指针;
// 为“业主”类设一个“狗”类的对象指针数组。
class Master; // Master 必须提前声明,不能先写 Master 类后写 Dog 类

class Dog{
	Master * pm;
}
class Master{
	Dog * dogs[10];
}
// 这样两个类可以相互独立,也可以找到他们的关系
1.3 覆盖和保护成员

  覆盖:派生类可以定义一个和基类成员同名的成员,这叫覆盖。在派生类中访问这类成员时,缺省情况是访问派生类定义的成员。要在派生类中访问基类同名成员时,要使用作用域::。
  一般来说,基类和派生类不定义同名成员变量。
  类的保护成员,汇总如下,
在这里插入图片描述
  举个例子,

#include <iostream>
#include <string>
using namespace std;

class Father{
	private: int Private;
	public: int Public;
	protected: Protected;
};
class Son:Father{
	void accessFather(){
		Public = 1;  // 可以
		Private = 1;  // 不可以
		Protected = 1;  // 可以,访问从基类继承的 protected 成员
		Son f;  // 没太理解
		f.Protected = 1; // 编译出错, f 不是当前对象
	}
}

int main(){
	Father f;
	Son s;
	f.Public = 1; // 可以
	f.Private = 1; // 不可以
	f.Protected = 1; // 不可以

	s.Public = 1; // 可以
	s.Protected = 1; // 不可以
	s.Private = 1; // 不可以
	
	return 0;
}
1.4 派生类的构造函数

  举个例子,

#include <iostream>
#include <string>
using namespace std;

class Bug{
	private:
		int legs;
		int color;
	public:
		int type;
		Bug(int nlegs, int ncolor);
		void printBug(){};
};
class flyBug: public Bug{
	int wings;
	public:
		flyBug(int nlegs, int ncolor, int nwings);
};
Bug::Bug(int nlegs, int ncolor){
	legs = nlegs;
	color = ncolor;
}
// 错误的 flyBug 构造函数
/*
flyBug::flyBug(int nlegs, int ncolor, int nwings){
	legs = nlegs;  // 不能访问
	color = ncolor; // 不能访问
	type = 1; // 可以
	wings = nwings; // 可以
}*/
// 正确的 flyBug 构造函数
flyBug::flyBug(int nlegs, int ncolor, int nwings):Bug(nlegs, ncolor){
	wings = nwings; // 可以
}

int main(){
	flyBug fb(2, 3, 4);
	fb.printBug();
	fb.type = 1;
	// fb.Legs = 2;  // 错误,私有的
	return 0;
}

  在创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员。在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
  调用基类构造函数的两种方式,

显示方式:在派生类的构造函数中,为基类的构造函数提供参数;
        derived::derived(arg_derived-list):base(arg_base-list)
隐式方式:在派生类的构造函数中,省略基类构造函数时,派生类的构造函数则自动调用基类的默认构造函数。

  派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的构造函数。例如

#include <iostream>
#include <string>
using namespace std;

class Base{
	public:
		int n;
		Base(int i):n(i){
			cout << "Base " << n << " constructed" << endl;
		}
		~Base(){
			cout << "Base " << n << " destructed" << endl;
		}
};
class Derived: public Base{
	public:
		Derived(int i):Base(i){
			cout << "Derived constructed" << endl;
		}
		~Derived(){
			cout << "Derived destructed" << endl;
		}
};
int main(){
	Derived Obj(3);
	return 0;
}

  结果如下,
在这里插入图片描述
  包含成员对象的派生类的构造函数写法,如下

#include <iostream>
#include <string>
using namespace std;

class Bug{
	private:
		int legs;
		int color;
	public:
		int type;
		Bug(int nlegs, int ncolor);
		void printBug(){};
};
class Skill{
	public:
		Skill(int n){}
};
class flyBug:public Bug{
	int wings;
	Skill sk1, sk2;  // 包含成员对象的派生类
	public:
		flyBug(int nlegs, int ncolor, int nwings);
};
flyBug::flyBug(int nlegs, int ncolor, int nwings):
	Bug(legs, color), sk1(5), sk2(color), wings(nwings){
}
1.5 公有继承的赋值兼容规则

  public 继承的赋值兼容规则,

class base{};
class derived:public base{};
base b;
derived d;

// 1) 派生类的对象可以赋值给基类对象
b = d; // d 里面有的内容拷贝到 b 中
// 2) 派生类的对象可以初始化基类引用
base & br = d; // 基类引用派生类包含的那一个部分
// 3) 派生类的对象地址可以赋值给基类指针
base * bp = & d; // 指针指向派生类包含的那一个部分(先存基类,再存派生类) 
  1. 派生类的对象可以赋值给基类对象
    b = d; // d 里面有的内容拷贝到 b 中
  2. 派生类的对象可以初始化基类引用
    base & br = d; // 基类引用派生类包含的那一个部分
  3. 派生类的对象地址可以赋值给基类指针
    base * bp = & d; // 指针指向派生类包含的那一个部分(先存基类,再存派生类)
    如果不是public 就不成立了
      直接基类和间接基类的理解。B:A, C:B, D:C,多个继承,比较容易理解。
    ----------》派生类沿着类的层次自动向上继承它的间接基类。
    ----------》派生类成员包括:派生类自己的成员,直接基类的所有成员,所有间接基类的全部成员
    ----------》在声明派生类时,只需要列出它的直接基类就行
    ----------》构造函数执行从基类到最底类,由顶层开始。析构函数执行时,由底至顶

2. 虚函数和多态

2.1 虚函数和多态定义

  在类的定义中,前面有 virtual 关键字的成员函数就是虚函数。virtual 关键字只用在类定义里的函数声明时,写函数体时不用。构造函数和静态成员函数不能是虚函数。例如

// 虚函数可以参与多态,其他函数不能
class base{
	virtual int get();
};
int base::get(){}

多态的表现形式一,
----》派生类的指针可以赋给基类指针;
----》通过基类指针调用基类和派生类中的同名虚函数时:1) 若该指针指向一个基类的对象时,那么被调用是基类的虚函数;2) 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。**这种机制就叫做多态。**举个例子,

class Base{
	public:
		virtual void someVirtualFunc(){}
};
class Derived: public Base{
	public:
		virtual void someVirtualFunc(){}
};

int main(){
	Derived derived;
	Base *p = & derived;
	// 调用哪个虚函数取决于 p 指向哪种类型的对象
	// 指向的是 Derived 类对象的虚函数
	p -> someVirtualFunc();
	return 0;
}

多态的表现形式二,
----》派生类的对象可以赋给基类引用
----》通过基类引用调用基类和派生类中的同名虚函数时:1) 若该引用引用的是一个基类的对象,那么被调用的是基类的虚函数;2) 若该引用引用的是一个派生类的对象,那么被调用的是派生类的虚函数。这种机制也叫做多态。 举个例子,

class Base{
	public:
		virtual void someVirtualFunc(){}
};
class Derived: public Base{
	public:
		virtual void someVirtualFunc(){}
};

int main(){
	Derived derived;
	Base & r = derived;
	// 调用哪个虚函数取决于 r 引用哪种类型的对象
	// 指向的是 Derived 类对象的虚函数
	r.someVirtualFunc();
	return 0;
}

  多态的简单示例,

#include <iostream>
#include <string>
using namespace std;

class A{
    public:
        virtual void print(){
            cout << "A::print"<<endl;
        }
};
class B: public A{
    public:
        virtual void print(){
            cout << "B::print"<<endl;
        }
};
class D: public A{
    public:
        virtual void print(){
            cout << "D::print"<<endl;
        }
};
class E: public B{
    public:
        virtual void print(){
            cout << "E::print"<<endl;
        }
};

int main(){
    A a; B b;   E e; D d;
    A * pa = & a; B * pb = & b;
    D * pd = & d; E * pe = & e;

    pa -> print(); // pa 是基类指针,多态
    pa = pb;
    pa -> print();
    pa = pd;
    pa -> print();
    pa = pe;
    pa -> print();
    return 0;
}

在这里插入图片描述
  
  多态的作用: 在面向对象的程序设计中使用多态,能够增强程序的可扩充性,即程序需要修改或增加功能的时候,需要改动和增加的代码较少。

2.2 多态实例:《魔法门之英雄无敌》

  游戏中有很多种怪物,每种怪物都有一个类与之对应,每个怪物就是一个对象。怪物能够互相攻击,攻击敌人和被攻击时都有相应的动作,动作是通过对象的成员函数实现的。
  游戏版本升级时,要增加新的怪物–雷鸟。如何编程才能使升级时的代码改动和增加量较小?
  基本思路:
----》为每个怪物类编写 Attack,FightBack 和 Hurted 成员函数
----》Attack 函数表现攻击动作,攻击某个怪物,并调用被攻击怪物的 Hurted 函数,以减少被攻击怪物的生命值,同时也调用被攻击怪物的 FightBack 成员函数,遭受被攻击怪物的反击。
----》设置基类 Creature, 使用怪物类都从基类派生而来
  
  
  非多态写法:

#include <iostream>
#include <string>
using namespace std;

class Creature{
    protected:
        int power;   // 代表生命力
        int lifeValue;  // 代表生命值
};
class Deagon: public Creature{
    public:
        void Attack(Wolf* pWolf){
            // ... 表现攻击动作的代码
            pWolf -> Hurted(power);
            pWolf -> FightBack(this);
        }
        void Attack(Ghost* pGhost){
            // ... 表现攻击动作的代码
            pGhost -> Hurted(power);
            pGhost -> FightBack(this);  // 指向攻击发起者
        }
        void Hurted(int power){
            // ... 表现受伤的动作
            lifeValue -= power;
        }
        void FightBack(Wolf* pWolf){
            // ... 表现反击的动作
            pWolf -> Hurted(power / 2);
        }
        void FightBack(pGhost* pGhost){
            // ... 表现反击的动作
            pGhost -> Hurted(power / 2);
        }
};

  这样,有 n 中怪物,Deagon 类中就会有 n 个 Attack 成员函数,以及 n 个 FightBack 成员函数。对于其他类也是一样的。
  
  
  多态写法:

#include <iostream>
#include <string>
using namespace std;

class Creature{
    protected:
        int power;   // 代表生命力
        int lifeValue;  // 代表生命值
    public:
        virtual void Attack(Creature * pCreature){}
        virtual void Hurted(int power){}
        virtual void FightBack(Creature * pCreature){}
};
class Dragon: public Creature{
    public:
        virtual void Attack(Creature * pCreature);
        virtual void Hurted(int power);
        virtual void FightBack(Creature * pCreature);
};
void Dragon::Attack(Creature * pCreature){
    // ... 表现攻击动作的代码
    pCreature -> Hurted(power);  // 多态
    pCreature -> FightBack(this);  // 多态
}
void Dragon::Hurted(int power){
    // ... 表现受伤的动作
    lifeValue -= power;
}
void Dragon::FightBack(Creature * pCreature){
    // ... 表现反击的动作
    pCreature -> Hurted(power / 2);  // 多态
}

  如果游戏升级,增加了新怪物。只要编写新类怪物,不需要在已有的类里为新怪物增加成员函数,已有的类可以原封不动。原理如下,

#include <iostream>
#include <string>
using namespace std;

// 例如
Dragon dragon;    Wolf wolf;   Ghost ghost;
thunderBird Bird;
Dragon.Attack(& wolf);  // (1)
Dragon.Attack(& ghost);  // (2)
Dragon.Attack(& Bird);  // (3)

  根据多态的规则,上面的 (1), (2), (3) 进入到 Dragon::Attack 函数以后,能分别调用:Wolf::Hurted, Ghost::Hurted, thunderBird::Hurted.

2.3 多态实例:几何形体程序

  几何形体处理程序:输入若干个几何形体的参数,要求按面积排序输出。输出时要指明形状,

Input:
第一行是几何形体数目 n (不超过100).下面有 n 行,每行以一个字母 c 开头。
如 c 是 "R", 则代表一个矩形,本行后面跟着两个整数,分别是矩形的宽和高;
如 c 是 "C", 则代表一个圆形,本行后面跟着一个整数代表其半径;
如 c 是 "T", 则代表一个三角形,本行后面跟着三个整数,代表三条边长度;

Output:
按面积从大到小依次输出每个几何形体的种类及面积。每行一个几何形体,输出格式为:
形体名称:面积

Sample Input:
3
R 3 5
C 9
T 3 4 5

Sample Output:
Triangle: 6
Rectangle: 15
Circle: 254.32

  实现如下,

#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

class Shape{
    public:
        virtual double Area() = 0; // 纯虚函数
        virtual void printInfo() = 0;
};
class Rectangle: public Shape{
    public:
        double w, h;
        virtual double Area();
        virtual void printInfo();
};
class Circle: public Shape{
    public:
        double r;
        virtual double Area();
        virtual void printInfo();
};
class Triangle: public Shape{
    public:
        double a, b, c;
        virtual double Area();
        virtual void printInfo();
};
double Rectangle::Area(){
    return w * h;
}
void Rectangle::printInfo(){
    cout << "Rectangle:" << Area() << endl;
}
double Circle::Area(){
    return 3.14 * r * r;
}
void Circle::printInfo(){
    cout << "Circle:" << Area() << endl;
}
double Triangle::Area(){
    double p = (a + b + c) / 2.0;
    return sqrt(p * (p - a)*(p - b)*(p - c));
}
void Triangle::printInfo(){
    cout << "Triangle:" << Area() << endl;
}

// 进入的是数组元素指针
int myCompare(const void * s1, const void * s2){
    double a1, a2;
    Shape ** p1; // s1, s2 是 void*, 不可写"*s1"来取得s1指向的内容
    Shape ** p2;
    p1 = (Shape **)s1; // s1, s2 指向 Shape 数组中的元素,数组元素的类型是 Shape*
    p2 = (Shape **)s2; // p1, p2 都是指向指针的指针,类型为 Shape**
    a1 = (*p1) -> Area(); // *p1 的类型是 Shape*, 是基类指针,故此句为多态
    a2 = (*p2) -> Area();
    if(a1 < a2)return -1;
    else if(a2 < a1)return 1;
    else return 0;
}

Shape * pShapes[100];
int main(){
    int i; int n;
    Rectangle * pr; Circle * pc; Triangle * pt;
    cin >> n;
    for(i = 0; i < n; i++){
        char c;
        cin >> c;
        switch(c){
        case 'R':
            pr = new Rectangle();
            cin >> pr->w >> pr->h;
            pShapes[i] = pr;
            break;
        case 'C':
            pc = new Circle();
            cin >> pc->r;
            pShapes[i] = pc;
            break;
        case 'T':
            pt = new Triangle();
            cin >> pt->a >> pt->b >> pt->c;
            pShapes[i] = pt;
            break;
        }
    }
    qsort(pShapes, n, sizeof(Shape*), myCompare);
    for(i = 0; i < n; i++)
        pShapes[i]->printInfo();
    return 0;
}

  结果如下,
在这里插入图片描述

2.4 多态实例:Base 例子

  接下来对这个例子进行理解,

#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

class Base{
	public:
	    void func1(){func2()}; 
	    /* 解释如下
		void func1(){this->func2()}; // this 是基类函数,fun2是虚函数,所以是多态 */
	virtual void fun2(){cout << "Base::func2()" << endl};
};
class Derived: public Base{
	public:
		virtual void func2(){cout << "Derived: fun2()" << endl;}
};
int main(){
	Derived d;
	Base *pBase = &d;
	pBase -> fun1();
	return 0;
}

  接下来对这个例子进行理解,不过真想感叹一句,这个老师喝水是真滴 LiuPi,一大瓶水真的喝完了,一大瓶。。。看下面,
在这里插入图片描述
  吐槽完了继续理解,为什么输出的是 “Derived: fun2()”,注意 this 指针。注意,在非构造函数,非析构函数的成员函数中调用虚函数,是多态

  构造函数和析构函数中调用虚函数。在构造函数和析构函数中调用虚函数,不是多态。编译时即可确定,调用的函数是自己的类或者基类中定义的函数,不会等到运行时才决定调用自己的还是派生类的函数。例如,

  
  
  注意:派生类中和基类中虚函数同名同参数表的函数,不加 virtual 也自动成为虚函数
  
  

#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

class myclass{
	public:
		virtual void hello(){cout << "hellp from myclass" << endl;};
		virtual void bye(){cout << "bye from myclass" << endl;}
};
class son:public myclass{
	public:
		// 注意:派生类中和基类中虚函数同名同参数表的函数,不加 virtual 也自动成为虚函数
		void hello(){cout << "hello from son" << endl;};
		// 注意 在构造函数和析构函数中调用虚函数,不是多态。
		son(){hello();};  // 虚函数
		~son(){bye();};  // 虚函数
};
class grandson: public son{
	public: 
	    // 虚函数,注意 在构造函数和析构函数中调用虚函数,不是多态。
		void hello(){cout << "hello from grandson" << endl;};
		void bye(){cout << "bye from grandson" << endl;};
		
		grandson(){cout << "constructing grandson" << endl;};
		~grandson(){cout << "distructing grandson" << endl;};
};

int main(){
	grandson gson;
	son * pson;
	pson = &gson;
	pson -> hello();  // 多态
	return 0;
}

  结果如下,
在这里插入图片描述

3. 多态实现原理及其一些细节

3.1 多态实现原理

  “多态”的关键字在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定,这叫 “动态联编”。有一个例子

class Base{
	public:
		int i;
		virtual void print(){
			cout << "Base:print" << endl;
		}
};
class Derived: public Base{
	public:
		int n;
		virtual void print(){
			cout << "Derived:print" << endl;
		}
};

int main(){
	Derived d;
	cout << sizeof(Base) << ", " << sizeof(Derived);
}
// 输出结果为: 8, 12 为什么都多出了四个字节

  多态实现的关键,虚函数表
  每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都存放虚函数表的指针。虚函数表中列出了该类的虚函数地址。多出来的四个字节就是用来放虚函数表的地址的
  多态的函数调用语句被编译成一系列根据基类指针所指向的(或基类引用所引用的)对象中存在的虚函数表的地址,在虚函数表中查找虚函数地址,并调用虚函数。在理解,举个例子,

#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

class A{
    public:
        virtual void Func(){
            cout << "A::Func" << endl;
        }
};
class B: public A{
    public:
        virtual void Func(){
            cout << "B::Func" << endl;
        }
};

int main(){
    A a;
    A * pa = new B();
    pa -> Func();
    // 64 位程序指针为 8 字节
    long long * p1 = (long long *) & a;
    long long * p2 = (long long *) pa;
    * p2 = * p1; // a 虚函数表的地址覆盖掉 B 虚函数表的地址
    pa -> Func(); // 查虚函数表才知道,

    return 0;
}

  结果如下,
在这里插入图片描述
  指针赋予了程序访问任意地址空间的能力,对函数的任意字节进行修改,有助于提升使用效率。

3.2 虚析构函数、纯虚函数和抽象类

  虚析构函数:
  通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数。
------》但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数。
  解决方法:把基类的析构函数声明为 virtual
------》派生类的析构函数可以 virtual 不声明
------》通过基类的指针删除派生类对象时,首先调用派生类的析构函数,然后调用基类的析构函数。
  一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义成虚函数。
  注意:不允许以虚函数作为构造函数。举个例子,

class son{
	public:
		~son{
			cout << "bye from son" << endl;
		}
};
class grandson: public son{
	public:
		~grandson{
			cout << "bye from grandson" << endl;
		}
};

int main(){
	son *pson;
	pson = new grandson();
	delete pson;
	
	return 0;
}

  输出:bye from son // 没有执行 grandson::~grandson(). 因此解决方法为,

class son{
	public:
		virtual ~son{
			cout << "bye from son" << endl;
		}
};
class grandson: public son{
	public:
		~grandson{
			cout << "bye from grandson" << endl;
		}
};

int main(){
	son *pson;
	pson = new grandson();
	delete pson;
	
	return 0;
}

  输出:bye from grandson \r\n bye from son // 先执行 grandson::~grandson(),引起执行 son:: ~ son() .
  
  
  纯虚函数:没有函数体的函数:
  例如,

class A{
	private: int a;
	public:
		virtual void print() = 0;  // 纯虚函数
		void func(){cout << "func"; }
};

  抽象类:包含纯虚函数的类
----------》 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象
----------》 抽象类的指针和引用可以指向由抽象类派生出来的类的对象
  例如,

A a;   // 错误的, A 是抽象类,不能创建对象
A * pa;  // 可以的,可以定义抽象类的指针和引用
pa = new A;  // 错误, A 是抽象类,不能创建对象

----------》 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部不能调用纯虚函数。(注意多态在不同形式下调用的情况)
----------》 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函数,它才能成为非抽象类。
  具体例子,

#include <iostream>
#include <stdlib.h>
#include <math.h>

using namespace std;

class A{
    public:
        virtual void f() = 0; // 纯虚函数
        void g(){this -> f(); // 可以的,是多态
        }
        A(){ // f()}; // 错误的,在构造函数调用虚函数不是多态
        }
};
class B: public A{
    public:
        void f(){
            cout << "B:f()" << endl;
        }
};
int main(){
    B b;
    b.g();
    return 0;
}

  结果如下,
在这里插入图片描述

4. 总结

  这个老师是讲的真不错,逻辑清晰,按照说的复现不卡 BUG,不用浪费时间调,直接理解就行,真推荐想学习的也看看他的视频。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值