技术宅的结界

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

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 2348|回复: 3
收起左侧

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

[复制链接]

1059

主题

2433

帖子

6万

积分

用户组: 管理员

一只技术宅

UID
1
精华
221
威望
348 点
宅币
19479 个
贡献
40233 次
宅之契约
0 份
在线时间
1837 小时
注册时间
2014-1-26
发表于 2016-1-2 08:02:51 | 显示全部楼层 |阅读模式

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

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

x
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了。
[C] 纯文本查看 复制代码
//=============================================================================
//作者: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;
}
测试代码:
[C] 纯文本查看 复制代码
#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 个宅币)

本帖被以下淘专辑推荐:

回复

使用道具 举报

1059

主题

2433

帖子

6万

积分

用户组: 管理员

一只技术宅

UID
1
精华
221
威望
348 点
宅币
19479 个
贡献
40233 次
宅之契约
0 份
在线时间
1837 小时
注册时间
2014-1-26
 楼主| 发表于 2016-1-16 16:26:46 | 显示全部楼层
总结:
1、UTF-8其实就是针对Unicode的一种纯粹的“压缩算法”。对于[0x01, 0x7F]区间内的所有Unicode字符,可以被UTF-8编码成兼容ASCII码的7-bit字节串,并且还能减少字符串的存储体积。
2、很多网页能在不同国家、不同语言、不同系统的环境中显示出正确的字符而不是乱码,其实是因为它使用了UTF-8编码,实质上的效果相当于网页是用Unicode编码的。跨语言的编码正是Unicode。

1

主题

7

帖子

50

积分

用户组: 小·技术宅

UID
1660
精华
0
威望
2 点
宅币
39 个
贡献
0 次
宅之契约
0 份
在线时间
7 小时
注册时间
2016-5-5
发表于 2016-5-7 07:27:42 来自手机 | 显示全部楼层
以前竟然一直以为Unicode和UTF-8是同一种编码

0

主题

18

帖子

51

积分

用户组: 小·技术宅

UID
2933
精华
0
威望
2 点
宅币
29 个
贡献
0 次
宅之契约
0 份
在线时间
2 小时
注册时间
2017-10-9
发表于 2017-10-11 22:47:44 | 显示全部楼层
编码这东西有时候真的让人很头痛

本版积分规则

QQ|申请友链||Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号 )|网站地图  

GMT+8, 2020-7-5 22:41 , Processed in 0.107846 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2020, Tencent Cloud.

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