zoukankan      html  css  js  c++  java
  • C++ part7

    C++11新增

    references:
    nowcoder

    auto关键字:编译器可以根据初始值自动推导出类型。但是不能用于函数传参以及数组类型的推导;
    nullptr关键字:nullptr是一种特殊类型的字面值,它可以被转换成任意其它的指针类型;而NULL一般被宏定义为0,在遇到重载时可能会出现问题;
    智能指针:C++11新增了std::shared_ptr、std::weak_ptr等类型的智能指针,用于解决内存管理的问题。
    初始化列表:使用初始化列表来对类进行初始化;
    右值引用:基于右值引用可以实现移动语义和完美转发,消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率;
    新增STL容器array以及tuple;
    std::move(),std::forward()。

    可变参数模板

    references:
    C++可变参数模板

    可变参数模板是可以接受任意参数个数(n (ge) 0)的模板,定义为:

    template<typename ...T>

    对于类来说,可变参数模板只能是最后一个参数,不然无法推导出对应的类型。

    template<typename ... T, typename U>
    class A{}; //错
    template<typename U, typename .. T>
    class A{}; //对

    对于函数,template中的顺序可以随便写,但是函数声明/定义中可变参数模板之恩你是最后一个参数。

    template<typename ... T, typename U>
    void f(U u, T ... t){} //对
    template<typename U, typename .. T>
    void f(T ... t, U u){} //错

    可以使用递归方式展开参数包

    template<typename T, typename ... Args>
    void print(T head, Args ... arg){
        cout << head<<endl;
        print(arg...);
    }

    移动语义

    references:
    [c++11]我理解的右值引用、移动语义和完美转发

    移动语义是为了防止资源浪费所出现的。在编码中,我们可能会遇到一种情况:我们会得到一个临时变量然后传参,那么会调用一次拷贝构造。但是显然,拷贝构造将临时变量的内容复制了一遍,然后临时变量被释放掉了。这样显然是很浪费的。所以C++支持了移动语义。

    实现移动语义:移动构造和移动赋值。

    在移动构造时,我们用右值引用把临时变量传进来,与拷贝构造不同,它并不是重新分配一块新的空间,不是将要拷贝的对象复制过来,而是"偷"了过来,将自己的指针指向别人(右值)的资源,然后将别人的指针修改为nullptr,这一步很重要,如果不将别人的指针修改为空,那么临时对象析构的时候就会释放掉这个资源。这样就减少了复制的消耗。
    C++提供了std::move()函数将左值变成右值,这样vector,set就可以使用移动构造了,提高了效率。但是move(a)之后,a的所有指针均为空,故不要再使用。
    下面为自定义移动构造和移动赋值。

    #include<iostream>
    #include<cstdio>
    #include<algorithm>
    #include<vector>
    #include<cstring>
    using namespace std;
    const int maxn = 2e5 + 5;
    
    class String{
    public:
        String(char *str = 0){   //构造函数
            if(str){
                int len = strlen(str);
                s = new char[len + 1];
                strncpy(s, str, len);
            }
            else{
                str = new char[1];
                str[0] = 0;
            }
        }
    
        String(String &&a) : s(a.s){ //移动构造
            cout << "move" << endl;
            a.s = nullptr;
        }
    
        String& operator = (String &&a){    //移动赋值
            cout << "move=" << endl;
            if(this == &a){
                return *this;
            }
            delete[] s;
            s = a.s;
            a.s = nullptr;
            return *this;
        }
    
        void print();
    private:
        char* s;
    };
    void String::print(){
        cout << s << endl;
    }
    int main() {
        vector<String> a;
        a.push_back(String("test"));	//move
        return 0;
    }
    
    

    完美转发和std::forward()

    references:
    c++11 中的 move 与 forward

    就是之前右值引用中讲的,模板函数形参使用右值引用,那么传进来是什么类型的左值/右值,就是什么类型的左值/右值。
    假如我们有一个重载的函数f(int&)f(int&&),当我们传了一个值给template<typename T>G(T&& a),让模板类G调用f,但是G虽然接受了左值/右值a,但是G是不知道他的类型的,但是a有名字,那么a就会被认为是一个左值,自动调用f(int&)。所以std::forward()的作用是接受一个参数,然后返回该参数本来所对应的类型的引用。

    void f(int &x){
        cout << "left" << endl;
    }
    void f(int &&x){
        cout << "right" << endl;
    }
    
    template<typename T>
    void G(T&& a){
        f(std::forward<T>(a));	//left right
        f(a);	//left left
    }
    int main() {
        int x = 1;
        G(x);
        G(1);
        return 0;
    }
    

    I/O复用之select、poll、epoll

    references:
    细谈Select,Poll,Epoll

    聊聊IO多路复用之select、poll、epoll详解
    epoll底层细节

    select:

    基本原理:
    调用后,select函数阻塞,kernel(内核)就会轮询所有的fd(文件描述符),直到有描述符就绪(可读、可写、except)或者超时(timeout设定时间,立即返回设null),函数返回。当select函数返回后,可以通过遍历fd_set(long数组)找到就绪的描述符。
    优点:
    良好的跨平台支持,几乎所有平台都支持。
    缺点:
    1.单个进程打开的fd数量有限制,大小为FD_SETSIZE,默认32位为1024个,64位为2048个;
    2.采用轮询方式,效率低,约线性时间;
    3.需要维护一个存放fd的fd_set,在用户空间和内核空间复制消耗资源大;
    特点:
    select还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次select时会再次报告该fd。

    poll

    基本原理:
    和select基本相同,只是描述fd集合的方式从fd_set变成了pollfd(链表)。
    优点:
    因为采用链表,所以没有最大连接数的限制。
    缺点:
    1.采用轮询方式,效率低,约线性时间;
    2.pollfd复制于用户空间和内核空间之间,不管这样的复制是否有意义,传递消耗资源大;
    特点:
    poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

    epoll(事件驱动)

    基本原理:
    epoll在内核中申请一个文件系统,用来存储需要监听的socket,并且用红黑树的方式存储,用链表存储就绪的事件。通过epoll_ctl注册fd,一旦该fd就绪(就绪链表中有节点),就会用一个回调函数来激活该fd,epoll_wait便可以收到通知。

    • int epoll_create(int size);
      建立一個 epoll 对象,并传回它的id

    • int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
      事件注册函数,将需要监听的事件和需要监听的fd交给epoll对象

    • int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
      等待注册的事件被触发或者timeout发生

    水平触发(LT)和边缘触发(ET):
    LT(默认):
    当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件。下次调用epoll_wait时,会再次响应应用程序并通知此事件。
    ET:
    当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件。如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。

    优点:
    1.几乎没有数量限制,数量上限只和能打开的fd有关;
    2.不是轮询,而是被动触发,效率提升;
    3.不需要大量在用户空间和内核空间之间拷贝,epoll在用epoll_ctl函数进行事件注册的时候,已经将fd复制到内核中,所以不需要每次都重新复制一次。共享一块内存空间。
    缺点:
    需要很多函数回调,fd很少时性能可能比select/poll差。

    为什么构造函数不能是虚函数

    references:为什么构造函数不能声明为虚函数?
    1、构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。
    2、虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。

  • 相关阅读:
    1121 Damn Single
    1122 Hamiltonian Cycle
    1123 Is It a Complete AVL Tree
    1420. Build Array Where You Can Find The Maximum Exactly K Comparisons
    1124 Raffle for Weibo Followers
    1125 Chain the Ropes
    测试面试题集-2.测试用例设计
    开奖|1024中奖名单公布以及Postman资料分享
    祝心想事成无Bug,1024快乐!
    1024福利|硬核无Bug,码上有红包!
  • 原文地址:https://www.cnblogs.com/KirinSB/p/12549570.html
Copyright © 2011-2022 走看看