如何在vim中将C语言风格的printf转换为C++语言风格的cout

4

我得到了一些老旧的代码,首先我想看看是否有可能更改一些内容,比如

printf("test %d\n", var);

转化为

std::cout << "test " << var << std::endl;

有很多这样的事情需要手动完成,非常耗时。是否有一种方法可以使用 vim 来实现这一点?

我所能做到的最远就是:

:%s/printf(\(.*\), \(.*\));/std::cout << \1 << \2 << std::endl;/g

但这只能让我得到


std::cout << "test %d\n" << var << std::endl;

我可以对代码应用clang格式,这样我可以保证在printf中逗号后面总是有一个空格。在这个例子中,空格在逗号和变量之间。
理想情况下,这个vim命令能够检测百分号以知道有多少变量,也能检测\n以知道何时用std::endl替换它。请给予建议。

听起来你更想在源代码上运行一个Perl脚本。 - πάντα ῥεῖ
@πάνταῥεῖ 我不熟悉 Perl,已经有一个 Perl 脚本可以做到这一点了吗? - user3667089
或者使用Python。硬核用户使用sed。但是我真的不会完全信任一些自动工具。 - too honest for this site
1
除了Perl外,如果您有访问权限,您还可以使用Unix sed或编写C宏。在这里的问题是我们需要查看所有的printf行以帮助您解决问题。如果您需要任何printf语句的通用解决方案,则会很麻烦。此外,vim的正则表达式也非常强大。如果您不知道Perlsed,那么请打开vim的正则表达式教程并一遍又一遍地尝试。 - lllllllllll
1
请注意,虽然现代的正则表达式变体和方言在原始的基本正则表达式上有了很大的发展(现在你可以回溯和各种奇怪的东西),但我所知道的没有一个正则表达式方言能够处理在匹配之间来回跳跃,因此如果您在格式化字符串中找到了一个匹配项,您无法获取参数中相应的匹配项。此外,仅仅使用正则表达式会给您带来另一个问题 - Some programmer dude
显示剩余6条评论
4个回答

2
你可以尝试编写宏。首先,需要一个将“%[insert here]”转换为cout格式的宏。
:reg
--- Registers
"f   0f%2xmcf"f,dwdw`ci" << ^[pa << "^[

这个宏键 F 的功能是:

  1. 0f% 跳转到行首,查找第一个百分号(假设 % 没有其他用途)。
  2. 2xmc 删除 %d 部分,然后将当前位置在行中存储在标记 c 中。
  3. f"f, 查找字符串的结尾,然后查找第一个逗号。
  4. dwdw 删除逗号,然后删除变量名。这将使变量被存储,以便稍后粘贴。
  5. [backtick]c 转到标记 c 中行中存储的位置。
  6. i" << ^[" << 插入字符串并返回命令模式。
  7. pa << "^[ 粘贴存储的变量名,然后插入字符串 << "

所以,最终结果如下:

printf("test %d\n", var); // Before
printf("test " << var << "\n"); // After

在另一个宏键 R 上,只需重复执行宏 F 多次(比如 100 次)。如果你的变量少于 100 个,它不会完成宏,因为 f% 会失败。请注意保留 HTML 标签。
:reg
--- Registers
"r   100@f

因此,一个例子是:
printf("test %d %d %d %d %d\n", var, var1, var2, var3, var4); // Before
printf("test " << var << " " << var1 << " " << var2 << " " << var3 << " " << var4 << "\n"); // After

现在编写一个C宏,将开始和结束转换为C++!
:reg
--- Registers
"c   0trcwcout << ^[f(x$F)x
  1. 0tr 跳转到行首,并查找第一个r之前的内容。这是为了处理不同缩进级别的情况。我们将定位在printf中的p处。
  2. cwcout << ^[printf更改为cout <<并切换到命令模式。
  3. f(x 查找printf(中的(并删除它。
  4. $F)x 跳转到行尾,查找最后一个)并删除它。

这样得到:

printf("test " << var << " " << var1 << " " << var2 << " " << var3 << " " << var4 << "\n"); // Before
cout << "test " << var << " " << var1 << " " << var2 << " " << var3 << " " << var4 << "\n"; // After

为了将所有内容联系起来,创建另一个名为T的宏,该宏查找printf,运行宏C,然后运行宏F。按照这个顺序完成操作,以便如果早期某个部分失败,其余命令不会运行。
:reg
--- Registers
"t   /printf^M@c@r

运行这个宏3次将会执行以下操作:
// Before
printf("test %d\n", var);
printf("test %d %d %d %d %d\n", var, var1, var2, var3, var4);
printf("test %d %d\n", var, var2);

// After
cout << "test " << var << "\n";
cout << "test " << var << " " << var1 << " " << var2 << " " << var3 << " " << var4 << "\n";
cout << "test " << var << " " << var2 << "\n";

这个解决方案并不完美,它假设在任何其他格式中都没有使用百分比,并且必须为每个printf手动重复(重复使用@@)。���望它至少有用,并展示了vim的强大功能。


很遗憾,宏并不是那样工作的。比如,如果我们给出fxx,也就是查找x并删除该字符。即使x不存在,fx也不会做任何事情,并且光标所在位置的字符(通常是一行中的第一个字符)将被删除。 - SibiCoder
@SibiCoder 我认为你没有理解重点。当你编写宏时,这是正确的。你首先执行命令 fx-"查找x"。无论它是否存在,然后你将运行 x-"删除字符"。但是,一旦宏被存储并运行,它将再次运行“查找x”。如果x不存在,它将失败,宏将停止,因此“删除字符”实际上不会运行。亲自试试吧 :) - mojo1mojo2
在这种情况下,宏可能会在任何x不存在的行中出现错误。如何解决? - SibiCoder
这个解决方案对于简单变量情况非常接近完美。也许我们需要另一个宏来将\n替换为endl - user3667089
1
@SibiCoder 的想法是,如果宏中不包含 x,则不希望运行该宏!在我们的代码中,我们只想在找到 "printf" 时运行代码。如果不存在,我们就没有必要转换为 C++ I/O,因此宏不应该运行。 - mojo1mojo2
显示剩余2条评论

1
这实际上比看起来更难,特别是当你有更复杂的格式,例如带有字段宽度说明符或其他类似的东西,或者更糟糕的是"%["格式。如果格式字符串的参数比简单变量或文字更复杂,例如如果您有函数调用,则也很复杂。
然而,对于简单的格式字符串和参数,例如问题中显示的printf调用,在脚本语言中做起来并不那么难。您获取格式字符串并将其放入字符串变量中,然后获取所有参数并将它们作为另一个变量中的字符串。在逗号上拆分参数字符串,您就有了一个参数列表。
然后迭代格式字符串,当您遇到不跟随另一个'%'字符的'%'字符时,打印格式字符串到该点并从参数列表中获取第一个参数。然后继续扫描格式字符串,并在遇到格式序列时从其列表中获取每个相应的参数。

“这是说‘我不知道vim的正则表达式,所以OP应该学点别的东西’吗?” - lllllllllll
幸运的是,我手头的代码中传递给格式字符串的参数都是简单变量。您认为是否仍然可以使用vim而不是切换到脚本语言来完成它? - user3667089
1
对于除了非常简单的情况之外,你需要比正则表达式多得多的东西。是的,可能可以用一点正则表达式和一些编程来实现。我想看看有人尝试解决 printf("%#f %F %18.2d \"quoted string\" *** ### %% %*s", 4.2, 93.46, 42, 10, "Hello, World");" - 在C中解析这个格式字符串需要几百行代码。我不是指将其转换为 "cout << 4.2 << 93.46 << 42 << ""quoted string"" << ...`,而是适当的宽度、精度值和其他格式化规则。 - Mats Petersson

1
我将匹配外部的%d\n"并在替换中添加一个空格和关闭的引号。
:%s/printf(\(.*\) \S\+, \(.*\));/std::cout << \1 " << \2 << std::endl;/g

0

答案需要更加通用! 因此,我们可以使用vim函数来检测%字符,例如%d%f%s以及转义序列字符,例如\n\r

这只是一个示例伪代码。

        :function ConvertToCPP()
        :let a = getline('.')
        : while a doesn't contain % or \ characters
        :    take any number of  \n and put as endl at end of line, if any
        :   remove off the % characters.
        : put << in between each of them
        :   call setline ('.', a)
        :return 1
        :end function

现在,你可以使用range调用这个函数,这样它会轻松地替换它们全部。

    :10,20ConvertToCPP()

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接