使用 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*
,不应该修改,所以改用&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的主要内容,如果未能解决你的问题,请参考以下文章