目录

C工程实战入门笔记6-函数三关于base16编码的原理和函数模块化实战

C++工程实战入门笔记6-函数(三)关于base16编码的原理和函数模块化实战

首先,理解为什么要编码?

计算机内部所有数据都是用二进制(0和1) 存储的。但二进制对人类来说很难阅读和理解。比如:

文本 "Hello" 在计算机中存储为:01001000 01100101 01101100 01101100 01101111

ASCII码

使用二进制表示所有的大写和小写字母,数字0 到9、标点符号,有固定对应表。

常见ASCII码的大小规则

数字< 大写字母 < 小写字母

1.数字比字母要小。如 “7”<“F”;
2.数字0比数字9要小,并按0到9顺序递增。如 “3”<“8” ;
3.字母A比字母Z要小,并按A到Z顺序递增。如“A”<“Z” ;
4.同个字母的大写字母比小写字母要小32。如“A”<“a” 。

几个常见字母的ASCII码大小: “A”为65;“a”为97;“0”为 48

https://i-blog.csdnimg.cn/direct/5f58a1da99f14a55991ccea5f0a18822.png

base16编码

Unicode 码点从分配的那一刻起就是永久固定、不可改变的规则。
https://i-blog.csdnimg.cn/direct/03ee4f014a044b9085e2d18908ccbdc4.png

字符 'H' 的 ASCII 码是 72 (十六进制 0x48)
会被编码为 "48"

例如:

'A' → U+ 0041 → 十进制65

'你' → U+ 4F60 → 十进制20320

'😊' → U+ 1F60A → 十进制128522

根据码点值确定字节数

规则1:如果码点值 ≤ 127 (0x7F),使用1字节

包含所有ASCII字符:英文字母、数字、标点符号等

规则2:如果码点值 ≤ 2047 (0x7FF),使用2字节

包含拉丁字母扩展、希腊字母、西里尔字母、希伯来字母等

规则3:如果码点值 ≤ 65535 (0xFFFF),使用3字节

包含几乎所有常用汉字、日文、韩文字符

基本多文种平面(BMP)中的所有字符

规则4:如果码点值 ≤ 1114111 (0x10FFFF),使用4字节

包含辅助平面字符:罕见汉字、历史文字、表情符号等

示例1:英文字母 ‘A’

Unicode码点: U+0041 (十进制65)
判断: 65  127  使用1字节编码
UTF-8编码: 01000001 (0x41)

示例2:中文 ‘你’

Unicode码点: U+4F60 (十进制20320)
判断: 20320 > 2047?   20320  65535?   使用3字节编码
UTF-8编码: 11100100 10111101 10100000 (0xE4 0xBD 0xA0)

示例3:表情符号 ‘😊’

Unicode码点: U+1F60A (十进制128522)  
判断: 128522 > 65535?   128522  1114111?   使用4字节编码
UTF-8编码: 11110000 10011111 10011000 10001010 (0xF0 0x9F 0x98 0x8A)

编码规则

https://i-blog.csdnimg.cn/direct/674bd777f21d4dedac897aa5f4a1bfeb.png

先看“你”

”的码点:4F60,属于U+0800 - U+FFFF之间,需要三个字节。
4F60 转成二进制是

0100 1111 0110 0000

格式参考上图3字节,然后填入
https://i-blog.csdnimg.cn/direct/d43e252d593d4587afcf04165281f4bc.png

11100100 10111101 10100000

再用十六进制表示上述码

E4 BD A0

再看“好”

”的码点:597D,属于U+0800 - U+FFFF之间,需要三个字节。
597D 转成二进制是

0101 1001 0111 1101

格式参考上图3字节,然后填入
https://i-blog.csdnimg.cn/direct/98bb07ea7e774cada7ac340c9dff0046.png

11100101 10100101 10111101

再用十六进制表示上述码

E5 A5 BD

所以综上:“你好”的base16编码是:E4BDA0E5A5BD

base16编码和解码C++实现

无函数,直接实现

int main()
{
	SetConsoleOutputCP(65001); // 设置控制台输出编码为UTF-8
	//string test_str = "测试用于base16的字符串";
	string test_str = u8"你好";
	
	//base16编码
	string base16str;
	//unsigned char 和char的区别
	//8位一个字节
	//unsigned 无符号,在做算术运算时不管符号
	//char 第一位是符号位 二进制不能有正负号
	for (unsigned char c : test_str)
	{
		char h = c >> 4 ;//取高位:移位丢弃低位 0100 0001 >>4 之后成为 0000 0100
		char l = c & 0b00001111;//取低位,与00001111做与操作 之后成为 0000 0001
		//cout << "h:"<<h << endl;
		//cout << "l:" << l << endl;
		base16str += base16_enc_tab[h];//0-15=>0-9,A-F
		//cout << base16str << endl;
		base16str += base16_enc_tab[l];
		//cout << base16str << endl;
	}
	//cout << base16str << endl;

	////base16解码
	string ostr;
	const vector<char> base16_dec_tab{
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//0-9
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//10-19
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//20-29
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//30-39
	-1,-1,-1,-1,-1,-1,-1,-1,//40-47
	0,1,2,3,4,5,6,7,8,9,//48-57
	-1,-1,-1,-1,-1,-1,-1,//58-64
	10,11,12,13,14,15//65-70 A-F
	};

	for (int i = 0; i < base16str.size(); i+=2)
	{
		char ch = base16str[i];
		char cl = base16str[i + 1];
		//'A'=>10,'0'=>0 0-15 
		//65=>A=>10 48=>0 70=>F
		unsigned char h = base16_dec_tab[ch];
		unsigned char l = base16_dec_tab[cl];
		// h:0000 0100  l:0000 0001 ==> 0100 0001
		ostr += (h << 4 ) | l;
	}
	cout << ostr << endl;
	system("pause");
}*/

函数模块化实现

base16.h

#pragma once
#include<string>
#include<vector>

///////////////////////////////////
//base16编码
//@para data:需要编码的二进制数据
//@return 返回base16编码的字符串
std::string Base16Encode(const std::vector<unsigned char> data);

///////////////////////////////////
//base16解码
//@para str:需要解码的base16字符串,必须是2的倍数
//@return 返回原始的二进制数据
std::vector<unsigned char> Base16Decode(const std::string &str);

base16.cpp

#include "stdafx.h"
#include"base16.h"
#include<iostream>
using namespace std;

//静态全局变量 作用域本cpp文件
static const string enc_tab = "0123456789ABCDEF";
static const vector<char> dec_tab{
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//0-9
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//10-19
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//20-29
	-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,//30-39
	-1,-1,-1,-1,-1,-1,-1,-1,//40-47
	0,1,2,3,4,5,6,7,8,9,//48-57
	-1,-1,-1,-1,-1,-1,-1,//58-64
	10,11,12,13,14,15//65-70 A-F
};

///////////////////////////////////
//base16编码
//@para data:需要编码的二进制数据
//@return 返回base16编码的字符串
std::string Base16Encode(const std::vector<unsigned char> data)
{
	string res;
	for (unsigned char c : data)
	{
		char h = c >> 4;//取高位:移位丢弃低位 0100 0001 >>4 之后成为 0000 0100
		char l = c & 0b00001111;//取低位,与00001111做与操作 之后成为 0000 0001
		res += enc_tab[h];//0-15=>0-9,A-F
		res += enc_tab[l];
	}
	return res;
}

///////////////////////////////////
//base16解码
//@para str:需要解码的base16字符串,必须是2的倍数
//@return 返回原始的二进制数据
std::vector<unsigned char> Base16Decode(const std::string &str)
{
	if (str.size() % 2 != 0)
	{
		cerr << "base16 string is error" << endl;
		return{};
	}
	std::vector<unsigned char> res;
	for (int i = 0; i < str.size(); i += 2)
	{
		char ch = str[i];
		char cl = str[i + 1];
		//'A'=>10,'0'=>0 0-15 
		//65=>A=>10 48=>0 70=>F
		unsigned char h = dec_tab[ch];
		unsigned char l = dec_tab[cl];
		// h:0000 0100  l:0000 0001 ==> 0100 0001
		res.push_back( (h << 4) | l);
	}
	return res;
}

主函数

#include "stdafx.h"
#include<iostream>
#include<bitset>
#include<string>
#include<vector>
#include "base16.h"
#include <windows.h>
using namespace std;
//编码用的映射表
//const string base16_enc_tab = "0123456789ABCDEF";
int main()
{
	SetConsoleOutputCP(65001);
	string test_str = u8"这个世界会好吗?";
	cout << u8"原文:" << test_str << endl;
	cout << endl;
	vector<unsigned char> data(test_str.begin(), test_str.end());
	data.push_back('\0');

	auto  base16_str = Base16Encode(data);
	cout << u8"编码后base16:" << base16_str << endl;
	cout << endl;
	auto dec_str =  Base16Decode(base16_str);
	cout << u8"base16解码后:" << dec_str.data() << endl;
	cout << endl;
	system("pause");
}