C++ 11新特性(二)

文章目录

前言

8.新的类功能?

9 可变参数模板

10 Lambert 表达式

10.1 C++98 的一个例子        

10.2 lambert 表示式

1. lambda表达式各部分说明

2.捕获列表说明

10.3 函数对象与lambda表达式

总结


前言

接上第一部分继续讲解c++ 11 新特性


序号顺序接上

8.新的类功能?

 默认成员函数
原来C++类中,有6个默认成员函数:
1. 构造函数
2. 析构函数
3. 拷贝构造函数
4. 拷贝赋值重载
5. 取地址重载
6. const 取地址重载
最后重要的是前4个,后两个用处不大。默认成员函数就是我们不写编译器会生成一个默认的。C++11 新增了两个:移动构造函数和移动赋值运算符重载。针对移动构造函数和移动赋值运算符重载有一些需要注意的点如下:如果你没有自己实现移动构造函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个。那么编译器会自动生成一个默认移动构造。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造,如果实现了就调用移动构造,没有实现就调用拷贝构造。如果你没有自己实现移动赋值重载函数,且没有实现析构函数 、拷贝构造、拷贝赋值重载中的任意一个,那么编译器会自动生成一个默认移动赋值。默认生成的移动构造函数,对于内置类型成员会执行逐成员按字节拷贝自定义类型成员,则需要看这个成员是否实现移动赋值,如果实现了就调用移动赋值,没有实现就调用拷贝赋值。(默认移动赋值跟上面移动构造完全类似)如果你提供了移动构造或者移动赋值,编译器不会自动提供拷贝构造和拷贝赋值。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

// 在C++11中,标准库在中提供了一个有用的函数std::move,
// std::move并不能移动任何东西,它唯一的功能是将一个左值引用强制转化为右值引用,
// 继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::
// move基本等同于一个类型转换:static_cast<T&&>(lvalue);
 
class Person{

    public:
        Person(const char*name="",int age=0):_name(name),_age(age)
        {

        }

        Person(const Person&p){
            this->_age=p._age;
            this->_name=p._name;
        }

        Person operator=(const Person &p){
            if(this!=&p){
                _name=p._name;
                _age=p._age;
            }

            return *this;
        }

        ~Person(){



        }



    private:
        string _name;
        int _age;

};





int main()
{

    Person s1;
    Person s2=s1;
    Person s3=move(s1);
    Person s4;
    s4=move(s2);
    cout<<endl;




system("pause");
return 0;
}

 类成员变量初始化
C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化,这
个我们在雷和对象默认就讲了,这里就不再细讲了。

强制生成默认函数的关键字default:
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原
因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以
使用default关键字显示指定移动构造生成。

禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,并且只声明补丁已,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

// 在C++11中,标准库在中提供了一个有用的函数std::move,
// std::move并不能移动任何东西,它唯一的功能是将一个左值引用强制转化为右值引用,
// 继而可以通过右值引用使用该值,以用于移动语义。从实现上讲,std::
// move基本等同于一个类型转换:static_cast<T&&>(lvalue);
 
class Person{

    public:
        Person(const char*name="",int age=0):_name(name),_age(age)
        {

        }

        Person(const Person&p){
            this->_age=p._age;
            this->_name=p._name;
        }

        Person operator=(const Person &p){
            if(this!=&p){
                _name=p._name;
                _age=p._age;
            }

            return *this;
        }

        Person(Person&&p)=default;

        // Person(Person &&p)=delete;


        ~Person(){



        }



    private:
        string _name;
        int _age;

};





int main()
{

    Person s1;
    Person s2=s1;
    Person s3=move(s1);
    Person s4;
    s4=move(s2);
    cout<<endl;




system("pause");
return 0;
}

继承和多态中的final与override关键字
        

  • override:保证在派生类中重写的虚函数,与基类的虚函数有相同的签名
  • final:阻止类的进一步派生和虚函数的进一步重写

比如下面的代码,加了override,明确表示派生类的这个虚函数是重写基类的,如果派生类与基类虚函数的签名不一致,编译器就会报错,下面的代码在实际的编译过程会报错,如下:

        

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;



class Base{

    public:
        virtual void printNum(int x){
            cout<<"hello"<<endl;
        }


};

class Son:public Base{

    public:
        //不报错
    //   void  printNum(int x)  override {
    //         cout<<"son hello"<<endl;
    //     }

        //报错了
        void printNum(int x,int y) override{
            cout<<"son hello"<<endl;
        }


};

void test01(){
    
    Base *b=new Son();
    int x=2;
    b->printNum(x);
}




int main()
{

test01();




system("pause");
return 0;
}

如果不希望某个类被继承,或不希望某个虚函数被重写,则可以在类名和虚函数后加上 final 关键字,加上 final 关键字后,再被继承或重写,编译器就会报错。如下:

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;



class Base{

    public:
        virtual void printNum(int x) final {
            cout<<"hello"<<endl;
        }


};

class Son:public Base{

    public:
        //报错final 生命的虚函数不能被重写
    //   void  printNum(int x)  override {
    //         cout<<"son hello"<<endl;
    //     }

        


};


class Base1 final{

    public:
        virtual void printNum(int x) final {
            cout<<"hello"<<endl;
        }


};

//报错声明为final 的类不能被继承
class Son1:public Base1{

    public:
        //报错final 生命的虚函数不能被重写
    //   void  printNum(int x)  override {
    //         cout<<"son hello"<<endl;
    //     }
    void func(){
        cout<<"hello"<<endl;
    }
        


};




void test01(){
    
    Base *b=new Son();
    int x=2;
    b->printNum(x);
}




int main()
{

// test01();




system("pause");
return 0;
}

9 可变参数模板

            C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板,相比
C++98/03,类模版和函数模版中只能含固定数量的模版参数,可变模版参数无疑是一个巨大的改
进。然而由于可变模版参数比较抽象,使用起来需要一定的技巧,所以这块还是比较晦涩的。现
阶段呢,我们掌握一些基础的可变参数模板特性就够我们用了,所以这里我们点到为止,以后大
家如果有需要,再可以深入学习。

Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数。
template <class ...Args>
void ShowList(Args... args)
{}

 上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数
包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,
只能通过展开参数包的方式来获取参数包中的每个参数,这是使用可变模版参数的一个主要特点,也是最大的难点,即如何展开可变模版参数。由于语法不支持使用args[i]这样方式获取可变参数,所以我们的用一些奇招来一一获取参数包的值。递归函数方式展开参数包

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
//递归终止函数
template<class T>
void ShowList(const T & t){
    cout<<t<<endl;
}
//展开函数
template<class T,class ...Args>
void ShowList(T value,Args ...args){
    cout<<value<<" ";
    ShowList(args...);

}


void test01(){
    ShowList(1);
    ShowList(1,'A');
    ShowList(1,'A',string("sort"));
}



int main()
{
test01();
system("pause");
return 0;
}

 逗号表达式展开参数包

这种展开参数包的方式,不需要通过递归终止函数,是直接在expand函数体中展开的, printarg不是一个递归终止函数,只是一个处理参数包中每一个参数的函数。这种就地展开参数包的方式实现的关键是逗号表达式。我们知道逗号表达式会按顺序执行逗号前面的表达式

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组, {(printarg(args), 0)...}将会展开成((printarg(arg1),0),(printarg(arg2),0), (printarg(arg3),0), etc... ),最终会创建一个元素值都为0的数组int arr[sizeof...(Args)]。由于是逗号表达式,在创建数组的过程中会先执行逗号表达式前面的部分printarg(args)打印出参数,也就是说在构造int数组的过程中就将参数包展开了,这个数组的目的纯粹是为了在数组构造的过程展开参数包。

#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

//递归终止函数
template<class T>
void ShowList(const T & t){
    cout<<t<<endl;
}

//展开函数
template<class T,class ...Args>
void ShowList(T value,Args ...args){

    cout<<value<<" ";
    ShowList(args...);

}


void test01(){

    ShowList(1);
    ShowList(1,'A');
    ShowList(1,'A',string("sort"));
}

template <class T>
void PrintArg(T t){
    cout<<t<<" ";
}


//展开函数
template <class ...Args>
void ShowList(Args... args){

    int arr[]={(PrintArg(args),0)...};
    cout<<endl;

}

void test02(){
    ShowList(1);
    ShowList(1,'A');
    ShowList(1,'A',string("sort"));
}




int main()
{

// test01();
test02();






system("pause");
return 0;
}

STL容器中的empalce相关接口函数

template <class... Args>
void emplace_back (Args&&... args)

首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。那么相对insert和
emplace系列接口的优势到底在哪里呢?

#include <iostream>
#include <vector>
#include <list>
#include <algorithm>
using namespace std;

//递归终止函数
template <class T>
void ShowList(const T &t)
{
    cout << t << endl;
}

//展开函数
template <class T, class... Args>
void ShowList(T value, Args... args)
{

    cout << value << " ";
    ShowList(args...);
}

void test01()
{

    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', string("sort"));
}

template <class T>
void PrintArg(T t)
{
    cout << t << " ";
}

//展开函数
template <class... Args>
void ShowList(Args... args)
{

    int arr[] = {(PrintArg(args), 0)...};
    cout << endl;
}

void test02()
{
    ShowList(1);
    ShowList(1, 'A');
    ShowList(1, 'A', string("sort"));
}

void test03()
{

    //pair<int,char> 需要置顶类型 make_pair 是一个函数模版,不需要置顶类型自动推断里面的内容
    list<pair<int, char>> mylist;
    // list<int,char> mylist;

    mylist.emplace_back(10, 'a');
    mylist.emplace_back(20, 'b');
    mylist.emplace_back(make_pair(30, 'c'));
    mylist.emplace_back(make_pair(40, 'd'));
    mylist.push_back({50,'e'});

    for(const auto e:mylist){
        cout<<e.first <<":"<<e.second<<endl;
    }

}



int main()
{

    // test01();
    // test02();
    // test03();
    test04();

    system("pause");
    return 0;
}

10 Lambert 表达式

10.1 C++98 的一个例子        

 在C++98中,如果想要对一个数据集合中的元素进行排序,可以使用std::sort方法

#include <iostream>
#include <vector>
#include <algorithm>

#include <functional>

// functional 函数用于处理greater<int> 等数据库
using namespace std;

void test01()
{

    int array[] = {4, 1, 8, 5, 3, 7, 0, 9, 2, 6};

    //升序排列
    sort(array, array + sizeof(array) / sizeof(array[0]));
    cout << "order up";
    for (const auto i : array)
    {
        
        cout << i << " ";
    }

    //降序排列
     cout << "order down";
    sort(array, array + sizeof(array) / sizeof(array[0]), greater<int>());
    for (const auto j : array)
    {
       
        cout << j << " ";
    }
}

// 如果待排序元素为自定义类型,需要用户定义排序时的比较规则:
struct Goods
{
    string _name;//名字
    double _price;//价格
    int _evaluate;//评价

    Goods(const char *str,double price,int evaluate){
        this->_name=str;
        this->_price=price;
        this->_evaluate=evaluate;
    }


};

class ComparePriceless{

public:
    bool operator()(const Goods&gl,const Goods &gr)
    {
        return gl._price<gr._price;
    }
};

class ComparePricegreater{

public:
    bool operator()(const Goods& gl,const Goods &gr){
        return gl._price>gr._price;
    }


};

void  printGoods(const Goods &good){
    cout<<"name:"<<good._name<<"price:"<<good._price<<"eula:"<<good._evaluate<<endl;
}


void test02(){

vector<Goods> v={ { "apple", 2.1, 5 }, { "banana", 3, 4 }, { "oranges", 2.2,
3 }, { "lemmon", 1.5, 4 } };
sort(v.begin(),v.end(),ComparePriceless());//从小到大进行排序

for( auto i:v){
    printGoods(i);
}

sort(v.begin(),v.end(),ComparePricegreater());
//const 都加上,要么都不加上,要不报错将非常量的引用到常量身上会报错的。
for(const auto j:v){
    printGoods(j);
}


}

//使用lambert 表达式的语法
void test03(){

vector<Goods> v = { { "苹果", 2.1, 5 }, { "香蕉", 3, 4 }, { "橙子", 2.2,
3 }, { "菠萝", 1.5, 4 } };
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price < g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._price > g2._price; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate < g2._evaluate; });
sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2){
return g1._evaluate > g2._evaluate; });

}



int main()
{

    // test01();
    test02();

    system("pause");
    return 0;
}

随着C++语法的发展,人们开始觉得上面的写法太复杂了,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名,这些都给编程者带来了极大的不便。因此,在C++11语法中出现了Lambda表达式。

10.2 lambert 表示式

lambda表达式书写格式:[capture-list] (parameters) mutable -> return-type { statement
}

1. lambda表达式各部分说明

  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
    判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda
    函数使用。
  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
    连同()一起省略
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
    性。使用该修饰符时,参数列表不可省略(即使参数为空)。
  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
    值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。
  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量

NOTE:在lambda函数定义中,参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为
空。因此C++11中最简单的lambda函数为:[]{}; 该lambda函数不能做任何事情。通过下述例子可以看出,lambda表达式实际上可以理解为无名函数,该函数无法直接调用,如果想要直接调用,可借助auto将其赋值给一个变量。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void test01()
{

    //最简单的lambert 表达式,改lambert 没有任何意义
    [] {};

    int a = 3, b = 4;
    //省略参数列表和返回值类型,返回值类型有编译器推断为int
    [=]
    { return a + 3; };

    auto fun1 = [&](int c)
    { b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;

    //各部分都很完善的lambert 表达式
    auto fun2 = [=, &b](int c) -> int
    { return b += a + c; };

    cout << fun2(2) << endl;

    //复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable
    {x*=2;return a+x; };
    cout<<add_x(10)<<endl;
}

int main()
{

    test01();

    system("pause");
    return 0;
}

2.捕获列表说明

        捕捉列表描述了上下文中那些数据可以被lambda使用,以及使用的方式传值还是传引用。

  • [var]:表示值传递方式捕捉变量var
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [&var]:表示引用传递捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

        a. 父作用域指包含lambda函数的语句块
        b. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
        比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量[&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
        c. 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复。

        d. 在块作用域以外的lambda函数捕捉列表必须为空。
        e. 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。
        f. lambda表达式之间不能相互赋值,即使看起来类型相同。

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void test01()
{

    //最简单的lambert 表达式,改lambert 没有任何意义
    [] {};

    int a = 3, b = 4;
    //省略参数列表和返回值类型,返回值类型有编译器推断为int
    [=]
    { return a + 3; };

    auto fun1 = [&](int c)
    { b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;

    //各部分都很完善的lambert 表达式
    auto fun2 = [=, &b](int c) -> int
    { return b += a + c; };

    cout << fun2(2) << endl;

    //复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable
    {x*=2;return a+x; };
    cout<<add_x(10)<<endl;
}

void (*PF)();
void test02(){

auto f1=[]{cout<<"hello world";};
auto f2=[]{cout<<"hello world";};

// f1=f2;//报错

//允许使用lambda 表达式拷贝构造一个新的副本
auto f3(f2);
f3();

//可以将lambda 表达式复制给相同的类型的函数指针
PF=f2;
PF();

}
int main()
{

    // test01();
    test02();

    system("pause");
    return 0;
}

10.3 函数对象与lambda表达式

函数对象,又称为仿函数,即可以想函数一样使用的对象,就是在类中重载了operator()运算符的
类对象。

        

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

void test01()
{

    //最简单的lambert 表达式,改lambert 没有任何意义
    [] {};

    int a = 3, b = 4;
    //省略参数列表和返回值类型,返回值类型有编译器推断为int
    [=]
    { return a + 3; };

    auto fun1 = [&](int c)
    { b = a + c; };
    fun1(10);
    cout << a << " " << b << endl;

    //各部分都很完善的lambert 表达式
    auto fun2 = [=, &b](int c) -> int
    { return b += a + c; };

    cout << fun2(2) << endl;

    //复制捕捉x
    int x = 10;
    auto add_x = [x](int a) mutable
    {x*=2;return a+x; };
    cout<<add_x(10)<<endl;
}

void (*PF)();
void test02(){

auto f1=[]{cout<<"hello world";};
auto f2=[]{cout<<"hello world";};

// f1=f2;//报错

//允许使用lambda 表达式拷贝构造一个新的副本
auto f3(f2);
f3();

//可以将lambda 表达式复制给相同的类型的函数指针
PF=f2;
PF();

}


class Rate{

public:
    Rate(double rate):_rate(rate){}

    double operator()(double money,int year){

        return money*_rate*year;
    }

private:
    double _rate;


};

void test03()
{

    double rate=0.49;
    Rate r1(rate);
   auto ans= r1(10000,2);
   cout<<ans<<endl;

   auto r2=[=](double money,int year) ->double{
    return money*rate*year;
   };
   cout<<r2(10000,2)<<endl;


}






int main()
{

    // test01();
    // test02();
    test03();

    system("pause");
    return 0;
}

从使用方式上来看,函数对象与lambda表达式完全一样。函数对象将rate作为其成员变量,在定义对象时给出初始值即可,lambda表达式通过捕获列表可以直接将该变量捕获到。

实际在底层编译器对于lambda表达式的处理方式,完全就是按照函数对象的方式处理的,即:如
果定义了一个lambda表达式,编译器会自动生成一个类,在该类中重载了operator()。


总结

这里对文章进行总结:剩下还有两部分包装器和函数的重载我们将在下一期的进行讲解。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值