几道不太简单的C艹面试题

下面几道问题是我整理的来源于网络的比较坑的问题,看起来题目很简单,但理清整个逻辑是非常不容易的,除非对底层知识有非常深刻的理解。你觉得你掌握了C++的基础么?这几道简单的问题,来试试吧?

1、下面哪些表达式必须加锁
A、a = 4; B、a = b; C、a++; D、a = b + 4;

2、下面代码double计算时间总比float小,请解释原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
float fs[] = { 1.1f, 2.2f, 3.3f, 4.4f, 5.5f };
double ds[] = { 1.1, 2.2, 3.3, 4.4, 5.5 };
DWORD tk = ::GetTickCount ();
float f = 0.0f;
for (int i = 0; i < 100000000; ++i) {
    f += fs[i % 5];
}
tk = ::GetTickCount () - tk;
std::cout << "float:" << f << "    " << tk << '\n';
tk = ::GetTickCount ();
double d = 0.0;
for (int i = 0; i < 100000000; ++i) {
    d += ds[i % 5];
}
tk = ::GetTickCount () - tk;
std::cout << "double:" << d << "    " << tk << '\n';

3、下面代码,257的二维数组计算时间总比256的二维数组计算时间短,请解释原因

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char a[256][256], b[257][257]; // 全局变量
DWORD tk = ::GetTickCount ();
for (int i = 0; i < 256; ++i)
    for (int j = 0; j < 256; ++j)
        for (int k = 0; k < 10000; ++k)
            a[i][j] += '\x01';
tk = ::GetTickCount () - tk;
std::cout << "256:" << tk << '\n';
tk = ::GetTickCount ();
for (int i = 0; i < 257; ++i)
    for (int j = 0; j < 257; ++j)
        for (int k = 0; k < 10000; ++k)
            b[i][j] += '\x01';
tk = ::GetTickCount () - tk;
std::cout << "257:" << tk << '\n';

4、std::string中的COW优化与SSO优化分别对应怎样的输出?

1
2
3
4
5
6
7
std::string a = "hello";
std::string b = a;
std::cout < < (a.c_str () == b.c_str () ? "true" : "false") << '\n';
char ch = b[0];
std::cout << (a.c_str () == b.c_str () ? "true" : "false") << '\n';
a = "";
std::cout << (a.c_str () == b.c_str () ? "true" : "false") << '\n';

继续阅读几道不太简单的C艹面试题

自制编程语言(一):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的使用

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++:模拟键盘

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) 简单介绍

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+用法总结

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__

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

Windows下编码转换

对于网络程序来说经常需要用到编码转换,比如访问utf8编码网页下载之后,转为unicode进行显示。windows自带的编码API比较难用,所以对其进行简单的封装。分SDK和MFC两个版本代码,SDK版本如下

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
#pragma once
#ifndef __HCODEC_HPP__
#define __HCODEC_HPP__
 
#include <windows .h>
#include <string>
 
class hCodec {
    //不可实例化
    hCodec () = delete;
    ~hCodec () = delete;
 
    static bool hCodec::_conv_Down (std::wstring& _old, std::string& _new, UINT ToType) {
        int lenOld = lstrlenW (_old.c_str ());
        int lenNew = ::WideCharToMultiByte (ToType, 0, _old.c_str (), lenOld, NULL, 0, NULL, NULL);
        std::string s;
        s.resize (lenNew);
        bool bRet = ::WideCharToMultiByte (ToType, 0, _old.c_str (), lenOld, const_cast<char *>(s.c_str ()), lenNew, NULL, NULL);
        _new.clear ();
        _new = s.c_str ();
        return bRet;
    }
    static bool hCodec::_conv_Up (std::string& _old, std::wstring& _new, UINT ToType) {
        int lenOld = lstrlenA (_old.c_str ());
        int lenNew = ::MultiByteToWideChar (ToType, 0, _old.c_str (), lenOld, NULL, 0);
        std::wstring s;
        s.resize (lenNew);
        bool bRet = ::MultiByteToWideChar (ToType, 0, _old.c_str (), lenOld, const_cast<wchar_t *>(s.c_str ()), lenNew);
        _new.clear ();
        _new = s.c_str ();
        return bRet;
    }
 
public:
    static bool hCodec::AnsiToUnicode (std::string& _old, std::wstring& _new) {
        return hCodec::_conv_Up (_old, _new, CP_ACP);
    }
    static bool hCodec::UnicodeToAnsi (std::wstring& _old, std::string& _new) {
        return hCodec::_conv_Down (_old, _new, CP_ACP);
    }
    static bool hCodec::Utf8ToUnicode (std::string& _old, std::wstring& _new) {
        return hCodec::_conv_Up (_old, _new, CP_UTF8);
    }
    static bool hCodec::UnicodeToUtf8 (std::wstring& _old, std::string& _new) {
        return hCodec::_conv_Down (_old, _new, CP_UTF8);
    }
    static bool hCodec::AnsiToUtf8 (std::string& _old, std::string& _new) {
        std::wstring t;
        if (!hCodec::AnsiToUnicode (_old, t)) return false;
        return hCodec::UnicodeToUtf8 (t, _new);
    }
    static bool hCodec::Utf8ToAnsi (std::string& _old, std::string& _new) {
        std::wstring t;
        if (!hCodec::Utf8ToUnicode (_old, t)) return false;
        return hCodec::UnicodeToAnsi (t, _new);
    }
};
 
#endif //__HCODEC_HPP__</wchar_t></char></string></windows>

继续阅读Windows下编码转换

Windows注册表访问的封装

注册表这东西用的越来越少了,但在特定场合还是不可替代的东西。比如开机启动项、环境变量和文件扩展名等各种系统信息都是放在注册表中。
由于注册表API访问稍微麻烦了点,于是我对其进行简单的封装。代码如下:

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
#pragma once
#ifndef __HREG_HPP__
#define __HREG_HPP__
 
#include <Windows.h>
 
// main_key 注册表根键可取值
// HKEY_CLASSES_ROOT
// HKEY_CURRENT_CONFIG
// HKEY_CURRENT_USER
// HKEY_LOCAL_MACHINE
// HKEY_USERS
 
// type 键值类型可取值
// REG_BINARY               二进制数据
// REG_DWORD                双字
// REG_DWORD_LITTLE_ENDIAN  双字小头模式
// REG_DWORD_BIG_ENDIAN     双字大头模式
// REG_EXPAND_SZ            包含未展开的环境变量的引用的字串
// REG_LINK                 一个用 RegCreateKeyEx 传 REG_OPTION_CREATE_LINK 创建的符号链接的字串
// REG_MULTI_SZ             多个字符串,用\0分隔,末尾为\0\0
// REG_NONE                 没定义值类型
// REG_QWORD                四字
// REG_QWORD_LITTLE_ENDIAN  四字小头模式
// REG_SZ                   普通字符串
 
 
class hReg {
    //不可实例化
    hReg () = delete;
    ~hReg () = delete;
    hReg (hReg&) = delete;
 
public:
    //创建注册表路径
    //main_key:   注册表根键
    //sub_key:    注册表路径
    //type:       键值类型
    //value:      值
    //value_size: 值长度(字节)
    static BOOL set_path (HKEY main_key, LPCTSTR sub_key, DWORD type, LPBYTE value, DWORD value_size) {
        return ERROR_SUCCESS == ::RegSetValueEx (main_key, sub_key, 0, type, value, value_size);
    }
 
    //创建注册表键值
    //main_key:   注册表根键
    //sub_key:    注册表路径
    //sub_key2:   键值名称
    //type:       键值类型
    //value:      值
    //value_size: 值长度(字节)
    static BOOL set_key (HKEY main_key, LPCTSTR sub_key, LPCTSTR sub_key2, DWORD type, LPBYTE value, DWORD value_size) {
        HKEY hKey;
        if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, sub_key, 0, KEY_WRITE, &hKey)) return FALSE;
        BOOL bRet = ERROR_SUCCESS == ::RegSetValueEx (hKey, sub_key2, 0, type, value, value_size);
        ::RegCloseKey (hKey);
        return bRet;
    }
 
    //获取注册表路径键值
    //main_key:   注册表根键
    //sub_key:    注册表路径
    //value:      值
    //value_size: 值长度(字节)
    static BOOL get_path_value (HKEY main_key, LPCTSTR sub_key, LPBYTE value, DWORD &value_size) {
        return ERROR_SUCCESS == ::RegQueryValueEx (main_key, sub_key, NULL, NULL, value, &value_size);
    }
 
    //获取注册表路径键值
    //main_key:   注册表根键
    //sub_key:    注册表路径
    //sub_key2:   键值名称
    //value:      值
    //value_size: 值长度(字节)
    static BOOL get_key_value (HKEY main_key, LPCTSTR sub_key, LPCTSTR sub_key2, LPBYTE value, DWORD &value_size) {
        HKEY hKey;
        if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, sub_key, 0, KEY_READ, &hKey)) return FALSE;
        BOOL bRet = ERROR_SUCCESS == ::RegQueryValueEx (hKey, sub_key2, NULL, NULL, value, &value_size);
        ::RegCloseKey (hKey);
        return bRet;
    }
 
    //删除注册表路径
    //main_key:   注册表根键
    //sub_key:    注册表路径
    static BOOL delete_path (HKEY main_key, LPCTSTR sub_key) {
        return ERROR_SUCCESS == ::RegDeleteKey (main_key, sub_key);
    }
 
    //删除注册表键值
    //main_key:   注册表根键
    //sub_key:    注册表路径
    //sub_key2:   注册表路径
    static BOOL delete_key (HKEY main_key, LPCTSTR sub_key, LPCTSTR sub_key2) {
        HKEY hKey;
        if (ERROR_SUCCESS != ::RegOpenKeyEx (main_key, sub_key, 0, KEY_ALL_ACCESS, &hKey)) return FALSE;
        BOOL bRet = ERROR_SUCCESS == ::RegDeleteKey (hKey, sub_key2);
        ::RegCloseKey (hKey);
        return bRet;
    }
};
 
#endif //__HREG_HPP__

全部为静态函数,注上了比较完整的注释,另外函数名也较之前清晰,统一返回BOOL表示执行成功或失败。

C++机器学习(4)Logistic回归

部分资料下载地址: http://pan.baidu.com/s/1bpsgt5t 提取码fwxf
源码下载地址:https://github.com/fawdlstty/hm_ML

继续开始机器学习的研究。这次的内容叫Logistic回归。什么叫Logistic回归?直观理解就是,通过不断的迭代,使结果接近最优解。
如下图示例:
20160517120948
在由θ0、θ1以及J(θ0,θ1)组成的三维立体空间中,从最开始的山顶上,每走一步计算一下怎样走下山最快。通过不同的起点或不同的步长,甚至可以获得不同的结果,比如红色箭头所标注的位置。
继续阅读C++机器学习(4)Logistic回归

C++:STL迭代

STL中的迭代是个重点,里面提供了大量的方法来方便我们程序猿开发,不过由于提供的太多导致学习比较吃力。这儿记录部分学习过程。
首先是C++11的for循环,比如对于一个vector v,可以通过for(int i : v)来实现迭代。如果对于自定义结构呢?也可以实现,如下代码示例调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <vector>
#include <string>
using namespace std;
 
class Int_col {
public:
    Int_col () { for (int i = 0; i < 10; i++) data [i] = i; }
    int data [10];
    int* begin () { return &data [0]; }
    int* end () { return &data [10]; }
};
 
int main(int argc, char* argv[]) {
    Int_col v;
    for (int i : v) {
        cout << i << " ";
    }
 
    return 0;
}

这儿的结构中只有10个数据,在结构中提供begin与end方法的迭代器,即可实现C++11的for循环调用。结果如下所示:
20160502225605
继续阅读C++:STL迭代