用C++开发一个图片爬虫 Ⅱ :编码转换
前言
我们上一节学习了获取网页的代码,那么大家可能会以为接下来就是分析网页了。但事实上,我们还需要先转换编码。我们都知道,C++编程常用的是ASCII编码,而网页大部分使用的是UTF-8编码。
因此我们要先将获取后的网页转换成我们想要的编码,再去分析内容。
理论
我们写编码转换的代码,肯定要知道这些编码之间的关系。
ASCII编码我们再熟悉不过了,然而我相信很多人和我一样,分不清什么是Unicode,UTF-8,UTF-16,UTF-32。
我不想用长篇大论来解释这些东西,就给出它们各自的定义和我们需要知道的东西。如果想更深入的了解,可以自行搜索。
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。
至于它为什么诞生,我简单说明一下。因为我们熟知的ASCII码能表示的字符是非常有限的,根本无法承受那么多国家的字符。因此国际组织就又发明了一个新的编码,几乎收尽所有国家的字符,甚至包括特殊字符。
Unicode与其他编码类似,也是每一个字符都对应一个数字来表示它。但Unicode也仅仅如此,它没有规定这个二进制代码如何存储。
那么问题来了,在Unicode编码下,假设有一个3字节的二进制代码,计算机怎么知道表示的是一个字符还是两个字符或者是三个字符呢?因此UTF-8、UTF-16这一系列东西就诞生了。
先介绍UTF-8,这是开发网页首选的编码存储方式。
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,又称万国码,由Ken Thompson于1992年创建。
如果略懂编码的话,可变长度是最大特点。但是为了实现可变长度,就需要一些二进制位来表示长度,我认为它的编码规则可以说是非常典型了。
字节数 | 二进制编码形式 |
---|---|
1字节 | 0xxxxxxx |
2字节 | 110xxxxx 10xxxxxx |
3字节 | 1110xxxx 10xxxxxx 10xxxxxx |
4字节 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
5字节 | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
6字节 | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
再简单说一下UTF-16,定义:
UTF-16是Unicode字符编码五层次模型的第三层:字符编码表(Character Encoding Form,也称为 “storage format”)的一种实现方式。
UTF-16虽然也可变长度,但不如UTF-8灵活。它有大尾序和小尾序两种储存形式,这个可以自行查阅。
UTF-32 (或 UCS-4)是一种将Unicode字符编码的协定,对每一个Unicode码位使用恰好32位元。其它的Unicode transformation formats则使用不定长度编码。
网上找到一个工具,Unicode和UTF编码转换,有兴趣可以试一下。
实战
我们为什么要了解上面那些东西,最终还是为了更容易理解代码。先给出一个将一个字符串从UTF-8转为ASCII的示例代码,从网上抄下来的。
1 | const char *changeTxtEncoding(const char *szU8) { |
此函数是将一个字符串,先从UTF-8转为UTF-16,再转为ASCII。
MultiByteToWideChar
此函数可将字符串映射到UTF-16(宽字符)字符串。字符串不一定来自多字节字符集。在这里,字符集或代码页我们都可以理解为编码映射表。
1 | int WINAPI MultiByteToWideChar( |
定义简化如下:
1 | int WINAPI MultiByteToWideChar( |
CodePage为指定执行转换的字符集。如果原字符串为ASCII通常选择CP_ACP,关于CP_ACP与CP_THREAD_ACP的区别可以自己了解。我们这里原字符串为UTF-8,因此选择CP_UTF8。数值表:
数值 | 说明 |
---|---|
CP_ACP | Windows系统默认的ANSI代码页。 |
CP_MACCP | Mac系统代码页。 |
CP_ACP | OEM系统代码页。 |
CP_SYMBOL | 符号字符集。 |
CP_THREAD_ACP | 当前Windows线程的ANSI代码页。 |
CP_UTF7 | 使用UTF-7。 |
CP_UTF8 | 使用UTF-8。 |
dwFlags设定转换类型。缺省值为MB_PRECOMPOSED,对于UTF-8必须为0或MB_ERR_INVALID_CHARS,此参数作用较小,数值表就不给出了。
lpMultiByteStr指向一个要转换的字符串。
cbMultiByte指定长度,如果lpMultiByteStr指向的字符串为空结束,此参数可设为-1。如果设为-1,函数将处理包括终止空字符在内的所有字符。我们可以用strlen来自行获得长度。
lpWideCharStr为指向接收缓冲器的指针。
cchWideChar为lpWideCharStr指示的缓冲器的大小(以字符为单位)。如果此值为0不使用lpWideCharStr缓冲区。
Return Value即返回值,返回写入到缓冲器的字符数。如果cchWideChar为0则返回缓冲器所需大小(以字符为单位)。
我们第一次调用此函数时应将cchWideChar设为0,并记录函数返回值,第二次调用时将cchWideChar设为此值。
WideCharToMultiByte
与MultiByteToWideChar恰恰相反,但多两个参数,此函数可将UTF-16(宽字符)字符串映射到新字符串。
1 | int WINAPI WideCharToMultiByte( |
CodePage为指定执行转换的字符集。可选数值与MultiByteToWideChar相同,我们这里选择CP_ACP。
dwFlags设定转换类型。设为0即可。
lpWideCharStr指向一个要转换的字符串。
cchWideChar指定长度。对于宽字符,自行使用wcslen来获得。
lpMultiByteStr为指向接收缓冲器的指针。
cbMultiByte为lpMultiByteStr指示的缓冲器的大小(以字符为单位)。
lpDefaultChar为指向另一个接收缓冲器的指针,如果某个字符不能在指定的代码页中表示将使用此缓冲器。我们不需要,设为NULL即可。
lpUsedDefaultChar为一个指向BOOL变量的指针。如果使用了lpDefaultChar将被设为TRUE,否则为FALSE。我们也设为NULL即可。
Return Value即返回值,与MultiByteToWideChar相类比。
调用两次,步骤也与MultiByteToWideChar相似。
delete
网上给的代码没有释放内存的语句,可能会造成内存泄露。
delete语句使用过指针都知道,释放一个指针指向的内存。
delete[]语句释放一个数组指针指向的内存。
如果想深入了解其中的原理,可以自行查阅。
小结
这一节简单介绍了编码的知识和使用C++进行编码转换的代码。我们这个项目深入了解编码系统,所介绍的也不过是冰山一角。
现在大家已经可以自己写代码对网页进行简单分析了。下一节我打算介绍关于WinHTTP的内容和用途。