Предположим, у меня есть следующий код:
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;
}
Существует по крайней мере одна проблема с этим фрагментом кода. Например, он расширит «r» и «n» в операторе цикла For в соответствии с определением макроса. Но вы поняли.
Цель здесь состоит в том, чтобы разработать метод для ввода в уравнения, которые можно было бы легко прочитать и проверить на наличие ошибок. Я открыт для решений, не связанных с макросами, хотя мне кажется, что здесь могут помочь макросы.
Что касается нерабочего фрагмента кода, который я разместил выше, чтобы проиллюстрировать, как, по моему мнению, может выглядеть решение … возможно ли использовать макросы таким образом, чтобы подстановка макросов происходила только внутри тела цикла For? или, по крайней мере, до после оператор For-loop?
Возможным решением является определение всех макросов непосредственно перед 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
На мой взгляд, макросы — не лучшее решение этой проблемы, но я не могу найти другого решения.
Обычно хорошей идеей является разделение концептуально разных вещей. Это обычно имеет большое значение для улучшения ясности кода, удобства сопровождения и гибкости.
Здесь есть как минимум две разные вещи:
Вы перебираете массивы данных.
Вы что-то вычисляете.
Лучшее, что вы можете сделать, это разделить эти вещи на разные функции или, еще лучше, классы. Что-то вроде этого сделало бы:
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 );
}
Обратите внимание на следующее:
Если вы передумаете о контейнерах хранения (например, как предложил Марк Б, замените P [2] [2] [10] на P [10] [2] [2] по соображениям производительности), вычислительный код выиграет » Это никак не повлияет, только относительно простой цикл немного изменится.
Если вам нужно выполнить одно и то же вычисление в 10 разных местах, вам не нужно будет копировать вычислительный код: вы просто вызовете MyFavoriteMatrix там.
Вы обнаружите, что вычислительный код нужно изменить, вам нужно изменить его только в одном месте: в конструкторе MyFavoriteMatrix.
Каждый кусок кода выглядит аккуратно, поэтому меньше шансов на опечатку.
И все, что вы получаете, разделяя концептуально разные вещи — вычисления и итерации.
Что не так с хорошими старомодными локальными переменными цикла? Я собираюсь предположить, что вы называете ваши векторы чем-то более значимым, чем 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;
}
Удивлен, никто не предложил использовать ссылки. 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;
}