当前位置: 首页 > news >正文

《探索C++11:现代语法的性能优化策略(中篇)》

C++11 语法的重要性

C++11 是 C++ 标准的一次重大更新,引入了许多现代编程特性,显著提升了代码的简洁性、安全性和性能。这些新特性不仅优化了开发效率,还使得 C++ 在系统编程、嵌入式开发和高性能计算等领域更具竞争力

C++11 的核心改进

C++11 的改进涵盖多个方面,包括自动类型推导、智能指针、Lambda 表达式、范围 for 循环、右值引用和移动语义等。这些特性使得代码更易于编写和维护,同时减少了常见错误的可能性

现代 C++ 编程风格

借助 C++11,开发者可以编写更简洁、更高效的代码。例如,使用 auto 关键字可以减少冗长的类型声明,而智能指针(如 std::unique_ptr 和 std::shared_ptr)则能有效管理内存,避免资源泄漏。此外,Lambda 表达式提供了更灵活的匿名函数机制,使得算法和回调的实现更加直观

         

目录

【一】可变模板参数

(1)引入

(2)语法

(3)使用参数包

(4)获取参数包大小

【二】列表初始化

(1)使用

(2)特点

【三】包装器

(1)包装函数

(2)包装仿函数

(3)包装成员函数

(4)混合包装

(5)知识点解释(注意)

(1)预留参数位置

(2)&类名::成员函数

(3)&对象名

(4)固定参数


【一】可变模板参数

注意:此语法我们知道即可,不用仔细研究!

(1)引入

之前我们学习了泛型编程:模板。例如:这些模板的参数T、V都是固定个数的,今天我们要学习的是不固定的模板参数,属于动态性的!

template<class T, class V>
void Func()
{;
}

在C语言中我们其实已经接触了可变的模板参数,比如:printf,它的参数是不固定的

printf("%s", "小明");printf("%s 喜欢 %s", "小明", "小美");

可变模板参数的好处:允许同一个模板接收0、1、2.....多个模板参数

(2)语法

可变模板参数的核心是  参数包  ,用  ...  表示,以函数模板为例:

模板参数包:表示 “一组类型参数”,语法是typename... ArgsArgs是参数包的名字,可自定义)

template<class...Args>
//或者
template<typename...Args>

函数参数包:表示 “一组对应类型的实际参数”,语法是Args... argsargs是参数包的名字,可自定义)

void Func(Args...args)
{;
}

此时我们如果想调用这个函数,可以直接传参,参数包会自己形成对应参数:

Func();
Func(1);
Func(1, 2.5);
Func(1, 2.5, "小明");

那么如何去使用参数包呢?比如打印、赋值这些功能?

(3)使用参数包

参数包(Args...args...)是一个 “打包” 的集合,编译器不知道怎么直接处理它(比如不能直接cout << args)。我们需要把它 “拆开”,逐个处理 —— 这个过程叫解包

在 解包 中递归是最简单的一种方法,我们先来看一下递归解法!

递归思路:

(1)递归我们需要保证至少有一个参数,因此需要手动增加一个模板参数,然后递归调用

template<typename T, typename... Args>
void Func(T first, Args... args) 
{cout <<" " << "参数:" << first;Func(args...);
}

(2)当没有参数时,调用结束终止函数

// 递归终止条件
void Func() 
{cout << endl;cout << "递归结束" << endl;
}

(3)提前写一个声明:让函数模板知道有结束函数,放心调用

// 提前声明:让模板函数知道“无参数版本存在”
void Func();

效果展示如下:

(4)获取参数包大小

这个语法有点奇怪,我们记住就行:sizeof...

【二】列表初始化

我们先看看之前的初始化方式(我感觉还挺好用的,但是新版本出来了我们得无条件接受!):

现在C++11版本出来之后,引入了 {} ,几乎所有的场景都可以用它初始化,下面我们看如何使用

(1)使用

比如内置类型:省略了赋值符号,相较于旧版本如果初始错误会报错,旧版本只是警告

int a{ 10 };//等价于 int a = 10;double b{ 2.4 };//等价于 double b = 2.4;bool c{ true };//等价 bool c = true;

比如数组类型:同理可省略赋值符号

int arr[]{ 1,2,3,4 };//相当于 int arr[]={1,2,3,4}pair<int, int> brr[]{ {1,2},{2,4},{3,3} };

其余自定义类型一样可以省略赋值符号直接 {} ,这里就不列举了!

(2)特点

空 {} 表示默认初始化,例如:

对于自定义类型,我们看个例子:

当你写{ }时,编译器会自动把它转换成initializer_list类型。而标准库容器(vectormap等)的构造函数都支持接收initializer_list参数,所以才能直接用{}初始化

【三】包装器

包装器:比如不同的工具(比如函数、lambda、仿函数等)如果想把它们存到同一个容器里,或作为统一的参数传递,直接操作会很麻烦。包装器就是为解决这个问题而生的(包装不同功能的工具可以统一调用)

我们今天要学习的是其中一种包装器:function

作用:把各种可调用对象包装成一个统一类型的对象

头文件:#include <functional>

只要某个可调用对象的参数类型返回值类型符合预期就可以用来包装,例如:

  • 普通函数 int add(int, int)
  • lambda 表达式 [](int a, int b) { return a - b; }
  • 仿函数(重载 operator() 的类)struct Multiply { int operator()(int, int); }

声明格式:std::function<函数签名>,其中 "函数签名" 是返回值类型(参数类型列表)

比如:std::function<int(int, int)> 表示:能接收两个int参数,返回int的可调用对象

(1)包装函数

例如:现在有一个普通函数和一个lambda表达式

int Func1(int a, int b)
{return a + b;
}

现在我对它进行包装:

// 定义一个能包装 "int(int, int)" 签名的function
function<int(int, int)> func;// 绑定普通函数
func = Func1;// 调用(和调用普通函数一样)
std::cout << func(2, 3) << std::endl; // 输出 5

(2)包装仿函数

例如现在有一个仿函数:

struct Func
{int operator()(int a, int b){return a * b;}
};

我们对它进行包装:

//定义包装器
function<int(int, int)> V;//绑定仿函数
V = Func();//调用
cout << V(2, 3) << endl;

(3)包装成员函数

例如现在有一个成员函数:

class Func1
{
public:int divide(int a, int b) const {return a * b;}
};

现在进行包装器包装:

//实例化对象
Func1 V;
//包装器
function<int(int, int)> func1;
 //绑定成员函数func1 = bind(& Func1::divide, V, placeholders::_1, placeholders::_2);//或者func1 = bind(& Func1::divide, Func1(), placeholders::_1, placeholders::_2);

注意:placeholders::_1, placeholders::_2 和 & 符号后面会在知识点解释里面讲解!

 // 调用:(8, 2)cout << func1(8, 2) << endl; // 16

(4)混合包装

一个包装器似乎只能绑定一个目标,那么如何绑定多个呢?没错用vector!

例如现在有三种工具:函数、仿函数和成员函数

void Func1()
{cout << "Func1" << endl;
}struct Func2
{void operator()(){cout << "Func2" << endl;}
};class Func3
{
public:void divide() {cout << "Func3" << endl;}
};

现在我对它们进行包装调用:

//实例化类
Func3 func;//包装器
vector<function<void()>> V;//包装
V.push_back(Func1);
V.push_back(Func2());
V.push_back(bind(&Func3::divide, func));//调用
for (auto e : V)
{//这里的e相当于拿到了每一个vector内容,也就是包装器参数调用e();
}

(5)知识点解释(注意)
(1)预留参数位置

placeholders::_1placeholders::_2为预留参数位置,它在命名空间placeholders里面,有多少参数就传多少即可!这个命名空间里面的参数个数范围在_1 ~ _20

例如:没有参数就不用传(如果传了_1和_2编译就会报错,因为成员函数没有接收位置)

bind(&Func3::divide, func)

例如:有两个参数就传_1 , _2,以此类推

bind(&Func3::divide, func, placeholders::_1、placeholders::_2)

注意:如果我们想交换参数的位置,就直接交换placeholders::_1placeholders::_2位置

函数调用的第一个参数永远传给placeholders::_1,然后_1再传给函数的第一个参数

函数调用的第二个参数永远传给placeholders::_2,然后_2再传给函数的第二个参数

总结:函数调用传参的位置有固定传送对象的,而传送对象有固定传送位置的

例如:

如果我们交换了_1 , _2之后,也就交换了函数参数的位置,例如:

(2)&类名::成员函数

这个叫获取成员函数指针

语法规则:要获取类的非静态成员函数的指针,必须使用 &类名::成员函数名 的形式

普通函数(如 void Func1())的指针可以直接用 Func1 或 &Func1(两种写法等价)

bind(Func3::divide, func)
bind(&Func3::divide, func)

成员函数必须显式用 &,不能直接写 Func3::divide(否则编译器无法识别为 “成员函数指针”)

bind(&Func3::divide, func)
(3)&对象名

总结精髓:避免拷贝

语法规则:&func 是对 Func3 类型的对象 func 取地址,得到 Func3* 类型的指针(即对象的地址)

作用:将这个对象指针传递给 bind,是为了告诉 bind当绑定后的可调用对象被执行时,要在 func 这个对象上调用 divide 成员函数

替代写法:除了传递 “对象指针”,也可以传递 “对象引用”(用 std::ref(func)),效果类似,但传递指针(&func)更直接,且能避免不必要的对象拷贝

(4)固定参数

固定参数的位置会传固定值,例如:

bind(&Func3::divide, func, placeholders::_1、placeholders::_2、10)

那么下面这个成员函数就会接收:placeholders::_1、placeholders::_2、10

class Func1
{
public:int divide(int a, int b,int c) const {return a * b;}
};

http://www.xdnf.cn/news/1439569.html

相关文章:

  • 从公共形象到专属定制,井云交互数字人满足金融/政务多元需求
  • etcd对比redis
  • MySQL--CRUD
  • Oracle 10g 安装教程(详解,从exe安装到数据库配置,附安装包)​
  • 食物分类案例优化改进 (数据增强,最优模型保存和使用)
  • oracle 从一张表更新到另外一张表的方法(MERGE)
  • IO进程线程;进程,发送信号;进程,消息队列通信;0903
  • 如何利用SMS、RDS把服务从阿里云迁移到华为云
  • FastGPT社区版大语言模型知识库、Agent开源项目推荐
  • 矿山 6KV 不接地系统中的绝缘监测解决方案
  • 简述 Java 的异常体系结构。Error 和 Exception 有什么区别?
  • 小米fastboot不能被电脑识别但手机正常使用模式时能被电脑识别
  • 【工具变量】“国家级大数据综合试验区”试点城市DID(2000-2024年)
  • FPGA ad9248驱动
  • Windows/Linux下vscode+vcpkg管理C++包链接方法
  • CD75.【C++ Dev】异常
  • 从发现到恢复,看瑞数信息如何构建“抗毁重构”实战路径
  • 网站建设需要多少费用?网站建设的步骤和流程?
  • 01-Redis 发展简史与核心定位解析:从诞生到三大产品矩阵
  • 涉私数据安全与可控匿名化利用机制研究(上)
  • Zotero白嫖腾讯云翻译
  • AI优化SEO关键词策略指南
  • 计算机网络---CA证书体系(Certificate Authority)
  • 机器学习从入门到精通 - 逻辑回归为什么是分类之王?深入决策边界与概率校准
  • 亚马逊ASIN定位广告想爆单?先搞懂流量逻辑!多账号增效策略直接用
  • Java版本兼容性:JDK 21的SDK在JDK 1.8使用
  • 嵌入式学习 day63 LCD屏幕驱动、ADC驱动、HC-SR04、ds18b20
  • 什么是好的系统设计
  • LangGraph MCP智能体开发
  • 【JavaEE】(22) Spring 事务