C++之面向对象

114 阅读7分钟

2023/02/13 更新

2023/02/18 更新完成


  1. 类的访问权限

    public
    private
    protected
    // 1. 在一个类中,private 和 public 可以出现多次
    // 2. 类成员缺省为 private, 结构体缺省为 public    
    
  2. 类的几个注意点

    // 1. 在类的声明中定义的函数自动成为内联函数,在类的声明之外定义的函数,如果使用了inline限定符,也是内联函数
    // 2. 在类的外部,一般不直接访问类的成员变量,而是用成员函数
    // 3. 对象一般不同memset()清空成员变量,可以写一个专门用于清空成员变量的成员函数
    // 4. 类的成员函数可以直接访问该类其他的成员函数(可以递归)
    
  3. 构造函数和析构函数

    // 1. 构造函数
    // 		1.1 访问权限必须是 public
    //		1.2 函数名必须与类名相同
    //		1.3 没有返回值,不写void
    //		1.4 可以有参数,可以重载,可以有默认参数
    //		1.5 创建对象时自动调用一次,不能手工调用
    
    // 注意点:
    //		1. 在构造函数名后面加括号和参数不是调用构造函数,而是创建匿名对象
    //		2. 尽量不要在构造函数中写太多代码,可以写写到成员函数中,然后在构造函数中调用成员函数
    Girl(string name) {
        cout << "aaa" << endl;
        Girl();    // 这里并不是调用构造函数,而是创建匿名对象,临时存在
        cout << "bbb" << endl;
        //m_name = name;
        //m_age = 0;
        //memset(m_memo, 0, sizeof m_memo);
    }
    
    // 2. 析构函数
    // 		2.1 没有参数,不能重载
    //		2.2 在函数名前加~
    //		2.3 销毁对象前会自动调用一次,可以手工调用
    
    // 3. new创建对象
    // 		3.1 new创建对象时需要使用指针接收
    // 		3.2 new创建的对象使用完成需要用delete进行销毁
    // 		3.3 new创建的对象使用堆空间,局部不用new创建的对象使用栈空间
    
  4. 拷贝构造函数

    // 1. 用已存在的对象创建新的对象时,不会调用普通的构造函数,而是调用拷贝构造函数
    // 2. 如果类中没有写拷贝构造函数,那么编译器会提供默认的拷贝构造函数,功能是将已存在对象的成员变量赋值给新对象的成员变量
    // 3. 用已存在对象创建新对象的语法
    //		类名 新对象(已存在对象)
    //		类名 新对象 = 已存在对象
    // 4. 拷贝构造函数语法
    //		Girl(const Girl& g) {}  // 形参必须有类对象的常引用
    // 5. 以值传递的方式进行函数调用时,如果实参为对象,则会调用拷贝构造函数
    // 6. 函数以值的方式返回对象时,可能会调用拷贝构造函数(VS会调用,linux不会,因为g++做了优化, 可以通过输出地址查看)
    // 7. 如果类中重载了拷贝构造函数,但是没有默认拷贝构造函数,编译器会提供默认的拷贝构造函数,这点与构造函数不同
    
  5. 深拷贝和浅拷贝

    // 1. 浅拷贝
    //		1.1 默认的拷贝构造函数是浅拷贝,当类中涉及动态成员时,如指针,在进行对象拷贝时,两个对象的指针会指向同一块内存,新产生的对象并没有开辟新的内存,因此在进程结束进行析构时,同一块内存析构两次,会出现异常。
    
    // 2. 深拷贝
    // 		2.1 对于类中的动态成员,会重新分配内存,然后进行赋值。在类中涉及动态成员时,需要注意拷贝构造函数的编写。
    
  6. 初始化列表

    // 1. 如果成员是类,使用初始化列表调用的是成员类的拷贝构造函数,而赋值则是先创建成员类的对象,然后再赋值
    // 2. 如果成员是类,初始化列表对性能略有提升 
    // 3. 如果成员是常量或引用,则必须使用初始化列表,因为常量和引用只能在定义的时候初始化
    // 4. 类的成员变量可以 不 出现在初始化列表中
    
  7. const修饰成员函数

    // 1. 在类的成员函数后面加const关键字,表示在成员函数中保证不会修改调用对象的成员变量(编程规范)
    // 2. mutable可以突破const的限制,被mutable修饰的成员变量将永远处于可修改的状态
    // 3. 构造函数或析构函数上不允许使用类型限定符
    
  8. this指针

    • this存放了对象的地址,它被作为隐藏参数传递给了成员函数,指向调用该函数的对象
    • *this可以返回对象
    • 每个成员函数都有一个this指针
    class CGirl {
    public:
    	string m_name;
    	int m_yz;
    
    	CGirl() {}
    
    	CGirl(string name, int yz) : m_name(name), m_yz(yz) {}
    
    	void show() const {
    		cout << "我是: " << m_name << ", 我最漂亮。" << endl;
    	}
    
    	const CGirl& pk(const CGirl& g) const {  // 注意这里函数名后面的const
    		if (m_yz > g.m_yz) return *this;
    		return g;
    	}
    };
    
    int main() {
    	CGirl g1("aa", 1);
    	CGirl g2("bb", 4);
    	CGirl g3("cc", 2);
    
    	const CGirl& gg = g1.pk(g2).pk(g3);
    
    	gg.show();
    
    	return 0;
    }
    
  9. 静态成员

    • 静态成员属于类,而不属于某个特定对象,使用类名和范围解析运算符就可以访问
    • 只能在全局区进行初始化,使用类名和范围解析运算符::,不能使用初始化列表
    • 静态成员只有一份
    • 静态成员函数不能访问非静态成员
    • 私有静态成员在类的外部无法访问
    • 静态成员函数没有this指针
  10. 简单对象模型

    • 对象中维护了多个指针表,表中存放了成员及其地址的对应关系
    • 对象占用的内存包括
      • 非静态成员大小
      • 内存对齐而填补的内存大小
      • 为了支持virtual成员而产生的额外负担
    • 静态成员属于类,不计算在对象的内存之内
    • 空指针可以访问没有用到this指针的成员函数
    • 空对象大小为1字节,这是为了让对象的实例能够相互区别
    • 对象的地址是第一个非静态成员变量的地址,如果没有非静态警员变量,编译器会隐含地增加一个大小为1字节的占位成员
  11. 友元

    1. 友元全局函数

      1. 可以访问类中的所有成员
      class CGirl {
      	friend void func();  // 友元全局函数
      public:
      	string name_;
      
      	CGirl() {
      		name_ = "Mary";
      		age_ = 18;
      	}
      
      	void showname() const {
      		cout << "姓名:" << name_ << endl;
      	}
      
      private:
      	int age_;
      
      	void showage() const {
      		cout << "年龄:" << age_ << endl;
      	}
      };
      
      void func() {
      	CGirl g;
      	g.showname();
      	g.showage();
      }
      
    2. 友元类

      1. 友元类中的所有成员函数可以访问另一个类的所有成员
      2. 友元关系是单向的
      3. 友元关系不能被继承
      class CGirl {
      	friend class CBoy;   // 友元类
      public:
      	string name_;
      
      	CGirl() {
      		name_ = "Mary";
      		age_ = 18;
      	}
      
      	void showname() const {
      		cout << "姓名:" << name_ << endl;
      	}
      
      private:
      	int age_;
      
      	void showage() const {
      		cout << "年龄:" << age_ << endl;
      	}
      };
      
      class CBoy {
      public:
      	void Bfunc(const CGirl& g) {
      		cout << "My girlfriend's name is: " << g.name_ << endl;
      		cout << "Her age is: " << g.age_ << endl;
      		g.showage();
      	}
      };
      
    3. 友元成员函数

      1. 友元类中指定的成员函数才能访问另一个类中的成员。声明和定义的顺序如下:
      class CGirl; // 前置声明
      class CBoy {...};
      class CGirl {...};
      
      class CGirl;  // first part
      
      class CBoy1 {  // second part
      public:
      	void func1(const CGirl& g);
      	void func2(const CGirl& g);
      };
      
       
      class CGirl {  // third part
      	friend void func();  // 友元全局函数
      	friend class CBoy;   // 友元类
      
      	friend void CBoy1::func1(const CGirl& g);
      	friend void CBoy1::func2(const CGirl& g);
      public:
      	string name_;
      
      	CGirl() {
      		name_ = "Mary";
      		age_ = 18;
      	}
      
      	void showname() const {
      		cout << "姓名:" << name_ << endl;
      	}
      
      private:
      	int age_;
      
      	void showage() const {
      		cout << "年龄:" << age_ << endl;
      	}
      };
      
      // fourth part
      void CBoy1::func1(const CGirl& g) {
      	cout << "func1:  ";
      	g.showage();
      }
      void CBoy1::func2(const CGirl& g) {
      	cout << "func2:  ";
      	g.showage();
      }
      
  12. 运算符重载

    1. 重载函数是非成员函数
    class Student {
    	friend Student& operator+(Student& stu, int sc);
    	friend Student& operator+(int sc, Student& stu);
    private:
    	string name_;
    	int score_;
    
    public:
    
    	Student(): name_("小明"), score_(80) {}
    
    	Student(string name, int score): name_(name), score_(score) {}
    
    	void show() {
    		cout << "姓名: " << name_ << endl;
    		cout << "分数: " << score_ << endl;
    	}
    };
    
    Student& operator+(Student& stu, int sc) {  
    	stu.score_ += sc;
    	return stu;
    }
    
    Student& operator+(int sc, Student& stu) {  // 更改参数的位置
    	stu.score_ += sc;
    	return stu;
    }
    
    1. 重载函数是成员函数
    class Student {
    private:
    	string name_;
    	int score_;
    
    public:
     
    	Student(): name_("小明"), score_(80) {}
    
    	Student(string name, int score): name_(name), score_(score) {}
    
    	void show() {
    		cout << "姓名: " << name_ << endl;
    		cout << "分数: " << score_ << endl;
    	}
    
    	Student& operator+(int sc) {  // 直接返回this指针的解引用
    		this->score_ += sc;
    		return *this;
    	}
    };
    
  13. 重载关系运算符

    class Student {
    private:
    	string name_;
    	int lan_;
    	int math_;
    	int eng_;
    
    public:
    
    	Student(string name, int lan, int math, int eng) : name_(name), lan_(lan), math_(math), eng_(eng) {}
    
    	// 比较两个学生的总分
    
    	bool operator==(const Student& stu) const {
    		if ((lan_ + math_ + eng_) == (stu.lan_ + stu.math_ + stu.eng_)) return true;
    		return false;
    	}
    
    	bool operator<(const Student& stu) const {
    		if ((lan_ + math_ + eng_) < (stu.lan_ + stu.math_ + stu.eng_)) return true;
    		return false;
    	}
    
    	bool operator>(const Student& stu) const {
    		if ((lan_ + math_ + eng_) > (stu.lan_ + stu.math_ + stu.eng_)) return true;
    		return false;
    	}
    };
    
  14. 重载左移运算符

    • 只能用全局函数的方式进行重载,不能用成员函数(因为重载要求cout对象在第一个参数的位置,但是成员函数中省略的自定义类型实际上是第一个参数,导致cout对象成为第二个参数,因此输出时cout就要写在右边)
    ostream& operator<<(ostream& out, const Student& s) {
    	out << "姓名:" << s.name_ << endl;
    	out << "语文成绩:" << s.lan_ << endl;
    	out << "数学成绩:" << s.math_ << endl;
    
    	return out;
    }
    
  15. 重载下标运算符

    • 只能以成员函数的方式呈现
    • 实际开发中一般提供两种,带const和不带const(给常对象使用)
    class Student {
    	friend ostream& operator<<(ostream& cout, const Student& s);
    private:
    	string name_;
    	int lan_;
    	int math_;
    	int eng_;
    	string gf[3];
    
    public:
    
    	Student(string name, int lan, int math, int eng) : name_(name), lan_(lan), math_(math), eng_(eng) {
    		gf[0] = "Amy";
    		gf[1] = "Tom";
    		gf[2] = "Dming";
    	}
    
    	string& operator[](int index) {
    		return this->gf[index];
    	}
    
    	const string& operator[](int index) const {
    		return this->gf[index];
    	}
    };
    
    // cout << stu1[0] << endl;
    
  16. 重载赋值运算符

    • 默认赋值函数是浅拷贝,如果对象中存在堆区内存,那么需要自定义赋值函数
    • 赋值函数是在两个已存在的对象间进行
    • 拷贝构造函数是用已存在的对象去初始化不存在的对象
    class Student {
    public:
    	string name;
    	int score;
    	int* kk;
    
    public:
    	Student() {
    		kk = nullptr;
    	}
    
    	Student& operator=(const Student& s) {
    		if (this == &s) {  // 目标对象与原对象相同
    			return *this;
    		}
    		if (s.kk == nullptr) {  // 原对象指针为空
    			if (kk != nullptr) {	// 若目标对象指针不为空,那么删除目标对象指针
    				delete kk;
    			}
    		} else {
    			if (kk == nullptr) { // 原对象指针不为空,那么若目标对象指针为空,则分配空间
    				kk = new int;
    			}
    			memcpy(kk, s.kk, sizeof(int));
    		}
    
    		return *this;
    	}
    
    	void show() const {
    		cout << "姓名: " << name << ",分数: " << score << ", kk值: " << kk << endl;
    	}
    
    	~Student() {
    		if (kk) {
    			delete kk;
    		}
    	}
    };
    
  17. 重载new&delete运算符

    • 如果写在类中,那么无论写不写static,new和delete都是static函数,不能调用非静态成员对象
    void* operator new(size_t size) {
    	cout << "调用了全局new函数" << endl;
    	cout << "申请的内存大小: " << size << " 字节" << endl;
    	void* ptr = malloc(size);
    	cout << "申请到的内存地址: " << ptr << endl;
    	return ptr;
    }
    
    void operator delete(void* p) {
    	cout << "调用了全局delete函数" << endl;
    	if (p == nullptr) return;
    	free(p);
    }
    
    
  18. 内存池

    class Student {
    public:
    	int score;
    	int age;
    	static char* pool; //内存池需要有一个指针
    public:
    	Student(int sc, int a) {
    		score = sc;
    		age = a;
    		cout << "构造函数" << endl;
    	}
    
    	~Student() {
    		cout << "析构函数" << endl;
    	}
    
    	static bool initpool() {
    		pool = (char*)malloc(18);
    		if (pool == nullptr) return false;
    		memset(pool, 0, 18);
    		return true;
    	}
    
    	static void freepool() {
    		if (pool[0] == 0 && pool[9] == 0) return;
    		free(pool);
    		cout << "释放了内存池" << endl;
    	}
    
    	void* operator new(size_t size) {
    		if (pool[0] == 0) {
    			cout << "------------------------" << endl;
    			cout << "分配了第一块内存" << endl;
    			cout << "内存起始地址: " << static_cast<void*>(pool + 1) << endl;
    			cout << "------------------------" << endl;
    			pool[0] = 1;
    			return pool + 1;
    		} 
    		if (pool[9] == 0) {
    			cout << "------------------------" << endl;
    			cout << "分配了第二块内存" << endl;
    			cout << "内存起始地址: " << static_cast<void*>(pool + 10) << endl;
    			cout << "------------------------" << endl;
    			pool[9] = 1;
    			return pool + 10;
    		}
    
    		void* ptr = malloc(size);
    		cout << "------------------------" << endl;
    		cout << "分配了系统内存" << endl;
    		cout << "内存起始地址: " << static_cast<void*>(ptr) << endl;
    		cout << "------------------------" << endl;
    		return ptr;
    	}
    
    	void operator delete(void* p) {
    		if (p == 0) return;
    		if (p == pool + 1) {
    			pool[0] = 0;
    			cout << "释放了第一块内存" << endl;
    		} else if (p == pool + 10) {
    			pool[9] = 0;
    			cout << "释放了第二块内存" << endl;
    		}
    		else {
    			cout << "释放了系统内存" << endl;
    			free(p);
    		}
    	}
    };
    
    char* Student::pool = nullptr;
    
    int main() {
    	if (Student::initpool() == false) {
    		cout << "内存池分配失败" << endl;
    		return 1;
    	}
    	Student* s1 = new Student(1, 2);
    	Student* s2 = new Student(1, 2);
    	Student* s3 = new Student(1, 2);
    	Student* s4 = new Student(1, 2);
    
    	delete s1;
    	Student* s5 = new Student(1, 2);
    
    	delete s2;
    	delete s3;
    	delete s4;
    	delete s5;
    
    	Student::freepool();
    	return 0;
    }
    
  19. 重载括号运算符

    • 必须以成员函数的方式进行重载
    • 对象名可以当作函数使用(又称为函数对象、仿函数)
    int add(int a, int b) {
    	int c = a + b;
    	cout << "调用了全局函数" << endl;
    	return c;
    }
    
    class MyAdd {
    public:
    	int operator()(int a, int b) {
    		int c = a + b;
    		cout << "调用了重载函数" << endl;
    		return c;
    	}
    };
     
    int main() {
    	MyAdd add;
    	cout << add(1, 2) << endl;  // 重载函数
    	cout << ::add(1, 2) << endl; // 全局函数  (前面加范围解析运算符)
    
    	return 0;
    }
    
  20. 重载一元运算符

    • 参数中写一个int表示后置自增运算符
    • 注意后置自增运算符不能嵌套
    • 前置++返回的是左值,后置++返回的是右值
    class Student {
    public:
    	int age;
    	int sc;
    public:
    	Student() {
    		age = 8;
    		sc = 10;
    	}
    
    	Student& operator++() {  // 前置自增运算符
    		sc++;
    		return *this;
    	}
    
    	Student& operator++(int) {  // 后置自增运算符
    		Student tmp = *this;  // 记录原始值
    		sc++;
    		return tmp;
    	}
    };
    
  21. 自动类型转换

    • explicit 加在构造函数前面可以关闭自动类型转换
    class Student {
    public:
    	int age;
    	int score;
    public:
    	explicit Student(int sc) {
    		age = 0;
    		score = sc;
    	}
    };
    
    Student s = 8;  // explicit 限制,不能隐式转换
    Student s1 = (Student)8;  // 可以显式转换
    
  22. 转换函数

    class Student {
    public:
    	int age;
    	string name;
    	double weight;
    public:
    	Student(int a, string n, double w) {
    		age = a;
    		name = n;
    		weight = w;
    	}
    
    	operator int() {  // 不能有返回值类型,不能有参数
    		return age;
    	}
    	operator string() {
    		return name;
    	}
    	operator double() {
    		return weight;
    	}
    };
    
  23. 继承的三种方式

    image-20230217205424083

  24. 继承的对象模型

    • 查看内存布局命令

      cl 源文件名 /d1 reportSingleClassLayout类名
      
    • 派生类的内存空间包括基类的成员,基类的私有成员也在其中

    • 如果通过修改内存,派生类是可以修改基类的私有成员的值的,例如memset函数

    class A {
    public:
    	int m_a;
    protected:
    	int m_b;
    private:
    	int m_c;
    public:
    	A() {
    		m_a = 1;
    		m_b = 2;
    		m_c = 3;
    		cout << "调用了A的构造函数" << endl;
    		cout << "A this指针地址: " << this << endl;
    		cout << "A m_a的地址: " << &m_a << endl;
    		cout << "A m_b的地址: " << &m_b << endl;
    		cout << "A m_c的地址: " << &m_c << endl;
    	}
    
    	~A() {
    		cout << "调用了A的析构函数" << endl;
    	}
    
    	void show1() const {
    		cout << m_a << " " << m_b << " " << m_c << endl;
    	}
    };
    
    class B : public A {
    public:
    	int m_d;
    public:
    
    	B() {
    		m_d = 4;
    		cout << "调用了B的构造函数" << endl;
    		cout << "B this 指针地址: " << this << endl;
    		cout << "B m_a的地址: " << &m_a << endl;
    		cout << "B m_b的地址: " << &m_b << endl;
    		//cout << "B m_c的地址: " << &m_c << endl;
    		cout << "B m_d的地址: " << &m_d << endl;
    	}
    
    	~B() {
    		cout << "调用了B的析构函数" << endl;
    	}
    
    	void show2() const {
    		cout << m_a << " " << m_b << " " << m_d << endl;
    	}
    };
    
    int main() {
    
    	B* p = new B();
    	p->show1();
    	p->show2();
    	memset(p, 0, sizeof B);  // 这里修改了A的私有成员的值
    	p->show1();
    	p->show2();
    
    	delete p;
    
    	return 0;
    }
    
    
  25. 如何构造基类

- 基类中的成员尽量在基类中初始化
```c++
class A {
public:
	int m_a;
	int m_b;

	A() : m_a(0), m_b(0) {
		cout << "调用了A的默认构造函数A()" << endl;
	}

	A(int a, int b) : m_a(a), m_b(b) {
		cout << "调用了A的构造函数A(int a, int b)" << endl;
	}

	A(const A& a) : m_a(a.m_a), m_b(a.m_b) {
		cout << "调用了A的拷贝构造函数A(const A& a)" << endl;
	}

	void show1() const {
		cout << "A m_a: " << m_a << ", A m_b: " << m_b << endl;
	}
};

class B : public A {
public:
	int m_c;

	B() : m_c(0) {
		cout << "调用了B的默认构造函数" << endl;
	}

	B(int a, int b) : A(a, b), m_c(0) {
		cout << "调用了B的构造函数B(int a, int b)" << endl;
	}

	B(const A& a) : A(a), m_c(1) {
		cout << "调用了B的构造函数B(const A& a)" << endl;
	}
};
```

26. 名字遮蔽和类作用域

- 基类和派生类之间不存在函数重载,派生类的函数会覆盖基类的所有同名函数
- 成员如果在派生类的作用域中找到了,就不会再去基类的作用域中寻找
- 可以通过范围解析运算符$::$来指定作用域

27. 继承的特殊关系

- 可以将派生类对象赋值给基类对象
- 基类指针可以在不进行显示转换的情况下指向派生类,但是基类指针只能操纵基类中有的成员,假如有同名函数的话也是调用的基类的成员函数
- 如果函数的形参是基类,那么实参可以传入派生类

28. 多继承与虚继承

- 多继承格式

```c++
class A1 {
public:
	int a = 10;
};

class A2 {
public:
	int b = 20;
};

class B : public A1, public A2 {

};
```

- 虚继承

  - 主要解决多继承下造成的菱形继承问题
  - 没有虚继承的时候需要这样写

  ```c++
  class A {
  public:
  	int m_a;
  };
  
  class B : public A {
  
  };
  
  class C : public A {
  
  };
  
  class D : public B, public C {
  
  };
  
  int main() {
  
  	D d;  // d的内存模型中有两个m_a,因此在访问时可以通过域名解析运算符指定访问哪个
  	d.B::m_a = 10;
  	d.C::m_a = 20;
  
  	return 0;
  }
  ```

  - 有虚继承时这样写

  ```c++
  class A {
  public:
  	int m_a;
  };
  
  class B : virtual public A {
  
  };
  
  class C : virtual public A {
  
  };
  
  class D : public B, public C {
  
  };
  
  int main() {
  
  	D d;
  	
  	d.m_a = 10;
  
  	cout << "B::m_a的地址: " << &d.B::m_a << endl;  //这两个地址相同,此时A是虚基类
  	cout << "C::m_a的地址: " << &d.C::m_a << endl;
  
  	return 0;
  }
  
  ```

29. 多态的基本概念

- 在一般情况下,基类的指针指向派生类的对象时,基类指针是无法访问派生类的成员变量和成员函数的
- 但是如果在基类中的同名函数前面加上virtual 关键字,那么当基类指针指向基类对象时,就会访问基类的成员函数;指向派生类时,就会访问派生类的成员函数,表现出了多态的性质
- 如果派生类重定义了基类的虚函数,那么这两个函数的函数名和参数必须完全相同,否则不构成多态
- 普通成员函数的效率比虚函数效率高
- 当有虚函数时,基类和派生类的内存模型中会有虚函数表,在基类的虚函数表中存放的是基类的成员函数;在派生类的虚函数表中,存放的是派生类的成员函数,因此当基类指针指向派生类时,调用的是派生类的成员函数。

30. 如何析构派生类

- 调用派生类的析构函数会自动调用基类的析构函数
- 当基类指针指向派生类时,如果不把基类的析构函数设置成虚函数,那么将无法调用派生类的成员函数。如果基类析构函数被设置成虚函数,那么就可以调用派生类的析构函数,根据第一条规则,基类的析构函数也会调用
- 基类若不需要析构函数的话最好提供一个空的虚析构函数

```c++
// 在析构函数中delete 指针之后,还要把指针设置成空指针,因为有可能手工调用多次析构函数
delete ptr;
ptr = nullptr;
```

31. 纯虚函数和抽象类

- 含有纯虚函数的类被称为抽象类,不能实例化对象,但是可以创建指针和引用

- 基类中不需要给出纯虚函数的实现,给出了也没问题。格式如下:

```c++
virtual void func() = 0; // {}  后面的实现可要可不要
```

- 纯虚析构函数一定要有实现。它存在的意义是当想定义一个抽象类,但是有没有内容需要写时,可以简单的定义一个纯虚函数来达到目的

```c++
class A {
public:
	A() {
		cout << "调用了A的构造函数" << endl;
	}

	virtual void func() = 0; // { cout << "调用了A的func()" << endl; }
	
	virtual ~A() = 0 { cout << "调用了A的析构函数" << endl; }
};


class B : public A {
public:
	B() {
		cout << "调用了B的构造函数" << endl;
	}

	void func() {
		cout << "调用了B的func()" << endl;
	}
	~B() {
		cout << "调用了B的析构函数" << endl;
	}
};
```