以类似于 C 语言中的 MATLAB 的方式从 .csv 文件加载的最佳方法

Best method to load from .csv file in a similar manner to MATLAB in C language

我正在尝试在不使用集成编码器的情况下将 MATLAB 模拟转换为 C,以尝试自学 C。在 MATLAB 中导入和使用来自 Excel/csv 文件的数据,您可以手动导入使用 GUI 将数据导入工作区,然后您可以在其中另存为 .mat 文件并使用其中的变量,或者不推荐的方法是使用 "csvread"。我正在尝试在 C 中执行类似的操作。

我可以使用下面的代码从 csv 文件(720 行 x 3 列)获取数据,但是我正在努力将数据从 load_PV 函数传递到 main() 函数输出的数据是一个多维数组。我的主要问题是这是否是最好的方法并追求它,或者如果我 could/should 创建一个函数将每一列单独加载为循环中的新变量并将它们传递给 main() 函数

#include<stdio.h>
#include<string.h>
#include<stdint.h>
#include<stdlib.h>

float CA[720];
float P[720];
float V[720];
//~ static float ARRAY[720];

float load_PV(int r, int c, float DATA[720][3])
{
  char buf[720];
  //~ static float ARRAY[720];

  FILE *fp = fopen("PV_Data.csv", "r");

  if(!fp)
  {
    printf("Could Not Open File\n");
  }
  int i = 1;

  while(fgets(buf, 720, fp))
  {
    CA[i] = atof(strtok(buf, ","));
    P[i] = atof((strtok(NULL, ",")));
    V[i] = atof((strtok(NULL, ",")));
    DATA[i][1] = CA[i];
    DATA[i][2] = P[i];
    DATA[i][3] = V[i];
    i++;
  }

  //~ printf("%f\n", ARRAY[540][3]);

  return(DATA[720][3]);
}

int main()
{
    int r = 720;
    int c = 3;
    float DATA[r][c];

    float data = load_PV(r, c, DATA[720][3]);

    printf("%f\n", data);

    //~ int i = 1;

    //~ for(i = 1; i<=720; i++);
    //~ {
        //~ printf("%f", data[i][1]);
        //~ printf("\t");
        //~ printf("%f", data[i][2]);
        //~ printf("\t");
        //~ printf("%f\n", data[i][3]);
    //~ }
}

我预计我对 C 不太熟悉(你能告诉我吗?:P)输出只是一个 720x3 的浮点数组,但是在编译时我收到以下错误消息;

gcc -Wall -o "Test2" "Test2.c" (in directory: C:\Users\Student\Dropbox\C Projects\MATLAB_to_C)
Test2.c: In function 'main':
Test2.c:47:29: error: incompatible type for argument 3 of 'load_PV'
  float data = load_PV(r, c, DATA[720][3]);
                             ^
Test2.c:11:8: note: expected 'float (*)[3]' but argument is of type 'float'
  float load_PV(int r, int c, float DATA[720][3])
        ^
Compilation failed.

我不太熟悉指针,因为它们是我正在尝试学习的 C 的一个方面,我看到你可以用它们来完成这个,但我没有完全理解这些例子,所以没有使用它们,因为我正在尝试学习而不是复制。那么这种方法有效还是另一种更广泛使用的方法?

P.S。这不是家庭作业问题。我刚从大学毕业,看到更多的公司正在寻找 C 而不是 MATLAB 所以为了未来的发展而努力学习。这是一个爱好/专业发展练习

您正在混合 defining/declaring 数组和访问数组的语法。

这是一个参数声明:

float DATA[720][3]

此处DATA声明为数组

这是对数组成员的访问:

return(DATA[720][3]);

您正在访问该数组位置 [720][3] 处的元素。这是 float 类型的单个元素。您需要注意的是,C 中的索引从 0 开始。这意味着索引 [720][3] 是越界访问。最大范围是 [719][2].

调用该函数时出现同样的问题:

float data = load_PV(r, c, DATA[720][3]);

虽然该函数期望获得一个数组,但您只提供了另一个数组的 1 个单个元素。同样,这是对该数组的越界访问。

如果要传递数组,需要使用

float data = load_PV(r, c, DATA);

您还需要注意,数组作为参数传递时会衰减为指针。这意味着当您的参数列表如下所示时

int func(int array[123])

array 的类型不是 int[123],而是 int*int(*)。另外 sizeof(array) 是指针的大小而不是整个数组的大小。

你的函数也一样:

float load_PV(int r, int c, float DATA[720][3])

这里第三个参数的类型不是float[720][3]而是float(*)[3]。 因此,编译器抱怨预期参数类型和传递参数类型不匹配:

test2.c:11:8: note: expected 'float (*)[3]' but argument is of type 'float'
  float load_PV(int r, int c, float DATA[720][3])

在第一次迭代中你可以这样写:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    float CA;
    float P;
    float V;
} Entry;


#define ROWS 720

Entry *load_PV() {
    char buf[1024];

    FILE *fp;
    if ((fp = fopen("PV_Data.csv", "r")) == NULL) {
        printf("Could Not Open File\n");
        exit(-1);
    }

    Entry *entries = malloc(sizeof(Entry) * ROWS);

    for (int i = 0; i < ROWS && fgets(buf, sizeof(buf), fp); i++) {
        entries[i].CA = atof(strtok(buf, ","));
        entries[i].P = atof((strtok(NULL, ",")));
        entries[i].V = atof((strtok(NULL, ",")));
    }

    fclose(fp);

    return entries;
}

int main() {
    Entry *entries = load_PV();
    for (int i = 0; i < ROWS; i++) {
        Entry entry = entries[i];
        printf("%f %f %f\n", entry.CA, entry.P, entry.V);
    }
    free(entries);
    return 0;
}

变化

  • 因为每一行都包含值 CA、P 和 V,所以我们可以使用结构
  • a define 确定行数
  • 我们根据行数动态分配内存
  • FILE* 用 fclose(fp) 关闭

更加动态和稳健的解决方案

上面的代码是第一次迭代,但即使是业余爱好项目,它也可能不像您希望的那样动态和健壮。

那么should/can有待改进:

  • 维度(行数和列数)应该是动态的
  • 它应该处理文件中缺失的行
  • 它应该处理文件中缺失的列
  • 应返回读取的行数

然后代码在第二次迭代中可能如下所示:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int load_PV(float **dataPtr, int r, int c) {
    char buf[1024];

    FILE *fp;
    if ((fp = fopen("PV_Data.csv", "r")) == NULL) {
        fprintf(stderr, "Could Not Open File\n");
        exit(-1);
    }

    float *data = calloc(r * c, sizeof(float));
    if (!data) {
        *dataPtr = NULL;
        return 0;
    }
    *dataPtr = data;

    int row = 0;
    for (row = 0; row < r && fgets(buf, sizeof(buf), fp); row++) {
        char *token = strtok(buf, ",");
        if(token) {
            data[row * c] = strtof(token, NULL);
            for (int col = 1; col < c; col++) {
                token = strtok(NULL, ",");
                if(token) {
                    data[row * c + col] = strtof(token, NULL);
                } else {
                    fprintf(stderr, "missing value in row %d\n", row);
                }
            }
        } else {
            fprintf(stderr, "missing value in row %d\n", row);
        }
    }

    fclose(fp);

    return row;
}

int main() {
    int r = 720;
    int c = 3;
    float *data;

    int number_of_Rows = load_PV(&data, r, c);
    for (int i = 0; i < number_of_Rows; i++) {
        float *rowData = &data[i * c];
        printf("[%d]: ", i);
        for(int col = 0; col < c; col++) {
            printf("%f ", rowData[col]);
        }
        printf("\n");
    }
    if(data) {
        free(data);
    }
    return 0;
}

那么它是如何工作的?

在 main 中有一个名为 data 的指针要浮动。此指针的 地址 连同维度 (rows/columns) 一起传递给 load_PV。浮点数的内存是动态分配的,并用零填充。为每一行读取指定数量的浮点数。如果数据丢失,将向 stderr 写入一条消息。返回行数。然后将数据简单地输出到 main 中的控制台,同时考虑维度。最后,动态分配的数据被释放。

进一步迭代

代码可以进一步改进,例如文件名应该是一个函数参数。您应该检查浮点数是否可以读取或是否存在转换错误。但这段代码仍然是一个很好的起点。