C++11 新特性

本文介绍了C++11的新特性,重点讲解了初始化列表如何用于内置类型和自定义类型的初始化,以及auto、typeid和decltype在变量类型推导中的作用。此外,还详细阐述了Lambda表达式的概念和使用方法,包括其书写格式和捕获列表的传参方式。最后,讨论了包装器(如std::function)如何统一不同类型的函数调用。
摘要由CSDN通过智能技术生成

C++11新特性

今天让我们来看看C++的新特性,右值引用智能指针 我会放在后面单独写一篇博客来介绍。
在C++11之前普遍使用的是C++98,C++11于2011年发布的(到现在已经有点年头了👀),在C++98的基础上增加了一些功能。

初始化列表

在以前我们可以使用花括号来初始化数组元素:

 	int a[] = { 1,2,3 };
    int b[5] = { 1 };

但是对于自定义类型,却无法使用这种方法初始化。但是C++11扩大了花括号(初始化列表)的使用范围,现在的花括号能初始化几乎所有类型(内置类型和自定义类型),使用花括号时前面的=也可不添加

内置类型的初始化

注意其中的动态数组开辟,这是C++11新添加的

	//内置类型的变量
    int a1 = 1;
    int a1{ 1 };   //两种写法是等价的
    int b1 = 1 + 2;
    int b1{ 1 + 2 };


    //数组
    int arr1[]{ 1,2,3,4,5 };
    int arr2[5]{ 1,2,3,4,5 };


    //动态数组
    int* arr3 = new int[5]{ 1,2,3,4,5 };

    //标准容器
    vector<int> v{ 1,2,3,4,5 };
    map<int, int> m{ {1,2},{2,2},{3,3},{4,4} };

自定义类型的初始化

struct Person
{
    int _age;
    string _name;

    Person(int age=10,string name="tony")
        :_age(age)
        ,_name(name)
    {
    }
};
    vector<Person> s1 = { {10,"jack"},{20,"peter"} };
    vector<Person> s2 { {10,"jack"},{20,"peter"} };    //两种定义方式都是一样的,都是调用构造函数

初始化列表的原理

初始化列表底层实际上是一个initializer_list,要想对象支持列表初始化,只需要给该类的构造函数添加一个函数参数为initializer_list类型的参数即可。你的花括号里面的内容会存到initializer_list中去,该模板向外面提供:begin()end()size()三个接口。

template<class T>
class sht::vector
{
public:
	vector(std::initializer_list<T> x)     //添加初始化列表初始化
	{
		for (auto& e : x)
			push_back(e);
	}
}

int main()
{
	sht::vector<string> s{ "hello","bye" };

	for (auto e : s)
	{
		cout << e << endl;
	}
}

变量类型推导

auto关键字

auo是一个类型的推导关键字,有以下几点需要注意:

  • auto仅仅只是占位符,编译阶段编译器根据初始化表达式推演出实际类型之后会替换auto
  • auto最大的作用是使代码变得更加简洁(迭代器的定义)
  • auto不能用来修饰函数参数
  • auto必须初始化,因为他要根据初始化的值来推断类型

typeid关键字

该关键字可以显示的打印出类型:

	int a = 1;
    double b = 1.0;
    cout << typeid(a*b).name() << endl;

输出结果为:
在这里插入图片描述

decltype关键字

该关键字将变量的类型声明为表达式的指定类型:
decltype (表达式) 对象; 这样对象就自动被推导成和表达式一样的类型。
该关键字与auto功能上一样,但是用法上decltype可以不用初始化

    int a = 1;
    double b = 1.0;
    auto c;     //错误的定义
    decltype(a * b) c;   //正确的定义

Lambda表达式

在C++98中我们对于一个函数的标识方式有:函数指针、仿函数
在C++11中我们新添加了另一种函数的表示方式——lamda表达式

举两个个例子:


  • 我们想让一个数组按从大到小的顺序重新排列👇
struct comp   //由于sort默认是从小到大的排序,所以我们必须实现自己的仿函数
{
    bool operator()(int a, int b)
    {
        return a > b;
    }
};

int main()
{
    vector<int> s{ 1,2,4,3,13,51,6,5 };
    sort(s.begin(), s.end(),comp());
    for (auto e : s)
        cout << e << " ";
}

  • 自定义类型按照某种属性排序:
struct fruit
{
    string name;
    double price;
};
struct comp
{
    bool operator()(fruit a, fruit b)   //我们想让数组按照水果价格倒叙排序
    {
        return a.price > b.price;
    }
};

int main()
{
    vector<fruit> s{ {"苹果",10.00},{"香蕉",15.00},{"橘子",20.00} };
    sort(s.begin(), s.end(), comp());
}

这样写仿函数有点麻烦所以C++11设计了Lambda表达式

书写格式

[捕捉列表] ( 参数列表) mutable ->returnvalue {函数体}

  1. lambda表达式各部分说明
  • [capture-list] : 捕捉列表,该列表总是出现在lambda函数的开始位置,编译器根据[]来
    判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文中的变量供lambda
    函数使用。

  • (parameters):参数列表。与普通函数的参数列表一致,如果不需要参数传递,则可以
    连同()一起省略

  • mutable:默认情况下,上面捕获列表的所有捕获内容均是const拷贝,mutable可以取消其常量的性质,但是他依然是捕获参数的一份拷贝
    性。使用该修饰符时,参数列表不可省略(即使参数为空)。

  • ->returntype:返回值类型。用追踪返回类型形式声明函数的返回值类型,没有返回
    值时此部分可省略。返回值类型明确情况下,也可省略,由编译器对返回类型进行推
    导。

  • {statement}:函数体。在该函数体内,除了可以使用其参数外,还可以使用所有捕获
    到的变量。

捕获列表的传参方式

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

注意

  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者
    非局部变量(全局变量、静态变量)都会导致编译报错
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    • [ =, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。
    • [=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复

Lambda表达式是什么

其实Lambda实际上就是一个匿名对象,该对象用你给的函数体重载了operator(),所以实际上和仿函数没有什么区别😅

包装器

包装器是一个函数模板,上面我们对于函数的范围扩张为:普通函数、仿函数、Lambda表达式。包装器实际上是对函数类型的封装,类似于C语言函数指针,但是我们在C语言中函数指针的类型是一个非常复杂的东西,包装器相对简单。

template<class F, class T>
T useF(F f, T x)
{
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}
double f(double i)
{
    return i / 2;
}
struct Functor
{
    double operator()(double d)
    {
        return d / 3;
    }
};
int main()
{
    // 函数名
    cout << useF(f, 11.11) << endl;
    // 函数对象
    cout << useF(Functor(), 11.11) << endl;
    // lamber表达式
    cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;
    return 0;
}

在这里插入图片描述

如上的代码,我们向函数模板useF传入函数、仿函数、Lambda表达式,模板中的类型F分别被识别为了:函数指针、仿函数类对象、Lambda表达式对象。所以实例化出了三份useF函数,导致了count的地址各不相同(因为静态变量属于整个类,一个类只有一个)

但是包装器在初始化类的成员函数时有些区别:


struct operate
{
    public:
    double add(double &x, double& y)    //类的普通成员函数
    {
        return x + y;
    }
    static double add_static(double& x, double& y)   //类的静态成员函数
    {
        return x + y;
    }
};
int main()
{
     double a = 1;
    double b = 2;
    //类的非静态成员函数 ——在圆括号中除了参数列表还需传入类名   同时还需注意赋值的函数指针要带上类名和&
    function<double(operate,double&, double&)> func4 = &operate::add;
    //注意调用该包装器的时候需要把类对象传进去,目的是this指针不能显示传递,只能传入对象来调用函数
    func4(operate(), a, b);
    //类的静态成员 
    function<double(double&, double&)> func5 = operate::add_static;
    return 0;
}

而我们使用包装器,可以将上述类型包装成同一个类型,从而达到useF只实例化出一个的结果!

下面来介绍一下包装器吧

  • std::function在头文件 <functional>
    // 类模板原型如下

  • template <class T> function;     // undefined
    template <class Ret, class... Args>
    class function<Ret(Args...)>;
    
  • 模板参数说明:
    Ret: 被调用函数的返回类型
    Args…: 被调用函数的形参

#include <functional>
template<class F, class T>
T useF(F f, T x)
{
    static int count = 0;
    cout << "count:" << ++count << endl;
    cout << "count:" << &count << endl;
    return f(x);
}

double f(double i)
{
    return i / 2;
}

struct Functor
{
    double operator()(double d)
    {
        return d / 3;
    }
};

int main()
{
    // 函数名
    std::function<double(double)> func1 = f;
    cout << useF(func1, 11.11) << endl;
    // 函数对象
    std::function<double(double)> func2 = Functor();
    cout << useF(func2, 11.11) << endl;
    // lamber表达式
    std::function<double(double)> func3 = [](double d)->double { return d / 4; };
    cout << useF(func3, 11.11) << endl;
    return 0;
}

在这里插入图片描述
这样useF中的模板类型F全部都是类型function<double(double)>了,所以只会实例化出一个useF函数!

谈谈我的个人理解

C++自从引入了仿函数,函数调用可以不仅通过函数指针还可以通过对象调用,但是相同功能(相同参数列表、相同返回值类型)的函数却不能用同一个函数指针表示(因为有仿函数的存在),所以包装器在C++中充当是一个大号的"函数指针"。

绑定

关于绑定有三个作用:

#include <functional>
int SubFunc(int a, int b)
{
    return a - b;
}
int Plus(int a, int b)
{
    return a + b;
}
class Sub
{
public:
    int sub(int a, int b)
    {
        return a - b;
    }
};

  • 绑定别的函数,相当于给函数起了一个“别名”
    这里的placeholders::_1placeholder::_2是函数第一个参数和第二个参数的占位符
std::function<int(int, int)> func1 = std::bind(Plus, placeholders::_1,placeholders::_2);
func1(1,2);  //和 Plus(1,2);等价


  • 还可以调换函数参数列表的顺序
    如下:这样传入参数的顺序就被交换了func2(1,2) ,就变成了2-1而不是1-2
std::function<int(int, int)> func2 = std::bind(SubFunc, placeholders::_2,placeholders::_1);
func2(1,2);

在这里插入图片描述


  • 还可以固定参数
    如上在使用包装器包装类的成员函数的时候,每次调用包装器的时候都要传入类得对象,使用绑定可以很好解决这个问题。我们在绑定的时候就将类对象传入,这样后面调用的时候就不需要传入对象了
//未使用绑定
std::function<int(Sub,int, int)> func3 = &Sub::sub;
    cout<<func3(Sub(), 1, 2);


//使用绑定
 std::function<int(int, int)> func4 = std::bind(&Sub::sub, Sub(), placeholders::_1, placeholders::_2);
    cout<<func4(2,1);
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值