笔记参考C++视频课程 黑马C++

C++ 面向对象的三大特性:封装、继承、多态

目录

目录

一、封装

1.1 封装的意义-属性和行为

封装是C++ 面向对象的三大特性之一

封装意义一:1、将属性和行为作为一个整体,表现生活中的事物、

​ 2、将属性和行为加以权限控制

语法: class 类名 {访问权限: 属性 / 行为};

示例: 设计一个圆类

#include <iostream>

const double PI = 3.14;
using namespace std;
class Circle
{
//访问权限
public:
//属性
int r;
//行为
double calculateZC()
{
return 2 * PI * r;
}
};
int main()
{
Circle c1; //创建实例
c1.r = 10;
cout << "圆的周长为:" << c1.calculateZC() << endl;
return 0;
}

封装意义二: 类在设计时,可以把属性和行为放在不同的权限下,加以控制

三种权限

  • public 公共权限 成员在类内可以访问,类外可以访问
  • protected 保护权限 类内可以访问,类外不可以访问,子类可以访问父类中的保护内容
  • private 私有权限 类内可以访问,类外不可以访问,子类不可以访问父类中的保护内容

示例:

#include <iostream>

using namespace std;
class Father
{
public:
string name;
protected:
string car;
private:
int password; public:
void func()
{
//三种属性类内皆可以访问
name = "张三";
car = "奔驰";
password = 123456;
}
};
int main()
{
Father c1;
c1.name = "李四";
//c1.car = "大众"; //protected权限内容,不能在类外访问(子类继承可访问)
//c1.password = 1213; //private权限内容,不能在类外访问
return 0;
}

1.2 struct和class的区别

在C++中struct 和class的唯一区别就在于 默认的访问权限不同

  • struct 默认权限为公共
  • class 默认权限为私有

示例:

class C
{
//不加权限默认为私有
int age;
};

1.3 成员属性设置为私有

优点1: 将所有成员属性设置为私有,可以自己控制读写权限

优点2: 对于写权限,可以检测数据的有效性

示例:

class Father
{
// 设置可读可写
string m_name;
//设置 只可写
int m_age;
//设置 只可读
string m_password = "12345";
public:
void setName(string name)
{
m_name = name;
}
string getName()
{
return m_name;
}
void setAge(int age)
{
//对于写权限,可以判断数据的有效性
if (age < 0 || age > 150)
{
m_age = 0;
cout << "设置的年龄有误" << endl;
}
m_age = age;
}
string getPassword()
{
return m_password;
}
};

【案例】判断点和圆的关系,在圆内、在圆外、以及在圆上

#include <iostream>
//判断点和圆的关系,在圆内、在圆外、以及在圆上
using namespace std;
//点类
class Point{
private:
int m_x;
int m_y;
public:
void setXY(int x, int y){
m_x = x;
m_y = y;
}
int getX(){
return m_x;
}
int getY(){
return m_y;
}
};
//圆类
class Circle{
private:
int R;
Point c_center;
public:
void setCenter(Point center){
c_center = center;
}
void setR(int r){
R = r;
}
int getR(){
return R;
}
Point getCenter(){
return c_center;
}
};
//判断点与圆的关系
void isinCircle(Circle &c, Point &a){
int c_x = c.getCenter().getX();
int c_y = c.getCenter().getY();
int R = c.getR();
int x = a.getX();
int y = a.getY();
int distance = (x - c_x) * (x - c_x) + (y - c_y) * (y - c_y);
if (distance == R * R){
cout << "点在圆上" << endl;
}
else if (distance > R * R){
cout << "点在圆外" << endl;
}
else{
cout << "点在圆内" << endl;
}
}
int main(){
Circle c;
Point c_center;
c_center.setXY(1, 1); //圆心坐标
c.setCenter(c_center);//设置圆心
c.setR(1); //设置半径
Point a;
a.setXY(0, 1); //设置点
isinCircle(c, a);
return 0;
}

二、对象的初始化和清理

2.1 构造函数和析构函数

  • 构造函数:主要作用在于创建对象时为对象的成员属性赋值,构造函数由编译器自动调用,无需手动调用
  • 析构函数:主要作用在于对象销毁前系统自动调用,执行一些清理工作

构造函数语法: 类名(){}

  1. 构造函数,没有返回值也不写void
  2. 函数名称与类名相同
  3. 构造函数可以有参数,因此可以发生重载
  4. 程序在调用对象时候会自动调用构造,无需手动调用,而且只会调用一次

析构函数语法:~类名(){}

  1. 析构函数,没有返回值也不写void
  2. 函数名称与类名称相同,在名称前加~
  3. 析构函数没有参数,不可以发生重载
  4. 程序在对象销毁前会自动调用析构函数,无需手动调用,而且只会调用一次

2.2 构造函数的分类及调用

分类

  1. 按参数:有参构造和无参构造
  2. 按类型:普通构造和拷贝构造

调用方式

  1. 括号法
  2. 显示法
  3. 隐式转换法

示例:

#include <iostream>

using namespace std;
class Person
{
public:
int age;
public:
//无参构造
Person()
{
cout << "Person 构造函数调用" << endl;
}
//有参构造
Person(int a)
{
age = a;
}
//拷贝构造
Person(const Person& p)
{
age = p.age;
}
~Person()
{
cout << "Person 析构函数的调用" << endl;
}
};
int main()
{
// 1、括号法
Person p1; //默认构造函数调用
Person p2(10); //有参构造函数
Person p3(p2); //拷贝构造函数
// 2、显示法
Person p4;
Person p5 = Person(10);
Person p6 = Person(p2);
// 3、隐式转换法
Person p7 = 10; //有参构造
Person p8 = p2; //拷贝构造
return 0;
}

2.3 拷贝构造函数调用时机

  1. 使用一个已经创建完毕的对象来初始化一个新对象
  2. 值传递的方式给函数参数传值
  3. 值方式返回局部对象

2.4 构造函数调用规则

默认情况下,C++编译器至少给一个类添加3个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝(默认浅拷贝操作)

构造函数调用规则如下:

  • 如果用户定义有参构造函数,C++不再提供默认无参构造,但是会提供默认拷贝构造
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数

2.5 深拷贝与浅拷贝

深拷贝与浅拷贝是面试经典问题,也是常见的一个坑

  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,进行拷贝操作

示例:

#include <iostream>

using namespace std;
class Person
{
public:
Person(int age, int height)
{
cout << "Person的有参构造函数调用" << endl;
Age = age;
Height = new int(height);
}
//更改的拷贝构造函数(Height处进行深拷贝操作)
Person(const Person& p)
{
cout << "自己构造的拷贝函数调用" << endl;
Age = p.Age;
//此处默认浅拷贝操作为 Height = p.Height;
Height = new int(*p.Height);
}
~Person()
{
if (Height != NULL)
{
delete Height;
Height = NULL;
}
cout << "Person的析构函数调用" << endl;
}
int Age;
int* Height;
};
void test()
{
Person p1(18, 170);
cout << "p1的年龄为:" << p1.Age << " 身高为:" << *p1.Height << endl;
Person p2(p1); //默认浅拷贝
//如果浅拷贝操作,此处会出现指针重复释放问题
cout << "p2的年龄为:" << p2.Age << " 身高为:" << *p2.Height << endl;
}
int main()
{
test();
return 0;
}

2.6 初始化列表

作用:C++提供了初始化列表语法,用来初始化属性

语法: 构造函数(): 属性1(值1), 属性2(值2) ... {}

示例:

#include <iostream>

using namespace std;
class Person
{
public:
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c){}
int m_A;
int m_B;
int m_C;
};
int main()
{
Person p(10, 20, 30);
cout << p.m_A << p.m_B << p.m_C << endl;
return 0;
}

2.7 类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

示例:可参考1.3【案例】点与圆的关系

class A{}
class B
{
A a;
}

2.8 静态成员

静态成员就是在成员变量和成员函数前加上关键字static,称静态成员,可分为:

  • 静态成员变量

    • 所有对象共享一份数据
    • 在编译阶段分配内存
    • 类内声明,类外初始化

示例:

#include <iostream>

using namespace std;
class Person
{
public:
static int A; //在类内定义,类外初始化
private:
static int B; //静态成员也有访问权限
};
int Person::A = 100;
int Person::B = 200; int main()
{
// 1、通过对象访问
Person p1;
cout << p1.A << endl;
// 2、通过类名访问
cout << Person::A << endl;
Person p2;
p2.A = 400; //所有对象共享一份数据
cout << p1.A << endl; //400
//cout << Person::B << endl; //私有权限访问限制
return 0;
}
  • 静态成员函数

    • 所有对象共享一个函数
    • 静态成员函数只能访问静态成员变量

示例:

#include <iostream>

using namespace std;
class Person
{
public:
static void func()
{
A = 100;
//B = 100; //静态成员函数不能访问非静态成员变量
cout << "static void func调用" << "A = " << A << endl;
}
static int A;
int B;
private:
static void func2()
{
cout << "静态成员函数也是有访问权限的" << endl;
}
};
int Person::A = 0;
int main()
{
//1、通过对象访问
Person p;
p.func();
//2、通过类名访问
Person::func();
//Person::func2(); //访问权限限制
return 0;
}

三、C++对象模型和this指针

3.1 this指针

C++中成员变量和成员函数是分开存储的,每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么,这一块代码是如何区分哪个对象调用呢?

C++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象,this指针是隐含每一个非静态成员函数内的一种指针,this指针不需要定义,直接使用即可。

  • 当形参和成员变量同名时,可用this指针来区分
  • 在类的非静态成员函数中返回对象本身,可以用return *this

示例:

#include <iostream>

using namespace std;
// 1. 解决名称冲突
// 2. 返回对象本身 *this
class Person
{
public:
int age;
Person(int age)
{
this->age = age;
}
Person& addAge(Person& p)
{
//返回本体要使用引用的方式返回
this->age += p.age;
return *this;
}
};
int main()
{
Person p1(18);
cout << p1.age << endl;
Person p2(18);
p2.addAge(p1).addAge(p1);
cout << "p2.Age = " << p2.age << endl;
return 0;
}

3.2 const修饰成员函数

常函数:

  • 成员函数后加const,我们称这个函数为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,在常函数中依然可以修改

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

示例:

#include <iostream> 

using namespace std;
// 常函数
class Person
{
public:
// this指针的本质 是指针常量 指针的指向是不可以修改的
// const Person * const this
// 在成员函数后面加const,修饰的是this指针,使指针指向的值也不可以修改
Person()
{
return;
}
void showPerson() const
{
// this->m_A = 100; //常函数内不可以修改普通成员的属性
this->m_B = 100; //特殊成员变量可以修改
}
int m_A;
mutable int m_B; //特殊变量,即使在常函数中,也可以修改此值
};
int main()
{
const Person p; //对象前加const 变为常对象
// p.m_A = 100; //常对象中不可以修改普通变量
p.m_B = 100; //常对象中可以修改特殊变量
p.showPerson() //常对象只能调用常函数
}

四、友元

生活中家里有客厅(Public),有卧室(Private)

客厅所有来的客人都可以进去,但是卧室是私有的,也就是说只有自己可以进去,但是也可以允许好基友进去。

在程序中,有些私有属性,也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术,友元的目的就是让一个函数或者类访问另一个类中私有成员

友元的关键字为 friend

友元的三种实现

  • 全局函数作友元 friend void func(arg);
  • 类作友元 friend class ClassName
  • 成员函数作友元

4.1 全局函数作友元

#include <iostream>
#include <string> using namespace std;
class Home
{
//友元函数声明
friend void func(Home *home);
public:
Home()
{
livingRoom = "客厅";
bedRoom = "卧室";
}
string livingRoom;
private:
string bedRoom;
};
void func(Home *home)
{
cout << "全局函数正在访问 --" << home->livingRoom << endl;
cout << "全局函数正在访问 --" << home->bedRoom << endl;
}
int main()
{
Home myhome;
func(&myhome);
return 0;
}

4.2 类作友元

#include <iostream>

using namespace std;
class Home
{
// 类作友元
friend class Myfriend;
public:
string livingRoom;
Home()
{
livingRoom = "客厅";
bedRoom = "卧室";
}
private:
string bedRoom;
};
class Myfriend
{
public:
Home *home;
Myfriend()
{
home = new Home;
}
void visit()
{
cout << "好基友类正在访问: " << home->livingRoom << endl;
cout << "好基友类正在访问: " << home->bedRoom << endl;
}
};
int main()
{
Myfriend Tom;
Tom.visit();
return 0;
}

4.3 成员函数作友元

#include <iostream>
#include <string> using namespace std;
class Home;
class Myfriend
{
public:
Home * home;
Myfriend();
void visit(); // 使该函数可以访问私有成员
void visit2(); // 使该函数不可以访问私有成员
};
class Home
{
// 成员函数作友元声明
friend void Myfriend::visit();
public:
string livingRoom;
Home();
private:
string bedRoom;
};
Home::Home()
{
livingRoom = "客厅";
bedRoom = "卧室";
}
Myfriend::Myfriend()
{
home = new Home;
}
void Myfriend::visit()
{
cout << "visit函数正在访问:" << home->livingRoom << endl;
cout << "visit函数正在访问:" << home->bedRoom << endl;
}
void Myfriend::visit2()
{
cout << "visit函数正在访问:" << home->livingRoom << endl;
}
int main()
{
Myfriend Tom;
Tom.visit();
Tom.visit2();
return 0;
}

五、运算符重载

运算符重载概念:对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型

5.1 加号运算符重载 +

作用:实现两个自定义数据类型相加(重新定义的加法)的运算

#include <iostream>

using namespace std;
class Person
{
public:
int A;
int B;
Person()
{
A = 10;
B = 20;
}
};
Person operator+(Person &p1, Person &p2)
{
Person temp;
//可自定义运算
temp.A = p1.A + p2.A;
temp.B = p1.B - p2.B;
return temp;
}
int main()
{
Person p1;
Person p2;
Person p3 = p1 + p2;
cout << p3.A << " " << p3.B << endl;
return 0;
}

5.2 左移运算符重载 <<

#include <iostream>

using namespace std;
class Person
{
public:
int A;
int B;
Person()
{
A = 10;
B = 20;
}
};
ostream & operator<<(ostream &cout, Person &p)
{
// cout 属于ostream类型
cout << "A = " << p.A << " B = " << p.B;
return cout;
}
int main()
{
Person p;
cout << p << endl;
return 0;
}

重载左移运算符配合友元可以实现输出自定义数据类型

5.3 递增运算符重载 ++

作用:通过重载递增运算符,实现自己的整型数据

#include <iostream>

using namespace std;
class MyIntergeer
{
public:
int num;
MyIntergeer()
{
num = 0;
}
//前置递增重载
MyIntergeer &operator++()
{
num++;
return *this;
}
//后置递增重载
MyIntergeer &operator++(int) //int代表一个占位参数
{
// 先 记录当时结果
MyIntergeer temp = *this; //记录当前本身的值,然后让本身的值加一,但是返回以前的值,达到先返回后++
// 后 递增
num++;
// 最后将记录结果做返回
return temp; //后置递增返回的是值
}
};
// 实现左移运算符重载
ostream &operator<<(ostream &cout, MyIntergeer myint)
{
cout << myint.num;
return cout;
}
//前置递增测试
void test01()
{
MyIntergeer myint;
cout << ++myint <<endl;
cout << myint << endl;
}
//后置递增测试
void test02()
{
MyIntergeer myint;
cout << myint++ << endl;
cout << myint << endl;
}
int main()
{
test01();
test02();
return 0;
}

5.4 赋值运算符重载 =

C++编译器至少给一个类添加四个函数

  1. 默认构造函数(无参,函数体为空)
  2. 默认析构函数(无参,函数体为空)
  3. 默认拷贝构造函数,对属性进行值拷贝
  4. 赋值运算符 operator=,对属性进行值拷贝

如果类中有属性指向堆区,做赋值操作时也会出现深浅拷贝问题

示例:

#include <iostream>

using namespace std;
class Person
{
public:
int *Age;
Person(int age)
{
Age = new int(age);
}
~Person()
{
if (Age != NULL)
{
delete Age;
Age = NULL;
}
}
//重载赋值运算符
Person &operator=(Person &p)
{
if (Age != NULL)
{
delete Age;
Age = NULL;
}
Age = new int(*p.Age);
return *this;
}
};
int main()
{
Person p1(18);
Person p2(20);
Person p3(30);
p3 = p2 = p1;
cout << "p1的年龄为:" << *p1.Age << endl;
cout << "p2的年龄为:" << *p2.Age << endl;
cout << "p3的年龄为:" << *p3.Age << endl;
}

5.5 关系运算符重载 > < == !=

#include <iostream>
#include <string> using namespace std;
class Person
{
public:
string Name;
int Age;
Person(string name, int age)
{
Name = name;
Age = age;
}
bool operator==(Person &p)
{
if(this->Name == p.Name && this->Age == p.Age)
{
return true;
}
return false;
}
};
int main()
{
Person p1("Tom", 18);
Person p2("Tom", 18);
if (p1 == p2)
{
cout << "p1和p2相等" << endl;
}
}

4.6 函数调用运算符重载 ()

  • 函数调用运算符 () 也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定方法,非常灵活

示例:

#include <iostream>

using namespace std;
class MyPrint
{
public:
void operator()(string test)
{
cout << test << endl;
}
};
int main()
{
MyPrint myprint;
//使用时非常像函数,因此称为仿函数
myprint("hello");
return 0;
}

六、继承

6.1 继承的基本语法

继承是面向对象的三大特性之一,利用继承可以有效减少重复代码

语法:class 子类: 继承方式 父类

示例:

class Sub: public Base{}

继承方式:

  • 公共继承
  • 保护继承
  • 私有继承

6.2 继承中的对象模型

#include <iostream>

using namespace std;
class A
{
public:
int A;
protected:
int B;
private:
int C;
};
class B: public A
{
public:
int D;
};
int main()
{
B b;
// 子类会将父类的所有成员继承,包括编译器隐藏不可访问的私有成员
cout << "sizeof b = " << sizeof(b) << endl; // 16
return 0;
}

使用开发人员命令提示符 cl /d1 reportSingleClassLayout类名 文件名 可查看单个类的具体布局

6.3 继承中构造和析构顺序

子类继承父类后,当创建子类对象,也会调用父类的构造函数

问题:父类和子类的构造和析构顺序是谁先谁后?

示例:

#include <iostream>

using namespace std;
class Base
{
public:
Base()
{
cout << "Base构造函数" << endl;
}
~Base()
{
cout << "Base析构函数" << endl;
}
};
class Son :public Base
{
public:
Son()
{
cout << "Son构造函数" << endl;
}
~Son()
{
cout << "Son析构函数" << endl;
}
};
void test()
{
Son s;
}
int main()
{
test();
return 0;
}

继承中的构造和析构顺序:先构造父类、后构造子类,析构的顺序与构造的顺寻相反

6.4 继承同名成员处理方式

问题1:当子类与父类出现同名的成员,如何通过子类对象,访问到子类或父类中同名的数据呢?

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

示例:

s.Base.func()	//加作用域
s.func() //默认为子类函数

当子类与父类拥有同名的成员函数,子类会隐藏父类中同名成员函数(包括重载同名函数)

问题2:继承中同名的静态成员在子类对象上如何进行访问?

静态成员与非静态成员出现同名,处理方式一致

  • 访问子类同名成员 直接访问即可
  • 访问父类同名成员 需要加作用域

示例:

#include <iostream>

using namespace std;
class Base
{
public:
static int A;
static void func();
};
int Base::A = 100;
void Base::func()
{
cout << "Base - func" << endl;
}
class Son :public Base
{
public:
static int A;
static void func();
};
int Son::A = 200;
void Son::func()
{
cout << "Son - func" << endl;
}
int main()
{
cout << "1. 通过对象访问" << endl;
Son s;
cout << s.A << endl;
s.func();
s.Base::func();
cout << "2. 通过类名访问" << endl;
cout << Son::A << endl;
Son::func();
Son::Base::func();
return 0;
}

总结:同名静态成员处理方式和非静态处理方式一样,只不过有两种访问方式(通过对象 和 通过类名)

6.5 多继承语法

C++ 允许一个类继承多个类

语法:class 子类 : 继承方式 父类1, 继承方式 父类2...

多继承可能会引发父类中有同名成员出现,需要加作用域区分

【注】C++实际开发中不建议使用多继承

6.6 菱形继承

菱形继承概念:

  • 两个派生类继承同一个基类
  • 又有某个类同时继承着两个派生类
  • 这种继承被称为菱形继承,或者钻石继承

典型的菱形继承案例:

菱形继承问题:

  1. 羊继承了动物的数据,驼同样继承了动物数据,当羊驼使用数据时,就会产生二义性
  2. 羊驼继承自动物的数据继承了两份,其实我们应该清楚,这份动物的数据我们只需要一份就可以
#include <iostream>

using namespace std;
class Animal //动物类
{
public:
int Age;
};
// 利用虚继承 解决菱形继承问题
// 继承之前 加上关键字 virtual 变为虚继承
// Animal类称为 虚基类
class Sheep :virtual public Animal{}; //羊类
class Camel :virtual public Animal{}; //驼类
class Alpaca :public Sheep, public Camel{}; //羊驼类
int main()
{
Alpaca alpaca;
alpaca.Sheep::Age = 18;
alpaca.Camel::Age = 28; //对于菱形继承,来自第一个父类的数据会继承两份
}

七、多态

7.1 多态的基本概念

多态是C++面向对象的三大特性之一

多态分为两类

  • 静态多态:函数重载和运算符重载属于静态多态,复用函数名
  • 动态多态:派生类和虚函数实现运行时多态

静态多态和动态多态的区别

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址、

多态的优点

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展及维护

7.2 多态案例—计算器类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

示例:

#include <iostream>

using namespace std;
class AbstractCalculator
{
public:
virtual int getResult()
{
return 0;
}
int num1;
int num2;
};
class AddCalculator :public AbstractCalculator
{
public:
int getResult()
{
return num1 + num2;
}
};
class SubCalculator :public AbstractCalculator
{
public:
int getResult()
{
return num1 - num2;
}
};
class MulCalculator :public AbstractCalculator
{
public:
int getResult()
{
return num1 * num2;
}
};
int main()
{
//多态使用条件
//父类指针或者引用指向子类对象
AbstractCalculator * abc = new AddCalculator;
abc->num1 = 100;
abc->num2 = 100;
cout << abc->num1 << "+" << abc->num2 << " = " << abc->getResult() << endl;
delete abc; //只是数据消除了,指针的类型没有变 abc = new SubCalculator;
abc->num1 = 100;
abc->num2 = 100;
cout << abc->num1 << "-" << abc->num2 << " = " << abc->getResult() << endl;
return 0;
}

7.3 纯虚函数和抽象类

在多态中,通常父类函数中的虚函数是无意义的,主要是调用子类重写的内容

因此可以将虚函数改为纯虚函数

纯虚函数语法:virtual 返回值类型 函数名 (参数列表) = 0

当类中有了纯虚函数,这个类也称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类

示例:

#include <iostream>

using namespace std;
class Base
{
public:
//纯虚函数与抽象类
virtual void func() = 0;
};
class Son :public Base
{
public:
virtual void func()
{
cout << "func 函数调用" << endl;
}
};
int main()
{
Base * abc = new Son;
abc->func();
return 0;
}

7.4 虚析构和纯虚析构

多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码

解决方式:将父类中的析构函数改为虚析构纯虚析构

虚析构和纯虚析构共性:

  • 可以解决父类指针和释放子类对象
  • 都需要有具体的函数实现

虚析构和纯虚析构的区别:

  • 如果是纯虚析构,该类属于抽象类,无法实例化对象

虚析构语法: virtual ~类名() {}

纯虚析构语法:virtual ~类名() = 0;

类名::类名() {}

总结:

  1. 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
  2. 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构
  3. 拥有纯虚析构函数的类也属于抽象类

C++类和对象笔记的更多相关文章

  1. python cookbook第三版学习笔记十:类和对象(一)

    类和对象: 我们经常会对打印一个对象来得到对象的某些信息. class pair:     def __init__(self,x,y):         self.x=x         self. ...

  2. 《python基础教程(第二版)》学习笔记 类和对象(第7章)

    <python基础教程(第二版)>学习笔记 类和对象(第7章) 定义类class Person:    def setName(self,name):        self.name=n ...

  3. Java学习笔记之---类和对象

    Java学习笔记之---类和对象 (一)类 类是一个模板,它描述一类对象的行为和状态  例如:动物类是一个类,动物们都有属性:颜色,动物们都有行为:吃饭 public class Dog { Stri ...

  4. Python学习笔记(六)——类和对象

    1.self的用法 全面理解self 2. 继承 子类继承父类,自动拥有父类的全部方法 >>> class Animal: def run(self): print('Animal ...

  5. ES6学习笔记(一):轻松搞懂面向对象编程、类和对象

    目录 面向过程编程P OP(Process oriented programming) 面向对象编程OOP(Object Oriented Programming) 总结 @ 面向过程编程P OP(P ...

  6. [Java入门笔记] 面向对象编程基础(一):类和对象

    什么是面向对象编程? 我们先来看看几个概念: 面向过程程序设计 面向过程,是根据事情发展的步骤,按进行的顺序过程划分,面向过程其实是最为实际的一种思考方式,可以说面向过程是一种基础的方法,它考虑的是实 ...

  7. Java学习笔记 04 类和对象

    一.类和对象的概念 类 >>具有相同属性和行为的一类实体 对象 >>实物存在的实体.通常会将对象划分为两个部分,即静态部分和动态部分.静态部分指的是不能动的部分,被称为属性,任 ...

  8. 《PHP Manual》阅读笔记3 —— 类与对象

    1.PHP 中的所有函数和类都具有全局作用域,可以定义在一个函数之内而在之外调用,反之亦然. PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数. 当一个函数是有条件被定义时,必须在调用函 ...

  9. [Effective JavaScript 笔记]第51条:在类数组对象上复用通用的数组方法

    前面有几条都讲过关于Array.prototype的标准方法.这些标准方法被设计成其他对象可复用的方法,即使这些对象并没有继承Array. arguments对象 在22条中提到的函数argument ...

随机推荐

  1. P6982 [NEERC2015]Jump

    P6982 [NEERC2015]Jump 题意 给你一个未知的 01 串,每次可以输出询问一个 01 串,如果该串中正确的个数刚好等于 \(n\) 或者 \(n/2\) ,将会返回相应的答案,否则会 ...

  2. 什么是EL表达式,以及作用

    1.概念 EL(Expression Language) 是为了使JSP写起来更加简单.减少java代码,便于开发和维护. 2.语法 格式都是以"${}"表示. 3.与运算符 EL ...

  3. ASP.NET中<%=%>、<%%>、<%@%>、<%#%>的用法与区别

    1.<%= %> 里面放变量名,获取后台的变量值,直接输入变量到页面上,里面放的变量名,未经过encode eg: 后台: seession["ab"]=ab; 前台: ...

  4. @FeignClient常用属性

    @FeignClient(name = "gateway-test", value = "gateway-test", url = "localhos ...

  5. 大数据学习(02)——HDFS入门

    Hadoop模块 提到大数据,Hadoop是一个绕不开的话题,我们来看看Hadoop本身包含哪些模块. Common是基础模块,这个是必须用的.剩下常用的就是HDFS和YARN. MapReduce现 ...

  6. 记录21.08.04 — mybatis入门学习

    mybatis入门 mybatis简介 MyBatis 是一款优秀的持久层框架,它支持自定义 SQL.存储过程以及高级映射.MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工 ...

  7. 使用 Assimp 库加载 3D 模型

    前言 要想让自己的 3D 之旅多一点乐趣,肯定得想办法找一些有意思一点的 3D 模型.3D 模型有各种各样的格式,obj的,stl的,fbx的等等不一而足.特别是 obj 格式的 3D 模型,完全是纯 ...

  8. AcWing 第11场周赛题解

    计算abc 首先 \(0<=a<=b<=c\) 会随机给出 \(a+b,a+c,b+c,a+b+c\)的值 因为\(a,b,c\)都为正整数,所以\(a+b+c\)一定为最大值 然后 ...

  9. Kerberos相关的安全问题

    用户名枚举 原理 不存在的用户 存在的用户 通过这个比较就可以写脚本改变cname的值进行用户名枚举. 利用 https://github.com/ropnop/kerbrute/ kerbrute. ...

  10. 面试官:MySQL 有哪些锁??

    大家好,我是小林. 这次,来说说 MySQL 的锁,主要是 Q&A 的形式,看起来会比较轻松. 不多 BB 了,发车! 在 MySQL 里,根据加锁的范围,可以分为全局锁.表级锁和行锁三类. ...