programacion - que es struct en c++
¿Por qué falla la lectura de los campos de una estructura de registro de std:: istream y cómo puedo solucionarlo? (9)
Tiene espacios en blanco entre firstname y lastname. Cambie su clase para tener firstname y lastname como cadenas separadas y debería funcionar. La otra cosa que puede hacer es leer en dos variables separadas como name1
y name2
y asignarla como
actRecord.name = name1 + " " + name2;
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 usando 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 una cantidad arbitraria de los registros de Person
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: (esa es una pregunta frecuente, en una u otra forma)
¿Qué puedo hacer para leer en los valores separados que almacenan sus valores en los actRecord
variables actRecord
?
La muestra de código anterior termina con errores de tiempo de ejecución:
Runtime error time: 0 memory: 3476 signal:-1
stderr: Input format error!
Otra solución es requerir ciertos caracteres delimitadores para un campo particular, y proporcionar un manipulador de extracción especial para este propósito.
Supongamos que definimos el carácter del 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 se necesita incluir:
#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 proxy (struct) que admite el uso con std::istream& operator>>(std::istream&, const delim_field_extractor_proxy&)
global overload del operador:
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 entre sí y delim_field_extractor_proxy
instancia del 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;
}
Vea el ejemplo de trabajo aquí .
NOTA:
Esta solución también funciona bien especificando un carácter TAB ( /t
) como delimitador, que es útil para analizar formatos .csv
estándar.
¿Qué puedo hacer para leer las palabras separadas que forman el nombre en la variable
actRecord.name
?
La respuesta general es: No , no puede hacer esto sin especificaciones de delimitadores adicionales y un análisis excepcional para las partes que forman los contenidos previstos de actRecord.name
.
Esto se debe a que un campo std::string
será analizado justo hasta la próxima ocurrencia de un carácter de espacio en blanco.
Cabe destacar que algunos formatos estándar (como, por ejemplo, .csv
) pueden requerir para admitir espacios en blanco ( '' ''
) de tabulación ( ''/t''
) u otros caracteres, para delimitar ciertos campos de registro (que pueden no ser visibles a primera vista) .
También tenga en cuenta:
Para leer un valor de uint8_t
como entrada numérica, deberá desviarse utilizando un valor unsigned int
temporal unsigned int
. Leer solo un unsigned char
(alias uint8_t
) uint8_t
el estado de análisis de flujo.
Una solución sería leer en la primera entrada en una variable de ID
.
Luego, lea todas las otras 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 el último puesto pero poder tratar el nombre como "J. Ross Unusual".
Actualice 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;
}
}
Una solución viable es reordenar 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 lea en los registros de la siguiente manera
#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;
}
Otro intento de resolver el problema de análisis.
int main()
{
std::ifstream ifs("test-115.in");
std::vector<Person> persons;
while (true)
{
Person actRecord;
// Read the ID and the first part of the name.
if ( !(ifs >> actRecord.id >> actRecord.name ) )
{
break;
}
// Read the rest of the line.
std::string line;
std::getline(ifs,line);
// Pickup the rest of the name from the rest of the line.
// The last token in the rest of the line is the age.
// All other tokens are part of the name.
// The tokens can be separated by '' '' or ''/t''.
size_t pos = 0;
size_t iter1 = 0;
size_t iter2 = 0;
while ( (iter1 = line.find('' '', pos)) != std::string::npos ||
(iter2 = line.find(''/t'', pos)) != std::string::npos )
{
size_t iter = (iter1 != std::string::npos) ? iter1 : iter2;
actRecord.name += line.substr(pos, (iter - pos + 1));
pos = iter + 1;
// Skip multiple whitespace characters.
while ( isspace(line[pos]) )
{
++pos;
}
}
// Trim the last whitespace from the name.
actRecord.name.erase(actRecord.name.size()-1);
// Extract the age.
// std::stoi returns an integer. We are assuming that
// it will be small enough to fit into an uint8_t.
actRecord.age = std::stoi(line.substr(pos).c_str());
// Debugging aid.. Make sure we have extracted the data correctly.
std::cout << "ID: " << actRecord.id
<< ", name: " << actRecord.name
<< ", age: " << (int)actRecord.age << std::endl;
persons.push_back(actRecord);
}
// If came here before the EOF was reached, there was an
// error in the input file.
if ( !(ifs.eof()) ) {
std::cerr << "Input format error!" << std::endl;
}
}
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;
}
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 de la secuencia de entrada. Aquí hay una demostración funcional.
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 puedes hacer:
while (ifs >> actRecord.id >> word_inserter(2, actRecord.name) >> actRecord.age) {
std::cout << actRecord.id << " " << actRecord.name << " " << actRecord.age << ''/n'';
}
Al ver un archivo de entrada de este tipo, creo que no es un archivo delimitado (de nueva manera), sino un campo de campos de tamaño fijo antiguo, como los programadores de Fortran y Cobol solían tratar. Entonces lo analizaría de esa manera (nota que separé el nombre y el apellido):
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
struct Person {
unsigned int id;
std::string forename;
std::string lastname;
uint8_t age;
// ...
};
int main() {
std::istream& ifs = std::ifstream("file.txt");
std::vector<Person> persons;
std::string line;
int fieldsize[] = {8, 9, 9, 4};
while(std::getline(ifs, line)) {
Person person;
int field = 0, start=0, last;
std::stringstream fieldtxt;
fieldtxt.str(line.substr(start, fieldsize[0]));
fieldtxt >> person.id;
start += fieldsize[0];
person.forename=line.substr(start, fieldsize[1]);
last = person.forename.find_last_not_of('' '') + 1;
person.forename.erase(last);
start += fieldsize[1];
person.lastname=line.substr(start, fieldsize[2]);
last = person.lastname.find_last_not_of('' '') + 1;
person.lastname.erase(last);
start += fieldsize[2];
std::string a = line.substr(start, fieldsize[3]);
fieldtxt.str(line.substr(start, fieldsize[3]));
fieldtxt >> age;
person.age = person.age;
persons.push_back(person);
}
return 0;
}