Directly convert char* to std::string inside fscanf
当使用fscanf读取字符串并将其存储到字符串变量中时,fscanf期望将char*作为格式字符串之后的参数,即,按照我所知,这应该是这样 :
1 2 3
| char str[];
std::fscanf(filepointer,"%s
",str); |
如果我使用的是std::string str而不是char str[],我会收到一个-Wformat警告(很明显),并且该程序因Segnetation fault而崩溃(我不知道为什么,但是我并不感到惊讶)。 除了修复段错误之外,我还可以正确执行此操作,即不接收-Wformat警告。
有没有办法将" x2>转换为std::string进行"内联",即在fscanf的参数列表中,还是我必须创建一个临时的char*变量来存储数据并将其转换为 之后std::string(例如,如此处所述,我看不到将这种方法应用于我的情况的方法)。
-
scanf系列函数是从C继承的,对string这样的C ++类不了解。 您是否有理由在C ++的IO流上使用fscanf?
-
@ sepp2k嗯,一个原因(也许不是一个很好的理由)是我了解如何使用printf和格式说明符正确格式化输出,因此使用scanf似乎是很自然的。 我知道有一些使用C ++流的格式化选项(例如setprecision等),但是我还没有完全理解它们,因为,到目前为止,我无法使用对我来说很简单的C ++流来做某些事情 C函数
-
将std :: string与C风格的IO混合使用非常危险且容易出错。 不要这样做。 如果您不了解iostream,请询问有关iostream的问题。
从C ++ 17开始,您可以:
1 2 3 4
| std::string str(, 0);
fscanf(filepointer,"%s
", str.data());
str.resize(strlen(str.c_str())); |
但是你不应该。
首先,它没有按照您想要的去做。如果您有类似"Tom Jerry
"的输入行,它将仅读取"Tom",更糟糕的是,下次您从文件中读取时,它将尝试读取"Jerry"。您应该真正使用fgets(),std::istream::getline(),或者更好的是std::getline(file, str)。
其次,'\0'或char()是C ++字符串中的有效字符。如果您阅读"Tom\0Jerry
",则代码将设法读取的实际字符串将包含"Tom\0Jerry",这可能是有效的结果,但是由于该resize(strlen(str.c_str())),它会将其剪切为"Tom"。您可以通过创造性地使用%n格式说明符来缓解这种情况,但是为什么首先会使您的生活复杂化?
第三,C ++字符串输入例程可以在必要时调整字符串的大小。在C函数中,您需要指定一个字符串大小限制,该限制通常是任意的。如果您对格式说明符草率(例如在示例中),这可能导致缓冲区溢出,但是如果您不能正确处理截断的输入的特殊情况,则可能导致逻辑错误。
因此,只需将std::getline(file, str)与C ++流输入一起使用,就可以避免所有这些情况。
-
使用%s而不指定宽度是不可以
-
0是std :: string中的有效字符,但不是空格。 –求爱吗?
-
你想说啥? (cppreference.com和cplusplus.com都很不错,但是它们绝不是规范性的),您对空白的关注是什么? scanf()不会再读了,但这是可以预期的。
-
string.accessors的内容与第一个链接中的注释一样。
-
好吧,在答案中阐明了为什么根本不应该这样做。
可以将std::string与std::scanf()一起使用,如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include <cstdlib> // EXIT_FAILURE
#include <cstdio> // std::scanf()
#include <cstring> // std::strlen()
#include <string>
#include <iostream>
int main()
{
std::string foo(40, '\0'); // fill the string with 40 0s
// for pre C++11 please make it 41
int length = 0;
if (std::scanf("%39s%n", &foo[0], &length) != 1) {
std::cerr <<"Input error :(
";
return EXIT_FAILURE;
}
foo.resize(length); // let foo know its own length ^^
std::cout << foo.length() <<" chars:" << foo << '
';
} |
如果使用的是C ++,则不应使用C或C样式的函数。尤其是像std::fscanf这样的功能,它会执行不受保护的写入。这是在C ++中正确地从文件读取数据到std::string的方法:在C ++中使用ifstream逐行读取文件
-
无保护的写-那是什么?
-
不受保护/未经检查的写入是指您在不知道该位置是否实际上是您要写入的对象的一部分的情况下写入内存位置的情况。在您的示例中,如果char str[]数组小于文件中的字符串,则std::fscanf将开始在该数组之外进行写入。如果恰好在您的程序存储器中,您将获得非常奇怪和不可预测的行为。如果那超出了程序存储器的范围,则会出现分段错误。您所询问的事实表明您有很多关于内存管理的知识。
-
抱歉@Swordfish,我指的是OPs代码。您的示例仍然很糟糕,因为您依靠两次写入正确的数组大小(40和39),并且因为您在不使用方法的情况下修改了std::string对象的内部数据。这会破坏封装,并且仍然可能导致写入错误的内存位置(例如,如果使指针无效,则可以通过使字符串变异来获得第一个字符)
-
[...]并仍然可能导致写入错误的内存位置(取决于字符串内部存储数据的方式,您无法做出任何假设)–是的,我可以:basic.string / 3
-
在C ++ 11之前,访问索引size()时未定义[]运算符,如果输入字符串恰好是39个字符(不包括空终止符),则编写空终止符时代码就是这样做的。 zh.cppreference.com/w/cpp/string/basic_string/operator_at
-
我添加了有关C ++ 11之前的版本的注释。谢谢。
正如您在参考书中所读到的,它应该是:
1 2 3
| char str[];
fscanf(filepointer,"%s
", str); |
因为str是一个数组。在什么是数组衰减中阅读更多内容。
有c_str()和data(),它们可以为您提供std::string的C字符串。但是,这些函数返回一个常量指针,因此不能在fscanf()中使用(这是一个来自C的函数,因此可以处理C字符串)。
注意:在C ++ 17中,data()具有一个重载,该重载返回一个非恒定指针,因此可以用于内联用法,如下所示:
1 2 3 4
| std::string str(10, '\0'); // a string of size 10, null terminated
fscanf(filepointer,"%9s
", str.data());
str.resize(strlen(str.data())); // so that 'str.size()' is correct |
Live Demo
但是您实际上应该使用C ++流,例如std::cin而不是C IO函数。
例:
1 2
| std::string str;
std::cin >> str; |
-
c_str返回的指针是const,因此不能与scanf一起使用。
-
当然,您是对的。.我删除了&运算符..但这不是问题的重点,我知道我将使用c_str()或data()(从c ++开始)获得C-String 11),但我想反过来...
-
好的,所以我评论时进行了编辑,这使我的评论现在无效..:D
-
@gsamaras是的,是的,谢谢。.我将其标记为答案。.评论和编辑的速度难以跟上:)
-
如果在实时演示中打印str.length(),您会感到惊讶。如果要将此对象用作实际的std::string,则需要resize()。
-
正确@Kit。也更新了您的答案,指出了+1。此外,感谢您启发我一个问题...