NIUCLOUD是一款SaaS管理后台框架多应用插件+云编译。上千名开发者、服务商正在积极拥抱开发者生态。欢迎开发者们免费入驻。一起助力发展! 广告
[TOC] ## C++和C的区别 **设计思想上**:C++是面向对象的语言,而C是面向过程的结构化编程语言。 **语法上**: * C++具有重载、继承和多态三种特性。 * C++相比C,增加多许多类型安全的功能,比如强制类型转换。 * C++支持范式编程,比如模板类、函数模板等。 ## C++ 三大特性 **封装**即将数据和代码捆绑在一起,避免外界干扰和不确定性访问: * public属性的成员变量和在类的内部外部都可以访问; * protected属性的成员变量和函数只能在类的内部和其派生类中访问; * private属性的成员变量和函数只能在类内访问; **继承**,让某种类型对象获得另一个类型对象的属性和方法。指一个派生类可以同时有多个基类(多重继承),也可以只有一个基类(单继承)。 * 公有继承:当类的继承方式为公有继承时,基类的公有和保护成员的访问属性在派生类中不变,而基类的私有成员不可访问; * 保护继承:当类的继承方式为保护继承时,基类中的公有成员和保护成员都以保护成员的身份出现在派生类中,而基类的私有成员不可访问; * 私有继承:当类的继承方式为私有继承时,基类中的公有成员和保护成员都以私有成员身份出现在派生类中,而基类的私有成员在派生类中不可访问; **多态**是让同一事物表现出不同事物的能力,指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。 * 编译时多态性(静态多态):通过重载函数实现; * 运行时多态性(动态多态):通过虚函数实现(基类指针或者基类引用可以指向派生类); ## 构造函数 & 析构函数 **初始化列表 vs 构造函数**:初始化列表的性能要优于构造函数,使用初始化列表将会直接调用成员变量的初始化函数,而使用构造函数的话,会先调用成员变量的默认构造函数,再调用赋值函数。 **派生类中基类构造函数和析构函数的调用顺序**: * 构造函数调用次序是先基类的构造后派生类的构造; * 析构函数调用次序是先派生类的析构后基类的析构; **构造和析构中不能调用虚函数**:派生类对象内的基类成分会在派生类自身成分被构造之前先构造妥当,此时如果基类的构造函数中调用了 virtual 函数,该 virtual 函数是基类版本的,而非派生类的。析构函数同理。 **拷贝构造函数的形参能否进行值传递**:不能。如果是值传递的话,调用拷贝构造函数时,首先将实参传给形参,这样又会调用拷贝构造函数,如此,会造成无限递归调用拷贝构造函数。 **析构函数抛异常**:析构函数抛异常可能导致程序出现问题,比如当`vector v`被销毁,编译器会负责销毁数组中的每个元素。假设数组长度为10,当第一个元素被调用析构函数时出现异常,这会导致后面9个元素没有被销毁。 解决办法有两种: * 第一种是捕获异常的话直接结束程序: ```c++ DBConn::~DBConn() { try { db.close(); // 释放资源; } catch (...) { ... // 标记失败; std::abort(); // 退出程序; } } ``` * 第二种是吞下异常,保证后续逻辑正常运行: ```c++ DBConn::~DBConn() { try { db.close(); // 释放资源; } catch (...) { ... // 标记失败,并吞下异常; } } ``` ## 虚函数 虚函数允许在派生类中重新定义与基类同名函数,并且可以通过基类指针或引用来访问基类或派生类同名函数。**纯虚函数**指在基类中没有定义的虚函数,它必须在派生类中被定义。 虚函数基于虚表实现,每一个含有虚函数的类都至少有一个与之对应的虚函数表(多继承有多个),其中存放着该类所有的虚函数对应的函数指针。 **静态函数和虚函数的区别**:静态函数在编译的时候就已经确定运行时机,虚函数在运行的时候动态绑定。虚函数因为用了虚函数表机制,调用的时候会增加一次内存开销。 **虚函数不能是内联函数**:虚函数是在运行的时间决定调用哪个函数,而 inline 函数是在编译的时候决定要不要 inline。 **派生类析构函数必须是虚函数**:当 derived class 对象经由一个 base class 指针被删除,而该 base class 的析构函数是 non-virtual 的,其结果未有定义(通常是调用 base class 的析构函数,而导致 derived 成分没有被销毁)。这种现象称为“局部销毁”,它会造成资源泄漏、败坏数据结构、在调试器上浪费时间等问题。 ```c++ #include <stdio.h> class Base { public: ~Base() { printf("base destructor\n"); } }; class VBase { public: virtual ~VBase() { printf("virtual base destructor\n"); } }; class Derived : public Base, public VBase { public: ~Derived() { printf("derived destructor\n"); } }; int main(int argc, char ** argv) { // output: // base destructor { Base b; } printf("-------\n"); // output: // derived destructor // virtual base destructor // base destructor { Derived d; } printf("-------\n"); { // output: // base destructor Base* pb = new Derived(); delete pb; } printf("-------\n"); { // output: // derived destructor // virtual base destructor // base destructor VBase* pvb = new Derived(); delete pvb; } return 0; } ``` **无虚函数的类使用虚析构函数**:如果 class 不含 virtual 函数,通常表示它并不意图被用作一个 bass class,但如果此时使用 virtual 析构函数会导致对象体积增加。因为想要实现 virtual 函数,对象会携带一个 vptr(virtual table pointer)指针,该指针指向一个由函数指针构成的数组,称为vtbl(virtual table);每一个带有 virtual 的函数的 class 都有一个相应的 vtbl。当对象调用某一 virtual 函数,实际被调用的函数取决于该对象的 vptr 所指向的那个 vtbl。 ## this指针 `this` 指针的本质是一个指向对象内存地址的指针,每一个对象都有一个对应的 `this` 指针。它并不是对象的一部分,不会占用对象的内存。 当对象调用类的非静态成员函数时,编译器会自动 的把 `this` 指针作为隐含的参数传递给函数,作为类的非静态成员函数的第一个隐含形参(注意静态成员函数是不会传递 `this` 指针的)。 **对象的空指针调用成员函数**: * 调用静态成员函数:正常运行,因为静态成员函数与类相关; * 调用虚函数:运行失败,因为需要通过this指针查虚表; * 调用未访问成员变量的成员函数:正常运行,因为没有使用this指针; * 调用访问了成员变量的成员函数:运行失败,调用this指针时访问了空指针; ## 赋值函数 **重载赋值函数需要返回一个对象引用**:关于复制,程序中通常会出现连锁形式 `x = y = z = 15; // x = (y = (z = 15)));` 为了实现“连锁赋值”,赋值操作符必须返回一个 reference 指向操作符的左侧实参,这时 class 实现赋值操作符时应该遵循的协议: ```c++ class Widget { public: ... Widget& operator=(const Wedget& rhs) { ... return *this; } ... }; ```