Qingyao Li
Qingyao Li

Reputation: 173

How to use fscanf() to input different parameters?

I try to use fscanf() to input parameters from txt file. Here's my code:

fpin = fopen("parameter_file.txt","r");
fscanf(fpin,"%d\n", &code_value);
fscanf(fpin,"%d\n", &origin_x);
fscanf(fpin,"%d\n", &origin_y);
fscanf(fpin,"%d\n", &dpi);
fscanf(fpin,"%d\n", &width);
fscanf(fpin,"%d\n", &height);

And the parameter_file.txt is(for exemple):

333 
123 
123 
800
6
8

I want to add some annotation after each line. For example: 333 //code_value or 123 //origin_x My problem is when I add some characters, fscanf() does not work as I desired to read the continues parameters.

I have tried fscanf(fpin,"%[^a-z]%d\n", &code_value);

Another problem is when the input file as follow, how can my code realize?

333 //code_value_1
123 //origin_x_1
123 //origin_y_1
800 //dpi_1
6 //width_1
8 //height_1
12 // code_value_2
10 //origin_x_2
20 //origin_y_2
800 //dpi_2
10 //width_2
10 //height_2
.
.
.
20 // code_value_n
10 //origin_x_n
20 //origin_y_n
800 //dpi_n
30 //width_n
30 //height_n

Upvotes: 3

Views: 9847

Answers (1)

jxh
jxh

Reputation: 70472

If the format is always one number per line, you can use:

fscanf(fpin, "%d%*[^\n]", &var);

The %*[^\n] will swallow all characters up to the newline character. The * is the suppression, and the ^\n means all characters except the newline character is part of the scan set.

However, this will fail on your old style input if a newline immediately follows the number. The more robust method is to read the entire line into a buffer first, and then parse the buffer, with sscanf() or strtol().

char buf[MAX_LINE_LENGTH];

if (fgets(buf, sizeof(buf), fpin) != 0) {
    if (sscanf(buf, "%d", &var) == 1) {
        /* ... */
    } else {
        /* ...handle parse error */
    }
} else {
    /* ...handle file read error */
}

In C++, it can be done similarly using getline() and an istringstream.

std::string line;
std::istringstream iss;

if (std::getline(std::cin, line)) {
    iss.str(line);
    if (iss >> var) {
        //...
    } else {
        //...handle scan error
    }
} else {
    //...handle read error
}

In a comment, you ask:

How to assign every cin to different parameter?

If each comment describes what the value represents, you can use a map<string, int> to create an association of names to values. Since the comments do not follow a regular format, you have to get tricky with the parsing.

The C version uses an array to store the associations. You can then later sort it (with qsort()) and perform binary search on it when you need to do a look up (with bsearch()).

struct value_item {
    char name[MAX_NAME_SIZE];
    int value;
};

struct value_item value_map[MAX_VALUE_MAP];
size_t value_map_size = 0;

while (fgets(buf, sizeof(buf), fpin) != 0) {
    const char *slash2 = strstr(buf, "//");
    if (slash2 && slash2[2] != '\0') {
        sscanf(buf, "%d", &value_map[value_map_size].value);
        sscanf(slash2+2, "%s", value_map[value_map_size].name);
        ++value_map_size;
    } else {
        /* ...handle parse error */
    }
}

The C++ version would look similar. C++ provides map, which you can use to easily create the association.

std::map<std::string, int> value_map;

while (std::getline(std::cin, line)) {
    std::string::size_type n = line.find("//");
    if (n != std::string::npos && line.size() > (n+2)) {
        int value;
        std::string name;
        iss.str(line);
        iss >> value;
        iss.str(line.substr(n+2));
        iss >> name;
        value_map[name] = value;
    } else {
        //...handle parse error
    }
 }

I am writing this off the top of my head, so there may be errors, and nicer ways to do it. But, this should give you the general idea.

Upvotes: 5

Related Questions