使用 scanf 读入 std::string

Posted

技术标签:

【中文标题】使用 scanf 读入 std::string【英文标题】:Read into std::string using scanf 【发布时间】:2013-11-23 18:06:42 【问题描述】:

正如标题所说,我很好奇是否有办法用scanf读取C++字符串。

我知道我可以读取每个字符并将其插入到应得的字符串中,但我想要类似的东西:

string a;
scanf("%SOMETHING", &a);

gets() 也不起作用。

提前致谢!

【问题讨论】:

只是好奇,为什么不能使用cin或getline? 在一些使用流的编译器上需要很多时间......我正在为这样的编译器编写代码...... @Vlad:如果从标准输入读取速度太慢,您应该使用std::ios_base::syc_with_stdio(false); 启动程序。 @DietmarKühl 你的意思是 std::ios_base::sync_with_stdio(false);,对吧?我同意您的意见,这通常会产生令人印象深刻的差异。 但有时您可能只想以尽可能少的开销进行格式化阅读。 【参考方案1】:

这可以工作

char tmp[101];
scanf("%100s", tmp);
string a = tmp;

【讨论】:

我不知道我可以用 char[] 初始化 C++ 字符串 .. 非常感谢! :) 它可以工作。它也可能有未定义的行为,因为tmp 的内容可能在此处未初始化。切勿在不检查其返回值的情况下使用scanf 但在我的情况下它正在切断字符串的第一个字符。我做了这样的事情:- ``` string x; scanf("%s", &x);```【参考方案2】:

在任何情况下都不能使用gets()!使用gets()总是错误的,它已从 C11 中删除并从 C++14 中删除。

scanf() 不支持任何 C++ 类。但是,您可以将来自scanf() 的结果存储到std::string

编者注:以下代码错误,如comments 中所述。请参阅Patato、tom 和Daniel Trugman 的答案以了解正确的方法。

std::string str(100, ' ');
if (1 == scanf("%*s", &str[0], str.size())) 
    // ...

我不完全确定在scanf() 中指定缓冲区长度的方法以及参数的顺序(有可能参数&str[0]str.size() 需要颠倒,我可能会格式字符串中缺少.)。请注意,生成的 std::string 将包含一个终止空字符,并且不会更改其大小。

当然,我只会使用if (std::cin >> str) ... ,但这是一个不同的问题。

【讨论】:

您确定您的代码正在编译吗?使用 C++98 和 C++11 时出现编译错误 @Vlad:我认为代码应该编译假设您包含<string><stdio.h> 并位于一个函数中,当然。您收到什么错误消息? 它应该是scanf("%99s", &str[0])),因为scanf() 不允许在运行时使用* 指定字段宽度,就像printf() 一样。同样scanf() 是用0-终止符读入的内容的后缀,所以需要读入比str 的大小少的一个。 对于scanf* supresses 赋值,并不意味着将下一个参数作为字段宽度。 "我不完全确定在 scanf() 中指定缓冲区长度的方法以及参数的顺序" - 简单:你不能这样做它根本没有。 scanf 不支持长度参数。此代码不起作用。【参考方案3】:

问题说明:

您可以使用scanf 填充std::string 的底层缓冲区,但是(!)托管的std::string 对象将不知道更改。

const char *line="Daniel 1337"; // The line we're gonna parse

std::string token;
token.reserve(64); // You should always make sure the buffer is big enough

sscanf(line, "%s %*u", token.data());
std::cout << "Managed string: '" << token
          << " (size = " << token.size() << ")" << std::endl;
std::cout << "Underlying buffer: " << token.data()
          << " (size = " << strlen(token.data()) << ")" << std::endl;

输出:

Managed string:  (size = 0)
Underlying buffer: Daniel (size = 6)

那么,这里发生了什么? std::string 对象不知道未通过导出的官方 API 执行的更改。

当我们通过底层缓冲区写入对象时,数据发生了变化,但字符串对象并没有意识到这一点。

如果我们将原始调用:token.reseve(64) 替换为 token.resize(64)(更改托管字符串大小的调用),结果会有所不同:

const char *line="Daniel 1337"; // The line we're gonna parse

std::string token;
token.resize(64); // You should always make sure the buffer is big enough

sscanf(line, "%s %*u", token.data());
std::cout << "Managed string: " << token
          << " (size = " << token.size() << ")" << std::endl;
std::cout << "Underlying buffer: " << token.data()
          << " (size = " << strlen(token.data()) << ")" << std::endl;

输出:

Managed string: Daniel (size = 64)
Underlying buffer: Daniel (size = 6)

再一次,结果是次优的。输出正确,但大小不正确。

解决方案:

如果你真的想这样做,请按照以下步骤操作:

    致电resize 以确保您的缓冲区足够大。使用#define 作为最大长度(请参阅第 2 步了解原因):
std::string buffer;
buffer.resize(MAX_TOKEN_LENGTH);
    使用scanf,同时使用“宽度修饰符”限制扫描字符串的大小并检查返回值(返回值是扫描的令牌数):
#define XSTR(__x) STR(__x)
#define STR(__x) #x
...
int rv = scanf("%" XSTR(MAX_TOKEN_LENGTH) "s", &buffer[0]);
    以安全的方式将托管字符串大小重置为实际大小:
buffer.resize(strnlen(buffer.data(), MAX_TOKEN_LENGTH));

【讨论】:

很好,我已经在我的回答中修复了这个错误。您的答案中有一些小问题:并排字符串文字的连接不适用于整数,因此 "%" MAX_TOKEN_LENGTH "s" 是语法错误。这可以使用stringification 修复:写入#define STR0(x) #x#define STR(x) STR0(x),然后使用"%" STR(MAX_TOKEN_LENGTH) "s"。另外,string::data() 的返回类型是const char*,不应该修改,所以改用&amp;buffer[0]【参考方案4】:

下面的 sn-p 有效

string s(100, '\0');
scanf("%s", s.c_str());

【讨论】:

【参考方案5】:

这里是一个没有长度限制的版本(在输入长度未知的情况下)。

std::string read_string() 
  std::string s; unsigned int uc; int c;
  // ASCII code of space is 32, and all code less or equal than 32 are invisible.
  // For EOF, a negative, will be large than 32 after unsigned conversion
  while ((uc = (unsigned int)getchar()) <= 32u);
  if (uc < 256u) s.push_back((char)uc);
  while ((c = getchar()) > 32) s.push_back((char)c);
  return s;

出于性能考虑,getchar 肯定比scanf 快​​,并且std::string::reserve 可以预先分配缓冲区以防止频繁重新分配。

【讨论】:

【参考方案6】:

您可以构造一个适当大小的 std::string 并读入其底层字符存储:

std::string str(100, ' ');
scanf("%100s", &str[0]);
str.resize(strlen(str.c_str()));

对 str.resize() 的调用很关键,否则 std::string 对象的长度将不会被更新。感谢Daniel Trugman 指出这一点。

(为字符串保留的大小与传递给 scanf 的宽度之间没有差一错误,因为从 C++11 开始,可以保证 std::string 的字符数据后跟一个空终止符,因此有空间容纳 size+1 个字符。)

【讨论】:

【参考方案7】:
int n=15; // you are going to scan no more than n symbols
std::string str(n+1); //you can't scan more than string contains minus 1
scanf("%s",str.begin()); // scanf only changes content of string like it's array
str=str.c_str() //make string normal, you'll have lots of problems without this string

【讨论】:

以上是关于使用 scanf 读入 std::string的主要内容,如果未能解决你的问题,请参考以下文章

如何在 C++ 中将整个文件读入 std::string?

将文件读入std :: string的最有效方法是什么?

NCurses 从 stdin 读取到 std::string,C++

std::string 等效于具有空字符的数据?

为啥不能用scanf读入一个含有空格的字符串

c语言中怎样用scanf()读入带空格的字符串