跟我学c++中级篇——c++17中的折叠表达式

一、折叠表达式

fold expression,折叠表达式,为什么要出这个东西?其实目的指向性非常明白,仍然是简化编程。但是有一个问题,不断的简化编程方向性一定是好的。但带来一个副作用,可能一些没接触过的c++开发人员会一脸蒙圈。折叠表达式长什么样子,在开发者眼里,它就是三个点(…),和变参模板里的一样,三个点。可能严格意义上讲不是这样,但对于刚刚接触的人来说就只会专注到这三个点上。
那么什么是折叠表达式呢?“以二元运算符对形参包进行规约”。太抽象了,不明白。
先搞一下什么是fold?“In functional programming, fold (also termed reduce, accumulate, aggregate, compress, or inject) refers to a family of higher-order functions that analyze a recursive data structure and through use of a given combining operation, recombine the results of recursively processing its constituent parts, building up a return value. Typically, a fold is presented with a combining function, a top node of a data structure, and possibly some default values to be used under certain conditions. The fold then proceeds to combine elements of the data structure’s hierarchy, using the function in a systematic way”.
这些话的主要意思很明白,第一,它是在函数式编程语言中应用起来的;第二,它通过递归的方式搞定解析;第三,要有给定的组合操作。举一个简单的例子,对集合{1100}求和,其实就可以搞成1+{2100},1+{2+{3~100}…},看到没,递归+和操作。一般在编程中这个操作叫Sum。把这个抽象一下,就是折叠,fold(fn,start,list),其中fn是Sum,list是操作的内容,另外的是启动初始值。折叠有两种形势,其实就是两种递归方式,一个是前向递归一个是后向递归,而后者又可以通过循环优化。
理解了折叠,再来理解折叠表达式,就明白了,就是对一个表达式搞折叠呗。有折叠就有展开。展开就容易理解了吧,把这个折叠回复成可以正常识别的式子。
折叠表达式这样看来,应该是向函数式语言学引入的一个技术,它在简化递归进行实例化函数参数模板的操作,使更加清晰也更加简便。

二、用法

折叠表达式的用法如下:

语法
折叠表达式	 
( pack op ... )	(1)	
( ... op pack )	(2)	
( pack op ... op init )	(3)	
( init op ... op pack )	(4)	
 
1) 一元右折叠
2) 一元左折叠
3) 二元右折叠
4) 二元左折叠
运算符	-	下列 32 个二元运算符之一:+ - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*。在二元折叠中,两个 运算符 必须相同。
形参包	-	含有未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式
初值	-	不含未展开的形参包且在顶层不含优先级低于转型(正式而言,是 转型表达式)的运算符的表达式
注意开闭括号也是折叠表达式的一部分。

解释
折叠表达式的实例化按以下方式展开成表达式 e:

1) 一元右折叠 (E op ...) becomes (E1 op (... op (EN-1 op EN)))
2) 一元左折叠 (... op E) becomes (((E1 op E2) op ...) op EN)
3) 二元右折叠 (E op ... op I) becomes (E1 op (... op (EN−1 op (EN op I))))
4) 二元左折叠 (I op ... op E) becomes ((((I op E1) op E2) op ...) op EN)
(其中 N 是包展开中的元素数量)

折叠表达式的用法,最典型就是就是应用在变参模板上。可以大大的节省代码数量,直观上会更清爽,至于易不易于理解,就不好说了。

三、例程

用一个传统的变参模板的例子就很好的诠释了这个折叠表达式:

//传统写法
int TestVT()
{
    return 1;
}

template<typename T,typename... Args>
int TestVT(T var0, Args... varn)
{ 
    std::cout << "args len is:" << sizeof...(varn) << std::endl;
    return var0 + TestVT(varn...);
}


int main()
{
    std::cout << "value is:"<< TestVT(1, 2, 3)<< std::endl;
    system("pause");

    return 0;
}

//变参写法
template <typename T>
int TestVT(T t) {
    std::cout << "cur value:" << t << std::endl;
    return t;
}

//c++17折叠表达式展开
template <typename... Args>
void TestVT(Args... args)
{
    //逗号表达式+初始化列表
    //int v[] = { (Add1(args),0)... };
 
    int d = (Add1(args)+ ...);
    std::cout << "cur sum:" << d << std::endl;
}


int  main()
{
    TestVT(1, 2,7);
    return 0;
}

几乎是大部分的运算符都支持折叠表达式,当然不支持的也有,有些特殊的,大家遇到注意即可。既然是可以处理变参,那么变参基类也可以折叠,变参数据类型也可以折叠。

template<typename... Bases>
class Sub : private Bases...
{
public:
    void Display()
    {
        (..., Bases::Display());
    }
};
class B1 {
public:
    void Display() { std::cout << "call B1 function" << std::endl; }
};
class B2 {
public:
    void Display() { std::cout << "call B2 function" << std::endl; }
};

int main()
{
    Sub<B1, B2> s;
    s.Display(); 
    return 0;
}

前面提到的变参是0个时也能折叠,但是就不能使用+和-号这些二元操作符了。只能使用与或非以及逗号等OP了。

template <typename... Args>
int IsTrue(const Args&... args) {
    return (... && args);
}

template <typename... Args>
int eitherTrue(const Args&... args) {
    return (... || args);
}

void ZeroEp() {
    std::cout << IsTrue(1, 1, 0) << IsTrue(1, 1) << IsTrue() << std::endl;
    std::cout << eitherTrue(1, 1, 0) << eitherTrue(0, 0) << eitherTrue() << std::endl;

}

int main()
{
    ZeroEp();
    return 0;
}

最后再看一下官网的例子:

#include <iostream>
#include <vector>
#include <climits>
#include <cstdint>
#include <type_traits>
#include <utility>
 
template<class... Args>
void printer(Args&&... args)
{
    (std::cout << ... << args) << '\n';
}
 
template<class T, class... Args>
void push_back_vec(std::vector<T>& v, Args&&... args)
{
    static_assert((std::is_constructible_v<T, Args&> && ...));
    (v.push_back(args), ...);
}
 
// 基于 http://stackoverflow.com/a/36937049 的编译期端序交换 
template<class T, std::size_t... N>
constexpr T bswap_impl(T i, std::index_sequence<N...>)
{
    return (((i >> N*CHAR_BIT & std::uint8_t(-1)) << (sizeof(T)-1-N)*CHAR_BIT) | ...);
}
 
template<class T, class U = std::make_unsigned_t<T>>
constexpr U bswap(T i)
{
    return bswap_impl<U>(i, std::make_index_sequence<sizeof(T)>{});
}
 
int main()
{
    printer(1, 2, 3, "abc");
 
    std::vector<int> v;
    push_back_vec(v, 6, 2, 45, 12);
    push_back_vec(v, 1, 2, 9);
    for (int i : v) std::cout << i << ' ';
 
    static_assert(bswap<std::uint16_t>(0x1234u) ==
                                       0x3412u);
    static_assert(bswap<std::uint64_t>(0x0123456789abcdefull) ==
                                       0xefcdab8967452301ULL);
}

源码面前,了无秘密。

四、总结

曹孟德说:英雄要有“有包藏宇宙之机”。所以语言发展也要如此,要敢于自我革命,不断吸取先进的好的技术进来。不能闭门造车,更不能循守旧,自高自大。“骄傲自满必翻车”。既不要放弃传统的优势,又勇于学习和吸收好的技术和经验,只有这样,c++才能做为一门优秀的语言,一直不断的发展下去。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是关于栈应用(表达式求值)的学习笔记。 ## 1. 表达式求值的基本概念 ### 1.1 表达式 表达式是我们平时写的表达式,如:2 + 3 * 4 - 6 / 2。 ### 1.2 后缀表达式 后缀表达式也叫逆波兰表达式,它是一种不包含括号的表达式。在后缀表达式,运算符在操作数的后面,因此也叫后缀表示法。例如,上面的表达式的后缀表达式为:2 3 4 * + 6 2 / -。 ### 1.3 前缀表达式 前缀表达式也叫波兰式,它与后缀表达式类似,只是运算符在操作数的前面。例如,上面的表达式的前缀表达式为:- + * 3 4 2 / 6 2。 ### 1.4 运算符优先级 在表达式,运算符有不同的优先级。通常,乘法和除法的优先级高于加法和减法。如果有括号,则括号内的表达式优先计算。 ### 1.5 表达式转后缀表达式表达式转换成后缀表达式的过程,也叫表达式的后缀表达式化。具体的转换规则如下: - 遍历表达式的每个元素。 - 如果当前元素是操作数,则将其加入后缀表达式。 - 如果当前元素是运算符,则判断其与栈顶运算符的优先级,如果栈顶运算符优先级高于或等于当前运算符,则弹出栈顶运算符加入后缀表达式,并继续比较下一个栈顶运算符,直到当前运算符的优先级高于栈顶运算符或栈为空时,将当前运算符入栈。 - 如果当前元素是左括号“(”,则直接入栈。 - 如果当前元素是右括号“)”,则依次弹出栈顶运算符加入后缀表达式,直到遇到左括号为止,此时将左括号弹出,但不加入后缀表达式。 ### 1.6 后缀表达式求值 将后缀表达式求值的过程,也叫后缀表达式的求值。具体的求值规则如下: - 遍历后缀表达式的每个元素。 - 如果当前元素是操作数,则将其入栈。 - 如果当前元素是运算符,则弹出栈顶的两个操作数,进行运算,并将运算结果入栈。 - 遍历完后缀表达式后,栈只剩下一个元素,即为表达式的值。 ## 2. 表达式求值的实现 ### 2.1 表达式转后缀表达式的实现 表达式转后缀表达式可以使用栈来实现。具体的代码实现如下: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; // 判断一个字符是否为操作符 bool isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } // 判断两个操作符的优先级 int getPriority(char op1, char op2) { if ((op1 == '*' || op1 == '/') && (op2 == '+' || op2 == '-')) { return -1; } else if ((op1 == '+' || op1 == '-') && (op2 == '*' || op2 == '/')) { return 1; } else { return 0; } } // 将表达式转换成后缀表达式 string infixToPostfix(string infix) { stack<char> opStack; // 运算符栈 string postfix; // 后缀表达式 for (char c : infix) { if (isdigit(c)) { // 如果是数字,直接加入后缀表达式 postfix += c; } else if (isOperator(c)) { // 如果是操作符 while (!opStack.empty() && opStack.top() != '(' && getPriority(opStack.top(), c) >= 0) { postfix += opStack.top(); // 弹出栈顶操作符加入后缀表达式 opStack.pop(); } opStack.push(c); } else if (c == '(') { // 如果是左括号,直接入栈 opStack.push(c); } else if (c == ')') { // 如果是右括号 while (!opStack.empty() && opStack.top() != '(') { postfix += opStack.top(); // 弹出栈顶操作符加入后缀表达式 opStack.pop(); } opStack.pop(); // 弹出左括号 } } while (!opStack.empty()) { // 将剩余的操作符加入后缀表达式 postfix += opStack.top(); opStack.pop(); } return postfix; } int main() { string infix = "2+3*4-6/2"; string postfix = infixToPostfix(infix); cout << postfix << endl; // 输出后缀表达式:234*+62/- return 0; } ``` ### 2.2 后缀表达式求值的实现 后缀表达式求值也可以使用栈来实现。具体的代码实现如下: ```cpp #include <iostream> #include <stack> #include <string> using namespace std; // 判断一个字符是否为操作符 bool isOperator(char c) { return c == '+' || c == '-' || c == '*' || c == '/'; } // 计算两个操作数的运算结果 int calculate(int a, int b, char op) { if (op == '+') { return a + b; } else if (op == '-') { return a - b; } else if (op == '*') { return a * b; } else { return a / b; } } // 计算后缀表达式的值 int evaluate(string postfix) { stack<int> numStack; // 操作数栈 for (char c : postfix) { if (isdigit(c)) { // 如果是数字,将其转换成整数并入栈 int num = c - '0'; numStack.push(num); } else if (isOperator(c)) { // 如果是操作符 int b = numStack.top(); numStack.pop(); int a = numStack.top(); numStack.pop(); int result = calculate(a, b, c); numStack.push(result); } } return numStack.top(); } int main() { string postfix = "234*+62/-"; int result = evaluate(postfix); cout << result << endl; // 输出计算结果:8 return 0; } ``` ## 3. 总结 栈在表达式求值的应用是很常见的,掌握了这个知识点,对于编写计算器等应用程序会有很大的帮助。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值