您的位置:首页 > 财经 > 产业 > C++_进阶:C++11新增语法(2)

C++_进阶:C++11新增语法(2)

2025/1/4 8:09:17 来源:https://blog.csdn.net/Lvision2/article/details/141109784  浏览:    关键词:C++_进阶:C++11新增语法(2)

文章目录

    • 1.新的类功能
      • 1.1 新的默认成员函数
      • 2. 类成员变量初始化
      • 3. 强制生成默认函数的关键字default
      • 4. 禁止生成默认函数的关键字delete:
      • 5. final与override关键字
    • 2. 可变参数模板
    • 3. lambda表达式
      • 3.1 一个使用场景
      • 3.2 lambda表达式语法
      • **3.3 lambda捕捉列表详细说明**
      • 3.4 **底层的lambda**
    • 4. function包装器
    • 5. bind包装器

1.新的类功能

1.1 新的默认成员函数

在这里插入图片描述

在 C++入门 类和对象:构造函数,析构函数,拷贝构造 中,提及类的默认成员函数有以上6个,C++11 新增了两个:移动构造函数移动赋值运算符重载,所以之后的类的默认成员函数有8个了 。

针对移动构造函数和移动赋值运算符重载有一点需要注意:

  1. 如果你没有自己实现 移动构造函数 / 移动赋值重载函数,并且没有实现析构函数拷贝构造拷贝赋值重载,那么编译器会自动生成一个默认移动构造函数 / 默认移动赋值重载函数
    在这里插入图片描述
  2. 默认生成的移动构造函数 / 移动赋值重载函数,对于内置类型成员会执行逐成员按字节拷贝,自定义类型成员,则需要看这个成员是否实现移动构造/移动赋值,如果实现了就调用移动构造/移动赋值,没有实现就调用拷贝构造/移动赋值。
    在这里插入图片描述

2. 类成员变量初始化

C++11允许在类定义时给成员变量初始缺省值,默认生成构造函数会使用这些缺省值初始化

3. 强制生成默认函数的关键字default

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

class Person
{
public://显示生成拷贝构造Person(const Person& tmp){age = tmp.age;name = tmp.name;}//强制生成移动拷贝Person(Person&& tmp) = default;string name;int age;
};

4. 禁止生成默认函数的关键字delete:

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

//C++98的方法,把对应函数声明在private中,
//并且只声明不实现
class Date
{
public://...实现的其他函数
private:Date(const Date& tmp);int year;int month;int day;
};
//C++11的方法,直接delete
class Date
{
public:Date(const Date& tmp) = delete;
private:int year;int month;int day;
};

5. final与override关键字

在C++_进阶:多态详解中有介绍。

2. 可变参数模板

相比C++98/03,类模版和函数模版中只能含固定数量的模版参数,C++11的新特性可变参数模板能够让您创建可以接受可变参数的函数模板和类模板

可变参数模板的语法

// Args 是一个模板参数包类型,写在<>中,前面要+...
// args是一个函数形参参数包,写在形参中,声明时Args后要+...
template <class ...Args>
void ShowList(Args... args)
{}int main()
{ShowList(3 , 3.3 , 3 ,3.3);
}

在这里插入图片描述
上面的参数args前面有省略号,所以它就是一个可变模版参数,我们把带省略号的参数称为“参数包”,它里面包含了0到N(N>=0)个模版参数。我们无法直接获取参数包args中的每个参数的,只能通过展开参数包的方式来获取参数包中的每个参数。

1️⃣递归函数方式展开参数包

// 递归终止函数
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...);
}
int main()
{ShowList(1, 'A', std::string("sort"));
}

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


2️⃣逗号表达式展开参数包

template <class T>
void PrintArg(T t)
{cout << t << " ";
}//展开函数
template <class ...Args>
void ShowList(Args... args)
{int arr[] = { (PrintArg(args), 0)... };cout << endl;
}int main()
{ShowList(1, 'A', std::string("sort"));return 0;
}

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

expand函数中的逗号表达式:(printarg(args), 0),也是按照这个执行顺序,先执行
printarg(args),再得到逗号表达式的结果0。同时还用到了C++11的另外一个特性——初始化列表,通过初始化列表来初始化一个变长数组
在这里插入图片描述

在这里插入图片描述

在STL容器中,empalce相关接口函数就是用可变参数模板实现的。
在这里插入图片描述
在这里插入图片描述

// emplace_back的接口
template <class... Args>
void emplace_back (Args&&... args);
//

Args&&… 万能引用参数包类型,能接受一个及以上的左值或者右值

emplace的功能是尾插,首先我们看到的emplace系列的接口,支持模板的可变参数,并且万能引用。他与push_back有什么区别呢?

int main()
{std::list< std::pair<int, char> > mylist;// emplace_back支持可变参数,拿到构建pair对象的参数后自己去创建对象// 那么在这里我们可以看到除了用法上,和push_back没什么太大的区别mylist.emplace_back(10, 'a');mylist.emplace_back(20, 'b');mylist.emplace_back(make_pair(30, 'c'));mylist.push_back(make_pair(40, 'd'));mylist.push_back({ 50, 'e' });for (auto e : mylist)cout << e.first << ":" << e.second << endl;return 0;
}
	//...//在模拟的list类中,模拟实现emplace_backtemplate <class... Args>void emplace_back(Args&&... args)	{			insert(end(), std::forward<Args>(args)...);}template <class... Args>//emplace_back复用insert 再实现一个模板的可变参数的insertiterator insert(iterator pos, Args&&... args){Node* cur = pos._node;Node* newnode = new Node(std::forward<Args>(args)...);Node* prev = cur->_prev;// prev  newnode  curprev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}
int main()
{// 下面我们试一下带有拷贝构造和移动构造的bit::string,再试试呢// 我们会发现其实差别也不到,emplace_back是直接构造了,push_back// 是先构造,再移动构造,其实也还好。std::list< std::pair<int, bit::string> > mylist;mylist.emplace_back(10, "sort");mylist.emplace_back(make_pair(20, "sort"));mylist.push_back(make_pair(30, "sort"));mylist.push_back({ 40, "sort"});return 0;
}

3. lambda表达式

3.1 一个使用场景

struct Goods
{string _name; // 名字double _price; // 价格int _evaluate; // 评价Goods(const char* str, double price, int evaluate):_name(str), _price(price), _evaluate(evaluate){}
};int main()
{vector<Goods> v = { { "香蕉", 3, 4 }, { "橙子", 2.2,3 }, { "菠萝", 1.5, 4 } };// lambda表达式填写sort的第三个参数sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._evaluate > g2._evaluate;});
}

sort的第三个参数是仿函数类型。对于像Goods一样的自定义类型,在C++11之前,每次为了实现一个algorithm算法,都要重新去写一个类,如果每次比较的逻辑不一样,还要去实现多个类,特别是相同类的命名。C++11之后,就可以使用lambda表达式,避免每次都去实现一个类,提高便利性。lambda表达式实际是一个匿名函q数。

3.2 lambda表达式语法

语法:

[capture-list] (parameters) mutable -> return-type { statement}

在这里插入图片描述

1️⃣ lambda表达式各部分说明:

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

3.3 lambda捕捉列表详细说明

捕捉列表描述了本作用域中那些数据可以被lambda使用。

int main()
{//本作用域有三个变量int x = 1, y = 2, z = 3;//1. []{}最简单的lambda, 该lambda表达式没有任何意义// auto f = lambda 没有其他的意思,就看右边部分就行auto f1 = [] {};//2. [x] 捕捉上文出现的变量x,使用的方式是传值auto f2 = [x] {};//3. [=] 表示 值传递方式 捕获所有父作用域中的变量(包括this)auto f3 = [=] {};//4. [&x] 表示 引用传递 捕捉所有父作用域中的变量(包括this)auto f4 = [&x] {};//5. [&] 捕捉上文出现的所有变量,使用的方式是引用auto f5 = [&] {};//6. [this] , 表示值传递方式捕捉当前的this指针//在类内使用[this] {};
}

通常使用样例:

int main()
{// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=] {return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c) {b = a + c; };fun1(10)cout << a << " " << b << endl;// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int {return b += a + c; };cout << fun2(10) << endl;// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;return 0;
}

📝备注:

  1. 父作用域包含lambda函数的语句块
  2. 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。
    [=, &a, &b]:以引用传递的方式捕捉变量a和b值传递方式捕捉其他所有变量
    [&,a, this]:值传递方式捕捉变量a和this引用传递捉其他变量。
  3. 捕捉列表不允许变量重复传递,否则就会导致编译错误
    [=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  4. lambda表达式之间不能相互赋值,即使看起来类型相同

3.4 底层的lambda

以一段代码案例说明:

//这是一个计算汇率的仿函数
class Rate
{
public:Rate(double rate) : _rate(rate){}double operator()(double money, int year){return money * _rate * year;}
private:double _rate;
};int main()
{// 函数对象double rate = 0.015;Rate r1(rate);cout << r1(10000, 2) << endl;// 第一个lambda// 两个lambda的内容与上面的仿函数一样auto r2 = [rate](double monty, int year)->double{return monty * rate * year;};cout << r2(10000, 2) << endl;// 第二个lambdaint x = 1, y = 2;auto r3 = [=](double monty, int year)->double{return monty * rate * year;};cout << r3(10000, 2) << endl;return 0;
}

对于上面的代码,我们在反汇编层面上就能很清晰的看到:
在这里插入图片描述
首先,对于生成仿函数对象并使用仿函数的过程,汇编先调用了仿函数的构造函数,再调用了仿函数的operator()函数

在这里插入图片描述
再看看第一个lambda,我们会发现它在汇编指令上与仿函数大致都相同,都是先调用了一个构造函数然后调用了一个operator(),并且这两个函数我们发现都是第一个lambda的。

在这里插入图片描述
第二个lambda也是相同的情况。

在这里插入图片描述
并且发现,lambda在捕获列表中捕获了rate之后,汇编也出现了形参入栈的代码,从这里看出,捕获的本质是给lambda的构造函数传参

在这里插入图片描述
还有一个细节,我这里的捕获列表[ rate ] 改成 [ = ] (捕获所有参数),但是入栈的参数却只有rate,这说明:就算是 [ = ] (捕获所有参数),对于函数体没有使用到的参数,lambda也是不会捕获的
在这里插入图片描述
这里我们让函数体再多使用一个x ,也确实发现 x 入栈了。

由此可以得出结论:lambda表达式本质上是匿名函数对象捕获的本质是给lambda的构造函数传参

4. function包装器

function包装器 也叫作适配器。C++中的function本质是一个类模板,也是一个包装器。

作用:包装器可以包装 函数指针,仿函数,以及lambda。

int f(int a, int b)
{return a + b;
}struct Functor
{
public:int operator() (int a, int b){return a + b;}
};
int main()
{// 包装可调用对象// 函数名(函数指针)function<int(int, int)> func1 = f;// 仿函数function<int(int, int)> func2 = Functor();// lamber表达式function<int(int, int)> func3 = [](const int a, const int b){return a + b; };
}

包装器要求:

  1. 需要包含头文件 < functional >
#include <functional>
  1. function声明:在<>内,()前为被包装函数的返回类型,()内为被包装函数的参数列表类型
 function<返回类型(参数列表类型)>
int f(int a, int b)
{return a + b;
}
//包装器func1包装返回类型为int,参数类型为int,int的函数
function<int(int, int)> func1 = f;

特殊场景:包装静态成员变量,与非静态成员变量

	class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{// 包装静态成员函数function<int(int, int)> f4 = &Plus::plusi;cout << f4(1, 1) << endl;// 包装非静态成员函数function<double(Plus*, double, double)> f5 = &Plus::plusd;Plus pd;cout << f5(&pd, 1.1, 1.1) << endl;cout<< f5(Plus(), 1.1,1.1) <<endl;
}

📝备注 : 包装二者都要&。 包装非静态成员函数要多传一个 Plus * (类指针)作为function第一个参数类型,在调用包装器的时候,也要多传一个类对象(匿名对象也可以)作为第一个参数

5. bind包装器

bind用于实现原函数对象的参数顺序调整。

std::bind函数是一个函数模板,它就像一个函数包装器(适配器),受一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表。一般而言,我们用它可以把一个原本接收N个参数的函数fn,通过绑定一些数,返回一个接收M个(M可以大于N,但这么做没什么意义)参数的新函数。同时,使std::bind函数还可以实现参数顺序调整等操作。

// 原型如下:
template <class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);
// with return type (2)
template <class Ret, class Fn, class... Args>
/* unspecified */ bind (Fn&& fn, Args&&... args);

可以将bind函数看作是一个通用的函数适配器,它接受一个可调用对象(function对象或者一些函数对象),生成一个新的可调用对象来“适应”原对象的参数列表。

调用bind的一般形式:auto newCallable = bind(callable,arg_list);
其中,newCallable本身是一个可调用对象arg_list是一个逗号分隔的参数列表,应给定的callable的参数。当我们调用newCallable时,newCallable会调用callable,并给它arg_list中的参数。

arg_list中的参数可能包含形如**_n的名字**,其中n是一个整数,这些参数是**“占位符”,表示newCallable的参数**,它们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1为newCallable的第一个参数,_2为第二个参数,以此类推 , _1, _2 … _n都保存在命名空间placeholders,在使用他们的时候需要指定类域。

//使用举例1
int Sub(int a, int b)
{return a / b;
}
int main(){// bind 本质返回的一个仿函数对象// 调整参数顺序(不常用)// _1代表第一个实参// _2代表第二个实参// ...//表示绑定函数plus 参数分别由调用 func1 的第一,二个参数指定
function<int(int, int)> func1 = bind(Sub, placeholders::_1,
placeholders::_2);func1(1, 2);// 1/2 结果是0//表示绑定函数plus 参数分别由调用 func1 的第二,一个参数指定
function<int(int, int)> func2 = bind(Sub, placeholders::_2,
placeholders::_1);func1=2(1, 2); // 2/1 结果是2
}

在这里插入图片描述
在这里插入图片描述

int Plus(int a, int b)
{return a + b;
}//使用举例2,用于绑死参数
int main()
{// 调整参数个数 (常用)auto sub3 = bind(Sub, 100, _1);cout << sub3(5) << endl;auto sub4 = bind(Sub, _1, 100);cout << sub4(5) << endl;// 分别绑死第123个参数auto sub5 = bind(SubX, 100, _1, _2);cout << sub5(5, 1) << endl;auto sub6 = bind(SubX, _1, 100, _2);cout << sub6(5, 1) << endl;auto sub7 = bind(SubX, _1, _2, 100);cout << sub7(5, 1) << endl;function<double(Plus, double, double)> f6 = &Plus::plusd;Plus pd;cout << f6(pd, 1.1, 1.1) << endl;cout << f6(Plus(), 1.1, 1.1) << endl;// bind一般用于,绑死一些固定参数function<double(double, double)> f7 = bind(&Plus::plusd, Plus(), _1, _2);cout << f7(1.1, 1.1) << endl;//auto func1 = [](double rate, double monty, int year)->double {return monty * rate * year;};auto func1 = [](double rate, double monty, int year)->double {double ret = monty;for (int i = 0; i < year; i++){ret += ret * rate;}return ret - monty;};
}
class Sub
{
public:
int sub(int a, int b){return a - b;}
};
int main()
{//使用举例3,绑定成员函数std::function<int(int, int)> func3 = std::bind(&Sub::sub, s,placeholders::_1, placeholders::_2);std::function<int(int, int)> func4 = std::bind(&Sub::sub, s,placeholders::_2, placeholders::_1);cout << func3(1, 2) << endl;cout << func4(1, 2) << endl;
}

本文就到这里,感谢你看到这里❤️❤️! 我知道一些人看文章喜欢静静看,不评论🤔,但是他会点赞😍,这样的人,帅气低调有内涵😎,美丽大方很优雅😊,明人不说暗话,要你手上的一个点赞😘!

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com