我们希望一套代码到处编译,所以这里我们选择了系统的API,系统API也比较简单:
我们先说明一下我们怎么测这个案例,就是每次你做一个案例,就要想怎么测。
UTF-8到GBK的转换
第一个就是UTF-8转GBK的话,GBK可以直接在控制台显示,这个跟系统有关,我们系统控制台默认是GBK,所以说UTF-8转GBK很简单,我们直接cout打印就行了;
那么UTF-8这种编码的字符串从哪里来呢?我们直接把这个代码字符串前面加一个u8就可以了,但这样呢还是会有问题的,并不是说很兼容;我们直接把代码文件设成utf-8的,但是有的机器上这样设置后,当编译成字符串的时候还是GBK,也就是说它编译的时候做了转换;
所以这个时候,我们比较靠谱的方法就是说,在字符串前面加一个u8,例如u8"我这个是utf-8的字符串"
,告诉编译器我们要把所标识的这个字符串转成utf-8,就跟你在linux当中编译的时候指定这个代码用什么编译一样。
GBK到UTF-8的转换
我们默认的就是GBK,你不加u8,并且把代码转成GBK的,把我们的代码文件转成GBK的,然后再把把转化成UTF-8的;
而UTF-8怎么测呢,打印是输出不了的,在Linux当中可以输出(linux默认输出是utf-8的),在windows当中不行,那怎么办呢?几个方案:
(1)把UTF-8写到文件里面去;
(2)我们前面不是做了UTF-8转GBK么,我们再转一遍把它输出就可以了。
我们可以看到控制台默认是GBK的。
有的vs版本(例如vs2017)的默认编码是UTF-8的,可以从上图看到,我们这里的vs2019默认编码是GBK的。
当然你可以基于其他的工具(例如notepad++、UltraEdit推荐):
我们把代码换成ANSI编码的话,代码里面的中文注释都成乱码了:
我们可以点击下面的菜单:转为ANSI编码,就可以把代码转为ANSI编码格式了。
// test_gbk_utf8.cpp
//#include
#include
#include using namespace std;string UTF8ToGBK(const char* data)
{string gbk = "";// 1. UTF-8转为unicode 其实unicode对windows来说呢就是utf-16,就是用宽字节来存储的// 1.1 统计转换后的字节数int len = MultiByteToWideChar(CP_UTF8, // 转换的格式0, // 默认的转换方式data, // 输入的字节数据-1, // 输入的字符串大小 -1就是找\0结束nullptr, // 输出0// 输出的空间大小);if (len <= 0)return gbk;wstring udata;udata.resize(len);// 转换为unidoce了MultiByteToWideChar(CP_UTF8, 0, data, -1, (wchar_t*)udata.data(), len);// 2. unicode转GBK// 2.1 统计转换后的字节数len = WideCharToMultiByte(CP_ACP, 0, (wchar_t*)udata.data(), -1, nullptr, 0, nullptr, // 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的0 // 是否使用默认替代);if (len <= 0)return gbk;gbk.resize(len);WideCharToMultiByte(CP_ACP, 0, (LPWSTR)udata.data(), -1, (char*)gbk.data(), len, nullptr, 0);return gbk;
}string GBKToUTF8(const char* data)
{string utf8 = "";// GBK转unicode// 1.1 统计转换后的字节数int len = MultiByteToWideChar(CP_ACP, // 转换的格式0, // 默认的转换方式data, // 输入的字节数据-1, // 输入的字符串大小 -1就是找\0结束nullptr, // 输出0// 输出的空间大小);if (len <= 0)return utf8;wstring udata;udata.resize(len);// 转换为unidoce了MultiByteToWideChar(CP_ACP, 0, data, -1, (wchar_t*)udata.data(), len);// 2. unicode转UTF-8// 2.1 统计转换后的字节数len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)udata.data(), -1, nullptr, 0,nullptr, // 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的0 // 是否使用默认替代);if (len <= 0)return utf8;utf8.resize(len);WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)udata.data(), -1, (char*)utf8.data(), len, nullptr, 0);return utf8;
}int main()
{// 为了保证下面的测试正确,先确保我们的代码是GBK或者ANSI的std::cout << "Hello World 测试!\n";// 1、测试UTF-8转GBKcout << UTF8ToGBK(u8"测试UTF-8转GBK") << endl;// 2、测试GBK到UTF-8的转换string utf8 = GBKToUTF8("测试GBK转UTF-8再转为GBK");cout << "luan ma : utf8 = " << utf8 << endl;cout << "GBK = " << UTF8ToGBK(utf8.c_str()) << endl;return 0;
}
我们用UE把代码转成UTF-8格式:
在windows下要选择utf-8(有BOM)。
// test_gbk_utf8_linux makefiletest:test_gbk_utf8_linux.cppg++ $^ -o $@
// test_gbk_utf8.cpp
//#include
#include #ifdef _WIN32
#include
#else
#include
#include
#endif
using namespace std;#ifndef _WIN32
static size_t Convert(char* from_cha, char* to_cha, char* in, size_t inLen, char* out, size_t outLen)
{// 转换上下文iconv_t cd;cd = iconv_open(to_cha, from_cha);if(cd == 0)return -1; // 如果字符编码打开失败,直接返回-1memset(out, 0, outlen);char** pin = ∈char** pout = &out;cout << "in = " << in << endl;cout << "inLen = " << inLen << endl;cout << "outLen = " << outLen << endl;// 返回转换字节的数量,但是转GBK时经常不正确 >=0就成功size_t re = iconv(cd, pin, &inLen, pout, &outLen);if(re < 0){cout << "iconv failed! re = " << (int)re << endl;return -1;}cout << "iconv success! re = " << (int)re << endl;if(cd != 0)iconv_close(cd);return re;
}
#endifstring UTF8ToGBK(const char* data)
{string re = "";// 1. UTF-8转为unicode 其实unicode对windows来说呢就是utf-16,就是用宽字节来存储的
#ifdef _WIN32 // 1.1 统计转换后的字节数int len = MultiByteToWideChar(CP_UTF8, // 转换的格式0, // 默认的转换方式data, // 输入的字节数据-1, // 输入的字符串大小 -1就是找\0结束nullptr, // 输出0// 输出的空间大小);if (len <= 0)return re;wstring udata;udata.resize(len);// 转换为unidoce了MultiByteToWideChar(CP_UTF8, 0, data, -1, (wchar_t*)udata.data(), len);// 2. unicode转GBK// 2.1 统计转换后的字节数len = WideCharToMultiByte(CP_ACP, 0, (wchar_t*)udata.data(), -1, nullptr, 0, nullptr, // 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的0 // 是否使用默认替代);if (len <= 0)return re;re.resize(len);WideCharToMultiByte(CP_ACP, 0, (LPWSTR)udata.data(), -1, (char*)re.data(), len, nullptr, 0);
#else// linux部分re.resize(1024);int inLen = strlen(data);Convert((char*)"utf-8", (char*)"gbk", (char*)data, inLen, (char*)re.data(), re.size());int outLen = strlen(re.data());re.resize(outLen);
#endifreturn re;
}string GBKToUTF8(const char* data)
{string re = "";
#ifdef _WIN32// GBK转unicode// 1.1 统计转换后的字节数int len = MultiByteToWideChar(CP_ACP, // 转换的格式0, // 默认的转换方式data, // 输入的字节数据-1, // 输入的字符串大小 -1就是找\0结束nullptr, // 输出0// 输出的空间大小);if (len <= 0)return re;wstring udata;udata.resize(len);// 转换为unidoce了MultiByteToWideChar(CP_ACP, 0, data, -1, (wchar_t*)udata.data(), len);// 2. unicode转UTF-8// 2.1 统计转换后的字节数len = WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)udata.data(), -1, nullptr, 0,nullptr, // 失败默认替代字符集,0的话我们就把它截断掉了,因为我们的字符串都是以\0结尾的0 // 是否使用默认替代);if (len <= 0)return re;re.resize(len);WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)udata.data(), -1, (char*)re.data(), len, nullptr, 0);
#else// linux部分re.resize(1024);int inLen = strlen(data);Convert((char*)"gbk", (char*)"utf-8", (char*)data, inLen, (char*)re.data(), re.size());int outLen = strlen(re.data());re.resize(outLen);
#endifreturn re;
}int main()
{// 为了保证下面的测试正确,先确保我们的代码是GBK或者ANSI的std::cout << "Hello World 测试!\n";// 1、测试UTF-8转GBK//string gbk = UTF8ToGBK(u8"测试UTF-8转GBK");//cout << "gbk = " << gbk << endl;//cout << GBKToUTF8(gbk.c_str()) << endl;string ascii_string = "测试UTF-8转GBK";string utf8_string = GBKToUTF8(ascii_string.c_str());cout << "输入 utf = " << utf8_string << endl;string gbk = UTF8ToGBK(utf8_string.c_str());cout << "gbk = " << gbk << endl;cout << GBKToUTF8(gbk.c_str()) << endl;// 2、测试GBK到UTF-8的转换//string utf8 = GBKToUTF8("测试GBK转UTF-8再转为GBK");//cout << "luan ma : utf8 = " << utf8 << endl;//cout << "GBK = " << UTF8ToGBK(utf8.c_str()) << endl;system("pause");return 0;
}
我们这边字符集转换主要涉及两方面的内容:插入和读取。
// ZPMysql makefiletestZPMysql:test_ZPMysql.cpp libZPMysql.sog++ test_ZPMysql.cpp -L./ -lZPMysql -o $@ -g
libZPMysql.so:ZPData.cpp ZPMysql.cpp ZPData.h ZPMysql.hg++ -fPIC -shared -g $^ -I/usr/include/mysql/ -lmysqlclient -o $@
run:./testZPMysql
install:@cp libZPMysql.so /usr/lib/@echo "install libZPMysql.so success!"
uninstall:@rm -rf /usr/lib/libZPMysql.so@echo "uninstall libZPMysql.so success!"
clean:clear@echo "clear project success!"
clear:@rm -rf *.o *.so testZPMysql@echo "clear project success!"
上述makefile执行输出:
g++ -fPIC -shared ZPData.cpp ZPMysql.cpp ZPMysql.h ZPData.h -o libZPMysql.so -I/usr/include/mysql -lmysqlclient
g++ ./test_ZPMysql/test_ZPMysql.cpp -o test_ZPMysql -I./ -lZPMysql -L./
cp libZPMysql.so /usr/lib
install libZPMysql.so success!
./test_ZPMysql
在linux当中我们也测试成功,这样的话,我们同一份代码在两个系统都已经插入成功。
utf8在各个平台通用,但是gbk数据在linux当中就没有了(无法正常显示);
这里我们先插入,等有问题了再来解决。
在linux当中我们测试发现是乱码,因为linux这边是当做utf-8的,要插入的字段name是gbk的,你把utf-8插进去肯定是乱码;
所以在linux当中我们要做个转换,把utf-8转换成gbk。
在ZPData.cpp中,把前面我们写的UTF8ToGBK和GBKToUTF8这两个函数的代码粘贴过来:
我们查询数据库发现显示没问题。
我们来编写从数据库中获取数据的代码:
在linux当中因为name字段存的是gbk的,所以获取后不能正常显示。
我们修改代码,可以判断是否是在linux当中,如果在linux当中我们要做个转换才能正常输出:
这样在windows和linux当中测试都没有问题。
我们可以看到在控制台中的utf8输出是乱码,这是因为控制台的编码是GBK的,所以我们在windows当中的时候需要转换,而在linux当中就不需要转换了,因为linux是直接支持utf-8显示的。
这样我们就完成了对于中文字符的UTF-8和GBK之间的互相转换,插入和读取功能就做完了。
经测试我们发现没有输出BLOG,说明我们FetchRow的时候没有把类型保存起来(为了性能的话可以在StoreResult里面保存)。
mysql的锁一般只是针对我们做事务的时候才会用到,最常见的用法,比方说订票,就是你不能保证你这次操作是原子操作,因为订票是两个操作的,第一步先查这个票有没有售完,第二步把这张票标识为已售完。
行锁,我们用事务来演示,开两个线程来演示,或者让我们程序运行两遍。
// 订票模拟(事务) t_tickets(id int, sold int)sql = "CREATE TABLE IF NOT EXISTS `t_tickets` \(`id` INT AUTO_INCREMENT, \`sold` INT, \PRIMARY KEY(`id`))";my.Query(sql.c_str());// 这里不能清空数据(因为我们模拟行锁,要运行两遍程序,两个程序共享这张表)//my.Query("TRUNCATE `t_tickets`");{XDATA data;data["sold"] = "0"; // 0是未售出my.Insert(data, "t_tickets"); // id=1// 取这张票,并且把这张票的标识添加进来my.StartTransaction();// 我们先看不锁的情况XROWS rows = my.GetResult("select * from t_tickets where sold=0 order by id");string id = rows[0][0].data;cout << "Buy ticket id is " << id << endl;// 模拟冲突(10秒钟之后我们更新这张票)this_thread::sleep_for(10s);data["sold"] = "1";string where = "where id = ";where += id;cout << my.Update(data, "t_tickets", where) << endl;cout << "Buy ticket id " << id << "success!" << endl;//my.GetResult("select * from t_tickets where sold=0 for updata");my.Commit();my.StopTransaction();}cout << endl;// 清理资源my.Close();cout << "test_ZPMysql!" << endl;getchar();
我们可以看到,两个人买同一张票都成功了!这就很有问题了,而且第二个人执行Update修改票的状态的时候是失败的(Update输出是0)。
我们可以看到第二个人阻塞在那里,就在那里等着,无法查询。
第一个人购买2号票成功后,第二个人查询的票号是3,第二个人购票成功。
这样的话就使得两者不冲突(判断资源和申请资源不冲突),我们就做了这样一个行锁,锁住这张票,
// agent makefileagent:agent.cpp XAgent.cpp XAgent.hg++ $^ -o $@ -I ../ZPMysql -lZPMysql./$@ 127.0.0.1
// ZPMysql makefileall:libZPMysql.so test_ZPMysql install#./test_ZPMysql
libZPMysql.so:ZPData.cpp ZPMysql.cpp ZPData.h ZPMysql.hg++ -fPIC -shared $^ -o $@ -I/usr/include/mysql/ -lmysqlclient
test_ZPMysql:../test_ZPMysql/test_ZPMysql.cppg++ $^ -o $@ -I./ -lZPMysql -L./
install:cp libZPMysql.so /usr/lib/@echo "install libZPMysql.so success!"
uninstall:rm -rf /usr/lib/libZPMysql.so@echo "uninstall libZPMysql.so success!"
clean:rm -rf *.o *.so test_ZPMysqlrm -rf /usr/lib/libZPMysql.so@echo "clear project success!"
// center makefilecenter:center.cpp XCenter.cpp XCenter.hg++ $^ -o $@ -I ../ZPMysql -lZPMysql./$@ install 127.0.0.1
clean:rm -rf *.orm -rf center
在linux下用makefile的时候,如果你的程序某个函数返回-1的话,make就会认为你的程序出错了,makefile可能会停止,所以我们这里测试的时候还是返回0吧。
// center makefilecenter:center.cpp XCenter.cpp XCenter.hg++ $^ -o $@ -I ../ZPMysql -lZPMysql./$@ install 127.0.0.1./$@ add 192.168.0.202 fileserver1./$@ add 192.168.0.201 fileserver2
clean:rm -rf *.orm -rf center
// center makefilecenter:center.cpp XCenter.cpp XCenter.hg++ $^ -o $@ -I ../ZPMysql -lZPMysql#./$@ install 127.0.0.1#./$@ add 192.168.0.202 fileserver1#./$@ add 192.168.0.201 fileserver2./$@
clean:rm -rf *.orm -rf center