源文件里面写文本,需要手动一行一行给它加\r\n\,挺不爽。所以现在想开发一个小工具解决这个问题,直接将文本转为源文件里需要的形式。想起来容易,做起来起始没那么容易,学到了挺多。
源文件,可以自己拿去编译。windows环境,系统编码设置为utf8,仅支持utf8,否则一定会有问题。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
char siweilicheng[];
// 仅支持utf8编码下的中文字符,按3字节计算
// 返回值是中文字符所占内存空间地址最小的那一个
// 在此位置可以直接insert,表示在此中文字符前插入
// 也可以直接substr,表示从此中文字符前面开始截断。如果要包括此中文字符就得+3。
size_t find_first_hanzi_of(string& str, string hanzi)
{
char c0 = hanzi[0];
char c1 = hanzi[1];
char c2 = hanzi[2];
string piece = str;
size_t sum = 0;
do
{
size_t pos = piece.find_first_of(c2);
if (pos == string::npos)
{
return string::npos;
}
if (piece[pos - 2] == c0 && piece[pos - 1] == c1)
{
return sum + pos - 2;
}
piece = piece.substr(pos + 1);
// 此时pos的位置并不是真的hanzi的位置,所以切断,计算偏移后继续找
sum = sum + pos + 1;
} while (true);
}
int main(int argc, char* argv[])
{
if (argc == 1)
{
printf("用法\r\n\
文本文件\r\n\
--info");
return 0;
}
if (argc > 2)
{
return 0;
}
int ret = strcmp(argv[1], "--info");
if (ret == 0 && argc == 2)
{
cout << siweilicheng << endl;
return 0;
}
string filename = argv[1];
ifstream ifs;
ifs.open(filename);
if (ifs.fail())
{
return 0;
}
string line;
vector<string> lines;
while (getline(ifs, line))
{
if (line.size() <= 180)
{
lines.push_back(line);
}
else
{
do
{
int pos = find_first_hanzi_of(line, "。");
if (pos == -1)
{
lines.push_back(line);
break;
}
string piece;
piece = line.substr(0, pos + 3);
line = line.substr(pos + 3);
lines.push_back(piece);
} while (line.size() > 180);
if (line.size() > 0)
{
lines.push_back(line);
}
}
}
ifs.close();
for (auto& line : lines)
{
int size = line.size();
for (int i = 0; i < size; i++)
{
if (line[i] == '\\' || line[i] == '\"')
{
line.insert(i, "\\");
size++;
i++;
}
}
}
ofstream ofs;
ofs.open(filename);
if (ofs.fail())
{
return 0;
}
int size = lines.size();
size--;
for (int i = 0; i < size; i++)
{
ofs << lines[i] << "\\r\\n\\\n";
}
ofs << lines[size] << "\n";
ofs.close();
return 0;
}
char siweilicheng[] =
{
"在程序里面写思维历程,这样的改善挺好,就是在源文件里面写,需要手动一行一行给它加\\r\\n\\,挺不爽。\r\n\
所以现在想开发一个小工具解决这个问题,直接将文本转为源文件里需要的形式。\r\n\
这样需要做一件事就行,就是当遇到换行时,将它前面一段的末尾加上\\r\\n\\即可。\r\n\
调试的时候遇到问题,那就是调试参数支持空格,但是不支持换行。\r\n\
就算是实际用的时候,命令行也是不支持换行的,如果一定要换行,也得手动加换行符,那还不如直接在源文件里面改。\r\n\
所以,现在把需求改为,打开一个文件,在每一行的末尾,加上`\\r\\n\\`,如果一行超过100个字符,就给他加一个换行,并且也加上`\\r\\n\\`。\r\n\
一个中文字符size等于多少,如果是中英文混合的呢?\r\n\
如果是unicode字符集,gbk编码,一个中文字符size是2,中英文混和的也是按一个中文字符2,英文字符1来计算。\r\n\
如果是unicode字符集,utf8编码,一个中文字符size是3。\r\n\
为什么切分字符串会导致乱码呢?怎样解决呢?\r\n\
如果在一个汉字中间的位置去使用substr切分字符传,会导致切分出来的两个字符串乱码。\r\n\
简单一点,还是找中文句号吧。以中文句号来分割。\r\n\
以句号来分割也会有问题,在`find_first_of(\"。\")`的时候,它不会把句号当成一个字符,而是两个字符。\r\n\
当查询到里面的其中一个字符,它就当时找到了,其本质就是不支持直接找中文。\r\n\
那么我准备封装一个找中文的find_first_of_hanyu()。\r\n\
在调试的时候发现中文乱码了,刚开始怎么没发现呢?\r\n\
原因是刚开始就没有去打印字符串,只是把它们读入内存,并没有去打印,最后写入文件。\r\n\
这些utf8字符编码的中文字符串没有动,只是在末尾加上了\\r\\n\\而已。这样再从txt文件打开,能看到正常的中文和自己加上的\\r\\n\\。\r\n\
现在去打印中文字符串,乱码就是正常的了,因为vs2022是按gbk编码来解析中文,而txt文件中的是utf8编码的中文,不乱码才怪。\r\n\
说到这里windows真是奇葩,自己的记事本编辑的txt文件默认是utf8编码的,为什么vs2022编译的中文默认是gbk编码呢,为什么文件名默认是gbk编码呢。\r\n\
当windows系统默认编码改为utf8之后,文件名的编码也变成utf8了。亲测有效,复制中文名的文件到linux,文件名不再乱码了。\r\n\
所以我只好先把win10默认的编码改为utf8。\r\n\
在改的过程中这按钮也是藏的隐蔽,正常人根本发现不了。\r\n\
在`控制面版`-`区域`-`管理`-`更改系统区域设置`-勾选`Beta版:使用Unicode UTF-8提供全球语言支持(U)`。\r\n\
然后立即重启才能生效。\r\n\
生效之后在命令行查看,输入命令`chcp`,如果回答是`Active code page: 65001`,说明是真的生效了,系统默认语言编码为utf8。\r\n\
再打开vs2022,打开项目,里面的中文乱码了,正常,因为之前是按gbk编码的,现在是按utf8解析。\r\n\
继续调试打印中文字符串,发现不乱码了。\r\n\
一行代码没有改,解决了乱码的问题。\r\n\
所以程序员光了解编程语言特性是不够的,还需要了解环境。\r\n\
如果源文件中乱码非常多,也比较重要,可以考虑把这些文件放到linux机器,用iconv命令恢复。\r\n\
又遇到了一个问题。\r\n\
一是可能会出现死循环,在一句话超过180个字符的时候。\r\n\
两个新需求,一是文本里面的反斜杠`\\`,需要在它前面加一个反斜杠作为转义符;二是英文的双引号也需要给它前面加上转义符。\r\n\
使用`string::find_first_of()`想寻找句号的位置,结果它返回了`如果`的位置。\r\n\
研究了一下,发现结论是,find_first_of查找中文字符时,它并没有正真去找中文字符,而是使用中文字符的最后一个字节跟字符串里中文的最后一个字节比较,如果相同就认为找到了,并返回最后一个字节的位置,这显然是不合理的。\r\n\
这一种情况是使用单引号的中文。\r\n\
如果是使用双引号的中文,它会把这个中文当成3个字符,依次寻找这3个字符,依然是将这3个字符与字符串里的中文的最后一个字节比较,相等就返回它的位置,所以返回的位置x满足(x+1)是3的倍数的规律,因为数组起始位置是0。\r\n\
研究了一下utf8字符串的切分,如果切错位置导致一个中文字符被切断,那么缺少的部分打印出来为空,多余的部分打印出来也为空,不会占位,不会乱码,并且剩余的没被切断的字符串都能正常打印。\r\n\
怎样才能不切断中文呢。\r\n\
如果一个中文字符是3个字节,那么substr(0,x)时,x必须是3的倍数,结束位置,不包括这个位置。\r\n\
substr(x)时,x必须是3的倍数,起始位置,包括这个位置。substr(x,y)时,x和y必须都是3的倍数。x表示起始位置,包括这个位置,y表示长度。\r\n\
如果一句话太长,超过180个字符,也就是60个汉字还没有句号,那么就不管了。\r\n\
就让他一行长一点吧。因为这样的情况只是少数,已经解决了95%以上的情况下问题了。\r\n\
处理混合的中英文的时候,会出问题,并且是随机出现,不是在句号附近出现。\r\n\
测试研究发现,处理混合的中英文并不会导致问题,是自己封装的函数粗心导致的逻辑错误,sum应该是追加而不是覆盖,在字符串特别长的时候会出现。\r\n\
发现处理文本的时候,会丢失最后的2句话。\r\n\
逻辑错误,只把切分的字符串压入容器,剩下的字符串没有管。\r\n\
当剩余的字符串长度小于180时就会出现。在循环结束后将剩余的字符串压入容器即可。\r\n\
为什么会出现空行,设计上是不会有空行的?\r\n\
逻辑不严谨,当字符串长度大于180并且只有这一句话的时候,字串长度为0,也会被压入容器,从而导致空行。\r\n\
循环结束压入子串时加一个判断即可,字串长度大于0才能压入。\r\n\
为什么处理文件时会打印?\r\n\
因为还有剩余的cout没有删掉。\r\n\
为什么最后一行会被丢弃?\r\n\
因为`getline(ifs, line)`的时候,会消耗一个文件中的换行符用于切分,所以在输出文件的时候需要将这个\\n进行还原。\r\n\
但是现在在输出最后一行时没有还原,只是将前面的\\n还原了。在输出最后一行时也加一个\\n即可。"
};