三种权限,一共对应九种场景。要做到心中有表,遇到任何一种场景都能直接反映出是否能访问。
类内 |
派生类中 |
全局 |
|
private |
√ |
× |
× |
protected |
√ |
√ |
× |
public |
√ |
√ |
√ |
#include
using namespace std;
class Base
{
protected:
//类内 派生类内访问,
string s = "保护权限";
public:
Base()
{
cout << s << endl;
}
};
class Son:public Base
{
public:
Son()
{
cout << s << endl;
}
};
int main()
{
// Base b1;
Son s1;
// cout << s1.s << endl; // 错误 s是保护权限
return 0;
}
上面的代码中一直使用的就是公有继承,公有继承也是使用的最多的一种继承方式。
在共有继承当中,派生类可以继承基类的成员,但是不可访问基类的私有成员,基类的公有成员与保护成员在派生类中权限不变。
#include
using namespace std;
class Base
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
class Son:public Base
{
public:
Son()
{
// cout << str1 << endl; // 错误 str1为私有成员
cout << str2 << endl;
cout << str3 << endl;
}
};
int main()
{
Son s1;
return 0;
}
在保护继承中,派生类可以继承基类的成员,不可访问基类的私有成员,基类的公有成员与保护成员在派生类中的权限都是保护权限。(只能在基类与派生类中访问,外部无法访问)。
#include
using namespace std;
class Base
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
// 保护继承
class Son:protected Base
{
public:
Son()
{
// cout << str1 << endl; // 错误 str1为私有成员
cout << str2 << endl;
cout << str3 << endl;
}
};
int main()
{
Son s1;
// s1.str3; //错误,str3在保护继承的派生类中为保护成员,外部无法访问
return 0;
}
在私有继承中,派生类可以继承基类的成员,但是不可以访问基类的私有成员,基类的公有成员与保护成员在派生类中的权限都是私有权限。
#include
using namespace std;
class Base
{
private:
string str1 = "私有成员";
protected:
string str2 = "保护成员";
public:
string str3 = "公有成员";
};
// 私有继承
class Son:private Base
{
public:
Son()
{
// cout << str1 << endl; // 错误 str1为私有成员
cout << str2 << endl;
cout << str3 << endl;
}
};
int main()
{
Son s1;
// s1.str3; //错误,str在私有继承的派生类中为私有成员
return 0;
}
函数覆盖与函数隐藏比较相似,但是函数隐藏不支持多态,而函数覆盖是多态的必备条件。
(函数覆盖是指在父类和子类之间存在继承关系时,子类定义了与父类中同名的函数,并且函数的参数类型、返回值类型必须与父类中的相应函数一致。当子类对象调用该同名函数时,会自动调用子类中的函数,而不是父类中的函数。这种机制就是函数覆盖。)
(函数隐藏是指在派生类中定义了与基类中同名的函数,从而隐藏了基类中的函数。当通过派生类的对象调用该函数时,实际上调用的是派生类中定义的函数,而不是基类中的函数。函数隐藏发生在不同作用域,即派生类的作用域中隐藏了基类的函数。)
在编程方式上,函数覆盖与函数隐藏有以下几点区别:
一个函数使用virtual关键字修饰,就是虚函数。虚函数是函数覆盖的前提。在Qt Creator中函数名称使用斜体字。
虚函数具有以下性质:
#include
using namespace std;
class Animal
{
public:
// 虚函数
virtual void eat();
void test()
{
}
};
void Animal::eat()
{
cout << "动物爱吃饭" << endl;
}
class Dog:public Animal
{
void eat() override
{
cout << "狗爱吃骨头" << endl;
}
// void test() override
// {
// }
};
int main()
{
Dog d1;
return 0;
}
多态可以理解为“一种接口,多种状态”,只需要编写一个函数接口,根据传入的参数类型,执行不同的策略代码。
多态的使用需要具备三个前提条件:
#include
using namespace std;
class Animal
{
public:
// 虚函数
virtual void eat()
{
cout << "动物爱吃饭" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout<<"狗爱吃骨头"<<endl;
}
};
class Cat:public Animal
{
public:
void eat()
{
cout<<"猫爱吃鱼"<<endl;
}
};
void animal_eat(Animal *al)
{
al->eat();
}
void animal_eat2(Animal &al)
{
al.eat();
}
int main()
{
//堆区开辟
Dog *d1=new Dog;
Cat *c1=new Cat;
animal_eat(d1);
animal_eat(c1);
//栈区定义
Dog d2;
Cat c2;
animal_eat2(d2);
animal_eat2(c2);
return 0;
}
具有虚函数的类会存在一张虚函数表。这张表被当前类所有对象共用。每个类的对象内部都会有一个隐藏的虚函数指针成员,指向当前类的虚函数表。
在代码运行时,通过对象的虚函数指针找到对应虚函数表,在表中定位到虚函数的调用地址,执行对应的虚函数的内容。
因此使用多态会产生一些额外的开销。优点是代码编写更加的灵活高效,缺点是会降低代码的执行速度,代码可读性降低。
(析构函数:当对象销毁时被调用,走完花括号“{}”,或delate释放堆空间)
如果不使用虚析构函数,且基类指针或引用指向派生类对象,使用delete销毁对象时,
析构函数不会被继承,但虚函数表会被继承,虚析构函数不会被覆盖,会自动生成一个新的对应派生类虚函数的虚析构函数,原虚析构函数对应基类虚函数。
只会触发基类的析构函数,如果在派生类中申请了内存资源,则会导致无法释放,出现内存泄漏的问题。
#include
using namespace std;
class Animal
{
public:
// 虚函数
virtual void eat()
{
cout << "动物爱吃饭" << endl;
}
~Animal()
{
cout << "Animal 析构函数被调用了" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout << "狗爱吃骨头" << endl;
}
~Dog()
{
cout << "Dog 析构函数被调用了" << endl;
}
};
int main()
{
Animal *al = new Dog;
al->eat();//狗爱吃骨头
delete al;//Animal 析构函数被调用了,只调用这一个
return 0;
}
解决方法是给基类的析构函数使用virtual关键字修饰为虚析构函数,通过传递性可以把各个派生类的析构函数都变为虚析构函数。因此建议给一个可能为基类的类中的析构函数设置为虚析构函数。
#include
using namespace std;
class Animal
{
public:
// 虚函数
virtual void eat()
{
cout << "动物爱吃饭" << endl;
}
virtual ~Animal()
{
cout << "Animal 析构函数被调用了" << endl;
}
};
class Dog:public Animal
{
public:
void eat() override
{
cout << "狗爱吃骨头" << endl;
}
~Dog()
{
cout << "Dog 析构函数被调用了" << endl;
}
};
int main()
{
Animal *al = new Dog;
al->eat();//狗爱吃骨头,函数执行完后执行该函数对应的析构函数。
//Dog 析构函数被调用了
delete al;//Animal 析构函数被调用了
return 0;
}
如果基类只表达一些抽象的概念,并不与实际的对象相关联,这时候就可以使用抽象类。
如果一个类中有纯虚函数,这个类就是一个抽象类。
如果一个类是抽象类,则这个类中一定有纯虚函数。
纯虚函数是虚函数的一种,这种函数只有声明没有定义。不能实例化对象·
virtual 返回值类型 函数名(参数列表) = 0;
不能直接使用抽象类作为类型声明,因为不存在抽象类类型的对象。
抽象类作为基类时,具有两种情况:
抽象类,无法实例化对象,但是可以创建指针和引用。
#include
using namespace std;
// 抽象类:形状
class Shape
{
public:
// 纯虚函数
virtual void area() = 0; // 面积
virtual void perimeter() = 0; // 周长
};
// 圆形
class Circle:public Shape
{
public:
void area()
{
cout << "圆形计算面积" << endl;
}
void perimeter()
{
cout << "圆形计算周长" << endl;
}
};
// 多边形
class Polygon:public Shape
{
public:
void perimeter()
{
cout << "多边形计算周长" << endl;
}
};
// 矩形
class Rectangle:public Polygon
{
public:
void area()
{
cout << "矩形计算面积" << endl;
}
};
int main()
{
// Shape s; // 错误抽象类无法实例化对象
Circle c;
c.area();
c.perimeter();
// Polygon p; // 错误,没有完全覆盖基类抽象函数,抽象类无法实例化对象
Rectangle r;
r.area();
r.perimeter();
return 0;
}
使用抽象类注意以下几点:
纯虚析构函数的定义:
纯虚析构的本质:是析构函数,作用是各个类的回收工作。而且析构函数不能被继承。
必须要为纯虚析构函数提供一个函数体。
纯虚析构函数,必须在类外实现。
#include
using namespace std;
class Animal
{
public:
virtual void speak()
{
}
Animal()
{
cout << "基类的构造函数被调用了" << endl;
}
// 纯虚析构
virtual ~Animal() = 0;
};
// 纯虚析构类外实现
Animal::~Animal()
{
cout << "基类析构函数被调用了" << endl;
}
class Dog:public Animal
{
public:
void speak()
{
cout << "狗会汪汪汪" << endl;
}
Dog()
{
cout << "dog类构造函数被调用了" << endl;
}
~Dog()
{
cout << "Dog类的析构函数被调用了" << endl;
}
};
int main()
{
// Animal al; // 错误基类是纯虚析构,无法实例化对象
Animal *al = new Dog();
al->speak();
delete al;
return 0;
}
虚析构与纯虚析构的区别: