c++ - 拆分学生列表,其格式如下:0001 William Bill Junior 8.5

标签 c++ char

我需要将这样的学生列表分为ID,姓名和分数。这是一个练习,因此不允许使用字符串,只能使用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和名称,但得分是

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, "\0"); \\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* namechar* score而不是一个并重用它。

最佳答案

您从错误的脚开始。请参见Why is while ( !feof (file) ) always wrong?。虽然有多种方法可以将信息分成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或POSIX getline),然后将其传递给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"。每种字符数组类型都使用field-width修饰符,以将存储在关联数组中的字符数限制为少于可用字符数。这样可以保护数组边界,并确保所存储的每个字符串都以nul结尾。 "%7s"是不言自明的-最多将7个字符读为id

该名称的下一个转换说明符是"%63[^0-9]",因为它使用"%[...]字符类转换说明符,并且使用'^'作为第一个字符来反转匹配,因此涉及更多。该类中的字符为数字0-9,转换说明符最多读取63个不包含数字的字符。这会产生副作用,包括在namescorename之间的空格。值得庆幸的是,它们很简单,可以通过使用strrchr (tmp.name, 0);指向字符串末尾的指针来删除,然后备份以检查字符是否为' '(空格)并用一个以n终止的字符(例如或数值等效的'\0')。

0转换的最后一部分sscanf只是"%lf"double值的转换说明符。

注意:最重要的是,通过检查对score的调用返回是否为sscanf来验证转换-请求的转换次数。如果所有转换都成功转换为临时结构3,则tmp会简单地添加到您的结构数组中。

要从tmp调用函数并读取学生信息,您只需声明一个main()数组来保存信息,打开并验证您的数据文件已打开以进行读取,然后调用student_t捕获返回值验证实际上是从文件中读取了学生信息。然后,您可以根据需要使用数据(以下仅输出):

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  %g\n", 
                students[i].id, students[i].name, students[i].score);

    return 0;
}


剩下的一切包括必需的标头,readstudentsstdio.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


它根据需要工作。

请注意,这是分隔值的最基本方法,并且仅在您的string.h字段以数字开头的假设下起作用。

您可以通过以相同的方式读取每一行来手动解析所需的信息来消除该假设,但是不用声明使用score,只需声明一对指针即可手动隔离sscanf。基本方法是将指针前进到第一个空白并读取id, name & score,跳过随后的空白并将指针定位在id的开头。从另一行的末尾开始,备份到末尾的第一个空格,并读取name,然后继续备份,将指针定位在score之后的第一个空格中。然后,只需将开始和结束指针之间的字符复制到name并以nul-terminate结束即可。从指针算术的角度来看,它涉及更多,但也很简单。 (留给您)

仔细检查一下,如果您还有其他问题,请告诉我。通常,您将动态声明学生数组,并根据需要分配/重新分配以处理文件中任意数量的学生。 (或者从实际的C ++角度来看,使用标准模板库提供的namevector类型,并让容器为您处理内存分配)这仅仅是添加一层以增加灵活性,码。



C ++实现

对于为C ++解决方案提供掩饰性,我深表歉意,但是鉴于您在发布的代码中使用了C字符串函数,因此我提供了C解决方案。从存储的角度来看,使用stringstd::string的C ++解决方案没有太大区别。这三个值的解析略有不同,将整行读入std::vectorid,然后从保存在name中的那部分行中获取score,然后将这些字符从< cc>。

将C name更改为name,将FILE*数组更改为std::ifstream,您的student_t函数可以写为:

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 */
    }
}


(注意:返回类型更改为std::vector<student_t>,因为返回时可以验证学生向量的readstudents())。

完整的示例为:

#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个空格之外)

仔细检查一下,如果您有任何问题,请告诉我。

关于c++ - 拆分学生列表,其格式如下:0001 William Bill Junior 8.5,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/59516918/

相关文章:

c++ - 当用于指向字符串时,指向 char 的指针是否会覆盖内存?

c++ - 如何在 Clang 中启用内联函数的编译?

c++ - 调用可变大小的数组作为参数

haskell - 如何在 Haskell 中将 Char 转换为 Int?

c++ - 需要将char*(指针)转换为wchar_t*(指针)

c++ - 什么样的数据类型将在 C++ 中保存 3000000^2?

c++ - 将一个指针分配给另一个指针时出现段错误

c++ - 在 C++ 中将字符数组从索引 i 转换为字符串 j

c++ - sscanf 错误 : cannot convert 'String' to 'const char*'

c - 如何检查 char* 变量是否指向空字符串?