c BAD 访问 fscanf 与结构
c BAD access fscanf with structs
我正在读取格式如下的文本文件:
Firstname Surname Age NumberOfSiblings motherage dadage
正在导入头文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
一个结构体定义如下:
typedef struct {
int person_ID; //not included in file
char full_name[20];
char sex[2];
char countryOfOrigin[20];
int num_siblings;
float parentsAges[2]; //this should store mother and fathers age in an array of type float
} PersonalInfo;
void viewAllPersonalInformation(){
FILE* file = fopen("People.txt", "r");
if (file == NULL){
printf("File does not exist");
return;
}
int fileIsRead = 0;
int idCounter = 0;
PersonalInfo People[1000];
//headers
printf("%2s |%20s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");
do{
fileIsRead = fscanf(file, "%s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
People[idCounter].person_ID = idCounter;
printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
idCounter++;
}
while(fileIsRead != EOF);
fclose(file);
printf("Finished reading file");
}
int main() {
viewAllPersonalInformation();
return 0;
}
其中 People.txt 看起来像:
约翰·奥唐奈 F 爱尔兰 3 32.5 36.1
玛丽·麦克马洪 M 英格兰 0 70 75
彼得·汤普森 F 美国 2 51 60
如果你有一个指针字段char *full_name
,这意味着只是一个指针,应该由某个存在的对象初始化,如果是char *
,它通常应该是一个char数组。您可以通过两种方式修复它:
- 只要像
char full_name[100]
那样做一个数组字段,把一个字符串的最大长度传给一个scanf
格式的字符串,比如%100s
,这是最简单的方法;
- 使用
malloc
函数,不要忘记 free
该地址,或以其他方式将一些有效地址分配给指针,例如将数组声明为普通的自动存储变量,并将零索引元素的地址分配给您的指针字段,并记住在离开您的函数后,您的自动存储变量的地址将变为无效。
还有一个麻烦。 %s
转换说明符告诉 fscanf
读取 单个 单词直到 any whitespace 字符像space,因此根据您的输入格式,您的 full_name
字段将被读取到第一个 space,并且任何进一步读取整数的尝试都将失败。
fscanf()
将在遇到空格时停止读取。在全名的情况下,您希望使用格式说明符 %s
读取两个字符串。 %s
一发现空格就停止,因此它只会将名字存储在 full_name 中,而姓氏将转到第二个 %s
,因此在 countryOfOrigin
中.
所以,如果你想阅读"Peter Thompson",那么你就需要引入两个字符串(char数组)来存储名字和姓氏,然后将它们连接起来。
但是,由于您要读取字数不同的全名,我建议您使用 fgets()
(它也有缓冲区溢出保护)。例如,"Peter Thompson" 有 2 个,"Mary Mc Mahon" 有 3 个。那么,如果您坚持使用 fscanf()
,您会使用多少个 %s
? 2 还是 3?您不知道,这取决于您在运行时获得的输入。也许有一些正则表达式可以用 fscanf()
来解决这个问题,但相信使用 fgets()
然后解析读取的文件行更适合练习。
现在我们用 fgets()
读取了一行文件,我们要用它做什么?我们仍然不知道每个全名由多少个单词组成!如何查明?通过计算该行包含的空格。如果它包含 w
个空格,那么它有 w + 1
个标记(在您的示例中可以是单词、数字或字符)。
使用简单的 if-else 语句,我们可以在您的示例中区分这两种情况,当有 6 个空格(7 个标记)和 7 个空格("Mary Mc Mahon M England 0 70 75" 的 8 个标记)时。
现在,如何从字符串(行)中提取出标记(全名、年龄等)?我们可以有一个循环并使用一堆 if-else 语句来表示,直到我找到第二个(或第三个取决于空格的数量)空格,我将把当前标记附加到 full_name
。然后,下一个标记将是性别,依此类推。
当然可以,但由于我有点懒惰,我将基于您使用 fscanf()
的出色工作,并使用 sscanf()
来提取标记。当然,使用这种方法,我们需要使用一两个(取决于空格的数量)额外的字符串,以便临时存储姓氏(在我们用 strcat()
将其附加到名字之前)。
最小的完整工作示例:
#include <stdio.h>
#include <string.h>
#define P 1000 // Max number of people
#define L 256 // Max length of line read from file (-1)
typedef struct {
int person_ID; //not included in file
char full_name[32];
char sex[2];
char countryOfOrigin[16];
int num_siblings;
float parentsAges[2];
} PersonalInfo;
int count_whitespaces(char* str)
{
int whitespaces_count = 0;
while(*str)
{
if(*str == ' ')
whitespaces_count++;
str++;
}
return whitespaces_count;
}
void viewAllPersonalInformation(){
FILE* file = fopen("People.txt", "r");
if (file == NULL){
printf("File does not exist");
return;
}
int fileIsRead = 0;
int idCounter = 0;
PersonalInfo People[P];
// line of file, placeholder for biworded surnames, surname.
char line[L], str[8], surname[16];
//headers
// You have 7 format specifiers for the headers, but only 6 six in fscanf!!!
printf("%2s |%5s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");
// read into 'line', from 'file', up to 255 characters (+1 for the NULL terminator)
while(fgets(line, L, file) != NULL) {
//fileIsRead = fscanf(file, "%s %s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
// eat trailing newline of fgets
line[strcspn(line, "\n")] = 0;
// Skip empty lines of file
if(strlen(line) == 0)
continue;
if(count_whitespaces(line) == 6)
{
sscanf(line, "%32s %16s %c %16s %d %f %f", People[idCounter].full_name, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
}
else // 7 whitespaces, thus 8 token in the string
{
sscanf(line, "%32s %8s %16s %c %16s %d %f %f", People[idCounter].full_name, str, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
// Separate name and first word of surname with a space
strcat(People[idCounter].full_name, " ");
strcat(People[idCounter].full_name, str);
}
// Separate name and surname with a space
strcat(People[idCounter].full_name, " ");
strcat(People[idCounter].full_name, surname);
People[idCounter].person_ID = idCounter;
printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
idCounter++;
if(idCounter == P)
{
printf("Max number of people read, stop reading any more data.\n");
break;
}
};
fclose(file);
printf("Finished reading file.\n");
}
int main() {
viewAllPersonalInformation();
return 0;
}
输出:
ID | Name |Sex | Born In |Number of siblings |Mother's age |Father's Age
0 John O'Donnell F Ireland 3 32.500000 36.099998
1 Mary Mc Mahon M England 0 70.000000 75.000000
2 Peter Thompson F America 2 51.000000 60.000000
Finished reading file.
您注意到 sscanf()
格式说明符中的数字了吗?他们是 .
动态内存分配怎么样?
在上面的代码中,我估计了姓名、原籍国等的最大长度。现在让这些尺寸动态化怎么样?我们可以,但我们仍然需要初步估计。
因此,我们可以读取固定长度的临时数组中的名称,然后找到字符串的实际长度 strlen()
. With that information in hand, we are now able to dynamically allocate memory (pointing by a char pointer), and then copy with strcpy()
从临时数组到最终目的地的字符串。
我正在读取格式如下的文本文件:
Firstname Surname Age NumberOfSiblings motherage dadage
正在导入头文件:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
一个结构体定义如下:
typedef struct {
int person_ID; //not included in file
char full_name[20];
char sex[2];
char countryOfOrigin[20];
int num_siblings;
float parentsAges[2]; //this should store mother and fathers age in an array of type float
} PersonalInfo;
void viewAllPersonalInformation(){
FILE* file = fopen("People.txt", "r");
if (file == NULL){
printf("File does not exist");
return;
}
int fileIsRead = 0;
int idCounter = 0;
PersonalInfo People[1000];
//headers
printf("%2s |%20s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");
do{
fileIsRead = fscanf(file, "%s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
People[idCounter].person_ID = idCounter;
printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
idCounter++;
}
while(fileIsRead != EOF);
fclose(file);
printf("Finished reading file");
}
int main() {
viewAllPersonalInformation();
return 0;
}
其中 People.txt 看起来像:
约翰·奥唐奈 F 爱尔兰 3 32.5 36.1
玛丽·麦克马洪 M 英格兰 0 70 75
彼得·汤普森 F 美国 2 51 60
如果你有一个指针字段char *full_name
,这意味着只是一个指针,应该由某个存在的对象初始化,如果是char *
,它通常应该是一个char数组。您可以通过两种方式修复它:
- 只要像
char full_name[100]
那样做一个数组字段,把一个字符串的最大长度传给一个scanf
格式的字符串,比如%100s
,这是最简单的方法; - 使用
malloc
函数,不要忘记free
该地址,或以其他方式将一些有效地址分配给指针,例如将数组声明为普通的自动存储变量,并将零索引元素的地址分配给您的指针字段,并记住在离开您的函数后,您的自动存储变量的地址将变为无效。
还有一个麻烦。 %s
转换说明符告诉 fscanf
读取 单个 单词直到 any whitespace 字符像space,因此根据您的输入格式,您的 full_name
字段将被读取到第一个 space,并且任何进一步读取整数的尝试都将失败。
fscanf()
将在遇到空格时停止读取。在全名的情况下,您希望使用格式说明符 %s
读取两个字符串。 %s
一发现空格就停止,因此它只会将名字存储在 full_name 中,而姓氏将转到第二个 %s
,因此在 countryOfOrigin
中.
所以,如果你想阅读"Peter Thompson",那么你就需要引入两个字符串(char数组)来存储名字和姓氏,然后将它们连接起来。
但是,由于您要读取字数不同的全名,我建议您使用 fgets()
(它也有缓冲区溢出保护)。例如,"Peter Thompson" 有 2 个,"Mary Mc Mahon" 有 3 个。那么,如果您坚持使用 fscanf()
,您会使用多少个 %s
? 2 还是 3?您不知道,这取决于您在运行时获得的输入。也许有一些正则表达式可以用 fscanf()
来解决这个问题,但相信使用 fgets()
然后解析读取的文件行更适合练习。
现在我们用 fgets()
读取了一行文件,我们要用它做什么?我们仍然不知道每个全名由多少个单词组成!如何查明?通过计算该行包含的空格。如果它包含 w
个空格,那么它有 w + 1
个标记(在您的示例中可以是单词、数字或字符)。
使用简单的 if-else 语句,我们可以在您的示例中区分这两种情况,当有 6 个空格(7 个标记)和 7 个空格("Mary Mc Mahon M England 0 70 75" 的 8 个标记)时。
现在,如何从字符串(行)中提取出标记(全名、年龄等)?我们可以有一个循环并使用一堆 if-else 语句来表示,直到我找到第二个(或第三个取决于空格的数量)空格,我将把当前标记附加到 full_name
。然后,下一个标记将是性别,依此类推。
当然可以,但由于我有点懒惰,我将基于您使用 fscanf()
的出色工作,并使用 sscanf()
来提取标记。当然,使用这种方法,我们需要使用一两个(取决于空格的数量)额外的字符串,以便临时存储姓氏(在我们用 strcat()
将其附加到名字之前)。
最小的完整工作示例:
#include <stdio.h>
#include <string.h>
#define P 1000 // Max number of people
#define L 256 // Max length of line read from file (-1)
typedef struct {
int person_ID; //not included in file
char full_name[32];
char sex[2];
char countryOfOrigin[16];
int num_siblings;
float parentsAges[2];
} PersonalInfo;
int count_whitespaces(char* str)
{
int whitespaces_count = 0;
while(*str)
{
if(*str == ' ')
whitespaces_count++;
str++;
}
return whitespaces_count;
}
void viewAllPersonalInformation(){
FILE* file = fopen("People.txt", "r");
if (file == NULL){
printf("File does not exist");
return;
}
int fileIsRead = 0;
int idCounter = 0;
PersonalInfo People[P];
// line of file, placeholder for biworded surnames, surname.
char line[L], str[8], surname[16];
//headers
// You have 7 format specifiers for the headers, but only 6 six in fscanf!!!
printf("%2s |%5s |%2s |%10s |%2s |%3s |%3s\n", "ID", "Name", "Sex", "Born In", "Number of siblings", "Mother's age", "Father's Age");
// read into 'line', from 'file', up to 255 characters (+1 for the NULL terminator)
while(fgets(line, L, file) != NULL) {
//fileIsRead = fscanf(file, "%s %s %s %s %d %f %f\n", People[idCounter].full_name, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
// eat trailing newline of fgets
line[strcspn(line, "\n")] = 0;
// Skip empty lines of file
if(strlen(line) == 0)
continue;
if(count_whitespaces(line) == 6)
{
sscanf(line, "%32s %16s %c %16s %d %f %f", People[idCounter].full_name, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
}
else // 7 whitespaces, thus 8 token in the string
{
sscanf(line, "%32s %8s %16s %c %16s %d %f %f", People[idCounter].full_name, str, surname, People[idCounter].sex, People[idCounter].countryOfOrigin, &People[idCounter].num_siblings, &People[idCounter].parentsAges[0], &People[idCounter].parentsAges[1]);
// Separate name and first word of surname with a space
strcat(People[idCounter].full_name, " ");
strcat(People[idCounter].full_name, str);
}
// Separate name and surname with a space
strcat(People[idCounter].full_name, " ");
strcat(People[idCounter].full_name, surname);
People[idCounter].person_ID = idCounter;
printf("%d %s %s %s %d %f %f\n", People[idCounter].person_ID, People[idCounter].full_name, People[idCounter].sex, People[idCounter].countryOfOrigin, People[idCounter].num_siblings, People[idCounter].parentsAges[0], People[idCounter].parentsAges[1]);
idCounter++;
if(idCounter == P)
{
printf("Max number of people read, stop reading any more data.\n");
break;
}
};
fclose(file);
printf("Finished reading file.\n");
}
int main() {
viewAllPersonalInformation();
return 0;
}
输出:
ID | Name |Sex | Born In |Number of siblings |Mother's age |Father's Age
0 John O'Donnell F Ireland 3 32.500000 36.099998
1 Mary Mc Mahon M England 0 70.000000 75.000000
2 Peter Thompson F America 2 51.000000 60.000000
Finished reading file.
您注意到 sscanf()
格式说明符中的数字了吗?他们是
动态内存分配怎么样?
在上面的代码中,我估计了姓名、原籍国等的最大长度。现在让这些尺寸动态化怎么样?我们可以,但我们仍然需要初步估计。
因此,我们可以读取固定长度的临时数组中的名称,然后找到字符串的实际长度 strlen()
. With that information in hand, we are now able to dynamically allocate memory (pointing by a char pointer), and then copy with strcpy()
从临时数组到最终目的地的字符串。