c函数strtok_s(Linux上是strtok_r)可以分隔字符串,原型如下:
|
示例如下:
//
// 功能:解析字符串szSrc,分隔符为szDelimit中的任一字符
//
char szSrc[] = "-abc-=-def";
char szDelimit[] = "-=";
// 由于strtok_s会破坏原始字符串,所以拷贝一份出来解析
char *pszDuplicate = strdup(szSrc);
char *pszToken = NULL; // 分隔后的子字符串
char *pszRemainder = NULL; // 每次解析后的剩余字符串
// 第一次解析原始字符串
pszToken = strtok_s(pszDuplicate, szDelimit, &pszRemainder);
while (NULL != pszToken && NULL != pszRemainder)
{
// 之后就解析上次解析后的剩余字符串
pszToken = strtok_s(NULL, szDelimit, &pszRemainder);
}
if (NULL != pszDuplicate)
{
free(pszDuplicate);
pszDuplicate = NULL;
}
由于strtok_s会修改原始字符串的内容,所以我们可以基于strtok_s的源代码,写一个不修改原始字符串的替代函数strtok_s2,函数的实现详细介绍了算法原理:
// 与标准c函数strtok_s不同,strtok_s2不修改*ppszRemainder的内容。
// 返回值只去掉了前面的分隔符,但返回的字符串中间或后面可能仍然包含分隔符,所以
// 需要通过*ppszRemainder减去返回值来确定长度。
const char *strtok_s2(__inout const char **ppszRemainder, __in const char *pszDelimit)
{
if (NULL == ppszRemainder || NULL == *ppszRemainder || NULL == pszDelimit)
{
return NULL;
}
// 定义一个bitmap,每一位代表一个控制字符,所以可以保存4*32=128个控制字符
// 因为可见字符不会超过128个,所以这个bitmap足够容纳所有可见字符。
unsigned char map[32] = { 1 };
for (int count = 0; count < 32; count++)
{
map[count] = 0;
}
// 把分隔符表中的字符放到bitmap中。算法如下:
// 分隔符高5位作为bitmap中的索引,因为二进制的11111等于十进制的31,所以0-31正好是32个数。
// 而分隔符的低3位(二进制最大值111,十进制 7)表示把1左移的位数,由于最多左移7位,
// 所以,最大可以表示1000 0000, 最小表示0000 0001,正好占满一个字节。左移后的值,或到
// 上面bitmap的“通过分隔符高5位”计算出来的索引中。所以,一个bitmap元素可以保存8个分隔符。
// 而32个元素可以保存128个分隔符。
// 比如分隔符为';',二进制值为00111 011:
// 高5位的00111(十进制7),表示在map数组中的索引,即分隔符保存到map[7]中;
// 低3位的011(十进制3),表示把1左移3位的值1000或到map[7]上。
// 由于每个分隔符分解后是或到元素值上的,所以这个算法还可以实现分隔符去重。
unsigned char const *unsigned_control = reinterpret_cast<unsigned char const *>(pszDelimit);
do
{
// *unsigned_control >> 3:得到分隔符高5位。
// (*unsigned_control & 7):得到分隔符低3位。
map[*unsigned_control >> 3] |= (1 << (*unsigned_control & 7));
}
while (*unsigned_control++);
// 如果string为NULL,解析*context,否则解析string
const char *it = *ppszRemainder;
const unsigned char *&unsigned_it = reinterpret_cast<const unsigned char *&>(it);
// 找到第一个不以分隔符开始的字符串
while ((map[*unsigned_it >> 3] & (1 << (*unsigned_it & 7))) && *it)
{
++it;
}
// token_first:第一个不以分隔符开始的字符串
const char *const token_first = it;
// 查找下一个分隔符,查到后把相应位置的字符置0。
for (; *it; ++it)
{
if (map[*unsigned_it >> 3] & (1 << (*unsigned_it & 7)))
{
//*it++ = '\0';
break;
}
}
// 更新剩余字符串指针
*ppszRemainder = it;
// 返回找到的分隔后的字符串
return it != token_first ? token_first : nullptr;
}
示例代码:
// 需要被解析的字符串
const char szSrc[] = "-abc-=-def";
// 分隔符集,里面的任何一个字符都是分隔符
const char szDelimit[] = "-=";
// 分隔后的子字符串
const char *pszToken = NULL;
// 每次解析后的剩余字符串
const char *pszRemainder = szSrc;
// 每次解析后分隔出来的字符串
std::string strToken;
while (NULL != (pszToken = strtok_s2(&pszRemainder, szDelimit)))
{
strToken.assign(pszToken, pszRemainder - pszToken);
}