-1

I need to read the contents of a text file and store the values to variables. Consider this simple file:

2
-0.5    -0.5     0.0
 0.5    -0.5     0.0

When specifying its filename (so without using resources), I proceed like this with fscanf_s:

FILE *pFile;
fopen_s(&pFile, filename.c_str(), "r");

fscanf_s(pFile, "%d", &nPoints);
points = new Vec3[nPoints];

for (int n = 0; n < nPoints; ++n)
    fscanf_s(pFile, "%lf %lf %lf", &points[n][0], &points[n][1], &points[n][2]);

fclose(pFile);

and the data is saved to two vectors, each having three values.

Now, I would like to do the same but with a file that is included as a user-defined resource. First, I follow this example to load the data into a buffer. The problem is that I don't know what to do with this buffer to retreive the data and save it in a similar way. I have tried using the sscanf_s function:

sscanf_s(buffer, "%d", &nPoints);
points = new Vec3[nPoints];

for (int n = 0; n < nPoints; ++n) {
    sscanf_s(buffer, "%lf %lf %lf", &points[n][0], &points[n][1], &points[n][2]);
}

but it doesn't seem to work like I expected. The number of points is read correctly into the nPoints variable, but both vectors end up with the values 2, -0.5, -0.5.

How can I save the values from the buffer to my Vec3s? Or is there a simpler alternative that I should consider?

JD80121
  • 591
  • 1
  • 6
  • 13

1 Answers1

1

When using sscanf_s() you are passing it the same buffer pointer each time, so it keeps re-reading from the same 2 value over and over.

You need to advance the pointer after each read. The return value of sscanf_f() is the number of fields read, but you would need the number of characters consumed instead, which you can get with the %n format specifier, eg:

char *ptr = buffer;
int consumed;

sscanf_s(ptr, "%d%n", &nPoints, &consumed);
ptr += consumed;

points = new Vec3[nPoints];

for (int n = 0; n < nPoints; ++n) {
    sscanf_s(ptr, "%lf %lf %lf%n", &points[n][0], &points[n][1], &points[n][2], &consumed);
    ptr += consumed;
}

A better option is to use C++ style I/O instead of C style I/O. For instance, you can assign the buffer data to a std::istringstream and then read from that 1, eg:

#include <sstream>

std::istringstream iss(buffer);

iss >> nPoints;
points = new Vec3[nPoints];

for (int n = 0; n < nPoints; ++n) {
    iss >> points[n][0] >> points[n][1] >> points[n][2];
}

1: to read from a file instead, simply replace std::istringstream with std::ifstream.

Or, if you want to avoid the overhead of allocating a std::string copy of the buffer data, you can write a custom std::basic_streambuf class (or find a 3rd party implementation) that can read from your buffer (or even better, from the original resource directly), eg:

#include <iostream>

SomeCharArrayStreamBuf buf(buffer, size);
std::istream is(&buf);

is >> nPoints;
points = new Vec3[nPoints];

for (int n = 0; n < nPoints; ++n) {
    is >> points[n][0] >> points[n][1] >> points[n][2];
}

Once you switch to using C++ I/O streams, you can greatly simplify things even further by utilizing containers and algorithms from C++'s standard library, eg:

#include <vector>

std::vector<Vec3> points; // <-- instead of 'Vec3 *pointers;'

#include <iostream>
#include <algorithm>
#include <iterator>

std::istream& operator>>(std::istream &in, Vec3 &v)
{
    in >> v[0] >> v[1] >> v[2];
    return in;
}

strm >> nPoints; // <-- where strm is your chosen istream class object
points.reserve(nPoints); // <-- instead of 'new Vec3[nPoints];'

std::copy_n(std::istream_iterator(strm), nPoints, std::back_inserter(points)); // <-- instead of a manual reading loop
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Exactly what I was looking for, thanks! I like using the C style I/O because it is closer to the syntax in Matlab and Python, which I use more frequently. What makes the C++ commands better in this case? Would they work the same if the values in the file were comma-separated? – JD80121 Jan 25 '19 at 02:56
  • For comma-separated data, look at [`std::getline()`](https://en.cppreference.com/w/cpp/string/basic_string/getline), which has an optional `delim` parameter that you can set to `','`. – Remy Lebeau Jan 25 '19 at 03:01