在C++中,system函数可以运行命令行,但是只能得到该命令行的int型返回值,并不能获得显示结果。例如system(“ls”)只能得到0或非0,如果要获得ls的执行结果,则要通过管道来完成的。首先用_popen(Linux下函数名为popen)打开一个命令行的管道,然后通过fgets获得该管道传输的内容,也就是命令行运行的结果。

VS2013下代码如下:

#include "stdafx.h"
#include<windows.h>
#include<string>
#include<iostream>
using namespace std;

std::string getCMD(char * cmd){
	FILE *file;
	char ptr[1024] = { 0 };
	char tmp[1024] = { 0 };
	strcat_s(ptr, cmd);
	string result = "";
	if ((file = _popen(ptr, "r")) != NULL)
	{
		while (fgets(tmp, 1024, file) != NULL){
			result = result + tmp;
		}
		_pclose(file);
	}
	return result;
}

int _tmain(int argc, _TCHAR* argv[])
{
	cout << getCMD("ping www.maplefan.com");
	system("pause");
}

 

上列代码在执行诸如ping命令这种需要等待的命令行的时候,会知道ping指令完全结束才一次输出信息。

如果想要像ping命令一样即时输出的话,可以在while循环里想办法输出即可。

CLabelUI等控件可以实现高亮,实现方法如下:

(1)将该控件的SetShowHtml()设置为true,即使用html的方式进行解析。

(2)在字符中加入下列想要的格式即可。让它自己去解析,完成绘制。

 

//   Bold:             <b>text</b>
//   Color:            <c #xxxxxx>text</c>  where x = RGB in hex
//   Font:             <f x>text</f>        where x = font id
//   Italic:           <i>text</i>
//   Image:            <i x y z>            where x = image name and y = imagelist num and z(optional) = imagelist id
//   Link:             <a x>text</a>        where x(optional) = link content, normal like app:notepad or http:www.xxx.com
//   NewLine           <n>
//   Paragraph:        <p x>text</p>        where x = extra pixels indent in p
//   Raw Text:         <r>text</r>
//   Selected:         <s>text</s>
//   Underline:        <u>text</u>
//   X Indent:         <x i>                where i = hor indent in pixels
//   Y Indent:         <y i>                where i = ver indent in pixels

按照上述样式,可进行绘制操作。

比如构造一个字符串设置颜色:

<c #ff00ff>test</c>

构造成类似这种字符串,控件会自动以HTML格式解析绘制。

最近Win32编程被编码折磨得很惨。。。记录一下两个转化函数。

wchar_t*转std::string:

std::string wchar_tToString(wchar_t *wchar){
	std::string szDst;
	wchar_t* wText = wchar;
	DWORD dwNum = WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, NULL, 0, NULL, FALSE);
	char *psText;
	psText = new char[dwNum];
	WideCharToMultiByte(CP_OEMCP, NULL, wText, -1, psText, dwNum, NULL, FALSE);
	szDst = psText;
	delete[]psText;
	return szDst;
}

std::string转wchar_t*:

wchar_t* stringToWchar_t( std::string str){
	std::string temp = str;
	int len = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)temp.c_str(), -1, NULL, 0);
	wchar_t *wszUtf8 = new wchar_t[len + 1];
	memset(wszUtf8, 0, len * 2 + 2);
	MultiByteToWideChar(CP_ACP , 0 , (LPCSTR)temp.c_str() , -1 , (LPWSTR)wszUtf8 , len);
	return wszUtf8;
}

 

最近的文件搜索项目使用到了SQLite3作为数据库来缓存本地的文件,而通过USN读取到NTFS盘上的文件(开发机器上大概40w个文件/文件夹)在Intel Core i7-6700上大概就需要7s左右的时间,而通过常规的SQLite提供的C++ API 将文件的这些信息插入到数据库的表中,大概需要30s的时间,于是开始摸索是否有办法可以提高插入的效率进而减少我们程序的后台文件搜索耗时线程所花费的时间。

慢速:直接使用SQLite的C++ API

int sqlite3_exec(  sqlite3*,    const char *sql,   int (*callback)(void*,int,char**,char**),   void *,   char **errmsg)

中速:显式开启事务

所谓”事务“就是指一组SQL命令,这些命令要么一起执行,要么都不被执行。在SQLite中,每调用一次sqlite3_exec()函数,就会隐式地开启了一个事务,如果插入一条数据,就调用该函数一次,事务就会被反复地开启、关闭,会增大IO量。如果在插入数据前显式开启事务,插入后再一起提交,则会大大提高IO效率,进而加数据快插入速度。

开启事务只需在上述代码的前后各加一句开启与提交事务的命令即可:

开启事务:

sqlite3_exec(db,"begin;",0,0,0);

提交事务:

sqlite3_exec(db,"commit;",0,0,0);

高速:关闭写同步(synchronous)

之前的速度仍然不能够接受,在有关讲解SQLite配置的资料中,看到了“写同步”选项。

在SQLite中,数据库配置的参数都由编译指示(pragma)来实现的,而其中synchronous选项有三种可选状态,分别是full、normal、off。这篇博客以及官方文档里面有详细讲到这三种参数的设置。简要说来,full写入速度最慢,但保证数据是安全的,不受断电、系统崩溃等影响,而off可以加速数据库的一些操作,但如果系统崩溃或断电,则数据库可能会损毁。

SQLite3中,该选项的默认值就是full,如果我们再插入数据前将其改为off,则会提高效率。如果仅仅将SQLite当做一种临时数据库的话,完全没必要设置为full。在代码中,设置方法就是在打开数据库之后,直接插入以下语句:

sqlite3_exec(db,"PRAGMA synchronous = OFF; ",0,0,0);

极速:

虽然写同步设为off后,速度又有小幅提升,但是仍然较慢。我又一次踏上了寻找提高SQLite插入效率方法的道路上。终于,我发现,SQLite执行SQL语句的时候,有两种方式:一种是使用前文提到的函数sqlite3_exec(),该函数直接调用包含SQL语句的字符串;另一种方法就是“执行准备”(类似于存储过程)操作,即先将SQL语句编译好,然后再一步一步(或一行一行)地执行。如果采用前者的话,就算开起了事务,SQLite仍然要对循环中每一句SQL语句进行“词法分析”和“语法分析”,这对于同时插入大量数据的操作来说,简直就是浪费时间。因此,要进一步提高插入效率的话,就应该使用后者。

“执行准备”主要分为三大步骤:

  1. 调用函数sqlite3_prepare_v2(),并且声明一个指向sqlite3_stmt对象的指针,该函数对参数化的SQL语句zSql进行编译,将编译后的状态存入ppStmt中。
  2. 调用函数 sqlite3_step(),这个函数就是执行一步(本例中就是插入一行),如果函数返回的是SQLite_ROW则说明仍在继续执行,否则则说明已经执行完所有操作。
  3. 调用函数 sqlite3_finalize(),关闭语句。
  4. 样例代码如下:
    int _tmain(int argc, _TCHAR* argv[])
    {
    	const int nCount = 500000;
    	sqlite3* db;
    	sqlite3_open("testdb.db", &db);
    	sqlite3_exec(db, "PRAGMA synchronous = OFF; ", 0, 0, 0);
    	sqlite3_exec(db, "drop table if exists t1", 0, 0, 0);
    	sqlite3_exec(db, "create table t1(id integer,x integer,y integer ,weight real)", 0, 0, 0);
    	clock_t t1 = clock();
    
    	sqlite3_exec(db, "begin;", 0, 0, 0);
    	sqlite3_stmt *stmt;
    	const char* sql = "insert into t1 values(?,?,?,?)";
    	sqlite3_prepare_v2(db, sql, strlen(sql), &stmt, 0);
    
    	for (int i = 0; i<nCount; ++i)
    	{
    		sqlite3_reset(stmt);
    		sqlite3_bind_int(stmt, 1, i);//bind第一个数据
    		sqlite3_bind_int(stmt, 2, i * 2);//bind第二个数据
    		sqlite3_bind_int(stmt, 3, i / 2);//bind第三个数据
    		sqlite3_bind_double(stmt, 4, i*i);//bind第四个数据
    		sqlite3_step(stmt);
    	}
    	sqlite3_finalize(stmt);
    	sqlite3_exec(db, "commit;", 0, 0, 0);
    	clock_t t2 = clock();
    	sqlite3_close(db);
    	std::cout << "cost time: " << (t2 - t1) / 1000. << "s" << std::endl;
    
    	system("pause");
    	return 0;
    }

     

综上所述啊,SQLite插入数据效率最快的方式就是:事务+关闭写同步+执行准备(存储过程),如果对数据库安全性有要求的话,就开启写同步。

在Win32中调试程序时经常需要输出调试信息以追踪数据流及程序运行状态。

当然,我们可以通过输出日志、文件等方式得到信息,但是控制台或许更方便、直观。

之前在使用基于Win32的ACLlib进行开发的时候,一直都可以显示一个控制台(也可以隐藏),感觉很方便。

那么,如何方便地在带GUI的Win32程序中,比如基于Duilib的Win32GUI程序中使用控制台进行调试输出?

具体操作流程如下:

  1. 打开控制台
  2. 重定向输出流至控制台
  3. 执行调试信息输出操作.

完整代码如下:

AllocConsole();
freopen("CONOUT$", "w", stdout);
std::cout << "This is a test info" << std::endl;

 

相关函数说明:

AllocConsole函数的功能是为当前的窗口程序申请一个Console窗口,其原型为

BOOL AllocConsole(void);

 

函数调用成功,返回非零值,调用不成功则返回0。

freopen函数用来替换一个流,或者说重新分配文件指针,以实现重定向。可重定向的流有:标准输入流、标准输出流或者标准错误流。其函数原型为

FILE *freopen(const char *path, const char *mode, FILE *stream);

其中”CONOUT$”是指代当前console的特殊字符串,”w”表明以written模式打开这个console,stdout指代标准输出流。

 

补充:

AllocConsole函数不能改变控制台窗口在屏幕上的位置、尺寸等属性。以下函数可以控制&获取控制台相关信息。

GetConsoleScreenBufferInfo   // 检索窗口大小,屏幕缓冲区大小及颜色属性
SetConsoleWindowInfo       // 改变控制台窗口大小
SetConsoleScreenBufferSize   // 改变控制台屏幕缓冲区大小
SetConsoleTextAttribute      // 设置颜色属性
SetConsoleTitle          // 设置控制台窗口标题
GetConsoleTitle          // 获取控制台窗口标题

临界区

一个临界区就是一段不会被中断的代码。

使用临界区共有四个函数。为了使用这些函数,你必须定义一个临界区对象,这个对象是一个类型为CRITICAL_SECTION的全部变量。比如,

CRITICAL_SECTION cs;

这个CRITICAL_SECTION数据类型是一个结构,但它的字段只被Windows内部使用。这个变量必须首先被程序中的一个线程通过如下方式初始化:

InitializeCriticalSection(&cs);

这创立了一个叫cs的临界区对象。这个函数的文档包括以下警告:“临界区对象不能被移动或复制。程序不可以修改它,并且必须把它当作不透明的对象。”说白了,这可以被理解为“不要改动或甚至读取它。”

在临界区对象被初始化后,一个线程通过调用以下函数来进入临界区:

EnterCriticalSection(&cs);

这时,我们就说这个线程拥有这个临界区对象。两个线程不能同时拥有同一个临界区。所以,如果一个线程进入了临界区,那么下一个线程在调用EnterCriticalSection来进入同一个临界区对象时会被挂起。这个函数直到第一个线程调用以下函数离开临界区时才会返回:

LeaveCriticalSection(&cs);

在这时,由于调用EnterCriticalSection而被挂起的第二个线程会拥有这个临界区,而且函数调用会返回,使得线程可以继续。

当程序不再需要临界区对象时,可以通过以下函数删除它:

DeleteCriticalSection(&cs);

这会释放为了维护临界区对象而分配的所有系统资源。

UTF8转Unicode:

char* UTF8ToUnicode(char* szUTF8)
{
    int wcscLen = ::MultiByteToWideChar(CP_UTF8, NULL, szUTF8, int(strlen(szUTF8)), NULL, 0);//得到所需空间的大小
    wchar_t* wszcString = new wchar_t[wcscLen + 1];//给'\0'分配空间
    ::MultiByteToWideChar(CP_UTF8, NULL,szUTF8, int(strlen(szUTF8)), wszcString, wcscLen);   //转换
    wszcString[wcscLen] = '\0';
    char *m_char;
    int len = WideCharToMultiByte(CP_ACP, 0, wszcString, int(wcslen(wszcString)), NULL, 0, NULL, NULL);
    m_char = new char[len + 1];
    WideCharToMultiByte(CP_ACP, 0, wszcString, int(wcslen(wszcString)), m_char, len, NULL, NULL);
    m_char[len] = '\0';
    return m_char;
}

 

Unicode转UTF-8:

char* UnicodeToUTF8(wchar_t* wszcString)
{
    int utf8Len = ::WideCharToMultiByte(CP_UTF8, NULL, wszcString, int(wcslen(wszcString)), NULL, 0, NULL, NULL);    //得到所需空间的大小
    char* szUTF8 = new char[utf8Len + 1];    //给'\0'分配空间
    ::WideCharToMultiByte(CP_UTF8, NULL, wszcString, int(wcslen(wszcString)), szUTF8, utf8Len, NULL, NULL);    //转换
    szUTF8[utf8Len] = '\0';
    return szUTF8;
}

 

ANSI转Unicode:

wchar_t* ANSIToUnicode(char *szAnsi)
{
    // ansi to unicode
    //预转换,得到所需空间的大小
    int wcsLen = ::MultiByteToWideChar(CP_ACP, NULL, szAnsi, strlen(szAnsi), NULL, 0);
    //分配空间要给'\0'留个空间,MultiByteToWideChar不会给'\0'空间
    wchar_t* wszString = new wchar_t[wcsLen + 1];
    //转换
    ::MultiByteToWideChar(CP_ACP, NULL, szAnsi, strlen(szAnsi), wszString, wcsLen);
    //最后加上'\0'
    wszString[wcsLen] = '\0';
	return wszString;
}

 

Unicode转Ansi:

char* UnicodeToANSI(wchar_t* wszString)
{
     // unicode to ansi
    //预转换,得到所需空间的大小,这次用的函数和上面名字相反
    int ansiLen = ::WideCharToMultiByte(CP_ACP, NULL, wszString, wcslen(wszString), NULL, 0, NULL, NULL);
    //同上,分配空间要给'\0'留个空间
    char* szAnsi = new char[ansiLen + 1];
    //转换
    //unicode版对应的strlen是wcslen
    ::WideCharToMultiByte(CP_ACP, NULL, wszString, wcslen(wszString), szAnsi, ansiLen, NULL, NULL);
    //最后加上'\0'
    szAnsi[ansiLen] = '\0';
	return szAnsi;
}

 

    def func(b):
        return a+b

这称为一个闭包,闭包函数比普通函数会多出一个__closure__属性,里面定义了一个元组用于存放所有的cell对象,每个cell对象一一保存了这个闭包中所有的外部变量,所有我们只需要修改其中对应的cell对象的值就好了。

完整代码如下:

# -*- coding: utf-8 -*-
def makefunc(a):
    def func(b):
        return a+b
    return func

f = makefunc(1)
f.__closure__[0].cell_contents = 2
print(f(1))
print(f(2))

输出成功的变为:

3
4

 

 

使用了Win32 API中的GetVolumeInformation函数:MSDN

#include<stdio.h>
#include<iostream>
#include<windows.h>
#include <stdarg.h>//可变参数使用所需头文件
using namespace std;
int main()
{
    LPCSTR path = "D:/";
    char sysNameBuf[MAX_PATH] = {0};
    int status = GetVolumeInformationA(path,
                                       NULL, // 驱动盘名缓冲,这里我们不需要
                                       0,
                                       NULL,
                                       NULL,
                                       NULL,
                                       sysNameBuf, // 驱动盘的系统名( FAT/NTFS)
                                       MAX_PATH);

    if (0!=status)
    {
        printf("盘符:%s\n文件系统名 : %s\n", path,sysNameBuf);
        // 比较字符串
    }
}

其中path为驱动盘的根路径,后面要加一个斜杠。

sysNameBuf接收文件系统名称的缓冲区的指针,例如FAT文件系统或NTFS文件系统。

 

Python的每个对象都分为可变和不可变,主要的核心类型中,数字、字符串、元组是不可变的,列表、字典是可变的。

不可变类型以int类型为例:

让我们看看如下代码:

i = 5
i = i + 1

实际上 i = i + 1 并不是真的在原有的int对象上+1,而是重新创建一个值为6的int对象,i引用自这个新的对象。

为了验证这一点,我们通过id函数查看变量i的内存地址进行验证(使用hex(id(i)) 可以查看16进制的内存地址)

可以看到执行 i += 1 时,内存地址都会变化,因为int 类型是不可变的。

再改改代码,但多个int类型的变量值相同时,看看它们内存地址是否相同。

对于不可变类型int,无论创建多少个不可变类型,只要值相同,都指向同个内存地址,同样情况的还有比较短的字符串。

而对于其他类型则不同,以浮点类型为例,从代码运行结果可以看出它是个不可变类型:对i的值进行修改后,它指向新的内存地址。

但如果修改代码声明两个相同值的浮点型变量,查看它们的id,会发现它们并不是指向同个内存地址,这点和int类型不同(这方面涉及Python内存管理机制,Python对int类型和较短的字符串进行了缓存,无论声明多少个值相同的变量,实际上都指向同个内存地址)

 

可变类型以List类型为例:

List在append之后,还是会指向同个内存地址,因为list是可变类型,可以在原处修改。

改改代码,当存在多个值相同的不可变类型变量时,看看它们是不是跟可变类型一样指向同个内存地址。答案是否定的,虽然a、b的值相同,但是指向的内存地址不同。我们也可以通过b = a 的赋值语句,让他们指向同个内存地址。

这个时候需要注意,因为a、b指向同个内存地址,而a、b的类型都是List,可变类型,对a、b任意一个List进行修改,都会影响另外一个List的值。