我想知道,什么是划分基本上可以包含任何字符的字符串的好/有效方法。所以,例如,我需要连接看起来像的字符串:
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?
我可以使用哪个分隔符来正确拆分它?
请注意,连接可能有两个以上的字符串。
我愿意接受建议。
由于
答案 0 :(得分:3)
也许您可以编码字符串的长度,然后在每个字符串前面加上一个特殊字符?这样您就不必担心接下来的N个字符中包含哪些字符。也可以将null终止每个子字符串。
这种方法的一个优点是你可以非常快速地解析字符串。
编辑:更好的方法是使用Chris在下面的注释中建议的前2-4个字节,而不是编码长度+特殊字符。
答案 1 :(得分:3)
因为我的评论越来越长,所以这是一个完整的答案:
你的char *
缓冲区应该将字符串的长度存储在前X个字节中(就像Pascal一样)。在该长度之后出现字符串数据,其中可以包含您喜欢的任何字符。之后,接下来的X个字节会告诉您 next 字符串的长度。等等,直到结束,由空字符串分隔(即最后的X字节声称下一个字符串的长度为零,并且您的应用程序将此作为停止查找更多字符串的信号)。
一个好处是您不需要扫描字符串数据 - 从第一个字符串的开头查找下一个字符串花费O(1)时间,查找列表中有多少字符串需要O(n )时间但仍然会非常快(如果O(n)是不可接受的,你可以解决这个问题,但我认为现在不值得进入)。
另一个好处是字符串数据可以包含您喜欢的任何字符。这可能是一个错误 - 如果你的字符串可能包含NUL字符,你可以安全地提取它,但你必须小心不要将它传递给C字符串函数(如strlen()
或strcat()
) ,它会将NUL字符视为数据的结尾(可能是也可能不是)。你必须依赖memcpy()
和指针算法。
问题是X的值(用于存储字符串长度的字节数)。最简单的是1,它会绕过所有字节序和对齐问题,但会将字符串限制为255个字符。如果这是一个限制你可以忍受,非常好,但255对我来说似乎有点低。
X可以是2或4个字节,但您需要确保您的(无符号)数据类型至少包含那么多字节(stdint.h
的{{1}}或{{1或者uint16_t
或uint32_t
)。更好的解决方案是制作uint_least16_t
,因为uint_least32_t
类型可以保证能够存储您想要存储的任何字符串的长度。
让X = sizeof(size_t)
引入对齐,如果网络可移植性是个问题,则使用endianness。将前X个字节作为size_t
变量读取的最简单方法是将X > 1
数据转换为size_t
,然后取消引用。但是,除非您可以保证您的char *
数据正确对齐,否则在某些系统上会中断。即使您确实保证了size_t *
数据的对齐,您也必须在大多数字符串的末尾浪费几个字节,以确保下一个字符串的长度值对齐。
克服对齐的最简单方法是手动将第一个char *
字节转换为char *
值。您必须决定是否要将数据存储为小端或大端。大多数计算机本身都是小端的,但对于手动转换,这无关紧要 - 只需选择一个。以4字节存储的数字65537(2 ^ 16 + 2),big-endian,看起来像sizeof(size_t)
; little-endian,size_t
。
一旦你决定了(无所谓,选择你喜欢的任何一个),你只需将前X个数据点投射到{ 0, 1, 0, 2 }
s,然后投射到{ 2, 0, 1, 0 }
,然后再做通过适当的指数稍微移位以将它们放在适当的位置,然后将它们全部加在一起。在上面的例子中,0将乘以2 ^ 32,1乘2 ^ 16,0乘2 ^ 8和2乘2 ^ 0(或1),产生0 + 65536 + 0 + 2或65537。如果您正在进行手动转换,那么大端和小端之间的效率差异为零 - 我想再次指出,就我所知,选择完全是任意的。
进行手动转换可避免对齐问题,和完全绕过对跨系统字节序的担忧,因此从小端计算机传输到大端计算机的数据将被读取相同的内容。数据从unsigned char
转移到size_t
的系统仍然存在潜在问题。如果这是一个问题,你可以a)抛弃sizeof(size_t) == 4
并选择一个不变的大小,或者b)编码(你只需要一个字节)发送者sizeof(size_t) == 8
的值作为第一个数据字节,并让接收器进行任何必要的调整。选择a)可能更容易,但可能会导致问题(如果您选择的尺寸太小而无法考虑网络中的旧计算机,并且因为它们已逐步淘汰您开始没有足够的空间存储您的数据?),所以我更喜欢选择b)因为它可以随你运行的任何系统(16位,32位,64位,甚至未来的128位)进行扩展,但是这种努力对你来说可能不是必需的
size_t
我把它留给读者来解决我刚写的所有混乱。
答案 2 :(得分:2)
一种选择是使用空字符作为分隔符,并使用double null终止列表。字符串。它看起来像这样:
const char* str_final = "foo; for|* 1.234+\"@!`\0bar; for|* 1.234+%\"@`\0";
delimiter ^ delimiter ^
Raymond Chen很好地概述了双空终止字符串in a blog post.它被Windows API中的几个函数使用。
答案 3 :(得分:2)
如果您知道您的字符串将始终是有效的UTF-8文本(或ASCII),则可以使用无法以有效UTF-8(或ASCII)形式出现的字节作为分隔符。在UTF-8中,字节C0,C1,F5,F6,F7,F8,F9,FA,FB,FC,FD,FE和FF无效。在ASCII中,任何设置了高位的字节都是无效的。
答案 4 :(得分: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
分隔效率更高,特别是因为你真的不需要拆分它,你可以使用一个指针来完整字符串在使用str
或printf
函数时使用单个部分字符串。
答案 5 :(得分:1)
2个想法:
1)使用标准的“转义”方法,类似于在C中定义char *文字。
2)使用一个'\0'
字符作为分隔符,其中两个作为字符串标记的结尾。