int n;
cin>>n;
cin.ignore();
char arr[n 1];
cin.getline(arr,n);
cin.ignore();
cout<<arr;
輸入:一年中的 11
輸出:年度
我已經為空字符提供了 n 1 。那為什么我的最后一個角色被排除在外了?
uj5u.com熱心網友回復:
您為陣列分配n 1了字符,但隨后您告訴getline只有n字符可用。它應該是這樣的:
int n;
cin>>n;
cin.ignore();
char arr[n 1];
cin.getline(arr,n 1); // change here
cin.ignore();
cout<<arr;
uj5u.com熱心網友回復:
根據 cppreference.com:
https://en.cppreference.com/w/cpp/io/basic_istream/getline
表現為UnformattedInputFunction。在構造和檢查哨兵物件后,從陣列的第一個元素指向的連續位置中提取字符
*this并將它們存盤s,直到發生以下任何情況(按所示順序測驗):
- 檔案結束條件出現在輸入序列中(在這種情況下
setstate(eofbit)執行)- 下一個可用字符
c是分隔符,由 . 確定Traits::eq(c, delim)。分隔符被提取(與 不同basic_istream::get())并計入gcount(),但不存盤。count-1字符已被提取(在這種情況下setstate(failbit)被執行)。如果函式沒有提取字符(例如,如果
count < 1),setstate(failbit)執行。在任何情況下,如果
count > 0,它將空字符 CharT() 存盤到陣列的下一個連續位置并更新gcount()。
在你的情況下,n=11. 您正在分配n 1(12) 個字符,但告訴getline()只有n(11) 個字符可用,因此它僅將n-1(10) 個字符讀取到陣列中,然后'\0'在第 11 個字符中終止陣列。這就是為什么您缺少最后一個字符的原因。
of the year
^
10th char, stops here
您需要 1在呼叫時getline(),以匹配您的實際陣列大小:
cin.getline(arr,n 1);
uj5u.com熱心網友回復:
約翰的回答應該可以解決您的問題。出于正當理由,可變長度陣列(您的char arr[n 1])不是 C 標準的一部分。然而,我花了幾個小時的時間超出了問題的范圍并創建了...
C I/O 學生指南
...和一般的 I/O,重點是I部分。不要害怕,用 C 的方式來做吧!以下片段應使用符合標準的 C 編譯器進行編譯。
C I/O 和標準庫
文本輸入
這是在 C (最廣泛的文本編碼)中讀取UTF-8 編碼字串的推薦方法。我們將std::string用于存盤,這是保存 UTF-8 編碼字串的事實上的方式,以及std::getline用于讀取本身。
#include <iostream> // std::cin, std::cout, std::ws
#include <string> // std::string, std::getline
int main() {
int size;
// std::ws ignores all whitespace in the stream,
// until the first non-whitespace character.
// it's prettier and handles cases a simple .ignore() does not.
std::cin >> size >> std::ws;
std::string input;
std::getline(std::cin, input);
// This condition will most certainly be true (output will be 1).
std::cout << (size == input.size()) << '\n';
}
std::string是動態分配的,或者,你可能聽說過,在堆上。這是一個廣泛的主題,因此請從這個給定的起點隨意冒險!這對我們有什么幫助?我們可以提前在堆上存盤大小未知的字串,因為我們總是可以重新分配更大的緩沖區!std::getline在讀取輸入時分配和重新分配,直到到達換行符,因此您可以在size事先不知道的情況下閱讀。size假設這是一個提供輸入長度的學校練習,因為您可能沒有教過動態記憶體,因此您的變數很可能與字串的大小相等。不過,這是有充分理由的——它很復雜,并且會不必要地分散實際主題(演算法、資料結構等)的注意力。請記住:std::strings 與 C 風格的字串不同,它不是以 null 結尾的,但是您可以std::string通過呼叫.c_str()方法從 an 中獲取以 null 結尾的 C 風格的字串。
二進制資料
什么是二進制資料?所有不是文本的東西:影像、視頻、音樂、2003 MS Word 檔案(.doc那些,等到你看到是什么.docx)等等。通常將二進制存盤為原始位元組,這是一種表示數字的奇特方式。unsigned char是用于表示這些原始位元組的 C/C 型別(std::byte為此目的引入了 C 17。為了處理來自二進制輸入的資料,我們需要將其存盤在記憶體中的某個位置——堆疊或堆上。我們可以一次存盤整個輸入,但是二進制檔案被認為太大了(實際上,考慮到電影的大小!),所以我們通常以塊的形式讀取它- 這意味著,我們一次只讀取它的有限部分(比如 256 個字符,這是我們的緩沖區),并且我們一直讀取直到我們到達輸入的末尾(通常稱為檔案結尾或簡稱 EOF)。根據經驗,當緩沖區很小且是靜態的(不需要調整大小,如我們上面的字串)時,我們可以將其存盤在堆疊中。如果其中任何一個條件不滿足,它就會進入堆。我們應該注意到,小和大的概念非常依賴于背景關系 - 編譯器、作業系統、硬體、運行時環境(請參閱有關堆疊大小限制和嵌入式系統的執行緒)。您將選擇的緩沖區大小也是特定于任務的,因此這里也沒有規則。現在讓我們看一些代碼!
#include <array> // std::array
#include <fstream> // std::ifstream, std::ofstream
int main() {
// We open this file in binary mode.
// The default mode may modify the input.
std::ifstream input{"some_image.jpg", std::ios::binary};
// 256 is our buffer size, unsigned char is the array type.
// This is the C way of `unsigned char buffer[256]`.
std::array<unsigned char, 256> buffer;
while (input.read(buffer.data(), buffer.size())) {
// Buffer is filled, do something with it
}
// At this point, either EOF is reached or an error occurred.
if (input.eof()) {
// Less characters than the buffer's size have been read.
// .gcount() returns the number of characters read by
// the last operation.
const std::streamsize chunk_size = input.gcount();
// Do something with these characters, as in the loop.
// Valid range to access in the buffer is [0, chunk_size).
// chunk_size can be 0, too. In that case, there is no more data
// to handle.
} else {
// Some other failure, handle error.
}
}
此代碼段使用 256 位元組的小型堆疊分配緩沖區讀取檔案。std::array使用它的方法使使用變得方便和安全 - 閱讀鏈接的檔案!如果我們想使用一個大緩沖區(比如 16MB),我們將 替換std::array為std::vector:
std::vector buffer(1 << 24); // 1 << 24 gives 16MB in bytes
休息也是一樣。您也可以std::string在此處使用,因為std::string這并不意味著/強制輸入 UTF-8 編碼。有一個約定可以在代碼中輕松區分二進制資料和文本資料是很有用的。需要注意的是,讀取較小的塊使用較少的空間,但需要更多的時間——從檔案中讀取位元組涉及分別從硬碟驅動器或SSD讀取時進行作業系統系統呼叫和移動磁盤或電子。C 的物件已經為您進行緩沖以加快讀取速度,這通常是急需的優化。你會知道這是否會影響你。fstream
另一件需要注意的是EOF和錯誤處理,使用.eof()方法。我們在文本輸入檢索中省略了錯誤處理,但是如果我們不想丟失資料,我們不得不這樣做。達到時EOF,通常讀取的位元組數少于緩沖區大小,因此我們需要一種方法來了解緩沖區中有多少已被資料填充。這就是.gcount()告訴我們的。根據您正在制作的程式,如果緩沖區被部分填充(回傳非 0 值) ,您可能會將EOF錯誤視為“意外” - 例如,根據假定的規則,讀取的資料不完整.gcount()在資料應該結束之前到達檔案末尾之后創建。除此之外,EOF是完全讀取后所有檔案都在的條件。
C 風格 I/O
這可能看起來更接近學校所教的內容。正如我們已經解釋了上面的一般概念,本節將在編碼和代碼解釋方面更加豐富。我們仍然使用 C 作為一種語言,因此將使用 C 版本的 C 頭檔案和std命名空間 - 讓后面的代碼在 C 編譯器中作業,用型別和函式替換<csomething>頭檔案<something.h>并洗掉std::命名空間前綴。讓我們深入了解它!
文本輸入
C 中的 C 流(std::cin、std::fstream 等)的等價物是std::FILE. FILEs 默認情況下被緩沖,C 流也是如此。我們將std::fscanf用于讀取輸入的大小,它只是scanf將您從中讀取的流作為引數,并std::fgets用于讀取文本行。
#include <cstdio> // std::FILE, std::fscanf, std::fgets, stdin
#include <cstring> // std::strcspn
// discard_whitespace does what std::ws did above.
// It consumes all whitespace before a non-whitespace
// character from stream f.
void discard_whitespace(std::FILE* f) {
char discard;
// The leading space in the format string
// tells fscanf to consume all whitespace.
std::fscanf(f, " %c", &discard);
}
int main() {
int size;
// stdin is a macro, doesn't have a namespace,
// hence no std:: prefix.
std::fscanf(stdin, "%d", &size);
// fscanf, like std::cin, doesn't consume whitespace
discard_whitespace(stdin);
// Your school exercise will probably have a size limit for the input.
// We consider it to be 256.
const int SIZE_UPPER_BOUND = 256;
// We add some extra bytes so the maximum length input can be accomodated.
// 1 is added for the null terminator of C-style strings.
// The other 2 is because `fgets` will also read the newline,
// which can be \n or \r\n, depending on OS. See explanation after code.
char input[SIZE_UPPER_BOUND 3];
// The actual read - sizeof gets the size of our input buffer,
// we don't have to write it twice.
std::fgets(input, sizeof input, stdin);
// fgets also reads the newline, unlike `std::getline` or
// `std::cin.getline` - we have to remove it ourselves.
input[std::strcspn(input, "\r\n")] = '\0';
// This condition will be true, as in the C example.
std::fprintf(stdin, "%d\n", std::strlen(input) == size);
}
讓我們解開換行洗掉。std::strcspn查找輸入中任何給定字符的第一個位置。我們同時提供\rand\n來支持 UNIX ( \n) 和 Windows ( \r\n) 換行符終止符 - 是的,它們是不同的,請參閱 Wikipedia 上的“換行符”。通過添加空終止符'\0',我們將字串的結尾移到換行符所在的位置,基本上是“洗掉”換行符。如果這是一個學校作業,我們可以假設輸入是正確的,所以我們可以使用size 1而不是std::strcspn洗掉換行符:
input[size 1] = '\0';
This doesn't work when we don't know the input size or the input may be invalid.
As an optimization trick, observe that std::strcspn returns the line length, in this case. When you don't know the size, but you need it for later, you can save the result of std::strcspn in a variable before, and then use it instead of std::strlen:
// std::size_t is an unsigned integral type, used to represent
// array sizes and indexes in C/C
const std::size_t input_size = std::strcspn(input, "\r\n");
input[input_size] = '\0';
You'll see some people use 0 or NULL for the terminator. I recommend against it - unlike the \0 literal, that is of char type, the other two variants are implicitly casted to char. If you read the linked documentation, you'll realize NULL is even incorrect, according to the spec, as it's meant to be used in contexts that require pointers only.
An alternative method to fgets is fscanf, again. Thread carefully, though - while a simple %s may do it, it makes your code vulnerable to buffer overflow exploits. See this StackOverflow thread on disadvantages of scanf, too. Let's see the (safe) code:
std::fscanf(stdin, "%6[^\r\n]s", input);
該數字將輸入大小限制為我們的SIZE_UPPER_BOUND,并且[^\r\n]告訴fscanf讀取所有字符到\ror \n。使用此方法,您可以洗掉discard_whitespace呼叫,因為fscanf動詞%s消耗前導空格。缺點是您必須保持輸入字串中的大小限制和緩沖區大小同步 - 除了動態構建格式字串fscanf之外,您無法動態指定輸入大小,這對于學校作業來說太過分了)。這在更大的代碼庫中是一個問題,但對于一個檔案、一次性的學校作業來說,這并不是什么大問題,所以你可能更喜歡,因為它的作業量更少。也不讀取緩沖區中的換行符。fscanffgetsfscanf
二進制資料
std::cin.readC 世界中 C 的等價物是std::fread. 代碼將類似于它的 C 對應物:
#include <cstdio>
int main() {
// The second parameter is the file access mode.
// In this case, it is read (r) binary (b).
std::FILE* f = std::fopen("some_image.jpg", "rb");
unsigned char buffer[256];
std::size_t chunk_size;
while (chunk_size = std::fread(buffer, sizeof buffer[0], sizeof buffer, f)) {
// chunk_size == sizeof buffer, do something with the buffer
}
if (std::feof(f)) {
// chunk_size != sizeof buffer, do something with buffer
// or handle as error
} else {
// an error occurred, handle it
}
// We need to close the file, unlike in C , where it is closed automatically.
std::fclose(f);
}
的論點std::fread是毛茸茸的:閱讀檔案。從回圈到錯誤處理,其他一切看起來都非常類似于 C 方式。為什么?因為它實際上是同一件事——我們只是使用不同的(標準)庫。另一個相似之處是 CI/O 在默認情況下也是緩沖的,就像 C 一樣。不同的是最后的行 - 呼叫std::fclose. 我們沒有在 C 代碼中做任何類似的事情,對吧?不。請記住,C 類具有建構式和析構函式,它們分別在變數生命周期的開始和結束時自動呼叫。這兩個允許我們實作RAII 技術,它將自動進行資源管理(在建構式中打開檔案,在解構式中關閉它)。RAII 用于內部std::string和std::vector(以及其他容器、智能指標和其他)。換句話說, 的解構式std::ifstream在 結尾處關閉檔案main(),就像我們在這里所做的那樣,手動關閉檔案。
混合方法(??)
你想把兩者結合起來嗎?所以它看起來。先說缺點:
- C I/O 庫,由于它的構建方式,與 C 的 (函式呼叫和額外的函式呼叫相比,特別是在使用和運算子和流操作符時,更注意以高性能方式使用,因為它們中的每一個都是一個函式呼叫,與使用 C 庫的單個普通函式呼叫/操作相比)。也可以在 i/ostream speed 上查看這個 StackOverflow 執行緒。C 庫也更冗長,尤其是在輸出的情況下(聽說過“雪佛龍地獄”嗎?)
virtual<<>> - CI/O 庫很容易使用不當/不安全,簡潔的簡寫命名使代碼難以遵循,并且輸出無法擴展以支持自定義型別(這是在 C 中使用 C 樣式 I/O 時的問題)。
malloc考慮到在 C 中管理堆記憶體的唯一方法是andfree,正確處理動態緩沖區也需要非常小心。 - 如果
std::string在視線中留下任何痕跡(或者我聽說過),有些學校可能會把你釘在十字架上 - 使用 C 風格的型別(例如,
char[N]而不是std::array<char, N>)更容易 - 無需包含標頭,因為型別是內置的原始型別,并且型別較少。在學校演算法練習等簡短的一次性程式中可能是首選。
有了這些,我們就可以看看在閱讀文本和二進制時如何方便地將兩者結合起來!
文本輸入
我們將利用 C 風格型別的簡潔性和 C 的 I/O 庫的易用性:
#include <iostream>
int main() {
int size;
std::cin >> size >> std::ws;
const int SIZE_UPPER_BOUND = 256;
char input[SIZE_UPPER_BOUND 1];
std::cin.getline(input, sizeof input);
// Input done, solve the problem.
}
老師們不必為您在這個兔子洞中潛水后開始使用的所有標準庫惡作劇而撓頭std::string。std::getline作為程式員,您不必為了讀取字串和 int 就清理換行符結尾并記住晦澀難懂的格式說明符。專注于代碼并解決問題,而無需除錯輸入讀取邏輯,永遠 - 它只是作業!
二進制資料
C 的 I/O 庫型別的錯綜復雜的層次樹讓你害怕,喜歡干凈的匯編輸出,就像 Linus Torvalds 一樣。你還是有點害怕手動管理記憶體,所以你選擇了這個解決方案:
#include <cstdio>
#include <vector>
int main() {
// The second parameter is the file access mode.
// In this case, it is read (r) binary (b).
std::FILE* f = std::fopen("some_image.jpg", "rb");
std::vector<unsigned char> buffer(1 << 24);
std::size_t chunk_size;
while (chunk_size = std::fread(buffer.data(), sizeof buffer[0], buffer.size(), f)) {
// use the buffer
}
if (std::feof(f)) {
// handle EOF
} else {
// handle error
}
std::fclose(f);
}
奇怪的選擇,因為您仍然手動管理檔案的生命周期。雖然這可能不是最好的例子,但將 C RAII 容器與 C 庫一起使用并不少見——記憶體安全至關重要。
瑣事
- 像往常一樣,權衡你的決定
using namespace std;
你不需要的很酷的東西:
- 在程式開頭使用一行來加速 C I/O (但要小心)
- 禁用 CI/O 緩沖
- 禁用 C I/O 緩沖
結論
I/O 是基本 CS 概念、硬體和軟體內部作業以及 C 的特性和怪癖的擁擠交匯點。一次盡你所能,專注于重要的事情,并確保你建立在堅實的基礎之上。
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/493794.html
上一篇:檢查向量是否通過頂點c
