三种权限,一共对应九种场景。要做到心中有表,遇到任何一种场景都能直接反映出是否能访问。
类内 | 派生类中 | 全局 | |
private | √ | × | × |
protected | √ | √ | × |
public | √ | √ | √ |
#include?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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?<iostream>
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;
}
虚析构与纯虚析构的区别: