W szczególności jestem zainteresowany istream& getline ( istream& is, string& str );
. Czy istnieje opcja dla konstruktora ifstream, aby nakazać mu konwersję wszystkich kodowań nowej linii na „\ n” pod maską? Chcę mieć możliwość dzwonienia getline
i sprawnej obsługi wszystkich zakończeń linii.
Aktualizacja : Aby wyjaśnić, chcę mieć możliwość pisania kodu, który kompiluje się prawie wszędzie i pobiera dane wejściowe z prawie każdego miejsca. W tym rzadkie pliki, które mają „\ r” bez „\ n”. Minimalizacja niedogodności dla wszystkich użytkowników oprogramowania.
Obejście problemu jest łatwe, ale nadal jestem ciekawy, jak w standardzie elastycznie obsługiwać wszystkie formaty plików tekstowych.
getline
czyta w pełnym wierszu, aż do „\ n”, do ciągu. „\ N” jest pobierane ze strumienia, ale getline nie włącza go do ciągu. Jak dotąd jest to w porządku, ale tuż przed znakiem „\ n” może znajdować się znak „\ r”, który zostanie dołączony do ciągu.
Istnieją trzy typy końcówek linii widocznych w plikach tekstowych: „\ n” to konwencjonalne zakończenie na komputerach z systemem Unix, „\ r” był (jak sądzę) używany w starych systemach operacyjnych Mac, a Windows używa pary „\ r” po "\ n".
Problem polega na tym, że getline
pozostawia znak „\ r” na końcu łańcucha.
ifstream f("a_text_file_of_unknown_origin");
string line;
getline(f, line);
if(!f.fail()) { // a non-empty line was read
// BUT, there might be an '\r' at the end now.
}
Edytuj Dziękuję Neilowi za wskazanie, że f.good()
nie tego chciałem. !f.fail()
jest tym, czego chcę.
Mogę go usunąć ręcznie (zobacz edycję tego pytania), co jest łatwe w przypadku plików tekstowych systemu Windows. Ale martwię się, że ktoś umieści plik zawierający tylko „\ r”. W takim przypadku zakładam, że getline zajmie cały plik, myśląc, że jest to pojedyncza linia!
.. i to nawet nie biorąc pod uwagę Unicode :-)
.. może Boost ma dobry sposób na wykorzystanie jednej linii na raz z dowolnego typu pliku tekstowego?
Edytuj Używam tego do obsługi plików systemu Windows, ale nadal uważam, że nie powinienem tego robić! I to nie rozwidli plików tylko „\ r”.
if(!line.empty() && *line.rbegin() == '\r') {
line.erase( line.length()-1, 1);
}
Odpowiedzi:
Jak zauważył Neil, „środowisko wykonawcze C ++ powinno poprawnie radzić sobie z każdą konwencją zakończenia linii dla twojej konkretnej platformy”.
Jednak ludzie przenoszą pliki tekstowe między różnymi platformami, więc to nie wystarczy. Oto funkcja, która obsługuje wszystkie trzy zakończenia linii („\ r”, „\ n” i „\ r \ n”):
std::istream& safeGetline(std::istream& is, std::string& t) { t.clear(); // The characters in the stream are read one-by-one using a std::streambuf. // That is faster than reading them one-by-one using the std::istream. // Code that uses streambuf this way must be guarded by a sentry object. // The sentry object performs various tasks, // such as thread synchronization and updating the stream state. std::istream::sentry se(is, true); std::streambuf* sb = is.rdbuf(); for(;;) { int c = sb->sbumpc(); switch (c) { case '\n': return is; case '\r': if(sb->sgetc() == '\n') sb->sbumpc(); return is; case std::streambuf::traits_type::eof(): // Also handle the case when the last line has no line ending if(t.empty()) is.setstate(std::ios::eofbit); return is; default: t += (char)c; } } }
A oto program testowy:
int main() { std::string path = ... // insert path to test file here std::ifstream ifs(path.c_str()); if(!ifs) { std::cout << "Failed to open the file." << std::endl; return EXIT_FAILURE; } int n = 0; std::string t; while(!safeGetline(ifs, t).eof()) ++n; std::cout << "The file contains " << n << " lines." << std::endl; return EXIT_SUCCESS; }
źródło
t
jest pusty przed ustawieniem eofbit. Czy ten bit nie powinien być ustawiony niezależnie od wczytania innych znaków?std::get_line
które ignoruje pustą ostatnią linię. Użyłem następującego kodu w przypadkustd::get_line
is.setstate(std::ios::eofbit); if (t.empty()) is.setstate(std::ios::badbit); return is;
Środowisko wykonawcze C ++ powinno poprawnie obsługiwać dowolną konwencję końca linii dla danej platformy. W szczególności ten kod powinien działać na wszystkich platformach:
#include <string> #include <iostream> using namespace std; int main() { string line; while( getline( cin, line ) ) { cout << line << endl; } }
Oczywiście, jeśli masz do czynienia z plikami z innej platformy, wszystkie zakłady są wyłączone.
Ponieważ dwie najpopularniejsze platformy (Linux i Windows) obie kończą wiersze znakiem nowego wiersza, przy czym Windows poprzedza go znakiem powrotu karetki, możesz sprawdzić ostatni znak
line
ciągu w powyższym kodzie, aby sprawdzić, czy tak jest,\r
a jeśli tak usuń go przed wykonaniem przetwarzania specyficznego dla aplikacji.Na przykład możesz zapewnić sobie funkcję w stylu getline, która wygląda mniej więcej tak (nie testowana, użycie indeksów, substr itp. Tylko do celów pedagogicznych):
ostream & safegetline( ostream & os, string & line ) { string myline; if ( getline( os, myline ) ) { if ( myline.size() && myline[myline.size()-1] == '\r' ) { line = myline.substr( 0, myline.size() - 1 ); } else { line = myline; } } return os; }
źródło
safegetline
jest ważną częścią rozwiązania. Ale jeśli ten program jest kompilowany w systemie Windows, czy będę musiał również otworzyć plik w formacie binarnym? Czy kompilatory Windows (w trybie tekstowym) pozwalają '\ n' zachowywać się jak '\ r' '\ n'?ifstream f("f.txt", ios_base :: binary | ios_base::in );
Czy czytasz plik w trybie BINARNY czy TEKSTOWY ? W TEKSTU trybie pasza powrót / linia przewóz pary, CRLF , jest interpretowana jako TEKST końca linii lub znaku końca linii, ale w BINARY Ci pobrać tylko JEDEN bajt na raz, co oznacza, że zarówno charakter koniecznościąbyć ignorowane i pozostawione w buforze do pobrania jako kolejny bajt! Powrót karetki oznacza w maszynie do pisania, że wózek maszyny do pisania, w którym leży ramię drukujące, osiągnął prawą krawędź papieru i powrócił do lewej krawędzi. To bardzo mechaniczny model mechanicznej maszyny do pisania. Wówczas wysunięcie wiersza oznacza, że rolka papieru jest nieco obrócona do góry, aby papier mógł rozpocząć kolejny wiersz pisania. O ile pamiętam jedna z małych cyfr w ASCII oznacza przesunięcie w prawo o jeden znak bez wpisywania, martwy znak i oczywiście \ b oznacza cofnięcie: cofnij samochód o jeden znak. W ten sposób możesz dodać efekty specjalne, takie jak podkład (typ podkreślenia), przekreślenie (typ minus), przybliżone różne akcenty, anulowanie (typ X), bez konieczności korzystania z rozszerzonej klawiatury, po prostu dostosowując położenie samochodu wzdłuż linii przed wejściem do linii zasilającej. Możesz więc używać napięć ASCII wielkości bajtów do automatycznego sterowania maszyną do pisania bez komputera pomiędzy nimi. Po wprowadzeniu automatycznej maszyny do pisaniaAUTOMATYCZNY oznacza, że po osiągnięciu najdalszej krawędzi papieru samochód jest cofany w lewo ORAZ zastosowany wysuw linii, czyli zakłada się, że samochód jest automatycznie cofany, gdy rolka przesuwa się do góry! Więc nie potrzebujesz obu znaków sterujących, tylko jeden, \ n, nowy wiersz lub nowy wiersz.
Nie ma to nic wspólnego z programowaniem, ale ASCII jest starszy i HEJ! wygląda na to, że niektórzy ludzie nie myśleli, kiedy zaczęli pisać tekst! Platforma UNIX zakłada automatyczną maszynę elektryczną; model Windows jest bardziej kompletny i pozwala na sterowanie maszynami mechanicznymi, chociaż niektóre znaki sterujące stają się coraz mniej przydatne w komputerach, jak np. znak dzwonka, 0x07, jeśli dobrze pamiętam ... Niektóre zapomniane teksty musiały być pierwotnie przechwycone za pomocą znaków sterujących do maszyn do pisania sterowanych elektrycznie i utrwalił model ...
Właściwie poprawną odmianą byłoby po prostu dołączenie \ r, wysuw wiersza, powrót karetki jest niepotrzebny, to znaczy automatyczny, stąd:
char c; ifstream is; is.open("",ios::binary); ... is.getline(buffer, bufsize, '\r'); //ignore following \n or restore the buffer data if ((c=is.get())!='\n') is.rdbuf()->sputbackc(c); ...
byłby najbardziej poprawnym sposobem obsługi wszystkich typów plików. Zauważ jednak, że \ nw TEKST trybie jest w rzeczywistości parą bajtów 0x0d 0x0a, ale 0x0d JEST po prostu \ r: \ n obejmuje \ r w trybie TEKST , ale nie w trybie BINARNYM , więc \ n i \ r \ n są równoważne ... lub Powinien być. W rzeczywistości jest to bardzo podstawowe zamieszanie w branży, typowa bezwładność w branży, ponieważ konwencja mówi o CRLF, na WSZYSTKICH platformach, a następnie należy do różnych interpretacji binarnych. Ściśle mówiąc, pliki zawierające TYLKO 0x0d (powrót karetki) jako \ n (CRLF lub nowy wiersz) są zniekształcone w TEKŚCIEtryb (maszyna do pisania: po prostu zwróć samochód i przekreśl wszystko ...) i są nieliniowym formatem binarnym (albo \ r lub \ r \ n, czyli zorientowanym na wiersz), więc nie powinieneś czytać jako tekstu! Kod powinien zawieść, być może z jakąś wiadomością użytkownika. Nie zależy to tylko od systemu operacyjnego, ale także od implementacji biblioteki C, co zwiększa zamieszanie i możliwe warianty ... (szczególnie w przypadku przezroczystych warstw tłumaczenia UNICODE, dodając kolejny punkt artykulacji dla mylących odmian).
Problem z poprzednim fragmentem kodu (mechaniczna maszyna do pisania) polega na tym, że jest on bardzo nieefektywny, jeśli nie ma \ n znaków po \ r (automatyczna maszyna do pisania). Wtedy też zakłada tryb BINARNY , w którym biblioteka C jest zmuszona ignorować interpretacje tekstu (ustawienia regionalne) i oddawać zwykłe bajty. Nie powinno być różnicy w rzeczywistych znakach tekstu między obydwoma trybami, tylko w znakach kontrolnych, więc ogólnie rzecz biorąc, czytanie BINARY jest lepsze niż tryb TEKST . To rozwiązanie jest wydajne dla BINARYtryb typowych plików tekstowych systemu operacyjnego Windows niezależnie od odmian biblioteki C i nieefektywny w przypadku innych formatów tekstowych platformy (w tym tłumaczenia stron internetowych na tekst). Jeśli zależy Ci na wydajności, najlepszym rozwiązaniem jest użycie wskaźnika funkcji, wykonanie testu kontrolek linii \ r vs \ r \ n w dowolny sposób, a następnie wybranie najlepszego kodu użytkownika getline do wskaźnika i wywołanie go z to.
Nawiasem mówiąc, pamiętam, że znalazłem też kilka plików tekstowych \ r \ r \ n ... co przekłada się na tekst w dwóch wierszach, tak jak jest to nadal wymagane przez niektórych użytkowników tekstu drukowanego.
źródło
Jednym z rozwiązań byłoby wyszukanie i zamiana wszystkich końcówek na '\ n' - tak jak np. Git robi to domyślnie.
źródło
Oprócz pisania własnego niestandardowego programu obsługi lub korzystania z zewnętrznej biblioteki, nie masz szczęścia. Najłatwiej jest to sprawdzić, aby się upewnić
line[line.length() - 1]
nie ma znaku „\ r”. W Linuksie jest to zbędne, ponieważ większość linii kończy się na '\ n', co oznacza, że stracisz sporo czasu, jeśli jest to pętla. W systemie Windows jest to również zbędne. A co z klasycznymi plikami Mac, które kończą się na „\ r”? std :: getline nie będzie działać dla tych plików w systemie Linux lub Windows, ponieważ „\ n” i „\ r” \ n 'kończą się na „\ n”, eliminując potrzebę sprawdzania „\ r”. Oczywiście takie zadanie, które działa z tymi plikami, nie zadziałałoby dobrze. Oczywiście istnieje wiele systemów EBCDIC, z czym większość bibliotek nie odważy się sobie poradzić.Sprawdzanie „\ r” jest prawdopodobnie najlepszym rozwiązaniem problemu. Czytanie w trybie binarnym umożliwiłoby sprawdzenie wszystkich trzech typowych zakończeń wierszy („\ r”, „\ r \ n” i „\ n”). Jeśli zależy Ci tylko na Linuksie i Windowsie, ponieważ zakończenia linii w starym stylu Maca nie powinny istnieć zbyt długo, sprawdź tylko '\ n' i usuń końcowy znak '\ r'.
źródło
Jeśli wiadomo, ile pozycji / liczb ma każda linia, można odczytać jedną linię z np. 4 liczbami jako
string num; is >> num >> num >> num >> num;
Działa to również z innymi zakończeniami linii.
źródło