分类

类型:
不限 游戏开发 计算机程序开发 Android开发 网站开发 笔记总结 其他
评分:
不限 10 9 8 7 6 5 4 3 2 1
原创:
不限
年份:
不限 2018 2019 2020

技术文章列表

  • Android Studio使用POI读取及修改Word文档(.docx格式)

    一、说明上一篇文章(Android Studio使用POI读取及修改Word文档(.doc格式))使用poi对.doc格式的word文档进行了读取和更改,但很多情况下还需要在word文档中插入图片,这时就需要对.docx格式的word进行操作了。
    二、实现过程2.1 制作文书文书在源代码中可以直接看到,简单说明一下:文书有普通字段、表格、特定位置的图片,又在页眉页脚中加了普通字段和表格,基本满足对于word操作的所有情况。

    2.2 导包还是上篇中poi-3.9压缩资源包中的jar包,对.docx格式文档的操作用到XWPFDocument方法,使用到所有ooxml相关的jar包。

    2.3 build配置这次的build配置有点特殊,特别拿出来截图一下。就像上一篇说的一样,apache的很多配置在安卓是跑不通的,这次导包后,你会遇到方法过多,文件重复,基于jdk1.6以上版本的变异保存等一系列问题,可以按照下面的方法处理。当然不同的android studio版本可能也会有不同的处理方法,可以百度一下。

    权限还是储存权限,直接上代码吧,注解的也很详细。
    2.4 实现源码public class MainActivity extends AppCompatActivity { //创建生成的文件地址 private static final String newPath = "/storage/emulated/0/test.docx"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 0); } Button go = (Button) findViewById(R.id.go); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { initData(); } }); } private void initData() { Map<String, Object> map = new HashMap<String, Object>(); map.put("$TITLE$", "标题");//$TITLE$只是个标识符,你也可以用${},[]等等 map.put("$TXT1$", "表格第一行"); map.put("$CONTENT1$", "第一行内容"); map.put("$TXT2$", "表格第二行"); map.put("$CONTENT2$", "第二行内容"); map.put("$CONTENT3$", "页脚中的内容"); map.put("$TXT4$", "页脚表格第一行"); map.put("$CONTENT4$", "页脚第一行内容"); map.put("$TXT5$", "页脚表格第二行"); map.put("$CONTENT5$", "页脚第二行内容"); try { //读取示例文书 InputStream is = getAssets().open("test.docx"); //自定义的XWPFDocument,解决官方版图片不显示问题 CustomXWPFDocument document = new CustomXWPFDocument(is); //读取段落(一般段落,页眉页脚没办法读取) List<XWPFParagraph> listParagraphs = document.getParagraphs(); processParagraphs(listParagraphs, map); //读取页脚 List<XWPFFooter> footerList = document.getFooterList(); processParagraph(footerList, map); //处理表格 Iterator<XWPFTable> it = document.getTablesIterator(); while (it.hasNext()) {//循环操作表格 XWPFTable table = it.next(); List<XWPFTableRow> rows = table.getRows(); for (XWPFTableRow row : rows) {//取得表格的行 List<XWPFTableCell> cells = row.getTableCells(); for (XWPFTableCell cell : cells) {//取得单元格 if ("$IMG$".equals(cell.getText())) { //直接插入图片会在文档的最底端,所以要插在固定位置,要把图片放在表格里 //所以使用判断单元格,并清除单元格放置图片的方式来实现图片定位 cell.removeParagraph(0); XWPFParagraph pargraph = cell.addParagraph(); document.addPictureData(getAssets().open("1.png"), XWPFDocument.PICTURE_TYPE_PNG); document.createPicture(document.getAllPictures().size() - 1, 120, 120, pargraph); } List<XWPFParagraph> paragraphListTable = cell.getParagraphs(); processParagraphs(paragraphListTable, map); } } } FileOutputStream fopts = new FileOutputStream(newPath); document.write(fopts); if (fopts != null) { fopts.close(); } } catch (Exception e) { e.printStackTrace(); } } //处理页脚中的段落,其实就是用方法读取了下页脚中的内容,然后也会当做一般段落处理 private void processParagraph(List<XWPFFooter> footerList, Map<String, Object> map) { if (footerList != null && footerList.size() > 0) { for (XWPFFooter footer : footerList) { //读取一般段落 List<XWPFParagraph> paragraphs = footer.getParagraphs(); processParagraphs(paragraphs, map); //处理表格 List<XWPFTable> tables = footer.getTables(); for (int i = 0; i < tables.size(); i++) { XWPFTable xwpfTable = tables.get(i); List<XWPFTableRow> rows = xwpfTable.getRows(); for (XWPFTableRow row : rows) {//取得表格的行 List<XWPFTableCell> cells = row.getTableCells(); for (XWPFTableCell cell : cells) {//取得单元格 List<XWPFParagraph> paragraphListTable = cell.getParagraphs(); processParagraphs(paragraphListTable, map); } } } } } } //处理段落 public static void processParagraphs(List<XWPFParagraph> paragraphList, Map<String, Object> param) { if (paragraphList != null && paragraphList.size() > 0) { for (XWPFParagraph paragraph : paragraphList) { List<XWPFRun> runs = paragraph.getRuns(); for (XWPFRun run : runs) { String text = run.getText(0); if (text != null) { boolean isSetText = false; for (Map.Entry<String, Object> entry : param.entrySet()) { String key = entry.getKey(); if (text.indexOf(key) != -1) { isSetText = true; Object value = entry.getValue(); if (value instanceof String) {//文本替换 text = text.replace(key, value.toString()); } } } if (isSetText) { run.setText(text, 0); } } } } } }}
    2.5 效果展示
    本文转载自:https://blog.csdn.net/qq_21972583/article/details/82740281
    2 留言 2019-10-08 13:02:23 奖励16点积分
  • Android Studio使用POI读取及修改Word文档(.doc格式)

    一、前言如果你可爱的项目经理要求安卓端的你来操作word实现各种功能,不要犹豫,直接动之以情晓之以理,因为这本来就是java的poi,安卓虽然源自java,但对于java的很多东西是不支持的,已有的各种jar包也不方便更改,各种报错会搞的你脑阔疼。所以编辑word文档这种事让后台来做要比安卓来做简单的多,但如果实在避免不了,接着,给你代码。
    二、说明本篇不支持word2007版,只支持2003版,也就是只支持.doc格式,不支持.docx格式(想要支持.docx格式请参考“Android Studio使用POI读取及修改Word文档(.docx格式)”)。.doc格式的word文档是不支持图片插入的,因为.doc格式和.docx格式有很大的区别,用到的jar包和方法也不同,如果需要插入图片,请查看下篇对.docx格式word文档的处理。
    三、实现过程3.1 制作文书制作.doc格式的文档,然后导入项目:

    文章展示的文档内容如下:

    3.2 导包用到java poi-3.9中的两个包,完整压缩包点这里下载。然后复制用来编辑.doc文档的两个依赖包,导入libs目录。

    如果提示报错,是因为jar中有重复文件,请看build配置。

    加入外部存储读写权限,以后还需要自行申请,代码中有体现:
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /><uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    3.3 实现代码读写过程直接上代码了,一看就懂:
    Button go;//生成文件的所在的地址private static final String newPath = "/storage/emulated/0/hwpfdocument.doc";@Overrideprotected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); go = (Button) findViewById(R.id.go); go.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { readWord(); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { ActivityCompat.requestPermissions(this, new String[]{ Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, 0); }}private void readWord() { try { //从assets读取我们的Word模板 InputStream is = getAssets().open("hwpfdocument.doc"); //创建生成的文件路径 File newFile = new File(newPath); //需要修改的字段放入map中 Map<String, String> map = new HashMap<String, String>(); map.put("${TITLE}", "标题"); map.put("${TXT}", "表格第一行"); map.put("${CONTENT}", "内容"); writeDoc(is, newFile, map); } catch (IOException e) { e.printStackTrace(); }}private void writeDoc(InputStream is, File newFile, Map<String, String> map) { try { //使用poi的HWPFDocument方法 HWPFDocument hdt = new HWPFDocument(is); //Range读取word文本内容 Range range = hdt.getOverallRange(); //replaceText替换文本内容 for (Map.Entry<String, String> entry : map.entrySet()) { range.replaceText(entry.getKey(), entry.getValue()); } ByteArrayOutputStream ostream = new ByteArrayOutputStream(); FileOutputStream out = new FileOutputStream(newFile, true); hdt.write(ostream); //输出字节流 out.write(ostream.toByteArray()); out.close(); ostream.close(); Toast.makeText(this, "文书已生成", Toast.LENGTH_SHORT).show(); } catch (IOException e) { e.printStackTrace(); }}
    3.4 效果展示
    附录关于Apache POI - HWPF和XWPF的说明可以参考这里。
    关于HWPF的API可以参考这里。左上角选择org.apache.poi.hwpf,然后左下角选择hwpfdocument即可。
    本文转载自:https://blog.csdn.net/qq_21972583/article/details/82385940
    1 留言 2019-10-06 11:36:29 奖励15点积分
  • 基于Skin++库实现的换肤功能

    背景之前自己经常使用MFC来开发一些界面程序,这些程序大都是自己练手用的。但,也会有极个别是帮别人开发,给别人使用。当你辛苦做出来的作品拿出去给别人用的时候,你总想让自己的作品给人留下深刻印象,无论是从功能,还是用程序界面上。
    对于,我们使用 VC6.0 或者 VS2008、VS2010等以上版本开发出来的界面程序,界面通常都是千篇一律,非常质朴的。所以,自己就像用最简单的方式去修改下界面,因为界面虽说重要,但也并不是说不可或缺的,所以自己不想花费太多的精力在界面上。
    后来,经过搜索查找,还真让我找到一个不错的、方便易用的界面修改方法,即使用 Skin++界面库 来实现。Skin++ 属于第二代的外挂式的界面库,提供了Skin Builder 工具将所有控件的资源全部设计成为一个独立的文件,便于在应用程序外部独立地进行增删改操作,采用Hook与子类化技术来实现应用程序的自动换肤。
    现在,本文就基于 Skin++界面库 实现给 VC 或 VS 开发的界面程序更换皮肤。把实现的过程整理成文档,分享给大家。
    实现过程程序要使用到的 Skin++库 文件包括:SkinPPWTL.h、SkinPPWTL.lib、skinppwtl.dll 以及 48个 .ssk 的皮肤文件。
    首先,我们需要把 Skin++ 库文件加载到程序里,做法如下:

    把 SkinPPWTL.h 头文件以及 SkinPPWTL.lib 库文件拷贝到我们的项目工程目录下面,然后在程序声明头文件并加载库文件:
    #include <afxcmn.h>#include <Windows.h>#include "skin\\SkinPPWTL.h"#pragma comment(lib, "skin\\SkinPPWTL.lib")
    然后,我们编译下程序,看看有没有错误提示。若出现类似这样的错误提示: “… _CRT_SECURE_NO_WARNINGS …”,则在 项目属性 —> C/C++ —> 预处理器 —> 预处理器定义 中添加 “__CRT_SECURE_NO_WARNINGS” 这个预定义即可。

    经过上面两步操作,就可以正确地把 Skin++ 库文件加载到程序中了。
    接下来,我们就直接调用 skinppLoadSkin 函数加载 .ssk 格式的皮肤库就可以了。但是要注意调用 skinppLoadSkin 函数的地方,一定要在界面实现出来之前的初始化操作里就开始调用,不能再界面显示出来后调用,否则会出问题。对于 MFC 程序和 Windows应用程序,它们调用 skinppLoadSkin 函数加载界面库文件的地方可以是:

    对于 MFC 程序,可以是在 CxxxAPP::InitInstance() 初始化函数的开头调用,也可以是在 CxxxDlg::OnInitDialog() 主窗口类初始化函数里调用。
    对于 Windows 应用程序,可以是在 WinMain 函数的开头调用,也可以是在窗口消息处理过程函数中的 WM_INITDIALOG 消息中加载。

    调用 skinppLoadSkin 函数加载界面库文件的代码如下所示:
    // 加载皮肤::skinppLoadSkin("skins\\XP-Home.ssk");
    要更换皮肤,只需要更改上面加载的 .ssk 库文件就好,本文有 48 个 .ssk 文件,所以,可以实现 48 种换肤。
    最后,我们编译链接生成可执行文件,在运行程序之前,只需要按照上面库文件 .ssk 的路径放置库文件,同时还需要把 skinppwtl.dll 动态链接库放在和可执行程序同一目录下,就可以正常运行程序了。
    程序测试按照上面的方法,我们分别创建了一个 MFC 程序和 Windows应用程序 来进行测试,程序成功更换皮肤。


    总结对于 Skin++界面库使用比较简单,主要在窗口初始化的时候,就对界面库初始化,并加载界面库就好,剩下的,不需要我们去理会,我们只需要正常开发我们的程序功能就好。
    3 留言 2018-11-29 09:23:21 奖励10点积分
  • 基于python构建搜索引擎系列——(四)检索模型 精华

    构建好倒排索引之后,就可以开始检索了。
    检索模型有很多,比如向量空间模型、概率模型、语言模型等。其中最有名的、检索效果最好的是基于概率的BM25模型。
    给定一个查询Q和一篇文档d,d对Q的BM25得分公式为:

    公式中变量含义如下:

    qtf:查询中的词频
    tf:文档中的词频
    ld:文档长度
    avg_l:平均文档长度
    N:文档数量
    df:文档频率
    b,k1,k3:可调参数

    这个公式看起来很复杂,我们把它分解一下,其实很容易理解。第一个公式是外部公式,一个查询Q可能包含多个词项,比如“苹果手机”就包含“苹果”和“手机”两个词项,我们需要分别计算“苹果”和“手机”对某个文档d的贡献分数w(t,d),然后将他们加起来就是整个文档d相对于查询Q的得分。
    第二个公式就是计算某个词项t在文档d中的得分,它包括三个部分。第一个部分是词项t在查询Q中的得分,比如查询“中国人说中国话”中“中国”出现了两次,此时qtf=2,说明这个查询希望找到的文档和“中国”更相关,“中国”的权重应该更大,但是通常情况下,查询Q都很短,而且不太可能包含相同的词项,所以这个因子是一个常数,我们在实现的时候可以忽略。
    第二部分类似于TFIDF模型中的TF项。也就是说某个词项t在文档d中出现次数越多,则t越重要,但是文档长度越长,tf也倾向于变大,所以使用文档长度除以平均长度ld/avg_l起到某种归一化的效果,k1和b是可调参数。
    第三部分类似于TFIDF模型中的IDF项。也就是说虽然“的”、“地”、“得”等停用词在某文档d中出现的次数很多,但是他们在很多文档中都出现过,所以这些词对d的贡献分并不高,接近于0;反而那些很稀有的词如”糖尿病“能够很好的区分不同文档,这些词对文档的贡献分应该较高。
    所以根据BM25公式,我们可以很快计算出不同文档t对查询Q的得分情况,然后按得分高低排序给出结果。
    下面是给定一个查询句子sentence,根据BM25公式给出文档排名的函数:
    def result_by_BM25(self, sentence): seg_list = jieba.lcut(sentence, cut_all=False) n, cleaned_dict = self.clean_list(seg_list) BM25_scores = {} for term in cleaned_dict.keys(): r = self.fetch_from_db(term) if r is None: continue df = r[1] w = math.log2((self.N - df + 0.5) / (df + 0.5)) docs = r[2].split('\n') for doc in docs: docid, date_time, tf, ld = doc.split('\t') docid = int(docid) tf = int(tf) ld = int(ld) s = (self.K1 * tf * w) / (tf + self.K1 * (1 - self.B + self.B * ld / self.AVG_L)) if docid in BM25_scores: BM25_scores[docid] = BM25_scores[docid] + s else: BM25_scores[docid] = s BM25_scores = sorted(BM25_scores.items(), key = operator.itemgetter(1)) BM25_scores.reverse() if len(BM25_scores) == 0: return 0, [] else: return 1, BM25_scores
    首先将句子分词得到所有查询词项,然后从数据库中取出词项对应的倒排记录表,对记录表中的所有文档,计算其BM25得分,最后按得分高低排序作为查询结果。
    类似的,我们还可以对所有文档按时间先后顺序排序,越新鲜的新闻排名越高;还可以按新闻的热度排序,越热门的新闻排名越高。
    关于热度公式,我们认为一方面要兼顾相关度,另一方面也要考虑时间因素,所以是BM25打分和时间打分的一个综合。
    比较有名的热度公式有两个,一个是Hacker News的,另一个是Reddit的,他们的公式分别为:


    可以看出,他们都是将新闻/评论的一个原始得分和时间组合起来,只是一个用除法,一个用加法。所以我们也依葫芦画瓢,”自创“了一个简单的热度公式:

    用BM25得分加上新闻时间和当前时间的差值的倒数,k1k1和k2k2也是可调参数。
    按时间排序和按热度排序的函数和按BM25打分排序的函数类似,这里就不贴出来了,详细情况可以看我的项目News_IR_Demo。
    至此,搜索引擎的搜索功能已经实现了,你可以试着修改./web/search_engine.py的第167行的关键词,看看搜索结果是否和你预想的排序是一样的。不过由于我们的数据量只有1000个新闻,并不能涵盖所有关键词,更多的测试可以留给大家线下完成。
    本文转载自:http://bitjoy.net/2016/01/07/introduction-to-building-a-search-engine-4
    2 留言 2019-06-01 15:29:45 奖励14点积分
  • windows下静态使用QxOrm框架并使用泛型编程 (二)

    这篇开始讲实际编程并且抽象化,让代码书写更少。
    为了模块划分我在Demo文件夹下新增了一个SQLModule文件夹,在此文件夹下又新增QxHandler,QxMapped,QxObject三个文件夹,QxObject是用来存储数据对象类的一个文件夹,QxMapped是存储键值映射的文件夹,QxHandler是操作数据库的实际句柄类文件夹。
    QxObject类
    User.h
    #ifndef USER_H#define USER_H#include "common.h"#include <QxOrm.h>#include <QJsonObject>class User{ QX_REGISTER_FRIEND_CLASS(User) //用于将类注册到QxOrm的宏定义public: User(); long getId(); QString getName(); int getAge();public: QJsonObject toJsonObject(); bool analyzeJson(QByteArray &json); void analyzeJson(QJsonObject &json);private: long m_lId QString m_sName; int m_nAge;};//QX_REGISTER_PRIMARY_KEY(User, int) //主键不是整数类型的时候使用QX_REGISTER_HPP_IMPORT_DLL(User, qx::trait::no_base_class_defined, DATABASE_VERSION) //用于将类注册到QxOrm的宏定义 第一个参数为类名 第二个为默认值 第三个为数据库版本#endif // USER_HUser.cpp
    #include "User.h"QX_REGISTER_CPP_IMPORT_DLL(User) //用于将类注册到QxOrm的宏定义namespace qx //用于将类注册到QxOrm的方法 { template <> void register_class(QxClass<User> & t) { qx::IxDataMember * pData = NULL; Q_UNUSED(pata); qx::IxSqlRelation * pRelation = NULL; Q_UNUSED(pRelation); qx::IxFunction * pFct = NULL; Q_UNUSED(pFct) qx::IxValidator * pValidator = NULL; Q_UNUSED(pValidator); // Register pData =t.id(& User::m_lId, "id",DATABASE_VERSION); pData =t.data(& User::m_nAge, "name",DATABASE_VERSION); pData =t.data(& User::m_nAge, "age",DATABASE_VERSION); qx::QxValidatorX<User> * pAllValidator = t.getAllValidator(); Q_UNUSED(pAllValidator); }}User::User() :m_lId(0) ,m_sName("") ,m_nAge(0){}long User::getId(){ return m_lId;}QString User::getName(){ return m_sName;}int User::getAge(){ return m_nAge}JsonObject User::toJsonObject(){ QJsonObject subObject; subObject.insert("Name",m_sName); subObject.insert("Age",m_nAge); return subObject;}bool User::analyzeJson(QByteArray &json){ bool success=false; QJsonParseError error; QJsonDocument document=QJsonDocument::fromJson(json,&error); if (!document.isNull() && (error.error == QJsonParseError::NoError)) { QJsonObject rootObject =documnt.object(); this->analyzeJson(rootObject); success=true; } else { success=false; qDebug()<<error.error; qDebug("LockoutFun analyze falied"); } return success;}void User::analyzeJson(QJsonObject &json){ QJsonObject rootObject = json.value("User").toObject(); m_sName=rootObject.value(QString("Name")).toString(); m_nAge=rootObject.value(QString("Age")).toInt();}QxMapped
    IMapped 映射抽象类
    #ifndef IMAPPED_H#define IMAPPED_H/*** @author tianmin* @brief The IMapped class 抽象出来的映射类* @date 2019-07-22*/#include <QStringList>#include <QSting>#include <QMapclass IMapped{public //字符map初始化 virtual void initMapped() = 0; //获取QString列表 virtual QStringList getListMapped(int en) =0; //获取QString 字段 virtual QString getMapped(int en) = 0; virtual ~IMapped(){}};#endif // IMAPPED_HUserMapped 际使用的类
    UserMapped .h
    #ifndef USERMAPPED_H#define USERMAPPED_H#include "IMapped.h"namespace USER {enum USER_MAPPED{ EN_ID = 0x00000001, EN_NAME = 0x00000002, EN_AGE = 0x00000004, EN_ALL = 0xFFFFFFFF};}class UserMapped:public IMapped{public: UserMapped(); virtual ~UserMapped();public: virtual void initMapped() virtual QStringList getListMapped(int en); virtual QString getMapped(int en);private: QMap<int,QString> m_map;};#endif // USERMAPPED_HUserMapped.cpp
    #include "UserMapped.h"UserMapped::UserMapped(){}UserMapped::~UserMapped(){}void UserMapped::initMapped(){ m_map.clear(); m_map.insert(USER::EN_ID, "id"); m_map.insert(USER::EN_NAME ,"name"); m_map.insert(USER::EN_AGE ,"age");}QStringList UserMapped::getListMapped(int en){ QStringList temp; QString str; QMap<int,QString>::iterator i; or (i = m_map.begin(); i != m_map.end(); ++i) { if(en&(i.key())) { str=i.value(); temp.append(str); str.clear(); } } return temp;}QString UserMapped::getMapped(int en){ QString str QMap<int,QString>::iterator i; for (i = m_map.begin(); i != m_map.end(); ++i) { if(en==i.key()) { str =i.value(); return str; } } return QString();}QxHandler
    IHandler 抽象化的类模板
    这里只抽象了部分方法 还有save 和事务以及关系的方法未抽象完成
    #ifndef IHANDLER_H#define IHANDLER_H#include <QxOrm.h>#include <QMutexLocker>#include <QMutex>#include <QtSql/QSqlDatabase>#include <QtSql/QSqlError>/*** @brief The ISqlInterface class 数据库操作的抽象方法类 并且模板化减少代码的繁杂*/class ISqlInterface{public: ISqlInterface(){} virtual ~ISqlInterface(){} //数据库连接初始化protected: virtual void initSqlconnect()=0; //建表 virtual bool createTable()=0; //断开连接 virtual void disconnect()=0;};template<class T,class T2,class T3>class IHandler{public: IHandler(){} virtual ~IHandler(){} Virtual bool createTable(QSqlDatabase &m_SqlDatabase) { QSqlError error= qx::dao::create_table<T3>(&m_SqlDatabase); return !error.isValid(); } virtual bool insert(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase) { QMutexLocker locker(&m_Mutex); if(!m_SqlDatabase.isOpen()) return false; if(qx::dao::exist(t,&m_SqlDatabase).getValue()!=false) return false; QSqlError error= qx::dao::insert(t,&m_SqlDatabase); return !error.isValid(); } virtual bool deleteObject(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,bool isDestroy) { QMutexLocker locker(&m_Mutex); if(!m_SqlDatabase.isOpen()) return false; QSqlError error; if(qx::dao::exist(t,&m_SqlDatabase).getValue()==false) return false; if(isDestroy==false) { error= qx::dao::delete_by_id(t,&m_SqlDatabase); } else { error= qx::dao::destroy_by_id(t,&m_SqlDatabase); } return !error.isValid(); } virtual bool update(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,QStringList &list) { QMutexLocker locker(&m_Mutex); if(!m_SqlDatabas.isOpen()) return false; if(qx::dao::exist(t,&m_SqlDatabase).getValue()==false) return false; QSqlError error= qx::dao::update(t,&m_SqlDatabase,list); return !error.isValid(); } virtual bool select(T &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,QStringList &list) QMutexLocker locker(&m_Mutex); if(!m_SqlDatabase.isOpen()) return false; QSqlError error; if(qx::dao::exist(t,&m_SqlDatabase).getValue()==false) return false; error= qx::dao::fetch_by_id(t,&m_SqlDatabase,list); return !error.isValid(); } virtual bool selectByQuery(T2 &t,QMutex &m_Mutex,QSqlDatabase &m_SqlDatabase,qx::QxSqlQuery &query,QStringList &list) { QMutexLocker locker(&m_Mutx); if(!m_SqlDatabase.isOpen()) return false; QSqlError error=qx::dao::fetch_by_query(query,t,&m_SqlDatabase,list); return !error.isValid(); }};#endif // IHANDLER_HUserHandler 实际操作句柄类
    UserHandler.h
    #ifndef USERHANDLER_H#define USERHANDLER_H#include <QxOrm.h>#include <QString>#include <QMutexLocker>#include <QMutex>#include <QtSql/QSqlDatabase>#include <QtSql/QSqlError>#include "IHandler.h"#include "SQLModule/QxMapped/UserMapped.h"#include "SQLModule/QxObject/User.h"namespace USER{const QString DATABASE_TYPE="QSQLITE";const QString CONNECT_NAME="USER_CONNECTED";const QString DATABASENAME="C:/Users/we/Desktop/workTools/demo/qxorm.db";const QString HOSTNAME="localhost";const QString USERNAME="root";const QString PASSWORD="";}using namespace USER;class User; //OBJECT 类typedef QSharedPointer<User> Shared_User; //User类智能指针typedef QList<Shared_User> List_User; //User类数组typedef qx::QxCollection<int,Shared_User> Collection_User; //User容器class UserHandler:public IHandler<Shared_User,Collection_User,User> ,public ISqlInterface{public: UserHandler(); virtual ~UserHandler(); /** * @brief insert 插入数据至数据库 * @param t 插入的单条数据 不需要指定ID值 自增 * @return 0失败 1成功 */ bool insert(Shared_User &t); /** * @brief deleteObject 从数据库中删除指定数据 * @param t 删除的单条数据 需要指定ID值 * @param isDestroy 是否软删除 * @return 0失败 1成功 */ bool deleteObject(Shared_User &t,bool isDestroy=false); /** * @brief update 根据ID值更新数据 * @param t 数据 * @param en 更新字段的映射值 * @return 0失败 1成功 */ bool update(Shared_User &t,int en=EN_ALL); /** * @brief select 根据ID值查询数据 * @param t 数据 * @param en 映射值 * @return 0失败 1成功 */ bool select(Shared_User &t,int en=EN_ALL); /** * @brief selectByQuery 根据搜寻条件查找 * @param t 数据集合 * @param query 搜寻语句 * @param en 数据库列映射值 * @return 0失败 1成功 */ bool selectByQuery(Collection_User &t,qx::QxSqlQuery &query,int en=EN_ALL);protected: virtual void initSqlconnect(); virtual bool createTable(); virtual void disconnect();private: UserMapped m_Mapped; QMutex m_Mutex; QSqlDatabase m_SqlDatabase;};#endif // USERHANDLER_HUserHandler.cpp
    #include "UserHandler.h"UserHandler::UserHandler(){ initSqlconnect(); createTable();}UserHandler::~UserHandler(){ disconnect();}void UserHandler::initSqlconnect(){ QMutexLocker locker(&m_Mutex); if(QSqlDatabase::contains(CONNECT_NAME)) m_SqlDatabase = QSqlDatabase::database(CONNECT_NAME); else m_SqlDatabase= QSqlDatabase::addDatabase(DATABASE_TYPE,CONNECT_NAME); m_SqlDatabase.setDatabaseName(DATABASENAME); m_SqlDatabase.setHostName(HOSTNAME); m_SqlDatabase.setUserName(USERNAME); m_SqlDatabase.setPassword(PASSWORD); m_SqlDatabase.open();}bool UserHandler::createTable(){ return IHandler<Shared_User,Collection_User,User>::createTable(m_SqlDatabase);}void UserHandler::disconnect(){ QMutexLocker locker(&m_Mutex); if(m_SqlDatabase.isOpen()) m_SqlDatabase.close(); QSqlDatabase::removeDatabase(CONNECT_NAME);}bool UserHandler::insert(Shared_User &t){ return IHandler<Shared_User,Collection_User,User>::insert(t,m_Mutex,m_SqlDatabase);}bool UserHandler::deleteObject(Shared_User &t,bool isDestroy){ return IHandler<Shared_User,Collection_User,User>::deleteObject(t,m_Mutex,m_SqlDatabase,isDestroy);}bool UserHandler::update(Shared_User &t, int en){ QStringList list= m_Mapped.getListMapped(en); return IHandler<Shared_User,Collection_User,User>::update(t,m_Mutex,m_SqlDatabase,list);}bool UserHandler::select(Shared_User &t, int en){ QStringList list= m_Mapped.getListMapped(en); return IHandler<Shared_User,Collection_User,User>::select(t,m_Mutex,m_SqlDatabase,list);}bool UserHandler::selectByQuery(Collection_User &t,qx::QxSqlQuery &query,int en){ QStringList list= m_Mapped.getListMapped(en); return IHandler<Shared_User,Collection_User,User>::selectByQuery(t,m_Mutex,m_SqlDatabase,query,list);}
    1 留言 2019-07-23 14:38:57 奖励6点积分
  • 【Cocos Creator 实战教程(1)】——人机对战五子棋(节点事件相关) 精华

    一、涉及知识点
    场景切换按钮事件监听节点事件监听节点数组循环中闭包的应用动态更换sprite图片定时器预制资源
    二、步骤2.1 准备工作首先,我们要新建一个空白工程,并在资源管理器中新建几个文件夹

    在这些文件夹中,我们用来存放不同的资源,其中

    Scene用来存放场景,我们可以把场景看作一个关卡,当关卡切换时,场景就切换了
    Script用来存放脚本文件
    Texture用来存放显示的资源,例如音频,图片
    Prefab用来存放预制资源,接下来我会详细的介绍

    接下来,我们在Scene文件夹中新建一个场景(右击文件夹->新建->Scene),命名为Menu,接着导入背景图片(直接拖拽即可)。最后调整图片大小使图片铺满背景,效果如图。

    2.2 按钮监听与场景切换接下来我们来学习此次实战的第一个难点,按钮监听与场景切换。
    首先,创建一个Button节点,并删除label,放在人机博弈的按钮上,并在属性上调成透明样式。

    接下来,新建一个Game场景,并添加一个棋盘节点,并把锚点设为(0,0)。

    这里,讲一下锚点的作用。
    anchor point 究竟是怎么回事? 之所以造成不容易理解的是因为我们平时看待一个图片是以图片的中心点这一个维度来决定图片的位置的。而在cocos2d中决定一个图片的位置是由两个维度一个是 position 另外一个是anchor point。只要我们搞清楚他们的关系,自然就迎刃而解。默认情况下,anchor point在图片的中心位置(0.5, 0.5),取值在0到1之间的好处就是,锚点不会和具体物体的大小耦合,也即不用关注物件大小,而应取其对应比率,如果把锚点改成(0,0),则进行放置位置时,以图片左下角作为起始点。也就是说,把position设置成(x,y)时,画到屏幕上需要知道:到底图片上的哪个点放在屏幕的(x,y)上,而anchor point就是这个放置的点,anchor point是可以超过图片边界的,比如下例中的(-1,-1),表示从超出图片左下角一个宽和一个高的地方放置到屏幕的(0,0)位置(向右上偏移10个点才开始到图片的左下角,可以认为向右上偏移了10个点的空白区域)
    他们的关系是这样的(假设actualPosition.x,actualPosition.y是真实图片位置的中点):actualPosition.x = position.x + width*(0.5 - anchor_point.x);acturalPosition.y = position.y + height*(0.5 - anchor_point.y)actualPosition 是sprite实际上在屏幕显示的位置, poistion是 程序设置的, achor_point也是程序设置的。
    然后,我们需要新建一个脚本,Menu.js,并添加开始游戏方法。
    cc.Class({ extends: cc.Component, startGame:function(){ cc.director.loadScene('Game');//这里便是运用导演类进行场景切换的代码 }});
    这里提示以下,编辑脚本是需要下载插件的,我选择了VScode,还是很好用的。
    最后我们将其添加为Menu场景的Canvas的组件(添加组件->脚本组件->menu),并在BtnP2C节点上添加按钮监听响应。


    这样,按钮监听就完成了。现在我们在Menu场景里点击一下人机按钮就会跳转到游戏场景了。
    2.3 预制资源预制资源一般是在场景里面创建独立的子界面或子窗口,即预制资源是存放在资源中,并不是节点中,例如本节课中的棋子。
    现在我们就来学习一下如何制作预制资源。

    再将black节点改名为Chess拖入下面Prefab文件夹使其成为预制资源。
    这其中,SpriteFrame 是核心渲染组件 Sprite 所使用的资源,设置或替换 Sprite 组件中的 spriteFrame 属性,就可以切换显示的图像,将其去掉防止图片被预加载,即棋子只是一个有大小的节点不显示图片也就没有颜色。
    2.4 结束场景直接在Game场景上制作结束场景。

    2.5 游戏脚本制作人机对战算法参考了这里https://blog.csdn.net/onezeros/article/details/5542379
    具体步骤便是初始化棋盘上225个棋子节点,并为每个节点添加事件,点击后动态显示棋子图片。
    代码如下:
    cc.Class({ extends: cc.Component, properties: { overSprite:{ default:null, type:cc.Sprite, }, overLabel:{ default:null, type:cc.Label }, chessPrefab:{//棋子的预制资源 default:null, type:cc.Prefab }, chessList:{//棋子节点的集合,用一维数组表示二维位置 default: [], type: [cc.node] }, whiteSpriteFrame:{//白棋的图片 default:null, type:cc.SpriteFrame }, blackSpriteFrame:{//黑棋的图片 default:null, type:cc.SpriteFrame }, touchChess:{//每一回合落下的棋子 default:null, type:cc.Node, visible:false//属性窗口不显示 }, gameState:'white', fiveGroup:[],//五元组 fiveGroupScore:[]//五元组分数 }, //重新开始 startGame(){ cc.director.loadScene("Game"); }, //返回菜单 toMenu(){ cc.director.loadScene("Menu"); }, onLoad: function () { this.overSprite.node.x = 10000;//让结束画面位于屏幕外 var self = this; //初始化棋盘上225个棋子节点,并为每个节点添加事件 for(var y = 0;y<15;y++){ for(var x = 0;x < 15;x++){ var newNode = cc.instantiate(this.chessPrefab);//复制Chess预制资源 this.node.addChild(newNode); newNode.setPosition(cc.p(x*40+20,y*40+20));//根据棋盘和棋子大小计算使每个棋子节点位于指定位置 newNode.tag = y*15+x;//根据每个节点的tag就可以算出其二维坐标 newNode.on(cc.Node.EventType.TOUCH_END,function(event){ self.touchChess = this; if(self.gameState === 'black' && this.getComponent(cc.Sprite).spriteFrame === null){ this.getComponent(cc.Sprite).spriteFrame = self.blackSpriteFrame;//下子后添加棋子图片使棋子显示 self.judgeOver(); if(self.gameState == 'white'){ self.scheduleOnce(function(){self.ai()},1);//延迟一秒电脑下棋 } } }); this.chessList.push(newNode); } } //开局白棋(电脑)在棋盘中央下一子 this.chessList[112].getComponent(cc.Sprite).spriteFrame = this.whiteSpriteFrame; this.gameState = 'black'; //添加五元数组 //横向 for(var y=0;y<15;y++){ for(var x=0;x<11;x++){ this.fiveGroup.push([y*15+x,y*15+x+1,y*15+x+2,y*15+x+3,y*15+x+4]); } } //纵向 for(var x=0;x<15;x++){ for(var y=0;y<11;y++){ this.fiveGroup.push([y*15+x,(y+1)*15+x,(y+2)*15+x,(y+3)*15+x,(y+4)*15+x]); } } //右上斜向 for(var b=-10;b<=10;b++){ for(var x=0;x<11;x++){ if(b+x<0||b+x>10){ continue; }else{ this.fiveGroup.push([(b+x)*15+x,(b+x+1)*15+x+1,(b+x+2)*15+x+2,(b+x+3)*15+x+3,(b+x+4)*15+x+4]); } } } //右下斜向 for(var b=4;b<=24;b++){ for(var y=0;y<11;y++){ if(b-y<4||b-y>14){ continue; }else{ this.fiveGroup.push([y*15+b-y,(y+1)*15+b-y-1,(y+2)*15+b-y-2,(y+3)*15+b-y-3,(y+4)*15+b-y-4]); } } } }, //电脑下棋逻辑 ai:function(){ //评分 for(var i=0;i<this.fiveGroup.length;i++){ var b=0;//五元组里黑棋的个数 var w=0;//五元组里白棋的个数 for(var j=0;j<5;j++){ this.getComponent(cc.Sprite).spriteFrame if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.blackSpriteFrame){ b++; }else if(this.chessList[this.fiveGroup[i][j]].getComponent(cc.Sprite).spriteFrame == this.whiteSpriteFrame){ w++; } } if(b+w==0){ this.fiveGroupScore[i] = 7; }else if(b>0&&w>0){ this.fiveGroupScore[i] = 0; }else if(b==0&&w==1){ this.fiveGroupScore[i] = 35; }else if(b==0&&w==2){ this.fiveGroupScore[i] = 800; }else if(b==0&&w==3){ this.fiveGroupScore[i] = 15000; }else if(b==0&&w==4){ this.fiveGroupScore[i] = 800000; }else if(w==0&&b==1){ this.fiveGroupScore[i] = 15; }else if(w==0&&b==2){ this.fiveGroupScore[i] = 400; }else if(w==0&&b==3){ this.fiveGroupScore[i] = 1800; }else if(w==0&&b==4){ this.fiveGroupScore[i] = 100000; } } //找最高分的五元组 var hScore=0; var mPosition=0; for(var i=0;i<this.fiveGroupScore.length;i++){ if(this.fiveGroupScore[i]>hScore){ hScore = this.fiveGroupScore[i]; mPosition = (function(x){//js闭包 return x; })(i); } } //在最高分的五元组里找到最优下子位置 var flag1 = false;//无子 var flag2 = false;//有子 var nPosition = 0; for(var i=0;i<5;i++){ if(!flag1&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){ nPosition = (function(x){return x})(i); } if(!flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame != null){ flag1 = true; flag2 = true; } if(flag2&&this.chessList[this.fiveGroup[mPosition][i]].getComponent(cc.Sprite).spriteFrame == null){ nPosition = (function(x){return x})(i); break; } } //在最最优位置下子 this.chessList[this.fiveGroup[mPosition][nPosition]].getComponent(cc.Sprite).spriteFrame = this.whiteSpriteFrame; this.touchChess = this.chessList[this.fiveGroup[mPosition][nPosition]]; this.judgeOver(); }, judgeOver:function(){ var x0 = this.touchChess.tag % 15; var y0 = parseInt(this.touchChess.tag / 15); //判断横向 var fiveCount = 0; for(var x = 0;x < 15;x++){ if((this.chessList[y0*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断纵向 fiveCount = 0; for(var y = 0;y < 15;y++){ if((this.chessList[y*15+x0].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断右上斜向 var f = y0 - x0; fiveCount = 0; for(var x = 0;x < 15;x++){ if(f+x < 0 || f+x > 14){ continue; } if((this.chessList[(f+x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //判断右下斜向 f = y0 + x0; fiveCount = 0; for(var x = 0;x < 15;x++){ if(f-x < 0 || f-x > 14){ continue; } if((this.chessList[(f-x)*15+x].getComponent(cc.Sprite)).spriteFrame === this.touchChess.getComponent(cc.Sprite).spriteFrame){ fiveCount++; if(fiveCount==5){ if(this.gameState === 'black'){ this.overLabel.string = "你赢了"; this.overSprite.node.x = 0; }else{ this.overLabel.string = "你输了"; this.overSprite.node.x = 0; } this.gameState = 'over'; return; } }else{ fiveCount=0; } } //没有输赢交换下子顺序 if(this.gameState === 'black'){ this.gameState = 'white'; }else{ this.gameState = 'black'; } }});
    这里便用到了节点监听,节点数组,定时器和动态更换sprite图片。
    新建Game脚本添加到ChessBoard节点下

    3 注意课程到这里就结束了,本课程部分资源来源于网络。
    第一次写技术分享,如果大家有什么好的建议和问题请在下方留言区留言。
    7 留言 2018-11-21 20:32:10 奖励20点积分
  • 深度学习 20、CNN

    前文链接:https://write-bug.com/article/2575.html
    卷积神经网络CNN上节我们介绍了DNN的基本网络结构和演示,其网络结构为:

    DNN BP神经网络实行全连接特性,参数权值过多,需求大量样本,计算困难,所以CNN利用局部连接、权值共享来实现减少权值的尝试,即局部感知野和权值共享。擅长图像分类问题,识别二维图形,现在也可以用于文本处理。

    CNN的隐藏层分为卷积层conv、和池化层pool,一般CNN结构(LeNet)依次为:

    INPUT->[[CONV -> RELU]N -> POOL?]M -> [FC -> RELU]*K->FC经典LeNet-5结构:最早用于数字识别的CNN


    卷积层Convolitions:
    用卷积层实现对图片局部的特征提取,窗口模式卷积核形式提取特征,映射到高维平面。
    两个关键性操作:

    局部关联:每个神经元看为一个滤波器filter
    窗口滑动:filter对局部数据计算

    局部感受野:
    1000 * 1000 图像 1000 * 1000神经元全连接: 1000 * 1000 * 1000 * 1000 = 10^12 个参数
    局部感受野:每个神经元只需要知道10 * 10区域,训练参数降到100M

    权值共享:
    所有神经元用同一套权重值,100M降到100,这样得到一个feature map。100种权重值100个map,一共需要训练100*100 = 10k参数

    卷积的计算:

    窗口滑动,我们可以想象一个窗口(卷积核feature_map),设置固定的feature_size,里面是固定的权值,这个窗口在同一张图片上从左到右从上到下游走,每次固定步长,从而得到更小size的矩阵。

    这里有两个神经元的卷积层,步长stride设置为2,边缘灰色部分为填充值zero-padding,防止窗口移到边缘时不够滑动遍历所有像素,而这里的神经元个数就是卷积层深度depth。
    在卷积层中每个神经元连接数据窗的权重是固定的,每个神经元只关注一个特性。神经元就是图像处理中的滤波器,比如边缘检测专用的Sobel滤波器,即卷积层的每个滤波器都会有自己所关注一个图像特征,比如垂直边缘,水平边缘,颜色,纹理等等,这些所有神经元加起来就好比就是整张图像的特征提取器集合。

    需要估算的权重个数减少: AlexNet 1亿 => 3.5w
    一组固定的权重和不同窗口内数据做内积: 卷积卷积理解:后面输出结果的前提是依赖前面相应权值积累,比如连续吃三天药身体才好,那么这三天的相关性是不同的权值

    激活函数把卷积层输出结果做非线性映射。CNN采用的激励函数一般为ReLU(The Rectified Linear Unit/修正线性单元),它的特点是收敛快,求梯度简单,但较脆弱,图像如下。

    激励层的实践经验:

    不要用sigmoid!不要用sigmoid!不要用sigmoid!首先试RELU,因为快,但要小心点如果2失效,请用Leaky ReLU或者Maxout某些情况下tanh倒是有不错的结果,但是很少
    池化层pooling:对特征进一步缩放
    池化层夹在连续的卷积层中间,用于压缩数据和参数的量,减小过拟合,压缩图像。
    特点:

    特征不变性:图像缩小还可以认出大概
    特征降维:去除冗余信息,防止过拟合


    池化层用的方法有Max pooling 和 average pooling,而实际用的较多的是Max pooling。
    这里就说一下Max pooling,其实思想非常简单。

    对于每个2*2的窗口选出最大的数作为输出矩阵的相应元素的值,比如输入矩阵第一个2*2窗口中最大的数是6,那么输出矩阵的第一个元素就是6,如此类推。
    平移不变性

    旋转不变性

    缩放不变性

    全连接层FC
    和BP神经网络一样,两层之间互相全连接,即把压缩后的维度打平为一组向量,再通过dnn的方法进行分类处理。
    Droupout
    正则化方法,CNN中解决过拟合问题,同时通用与其他网络。

    随机在全连接层,剪掉一些神经元,防止过拟合。
    卷积网络在本质上是一种输入到输出的映射,它能够学习大量的输入与输出之间的映射关系,而不需要任何输入和输出之间的精确的数学表达式,只要用已知的模式对卷积网络加以训练,网络就具有输入输出对之间的映射能力。
    CNN一个非常重要的特点就是头重脚轻(越往输入权值越小,越往输出权值越多),呈现出一个倒三角的形态,这就很好地避免了BP神经网络中反向传播的时候梯度损失得太快。
    卷积神经网络CNN主要用来识别位移、缩放及其他形式扭曲不变性的二维图形。由于CNN的特征检测层通过训练数据进行学习,所以在使用CNN时,避免了显式的特征抽取,而隐式地从训练数据中进行学习;再者由于同一特征映射面上的神经元权值相同,所以网络可以并行学习,这也是卷积网络相对于神经元彼此相连网络的一大优势。卷积神经网络以其局部权值共享的特殊结构在语音识别和图像处理方面有着独特的优越性,其布局更接近于实际的生物神经网络,权值共享降低了网络的复杂性,特别是多维输入向量的图像可以直接输入网络这一特点避免了特征提取和分类过程中数据重建的复杂度。
    卷积神经网络之训练算法:

    同一般机器学习算法,先定义Loss function,衡量和实际结果之间差距
    找到最小化损失函数的W和b, CNN中用的算法是SGD(随机梯度下降)

    卷积神经网络之优缺:

    优点

    共享卷积核,对高维数据处理无压力无需手动选取特征,训练好权重,即得特征分类效果好
    缺点

    需要调参,需要大样本量,训练最好要GPU物理含义不明确(也就说,我们并不知道没个卷积层到底提取到的是什么特征,而且神经网络本身就是一种难以解释的“黑箱模型”)

    卷积神经网络之典型CNN:

    LeNet,这是最早用于数字识别的CNN
    AlexNet, 2012 ILSVRC比赛远超第2名的CNN,比LeNet更深,用多层小卷积层叠加替换单大卷积层。
    ZF Net, 2013 ILSVRC比赛冠军
    GoogLeNet, 2014 ILSVRC比赛冠军
    VGGNet, 2014 ILSVRC比赛中的模型,图像识别略差于GoogLeNet,但是在很多图像转化学习问题(比如object detection)上效果奇好pytorch网络结构:

    class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 20, 5, 1) self.conv2 = nn.Conv2d(20, 50, 5, 1) self.fc1 = nn.Linear(4*4*50, 500) self.fc2 = nn.Linear(500, 10) def forward(self, x): print("x size: ", x.size()) x = F.relu(self.conv1(x)) print("conv1 size: ", x.size()) x = F.max_pool2d(x, 2, 2) print("pool size: ", x.size()) x = F.relu(self.conv2(x)) print("conv2 size: ", x.size()) x = F.max_pool2d(x, 2, 2) print("pool size: ", x.size()) x = x.view(-1, 4*4*50) print("view size: ", x.size()) x = F.relu(self.fc1(x)) print("fc1 size: ", x.size()) x = self.fc2(x) print("fc2 size: ", x.size()) sys.exit() return F.log_softmax(x, dim=1)
    1 留言 2019-06-11 11:42:43 奖励13点积分
  • 内核双机调试——追踪并分析内核API函数的调用 精华

    在64位Win10主机上调试32位Win7虚拟机内核,查看Win7内核中的函数调用关系,从而分析API函数实现的具体原理和流程。
    一、环境配置前提,对Win7虚拟机设置了调试的COM端口:\\.\pipe\com_1,波特率设置为115200,那么接下来就可以使用WinDbg来进行调试了。具体步骤如下所示:
    首先,在Win10上“以管理员身份”运行64位的WinDbg,点击File—>Kernel Debug—>COM,波特率设置为115200,端口与上面对应\\.\pipe\com_1,并勾选pipe、Reconnect,点击确定即可。

    如果我们不动它,WinDbg命令窗口将会一直显示“Waiting to reconnect…”的信息!这时,我们需要点击下工具栏上“Break”按钮,让Win7系统断下来,这样我们才可以用WinDbg进行调试,输入控制指令。


    接着,设置符号路径:File—>Symbol File Path—>输入:
    srv*c:\symbols*https://msdl.microsoft.com/download/symbols确定即可,这样WinDbg就会自动根据链接下载符号表到本地上。

    等待一会儿,即可下载完毕,方可输入指令。
    二、使用WingDbg追踪API调用流程接下来,我们以分析内核API函数NtQueryDirectoryFile为例,介绍WinDbg调试软件的使用方法。
    输入指令:uf nt!NtQueryDirectoryFile

    大概可以看出,NtQueryDirectoryFile函数的实现主要是调用了nt!BuildQueryDirectoryIrp函数构造查询文件的IRP以及nt!IopSynchronousServiceTail发送IRP来实现的。为了验证我们的想法,我们继续对这两个函数进行查看。
    输入指令:uf nt!BuildQueryDirectoryIrp

    从它函数实现调用了nt!IoAllocateIrp函数可知,我们的猜想是正确的。
    输入指令:uf nt!IopSynchronousServiceTail

    从它函数实现调用了nt!IofCallDriver函数可知,我们的猜想是正确的。
    nt!IofCallDriver函数定义如下所示:
    NTSTATUS IofCallDriver( PDEVICE_OBJECT DeviceObject, __drv_aliasesMem PIRP Irp);
    该函数的功能就是将IRP发送到指定的设备对象中处理,第1个参数就是处理IRP的设备对象。
    所以,接下来,我们继续用WinDbg分析上述nt!IopSynchronousServiceTail函数将IRP发给了哪个驱动设备进行处理的。
    输入指令:bu nt!NtQueryDirectoryFile
    输入指令:bl;查看所有断点
    输入指令:g;继续往下执行
    下断点,只要系统执行到nt!NtQueryDirectoryFile这个函数就会停下来。

    输入指令:u @eip
    输入指令:r;查看寄存器

    由于是对nt! NtQueryDirectoryFile这个函数下断点,所以现在停下来,指令指针eip指向的就是nt! NtQueryDirectoryFile函数的入口地址。此时,uf @eip就是反汇编nt! NtQueryDirectoryFile函数的内容。
    输入指令:bp 84043fdc
    输入指令:g

    84043fdc就是nt!IopSynchronousServiceTail函数的入口地址,断点会自动在此处断下。我们一步步下断点,逼近最终我们需要下断点的nt!IofCallDriver函数,确保是由nt!NtQueryDirectoryFile函数内容实现调用的nt!IofCallDriver函数。
    输入指令:uf @eip

    输入指令:bp 83e4cd19
    输入指令:g

    83e4cd19是nt!IofCallDriver函数的入口地址,WinDbg断点断下后,就会自动停在此处。
    输入指令:u @eip
    输入指令:r

    IofCallDriver函数是FASTCALL类型的调用约定,所以第1个参数的值存储在寄存器ecx中,即873f3508。所以,我们继续查看下该设备对象的结构数据。
    输入指令:dt nt!_DEVICE_OBJECT @ecx

    这时,我们边可以获取到驱动对象的地址DriverObject(0x86f5e670 _DRIVER_OBJECT),继续查看驱动对象的数据内容。
    输入指令:dt nt!_DRIVER_OBJECT 0x86f5e670

    由DriverName中我们可以看出,nt!NtQueryDirectoryFile是将IRP请求包发送给FltMgf驱动程序来处理的。
    3 留言 2019-09-01 09:25:57 奖励20点积分
  • 深度学习 19、DNN

    前文链接:https://write-bug.com/article/2540.html
    深度学习在前几节介绍的模型都属于单点学习模型,比如朴素贝叶斯的模型为概率,lr与softmax的模型为w,b权重,聚类kmeans的模型为中心向量点,还有后面的决策树的树模型等等都属于单点学习模型,都有各自的用途,而这里的深度学习来说,属于机器学习的一部分,可用不同的网络结构来解决各个领域的问题,模型为深度学习网络。
    通过前几节的学习,不知道大家有没有对单点学习的模型有感触,所谓模型是什么?
    我们从已有的例子(训练集)中发现输入x与输出y的关系,这个过程是学习(即通过有限的例子发现输入与输出之间的关系),而我们使用的function就是我们的模型,通过模型预测我们从未见过的未知信息得到输出y,通过激活函数(常见:relu,sigmoid,tanh,swish等)对输出y做非线性变换,压缩值域,而输出y与真实label的差距就是误差error,通过损失函数再求取参数偏导,梯度下降降低误差,从而优化模型,我们前面见过的损失函数是mse和交叉熵

    而DNN的模型就是网络结构,在说如何选取更好的网络结构之前,我们先介绍下神经网络:

    人类大脑的神经网络如上图具象出来就是无数个神经元,而神经元分为几个结构:

    树突:接收信号源
    神经元:核心计算单元
    轴突:传输数据到下一个神经元

    通过一个个神经元的传输,生成结果,比如说条件反射:缩手
    而在我们模型中,这个网络怎么表示呢?

    感知机:最最简单的神经网络,只有一个神经元


    树突:样本特征a1
    轴突:权重w1

    树突与轴突,也就是样本特征和权重相乘,有些类似lr的wx,共同决策了神经元的输入

    神经元:对信号进一步处理,比如lr中wx的∑,并加入偏置b,默认为1
    传递函数(激活函数):对f(s)做压缩非线性变换

    假设一个二分类问题:

    输入:
    树突P1:颜色
    树突P2:形状
    香蕉(黄色,弯形):(-1,-1)
    苹果(红色,圆形):(1,1)

    相当于坐标轴的两个点,我们需要一个分类面把两个点分割开。

    P1颜色的取值为1,-1
    P2形状的取值为1,-1
    初始化w1=w2=1,b=0

    则有:
    s=p1w1+p2w2=2>0s=p2 2w2 2+p2 2w2 2=-2 < 0这个初始化的参数可以区分,但是初始化有随机性,换一组参数就不能区分了
    所以感知机的训练方法,目的就是训练权重和偏置
    w=w+epb=b+ee(误差)=t-a=期望-实际
    如,假设w1=1,w2=-1,b=0

    苹果s=0
    期望为1,e=1-0=1

    修正:
    w1=1+1*1=2w2=-1+1*1=0b=0+1=1s=3>0为苹果但是,y=wx+b只能解决线性可分的问题,对于异或问题,也就是四点对立面,需要一个曲线做分割,而这就意味着需要多层神经元和信号源共同决策分割面,也就形成了复杂的神经网络,而目标:同样是那么多边的权重。

    通过不同层间的结果作为下一层的输入,再对输入中间结果进一步处理,以此类推形成足够的深度,对于大部分问题来说都可以提到一个提升效果的作用。
    感知机只有从输出层具有激活函数,即功能神经元,这里的隐层和输出层都是具有激活函数的功能神经元。
    层与层全连接,同层神经元不连接,不跨层连接的网络又称为多层前馈神经网络。
    前馈:网络拓扑结构上不存在环和回路
    我们通过pytorch实现演示:
    二分类问题:
    假数据准备:
    # make fake data#正态分布随机产生n_data = torch.ones(100, 2)x0 = torch.normal(2*n_data, 1) # class0 x data (tensor), shape=(100, 2)y0 = torch.zeros(100) # class0 y data (tensor), shape=(100, 1)x1 = torch.normal(-2*n_data, 1) # class1 x data (tensor), shape=(100, 2)y1 = torch.ones(100) # class1 y data (tensor), shape=(100, 1)#拼接数据x = torch.cat((x0, x1), 0).type(torch.FloatTensor) # shape (200, 2) FloatTensor = 32-bit floatingy = torch.cat((y0, y1), ).type(torch.LongTensor) # shape (200,) LongTensor = 64-bit integer# tensor类型 :张量:高维特征,不可更新#variable类型:可变参数,更新w,b从而更新xy# torch can only train on Variable, so convert them to Variablex, y = Variable(x), Variable(y)设计构建神经网络:class net 继承torch.nn.model
    200\*2, 2\*10, 10\*2参数:n_fea=2,n_hidden=10,n_output=2
    输入层2
    初始化hidden层10
    self.hidden=torch.nn.linear(n_fea,n_hidden)输出层2
    self.out=torch.nn.linear(n_hidden,n_output)class Net(torch.nn.Module): def __init__(self, n_feature, n_hidden, n_output): super(Net, self).__init__() self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer self.out = torch.nn.Linear(n_hidden, n_output) # output layer#激活函数,在隐藏层后加relu非线性变换 def forward(self, x): x = F.relu(self.hidden(x)) # activation function for hidden layer x = self.out(x) return x #输出的预测值xnet = Net(n_feature=2, n_hidden=10, n_output=2) # define the networkprint(net)参数梯度下降sgd:
    optimizer = torch.optim.sgd(net.parameters(),lr=0.02)损失函数:交叉熵:负对数似然函数,并计算softmax
    loss_func = torch.nn.CrossEntropyLoss()参数初始化:
    optimizer.zero_grad()反向传播:
    loss.backward()计算梯度:
    optimizer.step()for t in range(100): out = net(x) # input x and predict based on x loss = loss_func(out, y) # must be (1. nn output, 2. target), the target label is NOT one-hotted optimizer.zero_grad() # clear gradients for next train loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients if t % 2 == 0: # plot and show learning process plt.cla() prediction = torch.max(F.softmax(out), 1)[1] pred_y = prediction.data.numpy().squeeze() target_y = y.data.numpy() plt.scatter(x.data.numpy()[:, 0], x.data.numpy()[:, 1], c=pred_y, s=100, lw=0, cmap='RdYlGn') accuracy = sum(pred_y == target_y)/200. plt.text(1.5, -4, 'Accuracy=%.2f' % accuracy, fontdict={'size': 20, 'color': 'red'}) plt.pause(0.1)plt.ioff()plt.show()交叉熵问题:
    二次代价:

    a 是 神经元的输出,其中 a = σ(z), z = wx + b
    C求导:w,b求偏导:
    受到激活函数影响

    交叉熵函数:


    不用对激活函数导数影响,不管是对w求偏导还是对b求偏导,都和e有关,而和激活函数的导数无关,比如说sigmoid函数,只有中间下降最快,两边的下降逐渐缓慢,最后斜率逼近于直线
    反向传播算法:errorBP
    可应用于大部分类型神经网络
    一般说BP算法时,多指多层前馈神经网络
    上面的数据为了演示,造的假数据形成200*2维的矩阵,一列label
    那么如何对接我们的真实数据呢?

    在我们前几节的数据中大都是这种形式,label feature_id:score
    定义一个数组
    output =[None]*3 维护3列tensor数据:
    max_fid = 123num_epochs = 10data_path = '/root/7_codes/pytorch_test/data/a8a'all_sample_size = 0sample_buffer = []with open(data_path, 'r') as fd: for line in fd: all_sample_size += 1 sample_buffer.append(line.strip())output = [None] *3#idids_buffer = torch.LongTensor(all_sample_size, max_fid)ids_buffer.fill_(1)output[0] = ids_buffer#weightweights_buffer = torch.zeros(all_sample_size, max_fid)output[1] = weights_buffer#labellabel_buffer = torch.LongTensor(all_sample_size)output[2] = label_bufferrandom.shuffle(sample_buffer)sample_id = 0for line in sample_buffer: it = line.strip().split(' ') label = int(it[0]) f_id = [] f_w = [] for f_s in it[1:]: f, s = f_s.strip().split(":") f_id.append(int(f)) f_w.append(float(s)) #解析数据后填入数组 f_id_len = len(f_id)#适当拓展数组大小 output[0][sample_id, 0:f_id_len] = torch.LongTensor(f_id) output[1][sample_id, 0:f_id_len] = torch.FloatTensor(f_w) output[2][sample_id] = label sample_id += 1fid, fweight, label = tuple(output)fid, fweight, label = map(Variable, [fid, fweight, label])接下来是设计网络结构:

    def emb_sum(embeds, weights): emb_sum = (embeds * weights.unsqueeze(2).expand_as(embeds)).sum(1) return emb_sum#输入结构为22696条数据*124维特征*32维特征向量:可以将其想象成立方体的三维条边#想要把id的特征向量和对应的score相乘并相加,需要把score也就是权重weight拓展成和id同样的维度,即22696 *124 *32 ,而sum(1)就是把124维相加成1维,最后形成22696×32维的平面矩阵,即每个样本后有个32维的向量存在,相当于把feature_id向量化class Net(torch.nn.Module): def __init__(self, n_embed, n_feature, n_hidden, n_output): super(Net, self).__init__() self.embedding = nn.Embedding(max_fid + 1, n_feature) #124特征*32隐层向量 self.hidden = torch.nn.Linear(n_feature, n_hidden) # hidden layer self.out = torch.nn.Linear(n_hidden, n_output) # output layer def forward(self, fea_ids, fea_weights): embeds = self.embedding(fea_ids) embeds = emb_sum(embeds, fea_weights)#对特征进一步处理,融合id与weight_score进行相容 embeds = nn.functional.tanh(embeds)#非线性变换 hidden = self.hidden(embeds) output = self.out(hidden) return outputnet = Net(n_embed = 32, n_feature= 32, n_hidden=10, n_output=2) # define the networkprint(net)以上,我们对接了真实的数据类型,那这个demo有没有什么问题呢?
    1.是否支持大规模数据,进行快速训练?mini-batch
    我们之前学BGD、SGD、MGD梯度下降的训练方法,在上面就运用了sgd的方法,不管是BGD还是SGD都是对所有样本一次性遍历一次,如果想提升,大致相当于MGD的方法:
    把所有样本分批处理,每批次有多少个样本(batch),循环所有样本循环多少轮(epoch)。
    每训练一个batch产生一条log,参数可保存,之前是每epoch输出一次log,这种方法训练快,性能虽然赶不上全局跑出来的准确率,但仍然一直逼近,小步快跑,每次加载到内存的数据较小,性能方面就高。
    num_epochs=10batch_size =1000data_path = './data/a8a'max_fid = 123def read_and_shuffle(filepath,shuffle=True): lines=[] with open(filepath,’r’) as fd: for linr in fd: lines.append(line.strip()) if shuffle: random.shuffle(lines) return linesclass DataLoader(object): def __init__(self,data_path,batch_size,shuffle = True): self.batch_size = batch_size self.shuffle = shuffle if os.path.isdir(data_path): self.files = [ join_path(data_path,i) for I in os.listdir(data_path)] else: self.files = [data_path] def parse(self,line_ite): for line in line_ite: it = line.strip().split(‘ ’) label = int(it[0]) f_id = [] f_w = [] for f_s in it[1:]: f,s = f_s.strip().split(‘:’) f_id.append(int(f)) f_w.append(float(s)) f_id_len = len(f_id) output = [] output.append(f_id) output.append(f_w) output.append(f_id_len) output.append(label) yield output def to_tensor(self,batch_data,sample_size): output =[None]*3 ids_buffer = torch.LongTensor(sample_size,max_fid) ids_buffer.fill_(1) output[0]=ids_buffer weights_buffer = torch.zeros(sample_size,max_fid) output[1]=weights_buffer label_buffer = torch.LongTensor(sample_size) output[2] = label_buffer for sample_id,sample in enumerate(batch_data): f_id,f_weight,f_id_len,label = sample output[0][sample_id,0:f_id_len] = torch.LongTensor(f_id) output[1][sample_id,0:f_id_len] = torch.FloatTensor(f_w) output[2][sample_id] = label return tuple(output) def batch(self): count = 0 batch_buffer =[] for f in self.files: for parse_res in self.parse(read_and_shuffle(f,self.shuffle)): count+=1 batch_buffer.append(parse_res) if count ==self.batch_size: t_size = len(batch_buffer) batch_tensor = self.to_tensor(batch_buffer,t_size) yield (batch_tensor,t_size) count = 0 batch_buffer = [] if batch_buffer: t_size = len(batch_buffer) batch_tensor = self.to_tensor(batch_buffer,t_size) yield (batch_tensor,t_size)for epoch in range(1,num_epochs+1): for (batch_id,data_tuple) in enumrate(dataloader.batch(),1): data = data_tuple[0] all_sample_size = data_tuple[1] fid,fweight,label =data fid,fweight,label=map(Variable,[fid,fweight,label] optimizer = torch.optim.SGD(net.parameters(), lr=0.02) loss_func = torch.nn.CrossEntropyLoss() # the target label is NOT an one-hotted out = net(fid, fweight) optimizer.zero_grad() # clear gradients for next train loss = loss_func(out, label) loss.backward() # backpropagation, compute gradients optimizer.step() # apply gradients if batch_id % 2 == 0:#每两批次打印一次 # plot and show learning process prediction = torch.max(F.softmax(out), 1)[1] pred_y = prediction.data.numpy().squeeze() target_y = label.data.numpy() accuracy = sum(pred_y == target_y)/float(all_sample_size) print "epoch: %s, batch_id: %s, acc: %s" % (epoch, batch_id, accuracy)2.怎么使用模型?
    现在模型可以训练自己数据了,效果也提升了,那如何把模型的参数保存下来?
    print "epoch: %s, batch_id: %s, acc: %s" % (epoch, batch_id, accuracy) checkpoint = { 'model': net.state_dict(),#网络静态参数 'epoch': epoch, 'batch': batch_id, } model_name = 'epoch_%s_batch_id_%s_acc_%s.chkpt' % (epoch, batch_id, accuracy) model_path = join_path('./model', model_name) torch.save(checkpoint, model_path)拆分上面大文件出各个参数:

    import osimport sysimport torchimport torch.nn as nnimport randomfrom torch.autograd import Variablefrom os.path import join as join_pathimport torch.nn.functional as Fmodel_path = sys.argv[1]checkpoint = torch.load(model_path)model_dict = checkpoint['model']for k, v in model_dict.items(): print k model_sub_file_name = k model_sub_file_path = join_path('./dump', model_sub_file_name) f = open(model_sub_file_path, 'w') value = model_dict[k].cpu().numpy() value.dump(f) f.close()模型怎么用?加载、预测
    import sysimport numpy as npimport mathembedding_weight = np.load('dump/embedding.weight')hidden_weight = np.load('./dump/hidden.weight')hidden_bias = np.load('./dump/hidden.bias')out_weight = np.load('./dump/out.weight')out_bias = np.load('./dump/out.bias')emb_dim = embedding_weight.shape[1]def calc_score(fids, fweights): embedding = np.zeros(emb_dim) for id, weight in zip(fids, fweights): embedding += weight * embedding_weight[id] embedding_tanh = np.tanh(embedding)#对应相乘,第二维相加成10维向量 hidden = np.sum(np.multiply(hidden_weight, embedding_tanh), axis=1) + hidden_bias out = np.sum(np.multiply(out_weight, hidden), axis=1) + out_bias return outdef softmax(x): exp_x = np.exp(x) softmax_x = exp_x / np.sum(exp_x) return softmax_xfor line in sys.stdin: ss = line.strip().split(' ') label = ss[0].strip() fids = [] fweights = [] for f_s in ss[1:]: f, s = f_s.strip().split(':') fids.append(int(f)) fweights.append(float(s)) pred_label = softmax(calc_score(fids, fweights))[1] print label, pred_label3.怎么评估?auc
    cat auc.raw | sort -t$'\t' -k2g | awk -F'\t' '($1==-1){++x;a+=y}($1==1){++y}END{print 1.0 - a/(x*y)}'acc=0.827auc=0.842569acc=0.745auc=0.494206轮数、acc都影响着auc,数字仅供参考
    总结以上,是以二分类为例,从头演示了一遍神经网络,大家可再找一些0-9手写图片分类任务体验一下,这里总结下,模型拥有参数,所以涉及参数初始化,有了参数后涉及正向传递,分为拟合和泛化两部分建立好模型后,学习过程就是确定参数的过程,使用的是梯度下降法,定义一个误差函数loss function 来衡量模型预测值与真实值(label)之间的差距,通过不断降低差距来使模型预测值逼近真实值,更新参数的反向传递是利用对应的误差值来求得梯度(batch、epoch、learning rate 、shuffle),评估标准也根据任务类型和具体要求可以使用更多的指标。
    回归任务常用均方差MSE作为误差函数。评估可用均方差表示。
    分类任务常用交叉熵(cross entropy)和softmax配合使用。评估可用准确率表示。
    回归任务:

    借用知乎yjango末尾的一句话:
    但最重要的并非训练集的准确率,而是模型从未见过的测试集的准确率(泛化能力),正如高考,真正的目的是为了解出从未见过的高考题,而不是已经做过的练习题,学习的困难之处正是要用有限的样本训练出可以符合无限同类样本规律的模型。是有限同无限的对抗,你是否想过,为什么看了那么多的道理、听了那么多人生讲座,依然过不哈这一生,一个原因就在于演讲者向你展示的例子都是训练集的样本,他所归纳的方法可以很轻松的完美拟合这些样本,但未必对你将面临的新问题同样奏效,人的一生都在学习,都在通过观察有限的例子找出问题和答案的规律,中医是,玄学是,科学同样是,但科学,是当中最为可靠的一种。
    1 留言 2019-06-05 13:46:30 奖励18点积分
  • windows下静态使用QxOrm框架并使用泛型编程 (一)

    开发环境
    Qt 5.6 for Desktop
    QxOrm版本为1.46 下载链接https://github.com/QxOrm/QxOrm

    下载完成以后QxOrm文件夹里会有简单的说明文档doc/index.html 不过有些方法是比较过时的,于是我测试了许久才测试好的框架代码。
    下载完成后打开QtCreator,选择新建项目选择 其他项目/子目录项目 类似下图的建立

    然后进入该项目的文件夹路径即200-300.pro所在路径 将QxOrm整个文件夹复制进来
    接着在200-300.pro文件中改为
    TEMPLATE = subdirsSUBDIRS += \ QxOrm这样就可以看到QxOrm项目成功加入到整体项目中了。而程序需要主项目作为运行所以我们右键200-300新建一个Application/Qt Widgets Application 我这边就叫Demo好了 。接着右键Demo添加库/外部库 库文件选择QxOrm/lib/libQxOrm.a 链接选择静态然后点下一步完成
    注意:需要在Demo.pro文件中加入QT += sql 这句话是将数据库加入到项目中去。
    目前环境就算是搭好了。下一篇继续讲如何使用。
    1 留言 2019-07-23 14:37:42 奖励8点积分
  • 【Cocos Creator实战教程(4)】——炸弹人(TiledMap相关) 精华

    1. 相关知识点
    虚拟手柄(新)
    地图制作
    碰撞检测
    动画制作

    2. 步骤2.1 制作地图2.1.1.新建19x19的地图,Tile大小32x32,导入图块资源2.1.2 建立三个图层(ground,hide,main)和一个对象层(objects)ground是背景层,用绿色的草坪图块填充满

    hide是道具层,挑选道具图块(包括出口的门)放在几个位置

    main是障碍物层,还原经典炸弹人的地图布局,每隔一个位置放一个钢块,用土块覆盖道具层的道具,在其他位置再放几个土块

    objects层有两个对象(player,enemy)标记玩家和敌人的位置信息
    最终效果如图

    2.1.3 将除了草坪图块的其他图块添加type属性,属性值分别为,soil(土块),steel(钢块),door(门),prop1(道具1)…
    2.1.4 保存地图文件和图集文件放在一起2.2 绑定地图2.2.1 新建工程Bomberman,将所需资源全部导入,项目结构如下(有些是后来添加的)
    2.2.2 将地图拖入场景(自动生成map和layer节点),将锚点改为(0,0),下面出现的player等节点也都是(0,0)为锚点2.2.3 新建脚本game.js添加为map节点组件,声明全局变量,在属性面板中拖入相关层节点
    2.3 虚拟手柄2.3.1 我们准备了上下左右炸弹五个按钮的原始状态和按下状态共十张图片素材,在场景编辑器中看着顺眼的地方摆放这五个按钮(将label删除),并将按钮的相应状态的按钮图片添加到右侧属性面板中2.3.2 为每个按钮添加点击事件,(map->game->btnBomb, btnUp, btnDown, btnLeft, btnRight)
    2.4 炸弹动画2.4.1 将炸弹图片集中的任意一个拖入场景中,将其宽高改为32x32,再拖回资源管理器中制作一个炸弹的prefab bomb,为其添加Animation组件2.4.2 新建炸弹爆炸动画clip explode,拖入bomb的属性面板2.4.3 在动画播放完成的最后一帧加入一个帧事件,响应事件方法名为exploded
    2.5 主角和敌人2.5.1 添加player和enemy为map节点的两个子节点,锚点0,0,大小32x32 (位置随意,一会我们要在代码中动态设置他们的位置)
    3. 总结
    虚拟手柄是一个比较热点的控制,现实生活中还会有圆圈控制各个角度的,也有开源项目大家可以参考
    注意节点前后层的遮挡关系

    注意:本教程部分素材来源于网络,另请大家在评论区踊跃提问发言。
    7 留言 2018-11-24 23:12:26 奖励20点积分
  • 机器学习 23 、BM25 Word2Vec

    前文链接:https://write-bug.com/article/2689.html
    word embedding什么是word embedding? 简单的来说就是将一个特征转换为一个向量。在推荐系统当中,我们经常会遇到离散特征,如userid、itemid。对于离散特征,我们一般的做法是将其转换为one-hot,但对于itemid这种离散特征,转换成one-hot之后维度非常高,但里面只有一个是1,其余都为0。这种情况下,我们的通常做法就是将其转换为embedding。
    word embedding为什么翻译成词嵌入模型?一个向量可以想象成多维空间,那么在多维空间里面找到一个点来代表这个词,相当于把这个词嵌入到了多维空间里,所以可以称为词嵌入模型。
    one-hot编码形式
    输入:
    apple on a apple tree – Bag of Words: ["apple", "on", "a", "tree"]– Word Embedding的输出就是每个word的向量表示
    最简单直接的是one-hot编码形式:那么每个word都对应了一种数值表示
    例如,apple对应的vector就是[1, 0, 0, 0],a对应的vector就是[0, 0, 1, 0]
    两类表达方式:基于频率:(本质:one-hot)
    Count vector:需要穷举所有单词,形成非常庞大、稀疏的矩阵,穷举时,计算每词的词频TF
    每个文档用词向量的组合来表示,每个词的权重用其出现的次数来表示
    假设语料库内容如下:
    – D1: He is a boy. – D2: She is a girl, good girl.那么可以构建如下2 × 7维的矩阵

    tfidf vector:同时考虑了TF与IDF
    IDF=log(N/n)。 – N代表语料库中文档的总数 – n代表某个word在几个文档中出现过;
    Co-Occurrence vector:只考虑滑动窗口内单词的共现,等于考虑了上下文之间的关系
    前面两类方法,每个字都是独立的,缺少了语义的学习
    例如: He is not lazy. He is intelligent. He is smart.
    如果Context Window大小为2,那么可以得到如下的共现矩阵:

    he和is计算方法(窗口共现):

    基于模型:
    CBOW
    Skip-Gram
    技术升级TF-IDF-》BM25
    Co-Occurrence-》WordtoVector
    BM25:定位:搜索引擎相关性评分
    通常我们在搜索信息时,搜索的句子为query,关联出的排序列表为item,即:
    query -》item
    那么可分词query-》token1 token2 token3
    同时可把item -》doc-》token token token
    词在文章中的评分:token1 -》doc :score1(tfidf)
    所以相关性评分query-》item:score1+score2+…
    公式可表示为:
    S(q,d)=∑Wi*R(qi,d)Wi用idf表示:

    N为索引中的全部文档数
    n(qi)为包含了qi的文档数
    相关性评分R:


    k1,k2,b为调节因子,一般k1=2,b=0.75
    b是调整文档长度对相关性影响的大小 • b越大,文档长度影响R越大,反之越小


    fi为qi在d中的出现频率 =TF
    qfi为qi在Query中的出现频率 =query中的TF

    在之前的tfidf中TF越大最后的分数也越大,而通常情况下,文章越长,TF大的可能性越大,没有考虑到文章长度的问题,这里BM25考虑到了这个问题,如果文章过长就要给定一定的打压,那么如何界定文章长度呢?除以avgdl,即所有文章平均长度

    dl为文档d的长度
    avgdl为所有文档的平均长度

    公式简化:
    绝大部分情况下,qi在Query中只会出现一次,即qfi=1


    BM25实践:
    1.gensim word2vec
    语料库-》每个词的50维向量即word embedding
    from gensim.models import word2vecmodel=word2vec.Word2Vec(sentences,size=50,window=5,min_count=1,workers=6)model.wv.save_word2vec_format(file_voc, binary=False)2.coumpute_idf
    每个词的idf
    file_corpus='../data/file_corpus.txt'file_voc='../data/voc.txt'file_idf='../data/idf.txt'class ComIdf(object): def __init__(self,file_corpus,file_voc,file_idf): self.file_corpus=file_corpus self.file_voc=file_voc self.file_idf=file_idf self.voc=load_voc(self.file_voc) self.corpus_data=self.load_corpus() self.N=len(self.corpus_data) def load_corpus(self): input_data = codecs.open(self.file_corpus, 'r', encoding='utf-8') return input_data.readlines() def com_idf(self,word): n = 0 for _,line in enumerate(self.corpus_data): n+=line.count(word) idf=math.log(1.0*self.N/n+1) return {word:idf} def parts(self): words=set(self.voc.keys()) multiprocessing.freeze_support() cores=multiprocessing.cpu_count() pool=multiprocessing.Pool(processes=cores-2) reuslt=pool.map(self.com_idf,words) idf_dict=dict() for r in reuslt: k=list(r.keys())[0] v=list(r.values())[0] idf_dict[k]=idf_dict.get(k,0)+v with codecs.open(self.file_idf,'w',encoding='utf-8') as f:f.write(json.dumps(idf_dict,ensure_ascii=False,indent=2,sort_keys=False))if __name__ == '__main__': t1 = time.time() IDF=ComIdf(file_corpus,file_voc,file_idf)IDF.parts()3.get_sentence
    分割句子,取出最常见句子1w个
    def get_sentence(): file_corpus=codecs.open('../data/file_corpus.txt','r',encoding='utf-8') file_sentence=codecs.open('../data/file_sentence.txt','w',encoding='utf-8') st=dict() for _,line in enumerate(file_corpus): line=line.strip() blocks=re_han.split(line) for blk in blocks: if re_han.match(blk) and len(blk)>10: st[blk]=st.get(blk,0)+1 st=sorted(st.items(),key=lambda x:x[1],reverse=True) for s in st[:10000]: file_sentence.write(s[0]+'\n') file_corpus.close() file_sentence.close()get_sentence()4.test
    各种算法计算打分:cos\idf\bm25\jaccard
    file_voc='./data/voc.txt'file_idf='./data/idf.txt'file_userdict='./data/medfw.txt'class SSIM(object): def __init__(self): t1 = time.time() self.voc=load_voc(file_voc) print("Loading word2vec vector cost %.3f seconds...\n" % (time.time() - t1)) t1 = time.time() self.idf=load_idf(file_idf) print("Loading idf data cost %.3f seconds...\n" % (time.time() - t1)) jieba.load_userdict(file_userdict) def M_cosine(self,s1,s2): s1_list=jieba.lcut(s1) s2_list=jieba.lcut(s2) v1=np.array([self.voc[s] for s in s1_list if s in self.voc]) v2=np.array([self.voc[s] for s in s2_list if s in self.voc]) v1=v1.sum(axis=0) v2=v2.sum(axis=0) sim=1-spatial.distance.cosine(v1,v2) return sim def M_idf(self,s1, s2): v1, v2 = [], [] s1_list = jieba.lcut(s1) s2_list = jieba.lcut(s2) for s in s1_list: idf_v = self.idf.get(s, 1) if s in self.voc: v1.append(1.0 * idf_v * self.voc[s]) for s in s2_list: idf_v = self.idf.get(s, 1) if s in self.voc: v2.append(1.0 * idf_v * self.voc[s]) v1 = np.array(v1).sum(axis=0) v2 = np.array(v2).sum(axis=0) sim = 1 - spatial.distance.cosine(v1, v2) return sim def M_bm25(self,s1, s2, s_avg=10, k1=2.0, b=0.75): bm25 = 0 s1_list = jieba.lcut(s1) for w in s1_list: idf_s = self.idf.get(w, 1) bm25_ra = s2.count(w) * (k1 + 1) bm25_rb = s2.count(w) + k1 * (1 - b + b * len(s2) / s_avg) bm25 += idf_s * (bm25_ra / bm25_rb) return bm25 def M_jaccard(self,s1, s2): s1 = set(s1) s2 = set(s2) ret1 = s1.intersection(s2) ret2 = s1.union(s2) jaccard = 1.0 * len(ret1)/ len(ret2) return jaccard def ssim(self,s1,s2,model='cosine'): if model=='idf': f_ssim=self.M_idf elif model=='bm25': f_ssim=self.M_bm25 elif model=='jaccard': f_ssim=self.M_jaccard else: f_ssim = self.M_cosine sim=f_ssim(s1,s2) return simsm=SSIM()ssim=sm.ssimdef test(): test_data=[u'临床表现及实验室检查即可做出诊断', u'面条汤等容易消化吸收的食物为佳', u'每天应该摄入足够的维生素A', u'视患者情况逐渐恢复日常活动', u'术前1天开始预防性运用广谱抗生素'] model_list=['cosine','idf','bm25','jaccard'] file_sentence=codecs.open('./data/file_sentence.txt','r',encoding='utf-8') train_data=file_sentence.readlines() for model in model_list: t1 = time.time() dataset=dict() result=dict() for s1 in test_data: dataset[s1]=dict() for s2 in train_data: s2=s2.strip() if s1!=s2: sim=similarity.ssim(s1,s2,model=model) dataset[s1][s2]=dataset[s1].get(s2,0)+sim for r in dataset: top=sorted(dataset[r].items(),key=lambda x:x[1],reverse=True) result[r]=top[0] with codecs.open('./data/test_result.txt','a') as f: f.write('--------------The result of %s method------------------\n '%model) f.write('\tThe computing cost %.3f seconds\n'% (time.time() - t1)) f.write(json.dumps(result, ensure_ascii=True, indent=2, sort_keys=False)) f.write('\n\n') file_sentence.close()test()word2vec继承了之前的窗口滑动思想,得到更好的语义理解
    涉及的技术点:
    – Hierarchical softmax分层softmax
    – Negative sampling负采样
    两种方式:

    CBOW:根据前后词,预测中间词
    Skip-Gram :根据中间词预测前后词,工业用的更多

    CBOW一句话包含了很多单词,时间窗口选择到了一个中间词,w3,假设左边右边各学习两个,中间词假设不知道,那么根据w1245猜测中间这个w3词,故意装作w3不知道做预测,那么w3就是中心词label,w1245就是周围词训练数据,随着时间窗口滑动,同理w4又继续成为了中间词,那么由此类推形成了很多训练样本


    Input layer:由one-hot编码的输入上下文{x1,…,xC}组成, 其中窗口大小为C,词汇表大小为V
    Hidden layer:隐藏层是N维的向量
    Output layer:被one-hot编码的输出单词y

    被one-hot编码的输入向量通过一个V×N维的权重矩阵W连接到隐藏层; 隐藏层通过一个N×V的权重矩阵W′连接到输出层
    不管输入多少词,相加并都映射为中间得隐层向量上,再做一个softmax输出
    Skip-Gram:CBOW的网络翻转
    样本怎么得到的?

    input word和output word都是one-hot编码的向量。最终模型的输出是一个概率分布

    softmax分类需要所有单词:这里是50002个单词,需要遍历一遍,那么每次计算都需要一定时间,不管是多次还是一次时间成本都很高,所以引出了分层softmax:
    Hierarchical softmax哈夫曼树

    是二叉树的一种特殊形式,又称为最优二叉树,其主要作用在于数据压缩和编码长度的优化
    定义:给定n个权值作为n个叶子结点,构造一棵二叉树,若带权路径长度(WPL)达到最小,称这样的二叉 树为最优二叉树,也称为霍夫曼树(Huffman Tree)
    WPL:树的带权路径长度,所有叶子结点的带权路径长度之和

    让wpl最小,压缩数据信息
    主要用于编码:从根节点出发,向左为0,向右为1
    等长编码和哈夫曼编码


    生成:
    每个节点按权重大小从小到大排序
    两个最小的为叶子节点相加成为父节点并和其他最小的成两个叶子节点,最终形成哈夫曼树
    之前做softmax,50002个单词需要从头到尾计算一遍,把词表做成树,每个叶子节点都是一个单词,树的每个结点都是一个sigmoid,来了一个单词做一个lr二分类向左还是向右分类直至最后叶子节点为所要的词
    Negative sampling负采样
    思想:每次让一个训练样本仅更新一小部分权重
    需要离错误的样本更远

    不对所有输出神经元对应的权值进行更新,只是随机选取几个“negative”词,更新他们对应的权重。当然, 我们也会更新”positive”的对应权值
    随机取的样本只要不共现就是负样本,概率大的会多选,即出现多的单词自然被取出的概率越大
    实际论文实现:


    CBOW:input是context(周围词)而output是中心词,训练过程中其实是在从output的loss学习周围词的信息 也就是embedding,但是在中间层是average的,一共预测V(vocab size)次就够了
    一次softmax

    Skipgram:用中心词预测周围词,预测的时候是一对word pair,等于对每一个中心词都有K个词作为output, 对于一个词的预测有K次,所以能够更有效的从context中学习信息,但是总共预测K*V词
    多次softmax

    Skip-Gram的训练时间更长,更准确
    Cbow训练速度快,Skip-Gram更好处理生僻字

    Word2Vec实践:
    import sysimport torchimport torch.nn as nnimport torch.nn.functional as Fimport torch.utils.data as tudfrom torch.nn.parameter import Parameterfrom collections import Counterimport numpy as npimport randomimport mathimport pandas as pdimport scipyimport sklearnfrom sklearn.metrics.pairwise import cosine_similarity# 超参数K = 100 # number of negative samplesC = 3 # nearby words thresholdNUM_EPOCHS = 2 # The number of epochs of trainingMAX_VOCAB_SIZE = 30000 # the vocabulary sizeBATCH_SIZE = 128 # the batch sizeLEARNING_RATE = 0.2 # the initial learning rateEMBEDDING_SIZE = 100# 分词def word_tokenize(text): return text.split()with open("./data/text8", "r") as fin: text = fin.read()text = [w for w in word_tokenize(text.lower())]print(text[1])vocab = dict(Counter(text).most_common(MAX_VOCAB_SIZE - 1))#计数器,取前30000个高频元素vocab["<unk>"] = len(text) - np.sum(list(vocab.values()))print (np.sum(list(vocab.values())))print (len(text))print (vocab["<unk>"])print (len(vocab))#output:originated170052071700520769010330000idx_to_word = [word for word in vocab.keys()]word_to_idx = {word: i for i, word in enumerate(idx_to_word)}word_counts = np.array([count for count in vocab.values()], dtype=np.float32)word_freqs = word_counts / np.sum(word_counts)word_freqs = word_freqs ** (3. / 4.)word_freqs = word_freqs / np.sum(word_freqs) # 用来做 negative samplingVOCAB_SIZE = len(idx_to_word)print(VOCAB_SIZE)#text 每个单词#word_to_idx 单词id化:dict#idx_to_word 每个单词#word_freqs 负采样概率#word_counts 单词计数列表#数据样本列举:class WordEmbeddingDataset(tud.Dataset): def __init__(self, text, word_to_idx, idx_to_word, word_freqs, word_counts): ''' text: a list of words, all text from the training dataset word_to_idx: the dictionary from word to idx idx_to_word: idx to word mapping word_freq: the frequency of each word word_counts: the word counts ''' super(WordEmbeddingDataset, self).__init__() self.text_encoded = [word_to_idx.get(t, VOCAB_SIZE - 1) for t in text] self.text_encoded = torch.Tensor(self.text_encoded).long() self.word_to_idx = word_to_idx self.idx_to_word = idx_to_word self.word_freqs = torch.Tensor(word_freqs) self.word_counts = torch.Tensor(word_counts) def __len__(self): ''' 返回整个数据集(所有单词)的长度 ''' return len(self.text_encoded) def __getitem__(self, idx): ''' 这个function返回以下数据用于训练 - 中心词 - 这个单词附近的(positive)单词 - 随机采样的K个单词作为negative sample ''' center_word = self.text_encoded[idx] pos_indices = list(range(idx - C, idx)) + list(range(idx + 1, idx + C + 1)#窗口左3右3 pos_indices = [i % len(self.text_encoded) for i in pos_indices]#去除类似第一次左边无词这种情况 pos_words = self.text_encoded[pos_indices] neg_words = torch.multinomial(self.word_freqs, K * pos_words.shape[0], True) return center_word, pos_words, neg_wordsdataset = WordEmbeddingDataset(text, word_to_idx, idx_to_word, word_freqs, word_counts)dataloader = tud.DataLoader(dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=4)
    class EmbeddingModel(nn.Module): def __init__(self, vocab_size, embed_size): ''' 初始化输出和输出embedding ''' super(EmbeddingModel, self).__init__() self.vocab_size = vocab_size self.embed_size = embed_size initrange = 0.5 / self.embed_size self.out_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False) self.out_embed.weight.data.uniform_(-initrange, initrange) self.in_embed = nn.Embedding(self.vocab_size, self.embed_size, sparse=False) self.in_embed.weight.data.uniform_(-initrange, initrange) def forward(self, input_labels, pos_labels, neg_labels): ''' input_labels: 中心词, [batch_size] pos_labels: 中心词周围 context window 出现过的单词 [batch_size * (window_size * 2)] neg_labelss: 中心词周围没有出现过的单词,从 negative sampling 得到 [batch_size, (window_size * 2 * K)] return: loss ''' input_embedding = self.in_embed(input_labels) # B * embed_size pos_embedding = self.out_embed(pos_labels) # B * (2*C) * embed_size neg_embedding = self.out_embed(neg_labels) # B * (2*C * K) * embed_size print("input_embedding size:", input_embedding.size()) print("pos_embedding size:", pos_embedding.size()) print("neg_embedding size:", neg_embedding.size()) log_pos = torch.bmm(pos_embedding, input_embedding.unsqueeze(2)).squeeze() # B * (2*C) log_neg = torch.bmm(neg_embedding, -input_embedding.unsqueeze(2)).squeeze() # B * (2*C*K)先拓展再压缩 print("log_pos size:", log_pos.size()) print("log_neg size:", log_neg.size()) log_pos = F.logsigmoid(log_pos).sum(1)#1为横向压缩,2为竖向压缩,可以试试玩 log_neg = F.logsigmoid(log_neg).sum(1) loss = log_pos + log_neg print("log_pos size:", log_pos.size()) print("log_neg size:", log_neg.size()) print("loss size:", loss.size()) return -loss def input_embeddings(self): return self.in_embed.weight.data.cpu().numpy()model = EmbeddingModel(VOCAB_SIZE, EMBEDDING_SIZE)if USE_CUDA: model = model.cuda()if __name__ == '__main__': optimizer = torch.optim.SGD(model.parameters(), lr=LEARNING_RATE) for e in range(NUM_EPOCHS): for i, (input_labels, pos_labels, neg_labels) in enumerate(dataloader): print(input_labels.size()) print(pos_labels.size()) print(neg_labels.size()) input_labels = input_labels.long() pos_labels = pos_labels.long() neg_labels = neg_labels.long() if USE_CUDA: input_labels = input_labels.cuda() pos_labels = pos_labels.cuda() neg_labels = neg_labels.cuda() optimizer.zero_grad() loss = model(input_labels, pos_labels, neg_labels).mean() loss.backward() optimizer.step() if i % 100 == 0: with open(LOG_FILE, "a") as fout: fout.write("epoch: {}, iter: {}, loss: {}\n".format(e, i, loss.item())) print("epoch: {}, iter: {}, loss: {}".format(e, i, loss.item())) embedding_weights = model.input_embeddings() torch.save(model.state_dict(), "embedding-{}.th".format(EMBEDDING_SIZE))使用场景:
    1.item title检索 +ANN+word avg,word average: – 对所有输入词向量求和并取平均 – 例如:输入三个4维词向量:(1,2,3,4),(9,6,11,8),(5,10,7,12) – 那么我们word2vec映射后的词向量就是(5,6,7,8)
    2.模型初始化:处理特征等
    3.近义词(语义理解):可通过计算词向量的加减,与相似度可进行语义理解
    实践例:
    def evaluate(filename, embedding_weights): if filename.endswith(".csv"): data = pd.read_csv(filename, sep=",") else: data = pd.read_csv(filename, sep="\t") human_similarity = [] model_similarity = [] for i in data.iloc[:, 0:2].index: word1, word2 = data.iloc[i, 0], data.iloc[i, 1] if word1 not in word_to_idx or word2 not in word_to_idx: continue else: word1_idx, word2_idx = word_to_idx[word1], word_to_idx[word2] word1_embed, word2_embed = embedding_weights[[word1_idx]], embedding_weights[[word2_idx]] model_similarity.append(float(sklearn.metrics.pairwise.cosine_similarity(word1_embed, word2_embed))) human_similarity.append(float(data.iloc[i, 2])) return scipy.stats.spearmanr(human_similarity, model_similarity)# , model_similaritydef find_nearest(word): index = word_to_idx[word] embedding = embedding_weights[index] cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])#1-cosine return [idx_to_word[i] for i in cos_dis.argsort()[:10]]model.load_state_dict(torch.load("model/embedding-{}.th".format(EMBEDDING_SIZE), map_location='cpu'))embedding_weights = model.input_embeddings()# 找相似的单词for word in ["good", "computer", "china", "mobile", "study"]: print(word, find_nearest(word))# 单词之间的关系,寻找nearest neighborsman_idx = word_to_idx["man"]king_idx = word_to_idx["king"]woman_idx = word_to_idx["woman"]embedding = embedding_weights[woman_idx] - embedding_weights[man_idx] + embedding_weights[king_idx]cos_dis = np.array([scipy.spatial.distance.cosine(e, embedding) for e in embedding_weights])for i in cos_dis.argsort()[:20]: print(idx_to_word[i])
    1 留言 2019-06-23 14:57:53 奖励13点积分
  • 中山大学智慧健康服务平台应用开发-Broadcast使用

    一、实验题目中山大学智慧健康服务平台应用开发
    二、实现内容2.1 Broadcast 使用2.1.1 实验目的
    掌握 Broadcast 编程基础
    掌握动态注册 Broadcast 和静态注册 Broadcast
    掌握Notification 编程基础
    掌握 EventBus 编程基础

    2.1.2 实验内容在之前的基础上,实现静态广播、动态广播两种改变Notification 内容的方法。
    要求在启动应用时,会有通知产生,随机推荐一个食品

    点击通知跳转到所推荐食品的详情界面

    点击收藏图标,会有对应通知产生,并通过Eventbus在收藏列表更新数据

    点击通知返回收藏列表

    实现方式要求:启动页面的通知由静态广播产生,点击收藏图标的通知由动态广播产生。
    2.1.3 验收内容
    静态广播:启动应用是否有随机推荐食品的通知产生。点击通知是否正确跳转到所推荐食品的详情界面
    动态广播:点击收藏后是否有提示食品已加入收藏列表的通知产生。同时注意设置launchMode。点击通知是否跳转到收藏列表
    Eventbus:点击收藏列表图标是否正确添加食品到收藏列表。每点击一次,添加对应的一个食品到收藏列表并产生一条通知

    三、实验结果3.1 实验截图下图为打开app后,产生一个推荐食品的通知

    下图为点击该通知,会跳转至食物详情页面。点击收藏按钮时,产生收藏的通知

    下图为点击收藏通知,跳转至收藏列表页面

    3.2 实验步骤以及关键代码3.2.1 利用静态广播实现今日推荐功能在AndroidManifest.xml注册静态广播接受方其中StaticReceiver为类名
    <receiver android:name=".StaticReceiver"> <intent-filter> <action android:name="com.example.asus.health.MyStaticFilter" /> </intent-filter> </receiver>
    实现StaticReceiver类,重构onReceive函数其中要根据intent的action来确定是否接受该广播的内容,来实现功能,而需要实现的包括一个notification的弹出以及点击它跳转到详情页面。
    notification部分由builder的设置函数来设置名字,内容,等等,由NotificationManager来发出该notification。
    点击后跳转的功能则需要给builder设置一个ContentIntent,这个intent为PeddingIntent,即不会马上跳转,而是需要等待用户的操作。它的构造函数传递了一个普通的intent,而这个intent是携带了所需的数据来生成详情页面。
    public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(STATICACTION)){ Bundle bundle = intent.getExtras(); //TODO:添加Notification部分 Notification.Builder builder = new Notification.Builder(context); //跳回主页面 Intent intent2 = new Intent(context,Details.class); Bundle bundle2 = new Bundle(); String s[] = new String [5]; s[0] = ((MyCollection)bundle.getSerializable("collect")).getName(); s[1] = ((MyCollection)bundle.getSerializable("collect")).getMaterial(); s[2] = ((MyCollection)bundle.getSerializable("collect")).getType(); s[3] = ((MyCollection)bundle.getSerializable("collect")).getContent(); s[4] = ((MyCollection)bundle.getSerializable("collect")).getIs_star()?"yes":"no"; bundle2.putStringArray("msg",s); intent2.putExtras(bundle2); PendingIntent contentIntent = PendingIntent.getActivity( context, 0, intent2, PendingIntent.FLAG_UPDATE_CURRENT); //对Builder进行配置 builder.setContentTitle("今日推荐") //设置通知栏标题:发件人 .setContentText(((MyCollection)bundle.getSerializable("collect")).getName()) //设置通知栏显示内容:短信内容 .setTicker("您有一条新消息") //通知首次出现在通知栏,带上升动画效果的 .setSmallIcon(R.mipmap.empty_star) //设置通知小ICON 空星 .setContentIntent(contentIntent) //传递内容 .setAutoCancel(true); //设置这个标志当用户单击面板就可以让通知将自动取消 //获取状态通知栏管理 NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); //绑定Notification,发送通知请求 Notification notify = builder.build(); manager.notify(0,notify); } }
    在FoodList主页面onCreat时生成广播注意action的字符串要与上面的Reciver的相同,不然无法正确接受广播,随机数则是返回一个0到n-1的整数表示随机生成一个推荐食物,然后将所需数据放入intent,通过sendBroadcast函数发送该广播。
    //打开应用时,发送一个静态广播 private void boardcastforOpen(int n){ final String STATICACTION = "com.example.asus.health.MyStaticFilter"; Random random = new Random(); int num = random.nextInt(n); //返回一个0到n-1的整数 Intent intentBroadcast = new Intent(STATICACTION); //定义Intent Log.i("se",getPackageName()); Bundle bundles = new Bundle(); bundles.putSerializable("collect", data2.get(num)); intentBroadcast.putExtras(bundles); sendBroadcast(intentBroadcast); }
    3.2.2 利用动态广播实现收藏信息提示实现广播接受器DynamicReceiver类与静态Receiver的实现过程差不多,一样是实现builder,然后放置peddingIntent,这里就不再重复放代码。唯一的不同点在于,它所要跳回的是收藏夹页面,即FoodList主页面,这里要对intent设置flag,否则无法在foodlist中get到新的intent。
    //跳回收藏夹 Intent intent2 = new Intent(context,FoodList.class); Bundle bundle2 = new Bundle(); bundle2.putString("tag","collect"); intent2.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); intent2.putExtras(bundle2);
    对详情页面的收藏事件进行处理在监听器中设置发送广播的intent,当按下收藏后会发出广播,并传递参数。其中还使用了eventbus来传递收藏的数据。
    //处理收藏按钮 final ImageView collect_but = findViewById(R.id.collect); collect_but.setOnClickListener(new View.OnClickListener(){ @Override public void onClick(View v){ temp.setIs_collected(true); Toast.makeText(Details.this, "已收藏",Toast.LENGTH_SHORT).show(); EventBus.getDefault().post(new MessageEvent(temp)); //发送广播 Intent intentBroadcast = new Intent(); intentBroadcast.setAction(DYNAMICACTION); sendBroadcast(intentBroadcast); } });
    在FoodList注册动态接收器以及注销动态接收器注意分别要在onCreate函数以及onDestroy函数中实现注册与注销 。
    3.2.3 使用EventBus来实现数据的传输在这一点上,要改进上一周实验的代码,不再需要点击返回按钮利用setResult以及onActivityResult两个函数来返回信息。而是通过eventbus的订阅发布模式。
    在FoodList来注册订阅者,订阅消息。而在Detail来发布信息。其中onMessageEvent函数用于收到发布消息后,来调用之前的接口函数刷新列表。
    发布消息就是上面点击收藏按钮后 EventBus.getDefault().post(new MessageEvent(temp));
    //注册订阅者(注册收藏列表所在Activity为订阅者) EventBus.getDefault().register(this); @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { Log.i("hello","this is eventbus."); MyCollection mc = event.getCollection(); refreshList(mc,simpleAdapter); }
    3.2.4 从详情跳转回收藏夹这里由于收藏夹FoodList为经常返回的页面,故这里使用了android:launchMode=”singleInstance”,即不让它重复创建新的活动。
    所以再get我的返回intent时是拿不到新的intent的,这里需要重写onNewIntent函数,而且接收新的intent要在onResume中。
    这里要求要显示收藏夹页面,所以要将食物列表隐藏起来。
    @Override protected void onNewIntent(Intent intent){ super.onNewIntent(intent); setIntent(intent); } @Override protected void onResume(){ super.onResume(); //处理跳转 Bundle bundle=this.getIntent().getExtras(); if(bundle != null) { String str = bundle.getString("tag"); Log.i("resume2",str); if (str.equals("collect")) { findViewById(R.id.recyclerView).setVisibility(View.GONE); findViewById(R.id.listView).setVisibility(View.VISIBLE);//设置Favourite可见 tag_for_foodlist = false; f_but.setImageResource(R.mipmap.mainpage); } } }
    3.3 实验遇到的困难以及解决思路3.3.1 在安卓8.0版本中无法使用静态接收器,发送广播后,无法成功接收方法一:解决这个问题,需要给receiver设置component,给予它的包名以及类名。
    intent.setComponent(new ComponentName(getPackageName(),getPackageName()+".xxxxReceiver"));
    方法二:下载新的虚拟机,使用安卓7.0版本,则可以顺利接收静态广播。
    3.3.2 使用EventBus时候,FoodList主页面无法得到post的信息我按部就班地在Detail页面收藏按钮post,在FoodList订阅消息却毫无反应。首先,我认为是我的接收函数写错了,没有订阅到信息。通过Log.i发现确实没有进入到onMessageEvent函数中,于是对这个问题进行了查阅。网上有推荐使用stickyPost的,怀疑原因出在信息接收发生在创建接收者之前,但显然与函数执行顺序不符,它是先来到了主页面,所以必然创建了receiver。
    经过大半个小时的查找发现是,post传的参数错误,并没有生成MessageEvent,而是错误地直接传递了数据包。
    //错误EventBus.getDefault().post(temp);//正确EventBus.getDefault().post(new MessageEvent(temp));
    3.3.3 从收藏通知返回主页面时候,出现无法拿到intent的情况由于我是在动态接收方的builder绑定了Peddingintent,当点击通知,应该要返回这个intent到主页面,然而主页面所获取的intent是空值。这一点让我怀疑了很久,问了同学才得知,这是声明了singleInstance的问题。
    比如说在一个应用中A activity 跳转至 B activity 在跳转至 C activity 然后C做了一定的操作之后再返回A 界面。这样在A activity的启动模式设置为singleTask后。C界面跳转至A界面时,就会去判断栈内是否有改Activity实例,如果有就直接执行A界面的onNewIntent()方法,我们就可以把逻辑处理放在改生命周期方法中,如果没有就会走Activity的oncrate方法去创建实例。
    所以这里需要重写onNewIntent来获取新的intent,而不是直接传递旧intent导致错误。
    @Override protected void onNewIntent(Intent intent){ super.onNewIntent(intent); setIntent(intent); }
    四、实验思考及感想这次实验需要在安卓8.0与安卓7.0之间权衡,有些属性方法已经在8.0版本出现了变化,所以当使用错误,出现奇怪的现象时,第一步先检查自己的代码逻辑有否问题,第二步就是要查阅是否存在版本的兼容性问题产生了这些错误。这次作业就是如此,关于广播的实现,个人还是喜欢动态广播,不需要再静态注册在manifest中,代码也更加简便。
    对于不同活动之间的传输,使用EventBus比之前的intent更加方便,减轻了耦合性,不用经常记住,哪个intent返回哪里,所以这次我也修改了不少前面实验使用intent的代码。除此之外,充分理解信息传输还需要理解一下活动的存活过程,什么时候调用onCreat,什么时候使用onResume。
    2 留言 2019-07-17 23:38:40 奖励17点积分
  • 基于WinInet实现的HTTP文件下载 精华

    背景之前写过的网络数据传输的小程序都是基于Socket去写的,所以,如果要用Socket传输数据到网站,还需要根据域名获取服务器的IP地址,然后再建立连接,传输数据。虽然,Socket也可以实现网络传输,但是,总感觉不是很方便。所以,后来随着知识面的拓展,了解到Windows还专门提供了WinInet网络库,封装了比较简便的接口,去实现HTTP和FTP等传输协议的数据传输。
    本文就是基于WinInet网络库,实现通过HTTP传输协议下载文件功能的小程序。现在,就把开发过程的思路和编程分享给大家。
    主要函数介绍介绍HTTP下载文件使用到的主要的WinInet库中的API函数。
    1. InternetOpen介绍
    函数声明
    HINTERNET InternetOpen(In LPCTSTR lpszAgent,In DWORD dwAccessType,In LPCTSTR lpszProxyName,In LPCTSTR lpszProxyBypass,In DWORD dwFlags);
    参数lpszAgent指向一个空结束的字符串,该字符串指定调用WinInet函数的应用程序或实体的名称。使用此名称作为用户代理的HTTP协议。dwAccessType指定访问类型,参数可以是下列值之一:



    Value
    Meaning




    INTERNET_OPEN_TYPE_DIRECT
    使用直接连接网络


    INTERNET_OPEN_TYPE_PRECONFIG
    获取代理或直接从注册表中的配置,使用代理连接网络


    INTERNETOPEN_TYPE_PRECONFIG WITH_NO_AUTOPROXY
    获取代理或直接从注册表中的配置,并防止启动Microsoft JScript或Internet设置(INS)文件的使用


    INTERNET_OPEN_TYPE_PROXY
    通过代理的请求,除非代理旁路列表中提供的名称解析绕过代理,在这种情况下,该功能的使用



    lpszProxyName指针指向一个空结束的字符串,该字符串指定的代理服务器的名称,不要使用空字符串;如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY,则此参数应该设置为NULL。
    lpszProxyBypass指向一个空结束的字符串,该字符串指定的可选列表的主机名或IP地址。如果dwAccessType未设置为INTERNET_OPEN_TYPE_PROXY的 ,参数省略则为NULL。
    dwFlags参数可以是下列值的组合:



    VALUE
    MEANING




    INTERNET_FLAG_ASYNC
    使异步请求处理的后裔从这个函数返回的句柄


    INTERNET_FLAG_FROM_CACHE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND


    INTERNET_FLAG_OFFLINE
    不进行网络请求,从缓存返回的所有实体,如果请求的项目不在缓存中,则返回一个合适的错误,如ERROR_FILE_NOT_FOUND



    返回值成功:返回一个有效的句柄,该句柄将由应用程序传递给接下来的WinInet函数。失败:返回NULL。

    2. InternetConnect介绍
    函数声明
    HINTERNET WINAPI InternetConnect( HINTERNET hInternet, LPCTSTR lpszServerName, INTERNET_PORT nServerPort, LPCTSTR lpszUserName, LPCTSTR lpszPassword, DWORD dwService, DWORD dwFlags, DWORD dwContext);
    参数说明hInternet:由InternetOpen返回的句柄。lpszServerName:连接的ip或者主机名nServerPort:连接的端口。lpszUserName:用户名,如无置NULL。lpszPassword:密码,如无置NULL。dwService:使用的服务类型,可以使用以下

    INTERNET_SERVICE_FTP = 1:连接到一个 FTP 服务器上INTERNET_SERVICE_GOPHER = 2INTERNET_SERVICE_HTTP = 3:连接到一个 HTTP 服务器上
    dwFlags:文档传输形式及缓存标记。一般置0。dwContext:当使用回叫信号时, 用来识别应用程序的前后关系。返回值成功返回非0。如果返回0。要InternetCloseHandle释放这个句柄。

    3. HttpOpenRequest介绍
    函数声明
    HINTERNET HttpOpenRequest( _In_ HINTERNET hConnect, _In_ LPCTSTR lpszVerb, _In_ LPCTSTR lpszObjectName, _In_ LPCTSTR lpszVersion, _In_ LPCTSTR lpszReferer, _In_ LPCTSTR *lplpszAcceptTypes, _In_ DWORD dwFlags, _In_ DWORD_PTR dwContext);
    参数
    hConnect:由InternetConnect返回的句柄。
    lpszVerb:一个指向某个包含在请求中要用的动词的字符串指针。如果为NULL,则使用“GET”。
    lpszObjectName:一个指向某个包含特殊动词的目标对象的字符串的指针。通常为文件名称、可执行模块或者查找标识符。
    lpszVersion:一个指向以null结尾的字符串的指针,该字符串包含在请求中使用的HTTP版本,Internet Explorer中的设置将覆盖该参数中指定的值。如果此参数为NULL,则该函数使用1.1或1.0的HTTP版本,这取决于Internet Explorer设置的值。
    lpszReferer:一个指向指定了包含着所需的URL (pstrObjectName)的文档地址(URL)的指针。如果为NULL,则不指定HTTP头。
    lplpszAcceptTypes:一个指向某空终止符的字符串的指针,该字符串表示客户接受的内容类型。如果该字符串为NULL,服务器认为客户接受“text/*”类型的文档 (也就是说,只有纯文本文档,并且不是图片或其它二进制文件)。内容类型与CGI变量CONTENT_TYPE相同,该变量确定了要查询的含有相关信息的数据的类型,如HTTP POST和PUT。
    dwFlags:dwFlags的值可以是下面一个或者多个。



    价值
    说明




    INTERNET_FLAG_DONT_CACHE
    不缓存的数据,在本地或在任何网关。 相同的首选值INTERNET_FLAG_NO_CACHE_WRITE。


    INTERNET_FLAG_EXISTING_CONNECT
    如果可能的话,重用现有的连接到每个服务器请求新的请求而产生的InternetOpenUrl创建一个新的会话。 这个标志是有用的,只有对FTP连接,因为FTP是唯一的协议,通常在同一会议执行多个操作。 在Win 32 API的缓存一个单一的Internet连接句柄为每个HINTERNET处理产生的InternetOpen。


    INTERNET_FLAG -超链接
    强制重载如果没有到期的时间也没有最后修改时间从服务器在决定是否加载该项目从网络返回。


    INTERNET_FLAG_IGNORE_CERT_CN_INVALID
    禁用的Win32上网功能的SSL /厘为基础的打击是从给定的请求服务器返回的主机名称证书检查。 Win32的上网功能用来对付证书由匹配主机名和HTTP请求一个简单的通配符规则比较简单的检查。


    INTERNET_FLAG_IGNORE_CERT_DATE_INVALID
    禁用的Win32上网功能的SSL /厘为基础的HTTP请求适当的日期,证书的有效性检查。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许对HTTP重定向的URL从HTTPS。


    INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS
    禁用的Win32上网功能能够探测到这种特殊类型的重定向。 当使用此标志,透明的Win32上网功能允许从HTTP重定向到HTTPS网址。


    INTERNET_FLAG_KEEP_CONNECTION
    使用保持活动语义,如果有的话,给HTTP请求连接。 这个标志是必需的微软网络(MSN),NT LAN管理器(NTLM)和其他类型的身份验证。


    INTERNET_FLAG_MAKE_PERSISTENT
    不再支持。


    INTERNET_FLAG_MUST_CACHE_REQUEST
    导致一个临时文件如果要创建的文件不能被缓存。 相同的首选值INTERNET_FLAG_NEED_FILE。


    INTERNET_FLAG_NEED_FILE
    导致一个临时文件如果要创建的文件不能被缓存。


    INTERNET_FLAG_NO_AUTH
    不尝试HTTP请求身份验证自动。


    INTERNET_FLAG_NO_AUTO_REDIRECT
    不自动处理HTTP请求重定向只。


    INTERNET_FLAG_NO_CACHE_WRITE
    不缓存的数据,在本地或在任何网关。


    INTERNET_FLAG_NO_COOKIES
    不会自动添加Cookie标头的请求,并不会自动添加返回的Cookie的HTTP请求的Cookie数据库。


    INTERNET_FLAG_NO_UI
    禁用cookie的对话框。


    INTERNET_FLAG_PASSIVE
    使用被动FTP语义FTP文件和目录。


    INTERNET_FLAG_RAW_DATA
    返回一个数据WIN32_FIND_DATA结构时,FTP目录检索信息。 如果这个标志,或者未指定代理的电话是通过一个CERN,InternetOpenUrl返回的HTML版本的目录。


    INTERNET_FLAG_PRAGMA_NOCACHE
    强制要求被解决的原始服务器,即使在代理缓存的副本存在。


    INTERNET_FLAG_READ_PREFETCH
    该标志目前已停用。


    INTERNET_FLAG_RELOAD
    从导线获取数据,即使是一个本地缓存。


    INTERNET_FLAG_RESYNCHRONIZE
    重整HTTP资源,如果资源已被修改自上一次被下载。 所有的FTP资源增值。


    INTERNET_FLAG_SECURE
    请确保在使用SSL或PCT线交易。 此标志仅适用于HTTP请求。



    dwContext:OpenRequest操作的上下文标识符。

    4. InternetReadFile介绍
    函数声明
    BOOL InternetReadFile( __in HINTERNET hFile,__out LPVOID lpBuffer,__in DWORD dwNumberOfBytesToRead,__out LPDWORD lpdwNumberOfBytesRead);
    参数

    hFile[in]
    由InternetOpenUrl,FtpOpenFile, 或HttpOpenRequest函数返回的句柄.
    lpBuffer[out]
    缓冲器指针
    dwNumberOfBytesToRead[in]
    欲读数据的字节量。
    lpdwNumberOfBytesRead[out]
    接收读取字节量的变量。该函数在做任何工作或错误检查之前都设置该值为零

    返回值成功:返回TRUE,失败,返回FALSE

    程序设计原理该部分讲解下程序设计的原理以及实现的流程,让大家有个宏观的认识。原理是:

    首先,使用 InternetCrackUrl 函数分解URL,从URL中提取网站的域名、路径以及URL的附加信息等。关于 InternetCrackUrl 分解URL的介绍和实现,可以参考本站上的的 “URL分解之InternetCrackUrl” 这篇文章
    使用 InternetOpen 建立会话,获取会话句柄
    使用 InternetConnect 与网站建立连接,获取连接句柄
    设置HTTP的访问标志,使用 HttpOpenRequest 打开HTTP的“GET”请求
    使用 HttpSendRequest 发送访问请求
    根据返回的Response Header的数据中,获取将要接收数据的长度
    使用 InternetReadFile 接收数据
    关闭句柄,释放资源

    其中,上面的 8 个步骤中,要注意的就是第 6 步,获取返回的数据长度,是从响应信息头中的获取“Content-Length: ”(注意有个空格)这个字段的数据。
    编程实现1. 导入WinInet库#include <WinInet.h>#pragma comment(lib, "WinInet.lib")
    2. HTTP文件下载编程实现// 数据下载// 输入:下载数据的URL路径// 输出:下载数据内容、下载数据内容长度BOOL Http_Download(char *pszDownloadUrl, BYTE **ppDownloadData, DWORD *pdwDownloadDataSize){ // INTERNET_SCHEME_HTTPS、INTERNET_SCHEME_HTTP、INTERNET_SCHEME_FTP等 char szScheme[MAX_PATH] = { 0 }; char szHostName[MAX_PATH] = { 0 }; char szUserName[MAX_PATH] = { 0 }; char szPassword[MAX_PATH] = { 0 }; char szUrlPath[MAX_PATH] = { 0 }; char szExtraInfo[MAX_PATH] = { 0 }; ::RtlZeroMemory(szScheme, MAX_PATH); ::RtlZeroMemory(szHostName, MAX_PATH); ::RtlZeroMemory(szUserName, MAX_PATH); ::RtlZeroMemory(szPassword, MAX_PATH); ::RtlZeroMemory(szUrlPath, MAX_PATH); ::RtlZeroMemory(szExtraInfo, MAX_PATH); // 分解URL if (FALSE == Http_UrlCrack(pszDownloadUrl, szScheme, szHostName, szUserName, szPassword, szUrlPath, szExtraInfo, MAX_PATH)) { return FALSE; } // 数据下载 HINTERNET hInternet = NULL; HINTERNET hConnect = NULL; HINTERNET hRequest = NULL; DWORD dwOpenRequestFlags = 0; BOOL bRet = FALSE; unsigned char *pResponseHeaderIInfo = NULL; DWORD dwResponseHeaderIInfoSize = 2048; BYTE *pBuf = NULL; DWORD dwBufSize = 64 * 1024; BYTE *pDownloadData = NULL; DWORD dwDownloadDataSize = 0; DWORD dwRet = 0; DWORD dwOffset = 0; do { // 建立会话 hInternet = ::InternetOpen("WinInetGet/0.1", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); if (NULL == hInternet) { Http_ShowError("InternetOpen"); break; } // 建立连接 hConnect = ::InternetConnect(hInternet, szHostName, INTERNET_DEFAULT_HTTP_PORT, szUserName, szPassword, INTERNET_SERVICE_HTTP, 0, 0); if (NULL == hConnect) { Http_ShowError("InternetConnect"); break; } // 打开并发送HTTP请求 dwOpenRequestFlags = INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | INTERNET_FLAG_KEEP_CONNECTION | INTERNET_FLAG_NO_AUTH | INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_NO_UI; if (0 < ::lstrlen(szExtraInfo)) { // 注意此处的连接 ::lstrcat(szUrlPath, szExtraInfo); } hRequest = ::HttpOpenRequest(hConnect, "GET", szUrlPath, NULL, NULL, NULL, dwOpenRequestFlags, 0); if (NULL == hRequest) { Http_ShowError("HttpOpenRequest"); break; } // 发送请求 bRet = ::HttpSendRequest(hRequest, NULL, 0, NULL, 0); if (FALSE == bRet) { Http_ShowError("HttpSendRequest"); break; } // 接收响应的报文信息头(Get Response Header) pResponseHeaderIInfo = new unsigned char[dwResponseHeaderIInfoSize]; if (NULL == pResponseHeaderIInfo) { break; } ::RtlZeroMemory(pResponseHeaderIInfo, dwResponseHeaderIInfoSize); bRet = ::HttpQueryInfo(hRequest, HTTP_QUERY_RAW_HEADERS_CRLF, pResponseHeaderIInfo, &dwResponseHeaderIInfoSize, NULL); if (FALSE == bRet) { Http_ShowError("HttpQueryInfo"); break; }#ifdef _DEBUG printf("[HTTP_Download_ResponseHeaderIInfo]\n\n%s\n\n", pResponseHeaderIInfo);#endif // 从 中字段 "Content-Length: "(注意有个空格) 获取数据长度 bRet = Http_GetContentLength((char *)pResponseHeaderIInfo, &dwDownloadDataSize); if (FALSE == bRet) { break; } // 接收报文主体内容(Get Response Body) pBuf = new BYTE[dwBufSize]; if (NULL == pBuf) { break; } pDownloadData = new BYTE[dwDownloadDataSize]; if (NULL == pDownloadData) { break; } ::RtlZeroMemory(pDownloadData, dwDownloadDataSize); do { ::RtlZeroMemory(pBuf, dwBufSize); bRet = ::InternetReadFile(hRequest, pBuf, dwBufSize, &dwRet); if (FALSE == bRet) { Http_ShowError("InternetReadFile"); break; } ::RtlCopyMemory((pDownloadData + dwOffset), pBuf, dwRet); dwOffset = dwOffset + dwRet; } while (dwDownloadDataSize > dwOffset); // 返回数据 *ppDownloadData = pDownloadData; *pdwDownloadDataSize = dwDownloadDataSize; } while (FALSE); // 关闭 释放 if (NULL != pBuf) { delete[]pBuf; pBuf = NULL; } if (NULL != pResponseHeaderIInfo) { delete[]pResponseHeaderIInfo; pResponseHeaderIInfo = NULL; } if (NULL != hRequest) { ::InternetCloseHandle(hRequest); hRequest = NULL; } if (NULL != hConnect) { ::InternetCloseHandle(hConnect); hConnect = NULL; } if (NULL != hInternet) { ::InternetCloseHandle(hInternet); hInternet = NULL; } return bRet;}
    程序测试在main函数中,调用上述封装好的函数,下载文件进行测试。
    main函数为:
    int _tmain(int argc, _TCHAR* argv[]){ char szHttpDownloadUrl[] = "http://www.demongan.com/source/ccc/dasanxia/520.zip"; BYTE *pHttpDownloadData = NULL; DWORD dwHttpDownloadDataSize = 0; // HTTP下载 if (FALSE == Http_Download(szHttpDownloadUrl, &pHttpDownloadData, &dwHttpDownloadDataSize)) { return 1; } // 将下载数据保存成文件 Http_SaveToFile("http_downloadsavefile.zip", pHttpDownloadData, dwHttpDownloadDataSize); // 释放内存 delete []pHttpDownloadData; pHttpDownloadData = NULL; system("pause"); return 0;}
    测试结果:
    根据返回的Response Header知道,成功下载22761460字节大小的数据。

    查看目录,有22228KB大小的“http_downloadsavefile.zip”文件成功生成,所以,数据下载成功。

    总结基于WinInet库的HTTP下载文件原理并不复杂,但是,因为涉及较多的API,每个API的执行都需要依靠上一个API成功执行返回的数据。所以,要仔细检查。如果出错,也要耐心调试,根据返回的错误码,结合程序前后部分的代码,仔细分析原因。
    参考参考自《Windows黑客编程技术详解》一书
    3 留言 2018-12-20 17:46:07 奖励13点积分
  • C语言提高笔记

    C提高一 基本概念强化1、头文件函数声明
    分文件时,头文件防止头文件重复包含
    #pragma once//兼容C++编译器//如果是C++编译器,按照C标准编译#ifdef __cplusplusextern "c"{#endif//#ifdef __cplusplus}#endif数组作为函数参数会退化为一级指针:

    数组做函数参数时,应该把数组元素个数也传递给函数;
    形参中的数组,编译器把它当做指针处理,这是C语言的特色;
    实参中的数组,和形参中数组本质不一样;

    void print_array(int a[], int n)数据类型的本质:是固定内存大小的别名
    数据类型的作用:编译器预算对象(变量)分配的内存空间大小
    int a;//告诉C编译器分配4个字节的内存数据类型可以通过typedef起别名:typedef unsigned int u32;typedef struct student{ int a; char b;}STU;可以通过sizeof()测类型大小;Void类型 (无类型)

    如果函数没有参数,定义函数时,可以用void修饰:int fun(void);
    如果函数没有返回值,必须用void修饰:void fun(int a);
    不能定义void类型的普通变量,void a;//err,无法确定类型,不同类型分配空间不一样
    可以定义void 变量:void p;//ok,32位永远是4个字节,64位8字节
    void *p;万用指针,函数返回值,函数参数

    变量的本质:内存空间的别名
    必须通过数据类型定义变量
    变量相当于门牌号,内存相当于房间,通过门牌号找到房间,通过变量找到所对应的内存
    变量的赋值:1. 直接赋值 2. 间接赋值
    int a;a=100;//直接赋值int *p=0;p=&a;//指针指向谁,就把谁的地址赋值给指针*p=22;//间接赋值重点:没有变量,哪来内存,没有内存,哪里来内存首地址
    变量三要素(名称、大小、作用域),变量的生命周期
    内存四区模型

    栈区:系统分配空间,系统自动回收,函数内部定义的变量,函数参数,函数结束,其内部变量生命周期结束;
    堆区:程序员动态分配空间,由程序员手动释放,没有手动释放,分配的空间一直可用;
    静态区(全局区):(包括全局变量和静态变量,里面又分为初始化区和未初始化区,文字常量区:字符常量):整个程序运行完毕,系统自动回收;
    代码区,内存四区模型图

    栈区地址生长方向:地址由上往下递减;堆区地址生长方向:地址由下往上递增;数组buf,buf+1地址永远递增。

    函数调用模型
    程序各个函数运行流程(压栈弹栈,入栈出栈,先进后出)
    二 指针强化指针也是一种数据类型,指针变量也是一种变量,和int a本质是一样的
    1)指针变量也是一种变量,也有空间,32位程序大小为4个字节 int *p = 0x1122; 2)*操作符,*相当于钥匙,通过*可以找到指针所指向的内存区域 int a = 10; int *p = NULL; p = &a; //指针指向谁,就把谁的地址赋值给指针 *p = 22; //*放=左边,给内存赋值,写内存 int b = *p; //*放=右边,取内存的值,读内存 3)指针变量,和指向指向的内存是两个不同的概念 char *p = NULL; char buf[] = "abcdef"; //改变指针变量的值 p = buf; p = p + 1; //改变了指向变量的值,改变了指针的指向 *p = 'm'; //改变指针指向的内存,并不会影响到指针的值 4)写内存时,一定要确保内存可写 char *buf2 = "sadgkdsjlgjlsdk"; //文字常量区,内存不可改 //buf2[2] = '1'; //err间接赋值(*p)是指针存在最大意义
    1)间接赋值三大条件 a) 两个变量 b) 建立关系 c) 通过 * 操作符进行间接赋值 1) int a; int *p; //两个变量 p = &a; //建立关系 *p = 100; //通过 * 操作符进行间接赋值 2) int b; fun(&b); //两个变量之一:实参,给函数传参时,相当于建立关系 //p = &b void fun(int *p) //两个变量之一:形参参 { *p = 100; //通过 * 操作符进行间接赋值 } 2)如何定义合适类型的指针变量 //某个变量的地址需要定义一个怎么样类型的变量保存 //在这个类型的基础上加一个* int b; int *q = &b; int **t = &q;重要:如果想通过函数形参改变实参的值,必须传地址1、值传递,形参的任何修改不会影响到实参2、地址传递,形参(通过*操作符号)的任何修改会影响到实参用1级指针形参,去间接修改了0级指针(实参)的值。用2级指针形参,去间接修改了1级指针(实参)的值。用3级指针形参,去间接修改了2级指针(实参)的值。用n级指针形参,去间接修改了n-1级指针(实参)的值。 int a = 10; fun(a); //值传递 void fun(int b) { b = 20; } fun2(&a);//地址传递 void fun2(int *p) { *p = 20; //通过*操作内存 } int *p = 0x1122; void fun3(p);//值传递 void fun3(int *p) { p = 0x2233; } void fun4(&p);//地址传递 void fun4(int **p) { *p = 0xaabb; //通过*操作内存 }3、不允许向NULL和未知非法地址拷贝内存
    char *p = NULL; //给p指向的内存区域拷贝内容 strcpy(q, "1234"); //err //静态 char buf[100]; p = buf; strcpy(q, "1234"); //ok //动态 p = (char *)malloc(sizeof(char) * 10 ); strcpy(q, "1234"); //ok char *q = "123456"; strcpy(q, "abcd"); //?4、void *指针的使用
    void *p = 0x1122; //可以这么做,不建议,一般赋值为NULL char buf[1024] = "abcd"; p = (void *)buf; //指向 char printf("p = %s\n", (char *)p); //使用时转化为实际类型指针 int a[100] = { 1, 2, 3, 4 }; p = (void *)a; //指向 int int i = 0; for (i = 0; i < 4; i++) { //使用时转化为实际类型指针 int *tmp = (int *)p; printf("%d ", *(tmp+i)); printf("%d ", tmp[i]); printf("%d ", *( (int *)p + i ) ); } void * 常用于函数参数:memset(), memcpy()5、栈区返回变量的值和变量的地址区别
    int fun() { int a = 10; return a; } int *fun2() { int a = 10; return &a; } int *fun3() { static int a = 10; return &a; } int b = fun(); //ok, b的值为10 //也ok, p确实也保存了fun2()内部a的地址 //但是,fun2完毕,a就释放,p就指向未知区域 int *p = fun2(); //ok,fun3()中的a在全局区,函数运行完毕,a的空间不释放 int *q = fun3();6、.c -> 可执行程序过程 预处理:宏定义展开、头文件展开、条件编译,这里并不会检查语法 编译:检查语法,将预处理后文件编译生成汇编文件 汇编:将汇编文件生成目标文件(二进制文件) 链接:将目标文件链接为可执行程序 程序只有在运行才加载到内存(由系统完成),但是某个变量具体分配多大,是在编译阶段就已经确定了,换句话说,在编译阶段做完处理后,程序运行时系统才知道分配多大的空间,所以,很多时候说,这个变量的空间在编译时就分配(确定)了。
    7、指针做函数参数的输入输出特性输入:主调函数分配内存输出:被调用函数分配内存//结合内存四区模型图
    main() { char buf[100] = "123456"; fun1(buf); //输入 char *p = NULL; fun2(&p); //输出 //因为p在fun2()动态分配空间了,使用完毕应该释放 if(p != NULL) { free(p); p = NULL; } } void fun1(char *p /*in*/) { strcpy(p, "1234") } void fun2(char **p /*out*/) { char *tmp = malloc(100); strcpy(tmp, "1234"); *p = tmp; //间接赋值是指针存在最大意义,通过*操作内存 }8、变量内存的值和变量的地址int a = 0;a变量内存的值为0a变量的地址(&a)绝对不为0,只要定义了变量,系统会自动为其分配空间(一个合法不为0的地址)
    三 字符串操作1、字符串基本操作 1)字符串初始化 / C语言没有字符串类型,用字符数组模拟 C语言字符串以数字0,或字符 ‘\0’ 结尾,数字 0 和字符 ‘\0’ 等价 / char str1[100] = { ‘a’, ‘b’, ‘c’, ‘d’ }; //没赋值的自动以数字0填充 char str2[] = { ‘a’, ‘b’, ‘c’, ‘d’ }; //数组长度为4,结尾没有数字0 char str4[] = “abcdef”; //常用赋值方式,栈区 char p = “abcdef”; //文字常量区,内容不允许被修改 char buf = (char *)malloc(100); //堆区 strcpy(buf, “abcd”); //“abcd”拷贝到buf指向的内存区域中
    2)sizeof和strlen区别 //使用字符串初始化,常用 char buf8[] = “abc”; //strlen: 测字符串长度,不包含数字0,字符’\0’ //sizeof:测数组长度,包含数字0,字符’\0’ printf(“strlen = %d, sizeof = %d\n”, strlen(buf8), sizeof(buf8)); 3 4 char buf9[100] = “abc”; printf(“strlen = %d, sizeof = %d\n”, strlen(buf9), sizeof(buf9)); 3 100
    3)’\0’ 后面最好别跟数字,因为几个数字合起来有可能是一个转义字符 //\012相当于\n char str[] = “\0129”; printf(“%s aaaa\n”, str);
    4)字符’\0’, 数字0, 字符’0’, NULL的区别 a) 字符’\0’ ASCII码值为 0 的字符 字符’\0’ 和 数字 0 是等级的,’\0’中’\’是转义字符 char buf[100]; //下面是等级,在数组第10个位置设置字符串结束符 buf[10] = 0; buf[10] = ‘\0’; b) 字符’0’是字符串的某个字符内容为’0’, ASCII码值为 48 的字符 char buf[100]; buf[0] = ‘0’; //第0个字符为字符 ‘0’ c) NULL 标准头文件(stdio.h)的宏 其值为数字 0
    5)数组法、指针法操作字符串 char buf[] = “abdgdgdsg” char p = buf; //buf是数组首元素地址,它也是指针 for (i = 0; i < strlen(buf); i++) { //[ ] 和 操作是等价的,也是操作指针指向内存 printf(“%c “, buf[i]); //符合程序员习惯 printf(“%c “, p[i]); //符合程序员习惯 printf(“%c “, (p+i)); printf(“%c “, (buf + i)); } 注意:数组名也是指针,数组首元素地址,但是,它是一个只读常量 p++; //ok buf++; //err
    6)字符串拷贝函数 //成功为0,失败非0 //1 判断形参指针是否为NULL //2 最好不要直接使用形参 int my_strcpy(char dst, char src) { if (dst == NULL || src == NULL) { return -1; } //辅助变量把形参接过来 char to = dst; char from = src;
    //*dst = *src //dst++, src++ //判断 *dst是否为0, 为0跳出循环 while (*to++ = *from++) ; return 0;}2、项目开发常用字符串应用模型 1、利用strstr标准库函数找出一个字符串中substr出现的个数 1)do-while模型: char p = “11abcd111122abcd333abcd3322abcd3333322qqq”; int n = 0; do { p = strstr(p, “abcd”); if (p != NULL) { n++; //累计个数 //重新设置查找的起点 p = p + strlen(“abcd”); } else //如果没有匹配的字符串,跳出循环 { break; } } while (p != 0); //如果没有到结尾
    2)while模型: char p = “11abcd22222abcd33333abcd444444qqq”; int n = 0; while( (p = strstr(p, “abcd”)) != NULL ) { //能进来,肯定有匹配的子串 //重新设置起点位置 p = p + strlen(“abcd”); n++; if(p == 0)//如果到结束符 { break; } } printf(“n = %d\n”,n);
    3)函数封装实现 int my_strstr(char p,int n) { //辅助变量 int i = 0; char tmp = p; while((tmp = strstr(tmp, “abcd”)) != NULL) { //能进来,肯定有匹配的子串 //重新设置起点位置 tmp = tmp + strlen(“abcd”); i++; if(tmp == 0)//如果到结束符 { break; } } //间接赋值 n = i; return 0; } int main(void) { char p = “11abcd22222abcd33333abcd444444qqq”; int n = 0; int ret = 0; ret = my_strstr(p,&n); if(ret != 0) { return ret; } printf(“n = %d\n”,n); return 0;}
    2、两头堵模型 char *p = “ abcddsgadsgefg “; int begin = 0; int end = strlen(p) - 1; int n = 0; if(end < 0){ return; } //从左往右移动,如果当前字符为空,而且没有结束 while (p[begin] == ‘ ‘ && p[begin] != 0) { begin++; //位置从右移动一位 } //从右往左移动,如果当前字符为空 while (p[end] == ‘ ‘) { end—; //往左移动 } n = end - begin + 1; //非空元素个数 strncpy(buf, p + begin, n); buf[n] = 0;
    //如何证明strncpy()拷贝不会自动加字符串结束符'\0'char dst[] = "aaaaaaaaaaaaaaa";strncpy(dst, "123", 3);printf("dst = %s\n", dst); //dst = "123aaaaaaaaaaaa"四 const的使用1)const声明变量为只读 //const修饰的变量,定义时初始化 const int a = 10; //a = 100; //error int q = &a;
    q = 22; char buf[100] = “abcdef”;//从左往右看,跳过类型,看修饰那个字符 //如果是修饰,说明指针指向的内存不能改变 //如果是修饰指针变量,说明指针的指向不能改变,指针的值不能修改 const char p = buf; //类似于文字常量区 char p = “123445”; char const p = buf; //修饰,指针指向能变,指针指向的内存不能变 //p[0] = ‘1’; //error p = “123456”; //ok char const p1 = buf; //修饰指针变量,指针指向的内存,指针指向不能变 //p1 = “123456”; //error p1[0] = ‘1’; //ok const char * const p2 = buf; //p2, 只读
    2)如何引用另外.c中的const变量 extern const int a;不能再赋值,只能声明
    五 多级指针1)如何定义合适类型的指针变量 //某个变量的地址需要定义一个怎么样类型的变量保存 //在这个类型的基础上加一个 int b; int q = &b; //一级指针 int t = &q; //二级指针 int *m = &t; //三级指针
    2)二级指针做输出 输入:主调函数分配内存 输出:被调用函数分配内存 char *p1 = NULL; //没有分配内存 int len = 0; getMem(&p1, &len); //要想通过函数的形参改变实参的值,必须地址传递
    void getMem(char **p1 /*out*/, int *plen /*in*/){ //间接赋值,是指针存在最大的意义。 *p1 = malloc(100); *plen = 100;} 指针做参数输出特性3)二级指针做输入的三种内存模型 1、//指针数组,数组的每个元素都是指针类型 // [] 的优先级比 高,它是数组,每个元素都是指针类型(char ) char myArray[] = {“aaaaaa”, “ccccc”, “bbbbbb”, “111111”}; //char **p = {“aaaaaa”, “ccccc”, “bbbbbb”, “111111”}; //err void fun(int a[]); void fun(int a); // a[] 等价于 a void printMyArray(char myArray[], int num); // char 代表类型,myArray[]等价于 myArray // char myArray[] -> char myArray void printMyArray(char myArray, int num); void sortMyArray(char *myArray, int num); 如果排序,交换的是指针的指向,因为原来指针指向是文字常量区,文字常量区的内存一旦分配,内存就不能变。
    2、//二维数组 10行30列,10个一维数组a[30] //总共能容量10行字符串,这个用了 4 行 //每行字符串长度不能超过29,留一个位置放结束符:数字0 char myArray[10][30] = {“aaaaaa”, “ccccc”, “bbbbbbb”, “1111111111111”}; void printMyArray(char myArray[10][30], int num); void sortMyArray(char myArray[10][30], int num); //定义二维数组,不写第一个[ ]值有条件,必须要初始化 char a[][30] = {“aaaaaa”, “ccccc”, “bbbbbbb”, “1111111111111”};//ok char a[][30]; //err,定义时必须初始化
    二维数组的数组名代表首行地址(第一行一维数组的地址)首行地址和首行首元素地址的值是一样的,但是它们步长不一样首行地址+1,跳过一行,一行30个字节,+30首行首元素地址+1,跳过一个字符,一个字符为1个字节,+1sizeof(a): 有4个一维数组,每个数组长度为30,4 * 30 = 120sizeof(a[0]): 第0个一维数组首元素地址,相当于测第0个一维数组的长度:为30char b[30];&b代表整个一维数组的地址,相当于二维数组首行地址b代表一维数组首元素地址,相当于二维数组首行首元素地址&b 和 b 的值虽然是一样,但是,它们的步长不一样&b + 1: 跳过整个数组,+30b+1: 跳过1个字符,+1//不能通过 char ** 作为函数形参,因为指针+1步长不一样// char **,指针+1步长为 4 个字节// char a[][30],指针+1步长为 1 行的长度,这里为 30 个字节void printMyArray(char **buf, int num);3、int a[3];int *q = (int *)malloc(3 * sizeof(int)); //相当于q[3]//动态分配一个数组,每个元素都是char * //char *buf[3]int n = 3;char **buf = (char **)malloc(n * sizeof(char *)); //相当于 char *buf[3]if (buf = = NULL){ return -1;} for (i = 0; i < n; i++) { buf[i] = (char )malloc(30 sizeof(char)); }
    char **myArray = NULL;char **getMem(int num); //手工打造二维数组void printMyArray(char **myArray, int num);void sortMyArray(char **myArray, int num);void arrayFree(char **myArray, int num);第三种内存模型:
    char **getMem(int n){ int i = 0; char **buf = (char **)malloc(n * sizeof(char *)); //char *buf[3] if (buf == NULL) { return NULL; } for (i = 0; i < n; i++) { buf[i] = (char *)malloc(30 * sizeof(char)); char str[30]; sprintf(str, "test%d%d", i, i); strcpy(buf[i], str); } return buf;}void print_buf(char **buf, int n){ int i = 0; for (i = 0; i < n; i++) { printf("%s, ", buf[i]); } printf("\n");}void free_buf(char **buf, int n){ int i = 0; for (i = 0; i < n; i++) { free(buf[i]); buf[i] = NULL; } if (buf != NULL) { free(buf); buf = NULL; }}int main(void){ char **buf = NULL; int n = 3; buf = getMem(n); if (buf == NULL) { printf("getMem err\n"); return -1; } print_buf(buf, n); free_buf(buf, n); buf = NULL; printf("\n"); system("pause"); return 0;}1、一维数组的初始化 int a[] = { 1, 3, 5 }; //3个元素 int b[5] = { 1, 2, 3 }; //a[3], a[4]自动初始化为0 int c[10] = { 0 }; //全部元素初始化为0 memset(c, 0, sizeof(c)); //通过memset给数组每个元素赋值为0
    2、数组类型 int a[] = { 1, 3, 5 }; //3个元素 a: 数组首行首元素地址,一级指针 &a: 整个数组的首地址,二级指针
    首行首元素地址和首行(整个一维数组)地址值虽然是一样,但是它们的步长不一样a+1: 跳过1元素,一元素为4字节,步长4个字节&a+1: 跳过整个数组,整个数组长度为 3*4 = 12,步长 3 * 4 = 12sizeof(a): 传参为:数组首行首元素地址,测数组(int [3])的长度,3 * 4 = 12sizeof(a[0]): 传参为:数组首元素(不是地址),每个元素为int类, 4字节sizeof(&a):传参为:一维数组整个数组的地址(首行地址),编译器当做指针类型,4字节(重要)首行地址 --> 首行首元素地址(加*)&a:首行地址*&a -> a : 首行首元素地址//数组也是一种数据类型,类型本质:固定大小内存块别名//由元素类型和内存大小(元素个数)共同决定 int a[5] int[5]//可以通过typedef定义数组类型//有typedef是类型,没有typedef是变量typedef int ARRARY[5]; //定义了一个名字为ARRARY的数组类型//等价于typedef int (ARRARY)[5];//根据数组类型,定义变量//ARRARY的位置替代为d,去掉typedef,int d[5]ARRARY d; //相当于int d[5];3、指针数组(它是数组,每个元素都是指针) 1)指针数组的定义 //指针数组变量 //[]优先级比高,它是数组,每个元素都是指针(char ) char *str[] = { “111”, “2222222” };
    char **str = { "111", "2222222" }; //err2)指针数组做形参void fun(char *str[]);void fun(char **str); //str[] -> *str3)main函数的指针数组//argc: 传参数的个数(包含可执行程序)//argv:指针数组,指向输入的参数int main(int argc, char *argv[]);: demo.exe a b testint argc = 4char *argv[] = {"demo.exe", "a", "b", "test"}4、数组指针变量(它是指针变量,指向数组的指针) //定义数组变量 int a[10]; //有typedef:类型 //没有typedef:变量 1、根据数组类型,定义指针变量,数组指针变量 typedef int ARRARY[10]; //定义了一个名字为ARRARY的数组类型 //等价于typedef int (ARRARY)[10];
    ARRARY *p; //数组指针变量//编译会有警告,但不会出错,因为 a 和 &a的值一样//就算p = a这样赋值,编译器内部也会自动转换为 p = &a//不建议这么做p = a; //p 指向a数组,指向一维数组的指针p = &a; //如何操作数组指针变量 pint i = 0;for (i = 0; i < 10; i++){ (*p)[i] = i + 1; //p = &a //*p -> *(&a) -> a //(*p)[i] -> a[i]}2、直接定义数组指针变量(常用)//()[]同级,从左往右看//()有*,它是一个指针,[]代表数组//指向数组的指针变量,[]中的数字代表指针+1的步长int(*p)[10]; //p 指向a数组,指向一维数组的指针p = &a;3、先定义数组指针类型,再根据类型定义指针变量(常用)//和指针数组写法很类似,多了()//()和[]优先级一样,从左往右//()有指针,它是一个指针,[]//指向数组的指针,它有typedef,所有它是一个数组指针类型//数组指针类型,加上typedeftypedef int(*Q)[10];Q p; //根据类型定义变量,p是数组指针变量p = &a; //p指向数组a5、多维数组本质 1)二维数组初始化 int a1[3][4] = { {1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12} }; int a2[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 }; int a3[][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    2)内存中并不存在多维数组,多维数组在内存中都是线性存储int a[3][5] = { 0 };int *b = (int *)a;int i = 0;for(i = 0; i < 15; i++){ printf("%d ", b[i]);}printfA((int *)a,sizeof(a)/sizeof(a[0][0]));3)多维数组名//学会类比int b[5] = {0};b: 首行首元素地址, +1,跳 4 个字节&b:首行地址,+1,跳 4*5 = 20个字节//二维数组实际上就是 N 个一维数组//把二维数组第一个[]的值看做标志位,0 -> 2//第0个一维数组a[5] -> 第2个一维数组a[5]int a[3][5] = { 0 };a:二维数组首元素地址代表首行地址,相当于一维数组整个数组的地址,相当于上面的 &b,本来就是一个二级指针//(重要)首行地址 --> 首行首元素地址(加*)*a:首行首元素地址,相当于一维数组首元素地址,相当于上面的 ba + i -> &a[i]: 第i行地址//(重要)某行地址 --> 某行首元素地址(加*)*(a+i) -> *&a[i] -> a[i]: 第i行首元素地址//第i行j列元素的地址,某行首元素地址 + 偏移量*(a+i)+j -> a[i]+j -> &a[i][j]: 第i行j列元素的地址//第i行j列元素的值,第i行j列元素的地址的基础上(加 *)*(*(a+i)+j) -> a[i][j]: 第i行j列元素的值int a[3][5] = { 0 };sizeof(a): 二维数组整个数组长度,4 * 3 * 5 = 60sizeof(a[0]):a[0]为第0行首元素地址,相当于测第0行一维数组的长度:4 * 5 = 20sizeof(a[0][0]):a[0][0]为第0第0列元素(是元素,不是地址),测某个元素长度:4字节4)多维数组名,实际上是一个数组指针,指向数组的指针,步长为一行字节长度int a[3][5] = { 0 };//定义一个数组指针类型的变量int(*p)[5];//编译会有警告,但不会出错,因为 a 和 &a的值一样//但是&a代表整个二维数组的首地址//就算p = &a这样赋值,编译器内部也会自动转换为 p = a//不建议这么做p = &a;//a 本来就是第0个一维数组整个数组的地址,所以,无需加&p = a;5)二维数组做形参的三种形式//一维数组做函数参数退化为一级指针//二维数组(多维数组)做函数参数,退化为数组指针int a[3][5] = { 0 };void print_array1(int a[3][5]);//第一维的数组,可以不写//第二维必须写,代表步长,确定指针+1的步长 5*4void print_array2(int a[][5])//形参为数组指针变量,[]的数字代表步长void print_array3(int (*a)[5]);//a+1和二维数组的步长不一样//这里的步长为4//上面二维数组的步长为 5 * 4 = 20void print_array3(int **a); //err6、小结 typedef int A[10];//A:数组类型 A b;//int b[10],数组类型变量,普通变量 A *p;//数组类型定义数组指针变量
    typedef int (*P)[10];//数组指针类型P p;//数组指针变量int (*q)[10];//数组指针变量六 结构体1、结构体类型基本操作 1)结构体类型定义 //struct结构体关键字 //struct STU合起来才是类型名 //{}后面有个分号 struct Stu { char name[32]; char tile[32]; int age; char addr[50]; }; //通过typedef把struct Stu改名为Stu typedef struct Stu { int a; }Stu;
    2)结构体变量的定义//1)先定义类型,再定义变量,最常用struct Stu a;//全局变量、局部变量//2)定义类型的同时,定义变量struct _Stu{ char name[32]; char tile[32]; int age; char addr[50];}c;struct{ char name[32]; char tile[32]; int age; char addr[50];}e, f;3)结构体变量初始化//定义变量同时时初始化,通过{}struct Stu g = { "lily", "teacher", 22, "guangzhou" };4)变量和指针法操作结构体成员//变量法, 点运算符struct Stu h;strcpy(h.name, "^_^");(&h)->name//指针法, ->//结构体指针变量,没有指向空间,不能给其成员赋值struct Stu *p;p = &h;strcpy(p->name, "abc");(*p).name结构体也是一种数据类型,复合类型,自定义类型5)结构体数组
    //结构体类型 typedef struct Teacher { char name[32]; int age; }Teacher; //定义结构体数组,同时初始化 Teacher t1[2] = { { "lily", 18 }, { "lilei", 22 } }; //静态数组 Teacher t1[2] = {"lily", 18, "lilei", 22 }; int i = 0; for(i = 0; i < 2; i++) { printf(“%s, %d\n”, t1[i].name, t1[i].age);} //动态数组 Teacher *p = (Teacher *)malloc(3 * sizeof(Teacher)); if(p ==NULL) { return -1; } char buf[50]; int i; for(i = 0; i < 3; i++) { sprintf(buf,"name%d%d%d",i,i,i); strcpy(p[i].name,buf); p[i].age = 20 + i;}2、结构体赋值 //定义结构体类型是不要直接给成员赋值 //结构体只是一个类型,还没有分配空间 //只有根据其类型定义变量时,才分配空间,有空间后才能赋值 Teacher t1 = { “lily”, “teacher”, 18, “beijing” }; //相同类型的结构体变量,可以相互赋值 //把t1每个成员的内容逐一拷贝到t2对应的成员中 t1和t2没有关系 Teacher t2 = t1;
    3、结构体套指针
    1)结构体嵌套一级指针类型 typedef struct Teacher { char *name; int age; }Teacher; Teacher *p = NULL; p = (Teacher *)malloc(sizeof(Teacher)); p->name = (char *)malloc(30); strcpy(p->name,”lilei”); p->age = 22; 2)结构体嵌套二级指针类型 typedef struct Teacher { char *name; char **stu; int age; }Teacher; //1 Teacher t; //t.stu[3]; //char *t.stu[3];int n = 3; int i = 0; t.stu = (char **)malloc(n * sizeof(char *)); for(i = 0; i < n; i++) { t.stu[i] = (char *)malloc(30); strcpy(t.stu[i],”lily”); } //2 Teacher *p = NULL; //p->stu[3] p = (Teacher *)malloc(sizeof(Teacher)); //char *p->stu[3]p->stu = (char **)malloc(n * sizeof(char *));//3 Teacher *q = NULL; //Teacher *q[3] //q[i].stu[3] q = (Teacher *)malloc(sizeof(Teacher) * 3) for (i = 0; i < 3; i++) { //q[i].stu //(q+i)->stu q[i].stu = (char **)malloc(3 * sizeof(char *)); //char *stu[3] for(j = 0; j < 3; j++) { q[i].stu[j] = (char *)malloc(30); } }4、结构体做函数参数
    int getMem(Teacher **tmp, int n) { if(tmp == NULL) { return -1; } Teacher *p = (Teacher *)malloc(sizeof(Teacher) * 3); //Teacher q[3]; int i = 0; char buf[30]; for(i = 0; i < n; i++) { p[i].name = (char *)malloc(30); sprintf(buf,”name%d%d%d”, i, i, i); strycpy(p[i].name, buf); p[i].age = 20 + 2 * i;}*tmp = p;retrun 0; }5、浅拷贝和深拷贝 typedef struct Teacher { char name; int age; }Teacher; //结构体中嵌套指针,而且动态分配空间 //同类型结构体变量相互赋值 //不同结构体成员指针变量指向同一块内存 Teacher t1; t1.name = (char )malloc(30); strcpy(t1.name, “lily”); t1.age = 22;
    Teacher t2;t2 = t1;//深拷贝,人为增加内存,重新拷贝一下t2.name = (char *)malloc(30);strcpy(t2.name, t1.name);6、结构体偏移量(了解) //结构体类型定义下来,内部的成员变量的内存布局已经确定 typedef struct Teacher { char name[64]; //64 int age; //4 int id; //4 }Teacher;
    Teacher t1;Teacher *p = NULL;p = &t1;int n1 = (int)(&p->age) - (int)p; //相对于结构体首地址int n2 = (int)&((Teacher *)0)->age; //绝对0地址的偏移量7、结构体字节对齐(以空间换时间),详情请看《结构体字节对齐规则.doc》 原则1:数据成员的对齐规则(以最大的类型字节为单位)。原则2:结构体作为成员的对齐规则。 注意:

    结构体A所占的大小为该结构体成员内部最大元素的整数倍,不足补齐。不是直接将结构体A的成员直接移动到结构体B中 原则3:收尾工作结构体的总大小,也就是sizeof的结果,必须是其内部最大成员的整数倍,不足的要补齐。 struct A{ int a; double b; float c;};struct{ char e[2]; int f; int g; short h; struct A i;}B;//对齐单位 8 个字节sizeof(B) = 40//普通成员偏移量e: 2*0 = 0f: 4*1 = 4g: 4*2 = 8h: 2*6 = 12结构体起点坐标: 8*2 = 16//结构体成员偏移量a: 16 + 4*0 = 16b: 16 + 8*1 = 24c: 16 + 4*4 = 32
    七 文件7.1 基本概念7.1.1 文件分类 普通文件:存放在硬盘中的文件 设备文件:屏幕、键盘等特殊文件
    文本文件:ASCII文件,每个字节存放一个字符的ASCII码,打开文件看到的是文本信息二进制文件:数据按其在内存中的存储形式原样存放,打开文件看到的是乱码7.1.1文件缓冲区(了解) ANSI C(标准C语言库函数)标准采用“缓冲文件系统”处理数据文件。
    写文件(设备文件除外),并不会直接写到文件中,会先放在缓冲区,默认情况下,关闭文件或缓冲区满了才写到文件。如果没有关闭文件,缓冲区也没有满,可以通过程序正常结束,或者人为刷新缓冲区fflush(fd)来把缓冲区的内容写到文件中。缓冲区了解一下即可,增加缓冲区只是为了提高效率,减少频繁交互的次数,我们写程序基本上不用关心。7.2 读写文件步骤7.2.1 打开文件 //定义文件指针 FILE *fp = NULL; fopen(“c:\demo.txt”, “w+”); //“c:\demo.txt” windows有效 //“c:/demo.txt”: 文件路径,可以是绝对路径和相对路径 //“w+”: 打开权限,读写方式打开,文件不存在则创建,写内容时,会清空原来内容再写 //“r+”:读写方式打开,文件不存在则报错 fp = fopen(“./demo.txt”, “w+”); // 45度 “c:/demo.txt” linux windows都可用 if (fp == NULL) { perror(“fopen”); //打印错误信息 return; }
    默认情况下,VS, Qt相对路径说明:1)编译代码时,相对路径相对于工程目录2)直接点击可执行程序,相对路径相对于可执行程序//”C:\\Users” windows的写法//”C:/Users” Linux,windows都支持,建议”/”c语言中有三个特殊的文件指针无需定义、打开可直接使用:stdin: 标准输入 默认为当前终端(键盘)我们使用的scanf、getchar函数默认从此终端获得数据stdout:标准输出 默认为当前终端(屏幕)我们使用的printf、puts函数默认输出信息到此终端stderr:标准出错 默认为当前终端(屏幕)当我们程序出错或者使用: perror函数时信息打印在此终端fputc('a', stdout); //stdout -> 屏幕, 打印普通信息char ch;ch = fgetc(stdin); //std -> 键盘printf("ch = %c\n", ch);//fprintf(stderr, "%c", ch ); //stderr -> 屏幕, 错误信息fputc(ch, stderr);printf 标准输出sprintf 字符输出fprintf 文件输出7.2.2 读写文件 1、输出,即为写,把buf中的内容写到指定的文件中 2、输入,即为读,把文件中的内容取出放在指定的buf
    7.2.3 关闭文件 fclose(fp);
    if(fp != NULL){ fclose(fp); fp = NULL;}
    7.3 读写文件7.3.1 库函数的学习 1)包含所需头文件 2)函数名字 3)功能 4)参数 5)返回值
    7.3.2 按照字符读写文件:fgetc()、fputc() 1)写文件 char *str = “111abcdefg12345678”; int i = 0; for (i = 0; i < (int)strlen(str); i++) { //功能:往文件fp中写str[i],一个字符一个字符写 //参数:str[i]:写入文件的字符,fp:文件指针 //返回值:成功写入文件的字符,失败:-1 fputc(str[i], fp); }
    2)读文件char ch;//feof(fp)判断文件是否到结尾,已经到结尾返回值为非0,没有到结尾返回值为0while ( !feof(fp) ) //如果文件没有结尾{ //返回值:成功读取的字符 ch = fgetc(fp); printf("%c", ch);}7.3.4 按照行读写文件:fputs()、fgets() 1)写文件 char *buf[] = { “11111111\n”, “aaaaaaaaaaaa\n”, “bbbbbbbbbbbb\n” }; //指针数组 int i = 0; for (i = 0; i < 3; i++) { //功能:往文件fp写入一行内容buf[i] //参数:buf[i]:字符串首地址,fp:文件指针 //返回值:成功:0,失败:非0 fputs(buf[i], fp); }
    2)读文件char buf[512] = {0};//从文件中读取一行内容(以"\n"作为标志),放在buf中//一次最大只能读sizeof(buf)-1,如果小于sizeof(buf)-1,则按实际大小读取//然后在字符串结尾自动加上字符‘\0’(转换为C风格字符串)//返回值:成功:读出的字符串,失败:NULLif (fgets(buf, sizeof(buf), fp) != NULL) { printf("buf = %s", buf);}7.3.5 按照块读写文件:fread()、fwirte() typedef struct Stu { char name[50]; int id; }Stu; Stu s[3];
    1)写文件//写文件,按块的方式写//s:写入文件内容的内存首地址//sizeof(Stu):块数据的大小//3:块数, 写文件数据的大小 sizeof(Stu) *3//fp:文件指针//返回值,成功写入文件的块数目,不是数据总长度int ret = fwrite(s, sizeof(Stu), 3, fp);printf("ret = %d\n", ret);2)读文件//读文件,按块的方式读//s:放文件内容的首地址//sizeof(Stu):块数据的大小//3:块数, 读文件数据的大小 sizeof(Stu) *3//fp:文件指针//返回值,成功读取文件内容的块数目,不是数据总长度int ret = fread(s, sizeof(Stu), 3, fp);printf("ret = %d\n", ret);7.3.6 按照格式化进行读写文件:fprintf()、fscanf() 1)写文件 //格式化写文件 int a = 250; int b = 10; int c = 20; //和printf()用法一样,只是printf是往屏幕(标准输出)写内容 //fprintf往指定的文件指针写内容 //返回值:成功:写入文件内容的长度,失败:负数 fprintf(fp, “Tom = %d, just like %d, it is %d”, a, b, c);
    2)读文件int a, b, c;fscanf(fp, "Tom = %d, just like %d, it is %d", &a, &b, &c);printf("a = %d, b = %d, c = %d\n", a, b, c);7.3.7 随机读写 //文件光标移动到文件结尾 //SEEK_SET:文件开头 //SEEK_CUR:文件当前位置 //SEEK_END:文件结尾 fseek(fp, 0, SEEK_END);
    //获取光标到文件开头文件的大小ftelllong size = ftell(fp);//文件光标恢复到开始位置rewind(fp);typedef struct Stu{ char name[50]; int id;}Stu;Stu tmp; //读第3个结构体//假如文件中写了三个结构体//从起点位置开始,往后跳转2个结构体的位置fseek(fp, 2*sizeof(Stu), SEEK_SET);//从结尾位置开始,往前跳转一个结构体的位置//fseek(fp, -1 * (int)sizeof(Stu), SEEK_END);int ret = 0;ret = fread(&tmp,sizeof(Stu), 1, fp);if(ret == 1){ printf("[tmp]%s, %d\n", tmp.name, tmp.id);}//把文件光标移动到文件开头//fseek(fp, 0, SEEK_SET);rewind(fp);7.4 综合案例1)加密文件读写(使用别人写好的接口) 加密 解密2)配置文件读写(自定义接口)
    八 链表1、数组和链表的区别 数组:一次性分配一块连续的存储区域 优点: 随机访问元素效率高 缺点: 需要分配一块连续的存储区域(很大区域,有可能分配失败) 删除和插入某个元素效率低
    链表:现实生活中的灯笼 优点: 不需要一块连续的存储区域 删除和插入某个元素效率高 缺点: 随机访问元素效率低2、相关概念 节点:链表的每个节点实际上是一个结构体变量,节点,既有 数据域 也有 指针域 typedef struct Node { int id; //数据域 struct Node *next; //指针域 }SLIST;
    尾结点:next指针指向NULL3、结构体套结构体 typedef struct A { int b; }A; / 1)结构体可以嵌套另外一个结构体的任何类型变量 2)结构体嵌套本结构体普通变量(不可以) 本结构体的类型大小无法确定,类型本质:固定大小内存块别名 3)结构体嵌套本结构体指针变量(可以) 链表 指针变量的空间能确定,32位, 4字节, 64位, 8字节
    / typedef struct B { int a; A tmp1; //ok A p1; //ok //struct B tmp2; err struct B next; //32位, 4字节, 64位, 8字节 }B;
    4、链表的分类 1)单向带头链表和不带头链表
    2)双向链表带头链表和不带头链表
    3)双向循环链表带头链表和不带头链表
    5、链表的使用实际上是指针的拓展应用:指向指向谁,就把谁的地址赋值给指针。 typedef struct Stu { int id; //数据域 char name[100]; struct Stu *next; //指针域 }Stu;
    (1)静态链表//初始化三个结构体变量Stu s1 = { 1, "mike", NULL };Stu s2 = { 2, "lily", NULL };Stu s3 = { 3, "lilei", NULL };s1.next = &s2; //s1的next指针指向s2s2.next = &s3;s3.next = NULL; //尾结点Stu *p = &s1; while (p != NULL){ printf("id = %d, name = %s\n", p->id, p->name); //结点往后移动一位 p = p->next; //&s2}(2)动态链表//Stu *p1 = NULL;//p1 = (Stu *)malloc(sizeof(Stu));Stu *p1 = (Stu *)malloc(sizeof(Stu));Stu *p2 = (Stu *)malloc(sizeof(Stu));Stu *p3 = (Stu *)malloc(sizeof(Stu));p1->next = p2;p2->next = p3;p3->next = NULL; //尾节点Stu *tmp = p1;while(tmp != NULL){ printf("id = %d, name = %s\n", tmp->id, tmp->name); //结点往后移动一位 tmp = tmp->next;}6、链表的增、删、改、查操作
    1)单向链表基本操作#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>typedef struct Node{ int id; //数据域 struct Node *next; //指针域}Node;//创建头结点//链表的头结点地址由函数值返回。Node *SListCreat(){ Node *head = NULL; //头结点作为标志,不存储有效数据 head = (Node *)malloc(sizeof(Node)); if (head == NULL) { return NULL; } //给head的成员变量赋值 head->id = -1; head->next = NULL; Node *pCur = head; Node *pNew = NULL; int data; while (1) { printf("请输入数据:"); scanf("%d", &data); if (data == -1) //输入-1,退出 { break; } //新结点动态分配空间 pNew = (Node *)malloc(sizeof(Node)); if (pNew == NULL) { //continue; break; } //给pNew成员变量赋值 pNew->id = data; pNew->next = NULL; //链表建立关系 //当前结点的next指向pNew pCur->next = pNew; //pNew下一个结点指向NULL pNew->next = NULL; //把pCur移动到pNew,pCur指向pNew pCur = pNew; } return head;}//链表的遍历int SListPrint(Node * head){ if (head == NULL) { return -1; } //取出第一个有效结点(头结点的next) Node *pCur = head->next; printf("head -> "); while (pCur != NULL) { printf("%d -> ", pCur->id); //当前结点往下移动一位,pCur指向下一个 pCur = pCur->next; } printf("NULL\n"); return 0;}//在值为x的结点前,插入值为y的结点;若值为x的结点不存在,则插在表尾。int SListNodeInsert(Node * head, int x, int y){ if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; while (pCur != NULL) { if (pCur->id == x) //找到了匹配结点 { break; } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } //2种情况 //1. 找匹配的结点,pCur为匹配结点,pPre为pCur上一个结点 //2. 没有找到匹配结点,pCur为空结点,pPre为最后一个结点 //给新结点动态分配空间 Node *pNew = (Node *)malloc(sizeof(Node)); if (pNew == NULL) { return -2; } //给pNew的成员变量赋值 pNew->id = y; pNew->next = NULL; //插入指定位置 pPre->next = pNew; //pPre下一个指向pNew pNew->next = pCur; //pNew下一个指向pCur return 0;}//删除第一个值为x的结点int SListNodeDel(Node *head, int x){ if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; int flag = 0; //0没有找,1找到 while (pCur != NULL) { if (pCur->id == x) //找到了匹配结点 { //pPre的下一个指向pCur的下一个 pPre->next = pCur->next; free(pCur); pCur = NULL; flag = 1; break; } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } if (0 == flag) { printf("没有值为%d的结点\n", x); return -2; } return 0;}//清空链表,释放所有结点int SListNodeDestroy(Node *head){ if (head == NULL) { return -1; } Node * tmp = NULL; int i = 0; while (head != NULL) { //保存head的下一个结点 tmp = head->next; free(head); head = NULL; //head指向tmp head = tmp; i++; } printf("i = %d \n", i); return 0;}//删除值为x的所有结点int SListNodeDelPro(Node *head, int x){ if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; int flag = 0; //0没有找,1找到 while (pCur != NULL) { if (pCur->id == x) //找到了匹配结点 { //pPre的下一个指向pCur的下一个 pPre->next = pCur->next; free(pCur); pCur = NULL; flag = 1; pCur = pPre->next; //break; continue; //跳出本次循环,重要 } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } if (0 == flag) { printf("没有值为%d的结点\n", x); return -2; } return 0;}//链表节点排序int SListNodeSort(Node *head){ if(head == NULL || head->next == NULL) { return -1; } Node *pPre = NULL; Node *pCur = NULL; Node tmp; // pPre->next != NULL,链表倒数第2个结点 for (pPre = head->next; pPre->next != NULL; pPre = pPre->next) { for (pCur = pPre->next; pCur != NULL; pCur = pCur->next) { //注意,排序,除了数据域需要交换,next指针还需要交换 if (pPre->id > pCur->id) //升序 { //只交换数据域 tmp.id = pCur->id; pCur->id = pPre->id; pPre->id = tmp.id; } } } return 0;}//假如原来链表是升序的,升序插入新节点//不能插入节点后再排序,是升序插入新节点xint SListNodeInsertPro(Node *head, int x){ //保证插入前是有序的 int ret = SListNodeSort(head); if (ret != 0) { return ret; } if (head == NULL) { return -1; } Node *pPre = head; Node *pCur = head->next; //1 2 3 5 6, 插入4 //3:pre, 5: cur while (pCur != NULL) { if (pCur->id > x) //找到了匹配结点 { break; } //pPre指向pCur位置 pPre = pCur; pCur = pCur->next; //pCur指向下一个结点 } //给新结点动态分配空间 Node *pNew = (Node *)malloc(sizeof(Node)); if (pNew == NULL) { return -2; } //给pNew的成员变量赋值 pNew->id = x; pNew->next = NULL; //插入指定位置 pPre->next = pNew; //pPre下一个指向pNew pNew->next = pCur; //pNew下一个指向pCur return 0; return 0;}//翻转链表的节点(不是排序,是翻转)//把链表的指向反过来int SListNodeReverse(Node *head){ if (head == NULL || head->next == NULL || head->next->next == NULL) { return -1; } Node *pPre = head->next; Node *pCur = pPre->next; pPre->next = NULL; // head->next->next = NULL; Node *tmp = NULL; while (pCur != NULL) { tmp = pCur->next; pCur->next = pPre; pPre = pCur; pCur = tmp; } //head->next->next = NULL; head->next = pPre; return 0;}int main(void){ Node *head = NULL; head = SListCreat();//创建头结点 SListPrint(head); SListNodeInsert(head, 5, 4); printf("在5的前面插入4后\n"); SListPrint(head); SListNodeDelPro(head, 5); printf("删除所有5结点后\n"); SListPrint(head); SListNodeSort(head); printf("排序后\n"); SListPrint(head); SListNodeInsertPro(head, 6); printf("升序插入6后\n"); SListPrint(head); SListNodeReverse(head); printf("链表翻转后\n"); SListPrint(head); SListNodeDestroy(head); head = NULL; printf("\n"); system("pause"); return 0;}九 函数指针1、指针函数,它是函数,返回指针类型的函数 //指针函数 //()优先级比高,它是函数,返回值是指针类型的函数 //返回指针类型的函数 int fun() { int p = (int )malloc(sizeof(int)); return p; }
    2、函数指针,它是指针,指向函数的指针,(对比数组指针的用法) 一个函数在编译时被分配一个入口地址,这个地址就称为函数的指针,函数名代表函数的入口地址。
    函数指针变量,它也是变量,和int a变量的本质是一样的。int fun(int a){ printf("a ========== %d\n", a); return 0;}//定义函数指针变量有3种方式:(1)先定义函数类型,根据类型定义指针变量(不常用)//有typedef是类型,没有是变量typedef int FUN(int a); //FUN是函数类型,类型模式为: int fun(int);FUN *p1 = NULL; //函数指针变量p1 = fun; //p1 指向 fun 函数fun(5); //传统调用p1(6); //函数指针变量调用方式(2)先定义函数指针类型,根据类型定义指针变量(常用)//()()优先级相同,从左往右看//第一个()代表指针,所以,它是指针//第二个括号代表函数,指向函数的指针typedef int(*PFUN)(int a); //PFUN是函数指针类型PFUN p2 = fun; //p2 指向 funp2(7);(3)直接定义函数指针变量(常用)int(*p3)(int a) = fun;p3(8);int(*p4)(int a);p4 = fun;p4(9);3、函数指针数组,它是数组,每个元素都是函数指针类型 void add() {} void minus() {} void multi() {} void divide() {} void myexit() {}
    //函数指针变量,fun1指向add()函数void(*fun1)() = add;fun1(); //调用add()函数//函数指针数组void(*fun[5])() = { add, minus, multi, divide, myexit };//指针数组char *buf[] = { "add", "min", "mul", "div", "exit" };char cmd[100];int i = 0;while (1){ printf("请输入指令:"); scanf("%s", cmd); for (i = 0; i < 5; i++) { if (strcmp(cmd, buf[i]) == 0) { fun[i](); break; //跳出for()循环,最近的循环 } }}4、回调函数,函数的形参为:函数指针变量 int add(int a, int b) { return a + b; }
    int minus(int a, int b){ return a - b;}//int(*p)(int a, int b), p 为函数指针变量//框架,固定变量//多态,多种形式,调用同一种接口,不一样表现void fun(int x, int y, int(*p)(int a, int b) ){ int a = p(x, y); //回调函数 printf("a = %d\n", a);}typedef int(*Q)(int a, int b); //Q 为函数指针类型void fun2(int x, int y, Q p)//p 为函数指针变量{ int a = p(x, y); //回调函数 printf("a = %d\n", a);}//fun()函数的调用方式fun(1, 2, add);fun2(10, 5, minus);5、函数的递归 递归:函数可以调用函数本身(不要用main()调用main(),不是不行,是没有这么做,往往得不到你想要的结果)
    (1)普通函数调用(栈结构,先进后出,先调用,后结束)void funB(int b){ printf("b = %d\n", b); return;}void funA(int a){ funB(a-1); printf("a = %d\n", a);}调用流程:funA(2) -> funB(1) -> printf(b) (离开funB(),回到funA()函数)-> printf(a)(2)函数递归调用(调用流程和上面是一样,换种模式,都是函数的调用而已)void fun(int a){ if(a == 1) { printf("a == %d\n", a); return; //中断函数很重要 } fun(a-1); printf("a = %d\n", a);}fun(2);(3)递归实现累加 1+2+3+……+100#define _CRT_SECURE_NO_WARNINGS#include <stdio.h>#include <stdlib.h>#include <string.h>int fun(int n){ if (n == 1) { return n; } else { return fun(n - 1) + n; }}int main(void){ int i = 0; int sum = 0; for (i = 1; i <= 100; i++) { sum += i; } printf("sum1 = %d\n", sum); sum = fun(100); printf("\nsum2 = %d\n", sum); printf("\n"); system("pause"); return 0;}(4)函数递归字符串反转十 预处理1、C编译器提供的预处理功能主要有以下四种: 1)文件包含 #include 2)宏定义 #define 3)条件编译 #if #endif .. 4)一些特殊作用的预定义宏
    2、#include< > 与 #include “”的区别“”表示系统先在file1.c所在的当前目录找file1.h,如果找不到,再按系统指定的目录检索。< >表示系统直接按系统指定的目录检索。注意:
    1. #include <>常用于包含库函数的头文件2. #include ""常用于包含自定义的头文件3. 理论上#include可以包含任意格式的文件(.c .h等) ,但我们一般用于头文件的包含。3、宏定义
    #define 宏名 字符串#define PI 3.14#define TEST(a,b) (a)*(b)宏的作用域取消宏定义#undef 宏名4、宏定义函数
    #define MAX2(a,b) (a) > (b) ? (a) : (b)#define MAX3(a,b,c) (a) > (MAX2(b,c)) ? (a) : (MAX2(b,c))5、条件编译 防止头文件被重复包含引用//#pragma once
    //_FUN_H_ 自定义宏,每个头文件的宏都不一样//假如test.h->_TEST_H_#ifndef _FUN_H_#define _FUN_H_ //函数的声明 //宏定义 //结构体#endif //!_FUN_H_6、动态库的封装和使用 socketclient
    7、日志打印 FILE LINE
    8、内存泄漏检查 memwatch
    0 留言 2019-07-30 16:00:39 奖励11点积分
显示 45 到 60 ,共 15 条

热门回复

eject