如何正确指向 C 中的结构成员?
How to correctly point to a struct member in C?
我在 C 中创建了一个非常简单的 "csvread" 函数,它将按照暗示从 CSV 文件中读取。 (在这种情况下,为了测试,我稍微编辑了它以便我可以将虚拟数据写入文件然后读取它)。我创建了一个结构来存储复杂数据。但是,我的 psuedo-csv 文件只包含我需要使用的数据的真实部分。我想将此数据存储到 "data.real" 数组中。但是,我似乎无法理解正确的语法。 (虽然,诚然,这可能更多地是一个完全理解指针的问题,而不仅仅是语法)。如有任何帮助,我们将不胜感激!
在下面的代码中,我知道以下函数调用是问题所在:
csvread("test.txt", &data->real);
但是,我已经尝试了第二个参数的多种变体,这是我能想出的唯一一个可以编译的变体。
当数据不是结构时,我的代码可以正常工作。例如,如果声明了数据:
double data[10];
所以,正如您(希望)看到的那样,我在理解指向结构成员的指针时遇到了困难。
这是我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
typedef struct Complex
{
double real;
double imag;
}complex;
void csvread(char *filename, double *data_out);
int main(void)
{
complex *data;
csvread("test.txt", &data->real);
for(int i = 0; i<SIZE; i++)
{
printf("%f\n", data[i].real);
}
}
// This function reads csv files
void csvread(char *filename, double *data_out)
{
FILE *file;
char *no_commas; // character buffer to store strings without comma parse
double *buffer; // character data converted to double type
const char comma[2] = ",";
char *csv;
char *token;
int file_size;
int i = 0;
// Read CSV file
file = fopen(filename,"w+"); // change to "r" if read only
if(file == NULL)
{
printf("\n The file requested cannot be found.");
exit(1);
}
fprintf(file, "%s", "1.18493,0.68594,-7.65962,9.84941,10.34054,7.86571,0.04500,11.49505,-8.49410,-0.54901");
fseek(file, 0, SEEK_SET); // return to beginning of the file
// Find the file size in bytes
fseek(file, 0, SEEK_END); // go to end of file
file_size = ftell(file);
fseek(file, 0, SEEK_SET); // return to beginning of file
// Allocate buffer memory
no_commas = malloc((file_size) * sizeof(char));
buffer = malloc((file_size) * sizeof(double));
if (no_commas == NULL || buffer == NULL)
{
printf("Failed to allocate memory when reading %s.\n\n", filename);
exit(1);
}
while ((csv = fgets(no_commas, (file_size + 1), file)) != NULL) // fgets is used as file has no newline characters
{
// Remove Commas from string
token = strtok(csv, comma);
while (token != NULL)
{
//printf("%s\n", token);
buffer[i] = atof(strdup(token));
token = strtok(NULL, comma);
//printf("%f\n", buffer[i]);
i++;
}
}
data_out = buffer;
fclose(file);
free(no_commas);
free(buffer);
}
输出:
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
预期输出:
1.18493
0.68594
-7.65962
9.84941
10.34054
7.86571
0.04500
11.49505
-8.49410
-0.54901
编辑:感谢大家的评论和帮助!我已将 Johnny Mopp 的回答标记为最有帮助。这个问题演变成了比预期更多的关于分配内存的问题,这最终变得非常有用和有用。
在 java 中,这将被视为 NullPointerException,您不会在 C 中遇到此类错误,但这也有责任。在 C 中引用空指针(就像您所做的那样)可能会以多种方式响应。正如评论所说,您需要分配 data
指针。
complex *data = malloc(sizeof(complex));
并且您还应该释放分配给 malloc
的内存,所以在代码的末尾,就在将程序状态返回到 OS 之前(您没有这样做)你应该这样做:
free(data);
我不确定你的 csvread 是如何工作的,但它可能无法使用复杂结构的数组。下面的代码分配了 10 个复数来初始化它们,在 csvread
函数中你可能需要做一些修改来迭代它们,因为你用简单的数组声明表明你的代码有效。
complex *data = calloc(10, sizeof(complex));
上面的代码分配了 10 个 复杂结构 并且指针指向第一个分配的结构。要遍历它们,您可以使用数字(例如 data[0].real = 4
)对它们进行索引或使用指针算法。我有一种强烈的感觉,您将不得不修改在 csvread
函数内迭代 data_out
指针的方式。
您不能只分配 real
部分。您需要分配整个结构 - 即使您只使用 real
部分。如果您使用的是数组,它会像:
complex data[10];
data[0].real = 1.0;
data[1].real = 2.0;
// etc..
但是您必须动态分配 complex
数组,因为提前不知道文件中的项目数(我假设)。您可以一次分配 1 个 complex
对象,并随时调整数组的大小。
// Prototype has changed to pointer-to-pointer complex
// Return value is the number of items read
int csvread(char *filename, complex **data);
int main(void)
{
complex *data = NULL;
int num_records = csvread("test.txt", &data);
for(int i = 0; i < num_records; i++)
{
printf("%f\n", data[i].real);
}
free(data);
}
// This function reads csv files
int csvread(char *filename, complex **data_out)
{
// This will be used to avoid always having to dereference
// the data_out parameter
complex *array = NULL;
int num_records = 0;
while (1) {
double value = // read a value from the file.....
// If error or end of file break out of loop
// Allocate space for another complex object
array = realloc(array, sizeof(complex) * (num_records + 1));
if (NULL == array) // handle error
// Store just the real part
array[num_records].real = value;
// Also, you can initialize imag here but not required
array[num_records].imag = 0;
num_records += 1;
}
// Store and return
*data_out = array;
return num_records;
}
根据更新的评论:在我脑海中浮现出一种处理多个文件的方法。首先,创建 2 个函数:一个读取文件的全部内容,一个替换 strtok
。我们需要第二个的原因是 strtok
的工作方式,您一次只能在一个字符串上使用它,而我们想在两个字符串上使用它。然后,更改 readcsv
函数以获取 2 个文件名。这是未经测试的,可能有错误。
// Create a function that just opens and reads a file
char *load_file(const char *path) {
// TODO:
// Open the file and read entire contents
// return string with contents
// If path is NULL, must return NULL
// Must return NULL if file does not exist
// or read error
}
// Use this function instead of strok so you
// can use on 2 string simultaneously
double get_next_value(char **string)
{
char *start = *string;
char *end = *string;
// Loop until comma or end of string
while (*end && *end != ',') end++;
// If comma, terminate and increment
if (*end) *end++ = 0;
// Update for next time
*string = end;
return atof(start);
}
// This function reads csv files
int csvread(char *real_filename, char *imag_filename, complex **data_out)
{
// This will be used to avoid always having to dereference
// the data_out parameter
complex *array = NULL;
int num_records = 0;
// Load each file into a string. May be NULL
char *real_data_orig = load_file(real_filename);
char *imag_data_orig = load_file(imag_filename);
// Temporary copies of the pointers. Keep the originals
// to free() later. These will be modified
char *real_data = real_data_orig;
char *imag_data = imag_data_orig;
while (1) {
// Check for data. Make sure pointer is not
// NULL and it is still pointing to something
// that is not '[=12=]'
bool has_real = real_data && *real_data;
bool has_imag = imag_data && *imag_data;
// No data? Done.
if (!has_real && !has_imag) break;
// Allocate space for another complex object
array = realloc(array, sizeof(complex) * (num_records + 1));
if (NULL == array) // handle error
// Store the real part (if there is one)
if (has_real) {
array[num_records].real = get_next_value(&real_data);
}
// Store the imag part (if there is one)
if (has_imag) {
array[num_records].imag = get_next_value(&imag_data);
}
num_records += 1;
}
// Free the file contents
free(real_data_orig);
free(imag_data_orig);
// Store and return
*data_out = array;
return num_records;
}
我在 C 中创建了一个非常简单的 "csvread" 函数,它将按照暗示从 CSV 文件中读取。 (在这种情况下,为了测试,我稍微编辑了它以便我可以将虚拟数据写入文件然后读取它)。我创建了一个结构来存储复杂数据。但是,我的 psuedo-csv 文件只包含我需要使用的数据的真实部分。我想将此数据存储到 "data.real" 数组中。但是,我似乎无法理解正确的语法。 (虽然,诚然,这可能更多地是一个完全理解指针的问题,而不仅仅是语法)。如有任何帮助,我们将不胜感激!
在下面的代码中,我知道以下函数调用是问题所在:
csvread("test.txt", &data->real);
但是,我已经尝试了第二个参数的多种变体,这是我能想出的唯一一个可以编译的变体。
当数据不是结构时,我的代码可以正常工作。例如,如果声明了数据:
double data[10];
所以,正如您(希望)看到的那样,我在理解指向结构成员的指针时遇到了困难。
这是我的代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define SIZE 10
typedef struct Complex
{
double real;
double imag;
}complex;
void csvread(char *filename, double *data_out);
int main(void)
{
complex *data;
csvread("test.txt", &data->real);
for(int i = 0; i<SIZE; i++)
{
printf("%f\n", data[i].real);
}
}
// This function reads csv files
void csvread(char *filename, double *data_out)
{
FILE *file;
char *no_commas; // character buffer to store strings without comma parse
double *buffer; // character data converted to double type
const char comma[2] = ",";
char *csv;
char *token;
int file_size;
int i = 0;
// Read CSV file
file = fopen(filename,"w+"); // change to "r" if read only
if(file == NULL)
{
printf("\n The file requested cannot be found.");
exit(1);
}
fprintf(file, "%s", "1.18493,0.68594,-7.65962,9.84941,10.34054,7.86571,0.04500,11.49505,-8.49410,-0.54901");
fseek(file, 0, SEEK_SET); // return to beginning of the file
// Find the file size in bytes
fseek(file, 0, SEEK_END); // go to end of file
file_size = ftell(file);
fseek(file, 0, SEEK_SET); // return to beginning of file
// Allocate buffer memory
no_commas = malloc((file_size) * sizeof(char));
buffer = malloc((file_size) * sizeof(double));
if (no_commas == NULL || buffer == NULL)
{
printf("Failed to allocate memory when reading %s.\n\n", filename);
exit(1);
}
while ((csv = fgets(no_commas, (file_size + 1), file)) != NULL) // fgets is used as file has no newline characters
{
// Remove Commas from string
token = strtok(csv, comma);
while (token != NULL)
{
//printf("%s\n", token);
buffer[i] = atof(strdup(token));
token = strtok(NULL, comma);
//printf("%f\n", buffer[i]);
i++;
}
}
data_out = buffer;
fclose(file);
free(no_commas);
free(buffer);
}
输出:
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
0.000000
预期输出:
1.18493
0.68594
-7.65962
9.84941
10.34054
7.86571
0.04500
11.49505
-8.49410
-0.54901
编辑:感谢大家的评论和帮助!我已将 Johnny Mopp 的回答标记为最有帮助。这个问题演变成了比预期更多的关于分配内存的问题,这最终变得非常有用和有用。
在 java 中,这将被视为 NullPointerException,您不会在 C 中遇到此类错误,但这也有责任。在 C 中引用空指针(就像您所做的那样)可能会以多种方式响应。正如评论所说,您需要分配 data
指针。
complex *data = malloc(sizeof(complex));
并且您还应该释放分配给 malloc
的内存,所以在代码的末尾,就在将程序状态返回到 OS 之前(您没有这样做)你应该这样做:
free(data);
我不确定你的 csvread 是如何工作的,但它可能无法使用复杂结构的数组。下面的代码分配了 10 个复数来初始化它们,在 csvread
函数中你可能需要做一些修改来迭代它们,因为你用简单的数组声明表明你的代码有效。
complex *data = calloc(10, sizeof(complex));
上面的代码分配了 10 个 复杂结构 并且指针指向第一个分配的结构。要遍历它们,您可以使用数字(例如 data[0].real = 4
)对它们进行索引或使用指针算法。我有一种强烈的感觉,您将不得不修改在 csvread
函数内迭代 data_out
指针的方式。
您不能只分配 real
部分。您需要分配整个结构 - 即使您只使用 real
部分。如果您使用的是数组,它会像:
complex data[10];
data[0].real = 1.0;
data[1].real = 2.0;
// etc..
但是您必须动态分配 complex
数组,因为提前不知道文件中的项目数(我假设)。您可以一次分配 1 个 complex
对象,并随时调整数组的大小。
// Prototype has changed to pointer-to-pointer complex
// Return value is the number of items read
int csvread(char *filename, complex **data);
int main(void)
{
complex *data = NULL;
int num_records = csvread("test.txt", &data);
for(int i = 0; i < num_records; i++)
{
printf("%f\n", data[i].real);
}
free(data);
}
// This function reads csv files
int csvread(char *filename, complex **data_out)
{
// This will be used to avoid always having to dereference
// the data_out parameter
complex *array = NULL;
int num_records = 0;
while (1) {
double value = // read a value from the file.....
// If error or end of file break out of loop
// Allocate space for another complex object
array = realloc(array, sizeof(complex) * (num_records + 1));
if (NULL == array) // handle error
// Store just the real part
array[num_records].real = value;
// Also, you can initialize imag here but not required
array[num_records].imag = 0;
num_records += 1;
}
// Store and return
*data_out = array;
return num_records;
}
根据更新的评论:在我脑海中浮现出一种处理多个文件的方法。首先,创建 2 个函数:一个读取文件的全部内容,一个替换 strtok
。我们需要第二个的原因是 strtok
的工作方式,您一次只能在一个字符串上使用它,而我们想在两个字符串上使用它。然后,更改 readcsv
函数以获取 2 个文件名。这是未经测试的,可能有错误。
// Create a function that just opens and reads a file
char *load_file(const char *path) {
// TODO:
// Open the file and read entire contents
// return string with contents
// If path is NULL, must return NULL
// Must return NULL if file does not exist
// or read error
}
// Use this function instead of strok so you
// can use on 2 string simultaneously
double get_next_value(char **string)
{
char *start = *string;
char *end = *string;
// Loop until comma or end of string
while (*end && *end != ',') end++;
// If comma, terminate and increment
if (*end) *end++ = 0;
// Update for next time
*string = end;
return atof(start);
}
// This function reads csv files
int csvread(char *real_filename, char *imag_filename, complex **data_out)
{
// This will be used to avoid always having to dereference
// the data_out parameter
complex *array = NULL;
int num_records = 0;
// Load each file into a string. May be NULL
char *real_data_orig = load_file(real_filename);
char *imag_data_orig = load_file(imag_filename);
// Temporary copies of the pointers. Keep the originals
// to free() later. These will be modified
char *real_data = real_data_orig;
char *imag_data = imag_data_orig;
while (1) {
// Check for data. Make sure pointer is not
// NULL and it is still pointing to something
// that is not '[=12=]'
bool has_real = real_data && *real_data;
bool has_imag = imag_data && *imag_data;
// No data? Done.
if (!has_real && !has_imag) break;
// Allocate space for another complex object
array = realloc(array, sizeof(complex) * (num_records + 1));
if (NULL == array) // handle error
// Store the real part (if there is one)
if (has_real) {
array[num_records].real = get_next_value(&real_data);
}
// Store the imag part (if there is one)
if (has_imag) {
array[num_records].imag = get_next_value(&imag_data);
}
num_records += 1;
}
// Free the file contents
free(real_data_orig);
free(imag_data_orig);
// Store and return
*data_out = array;
return num_records;
}