一、封装
public
private
protected
二、继承
0.类成员类型的区别
-
public:可以被任意实体访问
-
protected:只允许子类及本类的成员函数访问
-
private:只允许本类的成员函数访问
1.一个派生类继承了所有的基类方法,但下列情况除外:
-
基类的构造函数、析构函数和拷贝构造函数。
-
基类的友元函数。
-
赋值操作符重载
点击查看代码
// operator= 返回的是本类对象可以被继承,返回其他对象或类型不能被继承
// 除过=以外的其他重载运算符都可以被子类继承
// 拷贝构造函数不能被继承
// 无参构造函数不能被继承
// 析构函数不能被继承
// 友元不会被继承
#include <iostream>
class A
{
public:
A() { this->n = 9; std::cout << "AAA\n"; }
A(const A& a) { this->n = a.n; std::cout << "&AAA\n"; }
int get() { return n; }
void set(int n_) { this->n = n_; }
protected:
~A() { std::cout << "~AAA\n"; } // 基类的析构不能声明为私有的
//A& operator=(const A& a) { this->n = a.n; std::cout << "=AAA\n"; return *this; } // 赋值操作符,注意区分和赋值操作符重载的区别
public:
int operator=(const int& x) { return x + 1; } // 赋值操作符重载
int operator+(const int& x) { return x + 1; } // 不能声明为私有和保护的,不然子类无法访问
private:
int n{ 0 };
};
class B:
public A
{
public:
B() { std::cout << "BBB\n"; }
~B() { std::cout << "~BBB\n"; }
B(const B& b) { std::cout << "&BBB\n"; }
//B& operator=(const B& b) { std::cout << "=BBB\n"; return *this; }// 如果基类和子类都没有重载=,编译器会自动添加这个函数
//B& operator= (const B&) = delete;
};
int main()
{
B b1;
b1.set(1);
B b2;
std::cout << b2.get() << std::endl;
b2.set(2);
std::cout << "===============\n";
b2 = b1;
std::cout << b2.get() << std::endl; // 1
//auto ret1 = (b2 = 1);
std::cout << (b2 + 1) << std::endl; // 子类可以继承基类的operator+
}
2.继承类型
- 公有继承
当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。 - 保护继承
当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。 - 私有继承
当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
无论哪种继承类型,子类都会继承父类的所有成员,只是部分成员没有访问权限而已
父类中的访问权限 | 继承方式 | 子类中的访问权限 |
public | public | public |
private | no access | |
protected | protected | |
public | private | private |
private | no access | |
protected | private | |
public | protected | protected |
private | no access | |
protected | protected |
3.继承构造函数含有参数的父类
父类含有多个构造函数,子类构造函数的写法?
4.继承之后类的构造,析构顺序
派生类对象在创建时构造函数调用顺序:
- 调用父类的构造函数
- 调用父类成员变量的构造函数
- 调用派生类本身的构造函数
派生类对象在析构时的析构函数调用顺序:
- 执行派生类自身的析构函数
- 执行派生类成员变量的析构函数
- 执行父类的析构函数
5.父类析构函数
无论派生类有没有申请堆上的资源,父类的析构函数都应该声明为virtual。如果父类的析构函数不为virtual,那么派生类的析构函数就不会被调用,可能会发生内存泄漏。
std::string不可以被继承,因为std::string的析构函数没有被声明为virtual
MSVC源码(std::string是basic_string的特化):
inline ~basic_string() noexcept {
_Tidy_deallocate();
}
三、多态
调用基类指针创建子类对象,那么基类应该有=虚析构函数==
1.虚函数
本质:由虚指针和虚表控制,虚指针指向虚表中的某个函数入口地址,就实现了多态,
作用:实现了多态,虚函数可以被子类重写,虚函数地址存储在虚表中
是在基类中使用关键字 virtual 声明的函数。在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。我们想要的是在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作被称为动态链接,或后期绑定。
2.纯虚函数
基类中不定义只声明,子类中必须定义。
3.动态多态
重写
4.静态多态
模板和重载
四、友元
如果想在类外访问该类的私有成员,可以声明一个友元类,例如下面的类B是类A的友元类,在类B中可以访问类A的私有成员。
class A
{
private:
int n;
friend class B;
};
class B
{
void fun(A a) { int x = a.n; }
};
还有友元函数,也是通过声明一个友元函数,在类外达到访问类的私有成员的目的,和友元类写法一致。