sscanf multivalue n length values?
I have a file which is similar to /etc/passwd (semi-colon separated values), and need to extract all three values per line into variables then compare them to what have been given into the program. Here is my code:
typedef struct _UserModel UserModel;
struct _UserModel {
char username[50];
char email[55];
char pincode[30];
};
void get_user(char *username) {
ifstream io("test.txt");
string line;
while (io.good() && !io.eof()) {
getline(io, line);
if (line.length() > 0 && line.substr(0,line.find(":")).compare(username)==0) {
cout << "found user!\n";
UserModel tmp;
sscanf(line.c_str() "%s:%s:%s", tmp.username, tmp.pincode, tmp.email);
assert(0==strcmp(tmp.username, username));
}
}
}
I can't strcmp the values as the trailing '\0' mean the strings are different, so the assertion fails. I only really want to hold the memory for the values anyway and not use up memory that I don't need for these values. What do I need 开发者_C百科to change to get this to work..?
sscanf is so C'ish.
struct UserModel {
string username;
string email;
string pincode;
};
void get_user(char *username) {
ifstream io("test.txt");
string line;
while (getline(io, line)) {
UserModel tmp;
istringstream str(line);
if (getline(str, tmp.username, ':') && getline(str, tmp.pincode, ':') && getline(str, tmp.email)) {
if (username == tmp.username)
cout << "found user!\n";
}
}
}
If you are using c++, I would try to use std::string
, iostreams and all those things that come with C++, but then again...
I understand that your problem is that one of the C strings is null terminated, while the other is not, and then the strcmp
is stepping to the '\0'
on one string, but the other has another value... if that is the only thing you want to change, use strncpy
with the length of the string that is known.
Here's a complete example that does what I think you asked about.
Things you didn't ask for but it does anyway:
- It uses exceptions to report data file format errors so that
GetModelForUser()
can simply return an object (instead of a boolean or something like that). - It uses a template function for splitting the line into fields. This really the heart of the original question and so it's a bit unfortunate that this is arguably over-complex. But the idea here of making it a template function is that this separates the concerns of splitting a string into fields from choosing a data structure to represent the result.
/* Parses a file of user data.
* The data file is of this format:
* username:email-address:pincode
*
* The pincode field is actually one-way-encrypted with a secret salt
* in order to avoid catastrophic loss of customer data when the file
* or a backup tape is lost/leaked/compromised. However, this code
* simply treats it as an opaque value.
*
* Internationalisation: this code assumes that the data file is
* encoded in the execution character set, whatever that is. This
* means that updates to the file must first transcode the
* username/mail-address/pincode data into the execution character
* set.
*/
#include <string> #include <vector> #include <fstream> #include <iostream> #include <iterator> #include <exception>
const char* MODEL_DATA_FILE_NAME = "test.txt";
// This stuff should really go in a header file. class UserUnknown : public std::exception { };
class ModelDataIsMissing : public std::exception { }; class InvalidModelData : public std::exception { }; // base: don't throw this directly. class ModelDataBlankLine : public InvalidModelData { }; class ModelDataEmptyUsername : public InvalidModelData { }; class ModelDataWrongNumberOfFields : public InvalidModelData { };
class UserModel { std::string username_; std::string email_address_; std::string pincode_;
public: UserModel(std::string username, std::string email_address, std::string pincode) : username_(username), email_address_(email_address), pincode_(pincode) { } UserModel(const UserModel& other) : username_(other.username_), email_address_(other.email_address_), pincode_(other.pincode_) { }
std::string GetUsername() const { return username_; }
std::string GetEmailAddress() const { return email_address_; }
std::string GetPincode() const { return pincode_; }
};
UserModel GetUserModelForUser(const std::string& username) throw (InvalidModelData, UserUnknown, ModelDataIsMissing);
// This stuff is the implementation. namespace { // use empty namespace for modularity. template void SplitStringOnSeparator( std::string input, char separator, ForwardIterator output) { std::string::const_iterator field_start, pos; bool in_field = false; for (pos = input.begin(); pos != input.end(); ++pos) { if (!in_field) { field_start = pos; in_field = true; } if (*pos == separator) { *output++ = std::string(field_start, pos); in_field = false; } } if (field_start != input.begin()) { *output++ = std::string(field_start, pos); } } }
// Returns a UserModel instance for the specified user. // // Don't call this more than once per program invocation, because // you'll end up with quadratic performance. Instead modify this code // to return a map from username to model data. UserModel GetUserModelForUser(const std::string& username) throw (InvalidModelData, UserUnknown, ModelDataIsMissing) { std::string line; std::ifstream in(MODEL_DATA_FILE_NAME); if (!in) { throw ModelDataIsMissing(); }
while (std::getline(in, line)) {
std::vector<std::string> fields;
SplitStringOnSeparator(line, ':', std::back_inserter(fields));
if (fields.size() == 0) {
throw ModelDataBlankLine();
} else if (fields.size() != 3) {
throw ModelDataWrongNumberOfFields();
} else if (fields[0].empty()) {
throw ModelDataEmptyUsername();
} else if (fields[0] == username) {
return UserModel(fields[0], fields[1], fields[2]);
}
// We don't diagnose duplicate usernames in the file.
}
throw UserUnknown();
}
namespace { bool Example (const char *arg) { const std::string username(arg); try { UserModel mod(GetUserModelForUser(username)); std::cout << "Model data for " << username << ": " << "username=" << mod.GetUsername() << ", email address=" << mod.GetEmailAddress() << ", encrypted pin code=" << mod.GetPincode() << std::endl; return true; } catch (UserUnknown) { std::cerr << "Unknown user " << username << std::endl; return false; } } }
int main (int argc, char *argv[]) { int i, returnval=0; for (i = 1; i < argc; ++i) { try { if (!Example(argv[i])) { returnval = 1; } } catch (InvalidModelData) { std::cerr << "Data file " << MODEL_DATA_FILE_NAME << " is invalid." << std::endl; return 1; } catch (ModelDataIsMissing) { std::cerr << "Data file " << MODEL_DATA_FILE_NAME << " is missing." << std::endl; return 1; } } return returnval; }
/* Local Variables: / / c-file-style: "stroustrup" / / End: */
I don't see a problem with strcmp
, but you have one in your sscanf format. %s
will read upto the first non white character, so it will read the :
. You probably want "%50[^:]:%55[^:]:%30s"
as format string. I've added field size in order to prevent buffer overflow, but I could be off by one in the limit.
精彩评论