如何剪出一个中文单词&英语单词mix string in c language

时间:2016-10-13 06:16:04

标签: c linux utf-8

我的字符串包含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流。显然我想避免这种情况。

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

5 个答案:

答案 0 :(得分:2)

首先,你需要知道你的编码。根据它的声音(3字节普通话),你的字符串用UTF-8编码。

您需要做的是将UTF-8转换回unicode代码点(整数)。然后你可以得到一个整数数组而不是字节数,所以数组的每个元素都是1个字符,不论语言是什么。

您还可以使用已经处理utf8的函数库,例如http://www.cprogramming.com/tutorial/utf8.c http://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);

将其再次转换回来

答案 1 :(得分:1)

Basic Multilingual Plane旨在包含几乎所有现代语言的字符。特别是它确实包含中文。

因此,您只需将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字符串,则可以使用ws...对应字符串str...函数。

答案 2 :(得分:1)

我建议在更高的抽象级别处理此问题:转换为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好测...'

答案 3 :(得分:0)

Pure C解决方案:

所有UTF8 multibyte characters will be made from char-s with the most-significant-bit set to 1,第一个字符的第一位表示代码点的字符数。

关于切割使用的标准,问题含糊不清;之一:

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

  2. 一个固定大小的输出缓冲区,它会强加任何你能够适应的内容&#34;

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

    // 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大小)的解决方案 - 不会将...附加到目的地,但您可以询问&#34;我需要多少个字符&#34 ;提前,如果它超出你的承受能力,保留额外的空间。

    // 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;
    }
    

答案 4 :(得分:0)

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) = '\0';  // if the last byte is Mandarin character
    }
    else if ((unsigned char)(*(--p)) < 0xA0)
    {
        *(++p) = '\0';  // if the last but one byte is Mandarin character
    }
    else if ((unsigned char)(*(--p)) < 0xA0)
    {
        *(++p) = '\0';  // if the last but two byte is Mandarin character
    }
    else
    {
        int i = 0;
        p = strTemp;
        while(*p != '\0' && i+2 <= nMaxLen)
        {
           if((unsigned char)(*p++) >= 0xA0 && (unsigned char)(*p) >= 0xA0)
           {
               p++;
               i++;
           }
           i++;
       }
       *p = '\0';
    }
    printf("str = %s\n",strTemp);
    return strTemp;
 }