找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 7274|回复: 4

【C】字符串编码UTF-8的详细资料和编码方式

[复制链接]
发表于 2016-1-2 08:02:51 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有账号?立即注册→加入我们

×
UTF-8编码的字符串,不同的字符长度不一样。和Unicode作为对比,Unicode的字符串每个字符的长度都是固定的,比如16位的Unicode(也就是Windows使用的这种)它就是2个字节一个字符(即便是半角字母)的固定长度字符数组。
UTF-8是一种针对Unicode的可变长度字元编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字元。[参考]
在Unicode字符值小于128的时候,UTF-8可以只用一个字节来表示一个字符,这点可以做到和ASCII兼容。因此,这使得原来处理ASCII字元的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或发送文字的应用中,优先采用的编码。[参考]

使用UTF-8编码的字符串,其中的字符的长度取决于这个字符的值。我们可以用下面这张表很直观地将编码方式表示出来。
字符的位数范围开始值结束值UTF-8字节数Byte 1Byte 2Byte 3Byte 4Byte 5Byte 6
0-70x000x7F10xxxxxxx
8-110x800x7FF2110xxxxx10xxxxxx
12-160x8000xFFFF31110xxxx10xxxxxx10xxxxxx
17-210x100000x1FFFFF411110xxx10xxxxxx10xxxxxx10xxxxxx
22-260x2000000x3FFFFFF5111110xx10xxxxxx10xxxxxx10xxxxxx10xxxxxx
27-310x40000000x7FFFFFFF61111110x10xxxxxx10xxxxxx10xxxxxx10xxxxxx10xxxxxx

其中的“xxxxxx”就是原字符的二进制位了。UTF-8编码将Unicode字符值的高位放在靠前的字节,而低位则放在靠后的字节中。
知道了以上的信息之后,你大概也知道怎么写Unicode到UTF-8之间互转的函数了吧?
这里放出我写好的库,它不使用API,直接就能编码、解码UTF-8的字符串为Unicode了。
//=============================================================================
//作者:0xAA55
//作者网站:http://www.0xaa55.com/
//本源码完全免费开放,大家可以随意拿去使用、修改、用于任何用途。
//但是,由于本源码带来的任何损失,均与本作者无关。请保留此信息。
//
//参考资料:
//RFC 3629
//https://zh.wikipedia.org/wiki/UTF-8
//http://www.0xaa55.com/thread-1676-1-1.html
//-----------------------------------------------------------------------------
#include"wcs2utf8.h"

//=============================================================================
//函数:_u16toutf8
//描述:将16位Unicode编码的字符串转换为UTF-8
//    返回转换为UTF-8后的字符串的长度。pUTF8可为NULL,表示不接收转换好的字符串
//-----------------------------------------------------------------------------
size_t _u16toutf8
(
    char*pUTF8,
    const uint16_t*pwStr,
    size_t count
)
{
    size_t cbUTF8 = 0;
    size_t i;
    char*pChr;

    //pUTF8为NULL时,返回存储UTF-8字符串所需字节数
    if(!pUTF8)
    {
        for(i=0;i<count;i++)
        {
            if(pwStr[i] >= 0x0800)
                cbUTF8 += 3;
            else if(pwStr[i] >= 0x0080)
                cbUTF8 += 2;
            else
                cbUTF8++;
        }
        return cbUTF8;
    }

    //输出UTF-8编码的字符串
    pChr = pUTF8;
    for(i=0;i<count;i++)
    {
        if(pwStr[i] >= 0x0800)
        {
            *pChr++ = 0xE0 | ((pwStr[i] >> 12) & 0x0F);
            *pChr++ = 0x80 | ((pwStr[i] >> 6) & 0x3F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 3;
        }
        else if(pwStr[i] >= 0x0080)
        {
            *pChr++ = 0xC0 | ((pwStr[i] >> 6) & 0x1F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 2;
        }
        else
        {
            *pChr++ = (char)pwStr[i];
            cbUTF8++;
        }
    }
    return cbUTF8;
}

//=============================================================================
//函数:_u32toutf8
//描述:将32位Unicode编码的字符串转换为UTF-8
//    返回转换为UTF-8后的字符串的长度。pUTF8可为NULL,表示不接收转换好的字符串
//-----------------------------------------------------------------------------
size_t _u32toutf8
(
    char*pUTF8,
    const uint32_t*pwStr,
    size_t count
)
{
    size_t cbUTF8 = 0;
    size_t i;
    char*pChr;

    //pUTF8为NULL时,返回存储UTF-8字符串所需字节数
    if(!pUTF8)
    {
        for(i=0;i<count;i++)
        {
            if(pwStr[i] >= 0x4000000)
                cbUTF8 += 6;
            else if(pwStr[i] >= 0x200000)
                cbUTF8 += 5;
            else if(pwStr[i] >= 0x10000)
                cbUTF8 += 4;
            else if(pwStr[i] >= 0x0800)
                cbUTF8 += 3;
            else if(pwStr[i] >= 0x0080)
                cbUTF8 += 2;
            else
                cbUTF8++;
        }
        return cbUTF8;
    }

    //输出UTF-8编码的字符串
    pChr = pUTF8;
    for(i=0;i<count;i++)
    {
        if(pwStr[i] >= 0x4000000)
        {
            *pChr++ = 0xFC | ((pwStr[i] >> 30) & 0x01);
            *pChr++ = 0x80 | ((pwStr[i] >> 24) & 0x3F);
            *pChr++ = 0x80 | ((pwStr[i] >> 18) & 0x3F);
            *pChr++ = 0x80 | ((pwStr[i] >> 12) & 0x3F);
            *pChr++ = 0x80 | ((pwStr[i] >> 6) & 0x3F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 6;
        }
        else if(pwStr[i] >= 0x200000)
        {
            *pChr++ = 0xF8 | ((pwStr[i] >> 24) & 0x03);
            *pChr++ = 0x80 | ((pwStr[i] >> 18) & 0x3F);
            *pChr++ = 0x80 | ((pwStr[i] >> 12) & 0x3F);
            *pChr++ = 0x80 | ((pwStr[i] >> 6) & 0x3F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 5;
        }
        else if(pwStr[i] >= 0x10000)
        {
            *pChr++ = 0xF0 | ((pwStr[i] >> 18) & 0x07);
            *pChr++ = 0x80 | ((pwStr[i] >> 12) & 0x3F);
            *pChr++ = 0x80 | ((pwStr[i] >> 6) & 0x3F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 4;
        }
        else if(pwStr[i] >= 0x0800)
        {
            *pChr++ = 0xE0 | ((pwStr[i] >> 12) & 0x0F);
            *pChr++ = 0x80 | ((pwStr[i] >> 6) & 0x3F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 3;
        }
        else if(pwStr[i] >= 0x0080)
        {
            *pChr++ = 0xC0 | ((pwStr[i] >> 6) & 0x1F);
            *pChr++ = 0x80 | (pwStr[i] & 0x3F);
            cbUTF8 += 2;
        }
        else
        {
            *pChr++ = (char)pwStr[i];
            cbUTF8++;
        }
    }
    return cbUTF8;
}

//=============================================================================
//函数:_GetUTF8NbWChars
//描述:取得UTF-8字符串转换为Unicode编码的字符串的字符数
//    失败返回0
//-----------------------------------------------------------------------------
static size_t _GetUTF8NbWChars(const char*pUTF8,size_t cb)
{
    size_t cchUnicode = 0;
    size_t i;

    for(i=0;i<cb;)
    {
        if((pUTF8[i] & 0xFE) == 0xFC)
        {
            cchUnicode++;
            i += 6;
        }
        else if((pUTF8[i] & 0xFC) == 0xF8)
        {
            cchUnicode++;
            i += 5;
        }
        else if((pUTF8[i] & 0xF8) == 0xF0)
        {
            cchUnicode++;
            i += 4;
        }
        else if((pUTF8[i] & 0xF0) == 0xE0)
        {
            cchUnicode++;
            i += 3;
        }
        else if((pUTF8[i] & 0xE0) == 0xC0)
        {
            cchUnicode++;
            i += 2;
        }
        else if((pUTF8[i] & 0xC0) == 0x80)
        {
            //遇到高2位是10的字符,这是不应该出现的。
            return 0;
        }
        else if((pUTF8[i] & 0x80) == 0x00)
        {
            cchUnicode++;
            i++;
        }
    }

    //字符串不完整
    if(i > cb)
        cchUnicode--;

    return cchUnicode;
}

//=============================================================================
//函数:_utf8tou16
//描述:将UTF-8编码的字符串转换为16位Unicode
//    返回转换为Unicode后的字符串的字符数。pUnicode可为NULL,表示不接收字符串。
//    注意失败返回0
//-----------------------------------------------------------------------------
size_t _utf8tou16
(
    uint16_t*pUnicode,
    const char*pUTF8,
    size_t cb
)
{
    size_t cchUnicode = 0;
    size_t i;
    uint16_t*pChr;

    if(!pUnicode)
        return _GetUTF8NbWChars(pUTF8,cb);

    pChr = pUnicode;
    for(i=0;i<cb;)
    {
        if( (pUTF8[i] & 0xFE) == 0xFC ||
            (pUTF8[i] & 0xFC) == 0xF8 ||
            (pUTF8[i] & 0xF8) == 0xF0)
        {
            //遇到32位Unicode字符,停止。本函数不包这服务。
            return 0;
        }
        else if((pUTF8[i] & 0xF0) == 0xE0)
        {
            if(i + 3 <= cb)
            {
                *pChr++ =
                    (((uint16_t)pUTF8[i+0] & 0x0F) << 12)|
                    (((uint16_t)pUTF8[i+1] & 0x3F) << 6)|
                    (((uint16_t)pUTF8[i+2] & 0x3F) << 0);
                cchUnicode++;
                i += 3;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xE0) == 0xC0)
        {
            if(i + 2 <= cb)
            {
                *pChr++ =
                    (((uint16_t)pUTF8[i+0] & 0x1F) << 6)|
                    (((uint16_t)pUTF8[i+1] & 0x3F) << 0);
                cchUnicode++;
                i += 2;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xC0) == 0x80)
        {
            //遇到高2位是10的字符,这是不应该出现的。
            return 0;
        }
        else if((pUTF8[i] & 0x80) == 0x00)
        {
            *pChr++ = pUTF8[i] & 0x7F;
            cchUnicode++;
            i++;
        }
    }
    return cchUnicode;
}

//=============================================================================
//函数:_utf8tou32
//描述:将UTF-8编码的字符串转换为32位Unicode
//    返回转换为Unicode后的字符串的字符数。pUnicode可为NULL,表示不接收字符串。
//    注意失败返回0
//-----------------------------------------------------------------------------
size_t _utf8tou32
(
    uint32_t*pUnicode,
    const char*pUTF8,
    size_t cb
)
{
    size_t cchUnicode = 0;
    size_t i;
    uint32_t*pChr;

    if(!pUnicode)
        return _GetUTF8NbWChars(pUTF8,cb);

    pChr = pUnicode;
    for(i=0;i<cb;)
    {
        if((pUTF8[i] & 0xFE) == 0xFC)//1111110x
        {
            if(i + 6 <= cb)
            {
                *pChr++ =
                    (((uint32_t)pUTF8[i+0] & 0x01) << 30)|
                    (((uint32_t)pUTF8[i+1] & 0x3F) << 24)|
                    (((uint32_t)pUTF8[i+2] & 0x3F) << 18)|
                    (((uint32_t)pUTF8[i+3] & 0x3F) << 12)|
                    (((uint32_t)pUTF8[i+4] & 0x3F) << 6)|
                    (((uint32_t)pUTF8[i+5] & 0x3F) << 0);
                cchUnicode++;
                i += 6;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xFC) == 0xF8)//111110xx
        {
            if(i + 5 <= cb)
            {
                *pChr++ =
                    (((uint32_t)pUTF8[i+0] & 0x03) << 24)|
                    (((uint32_t)pUTF8[i+1] & 0x3F) << 18)|
                    (((uint32_t)pUTF8[i+2] & 0x3F) << 12)|
                    (((uint32_t)pUTF8[i+3] & 0x3F) << 6)|
                    (((uint32_t)pUTF8[i+4] & 0x3F) << 0);
                cchUnicode++;
                i += 5;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xF8) == 0xF0)//11110xxx
        {
            if(i + 4 <= cb)
            {
                *pChr++ =
                    (((uint32_t)pUTF8[i+0] & 0x07) << 18)|
                    (((uint32_t)pUTF8[i+1] & 0x3F) << 12)|
                    (((uint32_t)pUTF8[i+2] & 0x3F) << 6)|
                    (((uint32_t)pUTF8[i+3] & 0x3F) << 0);
                cchUnicode++;
                i += 4;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xF0) == 0xE0)//1110xxxx
        {
            if(i + 3 <= cb)
            {
                *pChr++ =
                    (((uint32_t)pUTF8[i+0] & 0x0F) << 12)|
                    (((uint32_t)pUTF8[i+1] & 0x3F) << 6)|
                    (((uint32_t)pUTF8[i+2] & 0x3F) << 0);
                cchUnicode++;
                i += 3;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xE0) == 0xC0)//110xxxxx
        {
            if(i + 2 <= cb)
            {
                *pChr++ =
                    (((uint32_t)pUTF8[i+0] & 0x1F) << 6)|
                    (((uint32_t)pUTF8[i+1] & 0x3F) << 0);
                cchUnicode++;
                i += 2;
            }
            else
                break;
        }
        else if((pUTF8[i] & 0xC0) == 0x80)//10xxxxxx
        {
            //遇到高2位是10的字符,这是不应该出现的。
            return 0;
        }
        else if((pUTF8[i] & 0x80) == 0x00)//0xxxxxxx
        {
            *pChr++ = pUTF8[i] & 0x7F;
            cchUnicode++;
            i++;
        }
    }
    return cchUnicode;
}
测试代码:
#include"wcs2utf8.h"

#include<stdio.h>
#include<locale.h>
#include<malloc.h>
#include<string.h>
#include<Windows.h>

int main(int argc,char**argv)
{
    char*pszUTF8;
    size_t cbUTF8;
    //要注意wchar_t的大小不一定是2
    wchar_t wstr[] = L"蛤蛤蛤!我是wchar_t字符串!\n";
    size_t cchwstr = wcslen(wstr);
    wchar_t wcBuf[999] = {0};//测试用缓冲区

    //不加这个不能显示Unicode字符串
    setlocale(LC_ALL, ".ACP");

    //显示字符串
    fputs("这是原始的字符串:\n",stderr);
    fputws(wstr,stdout);

    //用自己的函数转换字符串
    cbUTF8 = _u16toutf8(NULL,(uint16_t*)wstr,cchwstr);
    pszUTF8 = (char*)malloc(cbUTF8);//没有'\0'结尾
    if(!pszUTF8)
    {
        fputs("额..\n",stderr);
        return 1;
    }
    _u16toutf8(pszUTF8,(uint16_t*)wstr,cchwstr);

    //然后用WinAPI测试它对不对
    MultiByteToWideChar(CP_UTF8, 0, pszUTF8, cbUTF8, wcBuf, 999);

    //显示字符串
    fputs("这是用MultiByteToWideChar把UTF-8字符串转换成Unicode的字符串:\n",stderr);
    fputws(wcBuf,stdout);

    //然后我们再测试用自己的函数将UTF-8字符串转换为Unicode
    _utf8tou16((uint16_t*)wcBuf, pszUTF8, cbUTF8);
    fputs("这是用_utf8tou16把UTF-8字符串转换成Unicode的字符串:\n",stderr);
    fputws(wcBuf,stdout);

    free(pszUTF8);
    return 0;
}
20160102085720.png
完整源码下载: utf8test.7z (3.69 KB, 下载次数: 0, 售价: 4 个宅币)


相关参考资料:


RFC 3629
https://zh.wikipedia.org/wiki/UTF-8

本帖被以下淘专辑推荐:

回复

使用道具 举报

 楼主| 发表于 2016-1-16 16:26:46 | 显示全部楼层
总结:
1、UTF-8其实就是针对Unicode的一种纯粹的“压缩算法”。对于[0x01, 0x7F]区间内的所有Unicode字符,可以被UTF-8编码成兼容ASCII码的7-bit字节串,并且还能减少字符串的存储体积。
2、很多网页能在不同国家、不同语言、不同系统的环境中显示出正确的字符而不是乱码,其实是因为它使用了UTF-8编码,实质上的效果相当于网页是用Unicode编码的。跨语言的编码正是Unicode。
回复 赞! 靠!

使用道具 举报

发表于 2016-5-7 07:27:42 来自手机 | 显示全部楼层
以前竟然一直以为Unicode和UTF-8是同一种编码
回复 赞! 靠!

使用道具 举报

发表于 2017-10-11 22:47:44 | 显示全部楼层
编码这东西有时候真的让人很头痛
回复 赞! 靠!

使用道具 举报

发表于 2020-12-4 10:28:56 | 显示全部楼层
感谢分享。。。
回复

使用道具 举报

本版积分规则

QQ|Archiver|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图

GMT+8, 2026-10-10 00:19 , Processed in 0.022220 second(s), 11 queries , Gzip On, Redis On.

Powered by Discuz! X5.0

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表