Esper 发布的文章

在写编译原理的时候,在词法分析器类里放了一个ifstream类型的成员变量,然后给语法分析器写一个构造函数,把生成的词法分析器作为参数传入。

声明代码大致如下:

class SimpleLexiconAnalyzer
{
public:
    SimpleLexiconAnalyzer(string filename);
    Word scan();
private:
    ifstream stream;
    char nextChar();
    static ifstream openFile(string filename);
};

class GrammarAnalyzer
{
public:
    GrammarAnalyzer(SimpleLexiconAnalyzer tokenSrc);
    void parseRules(vector<Word> tokens);
    static bool equals(vector<Word> a, vector<Word> b);
private:
    SimpleLexiconAnalyzer &tokenSrc;
    map<Word, vector<WordSeq>> rules;
};

然后就碰到了标题所述的问题。发现问题出在

    GrammarAnalyzer(SimpleLexiconAnalyzer tokenSrc);

这一行。经过资料查阅后,发现在C++中stream类型的对象是不允许被复制的(在类的定义时,删除了operator=操作),决定换用引用。查了老半天,卡在了引用的初始化上(根据C++语法,引用变量是不允许重新赋值的),然后在stackoverflow上受到了一点启发,写出了如下代码:

GrammarAnalyzer::GrammarAnalyzer(SimpleLexiconAnalyzer &tokenSrc): tokenSrc(tokenSrc) {}

然后错误消失,编译顺利通过。

顺带一提,启发来自一年前写的数据结构代码。鬼知道为什么当时我能查到可以这么写,结果一年后不但全忘了,连查都快查不到了。。。

在Eclipse中使用Gradle配置Web项目的时候,默认包含了junit测试工具,但是打开Project and External Dependencies的时候,发现junit-4.12.jar是灰色的,可能由于配置的原因,一开始在Eclipse里面甚至提示找不到junit包。

后来在网上查阅资料发现灰色表示引用了库,但是不参与编译,即对于java编译器来说是找不到该库的。显然找不到的类Eclipse是不会给提示的,但写个java没代码补全这日子咋过。

于是打算用各种方法,包括在build.gradle里把scope改成implementation,在Eclipse的build path里添加自带的junit库,总而言之要想办法把红线去掉,顺便把代码提示搞出来。<-这些都是错误示例。

最后重启了一下Eclipse就发现好了。

最近在学操作系统的时候,接触到了Linux的部分基础函数,比如与进程相关的forkexec等。其中exec是一个系列的函数,有一个名字相似的函数族,比如execvexecl等等。根据之前看到的某篇博客,叙述了exec后跟不同字母的含义:

l(可能代指list?)表示参数可以通过C/C++函数调用时的可变参数的形式调用exec,只需最后传递一个NULL表示参数结束即可; v(可能代指variable?)表示参数以一个字符串数组的形式将参数传递给待调用进程,同样要求最有一个元素为NULLp表示允许带PATH的相对路径搜索; e表示传递环境变量。

其中这个execvp,在MSVC中的thread.h中的定义是这样的:

_DCRTIMP intptr_t __cdecl execvp(
    _In_z_ char const*        _FileName,
    _In_z_ char const* const* _Arguments
    );

然后就看到了这么一个神奇的参数类型定义:char const* const*,在IntelliCode里给出的是另一个定义:const char* const*。这两者一样吗?分别代表的又是什么类型的数据?

然后就涉及到了最基本的问题,const修饰符与变量类型申明关键字,以及指针之间的关系。

两个在网上随便找找就能见到的例子:

char* const s1 = ...;
const char* s2 = ...;

两者分别定义了什么?有区别吗?

答案是,两者都定义了一个char*类型的指针,都具有某些值不能被修改的限制(const修饰符)。但是,第一个限制的是,s1本身不能被修改,而第二个限制的是不允许通过s2修改*s2的值。换句话说,可以对s1进行的相关写操作是修改*s1的内容,而可以对s2进行的相关写操作是修改s2本身。相应地,对s2不要求初始化,而s1一定要初始化,否则会报错,因为后续就不允许修改了,而使用没有初始化过的变量,结果又是不可预测的。

其中对声明的性质起到关键作用的是指针符号*的位置。

来自StackOverflow的一个答案:

int       *       mutable_pointer_to_mutable_int;
int const *       mutable_pointer_to_constant_int;
int       * const constant_pointer_to_mutable_int;
int const * const constant_pointer_to_constant_int;

从这几个声明可以看出,若是*const前面,则指针本身不可修改;否则指向的数据不可修改。可以结合指针的使用进行理解:定义的时候声明了const *p,则p可修改,*p不可修改;若是*const p,则*p可修改,p不可修改,此时const的作用域仅仅在p上。若是在*前后都加上const,则两者的作用复合,形成一个无论如何都无法修改的指针。

此外,由于const和数据类型的声明符的顺序没有影响,所以const int*int const*的作用是一样的。

然后就回到了char const* const*const char* const*的关系上。显然,由于char和第一个const在所有*的同侧,交换顺序不影响定义,所以两者是等价的。

那它定义了个啥呢?

首先去除所有的const,因为它仅仅是对于读写属性的限制,则实际的类型定义为:char **,也就是一个char类型的二级指针,即char*指针的指针。也就是说,定义的东西是一个传统的字符串数组,但是由于是使用指针而不是数组定义的,所以每个一维串的长度可以不一致,整个字符串数组的内存分配也不一定连续。然后再补回const属性,假设指针名为p,则该声明限定了*p**p是常量,不允许通过该指针进行修改。也就是说,char const* const* p定义了一个char类型的二级指针,且不允许修改该指针指向地址的值,这个值是一个直接指向char类型数据的指针;也不允许修改这个值作为指针,所指向的目标内存地址的值。所以重新把视角拉高,整个声明到底定义了啥?一个指向不允许内容被修改的字符串数组。从功能上看,也就像是Java编程时在某些地方声明的final对象一样,用于确保在执行该函数过程时,数据不会被有意或无意地修改。

Ура!是HTTP/2!

几个月之前就想给站点加上HTTPS了,我这么个无名小站都被强制上过一次广告(看自己网站的时候发现的= =)。万一变成人家的支付宝红包/淘宝活动/并夕夕推广重灾区了怎么办。直到今天才下定决心来解决这件事情,唉,拖延症病入膏肓啊。

初次接触到HTTP/2这个技术是某一次在看某v开头(咳咳)的魔法工具的时候,见到了Akamai公司做的一个示例。至于Akamai是个啥公司呢,接触过烂橘子的朋友肯定知道,这是一家CDN提供商,提供的服务主要是网络资源的托管一类的。(举个类似的例子,七牛云?)再加上Wappalyzer里面那个蓝色的小闪电看起来好炫!于是就自己动手,丰衣足食了。

其实整个过程中步骤十分简单,大致可以描述如下:

  1. 下载用于申请免费SSL证书的certbot-auto脚本;
  2. certbot-auto -d <domain-name>用于给指定域名申请SSL证书,如果用的是nginx,而且配置文件不是放在/etc/nginx.conf的环境下的话(LNMP说的就是你),可以选择在certbot-auto后添加certonly参数来表示只需要该脚本来申请证书,配置什么的由自己来;
  3. ssl on语句告诉nginx该server需要启用SSL协议;
  4. 如果用的是certbot-auto certonly的话,申请成功后会把证书的地址告诉用户,将该地址添加到nginx对应server的配置中即可。ssl_certificatessl_certificate_key对应的分别是证书的公钥和私钥,使用certbot-auto生成后两者的名字对应为fullchain.pemprivkey.pem,记得不要搞错了;
  5. listen参数的端口从80改为443,再在后面添加ssl http2参数,用于启用HTTP/2;
  6. 新建一个server,监听相同的地址的80端口,然后加一句:
    return 301 https://$server_name$request_uri;

    以使得访问该网站的用户都强制使用HTTPS。当然,如果浏览器支持HTTP/2的话,会自动启用该协议的;

  7. 可以添加这么两行:
    ssl_stapling on;
    ssl_stapling_verify on;

    来缩短SSL证书的查询时间,提高用户体验,具体信息请直接搜ssl stapling。

然后整个人就卡在了第二步,说是什么tk域名的CAA记录查不到,超时,卡了一上午,中午都差点头疼的午睡没睡着。。后来发现是云主机默认的DNS服务器有问题,加了一个Google的8888服务器问题马上解决,我tm。。。

最后顺带提一句,之前在网上看到过HTTP/2本身其实是可以不用SSL协议的,但是网络安全是大势所趋,所以基本上上了HTTP/2的都会用SSL,甚至两者变成了必然关系,很少有见到裸HTTP/2的配置教程了。

好了,又到了快乐的C++快速上手时间 之前在这里讲过实际中.cpp.h的一种用法,然后这次涉及到了模板函数template。模板函数与传统函数不同,并无法直接使用,需要先传入所给定的参数类型,C++才能根据类型推断出该函数体实际所需要执行的指令。所以,应该将模板函数的函数体在头文件中实现。不然的话,比如说在Visual Studio中,若是还是傻傻地(像我一样)在头文件定义模板函数,然后跑到源文件中定义函数体的话,就会报出LNK2019,也就是常见的找不到函数引用的错误。

拓展阅读 -> https://stackoverflow.com/questions/495021/why-can-templates-only-be-implemented-in-the-header-file