✍个人博客:Pandaconda-CSDN博客
专栏地址:http://t.csdnimg.cn/fYaBd
专栏简介:在这个专栏中,我将会分享 C++ 面试中常见的面试题给大家~
❤️如果有收获的话,欢迎点赞收藏,您的支持就是我创作的最大动力
C++ 是面向对象的(object oriented programming—OOP):强调对象,由对象实施动作。
C 是面向过程的(procedure oriented programming—POP):强调执行的过程。
总结起来就是:
面向对象是首先抽象出各种对象(各种类),把数据和方法都封装在对象中(类),然后各个对象之间发生相互作用。
面向过程是将问题分解成若干步骤(动作),每个步骤(动作)用一个函数来实现,在使用的时候,将数据传递给这些函数。
public: 可以被该类中的函数、子类的函数、其友元函数访问,也可以由该类的对象访问。
protected: 可以被该类中的函数、子类的函数、以及其友元函数访问,但不能被该类的对象访问。
private: 只能由该类中的函数、其友元函数访问,不能被任何其他访问,该类的对象也不能访问。
(1)访问权限
派生类可以继承基类中除了构造/析构、赋值运算符重载函数之外的成员,但是这些成员的访问属性在派生过程中也是可以调整的,三种派生方式的访问权限如下表所示:
注意外部访问并不是真正的外部访问,而是在通过派生类的对象对基类成员的访问。
派生类对基类成员的访问形象有如下两种:
内部访问:由派生类中新增的成员函数对从基类继承来的成员的访问。
外部访问:在派生类外部,通过派生类的对象对从基类继承来的成员的访问。
(2)继承权限
public 继承
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,都保持原有的状态,而基类的私有成员任然是私有的,不能被这个派生类的子类所访问。
protected 继承
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元函数访问,基类的私有成员仍然是私有的,访问规则如下表:
private 继承
私有继承的特点是基类的所有公有成员和保护成员都成为派生类的私有成员,并不被它的派生类的子类所访问,基类的成员只能由自己派生类访问,无法再往下继承,访问规则如下表:
(3)总结
一、访问权限
访问权限 | 外部 | 派生类 | 内部 |
public | ✔ | ✔ | ✔ |
protected | ❌ | ✔ | ✔ |
private | ❌ | ❌ | ✔ |
public、protected、private 的访问权限范围关系:
public > protected > private
二、继承权限
派生类继承自基类的成员权限有四种状态:public、protected、private、不可见
派生类对基类成员的访问权限取决于两点:一、继承方式;二、基类成员在基类中的访问权限
派生类对基类成员的访问权限是取以上两点中的更小的访问范围(除了 private 的继承方式遇到 private 成员是不可见外)。例如:
public 继承 + private 成员 => private
private 继承 + protected 成员 => private
private 继承 + private 成员 => 不可见
1、重载(overload)
重载是指在同一范围定义中的同名成员函数才存在重载关系。主要特点是函数名相同,参数类型和数目有所不同,不能出现参数个数和类型均相同,仅仅依靠返回值不同来区分的函数。重载和函数成员是否是虚函数无关。举个例子:
class A{
...
virtual int fun();
void fun(int);
void fun(double, double);
static int fun(char);
...
}
如果函数名,参数都一样,但只有返回值不一样,是重载吗?
不是,因为 C++ 编译器存在 name mangling,通过函数名和参数来生成唯一标识符。
2、重写(覆盖)(override)
重写指的是在派生类中覆盖基类中的同名函数,重写就是重写函数体,要求基类函数必须是虚函数且:
与基类的虚函数有相同的参数个数
与基类的虚函数有相同的参数类型
与基类的虚函数有相同的返回值类型
举个例子:
//父类
class A{
public:
virtual int fun(int a){}
}
//子类
class B : public A{
public:
//重写,一般加 override 可以确保是重写父类的函数
virtual int fun(int a) override{}
}
重载与重写的区别:
重写是父类和子类之间的垂直关系,重载是不同函数之间的水平关系。
重写要求参数列表相同,重载则要求参数列表不同,返回值不要求。
重写关系中,调用方法根据对象类型决定,重载根据调用时实参表与形参表的对应关系来选择函数体。
3、隐藏(hide)
隐藏指的是某些情况下,派生类中的函数屏蔽了基类中的同名函数,包括以下情况:
1. 两个函数参数相同,但是基类函数不是虚函数。和重写的区别在于基类函数是否是虚函数,举个例子:
//父类
class A{
public:
void fun(int a){
cout << "A中的fun函数" << endl;
}
};
//子类
class B : public A{
public:
//隐藏父类的 fun 函数
void fun(int a){
cout << "B中的fun函数" << endl;
}
};
int main(){
B b;
b.fun(2); //调用的是 B 中的 fun 函数
b.A::fun(2); //调用 A 中 fun 函数
return 0;
}
2. 两个 函数参数不同,无论基类函数是不是虚函数,都会被隐藏。和重载的区别在于两个函数不在同一个类中,举个例子:
//父类
class A{
public:
virtual void fun(int a){
cout << "A中的fun函数" << endl;
}
};
//子类
class B : public A{
public:
//隐藏父类的fun函数
virtual void fun(char* a){
cout << "A中的fun函数" << endl;
}
};
int main(){
B b;
b.fun(2); //报错,调用的是 B 中的 fun 函数,参数类型不对
b.A::fun(2); //调用 A 中 fun 函数
return 0;
}
补充:
//父类
class A {
public:
virtual void fun(int a) { //虚函数
cout << "This is A fun " << a << endl;
}
void add(int a, int b) {
cout << "This is A add " << a + b << endl;
}
};
//子类
class B: public A {
public:
void fun(int a) override { //覆盖
cout << "this is B fun " << a << endl;
}
void add(int a) { //隐藏
cout << "This is B add " << a + a << endl;
}
};
int main() {
//基类指针指向派生类对象时,基类指针可以直接调用到派生类的覆盖函数,也可以通过 :: 调用到基类被覆盖的虚函数;而基类指针只能调用基类的被隐藏函数,无法识别派生类中的隐藏函数。
A *p = new B();
p->fun(1); //调用子类 fun 覆盖函数
p->A::fun(1); //调用父类 fun
p->add(1, 2);
// p->add(1); //错误,识别的是 A 类中的 add 函数,参数不匹配
// p->B::add(1); //错误,无法识别子类 add 函数
return 0;
}