自制编程语言(一):EBNF表达式及Boost.Spirit的使用

大家有没想过,编程语言是如何被编译或解释执行的?使用sscanf还是正则表达式?今天我们一起来揭开编程语言的神秘面纱。
程序语言按照层次,可以分为普通语言以及语法描述语言。普通语言包括出语法描述语言外的其他所有语言。简单的说,C++,C#,Java、XML、JSON等等都属于这类;语法描述语言是用来描述一门语言的语法,通常一门语法描述语言就能描述所有的语言的语法。这类语言最著名的叫EBNF(扩展巴克斯范式)。
这类语言的作用就是定义一门语言的语法。语法定义完毕后,语言会按照设计思路,生成AST(抽象语法树)。抽象语法树能非常方便的生成中间语言。通常,这个过程就叫编译器前端。相应的,编译器后端是指将中间语言优化,再解析为本地字节码的过程,叫编译器后端。一般来说,生成AST后,一个语言基本上就设计完成了,后端可以自己撸,也可以对接llvm等,很方便就能实现一门编程语言。
语言前端基于EBNF的工具有很多,看一些工具的名称就能看出来,比如yacc(又一个编译器的编译器)。C++的“准”标准库也提供了全套库,叫“Boost.Spirit”。后面的代码我将以这个工具来举例。
说及这个库,首先不得不谈这个EBNF表达式。下面我给大家展示一个用于描述四则运算的EBNF代码:

1
2
3
4
5
6
expr1 := oper1 ('+' | '-') (expr1 | oper1)
expr2 := oper2 ('*' | '/') (expr2 | oper2)
oper1 := expr2 | integer | block;
oper2 := integer | block;
block := '(' oper ')';
oper := expr1 | expr2 | integer | block;

继续阅读自制编程语言(一):EBNF表达式及Boost.Spirit的使用

计算机语言总结

为了方便执行各种各样不同的任务,描述不同的数据,先后诞生了成千上万种计算机语言。每种计算机语言都有自己的特点,分类方式有多种,按照不同的分类方式,语言的类型也不同。下面按照最常用的方式对语言分类进行大致介绍。其实这种分类方式并不完全准确,数据和代码并没有明显分界线,所以我就按照使用习惯对它们进行分类。

1、数据描述语言
数据描述语言分为基础数据描述语言以及文本标记语言,它们的界限也并不十分明确。

1.1、基础数据描述语言主要有:regex、css、xml、json、xaml
regex是正则表达式语法,几乎所有的通用编程语言都能使用正则表达式,但让人意外的是不同语言的regex语法不完全一样,有些有细微区别,需要仔细鉴别。另外正则表达式对于数字处理上较弱,另外语法教复杂,对于新手来说非常不友好。
css是样式标记语言的老大,主要存在于网页或视频中,网页文字或图像的样式、电影字幕通常都使用css进行描述,它的儿子们有qss(css的qt移植版)、less(语法比css先进,编译为css后通用)、sass(与less差不多)等等。
xml、json是使用最广泛的基础数据描述语言,可以描述几乎所有的数据类型,它俩语法结构上比较相似,但几乎不成对出现。xml年代较古老,所以基于它的语法变种更多;从语法上讲json更先进,能以更少的空间描述相同的数据量。它俩都有schema,用于保证格式准确。
xaml是xml的一个变种,主要用于描述.NET系列程序界面(WinForm除外的窗口程序)。描述界面的语言特乱,一个框架就是一种界面描述语言,比如.NET、安卓等,所以跨框架迁移是一件非常麻烦的事。
继续阅读计算机语言总结

C++:模拟键盘

模拟键盘是一个简单的话题,随便普通程序猿都能说出好多种方式。不同的方式应用于不同场合,总的来说分为三大类:
1、用户层模拟键盘
这个层来模拟是最方便的,但也是最容易无效的。总的来说有三种方式,第一种是直接往目标窗口发送按键消息;第二种是使用剪贴板复制待粘贴消息然后在目标窗口模拟Ctrl+V;第三种是用户层触发按键事件
这儿贴一个模拟输入一个字节的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
bool input (char ch)
{
    DWORD KeyScan = ::OemKeyScan (ch);
    bool bShift = !!(KeyScan >> 16);
    UINT uCode = KeyScan & 0xFFFF;
    UINT uScanCode = ::MapVirtualKey (uCode, MAPVK_VK_TO_VSC);
    UINT uShiftScanCode = ::MapVirtualKey (VK_SHIFT, MAPVK_VK_TO_VSC);
    if (bShift)
    {
        ::keybd_event (VK_SHIFT, (BYTE) uShiftScanCode, KEYEVENTF_EXTENDEDKEY, 0);
        std::this_thread::sleep_for (std::chrono::milliseconds (20));
    }
    ::keybd_event (uCode, (BYTE) uScanCode, KEYEVENTF_EXTENDEDKEY, 0);
    std::this_thread::sleep_for (std::chrono::milliseconds (20));
    ::keybd_event (uCode, (BYTE) uScanCode, KEYEVENTF_KEYUP, 0);
    std::this_thread::sleep_for (std::chrono::milliseconds (20));
    if (bShift)
    {
        ::keybd_event (VK_SHIFT, (BYTE) uShiftScanCode, KEYEVENTF_KEYUP, 0);
        std::this_thread::sleep_for (std::chrono::milliseconds (20));
    }
    return true;
}

这个函数很可能在一些有简单安全措施的软件里面失效。软件屏蔽键盘模拟按键一般是使用钩子,那么可以dll注入然后反钩子,挂钩SetWindowsHookEx即可。注意反钩子必须和目标在同一个进程里,否则M$的Copy-On-Write会让反钩子失效。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL func_unhook (LPVOID func_ptr, WORD param_num, DWORD ret_val)
{
    // mov eax, 12345678h
    // ret 0004h
    BYTE bBuf [] = { '\xB8', '\x00', '\x00', '\x00', '\x00', '\xC2', '\x00', '\x00' };
    *(DWORD*) (&bBuf [1]) = ret_val;
    *(WORD*) (&bBuf [6]) = param_num * 4;
    DWORD dw = 0, dw2 = 0;
    BOOL bRet = ::VirtualProtect (func_ptr, 8, PAGE_EXECUTE_READWRITE, &dw);
    if (!bRet)
        return FALSE;
    ::memcpy (func_ptr, bBuf, 8);
    if (func_ptr != ::VirtualProtect)
        ::VirtualProtect (func_ptr, 8, dw, &dw2);
    return TRUE;
}
 
func_unhook (::SetWindowsHookA, 2, 1);
func_unhook (::SetWindowsHookW, 2, 1);
func_unhook (::UnhookWindowsHook, 2, 1);
func_unhook (::SetWindowsHookExA, 4, 1);
func_unhook (::SetWindowsHookExW, 4, 1);
func_unhook (::UnhookWindowsHookEx, 1, 1);

反钩子后模拟键盘事件差不多可以过绝大部分弱保护的安全措施了,不过这也不完全总是灵的。如果以上方法都不行,可以试试其他方案。
继续阅读C++:模拟键盘

加密介绍


加密通讯总共有四大类,签名算法、对称加密算法、非对称加密算法与量子加密技术,下面分别对几大类算法进行介绍

1、签名算法

代表算法:md5、sha1、crc32
有的地方叫签名算法,有的地方叫哈希算法,有的地方叫指纹算法、数字摘要等等,都代表一个意思。在解释这个之前我先说说“哈希”的意思。哈希也叫散列,意思是,将一串任意长度数据经过某种特定的算法转为固定长度的输出。比如10000个字符将其转为20个字符,这样的算法就叫哈希算法。20个字符虽然容易重复,但随着字符的增多,重复概率将会指数级下降,但通过20个字符很容易就能确定10000个字符的范围,比如对N组10000个字符进行对比,这样做的好处是通过比对20个字符的签名就能筛选出绝大部分数据,剩下的数据经过简单的比对就行了,非常省时省力。
签名算法有什么优势呢?
(1)、校验数据准确性。数据的准确性在通信领域有着举足轻重的作用。纠错编码比如海明码等等可以使错误编码在一定范围内时对其进行纠正,但一旦超过阈值,就算使用编码纠错,最终得出的结果也是错误的。这时候就需要哈希算法对数据进行签名,以确保数据准确性。
(2)、对数据进行归纳整理。比如哈希表就基于哈希算法,将一堆数据均匀分布在哈希结构中,使得寻找某个数据非常简单方便。
(3)、做验证之用。比如用户登录界面,在客户端经过哈希算法计算之后,服务器端只需要验证结果是不是符合就可以了,不用传输整个密码。就算黑客在中途截获数据包也无法推算出密码(暂时不考虑彩虹表等玩意儿,哈希算法可以定义为信息不可逆)。
优势挺多,但劣势也比较严重:
(1)、数据不可逆。也就是说,生成的签名几乎不可能再推导回原数据。
(2)、相对来说不那么安全。彩虹表有一定概率可以在短时间破解MD5加密的密码,王小云研究出来的哈希碰撞也能在一定程度上削弱MD5的安全性。
继续阅读加密介绍

C#:HTTP客户端与服务器的实现

HTTP协议算是使用最广泛的Web协议了,主要面向基于B/S的实现。对于很多语言比如PHP或者javascript等Web语言,HTTP协议使用上甚至比TCP协议更简单。但这协议对普通的系统语言程序员就不太友好了。C#这种语言嘛,支持上还行,但并没达到特别优秀的效果,比如发起一个POST请求至少需要十行代码以上。这个库是我基于对C#语言封装的协议的了解,在此基础上新增的一个更加方便开发的库。源码在 https://github.com/fawdlstty/hanHttpLib 查看或下载。因为可能更新频繁,所以源码我就不在文章里面贴了。
目前这个库不算特别优秀,目前只支持基础的调用,结构也非常简单,但它能力可不差,用来给小伙伴学习也是极好的。我将不定期更新它,力争将其做到完美。
先说说HTTP的客户端。HTTP客户端主要是浏览器用来请求Web服务器上的资源用的,最广泛的就是浏览器了。每个浏览器都有一套HTTP客户端实现方式,请求完成资源后在界面上显示。但随着时代的发展,HTTP协议有着基本化的趋势。因为使用HTTPS进行通讯可以使用非常小的代价就能非常安全的加密通讯数据。另外HTTP客户端也不止是做浏览器了,除此之外最大的用处是做爬虫。比如很多零散的数据分布在零散的Web服务器上,这时就可以使用爬虫技术将零散的数据归纳起来。更加厉害的就是,再结合人工智能技术,爬取数据之后使用人工智能进行分析,差不多就能成就改变世界的武器了。
继续阅读C#:HTTP客户端与服务器的实现

XML Schema (一种XSL) 简单介绍

简单介绍下,XSL(eXtensible Stylesheet Language,扩展样式语言)是描述XML文档结构规则的语言,XML Schema是其中使用最广泛的一种。也就是说,你可以使用XML Schema限制某一类XML完全按照你的规范来编写代码。
比如,你想让某个XML文件以MainNode作为根节点名称,但可能有main_node作为根节点名称的,符合XML文件规范,但你就是识别不了它。这就比较尴尬了。那么,使用XSL验证机制,就可以保证XML文件100%符合你的规范。
也许做过HTML4的前端知道,DOCTYPE需要指向一个dtd文件,比如

1
< !DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

DTD也是XSL的一种,但因为对扩展的支持性不好,在HTML5中被弃用,之后DTD的使用将会越来越少。对于基于HTML4的网页代码都使用DTD作为规则文件对网页文档进行规范。HTML是从XML基础上演化而来的,当然也属于XML标准规范的一部分。使用标准XML也可以使用dtd进行描述。dtd此处不细说,此处说说XML Schema。
继续阅读XML Schema (一种XSL) 简单介绍

C#:扩展方法集合类

扩展方法集合类在C#中是一种特别的语法糖,可以在开发中极大简化代码的编写。主要功能是在系统类中创建自定义方法。比如字符串转数字,每次都得Convert.ToInt32 (str),从语法上来说就太不简洁了。下面我一步一步讲解扩展方法集合类的编写。首先创建一个静态类,类名必须为ExtensionMethods。示例代码如下

1
2
3
public static class ExtensionMethods {
    //...
}

所有扩展方法都需要放在此类中进行实现。首先创建一个静态函数,第一个参数使用this描述符,表示基于哪个类提供扩展方法

1
2
3
public static Int32 toInt32 (this object o) {
    return Convert.ToInt32 (o);
}

此函数代表在object这个类基础上新增一个扩展方法,无参数,返回值为Int32类型。示例调用代码如下:

1
int t = "123".toInt32();

下面我提供一个我自用的扩展方法集合类供参阅
继续阅读C#:扩展方法集合类

C#:使用OpenXML读写Excel文档

对于Office文件的读写操作始终是一个比较麻烦的事情,最近做了一个Office操作的小程序,写一个文章用于备忘。
Office文件读写方法挺多,最好的方法自然是OpenXML无疑,但这库用起来还是比较麻烦。我在此做一个Excel读写小例子,其他Office类型同理。
对于OpenXML这库,首先需要知道OpenXML是什么。新建一个Excel文件,另存为a.xlsx,然后将其重命名为a.rar,解压,然后看到一大堆xml文件。不用怀疑,这就是xml标准。对于Office文件的读的访问来说,如果对OpenXML不熟,并且使用其他语言不方便调用C#的库,那么,手工解压然后找找数据的位置,然后手工解析,是最快捷的方式。当然,在可以使用OpenXML的场合,我个人还是推荐使用OpenXML。
OpenXML2.5下载地址(主体工具摘自微软官网,扩展摘自网络):https://pan.baidu.com/s/1slDa0Jr,提取码98dd
这个工具只有英语版的,但由于操作过于简单,所以对于英语小白也完全无压力。
继续阅读C#:使用OpenXML读写Excel文档

GDI/GDI+用法总结

GDI是Graphics Device Interface的缩写,含义是图形设备接口,它的主要任务是负责系统与绘图程序之间的信息交换,处理所有Windows程序的图形输出。GDI+是在GDI基础上提供的一层更高级的图像绘制抽象接口,语义更明确调用更方便。它们都支持向图片对象或者窗口上输出图形。在窗口上绘图时它们都使用窗口提供的HDC句柄实现绘制;在图片对象绘制图像时,GDI+支持直接传入图片对象实现对图片的绘制,GDI需要先创建一个与图片兼容的HDC,再将HDC与被绘制图片进行绑定,然后才能在图片上进行绘制。
它们在用法上相似,区别主要有以下几个方面:

  • GDI不支持透明图片处理(AlphaBlend只能混合颜色,透明得由第三方库支持)
  • GDI不支持反锯齿(对于图片绘制线条、图像或拉伸等处理时,可能出现白色锯齿形状图像,影响美观)
  • GDI对于图片颜色处理具有很大优势。GDI+慢的一比
  • GDI是以C的接口形式提供接口,GDI+是以C艹和托管类的方式提供接口
  • 使用GDI+的程序在初始化后、程序关闭前需调用GDI+初始化、释放的代码
  • 从层次结构上来说,GDI+更好用

继续阅读GDI/GDI+用法总结

Transact-SQL基本用法整理

Sql Server在服务器开发中用的不是最多,但也属于主流数据库服务器之一。对于各种不同的数据库服务器,数据库查询语句有细微差别。这边文章用于做为T-SQL语句的整理,一方面列出各种用法作为参考,另一方面也为其他不同类型数据库使用提供另一种实现思路。

常用数据类型:

1
2
3
4
5
6
7
8
9
10
INT        --整数,不带宽度
FLOAT      --浮点数,不带宽度
DATE       --日期,不带宽度
datetime   --日期时间,不带宽度
CHAR       --字符,带宽度
NCHAR      --Unicode字符,带宽度
VARCHAR    --变长字符,带宽度
nvarchar   --变长Unicode字符,带宽度
DECIMAL    --定点数,带宽度
NUMERIC    --定点数,带宽度

除了常用类型外还有二进制流类型、Image类型等,因为用的非常少所以就不全部列举了。

5种约束:

1
2
3
4
5
PRIMARY KEY    --主键,指定列或多个列不允许重复,查询数据库默认以主键排序
UNIQUE         --唯一键,指定列或多个列不允许重复
CHECK          --检查键,插入的数据必须符合此条件
DEFAULT        --默认键。当插入数据时写入的值为default,那么值将被设置为默认键指定的值
FOREIGN KEY    --外键。指定键必须与其他表某列相关联。

继续阅读Transact-SQL基本用法整理

C++:时间

时间在计算机中,通常以三种形式存储,三种方式各有各的特点。
1、Unix时间戳
这个东西算是时间表示的元老级结构了,因为出现的早,所以用的也最广泛。原理是,一个32位的数字,代表从1970年1月1日到现在的秒数。由于最高位代表符号位未被使用,所以可以表示的最长的时间点为2038年左右。目前所有Unix及类Unix操作系统都使用这种方式表示时间。著名的水果1970变砖BUG就是因为这种时间表示方式的固有特性所导致。这种时间表示方式还有一个非常大的问题就是,不能计算闰秒。闰秒的含义是,因为地球绕太阳自转的速度越来越慢,导致地球公转一圈的时间更久。现代的人们感受不出来,但如果时间线放长,并且不闰秒,那么可以预见,未来某一天的正午12点是晚上,凌晨12点是白天。所以,在某个时间点的闰秒尤为重要。闰秒通常在某个分钟的变化时,秒数记为57、58、59、60、0、1、2……,也就是说,多出来一个第60秒。闰秒虽好,统一地球公转,为太阳系历法做贡献,但每次闰秒通常会造成几千万美元的经济损失,原因为,使用了Unix时间戳这种计时方法的操作系统,并不能区分闰秒,导致操作系统时间与世界时间不同,然后在金融等领域,一秒钟多计算了什么什么,少计算了什么什么,导致结果不是实际想要的。这时候计算误差所导致的经济损失累积起来,就有这么严重了。另外,这种计时方式最多只能计算到2038年,之后就无能为力了。如果这些操作系统迟迟不更新原子计时方式,那么,等待它们的,只有,系统罢工了。因为以上两个问题太严重了,导致人们常常忽略了它的第三个问题:精度太差。最高精度就是秒了,但对于计算机来说,计算很多东西精度常常需要达到毫秒才够用,有的甚至需要微秒级精度,所以这些地方也用不了这种时间表示方式。
基于这种计时方式,出现了一个分支,使用64位进行计时,这就不存在年份限制这BUG了,不过这分支用的比较少。
继续阅读C++:时间

C++:字符串编码与字符串

1、编码
在讲字符串之前首先说说编码方式。字符串在程序用用数据类型进行存储,同时数据类型存储的也可以是不同编码方式的字符串。总的来说,常用编码方式有以下几种:
ASCII:最古老的编码方式,只使用后7位,可以存储英语大小写、数字及几乎所有常用半角符号。
ISO-8859-1:西欧地区使用的编码方式,兼容ASCII码,在最高位为1时用于描述西文符号。
GB2312/GBK/GB18030:这个是天朝用户专用编码方式,兼容ASCII码,对于英文字符使用1字节进行存储,对于中文使用2字节进行存储,同时两个字节的最高位均为1。值得注意的是,GB2312在Win32开发中常常被称作Ansi编码;其次,GBK为GB2312的扩充,GB18030为GBK的扩充。以前它们是不同的编码方式,但现在也没有严格的划分,通常三者代表同一种编码方式。
BIG-5:也是天朝用户专用编码方式,兼容ASCII码,与GB2312不同的是,它只能编码繁体字,不能编码简体字。
UTF-16/UCS-2:这两个名称所代表的是同一种编码方式,使用两个字节来存储一个中文字符或者一个字母,不兼容ASCII码。这种编码方式也划分为两种不同的子编码方式,分别为UCS-2 Big Endian与UCS2 Little Endian。常说的UTF-16或者UCS-2通常指的是UCS-2 Big Endian。这两种子编码方式的区别为,Big Endian高字节在前,低字节在后;Little Endian低字节在前,高字节在后。这种编码方式在Win32开发中常常被称作Unicode编码,但它属于一种误称;另外,这种编码方式有点浪费存储空间,并且也不能描述世界上所有的符号,相比其他编码,唯一优势是,字符串长度就等于字符个数。
UTF-8:使用最广泛的编码方式,没有之一!几乎所有的网页、XML描述文件、Json数据文件、大多数数据库以及Linux系统均使用的编码方式,相比而言GB2312、UTF-16只有在Windows平台用用而已,仗着Windows平台用的人多,所以也作为常用的编码方式,对于英文字符使用1字节进行存储,因此兼容ASCII编码;它同时也能编码世界上所有的文字,对于汉字而言这种编码方式使用3个字节进行存储,但理论上可以使用2、3、4、5或6字节来编码一个特定字符。
UTF-32/UCS-4:由于UTF-16不能编码所有的编码方式,但发明这编码的人不服,爱搞事,所以发明了4个字节来编码一个字符的编码方式,理论上可以描述世界上所有的字符,但由于一个字母都需要4个字节,过于浪费存储空间,所以这种编码方式几乎没有人使用。
以上是需要了解的编码方式,除了上面几个之外,不同地方也有他们自己的编码方式,以下为不完全统计:
西欧语系:ISO-8859-1
东欧语系:ISO-8859-2
土耳其语:ISO-8859-3
波罗的语:ISO-8859-4
斯拉夫语:ISO-8859-5
阿拉伯文:ISO-8859-6
希腊文:ISO-8859-7
希伯来文:ISO-8859-8
日文:Shift-JIS
韩文:EUC-KR
……
继续阅读C++:字符串编码与字符串

C++11:&&右值引用、std::ref与std::unique_ptr

非常容易混淆的三种语法,在新手看来含义似乎相同,都是在函数调用上参数的传递,但实际上有非常大的区别。下面我们来具体分析分析。
首先是&&右值引用,在说这个之前先得弄清什么是右值。右值也叫将亡值(这翻译简直(¬_¬)),顾名思义也就是说将会死亡的值。比如类似这样的代码:

1
std::string s = std::string("abc");

从原理上来说,这句代码的含义是,首先在等号右侧构建一个临时字符串对象,然后将右侧对象执行拷贝构造,拷贝至变量s中,然后释放等号右侧对象(实际的实现中,编译器通常会对这样的代码进行优化,直接在变量s中构建字符串对象)。
如果上面的例子不太明显那么再来一个例子,Gdi+中经常会出现这样的代码:

1
2
Gdiplus::Graphics g(&img);
g.DrawImage(&img2, &Gdiplus::Rect(0, 0, 10, 10), ...);

似曾相识的代码对吧?这儿的DrawImage函数在执行前首先构造匿名Gdiplus::Rect对象,然后作为参数传递,函数调用返回时,也就是这行代码执行完时,匿名对象将被释放。这也是它被叫做将亡值的原因。
既然这种对象在使用时生命周期短,拷贝至左值又可能会影响效率,那有没更好的引用方式呢?当然有,这叫右值引用。
继续阅读C++11:&&右值引用、std::ref与std::unique_ptr

C++:调用协定

调用协定这个东西涉及到C语言函数实现的原理,大家都知道,调用函数时,保存当前执行环境,跳转到目标函数指令地址执行函数代码,执行完成后恢复调用前的执行环境。但实现的方式这么多,甚至可能不同的语言提供的接口有不同的方式,如何实现与目标函数相同的方式来调用呢?调用协定因此诞生。
可能有人觉得,这东西我从没听说过,但我写的C艹代码一样没问题,有必要研究吗?说实话这东西还是有必要的,主要在不同框架、语言间使用。比如BCB主要用__fastcall实现函数调用,C语言自身默认使用__cdecl实现函数调用,Windows平台又以__stdcall作为主要函数调用方式,而.NET Framework托管平台又以__clrcall实现函数调用,它们之间如何互相调用就是个问题。
Win32平台上可用函数调用协定有以下几种,后面的为别名:
__cdecl : _cdecl 、 cdecl 、 CDECL 、 WINAPIV
__pascal : pascal 、 PASCAL
__stdcall : WINAPI 、 APIENTRY 、 CALLBACK 、 APIPRIVATE
__fastcall
__clrcall
可能因为操作系统版本不同,导致函数定义有些许差异,在Win8.1上,__pascal已经被定义成了__stdcall,但并不妨碍我们对调用协定的学习。上面的关键字看不懂?没关系,我们一一来验证它们的作用。

1、__cdecl调用协定

首先__cdecl,C语言默认调用协定。首先我们先定义一个这样的函数,然后对其进行调用。

1
2
3
4
5
int __cdecl func_cdecl (int a, int b, int c) {
    return a + b + c;
}
//...
func_cdecl (1, 2, 3);

调用协定的定义方式为,将名称写在返回类型与函数名之间。写完之后,通过对齐进行Disassembly,显示如下结果:
20160910230845-cdecl
20160910230845-cdecl-call
继续阅读C++:调用协定

C++11:tuple元组类型

这种类型可以简单理解为:不需要typedef的自定义结构体类型。有时候需要传递、返回一些结构体类型,但其他地方根本用不上这种结构体,那么就不必用typedef了,可以避免公开一些内部结构体名称,更加符合软件工程思想,另外还能少些一大堆代码。
同时,STL中的结构体map中,需要用到的类型std::pair,就可以将其理解为std::tuple
废话不多说,上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <tuple>
using namespace std;
 
int main (int argc, char* argv []) {
    //打包一个tuple
    tuple<int, int, char> c = make_tuple (1, 5, 'c');
    //结构体大小
    cout << sizeof (c) << endl;//结构体大小,结果为12
    //获取tuple中的某个元素
    cout << get<1> (c) << endl;//获取第1个元素(从0开始),值为5
 
    //tie解包,ignore为忽略
    int a, b;
    tie (a, b, ignore) = c;//此时a、b的值分别为1、5
    cout << a << '\t' << b << endl;
 
    return 0;
}

同时,这种随意的数据类型也有自定义结构体的特性,比如传参或返回时,直接传递即为拷贝,通过指针或右值可以减小拷贝开销;另外,tuple也能嵌套使用,使用方法大同小异。

C++:偏移类型

地址偏移类型在C艹中算是偏冷门的技术,但在特定场合下可以节省大量的代码从而实现需求。这种类型的定义为:类或结构体中的数据成员相对于基址的偏移量。比如,一个类里面成员a地址相对于类基地址偏移量为4;成员b地址相对于类基地址偏移量为8等等。
示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
using namespace std;
 
class A {
public:
    int a, b, c;
    A () : a (3), b (5), c (7) {}
};
 
int main (int argc, char* argv[]) {
    int (A::* x) = &A::a;
    int A::* y = &A::b;
    using TZ = int (A::*);
    TZ z = &A::c;    
 
    A *p = (A*)nullptr;
 
    cout << "地址偏移与偏移量值:" << endl;
    cout << &(p->a) << '\t' << &(p->*x) << endl;
    cout << &(p->b) << '\t' << &(p->*y) << endl;
    cout << &(p->c) << '\t' << &(p->*z) << endl;
 
    cout << "直接访问的结果:" << endl;
    cout << x << '\t' << y << '\t' << z << endl;
 
    return 0;
}

代码含义为:首先创建一个类,类里面有三个int类型成员属性,然后在main函数里面写了三种生成地址偏移类型的方法,其中第三种为首先构造地址偏移类型,然后用类型来定义。从经验上看,很容易看出偏移类型为0、4、8,实际运行结果也确实如此。
本机运行结果如下:
20160910151508
直接访问的结果有点出人意料,按道理说也应该是0、4、8才对,难道只代表一个符号么?
于是,在Disassembly上调试,为便于查看,我将代码改为cout << x;,以上代码反汇编结果为:
20160910152858
通过断点调试,x、y、z的值确实是0、4、8,另外反汇编代码可以明显看出,直接打印的含义为,如果值为0xFFFFFFFF,那么显示0,否则为1。因为通常不需要直接打印偏移长度,所以就把这个功能给省略了。真正需要用到的是,这个偏移是不是有效的。定义为,假如偏移长度为0xFFFFFFFF,那么代表这个偏移是无效的。于是我简单加了个hack:

1
2
__asm { mov x, 0ffffffffh }
cout << x;

代码不出所料,结果为0。这儿的0和1分别代表此处偏移类型是否有效。

C++:如何制作高逼格二维码

在网上看到一个制作高逼格二维码的文章,来源http://www.chenxublog.com/2016/05/22/pic-qrcode-colorful.html
步骤那可是相当的复杂,一堆软件各种ps啊什么的,不过根据实现的效果一看,原理貌似很简单嘛,何必弄那么复杂,于是我用代码实现了一个,本文着重介绍原理。编译后的程序在最下面,不过建议使用前先看过去,否则可能你不会用2333
先来几张图你们感受下,支付宝:
zfb

微信:
wx
继续阅读C++:如何制作高逼格二维码

C++:socket服务端模型

首先解释下socket含义。网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个socket。 Socket的英文原义是“孔”或“插座”。作为BSD UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
下面我列举几个常用的服务端TCP socket实现代码

1、首先是最原始的阻塞模型。这种模型简单易懂,[以下代码基于Windows]:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <winsock2 .h>
#pragma comment(lib, "ws2_32.lib")
 
int mian (int argc, char* argv []) {
    //Winsock环境初始化
    WSAData wd;
    WSAStartup (MAKEWORD (2, 2), &wd);
 
    //创建套接字
    SOCKET sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP);//对于UDP协议,第二个参数填 SOCK_DGRAM
 
    //绑定前的操作,UDP可以不用绑定
    sockaddr_in addr;
    addr.sin_family = AF_INET;//需要绑定的本地地址
    addr.sin_addr.s_addr = 0;
    addr.sin_port = htons (51423);//需要绑定的本地端口
 
    //执行绑定
    bind (sock, (struct sockaddr*)&addr, sizeof (addr));
 
    //监听
    listen (sock, SOMAXCONN);
 
    while (true) {
        //如果有链接请求,则接受链接
        SOCKET session = accept (sock, NULL, NULL);
        if (INVALID_SOCKET == session) break;
 
        //接收数据
        char buf [1024];
        int len = recv (session, buf, sizeof (buf), 0);
 
        //发送数据
        send (session, buf, len, 0);
 
        //关闭链接
        closesocket (session);
    }
 
    //关闭套接字
    closesocket (sock);
 
    //Winsock环境释放
    WSACleanup ();
    return 0;
}</winsock2>

继续阅读C++:socket服务端模型

C++:文件操作

文件操作是个啥大家都懂了,下面我给出几个文件操作示例
1、C语言文件操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
//在windows平台下避免函数不安全报错而定义的一个宏
#define _CRT_SECURE_NO_WARNINGS
#include <stdio .h>
#include <stdlib .h>
#include <string .h>
 
int main () {
    //fopen第二个参数有以下几种取值模式
    //首先是打开方式,从以下打开方式中任选一种或组合
    //    a(append)    以写方式打开文件并移动文件指针到文件尾部
    //    r(read)      以读方式打开文件,文件指针位置未知建议打开后重置文件指针
    //    w(write)     以写方式打开文件并清空文件内容
    //然后是数据类型,默认为文本方式,如果加上b则为二进制方式,区别在于,在windows环境下,不加b(文本模式)会造成:
    //    以文本方式读文件时,换行符\r\n被读成\n
    //    以文本方式写文件时,换行符\n被写成\r\n
    //最后是扩展方式,如果带+号代表如果文件不存在则新建。但我实际测试结果为,带不带+号并没什么卵用
    //示例:第二个参数为 rwb+ 代表以二进制读写方式打开文件,如果文件不存在则新建
    FILE *f = fopen ("a.txt", "rw+");
    if (f) {
        char *data = "hello";
        //写文件,不多做解释
        fwrite (data, strlen(data), 1, f);
 
        //移动文件指针到结束位置,第三个参数三种取值分别为 SEEK_SET(开始位置)、SEEK_CUR(当前位置)、SEEK_END(结束位置)
        fseek (f, 0, SEEK_END);
 
        //获取文件指针的偏移,这时候指针在末尾,含义就代表文件长度。
        //值得注意的是如果以文本方式并且有回车并且读取文件时,文件长度与读取的字节数不一样,原因上面有说明
        int len = ftell (f);
 
        //这行代码含义为移动文件指针到文件起始位置,等价于 fseek (f, 0, SEEK_SET);
        rewind (f);
 
        //其他文件操作函数不做说明,这儿给出常用列表
        //fwrite     输出一块数据到文件
        //fprintf    格式化输出到文件
        //fputc      输出一个字节到文件
        //fputs      输出一串字符串到文件
        //fread      读取一块数据到文件
        //fscanf     从文件格式化输入
        //fgetc      从文件中读取一个字符,因为文件可能结束,所以返回类型为int。如果值为 EOF 代表文件已读到最后
        //fgets      从文件中读取一串字符串
        //feof       判断文件指针是否在文件末尾
 
        //如果为多个程序同时访问文件的情况时建议加上这句。含义代表将文件缓冲区内容刷新到文件中
        //不加可能出现文件更新不及时的现象,不管是否调用这句,关闭文件时始终会刷新的
        fflush (f);
 
        //关闭文件
        fclose (f);
    } else {
        printf ("打开文件失败!");
    }
 
    system ("pause");
    return 0;
}</string></stdlib></stdio>

继续阅读C++:文件操作

Windows服务访问控制

服务控制在win32用户层系统编程中比较重要,一般用于创建自启动项或加载驱动,这样的话启动服务就是载入驱动,停止服务就是卸载驱动,极大方便了驱动控制。下面贴一个服务控制代码,可用于方便的控制服务。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#pragma once
#ifndef __HSERVER_HPP__
#define __HSERVER_HPP__
 
#include <Windows.h>
 
class hService {
    SC_HANDLE h_scm = NULL, h_service = NULL;
    QUERY_SERVICE_CONFIG *h_qsc = NULL;
    SERVICE_STATUS h_status;
 
public:
    hService (LPCTSTR serv_name) {
        h_qsc = (LPQUERY_SERVICE_CONFIG)new BYTE [8 * 1024];
        if ((h_scm = ::OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS)) && serv_name && serv_name[0]) {
            h_service = ::OpenService (h_scm, serv_name, SERVICE_ALL_ACCESS);
        }
    }
    ~hService () {
        delete h_qsc;
        if (h_service) ::CloseServiceHandle (h_service);
        if (h_scm) ::CloseServiceHandle (h_scm);
    }
 
    //服务是否正在运行
    BOOL is_running () {
        if (!h_service) return FALSE;
        if (::QueryServiceStatus (h_service, &h_status)) {
            if (h_status.dwCurrentState == SERVICE_RUNNING) return TRUE;
        }
        return FALSE;
    }
 
    //服务是否已停止
    BOOL is_stopped () {
        if (!h_service) return FALSE;
        if (::QueryServiceStatus (h_service, &h_status)) {
            if (h_status.dwCurrentState == SERVICE_STOPPED) return TRUE;
        }
        return FALSE;
    }
 
    //服务是否已暂停
    BOOL is_pause () {
        if (!h_service) return FALSE;
        if (::QueryServiceStatus (h_service, &h_status)) {
            if (h_status.dwCurrentState == SERVICE_PAUSED) return TRUE;
        }
        return FALSE;
    }
 
    //服务是否自动启动
    BOOL is_auto_run () {
        DWORD d;
        if (!h_service) return FALSE;
        if (::QueryServiceConfig (h_service, h_qsc, 8 * 1024, &d)) {
            return h_qsc->dwStartType <= 2;
        }
        return FALSE;
    }
 
    //启动服务
    BOOL start () {
        if (!h_service) return FALSE;
        if (is_running ()) return TRUE;
        return ::StartService (h_service, NULL, NULL);
    }
 
    //停止服务
    BOOL stop () {
        if (!h_service) return FALSE;
        if (is_stopped ()) return TRUE;
        return ::ControlService (h_service, SERVICE_CONTROL_STOP, &h_status);
    }
 
    //暂停服务
    BOOL pause () {
        if (!h_service) return FALSE;
        if (is_running ()) return ::ControlService (h_service, SERVICE_CONTROL_PAUSE, &h_status);
        return TRUE;
    }
 
    //恢复服务
    BOOL resume () {
        if (!h_service) return FALSE;
        if (is_pause ()) return ::ControlService (h_service, SERVICE_CONTROL_CONTINUE, &h_status);
        return is_running ();
    }
 
    //设置自动启动服务
    BOOL auto_start () {
        if (!h_service) return FALSE;
        if (is_auto_run ()) return TRUE;
        SC_LOCK sclLock = ::LockServiceDatabase (h_scm);
        BOOL bRet = ::ChangeServiceConfig (h_service, SERVICE_NO_CHANGE, SERVICE_AUTO_START, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        if (sclLock) ::UnlockServiceDatabase (sclLock);
        return bRet;
    }
 
    //设置手动启动服务
    BOOL demand_start () {
        if (!h_service) return FALSE;
        if (!is_auto_run ()) return TRUE;
        SC_LOCK sclLock = ::LockServiceDatabase (h_scm);
        BOOL bRet = ::ChangeServiceConfig (h_service, SERVICE_NO_CHANGE, SERVICE_DEMAND_START, SERVICE_NO_CHANGE, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
        if (sclLock) ::UnlockServiceDatabase (sclLock);
        return bRet;
    }
 
    //创建服务
    static hService *create_service (LPCTSTR serv_name, LPCTSTR display_name, DWORD service_type, LPCTSTR path) {
        hService *service = new hService (NULL);
        service->h_service = ::CreateService (service->h_scm, serv_name, display_name, SC_MANAGER_ALL_ACCESS, service_type, SERVICE_DEMAND_START, SERVICE_ERROR_IGNORE, path, NULL, NULL, NULL, NULL, NULL);
        return service;
    }
 
    //删除服务
    BOOL delete_service () {
        if (::DeleteService (h_service)) {
            h_service = NULL;
            return TRUE;
        }
        return FALSE;
    }
};
#endif //__HSERVER_HPP__

创建头文件,引入以上代码之后,就可以方便进行服务控制了。调用方式在代码中已有注释。