C++宏,将可读方程转换为相应的代码

C++ macros to convert readable equations into corresponding code

本文关键字:代码 转换 方程 C++      更新时间:2023-10-16

假设我当前有以下代码:

double P[2][2][10]; 
std::vector<double> b, r, n; 
//
// Assume that 10 doubles are pushed to each vector and
// that P has all its allocated values set.
//    
for(int t=0; t<10; ++t) {
    P[0][0][t] = b[t]*r[t]+n[t];
    P[0][1][t] = b[t]*2.0*r[t]+(1.0-n[t]);
    P[1][0][t] = b[t]*b[t]+r[t]*n[t];
    P[1][1][t] = r[t]+n[t];
}

这是一个微不足道的例子来说明我的问题。在实际情况下,P通常是P[9][9][100],并且方程会更加混乱。基本上,我的问题是,如何使用宏来使这些方程更可读?

特别是,这里有一个不工作的代码片段,来说明我希望这个问题的解决方案看起来如何:

#define P(i,j) P[i][j][t]
#define b b[t]
#define r r[t]
#define n n[t]
for(int t=0; t<10; ++t) {
    P(0,0) = b*r+n;
    P(0,1) = b*2.0*r+(1.0-n);
    P(1,0) = b*b+r*n;
    P(1,1) = r+n;
}

此代码片段至少有一个问题。例如,它将根据宏定义展开For循环语句中的"r"answers"n"。但你明白了。

这里的目标是开发一种输入方程的方法,该方法可以更容易地读取和检查错误。我对非宏解决方案持开放态度,尽管在我看来,宏在这里可能会有所帮助。


关于我在上面发布的非工作代码片段,以说明我认为解决方案可能是什么样子。。。是否可以以这样一种方式使用宏,即宏替换仅发生在For循环体内部?或者至少要等到For循环语句之后的

一个可行的解决方案是定义for之前的所有宏,然后定义#undef之后的所有宏。示例:

#define P(i,j) P[i][j][t]
#define b b[t]
#define r r[t]
#define n n[t]
for(int t=0; t<10; ++t) {
    P(0,0) = b*r+n;
    P(0,1) = b*2.0*r+(1.0-n);
    P(1,0) = b*b+r*n;
    P(1,1) = r+n;
}
#undef P
#undef b
#undef r
#undef n

在我看来,宏不是解决这个问题的最佳方案,但我找不到任何其他解决方案。

这里好的老式循环局部变量有什么问题?我假设你把向量称为比b更有意义的东西,所以我会给它们取稍长的名字。我还自由地在你非常密集的方程中添加了一些眼睛清晰度,允许空白:

double P_arr[10][2][2]; 
std::vector<double> b_arr, r_arr, n_arr; 
//
// Assume that 10 doubles are pushed to each vector and
// that P has all its allocated values set.
//    
for(int t = 0; t < 10; ++t)
{
    const double b = b_arr[t];
    const double r = r_arr[t];
    const double n = n_arr[t];
    double** P = P_arr[t];
    P[0][0] = b * r + n;
    P[0][1] = b * 2.0 * r + (1.0 - n);
    P[1][0] = b * b + r * n;
    P[1][1] = r + n;
}

将概念上不同的东西分开通常是个好主意。这通常会大大提高代码的清晰度、可维护性和灵活性。

这里至少有两种不同的东西:

  1. 您可以循环遍历数据数组。

  2. 你计算了一些东西。

你能做的最好的事情就是把这些东西分成不同的函数,或者更好的是,类。像这样的东西就可以了:

class MyFavoriteMatrix
{
  private:
    double m_P[2][2];
  public:
    MyFavoriteMatrix( double b, double r, double n ) {
      m_P[0][0] = b*r+n;
      m_P[0][1] = b*2.0*r+(1.0-n);
      m_P[1][0] = b*b+r*n;
      m_P[1][1] = r+n;
    }
    double Get( int i, int j ) {
      return m_P[i][j];
    }
}

然后你的循环会是这样的:

for(int t = 0; t < 10; ++t)
{
  // Computation is performed in the constructor
  MyFavoriteMatrix mfm( b[t], r[t], n[t] );
  // Now put the result where it belongs
  P[0][0][t] = mfm.Get( 0, 0 );
  P[0][1][t] = mfm.Get( 0, 1 );
  P[1][0][t] = mfm.Get( 1, 0 );
  P[1][1][t] = mfm.Get( 1, 1 );
}

注意这样的解决方案:

  1. 如果您改变了对存储容器的看法(例如,正如Mark B所建议的,出于性能原因,将p[2][2][10]更改为p[10][2][2]),则计算代码根本不会受到影响,只有相对简单的循环会发生一些变化。

  2. 如果你需要在10个不同的地方执行相同的计算,你不必复制计算代码:你只需要在那里调用MyFavoriteMatrix。

  3. 如果你发现计算代码需要更改,你只需要在一个地方修改它:在MyFavoriteMatrix构造函数中。

  4. 每一段代码看起来都很整洁,因此拼写错误的可能性较小。

所有这些都是通过分离概念上不同的东西——计算和迭代得到的。

令人惊讶的是,没有人建议使用引用。http://en.wikipedia.org/wiki/Reference_(C%2B%2B)

typedef double Array22[2][2]; // for convenience...
for(int t = 0; t < 10; ++t)
{
    const double &b(b_arr[t]);
    const double &r(r_arr[t]);
    const double &n(n_arr[t]);
    Array22 &P(P_arr[t]);
    P[0][0] = b * r + n;
    P[0][1] = b * 2.0 * r + (1.0 - n);
    P[1][0] = b * b + r * n;
    P[1][1] = r + n;
}

这就是运算符的设计目的。下面是一个使用加法和乘法运算符的例子。您仍然需要根据需要添加其他。

#include <iostream>
#include <algorithm>
#include <vector>
#include <functional>
std::vector<int>& operator+(std::vector<int>& a, std::vector<int>& b) {
    std::transform (a.begin(), a.end(), b.begin(), a.begin(), std::plus<int>());
    return a;
}
std::vector<int>& operator*(std::vector<int>& a, std::vector<int>& b) {
    std::transform (a.begin(), a.end(), b.begin(), a.begin(), std::multiplies<int>());
    return a;
}
int main() {
    int a[6] = {1,2,3,4,5,6};
    int b[6] = {6,7,8,9,10,11};
    std::vector<int> foo(a, a+6);
    std::vector<int> bar(b, b+6);
    foo = foo + bar;
    std::cout << foo[0] <<std::endl;
}