拆分学生名单,格式如:0001 William Bill Junior 8.5
Split student list that has format like: 0001 William Bill Junior 8.5
我需要将这样的学生列表拆分为ID、Name和score。这是一个练习,所以不允许使用字符串,只有char
0001 William Bob 8.5
0034 Howard Stark 9.5
0069 Natalia Long Young 8
这是代码
int readFile(list& a) {
char str[MAX];
short i = 0;
ifstream fi(IN);
if (!fi)
return 0;
while (!fi.eof()) {
fi.getline(str, MAX - 1);
char* temp = strtok(str, " ");
if (temp == NULL)
continue;
strcpy(a.sv[i].id, temp);
temp = strtok(NULL, "0123456789");
strcpy(a.sv[i].name, temp);
temp = strtok(NULL, "n");
a.sv[i].grade = atof(temp);
i++;
}
a.size = i;
fi.close();
return 1;
}
使用strtok()
,我已成功拆分ID和Name,但分数为
0.5
0.5
0
我知道问题是因为temp = strtok(NULL, "0123456789");
,但我不知道如何解决,"0123456789"
旁边有分隔符吗,或者我可以把指针移回吗?
这是我尝试修复while(!file.eof())
并解决我的问题。这是我的标题和结构:
#include<iostream>
#include<fstream>
#include<string>
#define IN "D:\Input.txt"
#define OUT "D:\Output.txt"
#define MAX 40
using namespace std;
struct sv{
char id[MAX], name[MAX] , sex[MAX];
float grade;
};
struct dssv {
sv sv[MAX];
short size;
};
这是我的功能:
int readFile(dssv& a) {
char str[MAX];
short i = 0;
ifstream fi(IN);
if (!fi)
return 0;
while (fi>>a.sv[i].id && fi.getline(str, MAX)) {
char* name = strchr(str, ' ');
int pos = strrchr(name, ' ') - name;
char* score = str + pos;
strcpy(name + pos, " "); \null-terminate to remove the score.
strcpy(a.sv[i].name, name + 1);
a.sv[i].grade = atof(score + 1);
i++;
}
a.size = i;
fi.close();
return 1;
}
仍在想如何修复eof(),以及为什么我需要两个指针char* name
和char* score
而不是一个并重用它
你一开始就走错了路。请参阅为什么while(!feof(file))总是错误?。虽然有很多方法可以将信息分离到id, name, score
中,但最基本的方法可能是简单地将整行数据读取到临时缓冲区(字符阵列)中,然后使用sscanf
来分离id, name & score
。
使用sscanf
进行解析并不困难,唯一需要注意的是name
可能包含空白,因此不能简单地使用"%s"
作为格式说明符来提取名称。score
字段总是以数字开头,并且位数不出现在名称中(该规则总是有例外——它可以通过一对指针的简单解析来处理,但对于基本示例,我们将进行这种格式化假设)
为了简化数据处理,并能够将一个学生的所有信息作为一个对象进行协调(允许您创建一个数组来保存所有学生信息),您可以使用简单的stuct
。声明几个常量来设置所有内容的大小可以避免在整个代码中使用幻数。(尽管对于sscanf
字段宽度修饰符,必须使用实际数字,因为宽度修饰符不能使用常量或变量)例如,您的结构可以是:
#define MAXID 8 /* if you need a constant, #define one (or more) */
#define MAXNM 64
#define MAXSTD 128
#define MAXLN MAXSTD
typedef struct { /* simple struct to hold student data */
char id[MAXID];
char name[MAXNM];
double score;
} student_t;
(POSIX为类型的扩展保留了"_t"
后缀,但不会有"student_t"
类型——但通常要注意限制,尽管你会经常看到"_t"
后缀)
基本方法是将文件中的一行读取到缓冲区(使用fgets
或POSIXgetline
),然后将该行传递给sscanf
。您的读取循环以成功读取每一行为条件,以便在达到EOF
时停止读取。对于使用sscanf
分隔值,可以方便地使用临时结构来保存分隔的值。这样,如果分离成功,只需将临时结构添加到数组中即可。要将学生读入student_t
数组,您可以执行以下操作:
size_t readstudents (FILE *fp, student_t *s)
{
char buf[MAXLN]; /* temporary array (buffer) to hold line */
size_t n = 0; /* number of students read from file */
/* read each line in file until file read or array full */
while (n < MAXSTD && fgets (buf, MAXLN, fp)) {
student_t tmp = { .id = "" }; /* temporary stuct to fill */
/* extract id, name and score from line, validate */
if (sscanf (buf, "%7s %63[^0-9] %lf", tmp.id, tmp.name, &tmp.score) == 3) {
char *p = strrchr (tmp.name, 0); /* pointer to end of name */
/* backup overwriting trailing spaces with nul-terminating char */
while (p && --p >= tmp.name && *p == ' ')
*p = 0;
s[n++] = tmp; /* add temp struct to array, increment count */
}
}
return n; /* return number of students read from file */
}
现在让我们花一分钟时间来看看所使用的sscanf
格式字符串:
sscanf (buf, "%7s %63[^0-9] %lf", tmp.id, tmp.name, &tmp.score)
上面,对于buf
中的行,使用的格式字符串是"%7s %63[^0-9] %lf"
。每种字符数组类型都使用字段宽度修饰符将相关数组中存储的字符数限制为,比可用字符数少一个。这可以保护数组边界,并确保存储的每个字符串都以nul结尾。"%7s"
是不言自明的——最多可以将7个字符读入id
。
该名称的下一个转换说明符是"%63[^0-9]"
,因为它使用了"%[...]
字符类转换说明符,其中通过使用'^'
作为第一个字符反转匹配。类中的字符是数字0-9
,转换说明符最多读取63个不包括数字的字符。这将具有在name
中包括name
和score
之间的空间的副作用。值得庆幸的是,它们很简单,可以删除,方法是用strrchr (tmp.name, 0);
将指针指向字符串的末尾,然后备份检查字符是否为' '
(空格),并用nul终止字符覆盖它(例如' '
或数值等效的0
)。
sscanf
转换的最后一部分,"%lf"
只是score
的double
值的转换说明符。
注意:最重要的是,通过检查对sscanf
的调用的返回是3
——请求的转换数来验证转换。如果所有转换都成功地转换到临时结构tmp
中,那么tmp
将简单地添加到结构的数组中。
要从main()
调用函数并读取学生信息,只需声明一个student_t
数组来保存信息,打开并验证您的数据文件是否已打开以供读取,然后调用readstudents
捕获返回以验证学生信息是否已从文件中实际读取。然后你可以随心所欲地使用数据(下面简单地输出):
int main (int argc, char **argv) {
student_t students[MAXSTD] = {{ .id = "" }}; /* array of students */
size_t nstudents = 0; /* count of students */
/* use filename provided as 1st argument (stdin by default) */
FILE *fp = argc > 1 ? fopen (argv[1], "r") : stdin;
if (!fp) { /* validate file open for reading */
perror ("file open failed");
return 1;
}
/* read students from file, validate return, if zero, handle error */
if ((nstudents = readstudents (fp, students)) == 0) {
fputs ("error: no students read from file.n", stderr);
return 1;
}
if (fp != stdin) /* close file if not stdin */
fclose (fp);
for (size_t i = 0; i < nstudents; i++) /* output each student data */
printf ("%-8s %-24s %gn",
students[i].id, students[i].name, students[i].score);
return 0;
}
剩下的包括所需的标头stdio.h
和string.h
以及测试:
示例使用/输出
$ ./bin/read_stud_id_name_score dat/stud_id_name_no.txt
0001 William Bob 8.5
0034 Howard Stark 9.5
0069 Natalia Long Young 8
它根据需要工作。
请注意,这是分隔值的最基本方法,并且仅在假设score
字段以数字开头的情况下有效。
您可以通过手动解析所需的信息来消除这种假设,方法是以相同的方式读取每一行,但不使用sscanf
,只需声明一对指针来手动隔离id, name & score
。基本方法是将指针前移到第一个空白并读取id
,跳过后面的空白并将指针定位在name
的开头。从另一行的末尾开始,备份到末尾的第一个空白并读取score
,然后继续备份,将指针定位在name
之后的第一个空格中。然后只需将起始指针和结束指针之间的字符复制到name
和nul-terminate。从指针算术的角度来看,它更为复杂,但同样简单。(由您决定)
仔细看看,如果你还有问题,请告诉我。通常,您会动态声明您的学生数组,并根据需要进行分配/重新分配,以处理文件中的任何数量的学生。(或者从实际C++的角度来看,使用标准模板库提供的vector
和string
类型,并让容器为您处理内存分配)这也是您可以添加的一个附加层,以增加代码的灵活性。
C++实现
我很抱歉掩盖了C++解决方案,但考虑到您在发布的代码中使用了C字符串函数,我提供了一个C解决方案作为回报。使用std::string
和std::vector
的C++解决方案与从存储角度来看没有太大区别。这三个值的解析略有不同,其中整行被读取到id
和name
中,然后从name
中保存的那部分行中获得score
,然后将这些字符从name
中擦除。
将CFILE*
更改为std::ifstream
,将student_t
的数组更改为std::vector<student_t>
,您的readstudents()
函数可以写成:
void readstudents (std::ifstream& fp, std::vector<student_t>& s)
{
std::string buf; /* temporary array (buffer) to hold line */
student_t tmp; /* temporary stuct to fill */
/* read each line in file until file read or array full */
while (fp >> tmp.id && getline(fp, tmp.name)) {
/* get offset to beginning digit within tmp.name */
size_t offset = tmp.name.find_first_of("0123456789"),
nchr; /* no. of chars converted with stod */
if (offset == std::string::npos) /* validate digit found */
continue;
/* convert to double, save in tmp.score */
tmp.score = std::stod(tmp.name.substr(offset), &nchr);
if (!nchr) /* validate digits converted */
continue;
/* backup using offset to erase spaces after name */
while (tmp.name.at(--offset) == ' ')
tmp.name.erase(offset);
s.push_back(tmp); /* add temporary struct to vector */
}
}
(注意:返回类型更改为void
,因为学生向量的.size()
可以在返回时进行验证)。
完整的例子是:
#include <iostream>
#include <iomanip>
#include <fstream>
#include <string>
#include <vector>
struct student_t { /* simple struct to hold student data */
std::string id;
std::string name;
double score;
};
void readstudents (std::ifstream& fp, std::vector<student_t>& s)
{
std::string buf; /* temporary array (buffer) to hold line */
student_t tmp; /* temporary stuct to fill */
/* read each line in file until file read or array full */
while (fp >> tmp.id && getline(fp, tmp.name)) {
/* get offset to beginning digit within tmp.name */
size_t offset = tmp.name.find_first_of("0123456789"),
nchr; /* no. of chars converted with stod */
if (offset == std::string::npos) /* validate digit found */
continue;
/* convert to double, save in tmp.score */
tmp.score = std::stod(tmp.name.substr(offset), &nchr);
if (!nchr) /* validate digits converted */
continue;
/* backup using offset to erase spaces after name */
while (tmp.name.at(--offset) == ' ')
tmp.name.erase(offset);
s.push_back(tmp); /* add temporary struct to vector */
}
}
int main (int argc, char **argv) {
std::vector<student_t> students {}; /* array of students */
if (argc < 2) { /* validate one argument given for filename */
std::cerr << "error: filename required as 1st argument.n";
return 1;
}
std::ifstream fp (argv[1]); /* use filename provided as 1st argument */
if (!fp.good()) { /* validate file open for reading */
std::cerr << "file open failed";
return 1;
}
/* read students from file, validate return, if zero, handle error */
readstudents (fp, students);
if (students.size() == 0) {
std::cerr << "error: no students read from file.n";
return 1;
}
for (auto s : students) /* output each student data */
std::cout << std::left << std::setw(8) << s.id
<< std::left << std::setw(24) << s.name
<< s.score << 'n';
}
(输出是相同的——除了值之间省略了2个空格之外)
仔细看看,如果你有问题,请告诉我。