使用 scanf 读入 std::string

Read into std::string using scanf

本文关键字:string std 读入 scanf 使用      更新时间:2023-10-16

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

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

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

gets()也不起作用。

提前感谢!

这可以工作

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

gets()的情况!使用gets()总是错误的,它从 C11 中删除并从 C++14 中删除。

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

编者注:以下代码是错误的,如注释中所述。查看Patato,Tom和Daniel Trugman的答案,了解正确的方法。

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

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

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

问题解释:

您可以使用 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)

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

溶液:

如果您确实要执行此操作,请按照以下步骤操作:

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

下面的代码片段有效

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

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

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可以预先分配缓冲区以防止频繁的重新分配。

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

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

对 str.resize(( 的调用至关重要,否则 std::string 对象的长度将不会更新。感谢丹尼尔·特鲁格曼指出这一点。

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

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