
个推语义情感分析实操 |10行代码帮你判断TA是否生气了
想必大家都听说过这样一句话,“男人来自火星,女人来自金星”。男女之间确实存在一定的思维差异。
怎么样才能更准确地GET到TA的真实想法和情绪,不至于和心仪的TA越走越远呢?也许可以试试技术的方式。
随着AI的日益普及和在各个领域的加速落地,很多企业不断尝试将自然语言处理、语义情感分析等技术应用到智能客服、交互机器人等领域。个推也在自然语言处理及人工智能领域拥有丰富的实践经验。
本文主要借这个有趣的实操案例,为大家分享如何基于NLP进行语义情感分析。
一、准备数据
本次实验,我们使用的是从GitHub下载的开源数据集:数据集来自新浪微博,一共361744行数据,并且已经有“喜悦” “愤怒” “厌恶” “低落”等四类标签的label标记。

二、数据预处理
我们知道,文本数据是一种非结构化的数据,且数据内部存在许多像标点、表情符号等类型的特殊字符。因此,我们要先对数据进行一些预处理。
首先,我们先剔除了文本中的一些特殊字符,并对文本进行了中文分词。
在该步骤,为了提升模型效果,我们一般需要建立停用词库,并在文本中将停用词予以剔除。但是考虑到在情感分析中,许多停用词其实有可能会对分类结果产生影响。例如对话时的“呀”,往往带有喜悦的情感色彩。因此在这里,我们并未将这一类的词汇进行剔除。
当然你也可以尝试自定义一个停用词库,并比较一下最后的分类效果。
接下来,我们进入第二步。我们按照7:3的比例将数据集分成了训练集和测试集这两个数据集。同时,我们也对每个词进行了编码,并将文本的序列调整成为相同的长度。
再接着,我们通过word2vec预训练来得到词向量,并且通过观察单词的相似度,对词向量的质量进行了检验。
从下图,可以看到,相似的单词还是基本符合预期的。
三、模型训练
将数据进行预处理之后,我们就可以将数据“喂入”到分类模型中。业内常用的经典文本分类模型有FastText(一种快速文本分类器)、TextCNN(利用卷积神经网络对文本进行分类的算法)、R-CNN(Region-CNN,用于实现目标检测)、Han(关键词提取算法)等。
下图是我们基于最简单的FastText算法进行文本分类的过程:
我们对该算法模型进行了10轮的持续迭代。在测试集上,该模型的准确率达到了0.582。然后,我们将FastText模型结果作为我们此次实验的baseline模型结果。
我们又分别对TextCNN、R-CNN、BiLSTM + Attention进行了测试,下表对不同算法模型的效果进行了梳理和对比:
由上表我们可以看到,相较而言,还是BiLSTM + Attention的预测效果更好。
下图是我们使用BiLSTM + Attention进行模型预测的代码结构:
Attention的结构和加权池化层类似,我们其实可以将权重提取出来并且进行可视化:
四、模型预测
接下来,我们将训练好的BiLSTM + Attention模型进行封装,并看一下该模型的预测效果。
通过下表,可以看到,模型的训练效果还是比较令人满意的,对于“呵呵”和“哈哈哈”、“了”和“啦”这些语气词之间的微妙情绪差别都能进行相对准确的判断:
至此,本次基于NLP进行情感分析的模型训练实验也就基本完成了。
这其中其实还有许多可以优化的点,例如:针对每个标签类别占比不均衡的情况,可以进行loss修改;不仅仅可以对每个类别的标签做onehot处理,还可以将label本身的文字信息做embedding等等。感兴趣的童鞋可以在自己的实操中尝试进行更多的优化。
总结
目前,虽然AI技术的应用已经越来越成熟和普遍,算法模型也一定程度上能够对文本的情感进行判断。但是Mr.Tech也要温馨提醒大家,在现实的人际交往中,我们彼此之间还是要真诚沟通、用心交流哦。
未来,个推将不断在自然语言处理、机器视觉等前沿技术领域开展实践,也会持续将其中有价值的内容分享给大家。我们相信技术的飞速发展一定会给我们的生活带来很多有意义、有趣的改变。我们也期待读者朋友们,多多和我们交流自己的前沿实战和趣味实操案例。
想必大家都听说过这样一句话,“男人来自火星,女人来自金星”。男女之间确实存在一定的思维差异。
怎么样才能更准确地GET到TA的真实想法和情绪,不至于和心仪的TA越走越远呢?也许可以试试技术的方式。
随着AI的日益普及和在各个领域的加速落地,很多企业不断尝试将自然语言处理、语义情感分析等技术应用到智能客服、交互机器人等领域。个推也在自然语言处理及人工智能领域拥有丰富的实践经验。
本文主要借这个有趣的实操案例,为大家分享如何基于NLP进行语义情感分析。
一、准备数据
本次实验,我们使用的是从GitHub下载的开源数据集:数据集来自新浪微博,一共361744行数据,并且已经有“喜悦” “愤怒” “厌恶” “低落”等四类标签的label标记。
二、数据预处理
我们知道,文本数据是一种非结构化的数据,且数据内部存在许多像标点、表情符号等类型的特殊字符。因此,我们要先对数据进行一些预处理。
首先,我们先剔除了文本中的一些特殊字符,并对文本进行了中文分词。
在该步骤,为了提升模型效果,我们一般需要建立停用词库,并在文本中将停用词予以剔除。但是考虑到在情感分析中,许多停用词其实有可能会对分类结果产生影响。例如对话时的“呀”,往往带有喜悦的情感色彩。因此在这里,我们并未将这一类的词汇进行剔除。
当然你也可以尝试自定义一个停用词库,并比较一下最后的分类效果。
接下来,我们进入第二步。我们按照7:3的比例将数据集分成了训练集和测试集这两个数据集。同时,我们也对每个词进行了编码,并将文本的序列调整成为相同的长度。
再接着,我们通过word2vec预训练来得到词向量,并且通过观察单词的相似度,对词向量的质量进行了检验。
从下图,可以看到,相似的单词还是基本符合预期的。
三、模型训练
将数据进行预处理之后,我们就可以将数据“喂入”到分类模型中。业内常用的经典文本分类模型有FastText(一种快速文本分类器)、TextCNN(利用卷积神经网络对文本进行分类的算法)、R-CNN(Region-CNN,用于实现目标检测)、Han(关键词提取算法)等。
下图是我们基于最简单的FastText算法进行文本分类的过程:
我们对该算法模型进行了10轮的持续迭代。在测试集上,该模型的准确率达到了0.582。然后,我们将FastText模型结果作为我们此次实验的baseline模型结果。
我们又分别对TextCNN、R-CNN、BiLSTM + Attention进行了测试,下表对不同算法模型的效果进行了梳理和对比:
由上表我们可以看到,相较而言,还是BiLSTM + Attention的预测效果更好。
下图是我们使用BiLSTM + Attention进行模型预测的代码结构:
Attention的结构和加权池化层类似,我们其实可以将权重提取出来并且进行可视化:
四、模型预测
接下来,我们将训练好的BiLSTM + Attention模型进行封装,并看一下该模型的预测效果。
通过下表,可以看到,模型的训练效果还是比较令人满意的,对于“呵呵”和“哈哈哈”、“了”和“啦”这些语气词之间的微妙情绪差别都能进行相对准确的判断:
至此,本次基于NLP进行情感分析的模型训练实验也就基本完成了。
这其中其实还有许多可以优化的点,例如:针对每个标签类别占比不均衡的情况,可以进行loss修改;不仅仅可以对每个类别的标签做onehot处理,还可以将label本身的文字信息做embedding等等。感兴趣的童鞋可以在自己的实操中尝试进行更多的优化。
总结
目前,虽然AI技术的应用已经越来越成熟和普遍,算法模型也一定程度上能够对文本的情感进行判断。但是Mr.Tech也要温馨提醒大家,在现实的人际交往中,我们彼此之间还是要真诚沟通、用心交流哦。
未来,个推将不断在自然语言处理、机器视觉等前沿技术领域开展实践,也会持续将其中有价值的内容分享给大家。我们相信技术的飞速发展一定会给我们的生活带来很多有意义、有趣的改变。我们也期待读者朋友们,多多和我们交流自己的前沿实战和趣味实操案例。
收起阅读 »
app端uni.request,requestType无效
将 uni.request换成uni.downloadFile
// #ifdef H5
let blob = new Blob([ArrayBuffer])
this.codeSrc = URL.createObjectURL(blob)
// #endif
// #ifdef APP-PLUS
uni.downloadFile({url:'',success:res=>{
this.codeSrc = res.tempFilePath
}})
// #endif
将 uni.request换成uni.downloadFile
// #ifdef H5
let blob = new Blob([ArrayBuffer])
this.codeSrc = URL.createObjectURL(blob)
// #endif
// #ifdef APP-PLUS
uni.downloadFile({url:'',success:res=>{
this.codeSrc = res.tempFilePath
}})
// #endif

uni-app纯webview项目代码示例,包括返回按钮的处理以及user-agent的更改
这个方法需要nvue,你可以新建项目时,把默认的index.vue改成index.nvue,然后用下面的代码全部覆盖
<script>
export default {
onLoad() {
const url = 'https://tools.qvdd.cn/request-headers' // 改成你自己的链接
const uaAppend = 'your_ua_content' // 改成你自己的ua
plus.navigator.setUserAgent(plus.navigator.getUserAgent() + ' ' + uaAppend)
const sysInfo = uni.getSystemInfoSync()
const styles = { // https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewStyles
width: sysInfo.safeArea.width,
top: sysInfo.statusBarHeight,
bottom: 0,
scalable: true, // 默认不能缩放,在访问PC页面时体验不好
errorPage: '/hybrid/html/error.html', // 网页打不开时打开这个页面
cachemode: 'cacheElseNetwork' // 默认是 default
}
const wv = plus.webview.open(url, 'id', styles)
plus.key.addEventListener('backbutton', ()=>{
wv.canBack((e)=>{
if (e.canBack) {
wv.back()
} else {
uni.showModal({
title: '是否退出App?',
success: (res) => {
if (res.confirm) {
plus.runtime.quit()
}
}
})
}
})
})
}
}
</script>
其中的/hybrid/html/error.html可以自己放个漂亮的404模板,我是简单这样写的:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>error</title>
</head>
<body>
<div style="padding: 15px;">
网页打开失败
</div>
</body>
</html>
pages.json里设置"navigationStyle": "custom",隐藏顶部标题栏
原文出处:https://coding3.com/archives/uniapp-webview.html
这个方法需要nvue,你可以新建项目时,把默认的index.vue改成index.nvue,然后用下面的代码全部覆盖
<script>
export default {
onLoad() {
const url = 'https://tools.qvdd.cn/request-headers' // 改成你自己的链接
const uaAppend = 'your_ua_content' // 改成你自己的ua
plus.navigator.setUserAgent(plus.navigator.getUserAgent() + ' ' + uaAppend)
const sysInfo = uni.getSystemInfoSync()
const styles = { // https://www.html5plus.org/doc/zh_cn/webview.html#plus.webview.WebviewStyles
width: sysInfo.safeArea.width,
top: sysInfo.statusBarHeight,
bottom: 0,
scalable: true, // 默认不能缩放,在访问PC页面时体验不好
errorPage: '/hybrid/html/error.html', // 网页打不开时打开这个页面
cachemode: 'cacheElseNetwork' // 默认是 default
}
const wv = plus.webview.open(url, 'id', styles)
plus.key.addEventListener('backbutton', ()=>{
wv.canBack((e)=>{
if (e.canBack) {
wv.back()
} else {
uni.showModal({
title: '是否退出App?',
success: (res) => {
if (res.confirm) {
plus.runtime.quit()
}
}
})
}
})
})
}
}
</script>
其中的/hybrid/html/error.html可以自己放个漂亮的404模板,我是简单这样写的:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>error</title>
</head>
<body>
<div style="padding: 15px;">
网页打开失败
</div>
</body>
</html>
pages.json里设置"navigationStyle": "custom",隐藏顶部标题栏
原文出处:https://coding3.com/archives/uniapp-webview.html

uni-app开发微信小商店的一些坑
小商店插件版本:1.1.54 基础库版本:2.9.5以上
根据官方文档引入小商店组件,初始化小商店插件,
但是在跳转插件页面时一直报错:has not init plugin
经过一番查找,发现uniapp中的onLaunch方法里的this跟小程序原生组件onLaunch里的this不一致,
在uniapp中需要使用this.$scope来初始化插件
修改后问题解决
小商店插件版本:1.1.54 基础库版本:2.9.5以上
根据官方文档引入小商店组件,初始化小商店插件,
但是在跳转插件页面时一直报错:has not init plugin
经过一番查找,发现uniapp中的onLaunch方法里的this跟小程序原生组件onLaunch里的this不一致,
在uniapp中需要使用this.$scope来初始化插件
修改后问题解决
收起阅读 »
交友软件包含聊天支付,会员系统,前后台管理
交友软件包含聊天支付,会员系统,动态,前后台管理一套出售,有需要的联系QQ728045048
交友软件包含聊天支付,会员系统,动态,前后台管理一套出售,有需要的联系QQ728045048

开源知识付费系统—— 万岳知识付费系统开源WEB+UNIAPP版
系统演示
目录结构
- 前端代码 knowledge_uni_app目录
-
后台代码 knowledge_admin目录
Web版地址
- 教师端地址: https://demo.sdwanyue.com/teacher 账号:13866666666 密码:123456
-
后台地址: https://demo.sdwanyue.com/admin 账号: demo 密码: 123456
项目介绍
万岳知识付费系统打造沉浸式学习体验,提升教学质量,还原真实课堂。知识付费功能包含热门精选、在线直播、付费视频、付费音频、付费阅读等营销功能,实现用户快速裂变。提高用户工作效率和收入是成为知识付费的刚需,可以从海量信息中寻找到适合自身的产品,利用碎片化时间和少许资金就能获得自己需要的信息。
万岳知识付费深刻理解用户诉求,紧盯市场需求。帮助大家低成本高效率体验知识付费平台,以利用互联网让人们生活更美好为使命,精益求精,创新研发,为客户创造更多价值!
- 所有使用到的框架或者组件都是基于开源项目,代码保证100%开源。
-
系统功能通用,无论是个人还是企业都可以利用该系统快速搭建一个属于自己的知识付费系统。
系统前端采用uni-app+socket.io+WebRtc核心技术, 接口采用PhalApi框架配合TP5.1框架ThinkCMF,系统功能如下:
功能展示
开源版使用须知
-
允许用于个人学习、教学案例
-
开源版不适合商用,商用请购买商业版
-
禁止将本项目的代码和资源进行任何形式的出售,产生的一切任何后果责任由侵权者自负
商业合作
- 如果你想使用功能更完善的知识付费系统,请联系QQ客服: 2415408120 获取专业版
- 如果您想基于知识付费系统进行定制开发,我们提供有偿定制服务支持!
- 其他合作模式不限,欢迎来撩!
- 官网地址:http://www.sdwanyue.com
系统演示
目录结构
- 前端代码 knowledge_uni_app目录
-
后台代码 knowledge_admin目录
Web版地址
- 教师端地址: https://demo.sdwanyue.com/teacher 账号:13866666666 密码:123456
-
后台地址: https://demo.sdwanyue.com/admin 账号: demo 密码: 123456
项目介绍
万岳知识付费系统打造沉浸式学习体验,提升教学质量,还原真实课堂。知识付费功能包含热门精选、在线直播、付费视频、付费音频、付费阅读等营销功能,实现用户快速裂变。提高用户工作效率和收入是成为知识付费的刚需,可以从海量信息中寻找到适合自身的产品,利用碎片化时间和少许资金就能获得自己需要的信息。
万岳知识付费深刻理解用户诉求,紧盯市场需求。帮助大家低成本高效率体验知识付费平台,以利用互联网让人们生活更美好为使命,精益求精,创新研发,为客户创造更多价值!
- 所有使用到的框架或者组件都是基于开源项目,代码保证100%开源。
-
系统功能通用,无论是个人还是企业都可以利用该系统快速搭建一个属于自己的知识付费系统。
系统前端采用uni-app+socket.io+WebRtc核心技术, 接口采用PhalApi框架配合TP5.1框架ThinkCMF,系统功能如下:
功能展示
开源版使用须知
-
允许用于个人学习、教学案例
-
开源版不适合商用,商用请购买商业版
-
禁止将本项目的代码和资源进行任何形式的出售,产生的一切任何后果责任由侵权者自负
商业合作
- 如果你想使用功能更完善的知识付费系统,请联系QQ客服: 2415408120 获取专业版
- 如果您想基于知识付费系统进行定制开发,我们提供有偿定制服务支持!
- 其他合作模式不限,欢迎来撩!
- 官网地址:http://www.sdwanyue.com

在横竖屏切换后 页面错乱问题的解决办法
需求:用户来到首页时候是竖屏的,点开详情页横屏展示详细信息,用户点击返回回到首页
问题:从横屏到竖屏或者竖屏到横屏都有可能出现页面错乱的问题,且还容易闪退!
原因:
因为页面使用rpx为单位的;如果用的是px为单位就不会;因为rpx是根据屏幕尺寸来计算的,回到首页时候,页面获取到的屏幕尺寸还是横屏的尺寸;
测试:
home.vue:
onShow(){
uni.getSystemInfo({
success: (res) => {
uni.showModal({
title:"sW:"+res.screenWidth+';sh:'+res.screenHeight+';ww:'+res.windowWidth+';wh:'+res.windowHeight
})
}
})
}
detail.vue:
methods: {
go_home(){
uni.showTabBar();
// #ifdef APP-PLUS
plus.screen.unlockOrientation(); //解除锁定屏幕方向
plus.screen.lockOrientation('portrait-primary');
// #endif
uni.reLaunch({
url:"../index/index"
})
},
首次进入首页弹窗内容如下:
sw:393;sh:857;ww:393;wh:807
进入详情页 点击首页后弹窗内容如下
sw:873;sh:393;ww:873;wh:343
很明显系统提前获取了错误的页面尺寸;
解决办法就呼之欲出了,我只需要保证在首页时候拿到正常的屏幕尺寸:
detail.vue
methods: {
go_home_real(){
uni.getSystemInfo({
success: (res) => {
if(res.screenWidth <= res.screenWidth) {
clearInterval(this.dingshiqi)
uni.reLaunch({
url:"../index/index"
})
}
}
})
},
go_home(){
uni.showTabBar();
// #ifdef APP-PLUS
plus.screen.unlockOrientation(); //解除锁定屏幕方向
plus.screen.lockOrientation('portrait-primary');
// #endif
this.dingshiqi = setInterval(this.go_home_real,300);//每隔0.3秒检测一次屏幕是否已经竖过来了 如果是就跳转
},
}
跳转时候做个检测
屏幕尺寸改变时候会触发onResize 也可以在这上面作文章;
data() {
return {
portraitUrl:'',
landScapeUrl:'',
}
},
onResize() {
let _this = this
uni.getSystemInfo({
success: function(res) {
if (res.windowWidth > res.windowHeight) {
if(_this.landScapeUrl) {
uni.reLaunch({
url:_this.landScapeUrl
})
}
} else {
if(_this.portraitUrl) {
uni.reLaunch({
url:_this.portraitUrl
})
}
}
}
})
},
methods: {
go_home(){
uni.showTabBar();
this.portraitUrl = '../index/index';
console.log("go_home")
// #ifdef APP-PLUS
plus.screen.unlockOrientation(); //解除锁定屏幕方向
plus.screen.lockOrientation('portrait-primary');
// #endif
console.log("go_home_end")
},
}
需求:用户来到首页时候是竖屏的,点开详情页横屏展示详细信息,用户点击返回回到首页
问题:从横屏到竖屏或者竖屏到横屏都有可能出现页面错乱的问题,且还容易闪退!
原因:
因为页面使用rpx为单位的;如果用的是px为单位就不会;因为rpx是根据屏幕尺寸来计算的,回到首页时候,页面获取到的屏幕尺寸还是横屏的尺寸;
测试:
home.vue:
onShow(){
uni.getSystemInfo({
success: (res) => {
uni.showModal({
title:"sW:"+res.screenWidth+';sh:'+res.screenHeight+';ww:'+res.windowWidth+';wh:'+res.windowHeight
})
}
})
}
detail.vue:
methods: {
go_home(){
uni.showTabBar();
// #ifdef APP-PLUS
plus.screen.unlockOrientation(); //解除锁定屏幕方向
plus.screen.lockOrientation('portrait-primary');
// #endif
uni.reLaunch({
url:"../index/index"
})
},
首次进入首页弹窗内容如下:
sw:393;sh:857;ww:393;wh:807
进入详情页 点击首页后弹窗内容如下
sw:873;sh:393;ww:873;wh:343
很明显系统提前获取了错误的页面尺寸;
解决办法就呼之欲出了,我只需要保证在首页时候拿到正常的屏幕尺寸:
detail.vue
methods: {
go_home_real(){
uni.getSystemInfo({
success: (res) => {
if(res.screenWidth <= res.screenWidth) {
clearInterval(this.dingshiqi)
uni.reLaunch({
url:"../index/index"
})
}
}
})
},
go_home(){
uni.showTabBar();
// #ifdef APP-PLUS
plus.screen.unlockOrientation(); //解除锁定屏幕方向
plus.screen.lockOrientation('portrait-primary');
// #endif
this.dingshiqi = setInterval(this.go_home_real,300);//每隔0.3秒检测一次屏幕是否已经竖过来了 如果是就跳转
},
}
跳转时候做个检测
屏幕尺寸改变时候会触发onResize 也可以在这上面作文章;
data() {
return {
portraitUrl:'',
landScapeUrl:'',
}
},
onResize() {
let _this = this
uni.getSystemInfo({
success: function(res) {
if (res.windowWidth > res.windowHeight) {
if(_this.landScapeUrl) {
uni.reLaunch({
url:_this.landScapeUrl
})
}
} else {
if(_this.portraitUrl) {
uni.reLaunch({
url:_this.portraitUrl
})
}
}
}
})
},
methods: {
go_home(){
uni.showTabBar();
this.portraitUrl = '../index/index';
console.log("go_home")
// #ifdef APP-PLUS
plus.screen.unlockOrientation(); //解除锁定屏幕方向
plus.screen.lockOrientation('portrait-primary');
// #endif
console.log("go_home_end")
},
}
收起阅读 »

C++防止头文件被重复引入的3种方法!
在之前我们详细介绍了 C 语言中如何使用宏定义(#ifndef / #define / #endif)来有效避免头文件被重复 #include,此方式在 C++ 多文件编程中也很常用。
举个例子,如下是一个 C++ 项目,其内部含有 school.h 和 student.h 这 2 个头文件以及 main.cpp 源文件,其各自包含的代码为:
//student.h
class Student {
//......
};
//school.h
include "student.h"
class School {
//......
private:
Student stu[50];
};
//main.cpp
include "student.h"
include "school.h"
int main() {
//......
return 0;
}
运行此项目会发现,编译器报“Student 类型重定义”错误。这是因为在 school.h 文件中已经 #include 了一次 "student.h",而在 main.cpp 主程序又同时 #include 了 "school.h" 和 "student.h",即 Student 类的定义被引入了 2 次,C++不允许同一个类被重复定义。
有小伙伴可能想到,既然 School.h 文件中已经引入了 Student 类,那去掉 main.cpp 主程序引入的 student.h 文件不就可以了吗?这样确实可以避免重复引入 Student 类,但此方式并不适用于所有“重复引入”的场景。
C++ 多文件编程中,处理“多次 #include 导致重复引入”问题的方式有以下 3 种。
————————
1) 使用宏定义避免重复引入
在实际多文件开发中,我们往往使用如下的宏定义来避免发生重复引入:
ifndef _NAME_H
define _NAME_H
//头文件内容
endif
其中,_NAME_H 是宏的名称。需要注意的是,这里设置的宏名必须是独一无二的,不要和项目中其他宏的名称相同。
当程序中第一次 #include 该文件时,由于 _NAME_H 尚未定义,所以会定义 _NAME_H 并执行“头文件内容”部分的代码;当发生多次 #include 时,因为前面已经定义了 _NAME_H,所以不会再重复执行“头文件内容”部分的代码。
也就是说,我们可以将前面项目中的 student.h 文件做如下修改:
ifndef _STUDENT_H
define _STUDENT_H
class Student {
//......
};
endif
虽然该项目 main.cpp 文件中仍 #include 了 2 次 "student.h",但鉴于 _STUDENT_H 宏只能定义一次,所以 Student 类也仅会定义一次。再次执行该项目会发现,其可以正常执行。
2) 使用#pragma once避免重复引入
除了前面第一种最常用的方式之外,还可以使用 #pragma one 指令,将其附加到指定文件的最开头位置,则该文件就只会被 #include 一次。
我们知道,#ifndef 是通过定义独一无二的宏来避免重复引入的,这意味着每次引入头文件都要进行识别,所以效率不高。但考虑到 C 和 C++ 都支持宏定义,所以项目中使用 #ifndef 规避可能出现的“头文件重复引入”问题,不会影响项目的可移植性。
和 ifndef 相比,#pragma once 不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,所以效率很高。
但值得一提的是,并不是每个版本的编译器都能识别 #pragma once 指令,一些较老版本的编译器就不支持该指令(执行时会发出警告,但编译会继续进行),即 #pragma once 指令的兼容性不是很好。
目前,几乎所有常见的编译器都支持 #pragma once 指令,甚至于 Visual Studio 2017 新建头文件时就会自带该指令。可以这么说,在 C/C++ 中,#pragma once 是一个非标准但却逐渐被很多编译器支持的指令。
除此之外,#pragma once 只能作用于某个具体的文件,而无法向 #ifndef 那样仅作用于指定的一段代码。
这里仍以前面的 "student.h" 文件为例,将其内容修改为:
pragma once
class Student {
//......
};
再次运行项目,同样可以正常执行。
3) 使用_Pragma操作符
C99 标准中新增加了一个和 #pragma 指令类似的 _Pragma 操作符,其可以看做是 #pragma 的增强版,不仅可以实现 #pragma 所有的功能,更重要的是,_Pragma 还能和宏搭配使用。
有关 _Pragma 操作符更多的功能和用法,本节不做详细讲解,这里仅介绍如何用 _Pragma 操作符避免头文件重复引入。
当处理头文件重复引入问题时,可以将如下语句添加到相应文件的开头:
_Pragma("once")
比如,将该语句添加到前面项目中 student.h 文件中的开头位置,再次执行项目,其可以正常执行。
事实上,无论是 C 语言还是 C++,为防止用户重复引入系统库文件,几乎所有库文件中都采用了以上 3 种结构中的一种,这也是为什么重复引入系统库文件编译器也不会报错的原因。
总结
本节介绍了 3 种避免头文件被重复引入的方法,其中 #pragma once 和 _Pragma("once") 可算作一类,其特点是编译效率高,但可移植性差(编译器不支持,会发出警告,但不会中断程序的执行);而 #ifndef 的特点是可移植性高,编译效率差。读者可根据实际情况,挑选最符合实际需要的解决方案。
除非对项目的编译效率有严格的要求,强烈推荐读者选用第一种解决方案,即采用 #ifndef / #define / #endif 组合解决头文件被重复引入。
另外在某些场景中,考虑到编译效率和可移植性,#pragma once 和 #ifndef 经常被结合使用来避免头文件被重复引入。比如说:
pragma once
ifndef _STUDENT_H
define _STUDENT_H
class Student {
//......
};
endif
当编译器可以识别 #pragma once 时,则整个文件仅被编译一次;反之,即便编译器不识别 #pragma once 指令,此时仍有 #ifndef 在发挥作用。
在之前我们详细介绍了 C 语言中如何使用宏定义(#ifndef / #define / #endif)来有效避免头文件被重复 #include,此方式在 C++ 多文件编程中也很常用。
举个例子,如下是一个 C++ 项目,其内部含有 school.h 和 student.h 这 2 个头文件以及 main.cpp 源文件,其各自包含的代码为:
//student.h
class Student {
//......
};
//school.h
include "student.h"
class School {
//......
private:
Student stu[50];
};
//main.cpp
include "student.h"
include "school.h"
int main() {
//......
return 0;
}
运行此项目会发现,编译器报“Student 类型重定义”错误。这是因为在 school.h 文件中已经 #include 了一次 "student.h",而在 main.cpp 主程序又同时 #include 了 "school.h" 和 "student.h",即 Student 类的定义被引入了 2 次,C++不允许同一个类被重复定义。
有小伙伴可能想到,既然 School.h 文件中已经引入了 Student 类,那去掉 main.cpp 主程序引入的 student.h 文件不就可以了吗?这样确实可以避免重复引入 Student 类,但此方式并不适用于所有“重复引入”的场景。
C++ 多文件编程中,处理“多次 #include 导致重复引入”问题的方式有以下 3 种。
————————
1) 使用宏定义避免重复引入
在实际多文件开发中,我们往往使用如下的宏定义来避免发生重复引入:
ifndef _NAME_H
define _NAME_H
//头文件内容
endif
其中,_NAME_H 是宏的名称。需要注意的是,这里设置的宏名必须是独一无二的,不要和项目中其他宏的名称相同。
当程序中第一次 #include 该文件时,由于 _NAME_H 尚未定义,所以会定义 _NAME_H 并执行“头文件内容”部分的代码;当发生多次 #include 时,因为前面已经定义了 _NAME_H,所以不会再重复执行“头文件内容”部分的代码。
也就是说,我们可以将前面项目中的 student.h 文件做如下修改:
ifndef _STUDENT_H
define _STUDENT_H
class Student {
//......
};
endif
虽然该项目 main.cpp 文件中仍 #include 了 2 次 "student.h",但鉴于 _STUDENT_H 宏只能定义一次,所以 Student 类也仅会定义一次。再次执行该项目会发现,其可以正常执行。
2) 使用#pragma once避免重复引入
除了前面第一种最常用的方式之外,还可以使用 #pragma one 指令,将其附加到指定文件的最开头位置,则该文件就只会被 #include 一次。
我们知道,#ifndef 是通过定义独一无二的宏来避免重复引入的,这意味着每次引入头文件都要进行识别,所以效率不高。但考虑到 C 和 C++ 都支持宏定义,所以项目中使用 #ifndef 规避可能出现的“头文件重复引入”问题,不会影响项目的可移植性。
和 ifndef 相比,#pragma once 不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,所以效率很高。
但值得一提的是,并不是每个版本的编译器都能识别 #pragma once 指令,一些较老版本的编译器就不支持该指令(执行时会发出警告,但编译会继续进行),即 #pragma once 指令的兼容性不是很好。
目前,几乎所有常见的编译器都支持 #pragma once 指令,甚至于 Visual Studio 2017 新建头文件时就会自带该指令。可以这么说,在 C/C++ 中,#pragma once 是一个非标准但却逐渐被很多编译器支持的指令。
除此之外,#pragma once 只能作用于某个具体的文件,而无法向 #ifndef 那样仅作用于指定的一段代码。
这里仍以前面的 "student.h" 文件为例,将其内容修改为:
pragma once
class Student {
//......
};
再次运行项目,同样可以正常执行。
3) 使用_Pragma操作符
C99 标准中新增加了一个和 #pragma 指令类似的 _Pragma 操作符,其可以看做是 #pragma 的增强版,不仅可以实现 #pragma 所有的功能,更重要的是,_Pragma 还能和宏搭配使用。
有关 _Pragma 操作符更多的功能和用法,本节不做详细讲解,这里仅介绍如何用 _Pragma 操作符避免头文件重复引入。
当处理头文件重复引入问题时,可以将如下语句添加到相应文件的开头:
_Pragma("once")
比如,将该语句添加到前面项目中 student.h 文件中的开头位置,再次执行项目,其可以正常执行。
事实上,无论是 C 语言还是 C++,为防止用户重复引入系统库文件,几乎所有库文件中都采用了以上 3 种结构中的一种,这也是为什么重复引入系统库文件编译器也不会报错的原因。
总结
本节介绍了 3 种避免头文件被重复引入的方法,其中 #pragma once 和 _Pragma("once") 可算作一类,其特点是编译效率高,但可移植性差(编译器不支持,会发出警告,但不会中断程序的执行);而 #ifndef 的特点是可移植性高,编译效率差。读者可根据实际情况,挑选最符合实际需要的解决方案。
除非对项目的编译效率有严格的要求,强烈推荐读者选用第一种解决方案,即采用 #ifndef / #define / #endif 组合解决头文件被重复引入。
另外在某些场景中,考虑到编译效率和可移植性,#pragma once 和 #ifndef 经常被结合使用来避免头文件被重复引入。比如说:
pragma once
ifndef _STUDENT_H
define _STUDENT_H
class Student {
//......
};
endif
当编译器可以识别 #pragma once 时,则整个文件仅被编译一次;反之,即便编译器不识别 #pragma once 指令,此时仍有 #ifndef 在发挥作用。
收起阅读 »
调用安卓系统分享功能,分享非媒体文件到微信
在我的 app 中需要分享 json 文件,看了好多文章大多数都是讲如何分享媒体文件的,比如图片音频什么的,其实调用系统分享功能也是比较简单的,但是分享到微信就会遇到找不到资源的问题,要使用 FileProvider 方式的话就必须要通过原生开发辅助才行,经过无数次的尝试发现微信要获取文件必须要 content:// 开头的 Uri 才行,最后使用了参考文档中的第二种方法 scanFile,目前只是为了达到目的所以代码比较粗糙,欢迎大佬指正
大概思路是这样的:
- 把要分享的文件从 app 私有目录复制到公共目录,这里选了 Download 目录
- 调用 MediaScannerConnection.scanFile 方法,在 onScanCompleted 回调函数中获取正确的 Uri 标识
- 再调用系统分享功能即可
// #ifdef APP-PLUS
plus.android.importClass('android.content.ContentResolver')
const MainActivity = plus.android.runtimeMainActivity(),
Intent = plus.android.importClass('android.content.Intent'),
Uri = plus.android.importClass('android.net.Uri'),
InputStreamReader = plus.android.importClass('java.io.InputStreamReader'),
OutputStreamWriter = plus.android.importClass('java.io.OutputStreamWriter'),
BufferedReader = plus.android.importClass('java.io.BufferedReader'),
BufferedWriter = plus.android.importClass('java.io.BufferedWriter'),
File = plus.android.importClass('java.io.File'),
FileInputStream = plus.android.importClass('java.io.FileInputStream'),
FileOutputStream = plus.android.importClass('java.io.FileOutputStream'),
MediaScannerConnection = plus.android.importClass('android.media.MediaScannerConnection'),
Environment = plus.android.importClass('android.os.Environment'),
SETTINGS_FILENAME = 'settings.json'
// #endif
function share_file() {
// #ifndef APP-PLUS
return false
// #endif
// #ifdef APP-PLUS
plus.io.requestFileSystem(plus.io.PUBLIC_DOCUMENTS, fs => {
fs.root.getFile(SETTINGS_FILENAME, {}, file_entry => {
const listener = new plus.android.implements('android.media.MediaScannerConnection$OnScanCompletedListener', {
'onScanCompleted': (path, uri) => {
console.log('uri: ' + uri.toString())
const intent = new Intent()
.setAction(Intent.ACTION_SEND)
.setType('text/*')
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Intent.EXTRA_TEXT, 'Share a file')
.putExtra(Intent.EXTRA_STREAM, uri) // Uri.parse(file_entry.fullPath))
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
MainActivity.startActivity(Intent.createChooser(intent, 'Share a file'))
// MainActivity.startActivity(intent)
new File(path).deleteOnExit()
}
})
copy_file_to_public_directory(file_entry.fullPath)
MediaScannerConnection.scanFile(MainActivity, [get_public_directory() + '/' + SETTINGS_FILENAME], null, listener)
return true
}, error => {
uni.showToast({
title: '文件不存在',
icon: 'none',
duration: 2000
})
})
})
// #endif
}
function copy_file_to_public_directory(file_path) {
// #ifndef APP-PLUS
return false
// #endif
// #ifdef APP-PLUS
const input_stream = new FileInputStream(file_path),
output_Stream = new FileOutputStream(get_public_directory() + '/' + SETTINGS_FILENAME),
file_reader = new BufferedReader(new InputStreamReader(input_stream)),
file_writer = new BufferedWriter(new OutputStreamWriter(output_Stream)),
content = file_reader.readLine()
file_writer.write(content, 0, content.length)
file_writer.flush()
file_writer.close()
file_reader.close()
output_Stream.close()
input_stream.close()
console.log('copy file success')
return true
// #endif
}
function get_public_directory() {
// '/storage/emulated/0/Download/temp'
// #ifndef APP-PLUS
return false
// #endif
// #ifdef APP-PLUS
return new File(Environment.getExternalStorageDirectory(), 'Download/temp').getAbsolutePath()
// #endif
}
参考文档:
在我的 app 中需要分享 json 文件,看了好多文章大多数都是讲如何分享媒体文件的,比如图片音频什么的,其实调用系统分享功能也是比较简单的,但是分享到微信就会遇到找不到资源的问题,要使用 FileProvider 方式的话就必须要通过原生开发辅助才行,经过无数次的尝试发现微信要获取文件必须要 content:// 开头的 Uri 才行,最后使用了参考文档中的第二种方法 scanFile,目前只是为了达到目的所以代码比较粗糙,欢迎大佬指正
大概思路是这样的:
- 把要分享的文件从 app 私有目录复制到公共目录,这里选了 Download 目录
- 调用 MediaScannerConnection.scanFile 方法,在 onScanCompleted 回调函数中获取正确的 Uri 标识
- 再调用系统分享功能即可
// #ifdef APP-PLUS
plus.android.importClass('android.content.ContentResolver')
const MainActivity = plus.android.runtimeMainActivity(),
Intent = plus.android.importClass('android.content.Intent'),
Uri = plus.android.importClass('android.net.Uri'),
InputStreamReader = plus.android.importClass('java.io.InputStreamReader'),
OutputStreamWriter = plus.android.importClass('java.io.OutputStreamWriter'),
BufferedReader = plus.android.importClass('java.io.BufferedReader'),
BufferedWriter = plus.android.importClass('java.io.BufferedWriter'),
File = plus.android.importClass('java.io.File'),
FileInputStream = plus.android.importClass('java.io.FileInputStream'),
FileOutputStream = plus.android.importClass('java.io.FileOutputStream'),
MediaScannerConnection = plus.android.importClass('android.media.MediaScannerConnection'),
Environment = plus.android.importClass('android.os.Environment'),
SETTINGS_FILENAME = 'settings.json'
// #endif
function share_file() {
// #ifndef APP-PLUS
return false
// #endif
// #ifdef APP-PLUS
plus.io.requestFileSystem(plus.io.PUBLIC_DOCUMENTS, fs => {
fs.root.getFile(SETTINGS_FILENAME, {}, file_entry => {
const listener = new plus.android.implements('android.media.MediaScannerConnection$OnScanCompletedListener', {
'onScanCompleted': (path, uri) => {
console.log('uri: ' + uri.toString())
const intent = new Intent()
.setAction(Intent.ACTION_SEND)
.setType('text/*')
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.putExtra(Intent.EXTRA_TEXT, 'Share a file')
.putExtra(Intent.EXTRA_STREAM, uri) // Uri.parse(file_entry.fullPath))
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
MainActivity.startActivity(Intent.createChooser(intent, 'Share a file'))
// MainActivity.startActivity(intent)
new File(path).deleteOnExit()
}
})
copy_file_to_public_directory(file_entry.fullPath)
MediaScannerConnection.scanFile(MainActivity, [get_public_directory() + '/' + SETTINGS_FILENAME], null, listener)
return true
}, error => {
uni.showToast({
title: '文件不存在',
icon: 'none',
duration: 2000
})
})
})
// #endif
}
function copy_file_to_public_directory(file_path) {
// #ifndef APP-PLUS
return false
// #endif
// #ifdef APP-PLUS
const input_stream = new FileInputStream(file_path),
output_Stream = new FileOutputStream(get_public_directory() + '/' + SETTINGS_FILENAME),
file_reader = new BufferedReader(new InputStreamReader(input_stream)),
file_writer = new BufferedWriter(new OutputStreamWriter(output_Stream)),
content = file_reader.readLine()
file_writer.write(content, 0, content.length)
file_writer.flush()
file_writer.close()
file_reader.close()
output_Stream.close()
input_stream.close()
console.log('copy file success')
return true
// #endif
}
function get_public_directory() {
// '/storage/emulated/0/Download/temp'
// #ifndef APP-PLUS
return false
// #endif
// #ifdef APP-PLUS
return new File(Environment.getExternalStorageDirectory(), 'Download/temp').getAbsolutePath()
// #endif
}
参考文档:
收起阅读 »