c++跨平台开发技术总结
作者:互联网
一、前言
博主初入c++开发,对技术的了解深度不足,如果编写内容有出错的,欢迎指出。
二、跨平台简介
这里的跨平台主要指windows、Android和iOS上的开发。PC用dll加载,安卓用 .so,ios用.a。
如果我们开发一个通用版本的sdk,在windows上开发之后运行没问题,但是当复制到Android stuido或者xcode发现各种报错。原因就是因为c++在不同平台上存在差异。
安卓和ios上的c++代码主要参考linux的语法,所以下面文章在总结c++语法的时候通常用linux来表述。
本文主要就是介绍在这三个平台开发上的区别和问题总结。
三、跨平台开发基础
在了解差异之前,我们先要了解一些知识,用来区分或者打通开发流程。
术语介绍
- android studio,安卓开发工具,后续简称as
- xcode,苹果开发工具
- visual studio,博主用的c++开发的ide,后续简称vs
平台宏定义
用于区分 安卓,ios,windows
注意ifdef和if的区别
#ifdef _WIN32
//表示windows系统,32位或64位
#ifdef _WIN64
//64位windows系统
#endif
#elif __APPLE__
#if TARGET_IPHONE_SIMULATOR
// iOS模拟器
#elif TARGET_OS_IPHONE
// iOS设备
#elif TARGET_OS_MAC
// 其他mac系统
#else
// 未支持的其他系统
#endif
#elif __ANDROID__
//安卓系统
#elif __linux
// linux
#elif __unix
// Unix
#elif __posix
// POSIX
#endif
unicode开发
为了避免不同平台编码不同导致的乱码问题,通常会转成unicode编码来做
首先需要切换解决方案的字符集:
配置属性 -> 常规 -> 字符集 -> 使用 Unicode 字符集
cmake指令是:add_definitions(-DUNICODE -D_UNICODE)
类型:wstring(字符串), wchar_t(字符)
// 常量字符初始化
wstring a = L"啊啊啊啊啊";
wchar_t b[] = {L'哈', L'呵'};
unicode操作集合见附件,windows可以用通用
,但是移动端就不行了,需要用UNICODE
那一列的方法。
对外接口设计尽可能使用基础类型
比如如果要返回string
类型的值,暴露的接口需要改成const char*
/char*
类型
因为string是c++语言独有的类型,其他语言解析需要经过额外处理,所以转成最基本的类型更好
其他的如vector
等,同理
ps:如果确实有返回整个类/结构的需求,有以下几种设计方法:
1、每个值,单独设置一个接口
2、对外暴露模板类,然后在其他语言的胶水层处理类的数据
3、json大法好
四、跨平台编程上的差异
主要介绍linux(安卓+ios)和windows中c++的语法差异
prgram once指令在linux下不起作用
在windows开发时,为了防止一个头文件include,从而导致冲突,我们可能会在.h文件上加 #prgram once
预编译指令。
但是这个用法在linux不起作用(在as和xcode上起作用,目前博主未出现过模块导入冲突的情况),需要改成 #ifndef + #define
的形式来处理
样例:
#ifndef _MAIN_H_
#define _MAIN_H_
#endif
需要注意宏定义不要和其他的头文件冲突,不然第二个同文件导入失败不太好查。
编码区别
linuxs上是utf8编码,windows上是gbk,因此在处理中文字符串的时候,需要注意区别处理。
方法:转宽字节(unicode),再转成另外一个编码
#include <iostream>
#include <string>
#include <string.h>
#include <stdlib.h>
using namespace std;
#ifdef _WIN32
#include <windows.h>
string GbkToUtf8(const char *src_str)
{
int len = MultiByteToWideChar(CP_ACP, 0, src_str, -1, NULL, 0);
wchar_t* wstr = new wchar_t[len + 1];
memset(wstr, 0, len + 1);
MultiByteToWideChar(CP_ACP, 0, src_str, -1, wstr, len);
len = WideCharToMultiByte(CP_UTF8, 0, wstr, -1, NULL, 0, NULL, NULL);
char* str = new char[len + 1];
memset(str, 0, len + 1);
WideCharToMultiByte(CP_UTF8, 0, wstr, -1, str, len, NULL, NULL);
string strTemp = str;
if (wstr) delete[] wstr;
if (str) delete[] str;
return strTemp;
}
string Utf8ToGbk(const char *src_str)
{
int len = MultiByteToWideChar(CP_UTF8, 0, src_str, -1, NULL, 0);
wchar_t* wszGBK = new wchar_t[len + 1];
memset(wszGBK, 0, len * 2 + 2);
MultiByteToWideChar(CP_UTF8, 0, src_str, -1, wszGBK, len);
len = WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, NULL, 0, NULL, NULL);
char* szGBK = new char[len + 1];
memset(szGBK, 0, len + 1);
WideCharToMultiByte(CP_ACP, 0, wszGBK, -1, szGBK, len, NULL, NULL);
string strTemp(szGBK);
if (wszGBK) delete[] wszGBK;
if (szGBK) delete[] szGBK;
return strTemp;
}
#endif
long类型内存差异
long类型的变量在32位和64位Windows上都是4个字节,而在64位Linux系统上占8字节
文件/文件夹操作上的差异
文件内容操作
因为windows和linux都有fstream,所以都可以用open
来对文件内容进行操作,操作平台互通
文件路径操作
路径操作主要分为三个方法:access(判断路径存在),mkdir(创建文件夹),rmdir(删除文件夹)
windows可以使用前缀带_的函数(比如_access),但是linux不行
还有就是头文件有差异,导入的时候注意区分
详细差异可以看这个:https://blog.csdn.net/NCEPUautomation/article/details/108304619
文件夹遍历
头文件和操作接口都不一样,详细见附录
linux没itoa()函数
linux有atoi,但是没itoa,就很nb……
可以用snprintf代替实现
#include <stdlib.h>
#include <stdio.h>
int number = 429496729;
char str[25];
snprintf(str, 25, "%d", number);
printf("integer = %d string = %s\n", number, str);
五、跨平台开发过程中遇到的疑难杂症
主要总结在安卓和ios开发过程中,遇到的非语法问题。
代码长度过长,as出现容量不足的报错
clang++.exe: error: clang frontend command failed due to signal (use -v to see invocation)
Android clang version 5.0.300080 (base on LLVM 5.0.300080)
Target: aarch 64-none-linux-android
ndk版本过小,注意升级
博主之前使用的是16版本的ndk,后面升级到21版本之后,就没这个问题了
不同平台桥接层代码生成方案
swig
SWIG (Simplified Wrapper and Interface Generator) ,即简化包以及接口生成器
目前博主用的就是这个,生成桥接代码包装原生代码,用以支持其他语言如python、java等语言调用
ps:具体使用的博客待补充
Djinni
Djinni是Dropbox开发的工具,可帮助利用C ++内置的代码发布到iOS和Android平台
目前博主没用到,后续准备调研下
ps:具体调研或使用的博客待补充
总结
文献参考
unicode字符定义和函数对照表: https://blog.csdn.net/venom_snake/article/details/88066475
不同平台文件遍历操作: https://blog.csdn.net/wh445306/article/details/106685269
附录
unicode字符定义和函数对照表
ANSI | UNICODE | 通用 | 说明 |
---|---|---|---|
数据类型 | |||
(char.h) | (wchar.h) | (tchar.h) | |
char | wchar_t | TCHAR | |
char * | wchar_t * | TCHAR* | |
LPSTR | LPWSTR | LPTSTR | |
LPCSTR | LPCWSTR | LPCTSTR | |
字符串转换 | |||
atoi | _wtoi | _ttoi | 把字符串转换成整数(int) |
atol | _wtol | _ttol | 把字符串转换成长整型数(long) |
atof | _wtof | _tstof | 把字符串转换成浮点数(double) |
itoa | _itow | _itot | 将任意类型的数字转换为字符串 |
字符串操作 | |||
strlen | wcslen | _tcslen | 获得字符串的数目 |
strcpy | wcscpy | _tcscpy | 拷贝字符串 |
strncpy | wcsncpy | _tcsncpy | 类似于strcpy/wcscpy,同时指定拷贝的数目 |
strcmp | wcscmp | _tcscmp | 比较两个字符串 |
strncmp | wcsncmp | _tcsncmp | 类似于strcmp/wcscmp,同时指定比较字符字符串的数目 |
strcat | wcscat | _tcscat | 把一个字符串接到另一个字符串的尾部 |
strncat | wcsncat | _tcsnccat | 类似于strcat/wcscat,而且指定粘接字符串的粘接长度. |
strchr | wcschr | _tcschr | 查找子字符串的第一个位置 |
strrchr | wcsrchr | _tcsrchr | 从尾部开始查找子字符串出现的第一个位置 |
strpbrk | wcspbrk | _tcspbrk | 从一字符字符串中查找另一字符串中任何一个字符第一次出现的位置 |
strstr | wcsstr/wcswcs | _tcsstr | 在一字符串中查找另一字符串第一次出现的位置 |
strcspn | wcscspn | _tcscspn | 返回不包含第二个字符串的的初始数目 |
strspn | wcsspn | _tcsspn | 返回包含第二个字符串的初始数目 |
strtok | wcstok | _tcstok | 根据标示符把字符串分解成一系列字符串 |
wcswidth | 获得宽字符串的宽度 | ||
wcwidth | 获得宽字符的宽度 | ||
字符串测试 | |||
isascii | iswascii | _istascii | 测试字符是否为ASCII 码字符, 也就是判断c 的范围是否在0 到127 之间 |
isalnum | iswalnum | _istalnum | 测试字符是否为数字或字母 |
isalpha | iswalpha | _istalpha | 测试字符是否是字母 |
iscntrl | iswcntrl | _istcntrl | 测试字符是否是控制符 |
isdigit | iswdigit | _istdigit | 测试字符是否为数字 |
isgraph | iswgraph | _istgraph | 测试字符是否是可见字符 |
islower | iswlower | _istlower | 测试字符是否是小写字符 |
isprint | iswprint | _istprint | 测试字符是否是可打印字符 |
ispunct | iswpunct | _istpunct | 测试字符是否是标点符号 |
isspace | iswspace | _istspace | 测试字符是否是空白符号 |
isupper | iswupper | _istupper | 测试字符是否是大写字符 |
isxdigit | iswxdigit | _istxdigit | 测试字符是否是十六进制的数字 |
大小写转换 | |||
tolower | towlower | _totlower | 把字符转换为小写 |
toupper | towupper | _totupper | 把字符转换为大写 |
字符比较 | |||
strcoll | wcscoll | _tcscoll | 比较字符串 |
日期和时间转换 | |||
strftime | wcsftime | _tcsftime | 根据指定的字符串格式和locale设置格式化日期和时间 |
strptime | 根据指定格式把字符串转换为时间值, 是strftime的反过程 | ||
打印和扫描字符串 | |||
printf | wprintf | _tprintf | 使用vararg参量的格式化输出到标准输出 |
fprintf | fwprintf | _ftprintf | 使用vararg参量的格式化输出 |
scanf | wscanf | _tscanf | 从标准输入的格式化读入 |
fscanf | fwscanf | _ftscanf | 格式化读入 |
sprintf | swprintf | _stprintf | 根据vararg参量表格式化成字符串 |
sscanf | swscanf | _stscanf | 以字符串作格式化读入 |
vfprintf | vfwprintf | _vftprintf | 使用stdarg参量表格式化输出到文件 |
vprintf | 使用stdarg参量表格式化输出到标准输出 | ||
vsprintf | vswprintf | _vstprintf | 格式化stdarg参量表并写到字符串 |
sprintf_s | swprintf_s | _stprintf_s | 格式化字符串 |
数字转换 | |||
strtod | wcstod | _tcstod | 把字符串的初始部分转换为双精度浮点数 |
strtol | wcstol | _tcstol | 把字符串的初始部分转换为长整数 |
strtoul | wcstoul | _tcstoul | 把字符串的初始部分转换为无符号长整数 |
_strtoi64 | _wcstoi64 | _tcstoi64 | |
输入和输出 | |||
fgetc | fgetwc | _fgettc | 从流中读入一个字符并转换为宽字符 |
fgets | fgetws | _fgetts | 从流中读入一个字符串并转换为宽字符串 |
fputc | fputwc | _fputtc | 把宽字符转换为多字节字符并且输出到标准输出 |
fputs | fputws | _fputts | 把宽字符串转换为多字节字符并且输出到标准输出串 |
getc | getwc | _gettc | 从标准输入中读取字符, 并且转换为宽字符 |
getchar | getwchar | _gettchar | 从标准输入中读取字符 |
putc | putwc | _puttc | 标准输出 |
putchar | putwchar | _puttchar | 标准输出 |
ungetc | ungetwc | _ungettc | 把一个字符放回到输入流中 |
不同平台文件夹遍历操作
以下为删除文件操作
windows
#include <io.h>
bool RmDir(const std::string & path)
{
std::string strPath = path;
struct _finddata_t fb; //查找相同属性文件的存储结构体
//制作用于正则化路径
if (strPath.at(strPath.length() - 1) != '\\' || strPath.at(strPath.length() - 1) != '/')
strPath.append("\\");
std::string findPath = strPath + "*";
intptr_t handle;//用long类型会报错
handle = _findfirst(findPath.c_str(), &fb);
//找到第一个匹配的文件
if (handle != -1L)
{
std::string pathTemp;
do//循环找到的文件
{
//系统有个系统文件,名为“..”和“.”,对它不做处理
if (strcmp(fb.name, "..")!=0 && strcmp(fb.name, ".")!=0)//对系统隐藏文件的处理标记
{
//制作完整路径
pathTemp.clear();
pathTemp = strPath + std::string(fb.name);
//属性值为16,则说明是文件夹,迭代
if (fb.attrib == _A_SUBDIR)//_A_SUBDIR=16
{
RmDir(pathTemp.c_str());
}
//非文件夹的文件,直接删除。对文件属性值的情况没做详细调查,可能还有其他情况。
else
{
//这里也可以用 DeleteFile(const char*)
remove(pathTemp.c_str());
}
}
} while (0 == _findnext(handle, &fb));//判断放前面会失去第一个搜索的结果
//关闭文件夹,只有关闭了才能删除。找这个函数找了很久,标准c中用的是closedir
//经验介绍:一般产生Handle的函数执行后,都要进行关闭的动作。
_findclose(handle);
}
//移除文件夹
return RMDIR(strPath.c_str())==0?true:false;
}
linux
#include <dirent.h>
bool RmDir(const std::string & path)
{
if (strPath.at(strPath.length() - 1) != '\\' || strPath.at(strPath.length() - 1) != '/')
strPath.append("/");
DIR *d = opendir(strPath.c_str());//打开这个目录
if (d != NULL)
{
struct dirent *dt = NULL;
while (dt = readdir(d))//逐个读取目录中的文件到dt
{
//系统有个系统文件,名为“..”和“.”,对它不做处理
if (strcmp(dt->d_name, "..")!=0 && strcmp(dt->d_name, ".")!=0)//判断是否为系统隐藏文件
{
struct stat st;//文件的信息
std::string fileName;//文件夹中的文件名
fileName = strPath + std::string(dt->d_name);
stat(fileName.c_str(), &st);
if (S_ISDIR(st.st_mode))
{
RmDir(fileName);
}
else
{
remove(fileName.c_str());
}
}
}
closedir(d);
}
return rmdir(strPath.c_str())==0?true:false;
}
标签:字符,string,开发技术,c++,跨平台,len,str,字符串,strPath 来源: https://www.cnblogs.com/end-emptiness/p/15083829.html