如何在C语言中剪切出中文单词和英语单词混合字符串



我有一个字符串,其中包含UTF-8格式的中文和英文单词:

char *str  = "你a好测b试";

如果您使用strlen(str),它将返回14,因为每个中文字符使用三个字节,而每个英文字符只使用一个字节。

现在我想复制最左边的4个字符("你a好测"),并在末尾附加"...",得到"你a好测..."

如果文本是单字节编码,我可以直接写:

strncpy(buf, str, 4);
strcat(buf, "...");

但是UTF-8中的4个字符不一定是4个字节。对于本例,它将是13个字节:各三个字节,a一个字节。对于这个特殊的例子,我需要

strncpy(buf, str, 13);
strcat(buf, "...");

如果长度的值是错误的,就会产生一个带有不完整字符的UTF-8流。显然我想避免那样。

如何计算要复制的正确字节数,对应于给定数量的字符?

首先你需要知道你的编码。听起来(3字节普通话)你的字符串是用UTF-8编码的。

您需要做的是将UTF-8转换回unicode代码点(整数)。这样就可以有一个整数数组,而不是字节数组,因此数组的每个元素都将是1个字符,无论使用哪种语言。

您也可以使用已经处理utf8的函数库,例如http://www.cprogramming.com/tutorial/utf8.chttp://www.cprogramming.com/tutorial/utf8.h

特别是这个函数:int u8_toucs(u_int32_t *dest, int sz, char *src, int srcsz);可能非常有用,它将创建一个整数数组,每个整数为1个字符。然后,您可以根据需要修改数组,然后使用int u8_toutf8(char *dest, int sz, u_int32_t *src, int srcsz);

将其转换回来。

我建议在更高的抽象级别上处理这个问题:要么转换为wchar_t,要么使用UTF-8库。但是,如果您真的想在字节级别上进行计算,您可以通过跳过延续字节(其形式为10xxxxxx)来计数字符:

#include <stddef.h>
size_t count_bytes_for_chars(const char *s, int n)
{
    const char *p = s;
    n += 1;  /* we're counting up to the start of the subsequent character */
    while (*p && (n -= (*p & 0xc0) != 0x80))
        ++p;
    return p-s;
}

下面是上述函数的演示:

#include <string.h>
#include <stdio.h>
int main()
{
    const char *str = "你a好测b试";
    char buf[50];
    int truncate_at = 4;
    size_t bytes = count_bytes_for_chars(str, truncate_at);
    strncpy(buf, str, bytes);
    strcpy(buf+bytes, "...");
    printf("'%s' truncated to %d characters is '%s'n", str, truncate_at, buf);
}
输出:

'你a好测b试' truncated to 4 characters is '你a好测...'

基本多语言平面被设计成包含几乎所有现代语言的字符。特别是,它确实包含中文。

所以你只需要将UTF8字符串转换为UTF16字符串,让每个字符使用一个位置。这意味着你可以只使用wchar_t数组,甚至更好的wstring来允许使用本地所有字符串函数。

从c++ 11开始,<codecvt>头文件声明了一个专用的转换器std::codecvt_utf8,专门将UTF8窄字符串转换为Unicode宽字符串。我必须承认它不是很容易使用,但在这里应该足够了。代码可以像这样:

char str[]  = "你a好测b试";
std::codecvt_utf8<wchar_t> cvt;
std::mbstate_t state = std::mbstate_t();
wchar_t wstr[sizeof(str)] = {0}; // there will be unused space at the end
const char *end;
wchar_t *wend;
auto cr = cvt.in(state, str, str+sizeof(str), end,
        wstr, wstr+sizeof(str), wend);
*wend = 0;

一旦你有了wstr宽字符串,你可以将其转换为wstring并使用所有c++库工具,或者如果你更喜欢C字符串,你可以使用str...函数的ws...对应项。

纯C解:

所有UTF8多字节字符将由char -s组成,其最高有效位设置为1,其第一个字符的第一个位表示有多少个字符构成一个码点。

关于切割所用的标准,问题是模糊的;:

  1. 一个固定数量的代码点,后面跟着三个点,这将需要一个可变大小的输出缓冲区

  2. 一个固定大小的输出缓冲区,它将施加"任何你可以放进去的"

这两个解决方案都需要一个辅助函数来告诉下一个代码点是多少个字符:

// Note: the function does NOT fully validate a
// UTF8 sequence, only looks at the first char in it
int codePointLen(const char* c) {
  if(NULL==c) return -1;
  if( (*c & 0xF8)==0xF0 ) return 4; // 4 ones and one 0 
  if( (*c & 0xF0)==0xE0 ) return 3; // 3 ones and one 0
  if( (*c & 0xE0)==0xC0 ) return 2; // 2 ones and one 0
  if( (*c & 0x7F)==*c   ) return 1; // no ones on msb
  return -2; // invalid UTF8 starting character
}

所以,标准1的解决方案(固定数量的代码点,可变的输出buff大小)-不附加...到目的地,但你可以问"我需要多少字符",如果它比你能承受的长,为自己保留额外的空间。

// returns the number of chars used from the output
// If not enough space or the dest is null, does nothing
// and returns the lenght required for the output buffer
// Returns negative val if the source in not a valid UTF8
int copyFirstCodepoints(
   int codepointsCount, const char* src,
   char* dest, int destSize
) {
  if(NULL==src) {
    return -1;
  }
  // do a cold run to see if size of the output buffer can fit
  // as many codepoints as required
  const char* walker=src;
  for(int cnvCount=0; cnvCount<codepointsCount; cnvCount++) {
    int chCount=codePointLen(walker);
    if(chCount<0) {
      return chCount; // err
    }
    walker+=chCount;
  }
  if(walker-src < destSize && NULL!=dest) {
    // enough space at destination
    strncpy(src, dest, walker-src);
  }
  // else do nothing
  return walker-src;
}

第二个条件(限制缓冲区大小):只使用第一个条件和这个条件返回的码点数

// return negative if UTF encoding error
int howManyCodepointICanFitInOutputBufferOfLen(const char* src, int maxBufflen) {
  if(NULL==src) {
    return -1;
  }
  int ret=0;
  for(const char* walker=src; *walker && ret<maxBufflen; ret++) {
     int advance=codePointLen(walker);
     if(advance<0) {
       return src-walker; // err because negative, but indicating the err pos
     }
     // look on all the chars between walker and walker+advance
     // if any is 0, we have a premature end of the source
     while(advance>0) {
       if(0==*(++walker)) {
         return src-walker; // err because negative, but indicating the err pos
       }
       advance--;
     } // walker is set on the correct position for the next attempt
  }
  return ret;
}
static char *CutStringLength(char *lpszData, int nMaxLen)
{
    if (NULL == lpszData || 0 >= nMaxLen)
    {
            return "";
    }
    int len = strlen(lpszData);
    if(len <= nMaxLen)
    {
            return lpszData;
    }
    char strTemp[1024] = {0};
    strcpy(strTemp, lpszData);
    char *p = strTemp;
    p = p + (nMaxLen-1);
    if ((unsigned char)(*p) < 0xA0)
    {
        *(++p) = '';  // if the last byte is Mandarin character
    }
    else if ((unsigned char)(*(--p)) < 0xA0)
    {
        *(++p) = '';  // if the last but one byte is Mandarin character
    }
    else if ((unsigned char)(*(--p)) < 0xA0)
    {
        *(++p) = '';  // if the last but two byte is Mandarin character
    }
    else
    {
        int i = 0;
        p = strTemp;
        while(*p != '' && i+2 <= nMaxLen)
        {
           if((unsigned char)(*p++) >= 0xA0 && (unsigned char)(*p) >= 0xA0)
           {
               p++;
               i++;
           }
           i++;
       }
       *p = '';
    }
    printf("str = %sn",strTemp);
    return strTemp;
 }

最新更新