4

我想知道,什么是分隔基本上可以包含任何字符的字符串的好/有效的方法。因此,例如,我需要连接 n 个字符串,如下所示:

char *str_1 = "foo; for|* 1.234+\"@!`";
char *str_n = "bar; for|* 1.234+%\"@`";

最后的字符串为:

char *str_final = "foo; for|* 1.234+\"@!`bar; for|* 1.234+%\"@`"; // split?

我可以使用哪个分隔符来正确拆分它?

请注意,要连接的字符串可能超过 2 个。

我愿意接受建议。

谢谢

4

6 回答 6

3

因为我的评论越来越长,这里有一个完整的答案:

您的char *缓冲区应该将字符串的长度存储在前 X 个字节中(就像 Pascal 的做法一样)。在该长度之后是字符串数据,它可以包含您喜欢的任何字符。之后,接下来的 X 个字节会告诉您下一个字符串的长度。依此类推,直到结尾,由一个空字符串分隔(即最后 X 个字节声称下一个字符串的长度为零,并且您的应用程序将此作为停止寻找更多字符串的信号)。

一个好处是您不需要扫描字符串数据 - 从第一个字符串的开头查找下一个字符串需要 O(1) 时间,查找列表中有多少个字符串需要 O(n) 时间但是仍然会非常快(如果 O(n) 是不可接受的,你可以解决这个问题,但我认为现在不值得进入)。

另一个好处是字符串数据可以包含您喜欢的任何字符。这可能是一个骗局——如果你的字符串可能包含 NUL 字符,你可以安全地提取它,但你必须小心不要将它传递给 C 字符串函数(如strlen()or strcat()),它会将 NUL 字符视为结尾您的数据(可能是也可能不是)。你将不得不依赖memcpy()和指针算术。

问题在于 X 的值(用于存储字符串长度的字节数)。最简单的是 1,它将绕过所有字节顺序和对齐问题,但会将您的字符串限制为 255 个字符。如果这是您可以忍受的限制,那太好了,但是 255 对我来说似乎有点低。

X 可能是 2 或 4 个字节,但您需要确保您有一个(无符号)数据类型,该数据类型至少有那么多字节(stdint.h's uint16_tor uint32_t,或者可能uint_least16_tor uint_least32_t)。更好的解决方案是 make X = sizeof(size_t),因为该size_t类型保证能够存储您想要存储的任何字符串的长度。

引入X > 1了对齐,如果网络可移植性是一个问题,则引入字节序。将前 X 个字节作为size_t变量读取的最简单方法是将char *数据转换为 asize_t *并取消引用。但是,除非您可以保证您的char *数据正确对齐,否则这将在某些系统上中断。即使您确实保证了char *数据的对齐,您也必须在大多数字符串的末尾浪费几个字节来确保下一个字符串的长度值是对齐的。

克服对齐的最简单方法是将第一个sizeof(size_t)字节手动转换为一个size_t值。您必须决定是否要以小端或大端方式存储数据。大多数计算机本机都是 little-endian,但对于手动转换,这无关紧要 - 只需选择一个。存储在 4 个字节中的数字 65537 (2 ^ 16 + 2),大端,看起来像{ 0, 1, 0, 2 }; 小端,{ 2, 0, 1, 0 }.

一旦你决定了(没关系,选择你喜欢的那个),你只需将数据的前 X 个点转换为unsigned chars,然后转换为size_t,然后通过适当的指数进行位移以将它们放入合适的地方,然后把它们加在一起。在上面的例子中,0 将乘以 2 ^ 32、1 乘以 2 ^ 16、0 乘以 2 ^ 8、2 乘以 2 ^ 0(或 1),得到 0 + 65536 + 0 + 2 或 65537。可能有如果您进行手动转换,大端和小端之间的效率差异将为零 - 我想(再次)指出,据我所知,选择完全是任意的。

进行手动转换避免了对齐问题,完全绕过了对跨系统字节序的担忧,因此从小端计算机传输到大端计算机的数据将被读取相同。sizeof(size_t) == 4数据从 where 系统传输到 where系统仍然存在潜在问题sizeof(size_t) == 8。如果这是一个问题,您可以 a) 放弃size_t并选择一个不变的大小,或 b) 编码(您只需要一个字节)的值sizeof(size_t)发送方作为数据的第一个字节,并让接收方进行任何必要的调整。选择 a) 可能更容易,但可能会导致问题(如果您选择的尺寸太小而无法容纳网络上的旧计算机,并且随着它们被淘汰,您开始没有空间来存储您的数据?),所以我更喜欢选择 b),因为它可以随您运行的任何系统(16 位、32 位、64 位,甚至未来 128 位)进行扩展,但您可能不需要这种努力.

</vomit>我把它留给读者来整理我刚刚写的所有乱七八糟的东西。

于 2010-11-04T21:45:46.543 回答
3

也许您可以对字符串的长度进行编码,然后在每个字符串前面加上一个特殊字符?这样您就不必担心接下来的 N 个字符中有哪些字符。将每个子字符串也以空值终止可能是个好主意。

这种方法的一个优点是您将能够非常快速地解析字符串。

编辑:更好的方法是使用 Chris 在下面的评论中建议的前 2-4 个字节,而不是编码长度 + 特殊字符。

于 2010-11-04T01:56:42.860 回答
2

一种解决方案是选择转义字符和分隔符。通常反斜杠\用作转义字符,但这可能会导致混淆,因为它已经是字符串文字的转义字符。选择真的无关紧要,让我们将正斜杠/作为转义符,将分号;作为分隔符。理想情况下,选择字符串中最不可能出现的两个字符。

连接字符串时,第一步是搜索未编码字符串中的两个字符,并用转义版本替换它们:

str1 = "foo;bar;baz";
str2 = "foo/bar/baz";

变成

estr1 = "foo/;bar/;baz";
estr2 = "foo//bar//baz";

然后将它们与分隔符连接:

res = "foo/;bar/;baz;foo//bar//baz";

而已。拆分是通过搜索没有前导转义字符的分隔符,然后将单个字符串中的转义字符替换回未转义的版本来完成的。

如果您想使用等待单个零终止字符串的函数处理字符串,例如使用str函数或使用函数打印它们,这是一个不错的选择printf。如果您可以保证只有您自己的函数才能使用这些字符串,那么提到的用零分隔\0会更有效,特别是因为您实际上不需要拆分它,您可以使用指向完整字符串的指针来使用单个使用strprintf函数时来自它的部分字符串。

于 2010-11-04T08:46:38.817 回答
2

如果您知道您的字符串将始终是有效的 UTF-8 文本(或 ASCII),您可以使用不能出现在有效 UTF-8(或 ASCII)中的字节作为分隔符。在 UTF-8 中,字节 C0、C1、F5、F6、F7、F8、F9、FA、FB、FC、FD、FE 和 FF 无效。在 ASCII 中,任何设置了高位的字节都是无效的。

于 2010-11-04T02:45:18.130 回答
2

一种选择是使用空字符作为分隔符并使用双空字符终止列表。的字符串。它看起来像这样:

const char* str_final = "foo; for|* 1.234+\"@!`\0bar; for|* 1.234+%\"@`\0";
                                     delimiter ^             delimiter ^ 

Raymond Chen在一篇博文中对双空终止字符串进行了很好的概述。 它被 Windows API 中的几个函数使用。

于 2010-11-04T01:58:21.240 回答
1

2个想法:

1)使用标准的“转义”方法,类似于在 C 中定义 char* 文字。

2) 使用一个'\0'字符作为分隔符,其中两个作为字符串结束标记。

于 2010-11-04T01:57:10.083 回答