
πάντα ῥεῖ
Supongamos que tenemos la siguiente situación:
-
Una estructura de registro se declara de la siguiente manera
struct Person {
unsigned int id;
std::string name;
uint8_t age;
// ...
};
-
Los registros se almacenan en un archivo con el siguiente formato:
ID Forename Lastname Age
------------------------------
1267867 John Smith 32
67545 Jane Doe 36
8677453 Gwyneth Miller 56
75543 J. Ross Unusual 23
...
El archivo debe leerse para recopilar un número arbitrario de los Person
registros mencionados anteriormente:
std::istream& ifs = std::ifstream("SampleInput.txt");
std::vector<Person> persons;
Person actRecord;
while(ifs >> actRecord.id >> actRecord.name >> actRecord.age) {
persons.push_back(actRecord);
}
if(!ifs) {
std::err << "Input format error!" << std::endl;
}
Pregunta:
¿Qué puedo hacer para leer los valores separados que almacenan sus valores en uno? actRecord
campos de variables?
Lo anterior ejemplo de código termina con errores de tiempo de ejecución:
Runtime error time: 0 memory: 3476 signal:-1
stderr: Input format error!

πάντα ῥεῖ
Una solución viable es reordenar los campos de entrada (si esto es posible)
ID Age Forename Lastname
1267867 32 John Smith
67545 36 Jane Doe
8677453 56 Gwyneth Miller
75543 23 J. Ross Unusual
...
y leer en los registros lo siguiente
#include <iostream>
#include <vector>
struct Person {
unsigned int id;
std::string name;
uint8_t age;
// ...
};
int main() {
std::istream& ifs = std::cin; // Open file alternatively
std::vector<Person> persons;
Person actRecord;
unsigned int age;
while(ifs >> actRecord.id >> age &&
std::getline(ifs, actRecord.name)) {
actRecord.age = uint8_t(age);
persons.push_back(actRecord);
}
return 0;
}

unxnut
Tiene espacios en blanco entre el nombre y el apellido. Cambie su clase para tener nombre y apellido como cadenas separadas y debería funcionar. La otra cosa que puede hacer es leer en dos variables separadas, como name1
y name2
y asignarlo como
actRecord.name = name1 + " " + name2;

david g
Aquí hay una implementación de un manipulador que se me ocurrió que cuenta el delimitador a través de cada carácter extraído. Usando la cantidad de delimitadores que especifique, extraerá palabras del flujo de entrada. Aquí hay una demostración de trabajo.
template<class charT>
struct word_inserter_impl {
word_inserter_impl(std::size_t words, std::basic_string<charT>& str, charT delim)
: str_(str)
, delim_(delim)
, words_(words)
{ }
friend std::basic_istream<charT>&
operator>>(std::basic_istream<charT>& is, const word_inserter_impl<charT>& wi) {
typename std::basic_istream<charT>::sentry ok(is);
if (ok) {
std::istreambuf_iterator<charT> it(is), end;
std::back_insert_iterator<std::string> dest(wi.str_);
while (it != end && wi.words_) {
if (*it == wi.delim_ && --wi.words_ == 0) {
break;
}
dest++ = *it++;
}
}
return is;
}
private:
std::basic_string<charT>& str_;
charT delim_;
mutable std::size_t words_;
};
template<class charT=char>
word_inserter_impl<charT> word_inserter(std::size_t words, std::basic_string<charT>& str, charT delim = charT(' ')) {
return word_inserter_impl<charT>(words, str, delim);
}
Ahora solo puedes hacer:
while (ifs >> actRecord.id >> word_inserter(2, actRecord.name) >> actRecord.age) {
std::cout << actRecord.id << " " << actRecord.name << " " << actRecord.age << '\n';
}
Demo en vivo

Ferenc Deak
Una solución sería leer en la primera entrada de un ID
variable.
Luego lea todas las demás palabras de la línea (simplemente empújelas en un vector temporal) y construya el nombre del individuo con todos los elementos, excepto la última entrada que es la Edad.
Esto le permitiría tener la edad en la última posición pero poder manejar un nombre como “J. Ross Unusual”.
Actualizar para agregar un código que ilustre la teoría anterior:
#include <memory>
#include <string>
#include <vector>
#include <iterator>
#include <fstream>
#include <sstream>
#include <iostream>
struct Person {
unsigned int id;
std::string name;
int age;
};
int main()
{
std::fstream ifs("in.txt");
std::vector<Person> persons;
std::string line;
while (std::getline(ifs, line))
{
std::istringstream iss(line);
// first: ID simply read it
Person actRecord;
iss >> actRecord.id;
// next iteration: read in everything
std::string temp;
std::vector<std::string> tempvect;
while(iss >> temp) {
tempvect.push_back(temp);
}
// then: the name, let's join the vector in a way to not to get a trailing space
// also taking care of people who do not have two names ...
int LAST = 2;
if(tempvect.size() < 2) // only the name and age are in there
{
LAST = 1;
}
std::ostringstream oss;
std::copy(tempvect.begin(), tempvect.end() - LAST,
std::ostream_iterator<std::string>(oss, " "));
// the last element
oss << *(tempvect.end() - LAST);
actRecord.name = oss.str();
// and the age
actRecord.age = std::stoi( *(tempvect.end() - 1) );
persons.push_back(actRecord);
}
for(std::vector<Person>::const_iterator it = persons.begin(); it != persons.end(); it++)
{
std::cout << it->id << ":" << it->name << ":" << it->age << std::endl;
}
}

Veritas
Dado que podemos dividir fácilmente una línea en espacios en blanco y sabemos que el único valor que se puede separar es el nombre, una posible solución es usar un deque para cada línea que contenga los elementos separados por espacios en blanco de la línea. La identificación y la edad se pueden recuperar fácilmente del deque y los elementos restantes se pueden concatenar para recuperar el nombre:
#include <iostream>
#include <fstream>
#include <deque>
#include <vector>
#include <sstream>
#include <iterator>
#include <string>
#include <algorithm>
#include <utility>
struct Person {
unsigned int id;
std::string name;
uint8_t age;
};
int main(int argc, char* argv[]) {
std::ifstream ifs("SampleInput.txt");
std::vector<Person> records;
std::string line;
while (std::getline(ifs,line)) {
std::istringstream ss(line);
std::deque<std::string> info(std::istream_iterator<std::string>(ss), {});
Person record;
record.id = std::stoi(info.front()); info.pop_front();
record.age = std::stoi(info.back()); info.pop_back();
std::ostringstream name;
std::copy
( info.begin()
, info.end()
, std::ostream_iterator<std::string>(name," "));
record.name = name.str(); record.name.pop_back();
records.push_back(std::move(record));
}
for (auto& record : records) {
std::cout << record.id << " " << record.name << " "
<< static_cast<unsigned int>(record.age) << std::endl;
}
return 0;
}
Otra solución es requerir ciertos caracteres delimitadores para un campo en particular y proporcionar un manipulador de extracción especial para este propósito.
Supongamos que definimos el carácter delimitador "
y la entrada debería verse así:
1267867 "John Smith" 32
67545 "Jane Doe" 36
8677453 "Gwyneth Miller" 56
75543 "J. Ross Unusual" 23
Generalmente necesario incluye:
#include <iostream>
#include <vector>
#include <iomanip>
La declaración de registro:
struct Person {
unsigned int id;
std::string name;
uint8_t age;
// ...
};
Declaración/definición de una clase de proxy (estructura) que admite su uso con el std::istream& operator>>(std::istream&, const delim_field_extractor_proxy&)
sobrecarga del operador global:
struct delim_field_extractor_proxy {
delim_field_extractor_proxy
( std::string& field_ref
, char delim = '"'
)
: field_ref_(field_ref), delim_(delim) {}
friend
std::istream& operator>>
( std::istream& is
, const delim_field_extractor_proxy& extractor_proxy);
void extract_value(std::istream& is) const {
field_ref_.clear();
char input;
bool addChars = false;
while(is) {
is.get(input);
if(is.eof()) {
break;
}
if(input == delim_) {
addChars = !addChars;
if(!addChars) {
break;
}
else {
continue;
}
}
if(addChars) {
field_ref_ += input;
}
}
// consume whitespaces
while(std::isspace(is.peek())) {
is.get();
}
}
std::string& field_ref_;
char delim_;
};
std::istream& operator>>
( std::istream& is
, const delim_field_extractor_proxy& extractor_proxy) {
extractor_proxy.extract_value(is);
return is;
}
Plomería todo conectado e instanciando el delim_field_extractor_proxy
:
int main() {
std::istream& ifs = std::cin; // Open file alternatively
std::vector<Person> persons;
Person actRecord;
int act_age;
while(ifs >> actRecord.id
>> delim_field_extractor_proxy(actRecord.name,'"')
>> act_age) {
actRecord.age = uint8_t(act_age);
persons.push_back(actRecord);
}
for(auto it = persons.begin();
it != persons.end();
++it) {
std::cout << it->id << ", "
<< it->name << ", "
<< int(it->age) << std::endl;
}
return 0;
}
Ver el ejemplo de trabajo aquí.
NOTA:
Esta solución también funciona bien especificando un carácter TAB (\t
) como delimitador, que es un estándar de análisis útil .csv
formatos.
¿Qué puedo hacer para leer en las palabras separadas que forman el nombre en el actRecord.name
¿variable?
La respuesta general es: Nono puede hacer esto sin especificaciones de delimitador adicionales y un análisis excepcional de las partes que forman el objetivo actRecord.name
contenido.
Esto se debe a que un std::string
El campo se analizará justo hasta la siguiente aparición de un carácter de espacio en blanco.
Es notable que algunos formatos estándar (como, por ejemplo, .csv
) puede requerir para admitir espacios en blanco distintivos (' '
) de la pestaña ('\t'
) u otros caracteres, para delimitar determinados campos del registro (que pueden no ser visibles a simple vista).
También tenga en cuenta:
para leer un uint8_t
valor como entrada numérica, tendrá que desviarse usando un temporal unsigned int
valor. leyendo solo un unsigned char
(también conocido como uint8_t
) arruinará el estado de análisis de flujo.
@ 0x499602D2 Creo que es relevante. También agregaré la etiqueta c ++ 11 para abrir un campo más amplio de soluciones. Como se mencionó, la pregunta original también puede reducirse demasiado. Siéntete libre de tocarlo 😉 …
– πάντα ῥεῖ
12 mayo 2014 a las 21:38
¿Ha encontrado una solución a esto para manejar múltiples espacios para nombres, porque creo que tengo una buena manera de manejar esto?
– Veritas
18 mayo 2014 a las 19:45
@Veritas Si tiene otra buena solución, no dude en agregar otra respuesta aquí. Esta publicación está pensada como una sesión canónica de preguntas y respuestas.
– πάντα ῥεῖ
18 mayo 2014 a las 19:47
¿Intentaste hacer una sobrecarga de operadores?
– padawan
21 mayo 2014 a las 21:39
@OnurÇağırıcı ‘¿Intentaste hacer una sobrecarga de operadores?’ Sí, mira aquí.
– πάντα ῥεῖ
21 mayo 2014 a las 21:42