HOWTO · C++
Datei in String einlesen in C++
Dieser Artikel demonstriert, wie man in C++ eine Datei in einen String einliest
Auf dieser Seite
Um eine Datei in einen std::string einzulesen, verwenden Sie std::ifstream mit std::getline, wenn Sie die Datei zeilenweise verarbeiten möchten. Soll der gesamte Stream eingelesen werden, können Sie den String aus std::istreambuf_iterator<char> konstruieren. Die folgenden Beispiele erstellen jeweils ihre eigene temporäre Eingabedatei, sodass sich jedes Programm unabhängig kompilieren und ausführen lässt.
Eine Datei in C++ mit std::ifstream und std::getline in einen String einlesen
std::getline entfernt den Zeilentrenner jeder gelesenen Zeile. Wenn Sie nach jedem erfolgreichen Aufruf "\n" anhängen, fügen Sie die Trennzeichen wieder ein. Dadurch wird jedoch auch nach der letzten Zeile ein Zeilenumbruch ergänzt, selbst wenn die Quelldatei nicht mit einem Zeilenumbruch endet. Ist dieser Unterschied wichtig, verfolgen Sie, ob ein Trennzeichen vorhanden war, oder verwenden Sie eine der unten beschriebenen Methoden zum Einlesen des gesamten Streams.
Dieses vollständige Beispiel schreibt zwei Zeilen in eine temporäre Datei, öffnet sie mit std::ifstream und hängt jede Zeile an einen String an. Der Text vor dem Beispiel beschreibt den üblichen erfolgreichen Ablauf.
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-lines.txt";
{
std::ofstream sample(path);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::ifstream inputFile(path);
if (!inputFile) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
std::string fileContent;
std::string line;
while (std::getline(inputFile, line)) {
fileContent += line + "\n";
}
if (inputFile.bad()) {
std::cout << "Read failed\n";
inputFile.close();
std::filesystem::remove(path);
return 0;
}
std::cout << fileContent;
inputFile.close();
std::filesystem::remove(path);
}
Die Schleife endet, wenn std::getline keine weitere Zeile extrahieren kann. Mit einer Prüfung von bad() lässt sich ein schwerwiegender E/A-Fehler vom normalen Ende des Streams unterscheiden. Die Datei wird standardmäßig im Textmodus geöffnet. Auf Systemen, die Zeilenenden umwandeln, können sich die Bytes im resultierenden String daher von den in der Datei gespeicherten Bytes unterscheiden. Verwenden Sie den Binärmodus, wenn die Bytes unverändert erhalten bleiben müssen.
Eine Datei in C++ mit istreambuf_iterator in einen String einlesen
Für den gesamten Stream konstruieren Sie einen String aus zwei std::istreambuf_iterator<char>-Werten. Der erste Iterator liest aus dem Streampuffer; der standardmäßig konstruierte Iterator markiert das Ende. Dabei werden die Daten nicht in Zeilen aufgeteilt, sodass die vom Stream gelieferten Zeilenumbruchzeichen erhalten bleiben.
#include <filesystem>
#include <fstream>
#include <iostream>
#include <iterator>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-iterator.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::ifstream inputFile(path, std::ios::binary);
if (!inputFile) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
const std::string fileContent{
std::istreambuf_iterator<char>(inputFile),
std::istreambuf_iterator<char>()};
if (inputFile.bad()) {
std::cout << "Read failed\n";
} else {
std::cout << fileContent << '\n';
}
inputFile.close();
std::filesystem::remove(path);
}
alpha
beta
Verwenden Sie diese Form, wenn die gesamte Datei in den Arbeitsspeicher passt und eine zeilenweise Verarbeitung nicht nötig ist. Durch das Öffnen im Binärmodus wird eine Umwandlung der Zeilenenden im Textmodus vermieden. Ein std::string kann Nullbytes enthalten. Funktionen, die c_str() als nullterminierten Text behandeln, können jedoch beim ersten Nullbyte abbrechen.
Eine Datei in C++ mit rdbuf in einen String einlesen
Eine weitere Möglichkeit aus der Standardbibliothek besteht darin, den rdbuf() des Eingabestreams in einen std::ostringstream einzufügen und anschließend mit str() dessen Inhalt als std::string abzurufen. Das ist praktisch, wenn der Stringstream bereits Teil des umgebenden Codes ist. Prüfen Sie den Eingabestream nach der Übertragung, wenn Lesefehler gemeldet werden müssen.
#include <filesystem>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-rdbuf.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::ifstream inputFile(path, std::ios::binary);
if (!inputFile) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
std::ostringstream stream;
stream << inputFile.rdbuf();
if (inputFile.bad()) {
std::cout << "Read failed\n";
} else {
const std::string fileContent = stream.str();
std::cout << fileContent << '\n';
}
inputFile.close();
std::filesystem::remove(path);
}
Beim Einfügen werden Zeichen aus dem Dateipuffer in den Stringstream kopiert; str() gibt den angesammelten String zurück. Für eine direkte Konstruktion aus der gesamten Datei benötigt die Iterator-Methode weniger Zwischenschritte. Bei beiden Methoden müssen Sie weiterhin prüfen, ob die Datei geöffnet wurde und beim Lesen ein Fehler aufgetreten ist.
Mit der Funktion fread eine Datei in einen String einlesen
fread ist eine Funktion der C-Standardbibliothek, die sich auch in C++ verwenden lässt. Sie eignet sich, wenn der vorhandene Code bereits FILE*-Streams verwendet oder eine byteorientierte C-API benötigt. Sie ist nicht grundsätzlich schneller als die C++-Stream-Methoden; die Leistung hängt von der Arbeitslast und der Implementierung ab.
Dieses Beispiel ermittelt die Größe einer seekbaren Beispieldatei mit fseek und ftell, reserviert einen String dieser Größe und liest die Bytes hinein. Bei nicht seekbaren Streams oder Dateien, die zu groß für den Arbeitsspeicher sind, sollten Sie stattdessen in Blöcken fester Größe lesen. Die Dateigröße kann sich zwischen dem Ermitteln und dem Lesen ändern. Prüfen Sie deshalb weiterhin die zurückgegebene Byteanzahl und ferror.
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-fread.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
std::FILE* file = std::fopen(path.string().c_str(), "rb");
if (file == nullptr) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
if (std::fseek(file, 0, SEEK_END) != 0) {
std::cout << "Could not seek sample file\n";
std::fclose(file);
std::filesystem::remove(path);
return 0;
}
const long length = std::ftell(file);
if (length < 0 || std::fseek(file, 0, SEEK_SET) != 0) {
std::cout << "Could not determine sample size\n";
std::fclose(file);
std::filesystem::remove(path);
return 0;
}
std::string content(static_cast<std::size_t>(length), '\0');
const std::size_t bytesRead = std::fread(content.data(), 1, content.size(), file);
const bool failed = std::ferror(file) != 0 || bytesRead != content.size();
std::fclose(file);
std::filesystem::remove(path);
if (failed) {
std::cout << "Read failed or file changed while reading\n";
return 0;
}
std::cout << content << '\n';
}
Das dritte Argument gibt die Anzahl der Elemente an, das zweite die Größe eines Elements. Mit einer Elementgröße von 1 gibt der Rückgabewert die Anzahl der gelesenen Bytes an. fread kann weniger Bytes als angefordert zurückgeben; gehen Sie daher nicht davon aus, dass ein einzelner Aufruf den Puffer vollständig füllt. Dieses Beispiel setzt eine seekbare Datei voraus und verwendet den von ftell unterstützten Wertebereich von long. Für größere Dateien sollten Anwendungen geeignete plattformspezifische APIs für große Dateien verwenden oder die Daten in Blöcken lesen. Der String speichert Bytes und hängt keinen Nullterminator für Funktionen an, die C-Strings erwarten.
Mit read eine Datei in einen String einlesen
Die POSIX-Funktion read arbeitet mit einem Dateideskriptor und einem vom Aufrufer bereitgestellten Puffer. Sie gehört nicht zum C++-Standard. Binden Sie die passenden POSIX-Header ein und verwenden Sie diese Methode nur auf Plattformen, die die API bereitstellen. Ein erfolgreicher Aufruf kann weniger Bytes als angefordert zurückgeben. Wiederholen Sie die Aufrufe daher bis zum Dateiende und behandeln Sie Unterbrechungen und Fehler.
Das folgende POSIX-spezifische Programm liest die Datei in Blöcken, statt sich auf eine vorherige Messung der Dateigröße zu verlassen. Da sich die verwendeten Systemaufrufe nicht auf jeder C++-Plattform einsetzen lassen, ist dies ein POSIX-Beispiel und kein portables C++.
#include <cerrno>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
#include <fcntl.h>
#include <unistd.h>
int main() {
const auto path = std::filesystem::temp_directory_path() / "cpp-string-read.txt";
{
std::ofstream sample(path, std::ios::binary);
sample << "alpha\nbeta";
if (!sample) {
std::cout << "Could not create sample file\n";
return 0;
}
}
const int fd = ::open(path.c_str(), O_RDONLY);
if (fd < 0) {
std::cout << "Could not open sample file\n";
std::filesystem::remove(path);
return 0;
}
std::string content;
char buffer[4096];
bool failed = false;
for (;;) {
const ssize_t count = ::read(fd, buffer, sizeof buffer);
if (count > 0) {
content.append(buffer, static_cast<std::size_t>(count));
} else if (count == 0) {
break;
} else if (errno != EINTR) {
failed = true;
break;
}
}
if (::close(fd) != 0) {
failed = true;
}
std::filesystem::remove(path);
if (failed) {
std::cout << "Read or close failed\n";
return 0;
}
std::cout << content << '\n';
}
Bei einer regulären Datei gibt read am Dateiende den Wert null zurück. Bei anderen Arten von Dateideskriptoren hängt das Verhalten vom Gerät oder Stream ab. Jeder positive Rückgabewert gibt die Anzahl der anzuhängenden Bytes an; ein negativer Wert signalisiert einen Fehler, und bei EINTR kann der Aufruf wiederholt werden. Schließen Sie den Dateideskriptor immer und berücksichtigen Sie Teillesevorgänge, statt anzunehmen, dass ein einzelner Aufruf den angeforderten Puffer füllt. Das Beispiel verwendet std::string::append, um die genaue Byteanzahl einschließlich möglicher Nullbytes zu erhalten.
Fazit
Wählen Sie für portables C++ std::istreambuf_iterator oder rdbuf(), wenn der gesamte Stream in einen String übernommen werden soll. Verwenden Sie std::getline, wenn eine zeilenweise Verarbeitung sinnvoll ist. Beachten Sie, dass beim zeilenweisen Einlesen die Trennzeichen entfernt werden und ihr erneutes Anhängen eine letzte Zeile ohne abschließenden Zeilenumbruch verändern kann. Verwenden Sie fread, wenn eine C-Schnittstelle mit FILE* zum umgebenden Code passt, und POSIX-read nur, wenn die plattformspezifische API für Dateideskriptoren geeignet ist. Bei großen Dateien sollten Sie die Daten in Blöcken verarbeiten, statt die gesamte Datei im Arbeitsspeicher zu halten. Prüfen Sie außerdem sowohl das Öffnen als auch das Lesen der Datei.