一、引言
当我们编写代码:实现网络接收、读取文件内容等功能时,我们往往要在内存中开辟一个输入缓冲区(又名:input buffer/读缓冲区)来存贮接收到的数据。在C++里面我们可以用如下方法开辟输入缓冲区。
①使用C语言中的数组:
char buf[100] = {0};
②使用malloc/new动态分配内存:
char *pBuf = new char[100];
③使用std::string
string sBuf;
④使用vector<char> / vector<unsigned char>
vector<char> vecBuf(100);
在这里面推荐使用方法④作为输入缓冲区。方法①在栈中开辟空间,对于大数组可能会有栈内存不够的问题。方法②在堆上分配内存,但是使用完需要程序员自行手动释放(delete pBuf),而且需要一个额外的变量记录申请空间的大小。方法③只能处理字符串,不能处理二进制数据。下面具体阐述使用vector<char>作为输入缓冲区的优势。
二、使用vector<char>作为输入缓冲区的优势
(一)跟方法①相比,vector<char>可以在程序运行时调整大小
例子1:
main.cpp
#include <iostream>
#include <fstream>
#include <vector>
#include <windows.h>
// 通过stat结构体 获得文件大小,单位字节
size_t getFileSize1(const char* fileName) {
if (fileName == NULL) {
return 0;
}
// 这是一个存储文件(夹)信息的结构体,其中有文件大小和创建时间、访问时间、修改时间等
struct stat statbuf;
// 提供文件名字符串,获得文件属性结构体
stat(fileName, &statbuf);
// 获取文件大小
size_t filesize = statbuf.st_size;
return filesize;
}
int main()
{
const char *fileName = "test.txt";
std::ifstream ifs(fileName);
int nFileSize = getFileSize1(fileName);
char buf[100] = { 0 };
ifs.read(buf, sizeof(buf));
printf("%s", buf);
return 0;
}
test.txt
hello world!
运行效果:
上述的例子中,定义一个大小为100字节的数组buf,一次性读取文件test.txt中的内存,并保存到buf里面,然后打印。该代码存在的问题是:假如文件test.txt中的内容非常多,超过数组的最大容量(100个字节),则超出数组容量外(超过100个字节之外)的数据会丢失。针对该问题我们可以尝试将上述代码优化为例子2。
例子2:
我们将例子1中的语句 char buf[100] = { 0 }; 修改为:char buf[nFileSize] = { 0 };
结果编译报错了:
在例子2中,我们尝试将数组buf的大小定义为要读取的文件的大小。很明显,这样是不行的,因为定义数组的时候,数组的大小必须确定,并且得是整型。我们继续优化代码。
例子3:
main.cpp
#include <iostream>
#include <fstream>
#include <vector>
#include <windows.h>
// 通过stat结构体 获得文件大小,单位字节
size_t getFileSize1(const char* fileName) {
if (fileName == NULL) {
return 0;
}
// 这是一个存储文件(夹)信息的结构体,其中有文件大小和创建时间、访问时间、修改时间等
struct stat statbuf;
// 提供文件名字符串,获得文件属性结构体
stat(fileName, &statbuf);
// 获取文件大小
size_t filesize = statbuf.st_size;
return filesize;
}
int main()
{
const char *fileName = "test.txt";
std::ifstream ifs(fileName);
int nFileSize = getFileSize1(fileName);
std::vector<char> vecBuf(nFileSize);
ifs.read(&vecBuf[0], vecBuf.size());
for (const auto& e : vecBuf)
{
std::cout << e;
}
return 0;
}
运行效果如下:
例子3使用了vector<char>,所以可以在程序运行过程中调整大小(可以用resize()调整vector大小)。从而解决例子2中的问题。可能有些朋友会说用方法②“使用malloc/new动态分配内存”,不一样可以吗?确实是可以。但是vector<char>相当于对malloc/new进行了一层封装,使用起来更方便。而且不用手动调用delete函数释放内存,避免内存泄漏。
(二)跟方法②相比,vector<char>提供了各种方法
使用vector::reserve预分配内存
使用vector::size的记录缓冲区位置
使用vector::resize增长/清除缓冲区
使用&your_vector[0]转换为C缓冲区
使用vector::swap转换缓冲区所有权
例子4:
int bufsize = 4096;
char *pBuf = new char[bufsize];
int recv = read(sock, pbuf, bufsize)
例子4是一个网络接收的小demo。可以看到使用new的方式,需要额外增加一个变量bufsize来存贮缓冲区的大小。我们可以用vector<char>优化如下:
例子5:
std::vector<char> buf(4096); // create buffer with preallocated size
int recv = read( sock, &buf[0], buf.size() );
可以看到vector已经提供了size()方法来记录缓冲区的大小,不需要再额外增加变量了。所以使用vector<char>更方便,而且离开作用域自动释放内存,不需要手动delete,更安全。
(三)跟方法③相比,vector<char>可以存贮二进制数据
例子6:
main.cpp
#include <iostream>
#include <vector>
#include <string>
using namespace std;
int main()
{
string strBuf = "abc\0ef";
cout << strBuf << endl;
std::vector<char> vecBuf = { 'a', 'b', 'c', '\0', 'e', 'f'};
for (const auto& e : vecBuf)
{
std::cout << e;
}
return 0;
}
运行效果如下:
可以看到使用std::string丢失了'\0'之后的数据,但是vector<char>不会。所以std::string只能存贮字符串,不能存贮二进制数据。二进制数据中可能会包含0x00(即:'\0'),刚好是字符串结束标志,使用std::string会有截断问题。所以对于二进制数据的保存(比如保存图片,网络接收)我们得要用vector<char>,不要用string。
三、总结
综上所述。我们首选vector<char>作为输入缓冲区。
参考:
What is the advantage of using vector<char> as input buffer over char array?
How do I use vector as input buffer for socket in C++
A more elegant way to use recv() and vector<unsigned char>
What are differences between std::string and std::vector<char>?