以类似于 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 中的控制台,同时考虑维度。最后,动态分配的数据被释放。
进一步迭代
代码可以进一步改进,例如文件名应该是一个函数参数。您应该检查浮点数是否可以读取或是否存在转换错误。但这段代码仍然是一个很好的起点。
我正在尝试在不使用集成编码器的情况下将 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 中的控制台,同时考虑维度。最后,动态分配的数据被释放。
进一步迭代
代码可以进一步改进,例如文件名应该是一个函数参数。您应该检查浮点数是否可以读取或是否存在转换错误。但这段代码仍然是一个很好的起点。