C++
Stream C ++
Ricerca…
Osservazioni
Il costruttore predefinito di std::istream_iterator
costruisce un iteratore che rappresenta la fine del flusso. Quindi, std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(), ....
significa copiare dalla posizione corrente in ifs
fino alla fine.
Stream di stringhe
std::ostringstream
è una classe i cui oggetti hanno l'aspetto di un flusso di output (ovvero, è possibile scrivere su di essi tramite l' operator<<
), ma in realtà memorizzano i risultati di scrittura e li forniscono sotto forma di flusso.
Considera il seguente codice breve:
#include <sstream>
#include <string>
using namespace std;
int main()
{
ostringstream ss;
ss << "the answer to everything is " << 42;
const string result = ss.str();
}
La linea
ostringstream ss;
crea un tale oggetto. Questo oggetto viene prima manipolato come un normale flusso:
ss << "the answer to everything is " << 42;
In seguito, tuttavia, lo stream risultante può essere ottenuto in questo modo:
const string result = ss.str();
(il result
della stringa sarà uguale a "the answer to everything is 42"
).
Ciò è utile soprattutto quando abbiamo una classe per la quale è stata definita la serializzazione del flusso e per la quale vogliamo una stringa. Ad esempio, supponiamo di avere qualche classe
class foo
{
// All sort of stuff here.
};
ostream &operator<<(ostream &os, const foo &f);
Per ottenere la rappresentazione della stringa di un oggetto foo
,
foo f;
potremmo usare
ostringstream ss;
ss << f;
const string result = ss.str();
Quindi il result
contiene la rappresentazione della stringa dell'oggetto foo
.
Leggere un file fino alla fine
Lettura di un file di testo riga per riga
Un modo corretto di leggere un file di testo riga per riga fino alla fine di solito non è chiaro dalla documentazione di ifstream
. Consideriamo alcuni errori comuni fatti dai programmatori C ++ per principianti e un modo corretto di leggere il file.
Linee senza caratteri di spazi bianchi
Per semplicità, supponiamo che ogni riga del file non contenga simboli di spazi vuoti.
ifstream
ha operator bool()
, che restituisce true quando uno stream non ha errori ed è pronto per essere letto. Inoltre, ifstream::operator >>
restituisce un riferimento al flusso stesso, così possiamo leggere e controllare EOF (così come gli errori) in una riga con una sintassi molto elegante:
std::ifstream ifs("1.txt");
std::string s;
while(ifs >> s) {
std::cout << s << std::endl;
}
Linee con caratteri di spaziatura
ifstream::operator >>
legge lo stream fino a quando non si verifica un carattere di spazio ifstream::operator >>
, quindi il codice sopra riportato stamperà le parole da una riga su righe separate. Per leggere tutto fino alla fine della riga, usa std::getline
invece di ifstream::operator >>
. getline
restituisce il riferimento al thread con cui ha lavorato, quindi la stessa sintassi è disponibile:
while(std::getline(ifs, s)) {
std::cout << s << std::endl;
}
Ovviamente, std::getline
dovrebbe essere usato anche per leggere un file a riga singola fino alla fine.
Lettura di un file in un buffer in una volta
Infine, leggiamo il file dall'inizio alla fine senza fermarci a nessun carattere, inclusi gli spazi bianchi e le nuove righe. Se sappiamo che le dimensioni esatte del file o il limite superiore della lunghezza sono accettabili, possiamo ridimensionare la stringa e quindi leggere:
s.resize(100);
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
s.begin());
Altrimenti, dobbiamo inserire ogni carattere alla fine della stringa, quindi std::back_inserter
è ciò di cui abbiamo bisogno:
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::back_inserter(s));
In alternativa, è possibile inizializzare una raccolta con dati di flusso, utilizzando un costruttore con argomenti dell'intervallo iteratore:
std::vector v(std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
Nota che questi esempi sono applicabili anche se ifs
è aperto come file binario:
std::ifstream ifs("1.txt", std::ios::binary);
Copia di flussi
Un file può essere copiato su un altro file con stream e iteratori:
std::ofstream ofs("out.file");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(ofs));
ofs.close();
o reindirizzato a qualsiasi altro tipo di flusso con un'interfaccia compatibile. Ad esempio Stream di rete Boost.Asio:
boost::asio::ip::tcp::iostream stream;
stream.connect("example.com", "http");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(stream));
stream.close();
Array
Poiché gli iteratori possono essere pensati come una generalizzazione dei puntatori, i contenitori STL negli esempi sopra possono essere sostituiti con matrici native. Ecco come analizzare i numeri nell'array:
int arr[100];
std::copy(std::istream_iterator<char>(ifs), std::istream_iterator<char>(), arr);
Attenzione all'overflow del buffer, poiché gli array non possono essere ridimensionati al volo dopo che sono stati allocati. Ad esempio, se il codice sopra verrà alimentato con un file che contiene più di 100 numeri interi, tenterà di scrivere all'esterno della matrice e di eseguire un comportamento indefinito.
Stampa di collezioni con iostream
Stampa di base
std::ostream_iterator
consente di stampare il contenuto di un container STL su qualsiasi flusso di output senza loop espliciti. Il secondo argomento del costruttore std::ostream_iterator
imposta il delimitatore. Ad esempio, il seguente codice:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ! "));
stamperà
1 ! 2 ! 3 ! 4 !
Cast di tipo implicito
std::ostream_iterator
consente di eseguire il cast del tipo di contenuto del contenitore implicitamente. Ad esempio, sintonizziamo std::cout
per stampare valori a virgola mobile con 3 cifre dopo il punto decimale:
std::cout << std::setprecision(3);
std::fixed(std::cout);
e istanziare std::ostream_iterator
con float
, mentre i valori contenuti rimangono int
:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(std::cout, " ! "));
quindi il codice sopra produce
1.000 ! 2.000 ! 3.000 ! 4.000 !
nonostante std::vector
detenga int
s.
Generazione e trasformazione
std::generate
funzioni std::generate
, std::generate_n
e std::transform
forniscono uno strumento molto potente per la manipolazione dei dati al volo. Ad esempio, avendo un vettore:
std::vector<int> v = {1,2,3,4,8,16};
possiamo facilmente stampare il valore booleano dell'istruzione "x is even" per ogni elemento:
std::boolalpha(std::cout); // print booleans alphabetically
std::transform(v.begin(), v.end(), std::ostream_iterator<bool>(std::cout, " "),
[](int val) {
return (val % 2) == 0;
});
o stampare l'elemento quadrato:
std::transform(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
Stampa di numeri casuali delimitati da spazio:
const int N = 10;
std::generate_n(std::ostream_iterator<int>(std::cout, " "), N, std::rand);
Array
Come nella sezione sulla lettura dei file di testo, quasi tutte queste considerazioni possono essere applicate agli array nativi. Ad esempio, stampiamo i valori al quadrato da un array nativo:
int v[] = {1,2,3,4,8,16};
std::transform(v, std::end(v), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
Analisi dei file
Analisi dei file nei contenitori STL
istream_iterator
s sono molto utili per leggere sequenze di numeri o altri dati analizzabili in contenitori STL senza loop espliciti nel codice.
Utilizzando la dimensione del contenitore esplicita:
std::vector<int> v(100);
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin());
o con l'inserimento di iteratore:
std::vector<int> v;
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
std::back_inserter(v));
Si noti che i numeri nel file di input possono essere divisi per qualsiasi numero di caratteri di spazi vuoti e nuove righe.
Analisi di tabelle di testo eterogenee
Come istream::operator>>
legge il testo fino ad un simbolo spazio bianco, può essere utilizzato in while
condizioni per analizzare tabelle di dati complessi. Ad esempio, se abbiamo un file con due numeri reali seguiti da una stringa (senza spazi) su ogni riga:
1.12 3.14 foo
2.1 2.2 barr
può essere analizzato in questo modo:
std::string s;
double a, b;
while(ifs >> a >> b >> s) {
std::cout << a << " " << b << " " << s << std::endl;
}
Trasformazione
Qualsiasi funzione di manipolazione degli intervalli può essere utilizzata con std::istream_iterator
intervalli std::istream_iterator
. Uno di questi è std::transform
, che consente di elaborare i dati al volo. Ad esempio, leggiamo valori interi, moltiplicandoli per 3,14 e archiviamo il risultato in un contenitore a virgola mobile:
std::vector<double> v(100);
std::transform(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin(),
[](int val) {
return val * 3.14;
});