#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是动态分配的,或者你可能听说过,on the heap。这是一个很宽泛的主题,所以从这个给定的起点开始,你可以自由地冒险!这对我们有什么帮助?我们可以在堆上提前存储未知大小的字符串,因为我们总是可以重新分配一个更大的缓冲区!std::getline在读取输入时进行分配和重新分配,直到到达换行符。这样你就可以在不知道size的情况下阅读了。你的size变量很可能等于字符串的大小,假设这是一个学校练习,因为你可能没有学习过动态内存,所以输入长度是提供的。尽管有很好的理由--它很复杂,而且会不必要地分散对实际主题的注意力(算法、数据结构等)。请记住:std::string与C样式字符串不同,它不是以空结尾的,但您可以通过调用.c_str()方法从std::string获取以空结尾的C样式字符串。
二进制数据
什么是二进制数据?所有不是文本的数据:图像、视频、音乐、2003 MS Word文档(.doc的,等着看what .docx is)和许多其他的。习惯上把二进制存储为 raw bytes,这是表示数字的一种奇特方式。unsigned char是用于表示这些原始字节的C/C类型(C17为此引入了std::byte。为了处理来自二进制输入的数据,我们需要将其存储在内存中的某个地方--要么在堆栈上,要么在堆上。我们可以一次存储整个输入,但二进制文件被认为太大了(而且,实际上,想象一下一部电影的大小),所以我们通常是分块阅读,也就是说,我们一次只读有限的一部分(比如说256个字符,这是我们的 buffer),然后我们继续阅读,直到到达输入的末尾(通常称为end-of-file or, short, EOF)。根据经验,当缓冲区较小且为静态时(不需要调整大小,就像上面的字符串一样),我们可以把它存储在堆栈中。如果不满足这些条件中的任何一个,我们应该注意到 small 和 large 的概念是非常依赖于上下文的-编译器、操作系统、硬件、运行时环境(参见this thread on stack size limits和embedded systems)。您将选择的缓冲区大小也是特定于任务的,所以这里也没有规则。现在让我们看一些代码!
#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.
}
}
其余的也是一样的。这里你也可以使用std::string,因为std::string并不暗示/强制输入的UTF-8编码。在代码中有一个容易区分二进制数据和文本数据的约定是很有用的。需要注意的是,以较小的块阅读使用较少的空间,但是花费更多的时间-从文件获取字节涉及X1 E12 F1 X和移动盘或电子,当从X1 E13 F1 X或X1 E14 F1 X阅读时,C++的fstream对象已经为你做了缓冲来加快读取速度,这通常是一个非常需要的优化,你会知道这是否会影响你。 另一件需要注意的事情是EOF和错误处理,使用.eof()方法。我们在文本输入检索中省略了错误处理,但是在这里,如果我们不想丢失数据,我们必须这样做。当达到EOF时,通常读取的字节数小于缓冲区大小。所以我们需要一种方法来知道缓冲区中有多少被数据填满了,这就是.gcount()告诉我们的,根据你所做的程序,如果缓冲区被部分填充,您可以将EOF错误视为“意外”错误(.gcount()返回非0值)-例如,根据假定在其之后创建的规则,读取的数据不完整,或者换句话说,在数据应该结束之前到达了文件的结尾。除此之外,EOF是一个条件,即所有文件在完全读取后都在。
#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查找输入中任何给定字符的第一个位置。我们提供了\r和\n,以支持UNIX(1米39英寸1 x)和Windows(\r\n)换行符-是的,它们是不同的,see Wikipedia, on "Newline"。通过添加空终止符,'\0',我们将字符串的结尾移动到换行符所在的位置,如果这是一个学校作业,我们可以假设输入是正确的,所以我们可以使用size + 1代替std::strcspn来删除换行符:
// 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';
该数字将输入大小限制为SIZE_UPPER_BOUND,[^\r\n]告诉fscanf读取\r或\n之前的所有字符。因为fscanf与%s动词一起使用会消耗前导空格。fscanf的缺点是,您必须保持输入字符串的大小限制和缓冲区大小同步-除了building the format string dynamically之外,你没有办法动态地指定输入大小,这对于学校作业来说是多余的)。这在更大的代码库中是一个问题,但是对于一个文件,一次学校作业来说,这不是什么大问题,所以你可能更喜欢fscanf而不是fgets,因为它的工作量更小。fscanf也不会读取缓冲区中的换行符。
#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);
}
#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);
}
3条答案
按热度按时间ulydmbyx1#
您为数组分配了
n+1
个字符,但随后您告诉getline
只有n
个字符可用。lmyy7pcs2#
Per cppreference.com:
https://en.cppreference.com/w/cpp/io/basic_istream/getline
表现为 * UnformatedInputFunction 。构造并检查sentry对象后,从
*this
中提取字符,并将它们存储在s
指向其第一个元素的数组中的连续位置,直到发生以下任一情况(按所示顺序测试):1.输入序列中出现文件结束条件(在这种情况下,执行
setstate(eofbit)
)1.下一个可用字符
c
是由Traits::eq(c, delim)
确定的分隔符。分隔符被提取(与basic_istream::get()
不同)并向gcount()
计数,但不被存储。1. * 已提取
count-1
个字符(在这种情况下,将执行setstate(failbit)
)。**如果函数未提取字符(例如,如果
count < 1)
,则执行setstate(failbit)
。count > 0
,则将空字符Chart()存储到数组的下一个连续位置,并更新gcount()
。**在您的例子中,
n=11
。您分配了n+1
(12)个字符,但告诉getline()
只有n
(11)个字符可用,因此它只将n-1
(10)个字符读入数组,然后在第11个字符中以'\0'
结束数组。这就是为什么缺少最后一个字符的原因。当调用
getline()
时,您需要+1
,以匹配您的实际数组大小:sxpgvts33#
john的回答应该可以解决你的问题。Variable-length arrays(你的
char arr[n+1]
)不是C++标准for justified reasons的一部分。然而,我花了几个小时的时间来超越问题的范围,创建...C++ I/O学员指南
...和I/O,重点是I部分。不用担心,用C的方式来做!下面的代码片段应该用符合标准的C编译器来编译。
C++ I/O和标准库
文本输入
这是在C++中阅读UTF-8 encoded strings的推荐方法,UTF-8 encoded strings是最广泛使用的文本编码,我们将使用
std::string
进行存储,这是保存UTF-8编码字符串的实际方法,而std::getline
用于读取本身。std::string
是动态分配的,或者你可能听说过,on the heap。这是一个很宽泛的主题,所以从这个给定的起点开始,你可以自由地冒险!这对我们有什么帮助?我们可以在堆上提前存储未知大小的字符串,因为我们总是可以重新分配一个更大的缓冲区!std::getline
在读取输入时进行分配和重新分配,直到到达换行符。这样你就可以在不知道size
的情况下阅读了。你的size
变量很可能等于字符串的大小,假设这是一个学校练习,因为你可能没有学习过动态内存,所以输入长度是提供的。尽管有很好的理由--它很复杂,而且会不必要地分散对实际主题的注意力(算法、数据结构等)。请记住:std::string
与C样式字符串不同,它不是以空结尾的,但您可以通过调用.c_str()
方法从std::string
获取以空结尾的C样式字符串。二进制数据
什么是二进制数据?所有不是文本的数据:图像、视频、音乐、2003 MS Word文档(
.doc
的,等着看what.docx
is)和许多其他的。习惯上把二进制存储为 raw bytes,这是表示数字的一种奇特方式。unsigned char
是用于表示这些原始字节的C/C类型(C17为此引入了std::byte
。为了处理来自二进制输入的数据,我们需要将其存储在内存中的某个地方--要么在堆栈上,要么在堆上。我们可以一次存储整个输入,但二进制文件被认为太大了(而且,实际上,想象一下一部电影的大小),所以我们通常是分块阅读,也就是说,我们一次只读有限的一部分(比如说256个字符,这是我们的 buffer),然后我们继续阅读,直到到达输入的末尾(通常称为end-of-file or, short, EOF)。根据经验,当缓冲区较小且为静态时(不需要调整大小,就像上面的字符串一样),我们可以把它存储在堆栈中。如果不满足这些条件中的任何一个,我们应该注意到 small 和 large 的概念是非常依赖于上下文的-编译器、操作系统、硬件、运行时环境(参见this thread on stack size limits和embedded systems)。您将选择的缓冲区大小也是特定于任务的,所以这里也没有规则。现在让我们看一些代码!这段代码使用一个256字节的堆栈分配的小缓冲区来阅读文件。
std::array
通过它的方法使使用变得方便和安全-读取链接的文档!如果我们想使用一个大缓冲区(比如16 MB),我们用std::vector
替换std::array
:其余的也是一样的。这里你也可以使用
std::string
,因为std::string
并不暗示/强制输入的UTF-8编码。在代码中有一个容易区分二进制数据和文本数据的约定是很有用的。需要注意的是,以较小的块阅读使用较少的空间,但是花费更多的时间-从文件获取字节涉及X1 E12 F1 X和移动盘或电子,当从X1 E13 F1 X或X1 E14 F1 X阅读时,C++的fstream
对象已经为你做了缓冲来加快读取速度,这通常是一个非常需要的优化,你会知道这是否会影响你。另一件需要注意的事情是
EOF
和错误处理,使用.eof()
方法。我们在文本输入检索中省略了错误处理,但是在这里,如果我们不想丢失数据,我们必须这样做。当达到EOF
时,通常读取的字节数小于缓冲区大小。所以我们需要一种方法来知道缓冲区中有多少被数据填满了,这就是.gcount()
告诉我们的,根据你所做的程序,如果缓冲区被部分填充,您可以将EOF
错误视为“意外”错误(.gcount()
返回非0值)-例如,根据假定在其之后创建的规则,读取的数据不完整,或者换句话说,在数据应该结束之前到达了文件的结尾。除此之外,EOF
是一个条件,即所有文件在完全读取后都在。C样式I/O
这可能看起来更接近于学校里教的东西。正如我们在上面解释的一般概念,这一节将在编码和代码解释方面更加丰富。我们仍然使用C作为一种语言,所以将使用C版本的C头文件和
std
命名空间-让下面的代码在C编译器中工作。用<something.h>
替换<csomething>
头文件,并从类型和函数中删除std::
命名空间前缀。文本输入
在C中,一个C流(std::cin,std::fstream等)的等价物是
std::FILE
。FILE
默认情况下是缓冲的,就像C流一样。我们将使用std::fscanf
来读取输入的大小,它只是scanf
,但它将你从中读取的流作为参数,std::fgets
用于读取文本行。让我们解开这个换行符删除。
std::strcspn
查找输入中任何给定字符的第一个位置。我们提供了\r
和\n
,以支持UNIX(1米39英寸1 x)和Windows(\r\n
)换行符-是的,它们是不同的,see Wikipedia, on "Newline"。通过添加空终止符,'\0',我们将字符串的结尾移动到换行符所在的位置,如果这是一个学校作业,我们可以假设输入是正确的,所以我们可以使用size + 1
代替std::strcspn
来删除换行符:当我们不知道输入的大小或者输入可能无效时,这个方法就不起作用了,作为一个优化技巧,注意
std::strcspn
返回的是行长度,在这个例子中,当你不知道大小,但是以后需要它时,你可以把std::strcspn
的结果保存在一个变量中,然后用它代替std::strlen
:你会看到一些人使用
0
或NULL
作为终止符。我建议不要这样做--与\0
字面量不同,\0
字面量是char
类型,其他两个变体隐式地转换为char
。如果你阅读了链接文档,你会发现NULL
甚至是不正确的,根据规范,因为它只用于需要指针的上下文中。fgets
的另一个替代方法是fscanf
。不过要小心线程--虽然简单的%s
可能会这样做,但它会使您的代码容易受到buffer overflow exploits的攻击。也请参见this StackOverflow thread on disadvantages of scanf。让我们看看(安全的)代码:该数字将输入大小限制为
SIZE_UPPER_BOUND
,[^\r\n]
告诉fscanf
读取\r
或\n
之前的所有字符。因为fscanf
与%s
动词一起使用会消耗前导空格。fscanf
的缺点是,您必须保持输入字符串的大小限制和缓冲区大小同步-除了building the format string dynamically之外,你没有办法动态地指定输入大小,这对于学校作业来说是多余的)。这在更大的代码库中是一个问题,但是对于一个文件,一次学校作业来说,这不是什么大问题,所以你可能更喜欢fscanf
而不是fgets
,因为它的工作量更小。fscanf
也不会读取缓冲区中的换行符。二进制数据
在C世界中,C的
std::cin.read
的等价物是std::fread
。代码将类似于C的对应物:std::fread
的参数非常复杂:我读了文档。其他的一切看起来都非常类似于C的方式,从循环到错误处理。为什么呢?因为它实际上是一样的--我们只是使用了不同的另一个相似之处是C I/O在缺省情况下也被缓冲,就像C的一样。不同的是最后一行--对std::fclose
的调用。我们在C代码中没有做任何类似的事情,对吧?不,记住C类有constructors和destructors,这两个函数分别在变量lifetime的开头和结尾被自动调用,这两个函数允许我们实现RAIItechnique,它将自动执行资源管理(在构造函数中打开文件,在析构函数中关闭). RAII用于std::string
和std::vector
(以及其他containers,smart pointers & others).换句话说,std::ifstream
的析构函数在main()
结束时关闭文件,就像我们在这里手动做的一样。混合方法(??)
你想把两者结合起来吗?看起来是这样。让我们谈谈缺点:
由于C++ I/O库的构建方式,与C相比,它在使用时更注重性能(一般来说,
virtual
函数调用和额外函数调用,尤其是当使用<<
和>>
运算符& stream manipulators时,因为这些运算符中的每一个都是函数调用,与使用C库的单个普通函数调用/操作相比)。也请参见this StackOverflow thread on i/ostream speed。C++库也更冗长,尤其是在输出的情况下(听说过"chevrone hell"吗?)C I/O库很容易使用不当/不安全,简洁的缩写命名使代码难以遵循,输出无法扩展以支持自定义类型(这是在C++中使用C风格I/O时的问题)。它还需要非常小心地正确处理动态缓冲区,因为在C中管理堆内存的唯一方法是
malloc
andfree
。如果看到
std::string
的踪迹(我听说过),有些学校可能会惩罚你。使用C风格的类型(例如,
char[N]
代替std::array<char, N>
)更容易--不需要包含头,因为类型是内置的原语,类型更少。在简短的一次性程序中,如学校的算法练习中,可能会更受欢迎。记住这些,我们可以看看在读取文本和二进制时如何方便地将两者结合起来!
文本输入
我们将利用C风格类型的简洁性和C++ I/O库的易用性:
教师们不必为
std::string
和std::getline
的出现以及你在这个兔子洞里开始使用的所有标准库恶作剧而挠头;你,作为程序员,不必为了读取一个字符串和一个int而清理换行符结尾和记忆晦涩的格式说明符;专注于代码和解决问题,而不必调试输入读取逻辑。永远-它只是工作!二进制数据
复杂的hierarchical tree of C++'s I/O library types让你感到害怕,干净的汇编输出让你很享受,just like Linus Torvalds,你仍然害怕手动管理内存,所以你选择了这个解决方案:
奇怪的选择,因为你仍然手动管理文件的生命周期。虽然这可能不是最好的例子,但使用C++ RAII容器和C库并不罕见-内存安全是至关重要的。
琐事
using namespace std;
的决定你不需要的酷东西:
结论
I/O是CS基本概念、硬件和软件内部工作原理以及C++特性和怪癖的密集结合点。一次尽可能地吸收并专注于重要的东西,确保你建立在坚实的基础之上。