美文网首页
C++ 11(1) ---- C++11 基础

C++ 11(1) ---- C++11 基础

作者: 特立独行的佩奇 | 来源:发表于2023-02-27 19:45 被阅读0次

    STL 基础知识

    STL 基础知识 说明
    常用头文件 万能头文件 bits/stdc++.h
    迭代器用法 四种迭代器类型
    统一初始化 { } 初始化
    enum class 强类型枚举
    继承构造和委托构造 C++11 面向对象增强
    override final关键字 显式虚函数重载
    default delete关键字 显式使用,禁用默认函数

    常用头文件

    STL 中的常用的算法和容器包含在下面的头文件中

    #include <algorithm>
    #include <deque>
    #include <functional>
    #include <iterator>
    #include <vector>
    #include <list>
    #include <map>
    #include <memory>
    #include <numeric>
    #include <queue>
    #include <set>
    #include <stack>
    #include <utility>
    

    为了调试的方便,节约时间,可以使用万能头文件(bits/stdc++.h)(内容较多,这里不再给出)

    迭代器

    迭代器是容器类中一个成员变量,用于访问容器中的内容,相当于容器和操纵容器算法之间的中介,通过迭代器可以读写容器中的元素,这一点类似于指针,可以认为数组也是一种容器,数组的迭代器就是指针
    迭代器按照定义方式分为四种:


    迭代器类型.jpg
    {
        vector<uint32_t> mv{100,200,300,400};
        vector<uint32_t>::iterator iter;  // forward iter 
        vector<uint32_t>::const_iterator itera; // const forward iter 
        vector<uint32_t>::reverse_iterator iterb; // backward iter
        vector<uint32_t>::const_reverse_iterator iterc; //const backward iter
    
        cout << "forward vector mv element" << endl;
        for (iter = mv.begin(); iter != mv.end(); iter++) {
            cout << " " << *iter;
            *iter *= 2;
        }
        cout << endl;  // 100 200 300 400 通过迭代器修改成员变量
    
        cout << "backward vector mv element" << endl;
        for (iterb = mv.rbegin(); iterb != mv.rend(); iterb++) {
            cout << " " << *iterb;
        }
        cout << endl; // 800 600 400 200
    
        iter = mv.begin();
        cout << "iter[3]: " << iter[2] << endl; // 随机访问迭代器支持
    }
    

    迭代器按照功能可以分为 输入,输出,正向,双向,随机访问 五种,不同的容器,支持的迭代器功能不同;比如 排序算法就需要通过随机访问迭代器访问容器中的元素,因此有的容器就不支持排序算法

    • 输入迭代器
      可以用于读取容器内的元素,不保证能支持容器的写入操作,输入迭代器支持的 == 和 != 用于比较两个迭代器,p++ 和 ++p 指向下一个元素,p 取迭代器指向元素的值,箭头操作符:(p).member 同义
      输入迭代器只能顺序使用,一旦输入迭代器自增了,就无法检查它之前的元素,标准库istream_iterator 类型就是输入迭代器

    • 输出迭代器
      输出迭代器是和输入迭代器互补的迭代器,可以向容器写入元素,但是不保证能支持读取元素的内容,标准库的 ostream_iterator 就是输出迭代器

    • 正向迭代器
      假设 p 是一个正向迭代器,则 p 支持以下操作:++p,p++,*p,此外,两个正向迭代器可以互相赋值,还可以用 == 和 != 运算符进行比较

    • 双向迭代器
      双向迭代器具有正向迭代器的全部功能,除此之外,若 p 是一个双向迭代器,则 --p 和 p-- 都是有定义的;--p使得 p 朝和++p相反的方向移动

    • 随机访问迭代器
      随机访问迭代器具有双向迭代器的全部功能,若 p 是一个随机访问迭代器,i 是一个整型变量或常量,则 p 还支持以下操作:
      p += i: 使得 p 往后移动 i 个元素
      p -= i: 使得 p 往前移动 i 个元素
      p + i: 返回 p 后面第 i 个元素的迭代器
      p - i: 返回 p 前面第 i 个元素的迭代器
      p[i]: 返回 p 后面第 i 个元素的引用

    两个随机访问迭代器 p1、p2 还可以用 <、>、<=、>= 运算符进行比较,p1 < p2 的含义是 p1 经过若干次++操作后才会和 p2 相等

    两个随机访问迭代器p1、p2,表达式 p2 - p1 也是有定义的,表示p2 和 p1所指向的元素的序号之差

    不同的容器支持的迭代器类型:


    容器迭代器类型.jpg

    STL 中存在三个用于操作迭代器的函数模板:

    • advance(p, n):使迭代器 p 向前或向后移动 n 个元素
    • distance(p, q):计算两个迭代器之间的距离,即迭代器 p 经过多少次 + + 操作后和迭代器 q 相等。如果调用时 p 已经指向 q 的后面,则这个函数会陷入死循环
    • iter_swap(p, q):用于交换两个迭代器 p、q 指向的值

    下面示例中,双向迭代器不支持比较的操作

    {
        list<uint32_t> ml{1, 2, 3, 4, 5, 6, 7};
        list<uint32_t>::iterator iter;
    
        cout << "forward list ml element" << endl;
        for (iter = ml.begin(); iter != ml.end(); ++iter) {
            cout << " " << *iter;
        }
        cout << endl; // 1 2 3 4 5 6 7
    
        /*for (iter = ml.begin(); iter < ml.end(); ++iter) {
            cout << " " << *iter;
        }*/  //双向迭代器不支持 小于符号的比较
    
        iter = ml.begin();
        advance(iter, 3);
        cout << "advence 3 offset element:" << *iter << endl;
    
        advance(iter, -1);
        cout << "advence -1 offset element:" << *iter << endl;
    }
    
    {
        list<uint32_t> ml{ 1, 2, 3, 4, 5, 6, 7 };
        list<uint32_t>::iterator iter;
        iter = ml.begin();
    
        cout << "distance :" << distance(iter, ml.end()) << endl; // 7 
    
    
        list<uint32_t>::iterator itera = ml.end();
        --itera;
        iter_swap(iter, itera);
        for (auto temp : ml) {
            cout << " " << temp; // 7 2 3 4 5 6 1 将最后一个值和第一个值交换
        }
    }
    

    统一初始化

    统一初始化就是使用大括号初始化的方式,例如:

    {
        int value[]{1, 2, 3, 4, 5};   // OK
        //int value[] = { 1, 2, 3 }; // OK
        for (auto temp : value) {
            cout << " " << temp;
        }
        cout << endl;
    
        vector<uint32_t> v({100,200,300,400});
        for (auto temp: v) {
            cout << " " << temp;
        }
        cout << endl;
    
    
        demoObject{"demo",0,0}; // 等同于 demoObject("demo", 0, 0)
    }
    

    编译器看到{ a,b,c} 会形成一个initializer_list,它关联到一个array<T,n>,调用构造函数的时候,该array 内的元素会被编译器分解逐一传递给函数,但若函数的参数就是 initializer_list,便不会逐一分解,而是直接调用该参数的构造函数

    所有的标准容器都有以initializer_list为参数的构造函数

    initializer_list 常常用于构造函数和成员函数的参数,用于不确定长度但是相同类型参数的情况

    template <typename T>
    class demotemplate {
    public:
        inline demotemplate(initializer_list<T> v) :mvec(v) {
            cout << "initializer_list size: " << v.size() << endl;
            cout << "begin element: " << *(v.begin()) << endl;
            cout << "end element: " << *(v.end() - 1) << endl;
        };
        void append(initializer_list<T> v);
        void dumpdemotemplate();
    private:
        vector<T> mvec;
    };
    
    
    template <typename T>
    void demotemplate<T>::append(initializer_list<T> v) {
        mvec.insert(mvec.end(), v.begin(), v.end());
    }
    
    template <typename T>
    void demotemplate<T>::dumpdemotemplate() {
        for (auto p : mvec) {
            cout << " " << p;
        }
        cout << endl;
    }
    

    调用结果:

    {
        demotemplate<uint32_t> a{ 1,2,3,4,5 }; // begin element 1 end element 5
        a.append({ 10,11,12,14 });
        a.dumpdemotemplate(); //1,2,3,4,5,10,11,12,14
    
        demotemplate<uint32_t> b = { 11,12,13,14,15 }; // begin element 11 end element 15
        b.append({ 20,21,22,23 });
        b.dumpdemotemplate(); // 11,12,13,14,15,20,21,22,23
    }
    

    initializer_list 和重载构造函数之间的关系:

    1. 构造函数中不带 initializer_list,大括号和小括号的意义一致
    2. 如果构造函数中带有initializer_list形参,采用大括号初始化语法会强烈优先匹配带有initializer_list形参的重载版本,而其他更精确匹配的版本可能没有机会被匹配;
    3. 空大括号构造一个对象时,表示没有参数,会匹配无参构造函数而不是带initializer_list 的构造函数

    enum class

    C++ 11 定义了enum class,对比于C传统的 enum,包含了命名空间和指定存储类型的功能,下面的 demo 展示了 传统enum 和 enum class 的区别

    enum class CWeekday: uint32_t {
        Mon,
        Tue,
        Wed,
        Thr,
        Fri,
        Sat,
        Sun,
    };
    
    enum class CWeekdayNew : uint32_t {
        Mon,
        Tue,
        Wed,
        Thr,
        Fri,
        Sat,
        Sun,
    };
    
    {
        weekday day = Thr;
        uint32_t a = day; // OK
    
        uint32_t b = 4;
        day = static_cast<weekday>(b);
        cout << "convert b as enum : " << day << endl;
    }
    
    {
        CWeekday day = CWeekday::Mon;
        CWeekdayNew Nday = CWeekdayNew::Mon; // OK 
    
        //uint32_t a = day; // error Can't implicit cast
        uint32_t a = static_cast<uint32_t>(day); //success explict cast to uint32_t
    
    
        uint32_t b = 5;
        day = static_cast<CWeekday>(b);  //success explict cast to modern enum
    }
    

    主要区别:

    • enum 不限制其中枚举值的命名空间,enum class 的枚举值包含在该enum 的命名空间中
    • enum class 可以手动指定其中枚举值的存储类型,默认是32位的整形
    • enum 可以隐式转换到整形,enum class 必须显示转换
    • 整形转换到 enum 和 enum class 都必须显示转换
    • enum class 可以前向声明

    继承构造和委托构造

    委托构造C++11 引入,指的是构造函数可以通过初始化参数列表调用同一个类的其他构造函数,可以简化代码的书写

    enumClassDemo(const char* str, uint32_t size, double f);
    enumClassDemo(const char* str);
        
    
    enumClassDemo::enumClassDemo(const char* str, uint32_t size, double f) {
        cout << " construtct enumClassDemo 3 para" << endl;
    }
    
    
    enumClassDemo::enumClassDemo(const char* str) :enumClassDemo(str, 10, 1.0f) {
        cout << " construtct enumClassDemo signle para" << endl;
    }
    

    注意如果使用委托构造,初始化参数列表只能包含一个同一个类的其他的构造函数,不能再包含其他变量的初始化

    继承构造

    子类为了完成基类的初始化,在C++11 之前,需要在初始化参数列表调用基类的构造函数,如果基类有多个构造函数,子类也需要实现多个个基类对应的构造函数

    class NewClass : public enumClassDemo{
    public:
        using enumClassDemo::enumClassDemo;
        //inline NewClass(uint32_t* p) { (void*)p} // error already have
        ~NewClass() = default;
    };
    

    通过 using Base::Base 把基类构造函数继承到派生类中,不再需要书写多个派生类构造函数来完成基类的初始化,C++11 标准规定,继承构造函数与类的一些默认函数(默认构造、析构、拷贝构造函数等)一样,是隐式声明,如果一个继承构造函数不被相关代码使用,编译器不会为其产生真正的函数代码,这样比总是需要定义派生类的各种构造函数更加节省目标代码空间

    override 和 final

    override 用于修饰子类成员函数,override 关键字将显式的告知编译器进行重写,编译器将检查基函数是否存在这样的虚函数,否则将无法通过编译
    final 在用于修饰子类成员函数时,表示此函数不能再进行重写

    class  chidClassdemo : public newkeyWordDemo {
    public:
        using newkeyWordDemo::newkeyWordDemo;
    
        inline virtual void testfunction() override {
    
    
        }
    
        /*inline void testsecondfunction() override {
    
        }*/ //compilre error 父类中没有此虚函数
    
    };
    

    final 关键字用于修饰类的声明时,表示该类不能再用于继承,使用形式如下:

    class newkeyWordDemo final {
    public:
        newkeyWordDemo();
        ~newkeyWordDemo() = default;
        inline virtual void testfunction() {
            mstr = "hello";
        };
        static void testnewkeyWordDemo();
    private:
        string mstr;
    };
    
    显式使用或者禁用默认构造函数

    在传统 C++ 中,如果定义时没有提供,编译器会默认为对象生成默认构造函数,拷贝构造,赋值构造以及析构函数
    另外,C++ 也为所有类定义了诸如 new delete 这样的运算符,当程序员有需要时可以重载这部分函数

    这就引发了一些需求:无法精确控制默认函数的生成行为,例如禁止类的拷贝时,必须将赋值构造函数与赋值算符声明为 private;尝试使用这些未定义的函数将导致编译或链接错误,这是一种非常不优雅的方式
    C++11 允许显式的声明采用或拒绝编译器自带的函数

    class newkeyWordDemo {
    public:
        newkeyWordDemo();
        ~newkeyWordDemo() = default;
        newkeyWordDemo& operator=(const newkeyWordDemo& f) = delete;
        inline virtual void testfunction() {
            mstr = "hello";
        };
        static void testnewkeyWordDemo();
    private:
        string mstr;
    };
    

    相关文章

      网友评论

          本文标题:C++ 11(1) ---- C++11 基础

          本文链接:https://www.haomeiwen.com/subject/idckldtx.html