【C++】C++11常用特性梳理

C++11特性梳理

  • 1. 列表初始化
  • 2. auto & decltype
  • 3. 右值引用
    • 3.1. 左右值引用比较
    • 3.2. 右值引用的意义
    • 3.3. 万能引用与完美转发
    • 3.4. 移动构造与移动赋值
  • 4. default & delete
  • 5. 可变参数模板
  • 6. push_back 与 emplace_back
  • 7. lambda表达式
    • 7.1. 捕捉列表
  • 8. function包装器
    • 8.1. bind

C++11是继C++98之后,第二个真正意义上的标准。C++11增加的语法特性非常多,这里主要介绍实际比较实用的语法。
可以从C++官网对C++11版本特性进行全面的了解:https://en.cppreference.com/w/cpp/11

1. 列表初始化

C++11中的{}不仅可以用来初始化数组,已经扩展到可以用来初始化C++11的一切对象,而且使用时等号(=)可有可无。

void Test1()
{
	int a1 = { 1 };
	int a2{ 2 };
	
	vector<int> v1 = { 1,2,3,4,5 };
	vector<int> v2{ 1,2,3,4,5 };
	
	map<int, string> m1 = { {1, "one"}, {2, "two"} };
	map<int, string> m2{ {1, "one"}, {2, "two"} };
	
	// 也可以用在new表达式中
	int* p = new int[4]{ 0 };
}

注意一点:列表初始化,如果出现类型截断,是会报警告或者错误的。short c = 65535; short d { 65535 }; // err
STL中的很多容器能支持{}的初始化,其实是因为增加了std::initializer_list作为构造函数参数的设计,这样使容器的初始化更方便了。operator=同理。
在这里插入图片描述
std::initializer_list又是什么类型呢?

void Test2()
{
	auto il = { 1,2,3 };
	cout << typeid(il).name() << endl;
}

在这里插入图片描述
可以看到,{}对象其实就是std::initializer_list类型的对象。
这里建议,如果在使用容器时有大括号{}初始化的需求,可以选择使用;其它地方尽量不要使用,来保持语言风格的一致性。

2. auto & decltype

auto在C++98中是一个存储类型的说明符,但实质上没有什么价值。所以C++11中,auto就弃用了之前的用法,将其用于实现自动类型推导。auto此时仅仅只是占位符,编译阶段编译器根据初始化表达式推演出实际类型之后会替换auto

void Test3()
{
	int i = 10;
	auto p = i;
	auto pf = strcpy;
	cout << typeid(p).name() << endl;
	cout << typeid(pf).name() << endl;
}

在这里插入图片描述

decltype关键字可以将对象的类型声明为表达式生成的类型。

void Test4()
{
	char c = 'a';
	int i = 1;
	decltype(c * i) d1;
	decltype(strcpy) d2;
	cout << typeid(d1).name() << endl;
	cout << typeid(d2).name() << endl;
}

在这里插入图片描述

3. 右值引用

有右值引用,自然也有左值引用。其实C++11之前所学的引用都叫做左值引用。但无论左值引用还是右值引用,都是给对象取别名。
对于左值,我们可以取它的地址+对它赋值。但经过const修饰的左值可以取它的地址,却不能对它赋值。
对于右值,是不能取其地址的。
左值可以出现在赋值符号左边,右值可以出现在赋值符号右边,但反过来就不行。

void Test5()
{
	int i = 1;
	int* pi = &i;

	// 左值引用,给左值取别名
	int& ri = i; // 变量
	int*& rpi = pi; // 变量
	int& r = *pi; // 指针解引用

	double d = 2.2;
	
	// 右值引用,给右值取别名
	int&& rr1 = 10; // 字面常量
	double&& rr2 = i + d; // 表达式返回值
}

虽然右值不能被取地址,但右值引用后,会导致右值被存储到特定位置,从而可以取到该位置的地址。

void Test6()
{
	int&& rr1 = 10; // 右值引用
	cout << rr1 << endl;
	cout << &rr1 << endl; // 可以取地址

	rr1 = 20; // 可以赋值
	cout << rr1 << endl;
	cout << &rr1 << endl; // 可以取地址
}

在这里插入图片描述

3.1. 左右值引用比较

左值引用只能引用左值,不能引用右值;但const左值引用既可以引用左值,也可以引用右值。
右值引用只能引用右值,不能引用左值;但右值引用可以引用move以后的左值。

void Test7()
{
	int i = 10;

	int& r1 = i;
	//int& r2 = 10; // err
	const int& r3 = i;
	const int& r4 = 10;

	//int&& r5 = i; // err
	int&& r6 = 10;
	int&& r7 = move(i);
}

3.2. 右值引用的意义

左值引用既可以引用左值,又可以通过const修饰引用右值,那为什么还要提出右值引用呢?
右值引用的加入其实是对左值引用一些缺陷上的补足。
比如说,stackpop接口返回值是voidvoid pop();)。但是如果要将返回值进行修改,使pop的同时返回其弹出的元素,那该如何设计其返回值类型呢?
如果设计成传值返回,当返回值需要深拷贝时,就会对效率造成影响。
如果设计成传引用返回,如果是左值引用,当出了函数作用域,返回的对象就不存在了,即引用也失效了。
此时,只能设计成右值引用返回。设计成右值引用,把将要被销毁的对象的资源转移出来,可以继续使用,而且不需要担心深拷贝的问题,填补了左值引用使用上的一些缺陷。
右值引用可以通过移动构造或移动赋值,对返回值是右值(将亡值)的资源进行转义,减少拷贝,延长资源生命周期。

3.3. 万能引用与完美转发

void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右值引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

// 模板中的&&不是右值引用,而是万能引用/引用折叠,其提供了既能接收左值又能接收右值的能力
// 但在接收之后的使用中,值类型都退化成了左值
template<typename T>
void PerfectForward(T&& t)
{
	Fun(t);
}

void Test8()
{
	int i = 1;
	PerfectForward(i);  // 左值
	PerfectForward(move(i)); // 右值

	const int ci = 2;
	PerfectForward(ci); // const 左值
	PerfectForward(move(ci)); // const 右值
}

在这里插入图片描述

// 如果想要在传递过程中保持最初的左值或右值属性,就需要用到完美转发
template<typename T>
void PerfectForward(T&& t)
{
	Fun(forward<T>(t));
}

在这里插入图片描述

3.4. 移动构造与移动赋值

C++11STL容器中都是增加了移动构造和移动赋值的。
在这里插入图片描述
移动构造和移动赋值是C++11新增的两个默认成员函数。
当没有自己实现移动构造函数,且没有实现析构、拷贝构造、拷贝赋值中的任何一个函数。那么编译器会自动生成一个默认的移动构造。其对于内置类型成员会执行按字节拷贝(值拷贝);对于自定义类型成员,会看这个成员是否实现了移动构造,如果实现了就调用,没有实现就调用拷贝构造。移动赋值同理。

4. default & delete

default和delete的使用可以让我们更好地控制默认函数的生成。
default可以强制某个默认函数的生成。比如:提供拷贝构造后,移动构造就不生成了,那么可以使用default显示指定移动构造生成。
如果不想要某个默认函数生成,只需要在函数声明处加上=delete即可。=delete修饰的函数也称为删除函数。

// 要求delete关键字实现一个类,只能在堆上创建对象
class HeapOnly
{
public:
	HeapOnly()
	{
		_str = new char[10];
	}

	~HeapOnly() = delete;

	void Destroy()
	{
		delete[] _str;
		operator delete(this);
	}
private:
	char* _str;
};
void Test9()
{
	HeapOnly* ptr = new HeapOnly;
	ptr->Destroy();
}

5. 可变参数模板

相比C++98固定的模板参数,C++11可变参数模板的的引入无疑是一个巨大的改进。

// 一个基本的函数可变参数模板定义
// Args是一个模板参数包,args是一个函数形参参数包,包含有大于等于0个参数
template<class ...Args>
void ShowList(Args... args)
{}

参数args前面有省略号,表示它是一个可变模板参数。把带有省略号的参数称为“参数包”,里面包含了N(N>=0)个模板参数。由于语法不支持使用args[i]这样的方式获取参数,需要使用一些特别的方法来一一获取参数包中的参数。

  1. 递归展开参数包
void ShowList() {} // 递归终止函数

template <class T, class ...Args>
void ShowList(const T& val, Args... args)
{
	cout << "ShowList(" << val << ", " << sizeof...(args) <<"个参数)" << endl;

	ShowList(args...);
}

void Test10()
{
	ShowList(1, 'A', string("one"));
}

在这里插入图片描述
2. 构建数组展开参数包

template<class T>
int PrintArg(const T& val)
{
	cout << val << " ";
	return 0;
}

template <class ...Args>
void ShowList(Args... args)
{
	int a[] = { PrintArg(args)... };
	cout << sizeof(a) / sizeof(int) << endl;
}

void Test11()
{
	ShowList(1, 'A', string("one"));
}

在这里插入图片描述
{ PrintArg(args)... }将会展开成 { PrintArg(arg1), PrintArg(arg2), PrintArg(arg3), ... },最终会创建一个元素值都为PrintArg返回值的数组。也就是说在构建数组的过程中参数包就展开了,而这个数组构建的目的纯粹就是为了在数组构建的过程中展开参数包。

6. push_back 与 emplace_back

在这里插入图片描述
在这里插入图片描述
push_back 与 emplace_back都是在end位置插入一个值。
可以注意到emplace接口支持可变模板参数,并且使用了万能引用。那么相对传统的插入接口,emplace接口优势到底在哪里呢?
在用法上,emplace支持可变参数,拿到参数后,会自己去创建对象,简化使用。

void Test12()
{
	vector<pair<int, int>> v;
	v.push_back(make_pair(1,1));
	v.push_back({ 2,2 });
	v.emplace_back(3, 3);
}

在底层上,emplace_back是直接构造,push_back是先构造,再拷贝构造/移动构造。
emplace_back直接构造确实会效率高一点,但实际上emplack_back和push_back的使用效果是差别不大的。

7. lambda表达式

lambda表达式实际是一个匿名函数。
lambda表达式语法:[capture-list](parameters)mutable->return-type{statement}

  • [capture-list]:捕捉列表。捕捉列表能够捕捉上下文中的变量供lambda使用,同时编译器也会根据[]来判断接下来的代码是否为lambda函数。
  • (parameters):参数列表。和普通函数的参数列表使用方式一致。如果不需要传参,()也可以省略。
  • mutable:默认情况下,lambda函数是一个const函数,而mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数列表为空)。
  • ->returntype:返回值类型声明。无返回值时可省略;返回值类型明确的情况下,也可省略,交由编译器自行推导返回值类型。
  • {statement}:函数体。在函数体内可以使用参数列表的参数和捕捉列表捕获的变量。
    从lambda表达式定义中,可以知道参数列表和返回值是可选部分,捕捉列表和函数体是可为空部分。所以最简单的lambda函数是[]{}

7.1. 捕捉列表

  • [var]:表示传值方式捕捉变量var
  • [=]:表示传值方式捕捉父作用域中的所有变量(包括this)
  • [&var]:表示传引用方式捕捉变量var
  • [&]:表示传引用方式捕捉父作用域中的所有变量(包括this)
  • [this]:表示传值方式捕捉this指针
void Test13()
{
	int x = 1;
	int y = 2;

	// 默认捕捉(传值捕捉)的变量不能修改
	// auto swap2 = [=]
	auto swap2 = [=]()mutable
	{
		int tmp = x;
		x = y;
		y = tmp;
	};
	swap2();
	cout << x << " " << y << endl;
}

即使对于传值捕捉的变量进行了修改,也不会对外部实际的变量造成修改。

所谓父作用域是指与lambda所在栈帧平行的栈帧空间。

static int si = 1;
void Test14()
{
	int i = 2;

	auto a = [=] {cout << si << endl; cout << i << endl; };
	a();
}

捕捉列表可由多个项组成,并以逗号分割。

void Test15()
{
	int a, b, c, d, e;
	a = b = c = d = e = 1;

	// 全部传值捕捉
	auto f1 = [=]()
	{
		cout << a << b << c << d << e << endl;
	};
	f1();
	cout << a << endl;

	// 混合捕捉
	auto f2 = [=, &a]()
	{
		++a;
		cout << a << b << c << d << e << endl;
	};
	f2();
	cout << a << endl;

	auto f3 = [&, a]()mutable
	{
		++a;
		cout << a << b << c << d << e << endl;
	};
	f3();
	cout << a << endl;
}

捕捉列表不允许变量重复传递。比如[=, this],this捕捉会重复,从而导致编译错误。
lambda表达式之间不能相互赋值。

void Test16()
{
	auto f1 = [] {cout << "hello world"; };
	auto f2 = [] {cout << "hello world"; };
	//f2 = f1; // err
	cout << typeid(f1).name() << endl;
	cout << typeid(f2).name() << endl;
}

在这里插入图片描述
lambda对象不能相互赋值,本质原因是因为类型不同,不能相互赋值。
这也说明,lambda在使用者角度是匿名的,在底层还是有名的。
实际在底层,编译器对于lambda表达式的处理,完全是按照函数对象的方式处理的。即:如果定义了一个lambda表达式,编译器会自动生成一个类(为其生成lambda_uuid的类型名),在该类中会重载operator()。
在这里插入图片描述

8. function包装器

C++中的function本质是一个类模板。
那为什么要有包装器呢?

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;
	}
};

void Test17()
{
	// 函数指针
	cout << useF(f, 11.11) << endl;
	// 函数对象
	cout << useF(Functor(), 11.11) << endl;
	// lambda表达式对象
	cout << useF([](double d)->double { return d / 4; }, 11.11) << endl;

	// 函数指针 | 仿函数/函数对象 | lambda(匿名函数) -> 都能像函数一样被使用
}

在这里插入图片描述
可以发现useF函数模板实例化了三份。但如果使用包装器就可以很好地优化上面的问题。
在这里插入图片描述
Ret:被调用函数的返回类型
Args…:被调用函数的形参

void Test18()
{
	// 函数指针
	function<double(double)> f1 = f;
	cout << useF(f1, 11.11) << endl;

	// 函数对象
	function<double(double)> f2 = Functor();
	cout << useF(f2, 11.11) << endl;

	// lambda表达式对象
	function<double(double)> f3 = [](double d)->double {return d / 4; };
	cout << useF(f3, 11.11) << endl;
}

在这里插入图片描述

8.1. bind

可以把bind看做是函数适配器。bind的作用主要是可以接收一个可调用对象(callable object),同时生成一个新的可调用对象来“适应”原对象的参数列表。使用bind我们可以把一个原本接收 n 个参数的函数 f,通过绑定一些参数,然后返回一个接收 m 个参数的新函数,同时可以做到对参数顺序的调整。
在这里插入图片描述
bind的一般使用形式:auto newCallable = bind(callable, arg_list)
newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对应callable的参数。当调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。
arg_list中的参数可能包含形如 _n (n 是一个整数)的名字,这些参数是“占位符”,表示newCallable的参数。这些“占位符”占据了传递给newCallable的参数的“位置”。例如,_1为newCallable的第一个参数,_2为第二个参数,…

class Plus
{
public:
	static int plusi(int a, int b)
	{
		return a + b;
	}
	double plusd(double a, double b)
	{
		return a + b;
	}
};

void Test19()
{
	function<int(int, int)> f1 = Plus::plusi;
	function<double(Plus, double, double)> f2 = &Plus::plusd;
	cout << f1(1, 2) << endl;
	cout << f2(Plus(), 1.1, 2.2) << endl;

	// 调整参数个数,绑死固定参数
	function<int(int, int)> f3 = bind(Plus::plusi, placeholders::_1, placeholders::_2);
	function<double(double, double)> f4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);
	cout << f3(1, 2) << endl;
	cout << f4(1.1, 2.2) << endl;
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/132965.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

(一)QML加载离线地图+标记坐标点

1、实现效果 加载离线地图瓦片、鼠标拖拽、滚轮缩放在地图上固定坐标位置标注地名 &#xff08;一&#xff09;QML加载离线地图标记坐标点&#xff1a;MiniMap-mini 2、实现方法 2.1、使用工具下载离线地图 不废话&#xff0c;直接搬别人的砖&#xff0c;曰&#xff1a;他山…

VirtualBox网络地址转换(NAT),宿主机无法访问虚拟机的问题

问题&#xff1a;NAT模式下&#xff0c;默认只能从内访问外面&#xff0c;而不能从外部访问里面&#xff0c;所以只能单向ping通&#xff0c;虚拟机的ip只是内部ip。 PS&#xff1a;桥接则是与主机公用网卡&#xff0c;有独立的外部ip。 解决&#xff1a;NAT模式可以通过配置 …

好物周刊#30:Github 上大学

https://github.com/cunyu1943/JavaPark https://yuque.com/cunyu1943 村雨遥的好物周刊&#xff0c;记录每周看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;每周五发布。 一、项目 1. Fighting Design 一款灵活、优质的组件库&#xff0c;可在 vue3 应用程…

【api_fox】ApiFox简单操作

1、get和post请求的区别&#xff1f;2、接口定义时的传参格式&#xff1f;3、保存接口文档 apifox当中接口文档的设计和接口用例的执行是分开的。 1、get和post请求的区别&#xff1f; 2、接口定义时的传参格式&#xff1f; 3、保存接口文档 就生成如下的接口文档。

C语言实现编写一个函数,输入n为偶数时,调用函数求1/2+1/4+...+1/n,当输入n为奇数时,调用函数1/1+1/3+...+1/n

完整代码&#xff1a; /*编写一个函数&#xff0c;输入n为偶数时&#xff0c;调用函数求1/21/4...1/n,当输入n为奇数时&#xff0c; 调用函数1/11/3...1/n */ #include<stdio.h>//n为偶数 double Odd(int n){double sum0;//i为2&#xff0c;4&#xff0c;6....for (int …

linux_day02

1、链接&#xff1a;LN 一个点表示当前工作目录&#xff0c;两个点表示上一层工作目录&#xff1b; 目录的本质&#xff1a;文件&#xff08;该文件储存目录项&#xff0c;以链表的形式链接&#xff0c;每个结点都是目录项&#xff0c;创建文件相当于把目录项添加到链表中&…

在Spring Boot中使用JTA实现对多数据源的事务管理

了解事务的都知道&#xff0c;在我们日常开发中单单靠事务管理就可以解决绝大多数问题了&#xff0c;但是为啥还要提出JTA这个玩意呢&#xff0c;到底JTA是什么呢&#xff1f;他又是具体来解决啥问题的呢&#xff1f; JTA JTA&#xff08;Java Transaction API&#xff09;是…

C语言--青蛙跳台阶【内容超级详细】

今天与大家分享一下递归的经典题目--青蛙跳台阶问题。 一.引子 电影《少年班》&#x1f357;中的老师在全国各地寻找天才少年时&#xff0c;就问了一个这样的问题&#xff0c;有20阶台阶&#xff0c;每次只能上一阶或两阶&#xff0c;总共有多少种上法&#xff1f;这个少年王…

【LeetCode刷题日志】138.随机链表的复制

&#x1f388;个人主页&#xff1a;库库的里昂 &#x1f390;C/C领域新星创作者 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏✨收录专栏&#xff1a;LeetCode 刷题日志&#x1f91d;希望作者的文章能对你有所帮助&#xff0c;有不足的地方请在评论区留言指正&#xff0c;…

WebSphere Liberty 8.5.5.9 (一)

WebSphere Liberty 8.5.5.9 (一) 安装 1. 从官网下载 WebSphere Liberty 8.5.5.9 2. 解压 解压到 D:\wlp-webProfile7-java8-8.5.5.93. 启动 D:\wlp-webProfile7-java8-8.5.5.9\wlp\bin>server start 正在启动服务器 defaultServer。 服务器 defaultServer 已启动。4. …

让你的win10/win11系统变得不再卡顿,优雅草伊凡整理-长期更新-如何让windows操作系统不用老是重装在不断的更新中依然保持流畅运行

概述 如题&#xff1a;让你的win10/win11系统变得不再卡顿&#xff0c;优雅草伊凡整理-长期更新-如何让windows操作系统不用老是重装在不断的更新中依然保持流畅运行 本文长期更新&#xff0c;本次更新2023年11月8日&#xff01; 很多时候 我们的win10win11系统不管再怎么关…

【Python】20大报告生成词云

这个我其实写过一篇类似的博客&#xff0c;但是那个的文件对象是.csv&#xff0c;对应到.docx文件的话&#xff0c;就不太适用了。如下&#xff1a; Python生成词云-CSDN博客 代码&#xff1a; import jieba import os import wordcloud import numpy as np from PIL import…

自动化测试框架Playwright安装以及使用

最近&#xff0c;微软开源了一个非常强大的自动化项目叫 playwright-python 它支持主流的浏览器&#xff0c;包含&#xff1a;Chrome、Firefox、Safari、Microsoft Edge 等&#xff0c;同时支持以无头模式、有头模式运行&#xff0c;并提供了同步、异步的 API&#xff0c;可以…

【读点论文】结构化剪枝

结构化剪枝 在一个神经网络模型中&#xff0c;通常包含卷积层、汇合层、全连接层、非线形层等基本结构&#xff0c;通过这些基本结构的堆叠&#xff0c;最终形成我们所常用的深度神经网络。 早在 1998 年&#xff0c;LeCun 等人使用少数几个基本结构组成 5 层的 LeNet-5 网络&…

概念解析 | Richardson-Lucy去卷积算法

注1:本文系“概念解析”系列之一,致力于简洁清晰地解释、辨析复杂而专业的概念。本次辨析的概念是:Richardson-Lucy去模糊算法 Richardson-Lucy去模糊算法:重现图像的真实面目 Blind deconvolution by means of the Richardson–Lucy algorithm 背景介绍 在图像处理中,图像获取…

Antd React Form.Item内部是自定义组件怎么自定义返回值

在线演示https://stackblitz.com/edit/stackblitz-starters-xwtwyz?filesrc%2FSelfTreeSelect.tsx 需求 当我们点击提交,需要返回用户名和选中树的id信息,但是,我不关要返回树的id信息,还需要返回选中树的名称 //默认返回的 {userName:梦洁,treeInfo:leaf1-value } //但是需…

十年测试工龄,揭露软件测试痛点以及分析

做软件测试的同学们&#xff0c;你在平时的测试工作中有哪些困惑或困扰呢&#xff1f;你可以自行简单思考一下。下面我梳理一下&#xff0c;大家可以看看自己是不是也有如此的感受。 从测试整体角度分析&#xff1a; 第一个痛点是入门容易深入难。 很多人认为软件测试也就那么…

python中的异常与模块

异常 为了能够让代码可以正常的运行下去&#xff0c;不会因为某个语句而让程序崩溃&#xff0c;所以我们就需要使用异常&#xff0c;异常的语法格式如下&#xff1a; try:可能出现异常的语句 except:出现异常之后的处理同时python也是支持捕获指定异常的 try:可能出现异常的…

阿里大佬:DDD中Interface层、Application层的设计规范

说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如阿里、滴滴、极兔、有赞、希音、百度、网易、美团的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 谈谈你的DDD落地经验&#xff1f; 谈谈你对DDD的理解&#xff1f…

C语言 音乐播放器项目(综合)

1.main.c文件 #include<stdio.h> #include<stdlib.h> #include<string.h> #include <unistd.h>//休眠所需的头文件 #include "./pos/console.h"//光标使用所需的头文件 #include "lrc.h" #include "./mplayer/start_mplayer…