尝试针对借由 string stream 抽取成的 string 对象使用 regex_replace 时出错,求解。

2022-01-28 14:39:49 +08:00
 ShikiSuen

下文有三句 regex_replace 。

注释掉还好,不注掉的话会卡壳(且作为操作对象的文本档案会被清空)。所以我想来请教各位大佬。

(我一个月前刚开始自学编程,为了维护某个开源输入法专案。)

path 是某个有权限写入的纯文本 txt 档案的路径。

这段程式码主要用来做这几点(按顺序):

一、将所有 tab 与 全形空格 等 广义上的空格 都转成 半形英数空格。如果这些空格是连续的话,合并成一个空格。

二、抽掉行首与行尾的空格。

// FORMAT CONSOLIDATOR. CREDIT: Shiki Suen.
bool LMConsolidator::ConsolidateFormat(const char *path, bool hypy) {
    ifstream zfdFormatConsolidatorIncomingStream(path);
    stringstream zfdLoadedFileStreamToConsolidateBuff; // 設立字串流。
    ofstream zfdFormatConsolidatorOutput(path); // 這裡是要從頭開始重寫檔案內容,所以不需要「 ios_base::app 」。
    
    zfdLoadedFileStreamToConsolidateBuff << zfdFormatConsolidatorIncomingStream.rdbuf();
    string zfdBuffer = zfdLoadedFileStreamToConsolidateBuff.str();
    
    // 下面這幾句用來執行非常複雜的 Regex 取代。
    regex sedWhiteSpace("\\h+"), sedLeadingSpace("^ "), sedTrailingSpace(" $");
    zfdBuffer = regex_replace(zfdBuffer, sedWhiteSpace, " ").c_str();
    zfdBuffer = regex_replace(zfdBuffer, sedLeadingSpace, "").c_str();
    zfdBuffer = regex_replace(zfdBuffer, sedTrailingSpace, "").c_str();
    
    // 漢語拼音二式轉注音。
    if (hypy) {
        // 該功能尚未正式引入。
    }
    
    // 最終將取代結果寫入檔案。
    zfdFormatConsolidatorOutput << zfdBuffer << std::endl;
    zfdFormatConsolidatorOutput.close();
    if (zfdFormatConsolidatorOutput.fail()) {
        syslog(LOG_CONS, "// REPORT: Failed to write format-consolidated data to the file. Insufficient Privileges?\n");
        syslog(LOG_CONS, "// DATA FILE: %s", path);
        return false;
    }
    zfdFormatConsolidatorIncomingStream.close();
    if (zfdFormatConsolidatorIncomingStream.fail()) {
        syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for format-consolidation. Insufficient Privileges?\n");
        syslog(LOG_CONS, "// DATA FILE: %s", path);
        return false;
    }
    return true;
} // END: FORMAT CONSOLIDATOR.
1673 次点击
所在节点    C++
25 条回复
ShikiSuen
2022-01-28 14:43:56 +08:00
忘了给出需要 include 的清单了:
```
#include <syslog.h>
#include <stdio.h>
#include <fstream>
#include <sstream>
#include <iostream>
#include <string>
#include <map>
#include <set>
#include <regex>
```
zzxxisme
2022-01-28 15:05:34 +08:00
std::regex 的语法好像比较特别。不太常用不太熟,但是我改成下面的 regex 好像是可以的。

```c++
regex sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s+"), sedTrailingSpace("\\s+$")
```
KuroNekoFan
2022-01-28 15:11:03 +08:00
好像知乎还关注了贴主哈哈
ShikiSuen
2022-01-28 15:22:07 +08:00
@zzxxisme 谢谢。程序正常执行了(不卡住了),但 Xcode 编译出来之后我发现我这 txt 档案的内容会被清空。
Inn0Vat10n
2022-01-28 15:24:54 +08:00
zfdBuffer = regex_replace(zfdBuffer, sedWhiteSpace, " ").c_str();
regex_replace 返回的是一个 rvalue, 这一行结束之后就会析构掉,zfdBuffer 指向的内存内容在后面使用的时候已经是非法的了
ShikiSuen
2022-01-28 15:25:12 +08:00
@KuroNekoFan 我确实是威注音输入法的开源专案的维护人。
最近在做新的启发式自订语汇格式统整功能,但正好在正则这一块吃了瘪。
要是 C++ 真不行的话,我就只能用 swift 写这段了。
ShikiSuen
2022-01-28 15:26:28 +08:00
@Inn0Vat10n 谢谢。没注意到居然会有这种情况。
zzxxisme
2022-01-28 15:36:55 +08:00
@Inn0Vat10n 说的那个 c_str(),只是有点奇怪,但不至于非法。zfdBuffer 它是一个 std::string ,regex_replace 返回的也是一个 std::string ,对 std::string 取 c_str()得到一个 const char*,这样最终就是把一个 const char* 赋值给 std::string ,会把 const char*复制一遍到 std::string 的。我觉得奇怪只是说,c_str()其实是不需要的。

@ShikiSuen 我当初测试用的是这个例子
```c++
std::string zfdBuffer = " \t123 456\t\n789 \taaaa ";
std::cout << '@' << zfdBuffer << '@' << std::endl;
std::regex sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s+"), sedTrailingSpace("\\s+$");
zfdBuffer = std::regex_replace(zfdBuffer, sedWhiteSpace, " ");
zfdBuffer = std::regex_replace(zfdBuffer, sedLeadingSpace, "");
zfdBuffer = std::regex_replace(zfdBuffer, sedTrailingSpace, "");
std::cout << std::endl << '@' << zfdBuffer << '@' << std::endl;
```

它的结果是会把空格,\t ,\n 这些去掉或者合并成一个空格的。所以我觉得原来 regex_replace 的问题应该是解决了的。
```
@ 123 456
789 aaaa @

@123 456 789 aaaa@
```

> 但 Xcode 编译出来之后我发现我这 txt 档案的内容会被清空
我猜可能是其他方面的问题。或者你在第一个 regex_replace 之前把 zdfBuffer 打印出来看是什么内容,然后在最后一个 regex_replace 之后再把 zdfBuffer 打印出来看是什么内容,进行对比?
ShikiSuen
2022-01-28 16:01:19 +08:00
@zzxxisme 我保留\n 是有原因的,因为输入法的用户辞典每个词音定义占一行。
Inn0Vat10n
2022-01-28 16:01:49 +08:00
@zzxxisme 你说的是对的,之前只看了那几行,想当然的以为 zfdBuffer 是个 pointer 了
zzxxisme
2022-01-28 16:38:24 +08:00
@ShikiSuen 这样我建议你从文件(也就是档案) zfdFormatConsolidatorIncomingStream 里面一行一行的读进来,每次 regex_replace 处理一行然后输出到新的文件 zfdFormatConsolidatorOutput 。

你可能会问,我能不能改一下 regex 的规则,让它不删除\n 就好了?是可以试着改成"[^\\S\r\n]+",这里\\S 就是所有 非空格字符,\\S\r\n 就是所有非空格加上换行,[^\\S\r\n]就是对“所有非空格字符和换行字符”取反,就变成了“除去换行字符的所有空格字符”。但是这会有一个不好的地方,对于"a \nb",它的替换结果还是"a \nb",而不是“a\nb”,因为这个空格不是 leading 或者 tailing 的空格,所以是被压缩成一个而不是去掉。所以我建议的是读一行处理一行。
ShikiSuen
2022-01-28 17:07:08 +08:00
@zzxxisme 我在想「\h 」是否受 C++11 / ObjCpp 11 的支持。
\h 的话,是不会包含 \n 的。
ShikiSuen
2022-01-28 17:16:38 +08:00
@zzxxisme 我改用 ObjCpp 利用 NSString 与 Foundation 内部的正则,却发现整个词库档案的内容被替换成了一个数字。

```cpp
// FORMAT CONSOLIDATOR. CREDIT: Shiki Suen.
bool LMConsolidator::ConsolidateFormat(const char *path, bool hypy) {
stringstream zfdLoadedFileStreamToConsolidateBuff; // 設立字串流。
ifstream zfdFormatConsolidatorIncomingStream(path);
zfdLoadedFileStreamToConsolidateBuff << zfdFormatConsolidatorIncomingStream.rdbuf();
ofstream zfdFormatConsolidatorOutput(path); // 這裡是要從頭開始重寫檔案內容,所以不需要「 ios_base::app 」。

// 下面這幾句用來執行非常複雜的 Regex 取代。
string zfdBufferStringC = zfdLoadedFileStreamToConsolidateBuff.str().c_str();
NSString *zfdBufferString = [NSString stringWithCString:zfdBufferStringC.c_str() encoding:[NSString defaultCStringEncoding]];
zfdBufferString = [zfdBufferString replacingWithPattern:@"[^\\S\\r\\n]+" withTemplate:@" " error:nil]; // Replace consecutive spaces to single spaces.
zfdBufferString = [zfdBufferString replacingWithPattern:@"^\\s" withTemplate:@"" error:nil]; // Initial Spaces in a line.
zfdBufferString = [zfdBufferString replacingWithPattern:@"\\s$" withTemplate:@"" error:nil]; // Trailing Spaces in a line.

// 漢語拼音二式轉注音。
if (hypy) {
// 該功能尚未正式引入。
}

// 最終將取代結果寫入檔案。
zfdFormatConsolidatorOutput << zfdBufferString << std::endl;
zfdFormatConsolidatorOutput.close();
if (zfdFormatConsolidatorOutput.fail()) {
syslog(LOG_CONS, "// REPORT: Failed to write format-consolidated data to the file. Insufficient Privileges?\n");
syslog(LOG_CONS, "// DATA FILE: %s", path);
return false;
}
zfdFormatConsolidatorIncomingStream.close();
if (zfdFormatConsolidatorIncomingStream.fail()) {
syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for format-consolidation. Insufficient Privileges?\n");
syslog(LOG_CONS, "// DATA FILE: %s", path);
return false;
}
return true;
} // END: FORMAT CONSOLIDATOR.
```
ShikiSuen
2022-01-28 17:18:42 +08:00
@zzxxisme 逐行处理的话,我今天早上倒是有测试过,问题没有任何改善就是了。
相关的脚本已经被我盖掉了。我现在想想干脆用 swift 写算了。
没想到 Cpp 这语言居然如此麻烦。
zzxxisme
2022-01-28 18:27:49 +08:00
@ShikiSuen 我对 C++的 std::regex 真的不熟,它的语法应该是这里提到的 https://en.cppreference.com/w/cpp/regex/ecmascript ,这里面没有提到\h ,所以应该是不支持的。

ObjCpp 和 C++ 是两门不同的语言,ObjCpp 我就完全不懂了…

不过如果可以选择其他语言的话,用自己更擅长的语言会更好 0.0
ShikiSuen
2022-01-28 18:43:30 +08:00
@zzxxisme ObjCpp 其实就是 C++ 与 Objective C 的缝合怪。
C++ 的东西改 cpp 后缀为 mm 、改 hpp 后缀为 hh 之后就变成了 ObjCpp 。
不过因为 Objective C 本来就支持对象特性,所以 ObjCpp 的知名度并不是很高。
ShikiSuen
2022-01-28 19:10:27 +08:00
@zzxxisme 我重写了逐行处理的版本,倒是成功了。
看来是我今天早上写的版本有别的错误。
```cpp
// FORMAT CONSOLIDATOR. CREDIT: Shiki Suen.
bool LMConsolidator::ConsolidateFormat(const char *path, bool hypy) {
ifstream zfdFormatConsolidatorIncomingStream(path);
vector<string>vecEntry;
while(!zfdFormatConsolidatorIncomingStream.eof())
{
string zfdBuffer;
getline(zfdFormatConsolidatorIncomingStream,zfdBuffer);
vecEntry.push_back(zfdBuffer);
}
ofstream zfdFormatConsolidatorOutput(path); // 這裡是要從頭開始重寫檔案內容,所以不需要「 ios_base::app 」。
// RegEx 先定義好。
regex sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s+"), sedTrailingSpace("\\s+$");
for(int i=0;i<vecEntry.size();i++)
{
vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedLeadingSpace, "").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedTrailingSpace, "").c_str();
if (hypy) {
// 該功能尚未正式引入。
}
zfdFormatConsolidatorOutput<<vecEntry[i]<<endl;
}
zfdFormatConsolidatorOutput.close();
if (zfdFormatConsolidatorOutput.fail()) {
syslog(LOG_CONS, "// REPORT: Failed to write format-consolidated data to the file. Insufficient Privileges?\n");
syslog(LOG_CONS, "// DATA FILE: %s", path);
return false;
}
zfdFormatConsolidatorIncomingStream.close();
if (zfdFormatConsolidatorIncomingStream.fail()) {
syslog(LOG_CONS, "// REPORT: Failed to read lines through the data file for format-consolidation. Insufficient Privileges?\n");
syslog(LOG_CONS, "// DATA FILE: %s", path);
return false;
}
return true;
} // END: FORMAT CONSOLIDATOR.
```
ShikiSuen
2022-01-28 19:22:21 +08:00
另外,\s 不包含中日韩全形空格,需要补一道前置处理。改过的处理模组如下:
```cpp
regex sedCJKWhiteSpace("\\u3000"), sedWhiteSpace("\\s+"), sedLeadingSpace("^\\s"), sedTrailingSpace("\\s$"); // RegEx 先定義好。
for(int i=0;i<vecEntry.size();i++)
{ // RegEx 處理順序:先將全形空格換成西文空格,然後合併任何意義上的連續空格(包括 tab 等),最後去除每行首尾空格。
vecEntry[i] = regex_replace(vecEntry[i], sedCJKWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedLeadingSpace, "").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedTrailingSpace, "").c_str();
if (hypy) {
// 該功能尚未正式引入。
}
zfdFormatConsolidatorOutput<<vecEntry[i]<<endl;
}
```
ShikiSuen
2022-01-28 19:43:50 +08:00
上述脚本还有一个问题:每跑一遍都会让空行倍增。
得让循环部分仅对非空行做处理才行:
```cpp
for(int i=0;i<vecEntry.size();i++)
{
if (vecEntry[i].size() != 0) { // 不要理會空行,否則給空行加上 endl 等於再加空行。
// RegEx 處理順序:先將全形空格換成西文空格,然後合併任何意義上的連續空格(包括 tab 等),最後去除每行首尾空格。
vecEntry[i] = regex_replace(vecEntry[i], sedCJKWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedLeadingSpace, "").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedTrailingSpace, "").c_str();
zfdFormatConsolidatorOutput<<vecEntry[i]<<endl; // 這裡是必須得加上 endl 的,不然所有行都變成一個整合行。
}
}
```
ShikiSuen
2022-01-28 20:24:38 +08:00
然后还得防止那些在经过清理后出现的空行被写入档案内:
```cpp
for(int i=0;i<vecEntry.size();i++)
{
if (vecEntry[i].size() != 0) { // 不要理會空行,否則給空行加上 endl 等於再加空行。
// RegEx 處理順序:先將全形空格換成西文空格,然後合併任何意義上的連續空格(包括 tab 等),最後去除每行首尾空格。
vecEntry[i] = regex_replace(vecEntry[i], sedCJKWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedWhiteSpace, " ").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedLeadingSpace, "").c_str();
vecEntry[i] = regex_replace(vecEntry[i], sedTrailingSpace, "").c_str();
}
if (vecEntry[i].size() != 0) { // 這句得單獨拿出來,不然還是會把經過 RegEx 處理後出現的空行搞到檔案裡。
zfdFormatConsolidatorOutput<<vecEntry[i]<<endl; // 這裡是必須得加上 endl 的,不然所有行都變成一個整合行。
}
}
```

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/831119

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX