C++
C ++ Streams
Zoeken…
Opmerkingen
Standaardconstructor van std::istream_iterator
construeert een iterator die het einde van de stream vertegenwoordigt. Dus std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(), ....
betekent kopiëren van de huidige positie in ifs
tot het einde.
Stringstreams
std::ostringstream
is een klasse waarvan de objecten eruit zien als een uitvoerstroom (dat wil zeggen, je kunt ze schrijven via operator<<
), maar de schrijfresultaten opslaan en ze in de vorm van een stroom aanbieden.
Overweeg de volgende korte code:
#include <sstream>
#include <string>
using namespace std;
int main()
{
ostringstream ss;
ss << "the answer to everything is " << 42;
const string result = ss.str();
}
De lijn
ostringstream ss;
maakt zo'n object. Dit object wordt eerst gemanipuleerd als een normale stream:
ss << "the answer to everything is " << 42;
Hierna kan de resulterende stroom echter als volgt worden verkregen:
const string result = ss.str();
(het result
de reeks is gelijk aan "the answer to everything is 42"
).
Dit is vooral handig als we een klasse hebben waarvoor streamserialisatie is gedefinieerd en waarvoor we een tekenreeksvorm willen. Stel bijvoorbeeld dat we wat klasse hebben
class foo
{
// All sort of stuff here.
};
ostream &operator<<(ostream &os, const foo &f);
Om de stringvoorstelling van een foo
object te krijgen,
foo f;
we zouden kunnen gebruiken
ostringstream ss;
ss << f;
const string result = ss.str();
Het result
bevat dan de stringvoorstelling van het foo
object.
Een bestand lezen tot het einde
Een tekstbestand regel voor regel lezen
Een juiste manier om een tekstbestand regel voor regel tot het einde te lezen, is meestal niet duidelijk uit ifstream
documentatie. Laten we eens kijken naar enkele veelgemaakte fouten die door beginnende C ++ -programmeurs zijn gemaakt en een goede manier om het bestand te lezen.
Lijnen zonder spaties
Laten we omwille van de eenvoud aannemen dat elke regel in het bestand geen witruimte-symbolen bevat.
ifstream
heeft operator bool()
, die true retourneert wanneer een stream geen fouten bevat en klaar is om te lezen. Bovendien ifstream::operator >>
een verwijzing naar de stream zelf, zodat we EOF (en fouten) kunnen lezen en controleren op één regel met een zeer elegante syntaxis:
std::ifstream ifs("1.txt");
std::string s;
while(ifs >> s) {
std::cout << s << std::endl;
}
Lijnen met witruimtetekens
ifstream::operator >>
leest de stream totdat er een spatie ontstaat, zodat de bovenstaande code de woorden van een regel op afzonderlijke regels afdrukt. Gebruik std::getline
plaats van ifstream::operator >>
om alles tot het einde van de regel te lezen. getline
retourneert verwijzing naar de thread waarmee het werkte, dus dezelfde syntaxis is beschikbaar:
while(std::getline(ifs, s)) {
std::cout << s << std::endl;
}
Het is duidelijk dat std::getline
ook tot het einde moet worden gebruikt om een bestand met één regel te lezen.
Een bestand in één keer in een buffer lezen
Laten we tot slot het bestand van het begin tot het einde lezen zonder te stoppen bij een willekeurig teken, inclusief witruimten en nieuwe regels. Als we weten dat de exacte bestandsgrootte of de bovengrens van de lengte acceptabel is, kunnen we de tekenreeks verkleinen en vervolgens lezen:
s.resize(100);
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
s.begin());
Anders moeten we elk teken aan het einde van de tekenreeks invoegen, dus std::back_inserter
is wat we nodig hebben:
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::back_inserter(s));
Als alternatief is het mogelijk om een verzameling met stroomgegevens te initialiseren, met behulp van een constructor met iteratorbereikargumenten:
std::vector v(std::istreambuf_iterator<char>(ifs),
std::istreambuf_iterator<char>());
Merk op dat deze voorbeelden ook van toepassing zijn als ifs
wordt geopend als binair bestand:
std::ifstream ifs("1.txt", std::ios::binary);
Streams kopiëren
Een bestand kan worden gekopieerd naar een ander bestand met streams en iterators:
std::ofstream ofs("out.file");
std::copy(std::istreambuf_iterator<char>(ifs), std::istreambuf_iterator<char>(),
std::ostream_iterator<char>(ofs));
ofs.close();
of omgeleid naar een ander type stream met een compatibele interface. Bijvoorbeeld Boost.Asio-netwerkstream:
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();
arrays
Aangezien iterators kunnen worden beschouwd als een generalisatie van pointers, kunnen STL-containers in de bovenstaande voorbeelden worden vervangen door native arrays. Hier leest u hoe u getallen in een array kunt parseren:
int arr[100];
std::copy(std::istream_iterator<char>(ifs), std::istream_iterator<char>(), arr);
Pas op voor buffer overflow, omdat arrays niet direct kunnen worden aangepast nadat ze zijn toegewezen. Als de bovenstaande code bijvoorbeeld wordt gevoed met een bestand dat meer dan 100 gehele getallen bevat, zal het proberen buiten de array te schrijven en ongedefinieerd gedrag vertonen.
Collecties afdrukken met iostream
Eenvoudig afdrukken
std::ostream_iterator
maakt het mogelijk om de inhoud van een STL-container af te drukken naar elke uitvoer zonder expliciete lussen. Het tweede argument van std::ostream_iterator
constructor stelt het scheidingsteken in. Bijvoorbeeld de volgende code:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ! "));
zal afdrukken
1 ! 2 ! 3 ! 4 !
Impliciet type cast
std::ostream_iterator
maakt het mogelijk het inhoudstype van de container impliciet te casten. Laten we bijvoorbeeld std::cout
afstemmen om waarden met drijvende komma af te drukken met 3 cijfers achter de komma:
std::cout << std::setprecision(3);
std::fixed(std::cout);
en std::ostream_iterator
met float
, terwijl de ingesloten waarden int
blijven:
std::vector<int> v = {1,2,3,4};
std::copy(v.begin(), v.end(), std::ostream_iterator<float>(std::cout, " ! "));
dus de bovenstaande code levert op
1.000 ! 2.000 ! 3.000 ! 4.000 !
ondanks std::vector
bevat int
s.
Generatie en transformatie
std::generate
, std::generate_n
en std::transform
bieden een zeer krachtig hulpmiddel voor on-the-fly gegevensmanipulatie. Bijvoorbeeld, met een vector:
std::vector<int> v = {1,2,3,4,8,16};
we kunnen eenvoudig de booleaanse waarde van de "x is even" -instructie voor elk element afdrukken:
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;
});
of druk het vierkante element af:
std::transform(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "),
[](int val) {
return val * val;
});
N-door spaties gescheiden willekeurige getallen afdrukken:
const int N = 10;
std::generate_n(std::ostream_iterator<int>(std::cout, " "), N, std::rand);
arrays
Net als in het gedeelte over het lezen van tekstbestanden, kunnen bijna al deze overwegingen worden toegepast op native arrays. Laten we bijvoorbeeld vierkante waarden afdrukken vanuit een native array:
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;
});
Bestanden parseren
Bestanden in STL-containers parseren
istream_iterator
zijn erg handig voor het lezen van reeksen getallen of andere ontleedbare gegevens in STL-containers zonder expliciete lussen in de code.
Gebruik expliciete containergrootte:
std::vector<int> v(100);
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin());
of met het invoegen van iterator:
std::vector<int> v;
std::copy(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
std::back_inserter(v));
Merk op dat de getallen in het invoerbestand kunnen worden gedeeld door een willekeurig aantal witruimte-tekens en nieuwe regels.
Heterogene teksttabellen parseren
Zoals istream::operator>>
leest de tekst tot een witruimte symbool, kan het worden gebruikt in while
conditie te ontleden complexe gegevens tabellen. Als we bijvoorbeeld een bestand hebben met twee reële getallen gevolgd door een string (zonder spaties) op elke regel:
1.12 3.14 foo
2.1 2.2 barr
het kan als volgt worden ontleed:
std::string s;
double a, b;
while(ifs >> a >> b >> s) {
std::cout << a << " " << b << " " << s << std::endl;
}
transformatie
Elke bereikmanipulatiefunctie kan worden gebruikt met std::istream_iterator
bereiken. Een daarvan is std::transform
, waarmee gegevens direct kunnen worden std::transform
. Laten we bijvoorbeeld gehele waarden lezen, ze vermenigvuldigen met 3.14 en het resultaat opslaan in een container met drijvende komma:
std::vector<double> v(100);
std::transform(std::istream_iterator<int>(ifs), std::istream_iterator<int>(),
v.begin(),
[](int val) {
return val * 3.14;
});