porridgechen890的笔记

梦里不知身是客

关卡文件字段说明

一级字段config

这里边包含了行数、列数、颜色数、剩余步数、剩余时间。

一级字段target

过关目标

其他一级字段

radio表示概率

magic_charge_need

magic_charge_tool

magic_charge_color

magic_charge_price

一级字段screen

二级字段map

表示对应格子是否镂空。只有0和1两个值。没有逗号隔开。

例子:

[map]
000000000
011101110
111111111
111111111
111111111
111111111
011111110
001111100
000111000
[map]
二级字段from

传送门,当出口空了的时候,会有元素从入口传过来。

是成对出现的。格式为:出口行,出口列,入口行,入口列

例子:
[from]
4,5,6,5
[from]

需要注意的是,这个入口和出口的关系不是独占的,即一个入口可以对应对各出口,一个出口可以对应多个入口。

并且一个格子可以同时是出口和入口。

所以在描述这种关系的时候,我用了下面这个结构体。

struct MyCellDataFromInfo
{
    //我这个格子作为哪些格子的出口
    std::map<std::pair<int, int>, bool> map_as_out;
    //我这个格子作为哪些格子的入口
    std::map<std::pair<int, int>, bool> map_as_in;
};

当点中某一个格子来删除这个格子的传送门关系时,需要根据这个结构体存的值,找到改格子作为入口和出口关联的格子,并删除对应的传送门关系。

二级字段exit

收集口。当收集物运动到这个格子时,完成收集。

例子:

[exit]
0,3
0,4
0,5
[exit]
二级字段footcloth

地毯,只要出现了地毯,本关的目标就会有把地毯铺满。

只有0和1两种值。类似于map字段。

二级字段jelly

冰块,只有0、1、2三种值。没有逗号分隔,类似map字段。

二级字段jellybear

可能的值是0、A、B、C、D、H、I、J、K、1、2、3、4、5、6、7、8、9

0是没有

ABCD是躺着的,从小到大

HIJK是站着的,从小到大

123456789是正方形的,从小到大

二级字段new

例子:

2,3
4,5

这个例子表示2行3列的格子和4行5列的格子是新元素产生的地方。

注意:如果没有new字段,那么程序会把最上面一排当作new。

二级字段new_cell_data

指定下落的元素。可以是重复的,也可以不重复。

例子:

[new_cell_data]
8,4,REPEAT(20:0,T4,T1)
8,5,1,1,1
[new_cell_data]
二级字段bar

格式:行1,列1,行2,列2

注意:必须是两个相邻的格子。

二级字段symbol

这个字段里的数据包含:

- 基础元素:随机颜色、另外6种基础元素
- 不区分颜色的道具:彩球
- 区分颜色的道具:
- 不区分颜色的普通石块(不可移动):ABCDE(abcde)
- 石头油(只有一层的障碍物):F(f)
- 四格的大蛋糕:G(g)
- 巧克力(会向周围蔓延):H(h)一层、I(i)两层
- 可移动的石块:JKOPQ(jkopq)。对应的图片名字是move_stone1到5
- 幽灵(会往上跳):N(n)。对应的图片名字是move_stone_bug
- 瓶子:W(w)。
- 收集物一:X(x或者12)。
- 收集物二:Y(y或者13)。
- 空白:Z(z)。
- 不可移动的石块:RSTUV(rstuv)。TODO

注意:

- 想大蛋糕这种元素,在读取完symbol字段后,需要再检查一边,如果某个格子是大蛋糕,那么另外三个格子里原来的值就失效了。
- symbol字段里的ABCDE,是没有颜色的石块,可以配合字段stone_0_color来设置成带颜色的石块。
- symbol字段里的*012345表示随机和6种颜色,配合tool字段就能决定这个格子是个什么颜色的道具。
二级字段stone_0_color

配合symbol字段设置不可移动的普通石块(即ABCDE)的颜色。

二级字段tool

横消:1
竖消:2
炸弹:4
彩球:5
蝴蝶:6
加五秒:8
加五步:9
产出其他元素的egg:11
产出其他元素的luck:12

注意:

- 横消、竖消、炸弹、蝴蝶都是区分颜色的。彩球不区分颜色。
- 横消、竖消、炸弹、蝴蝶的颜色是根据symbol字段里对应格子的颜色来决定的。
- 横消、竖消、炸弹、蝴蝶的颜色有012345共6种,另外还有一种随机颜色。
二级字段timer

倒计步数炸弹。取值范围是0到30步。

这个字段里的值生效的前提条件是tool字段里对应格子的道具类型是倒计步数道具。

二级字段factory_symbol

作用是生成一个新元素。

可能的值有空值(Z),然后012345对应6种颜色。对应的图片名字是basket0到5。

这个字段的读取要在symbol字段之后,应为这个字段读取后会覆盖掉symbol字段里的值。

二级字段firefly

萤火虫,可能的值是0到999。但是编辑器里只支持0到9。因为实际配置关卡时也不会配置超过9个的萤火虫。

这个字段的读取要在symbol字段之后,应为这个字段读取后会覆盖掉symbol字段里的值。

二级字段mushroom

蘑菇,有开闭两种状态。

那么这个字段可能的值有三个,0,1,2。分别代表没有蘑菇,开着的蘑菇,闭着的蘑菇。

当棋盘里的所有蘑菇都处于开状态时,所有蘑菇一起被消除掉。

闭着的蘑菇可以被旁消形成开状态的蘑菇。由闭到开是一个不可逆的过程。

这个字段的读取要在symbol字段之后,应为这个字段读取后会覆盖掉symbol字段里的值。

对应的图片是mushroom1和mushroom2。

二级字段hardstone

不可移动的硬石块。

取值范围是一层到五层。不过只有三层的图片,所以编辑器也只支持了三层。

注意:一层硬石块可以被旁消。其他层的硬石块是不能被旁消的,只能被道具消除。

这个字段的读取要在symbol字段之后,应为这个字段读取后会覆盖掉symbol字段里的值。

对应的图片名字是hardstone1到5。

二级字段hard_move_stone

可移动的硬石块。

取值范围是一到五层。只能被道具打掉,不能被旁消。

这个字段的读取要在symbol字段之后,应为这个字段读取后会覆盖掉symbol字段里的值。

对应的图片名字是hard_move_stone1到5。

二级字段move_static_stone

可以下落,不能交换的石块。

会被旁消。

取值范围是一层到五层。

这个字段的读取要在symbol字段之后,应为这个字段读取后会覆盖掉symbol字段里的值。

二级字段bigstone

大石块。取值范围是0到8。

0表示没有。

1234是2*2的大石块。

5678是3*3的大石块。

二级字段oil和moveoil

oil和moveoil都是最多两层。

oil和moveoil都不可交换。moveoil可以下落,oil不可以下落。

moveoil不能被旁消。oil可以被旁消。

总结

石头的属性:

  1. 颜色

  2. 厚度(层数)

  3. 是否可以移动

    • 可移动(分为两种):这个带move

      • 只能下落,不能被交换。这个带static

      • 可以下落,也可以交换

    • 不可移动:

  4. 硬度(分为两种):

    • 普通硬度:可以被道具消除,也可以被旁消。

    • 高硬度:能被道具消除,不能被旁消。这个带hard

Github地址

打开 DependenciesGui.exe 后把需要解析的 exe 拖拽到窗口里就可以分析出来用到了哪些依赖,会指明每个依赖的路径,这点很方便。

另外,我在搜索的时候看到了另一个方法,是利用 Visual Studio 自带的工具来查看依赖。

先在开始菜单里找到对应的命令行工具,我这里安装的是 VS2015。

打开后,切换到需要查看的 exe 所在的目录,使用 dumpbin /dependents xxx.exe 来查看依赖。这里的 xxx 是你的 exe 的名字。

注:Windows 下切换盘符是直接输入盘的名称,比方说切换到 D 盘,就输入 D: ,然后再用 cd 命令在当前盘下切换目录。

问:模板函数能否把声明和定义分开放在头文件和源文件中?

答:能,但是推荐做法是把定义也写在头文件里。

问:分开写,具体应该怎么写呢?

答:以两个数相加做例子,分别看看声明、定义、调用处的写法:

//a.h
#pragma once

template <typename T>
T func_add(T t1, T t2);
//a.cpp
#include "a.h"

template <typename T>
T func_add(T t1, T t2)
{
    return t1 + t2;
}
template int func_add(int, int);
template double func_add(double, double);
//main.cpp
#include "a.h"

int main()
{
    int a1 = func_add(3, 2);
    printf("%d\n", a1);

    double b1 = func_add(3.3, 2.2);
    printf("%lf\n", b1);

    char c1 = func_add('a', 'b');
    printf("%c\n", c1);

    system("pause");
    return 0;
}

说明一:调用的时候可以显式的指明数据类型,如double b1 = func_add<double, double>(3.3, 2.2);,如果程序员没有显式指明,那么编译器会自动推导类型。

说明二:如果是声明和实现分开写(如上面例子)的情况,在实际调用中,用到那种类型,就得指明让编译器去生成对应的定义,如上面程序,用到了char c1 = func_add('a', 'b');,那就必须在a.cpp里添加template char func_add(char, char);,不然会报找不到函数定义的错。

参考文章

个人吐槽:如果想要把模板函数的声明和实现分离,还得自己挨个写一遍告诉编译器,那这个模板函数,就有点不“模板”了!

换行符相关的配置

  1. autocrlf

    Windows上用CR加LF来表示换行,而MacOS和Linux用LF表示换行。所以有了这个配置。

    git config --global core.autocrlf true
    git config --global core.autocrlf false
    git config --global core.autocrlf input

    autocrlf = true 表示在Windows上拉取时转成回车换行,提交时变成换行。

    autocrlf = false 表示在Windows上拉取或提交时不转换。

    autocrlf = input 表示在Windows上拉取时不转换,提交时变成换行。

  2. safecrlf

    git config --global core.safecrlf true
    git config --global core.safecrlf false
    git config --global core.safecrlf warn

    safecrlf = true 表示拒绝提交包含混合换行符的文件。

    safecrlf = false 表示允许提交包含混合换行符的文件。

    safecrlf = warn 表示提交包含混合换行符的文件时给出警告。

大小写敏感的配置

  1. ignoreCase
git config --global core.ignoreCase false

在firebase的后台看到的崩溃信息:

Fatal Exception: NSInvalidArgumentException
-[WKWebViewConfiguration setDataDetectorTypes:]: unrecognized selector sent to instance 0x1861a8e0

搜索到的答案

setDataDetectorTypes is available only from iOS 10. You have to add if @available(iOS 10.0, *) { ... } into your code

JNI

Java Native Interface。jni.h封装了Java代码和原生代码的交互功能,Cocos2dx再次封装成了JniHelper类。

C++数据类型转换成Java数据类型

C++ java
void V
bool Z
char C
int I
short S
long J
float F
double D
std::string Ljava/lang/Object;

C++调Java

  1. Java端要做的事:把要被调用的函数定义好。

    // 定义在org/cocos2dx/cpp/AppActivity.java文件里的函数
    public static void java_func_1(int i, String s) {}
  2. C++端要做的事:

    // 包含头文件
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
        #include "platform/android/jni/JniHelper.h"
    #endif
    
    // 常见一点的写法
    void YourClass::YourFunc(int IntPara, const std::string& StrPara)
    {
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    
        // 声明jni方法结构体
        cocos2d::JniMethodInfo jm;
    
        // 查找方法是否存在,分为静态方法和实例方法。这里的例子是静态方法。
        bool bHave = cocos2d::JniHelper::getStaticMethodInfo(jm, "org/cocos2dx/cpp/AppActivity", "java_func_1", "(ILjava/lang/String;)V");
    
        // 如果方法存在就能调用CallStaticMethod或者CallObjectMethod。
        if (bHave)
        {
            // C++的字符串要转换成Java的字符串
            jstring k1 = jm.env->NewStringUTF(str.c_str());
            jm.env->CallStaticVoidMethod(jm.classID, jm.methodID, i, k1);
            jm.env->DeleteLocalRef(jm.classID);
        }
    #endif
    }
    
    // 更简单一点的写法
    void YourClass::YourFunc(int IntPara, const std::string& StrPara)
    {
    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
        // 这么写不用去判断方法是否存在,也不需要去把C++的数据类型转成Java的数据类型。(PS:如果方法不存在,也不会出错)
        cocos2d::JniHelper::callStaticVoidMethod("org/cocos2dx/cpp/AppActivity", "java_func_1", i, str);
    #endif
    }

Java调C++

  1. Java端要做的事:把要被调用的函数声明好。

    public static native void funcName2(int i, String s);

    在java代码里合适的地方调用这个函数。另外我发现声明不需要写在调用处之前。为啥我也不知道。

  2. C++端要做的事:随便找个cpp文件,写上这个函数的实现部分。

    #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID)
    extern "C"
    {
        JNIEXPORT void Java_org_cocos2dx_cpp_AppActivity_funcName2(JNIEnv* env, jobject thiz, int i, jstring str1)
        {
            std::string str2 = cocos2d::JniHelper::jstring2string(str1);
            cocos2d::log("i=%d s=%s", i, str2.c_str());
        }
    }
    #endif

    另外,我发现一个问题,这个函数似乎不能带下划线,比方说我写成func_Name2,并且我在声明处、调用处、定义处都做了相应的修改,还是不行,而且会引起崩溃。

引入问题

先看下面这段代码:

#include <cstdlib>
#include <iostream>

class A {
public:
    void f() {
        if (this == nullptr) {
            std::cout << "is null" << std::endl;
        }
        else {
            std::cout << "not null" << std::endl;
        }
    }
};

int main()
{
    A* p = new A();
    p->f();
    delete p;
    p = nullptr;

    //我本以为程序执行到这一句会奔溃,结果没有
    p->f();

    system("pause");
    return 0;
}

执行结果:

not null
is null
请按任意键继续. . .

我第一个疑惑的点是,为啥空指针还能调用成员函数。

修改代码,验证猜想

#include <cstdlib>
#include <iostream>

class A {
public:
    A::A() {
        m_num = 666;
    }

    //成员函数1,没有访问成员变量
    void member_func_1() {
        if (this == nullptr) {
            std::cout << "is null" << std::endl;
        }
        else {
            std::cout << "not null" << std::endl;
        }
    }

    //成员函数2,有访问成员变量
    void member_func_2() {
        if (this == nullptr) {
            std::cout << "is null ";
            std::cout << m_num << std::endl;
        }
        else {
            std::cout << "not null ";
            std::cout << m_num << std::endl;
        }
    }

    //静态成员函数,没有this
    static void s_memeber_func() {
        std::cout << "static member func called" << std::endl;
    }

    //虚函数
    virtual void v_member_func() {
        std::cout << "virtual member func called" << std::endl;
    }
private:
    int m_num;
};

int main()
{
    A* p = new A();
    p->member_func_1();
    p->member_func_2();
    p->s_memeber_func();
    p->v_member_func();
    delete p;
    p = nullptr;

    p->member_func_1();//ok
    p->member_func_2();//error
    p->s_memeber_func();//ok
    p->v_member_func();//error

    system("pause");
    return 0;
}

类的成员函数并不与具体对象绑定,所有的对象共用同一份成员函数体,当程序被编译后,成员函数的地址即已确定,这份共有的成员函数体之所以能够把不同对象的数据区分开来,靠的是隐式传递给成员函数的this指针,成员函数中对成员变量的访问都是转化成”this->数据成员”的方式。

为何会有这篇文

因为我看到了这样一句代码 if (this == nullptr)return;

例1

class A {
public:
    int m_a;
};

class B {
public:
    int m_b;
};

class C : public A, public B {
public:
    int m_c;
};

int main()
{
    C c;
    A* pa = &c;
    B* pb = &c;
    void* pc = &c;
    printf("%p\n%p\n%p\n", pa, pb, pc);
    return 0;
}

输出结果:

pa=0133F59C
pb=0133F5A0
pc=0133F59C

为啥pb的值和pc的值不一样呢?派生类指针转成基类指针时,派生类独有的部分会被丢弃掉,比方说对象c里有一个m_a的内存区间是对象b里没有的,当把c的指针转成b的指针时,m_a对应的内存区间会被丢弃,所以发生了pb和pc的值不相等的情况。

例2

class A {
public:
    virtual void f() {}
};

class B{
public:
    int m_b;
};

class C : public A, public B {
};

int main()
{
    C c;
    A* pa = &c;
    B* pb = &c;
    void* pc = &c;
    printf("%p\n%p\n%p\n", p1, p2, p3);
    system("pause");
    return 0;
}

这里的输出结果,pb和pc同样是不相等的。因为有虚函数的对象,会有一个指向虚函数表的指针变量,这个变量的内存是在其他成员变量之前的,占一个指针变量那么宽的内存空间。

关于虚函数的内存的其他知识

从内存角度来看,虚函数被覆盖后则相当于另一个函数了

如果派生类的虚函数覆盖了基类的虚函数,那么这两个函数地址是不同的。如果派生类只是继承了基类的虚函数,并没有覆盖它,那么这两个函数的地址是相同的。

#include <cstdlib>
#include <cstdio>

class A {
public:
    virtual void fa() {}
};

class B : public A {
};

class C : public A {
public:
    virtual void fa() override {}
};

int main()
{
    A a;
    B b;
    C c;
    //可以断点查看,三个对象里的虚函数指针的值,a和b是一样的,但是c和ab不一样。
    return 0;
}

为什么要内存对齐

方便计算机读写数据。对齐的地址一般都是 n(n = 2、4、8)的倍数。

  1. 1 个字节的变量,例如 char 类型的变量,放在任意地址的位置上;

  2. 2 个字节的变量,例如 short 类型的变量,放在 2 的整数倍的地址上;

  3. 4 个字节的变量,例如 float、int 类型的变量,放在 4 的整数倍地址上;

  4. 8 个字节的变量,例如 long long、double 类型的变量,放在 8 的整数倍地址上;

基本类型所占字节数

  1. char 1

  2. short 2

  3. int 4

  4. long 4(32位)或者8(64位)

  5. long long 8

  6. float 4

  7. double 8

  8. 指针 4(32位)或者8(64位)

  9. 枚举 4

影响内存对齐的因素

  1. 变量的排列顺序

  2. 取消变量对齐:attribute((packed))

  3. #pragma pack (n):让变量强制按照 n 的倍数进行对齐

内存对齐原则

  1. sizeof一个结struct、class的结果必然是其内部的“最宽基本类型成员”的整数倍。

    struct A { int a; char b; }; //sizeof(A)的结果是4的倍数。

    struct B { int a; char b; double c; }; //sizeof(B)的结果是8的倍数。

    struct C { A a; char b; }; //sizeof(C)的结果是4的倍数。

    struct D { B a; char b; }; //sizeof(D)的结果是8的倍数。

结构体或类的自身对齐值:其成员中自身对齐值最大的那个值。

#include <cstdlib>
#include <cstdio>

class A {
public:
    int m_a;//占0~3字节
    char m_b;//占第4字节
    char m_c;//占第5字节,第6~7字节空着
};

class B {
public:
    char m_a;//占第0字节,1~3空着
    int m_b;//占4~7字节
    char m_c;//占第8字节,第9~11空着
};

class C {
public:
    char m_a;//占第0字节
    char m_b;//占第1字节,2~3空着
    int m_c;//占4~7字节
};

int main()
{
    printf("%d\n%d\n%d\n", sizeof(A), sizeof(B),sizeof(C));//8 12 8
    system("pause");
    return 0;
}

虚函数表也遵循字节对齐

虚函数表指针永远在类的开头位置,并且虚函数表指针占用4(32位)或8(64位)字节。

#include <cstdlib>
#include <cstdio>

class D {
public:
    virtual void f() {}
};

class E {
public:
    virtual void f() {}
    double m_a;
};

class F {
public:
    virtual void f() {}
    int m_a;
};

class G {
public:
    virtual void f() {}
    char m_a;
};

int main()
{
    //结果4 16 8 8
    printf("%d\n%d\n%d\n%d\n", sizeof(D), sizeof(E), sizeof(F), sizeof(G));
    system("pause");
    return 0;
}

参考文章

https://blog.csdn.net/weixin_48896613/article/details/127371045

涉及的知识

泰山封禅

历史上泰山封禅的皇帝有六位:秦始皇、汉武帝、汉光武帝、唐高宗、唐玄宗、宋真宗。电视剧第11集提到了这个事,由此推断电视剧所处的时代是北宋真宗时期。

多嘴一句:后来的皇帝嫌弃宋真宗拉低了封禅的水平,所以就都不去封禅了。

北宋四京

一开始只有东京(今河南开封),西京(今河南洛阳)。后来增加了南京(今河南商丘)、北京(今河北大名东北)。

澶渊之盟

宋真宗在位期间,辽南下侵宋,打到了澶渊(今河南濮阳)。宋真宗也想往南边跑路,但是被宰相寇准硬拉着去前线督战了。后来宋军用一个威力很大的弓箭射死了辽军主帅,辽军那边瞬间就不想打了。正好宋真宗也不想打了,于是签了个和约。和约规定宋每年给辽送些银绢。正因为签了这个和约,宋辽之后百年没打仗,给宋朝的发展争取了机会。

东京梦华录

北宋被金所灭之后,开封有个叫孟元老的市民逃难到了南方。他时常回忆起东京的繁华,再看看现在颠沛流离的生活,心中感慨,于是写下了一本回忆东京生活的书《东京梦华录》。电视剧的名字叫梦华录,我觉得大概是根据这个来的吧。

女主昌

太白昼现,女主昌。意思是说“太白星白天也能看见啦!要出现女皇帝了!”。这种话就属于谶言(可以理解为预言),搁现在就是反动言论。类似于“大楚兴,陈胜王”。所以皇城司的工作人员(顾千帆)要去处理这个事。

真实历史上,在真宗朝后期,他的老婆刘娥还真就掌握了朝政,还穿了龙袍,只是没有称帝。

赵盼儿风月救风尘

《赵盼儿风月救风尘》是元代戏曲家关汉卿创作的杂剧,主要讲述花花公子周舍利用手段骗娶宋引章,婚后又对其百般虐待,而赵盼儿为救助姐妹宋引章,以风月手段智胜恶少周舍,最终宋引章被救出并与安秀实结为夫妇的故事。

明妃曲

宋引章弹的《明妃曲》,这个明妃是指王昭君。我觉得有趣的是,王昭君的“落雁”称号的得来,也是因为弹琵琶。

后人称王昭君为王明君是为了避司马昭的讳。司马昭真讨厌,是吧!

白头如新,倾盖如故

我们得承认,确实是有一见如故的情况。这话出自西汉时期,邹阳《狱中上梁王书》语曰:“有白头如新,倾盖如故。”何则?知与不知也。

翻译过来就是,俗话说:“有相处到老还是陌生的,也有停车交谈一见如故的。”为什么?关键在于理解和不理解啊。

剧中赵盼儿伸手说倾盖如故,顾千帆说白头如新。顾千帆心里想“皮一下,很开心”。

宋代的重文轻武

因为宋代重文轻武,所以顾千帆弃文从武被很多人不理解。

南唐

夜宴图

瓷器

吐槽

  1. 画蛇添足的福尔摩斯

    高府奶娘陷害赵盼儿,大哥死了,妹子喊着报官。赵盼儿和其余的人都没有去查看一下大哥是不是真死了。而且后面剧情是,赵盼儿识破了妹子的诡计。难道识破诡计不是有更直接更简单的办法吗?你去看一下大哥死了没不就行了,还非搞得跟福尔摩斯推理似的。

0%