C++语法概览

一、基础语法

  • const:常量是固定值,在程序执行期间不会改变
    int *const p;  // 指针是常量,指向的值可以发生改变(const pointer to int)
    const int p;  //p是常量,无法发生改变
    const int *p; // p的值(指针)可以发生改变,指向的值不能发生改变(pointer to const int)
    const int *const p; // 指针和值都不能发生改变(const pointer to const int)
    • constexpr:编译时常量,因此使用这个关键字可以做编译时优化,运行时性能会比const好。常量表达式不能包含函数调用或者对象构造。constexpr函数的返回值不能是void类型,不能声明变量或新类型。
    constexpr int get_five() { return 5; }
    int some_value[get_five() + 7];
    constexpr int a = 3;
    • typedef:为一个已有的类型取一个新的名字,在复杂项目中可以提高代码可读性
    • volatile:标识一个易变量。每次都需要从内存地址读取这个数据
    • extern:在修饰函数或者变量的时候,告知编译器该函数或变量已在别处定义,可以放心编译构建
    • static:对应函数限制只能在本文件调用;不能在其他文件中被调用
    • inline:编译器会将函数在调用点展开,减少了调用开销,提高了代码的执行效率但是可能会带来代码体积的膨胀。相比较宏定义,inline函数有类型检查,更安全。
    • auto:能够让编译器在编译期间自动推导出变量的类型。使用auto类型的变量必须马上初始化,否则编译器无从推导变量类型
    • decltype:以一个普通表达式作为参数在编译时进行类型推导,返回表达式的类型

    二、面向对象编程

    类成员的访问控制:

    • 访问权限类型分为公有类型public、保护类型protected、私有类型private
    • 成员默认的访问控制权限为private
    • 元函数或友元类可访问类的保护成员或私有成员

    类的构造:

    • 构造函数是用于构造对象的特殊函数,在对象被创建时被调用以初始化对象
    • 未定义构造函数时,编译器自动生成不带参数的默认版本
    • 执行构造函数时先执行其初始化列表,再执行函数体
    • 构造函数同其他函数一样,允许被重载,允许被委托
    • 使用default关键字可要求编译器生成默认构造函数
    • 使用explict关键字拒绝对象被隐式构造(参数的类型被隐式转换后匹配,单参数的构造函数尤其需要注意)
    • 使用delete关键字删除部分构造函数避免对象不符合预期的被构造

    复制构造与移动构造:

    • 复制构造函数是特殊的构造函数,形参为本类的对象引用,用已存在的对象初始化同类新对象
    • 若未定义复制构造函数,编译器自动生成默认的按比特位进行复制的复制构造函数,即浅拷贝
    • 若不希望对象被复制构造,可使用delete关键字拒绝
    • 复制构造函数被调用的三种情况:直接定义、函数传参、函数返回值
    • 移动构造函数用于将某个临时对象的资源移交给另一个正在创建的新对象
    • 当临时对象在即将销毁阶段被用于拷贝构造新对象时,可触发移动构造函数被调用以避免拷贝发生
    • 移动构造函数是特殊的构造函数,其参数为本类对象的右值引用
    • 编译器默认生成的移动构造函数与默认复制构造函数一样,可使用delete关键字拒绝

    类的析构:

    • 析构函数用以完成对象销毁前的清理工作,如释放内存、关闭文件等
    • 在对象生存期结束时立即被调用,然后再释放对象所占空间
    • 未定义析构函数则编译器自动生成默认版本,函数体为空
    • 利用局部对象实现RAII机制(Resource Acquisition Is Initialization)

    类的静态成员与常量成员:

    • 类的静态成员变量被所有对象共享,具有静态生存期,且不占用某个具体对象的内存
    • 静态成员变量只能在类外定义与初始化,静态成员函数只能访问静态成员变量
    • 常量成员只能通过类内初始值或初始化列表完成初始化

    this指针:

    • 指向当前对象本身,与对象首地址相同
    • 隐含于类的每一个非静态成员函数参数列表中

    类的运算符重载:

    • 对已存在对象的赋值操作若需要特别的控制,可通过重载赋值运算符实现
    • 编译器会生成默认版本赋值操作符,效果等同于默认复制构造函数,可使用delete关键字拒绝
    • 通过重载new/delete操作符可以定制对象的内存分配和释放过程
    • 通过重载函数运算符重载可使对象成为带有数据的函数,也称仿函数
    • 对已存在对象的赋值操作若需要特别的控制,可通过重载赋值运算符实现
    • 编译器会生成默认版本赋值操作符,效果等同于默认复制构造函数,可使用delete关键字拒绝
    • 通过重载函数运算符重载可使对象成为带有数据的函数,也称闭包
    • 通过重载new/delete操作符可以定制对象的内存分配和释放过程

    类的组合与前向声明:

    • 类的成员可以是另一个类的对象,在已有抽象基础上实现更复杂的抽象(has-a)
    • 组合类的的构造函数不仅需要负责本类中基本类型成员数据初始化,也要对对象成员初始化
    • 组合类对象初始化顺序为其声明的顺序,析构顺序则相反
    • 前向声明用于某个类在声明之前被引用,但仅限引用,不可对该类对象进行操作

    继承与派生:

    • 继承是面向对象程序设计中实现代码复用与软件抽象的重要表现形式
    • 继承允许在类的基础上定义新的类,被继承的类叫基类,新的类称为派生类(is-a)
    • 派生类拥有所有基类的特性,并可以做必要的调整以给自身增加新特性
    • 一个基类允许派生出多个基类,并且继承是可以往下传递的,一个派生类也可以派生自多个基类

    类成员访问限制:

    • 不同的继承方式影响派生类成员对基类成员的访问权限与派生类对象的用户对基类成员的访问权限
    • 友元关系不能被继承
    • 若需要显式地指明访问基类的成员时,可加作用域限定符

    基类成员访问属性

    在派生类中的访问属性

    public

    protected

    private

    public

    public

    protected

    private

    protected

    protected

    protected

    private

    private

    inaccessible

    inaccessible

    inaccessible

    赋值兼容规则:

    • 公有派生类对象可以当作基类的对象使用,隐含转换为基类对象
    • 派生类的对象可以初始化基类的引用,派生类的指针可以隐含转换为基类的指针,以上规则反过来不适用
    • 通过基类对象名、指针只能访问从基类继承的成员
    • 公有派生类对象可以当作基类的对象使用,隐含转换为基类对象,即对象切片
    • 派生类的对象可以初始化基类的引用,派生类的指针可以隐含转换为基类的指针,以上规则反过来不适用
    • 通过基类对象名、指针只能访问从基类继承的成员

    派生类的构造与析构:

    • 默认情况下派生类需定义自己的构造函数,不会继承基类的构造函数
    • 可主动使用using关键字继承基类构造函数,但只能初始化基类成员,其新增成员可使用类内初始值完成初始化
    • 派生类构造函数执行顺序为:1. 基类构造函数;2. 派生类初始化列表中其余项;3. 构造函数体
    • 若基类构造函数有参或无默认参数,需显式被调用,否则可不显式调用基类构造函数
    • 基类的析构函数不能被继承,若派生类需要,可自行定义析构函数
    • 派生类析构函数中不需要显式调用基类析构函数,编译器会隐式调用
    • 派生类对象析构时,先执行派生类析构函数再执行基类析构函数,此过程与构造过程相反

    多重继承与虚继承:

    • 当派生类的继承自多个基类,这些基类又有公共基类,则派生类对象中会存在多份公共基类成员,存在冗余
    • 访问公共基类成员产生二义性,无法通过编译,使用虚基类机制消除二义性
    • 派生类构造时不仅要对直接基类进行初始化,也要负责对虚基类初始化
    • 虚继承引入额外运行开销,尽量避免虚继承,或者虚基类中尽量不存放任何数据

    多态:

    • 多态是指操作同一接口具有表现多种形态的能力,或者同样的数据交给不同类型的实体处理时导致不同的行为
    • 基于多态性,相同的程序能够处理多种类型的对象。同一接口,多种实现
    • 多态通过运行时动态绑定实现,在程序运行期间将一个函数标识符和函数地址绑定在一起
    • virtual关键字标识成员函数为虚函数,虚函数是实现动态绑定的基础

    虚函数

    • 对需要多态特性的成员函数添加virtual关键字进行说明,虚函数经过派生类重写后就可以实现多态
    • 虚函数不允许是静态的成员函数与构造函数
    • 派生类重写基类虚函数时,可以添加override关键字标识。若基类某个函数不希望被重写,可使用final明确拒绝
    • 基类的析构函数最好都声明为虚函数

    虚表与动态绑定

    • 若某个类声明了虚函数,则该类及其所有直接、间接派生类,都各有一张虚表
    • 虚表中有当前类的各个虚函数入口地址,包括自身新增和继承而来的
    • 有虚表的类的每个对象拥有一个指向当前类的虚表的指针
    • 编译器遇到虚函数调用时,产生虚表指针+表偏移的指令来确定函数地址,以实现动态绑定
    • 对象虚表指针赋值发生在当前类的构造函数初始化列表执行前
    • 禁止在父类的构造函数与析构函数内调用虚函数,在父类构造和析构函数内,对象被下降为父类类型对待

    RTTI(运行时类型识别)

    • 使用dynamic_cast可以在继承体系中完成类型双向转换,并运行时检查类型转换合法性
    • 使用typeid运算符获取基类类型指针或引用所指向对象的实际类型
    • 若继承树中没有虚函数存在,dynamic_cast无法通过编译,typeid运算符会给出静态绑定结果
    • 不需要RTTI时,使用-fno-rtti选项关闭

    抽象类

    • 基类无需或无法给出定义的虚函数可被声明为纯虚函数,具体实现留给派生类定义
    • 拥有纯虚函数的类称为抽象类,抽象类无法被实例化。析构函数不能为纯虚函数
    • 抽象类用于代码抽象和设计的目的,以约束不同的具体实现类对外表现出相同的接口

    RAII(Resource acquisition is initialization,资源获取即初始化)

    • 是以对象管理资源,在栈展开过程当中,封装了资源的对象会被自动调用其析构函数以释放资源
    • RTTI,不断地回溯,以及较多的cache miss是导致异常性能较低的主要原因,另外,异常还会导致代码膨胀,代码可读性较低。所以异常应该在那些非常必要的场景中使用,我们应该尽量避免用C++的异常机制

    三、模板编程

    static_cast

    • static_cast强制转换只会在编译时检查,但没有运行时类型检查来保证转换的安全性
    • 用于类层次结构中基类和子类之间指针或引用的转换:进行上行转换(子类->父类)是安全的;进行下行转换(父类->子类)时,由于没有动态类型检查,所以是不安全的
    • 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证
    • 把void*指针转换成目标类型的指针,此种转换方式不安全
    • 把任何类型的表达式转换成void类型
    • 将enum class值转化为整数或者浮点数
    • 转换为右值引用

    dynamic_cast

    • new_type 必须是一个指针或引用或“指向void* 的指针”
    • 如果 new_type 是指针,则expression 的类型必须是指针,如果 new_type 是引用,则expression为左值。 如果转型失败会返回nullptr(转型对象为指针时)或抛出异常(转型对象为引用时)。
    • dynamic_cast 使用RTTI来进行类型安全检查,因此dynamic_cast 存在一定的效率损失

    const_cast

    • new_type 必须是一个指针、引用或者指向对象类型成员的指针。
    • const_cast用于去除除对象的const或者volatile属性。

    reinterpret_cast

    • new_type必须是一个指针、引用、算术类型、函数指针或者成员指针。
    • 其转换结果与编译平台息息相关,不具有可移植性,因此在一般的代码中不常见到它。
    • reinterpret_cast 常用的一个用途是转换函数指针类型,即可以将一种类型的函数指针转换为另一种类型的函数指针,但这种转换可能会导致不正确的结果。
    • reinterpret_cast就是对指针类型进行重新解释,非常类似纯C风格的指针类型强转

    类型转换操作符

    • 类型转换操作符(type conversion operator)是一种特殊的类成员函数,它定义将类类型值转变为其他类型值的转换。转换操作符在类定义体内声明,在保留字 operator 之后跟着转换的目标类型。

    函数原型

    • 转换函数必须是成员函数,不能指定返回类型,并且形参表必须为空;返回值是隐含的,返回值是与转换的类型相同的,即为上面原型中的T2
    • T2表示内置类型名(built-in type)、类类型名(class type)或由类型别名(typedef)定义的名字;对任何可作为函数返回类型的类型(除了 void 之外)都可以定义转换函数,一般而言,不允许转换为数组或函数类型,转换为指针类型(数据和函数指针)以及引用类型是可以的
    • 转换函数一般不应该改变被转换的对象,因此转换操作符通常应定义为 const 成员
    • 支持继承,可以为虚函数
    • 只要存在转换,编译器将在可以使用内置转换的地方自动调用它

    函数模板

    template<typename T, size_t N>
    void init(T(&data)[N])
    {
        for (size_t i = 0; i < N; ++i) {
            data[i] = 0;
        }
    }

    类模板

    template<typename T>
    class Operator {
        public:
            int compare(const T& a, const T& b) { … }
            void swap(T& a, T& b) { … }
    };
    

    成员函数模板

    class Operator {
        public:
            template<typename T>
            int compare(const T& a, const T& b){ … }
    };
    

    可变参数模板

    // 展开函数
    template<typename Head, typename... Args>
    void print(const Head& t, const Args&... args){
        std::cout << t << " ";
        return print(args...);
    }// 递归终止函数
    
    void print() {}
    

    四、C++11新特性

    左值与右值:

    • 左值是在作用域内表达式运行结束后依然存在的持久对象,它有确切的地址能够在程序中被访问
    • 右值指表达式结束后就不再存在的临时对象,C++进一步划分为纯右值与将亡值
    • 纯右值指字面量或者求值结果相当于字面量或匿名临时变量、非引用返回的临时变量等,它没有地址可供访问
    • 将亡值指即将被销毁,却能够被移动的值,它有地址但是仅编译器能够操作,程序不可访问,它的资源可以被复用

    右值引用与左值引用:

    • 使用std::move方法将左值转为右值

    移动语义:

    • 定义移动构造和移动赋值函数避免无意义的拷贝操作

    完美转发:

    • 右值引用是左值,使用std::forward方法解决右值无法转发问题(引用坍缩)

    Lambda表达式

    [捕获列表](参数列表) mutable(可选) 异常属性 -> 返回类型 {
        // 函数体
    }
    

    捕获列表分为以下几种:1)值捕获;2)引用捕获;3)隐式捕获;4)表达式捕获
    它们的语法形式如下:

    • 1)[ ] 空捕获列表
    • 2)[name1, &name2] 按值捕获name1,按引用捕获name2
    • 3)[this] 捕获当前对象,使表达式可以访问该对象所有成员
    • 4)[&] 引用捕获所有变量, 让编译器自行推导捕获列表
    • 5)[=] 值捕获所有变量, 让编译器执行推导引用列表
    • [=, &x] 默认按值捕获所有变量,除了变量x按照引用捕获
    • [&, x] 默认按照引用捕获所有变量,除了变量x按值捕获
    • 8) [name1=expr1, name2=expr2, …] 捕获一系列表达式

    闭包与std::function

    • Lambda表达式捕获列表不为空时,本质上是一个与仿函数相似的闭包类型
    • Lambda表达式捕获列表为空时,可以转换为函数指针进行传递
    • std::function是一种类型安全的通用、多态的函数封装,可对任何可调用对象进行打包
    • std::function对象可以存储、复制及调用,提供了闭包的统一封装形式

    初始化列表:

    • 如果函数的参数个数未知,但是类型相同,可以使用初始化列表类型作为函数形参
    • 初始化列表中的元素永远为常量值
    class MyIntArray {
    public:
        MyIntArray(std::initializer_list<int> values)
            : MyIntArray(values.size())
        {
            int count = 0;
            for (auto& elem : values) {
                data_[count++] = elem;
            }
        }
        MyIntArray(size_t size) : data_(new int[size]), size_(size) {}
        int &operator[](size_t index)
        {
            assert(index < size_);
            return data_[index];
        }
        ~MyIntArray()
        {
            if (data_) { delete [] data_; }
        }
        size_t Length() { return size_; }
    private:
        int *data_;
        size_t size_;
    };
     int main()
    {
        MyIntArray arr = {1, 3, 4, 6, 7};
        for(size_t i = 0; i < arr.Length(); i++) {
            std::cout << arr[i] << std::endl;
        }
        return 0;
    }

    内存模型

    • memory_order_seq_cst:顺序一致性模型,是默认的选项,保证所有原子操作的顺序一致性
    • memory_order_acquire:用来修饰一个读操作,表示在本线程中,所有后续的内存操作都必须在本条原子操作完成后执行。
    • memory_order_release:用来修饰一个写操作,表示在本线程中,所有之前的内存操作完成后才能执行本条原子操作。
    • memory_order_acq_rel:同时包含memory_order_acquire和memory_order_release标志。
    • memory_order_consume:只保证和p变量相关的数据具有顺序关系,而不保证变量a的顺序。
    • memory_order_relaxed:松弛模型,不同线程的原子操作顺序任意,可能会出现乱序。但是单个变量的读写是原子操作。

    memory barrier(fence):

    • 由于有了cache的存在,很多操作不用写到内存就可以继续执行后面的操作,为了保证某些操作是写入到内存才执行的,就引入了内存栅栏。
    • 内存栅栏的作用是:所有在内存栅栏之前的内存操作,都要写入到内存中。内存栅栏其实是显示的在程序的一些执行点上保证顺序一致性。

    五、STL标准库容器

    unique_ptr

    • 独占所指向的对象
    • 某个时刻只能有一个unique_ptr指向一个给定的对象
    • 当unique_ptr销毁时,它所指向的对象也被销毁

    shared_ptr

    • 内部使用引入计数的方式管理资源,如果资源的计数值变为0,则释放资源

    weak_ptr

    • 是一种不控制所指向对象生存期的智能指针
    • 它指向由一个shared_ptr管理的对象
    • 不会改变绑定的shared_ptr的引用计数
    • 即使有weak_ptr指向对象,对象还是会被释放

    六、并发编程

    C++11 新标准中引入了几个头文件来支持多线程编程:

    • <atomic>:主要声明了两个类, std::atomic 和 std::atomic_flag,另外还声明了一套 C 风格的原子类型和与 C 兼容的原子操作的函数。
    • <thread>:主要声明了 std::thread 类,另外 std::this_thread 命名空间也在该头文件中。
    • <mutex>:主要声明了与互斥量(mutex)相关的类,包括 std::mutex 系列类,std::lock_guard, std::unique_lock, 以及其他的类型和函数。
    • <condition_variable>:主要声明了与条件变量相关的类,包括 std::condition_variable 和 std::condition_variable_any。
    • <future>:主要声明了 std::promise, std::package_task 两个 Provider 类,以及 std::future 和 std::shared_future 两个 Future 类,另外还有一些与之相关的类型和函数,std::async() 函数就声明在此头文件中。
    template<typename T>
    class threadsafe_queue {
        public:
            threadsafe_queue() = default;
            void push(T new_value) {
                std::lock_guard<std::mutex> lk(mut);
                data_queue.push(std::move(new_value));
                data_cond.notify_one();
            }
            void wait_and_pop(T& value) {
                std::unique_lock<std::mutex> lk(mut);
                data_cond.wait(lk,[this]{return !data_queue.empty();});
                value = std::move(data_queue.front());
                data_queue.pop();
            }
            bool try_pop(T& value) {
                std::lock_guard<std::mutex> lk(mut);
                if(data_queue.empty()) { return false; }
                value = std::move(data_queue.front());
                data_queue.pop();
            }
            bool empty() const {
                std::lock_guard<std::mutex> lk(mut);
                return data_queue.empty();
            }
        private:
            mutable std::mutex mut;
            std::queue<T> data_queue;
            std::condition_variable data_cond;
    };
    

    七、C++ 20

    Modules

    定义:
    一组源文件与编译单元(translation units)独立编译
    一个Module包含一个Module Interface Unit与若干个Module Implementation Unit
    特点:
    C++的库和程序的组件化的现代化解决方案
    与头文件的方法比有以下优点
    编译速度更快,modules预先编译,只编译一次
    不需要依赖预处理器
    不需要头文件里面那些丑陋的东西,如防重复引用宏
    不易出错,头文件会有循环依赖,间接引用的问题

    Coroutines

    定义:

    • 协程是一种可以被中断、恢复的函数

    状态:

    • 目前C++20的Coroutines是一个Coroutines框架,开发者需要根据框架写相关代码,完全支持大概在C++23

    用法:被以下关键字之一修饰

    • co_wait: 挂起协程, 等待其它计算完成
    • co_return:从协程返回
    • co_yield:弹出一个值, 挂起协程, 下一次调用继续协程的运行

    与多线程的区别:

    • 协程的数量理论上可以是无限个,而且没有线程之间的切换动作,执行效率比线程高。
    • 协程不需要“锁”机制,即不需要lock和release过程,因为所有的协程都在一个线程中。
    • 相对于线程,协程更容易调试debug,因为所有的代码是顺序执行的。

    应用场景:生成器,异步I/O,延迟计算

    Concept

    定义:concept 是一组约束条件,必须定义在namespace 空间
    Constraints:约束是一串逻辑操作指定模板参数的需求,常用require表达式表示
    三种约束类型:Conjunctions,Disjunctions,atomic constraints

    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值