Как обрабатывать переменные размеры массива в качестве аргументов для функции? - PullRequest
1 голос
/ 28 апреля 2019

Мне удалось преобразовать более 5000 строк кода программы на Fortran 77 в C ++ вручную, но преобразование не прошло по плану. Поэтому я пытаюсь отладить программу на C ++, используя мою программу на Fortran 77. В Фортране я разработал подпрограмму, которая принимает массив и распечатывает индекс массива и его значение в файле с разделителями-запятыми. Я пытаюсь сделать то же самое в C ++. но запутаться в объявлении "double temp1 [tempi]". Массив не обязательно должен быть одинакового размера во всех вызовах функции. Поэтому я не могу закодировать это, скажем, «double temp1 [21]», потому что в следующий раз это 25. Fortran передает массивы по ссылке. Что вы предлагаете мне сделать?

Мне удалось это сделать для программы на Фортране. Идея состоит в том, чтобы взять дамп переменной памяти из программы c ++ и сравнить значения в Excel, используя vba, чтобы увидеть, какое из них больше всего изменяется, а затем сосредоточиться на этой переменной в программе C ++, начиная с точки отладки.

c ++ логика кода:

 void singlearrayd(double temp1[tempi], int tempi, string str1){
   for (int md_i = 1; md_i <= tempi; md_i++){
   cout << temp1[md_i] << "," << str1 << "(" << md_i << ")";
   }
 }

 int main(){
   double askin[22];
   double fm[26];
   singlearrayd(askin,22,"askin");
   singlearrayd(fm,26,"fm");
   return 0;
 }

Логика кода Fortran 77:

 PROGRAM PRINT_MEMORY
 real*8 :: ASKIN(21)
 real*8 :: FM(25)
 CALL SINGLEARRAYD(ASKIN,21,"ASKIN")
 CALL SINGLEARRAYD(FM,25,"FM")
 END PRINT_MEMORY

 SUBROUTINE SINGLEARRAYD(TEMP1,TEMPI,STR1)
 IMPLICIT NONE
 CHARACTER(LEN=*) :: STR1
 INTEGER*4 MD_I,TEMPI
 REAL*8, DIMENSION(1:TEMPI) :: TEMP1
 DO MD_I = 1, TEMPI
 WRITE(51,'(ES25.16E3,A1,A25,A1,I5,A1)') TEMP1(MD_I),',',STR1,'(',
1 MD_I,')'
 ENDDO
 ENDSUBROUTINE SINGLEARRAYD

Ответы [ 3 ]

4 голосов
/ 28 апреля 2019

В вашем коде несколько проблем.

В C ++ собственный массив (например, askin в main()) преобразуется в указатель при передаче в функцию.Поэтому нет необходимости объявлять измерение в массиве в списке аргументов, НО все же необходимо передать второй аргумент, так как вы указываете размер.

Это означает, что функция C ++ должна иметь форму

void singlearrayd(double temp1[], int tempi, std::string str1)

или (эквивалентно)

void singlearrayd(double *temp1, int tempi, std::string str1)

Обратите внимание, что в качестве полного третьего имени я указал тип третьего аргумента как std::string.Во многих случаях лучше избегать using namespace std.

Вторая проблема заключается в том, что вы предполагаете, что индексирование массивов Fortran и индексирование массивов C ++ совпадают.В действительности индексирование массива Fortran основано на 1 (первый элемент массива по умолчанию имеет индекс один), а индексирование массива C ++ основано на 0 (первый элемент в массиве имеет нулевой индекс).Использование индексации массивов Fortran в C ++ вызывает неопределенное поведение, потому что оно будет обращаться к элементам за пределами допустимого диапазона.

Третья (потенциальная) проблема заключается в том, что ваша функция определяет две переменные с именем md_i (одна в функции, а другая в цикле).Лучше избегать этого.

Обращение ко всему вышеперечисленному превратит вашу функцию в (полностью)

void singlearrayd(double temp1[], int tempi, std::string str1)
{
    for (int md_i = 0; md_i < tempi; ++md_i)    // note the differences here carefully
    {
        cout << temp1[md_i] << "," << str1 << "(" << md_i << ")";
    }
 }

Четвертая проблема заключается в том, что main() в C ++ возвращает int, а не void.

Пятая проблема заключается в том, что main() не инициализирует массивы до того, как singlearrayd() напечатает их.В Фортране массивы, локальные для функции, (часто) инициализируются нулями.В C ++ они неинициализированы по умолчанию, поэтому доступ к их значениям (например, для их печати) дает неопределенное поведение.

int main()
{
   double askin[21] = {0.0};   // initialise the first element.  Other elements are initialised to zero
   double fm[21] = {0.0};
   singlearrayd(askin,21,"askin");
   singlearrayd(fm,25,"fm");
}

Это заставит ваш код работать.Однако на практике возможны улучшения.Первое улучшение заключается в использовании стандартного контейнера, а не массива.Стандартные контейнеры знают свой размер, что позволяет упростить вашу функцию.Во-вторых, передайте нетривиальные аргументы (например, контейнеры или строки) по ссылке - и предпочтительно const ссылку, если в аргумент не было внесено никаких изменений.В отличие от Fortran, где аргументы функции часто передаются по ссылке BY DEFAULT, необходимо БЕЗОПАСНО вводить ссылки в C ++.

#include <vector>

void singlearrayd(const std::vector<double> &temp1, const std::string &str1)
{
    for (std::size_t md_i = 0; md_i < temp1.size(); ++md_i)
   {
        cout << temp1[md_i] << "," << str1 << "(" << md_i << ")";
   }
}

int main()
{
   std::vector<double> askin(21);   // askin has 21 elements, initialised to zero
   std::vector<double> fm(21);
   singlearrayd(askin, "askin");
   singlearrayd(fm, "fm");
}

C ++ контейнеры также поддерживают итераторы - которые на практике безопаснее и часто более эффективны - чемиспользуя индексирование массива.Я оставлю это в качестве упражнения для вас, чтобы научиться их использовать.

Ключевое сообщение, однако: не думайте, что простой механический перевод с Fortran на C ++ будет работать.Вы уже продемонстрировали подводные камни такого предположения.Потратьте время на изучение C ++ ПЕРЕД попыткой перевести слишком много кода с Fortran на C ++.Это необходимо как для правильной работы кода C ++, так и для его эффективной работы.

2 голосов
/ 28 апреля 2019

Более современная реализация будет

 #include <string>
 #include <array>
 #include <iostream>

 template <std::size_t size, class U>
 void singlearrayd(const std::array<U, size>& temp1, const std::string& str1){
   int i = 0;
   for (const auto& x : temp1)
      std::cout << x << "," << str1 << "(" << (i++) << ")";
   }

 int main(){
   std::array<double, 21> askin;
   std::array<double, 21> fm; 
   singlearrayd(askin, "askin");
   singlearrayd(fm, "fm");

   return 0;
 }

Обратите внимание, что в коде выше два массива askin и fm не инициализируются.Предположительно, в реальном коде вы бы уже инициализировали их перед вызовом singlarrayd.Также помните, что main должен возвращать int.

0 голосов
/ 29 апреля 2019

Спасибо за ваше ценное понимание и комментарии. Я думаю, что лучший подход был использовать

    void singlearrayd(double *temp1, int tempi, std::string str1)

Расширяя эту идею и проводя дополнительные исследования с помощью Google, я смог расширить эту идею для обработки 2D и 3D массивов.

    void doublearrayd(double *temp1, int tempi, int tempj, std::string str1){
        for (int md_j = 1; md_j<tempj; md_j++){
            for (int md_i = 1; md_i<tempi; md_i++){
                std::cout << *(temp1 + md_i*tempj + md_j) << "," << str1 << "(" << md_i << ";" << md_j << ")" << std::endl;
            }
        }
    }

    void triplearrayd(double *temp1, int tempi, int tempj, int tempk, std::string str1){
        for (int md_k = 1; md_k < tempk; md_k++){
            for (int md_j = 1; md_j<tempj; md_j++){
                for (int md_i = 1; md_i<tempi; md_i++){
                    std::cout << *(temp1 + md_i*tempj*tempk + md_j*tempk + md_k) << "," << str1 << "(" << md_i << ";" << md_j << ";" << md_k << ")" << std::endl;
                }
            }
        }
    }

https://en.wikipedia.org/wiki/Row-_and_column-major_order

Как передать динамический многомерный массив в функцию?

...