存档

lambda 表达式 (in Python)

2016/11/16 7,563

概述lambda

python 中的 lambda 比较简单,只是作为创建匿名函数来使用

  • lambda 只是一个表达式,而不是一个代码块。类似于 C++ 中的 define ,但比 define 要简单.Python 中的 lambda 只能封装有限的逻辑进去。
  • lambda 拥有自己的全名空间,且不能访问自有参数列表之外及全局的参数。

语法

labmda 函数的语法只包含一个语句:

lambda [arg ...] : exception

lambda 可以传入多个参数,使用逗号 “,” 分隔。 exception 使用这些参数进行运算并将结果返回。这里的 exception 隐藏了 return 关键词。 示例:

fun = lambda a,b,c : a+b+c
print "call fun(1,2,3) : " , fun(1,2,3)

以上输出:

call fun(1,2,3) : 6

在 python 中的使用

lambda 的使用比较灵活。例如可以在函数内部定义匿名函数,实现简单逻辑并在函数内部使用。另一个常见的应用场景则是作为匿名函数对象在迭代器中使用。比如:

map(function, iterable, ...)
filter(function, iterable)
reduce(function, iterable[, initializer])
  • map : 对迭代器招待 function 操作,并将操作后的 list 返回
  • filter : function 应该返回一个布尔值,对迭代器操作为 False 的,将从 List 里排除
  • reduce : 对迭代器进行累计操作,即将上一次操作的结果作为function 的第一个参数进行下一次操作。reduce 最后一个参数作为可选参数,将在首先参与运算。

这几个函数的一般用法如下:

def funMap(val):
    val = val + 1
    return val

def funFilter(val):
    return  val % 2 == 0

def funReduce(x,y):
    return x+y

#main==========
#构造数组
lst = map(funMap,range(0,11))
print lst

#过滤出数组中的偶数
lst = filter(funFilter,lst)
print lst

#将数组中的每个数累加(首先加10)
print reduce(funReduce,lst,10)

可以看到,我们需要为每个函数再定义一个函数,作为参数传入。 如果我们使用 lambda, 可以简化这些工作:

#构造数组
lst = map(lambda x : x + 1, range(0,11))
print lst

#过滤出数组中的偶数
lst = filter(lambda x : x % 2 == 0, lst)
print lst

#将数组中的每个数累加(首先加10)
print reduce(lambda x,y : x + y, lst, 10)

上面两种写法都输出:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
[2, 4, 6, 8, 10]
40

但是,显然使用lambda的写法更加优雅。

多语言编程之 C and Java: Android NDK

2016/07/11 8,816

NDK(Native Debelopment Kit) 是 Android 提供的一个工具集,它允许在 Android app 使用 native 代码(C/CPP). 它集成了交叉编译器, 使用 mk 文件对平台、CPU 等进行隔离。在不同的平台下,只需要配置相应的 mk 文件即可以编译相就的模块。

1 NDK project 基本结构

ndkprojectFoloer

 

 

 

 

 

2 Android.mk

Android.mk 文件存在于工程路径中的 jni/ 路径下。它可以看作是 GNUMakefile 的一部分,用来向编译器描述如何从 native 源码构建共享库(动态库)。
Android.mk 可以将源码分成不同的模块。这些 模块 可以是静态库、共享库(甚至是可执行文件)。静态库用来生成共享库,共享库则会和 app 打包。

继续阅读

多语言编程之 C and Java:JNI进阶–字段访问与方法回调

2016/07/09 8,365

在上一章(《多语言编程之 C and Java:JNI 30 分钟入门》)中讲解了 JNI 的基本使用。这一章将讨论一些更加实用的用法:如何在 JNI 中访问 Java 对象的成员变量或静态变量,以及回调 Java 中的成员方法或静态方法。

1 JNI 访问 Java 成员变量

1.1 实例

Java 实现

package transvar;
public class TransVar{
    static{
	System.loadLibrary("hello");
    }

    //成员变量(字段)
    private int age = 88;
    private String name = "Jims";

    //静态成员变量
    private static int years = 20;

    private native void modifyInstanceVariable();  
}

这个 Java 类中包含 3 个私有成员变量,名为 age 的 int 型变量, 名为 name 的 String 型变量和一个名为 years 的 int 型静态变量。我们将在使用 C 来访问或修改这些成员变量。

C 实现

JNIEXPORT void JNICALL Java_transvar_TransVar_modifyInstanceVariable
  (JNIEnv *env, jobject obj)
{
    //获取 this object 的类
    jclass thisClass = (*env)->GetObjectClass(env,obj);
    if(thisClass == NULL)
    {
      printf("thisClass null\n");
    }

    //获取字段ID
    jfieldID fidAge = (*env)->GetFieldID(env, thisClass, "age",  "I");
    jfieldID fidName = (*env)->GetFieldID(env, thisClass, "name", "Ljava/lang/String;");
    jfieldID fidYear = (*env)->GetStaticFieldID(env, thisClass, "years", "I");

    //获取字段
    jint age = (*env)->GetIntField(env, obj, fidAge);
    jint years = (*env)->GetStaticIntField(env, obj, fidYear);
    jstring name = (*env)->GetObjectField(env, obj, fidName);
    const char* pszName = (*env)->GetStringUTFChars(env, name, NULL);

    printf("[%d Years Later for  %s] \n", years, pszName);

    (*env)->ReleaseStringUTFChars(env, name, pszName);

    age += years;

    //修改成员变量
    (*env)->SetIntField(env, obj, fidAge, age);
}

1.2 访问 Java 变量

在 JNI 中我们获取了这 3 个变量,并对 age 变量进行了修改。
由这段代码可以看出,访问 Java 成员变量的一般步骤为:

  1. 通过 GetObjectClass() 获取 Java 类的引用。
  2. 通过 GetFieldID()/GetStaticFieldID() 从 Java 类中获取 FieldID 。
  3. 基于 FieldID, 通过 Get<type>Field()/GetStatic<type>Field() 取回变量。
  4. 基于 FieldID, 通过 Set<type>Field()/GetStatic<type>Field() 修改变量。

GetFieldID()/GetStaticFieldID() 原型如下:

jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);
jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig);

继续阅读

多语言编程之 C and Java:JNI 30 分钟入门

2016/06/29 8,464

1 引言

1.1 简介

在实际项目中,很多时候是以多种语言混合开发的。常用的模式是使用 C/C++ 做底层支撑,然后上层辅之以高级语言。 JNI可以使 native 代码与 Java 对象交互,而不会像 Java 代码中的功能那样受到诸多限制, 往往充作 Java 与 C/C++/Assembly 语言的粘合剂。

JNI 明确分开了 Java 代码与 native 代码的执行,为两者的通信定义了一套清晰的API.避免 native 代码对 JVM 的直接内存引用。

2 JNI 入门

2.1 JNI 调用 C

2.1.1  java 中的 native 方法声明

HelloJNI.java

public class HelloJNI{
    static{
        System.loadLibrary("hello");  //运行时加载本地库 hello.dll(Windows)或libhello.so(Unixes) 或 libhello.dylib(MacOX)
    }

    private native void sayHello();  //声明 native 方法

    public static void main(String[] args ){
        new HelloJNI().sayHello();  //调用本地方法
    }
}

当HelloJNI类加载的时候,静态初始化调用 System.loadLibrary() 来加载本地库 hello.dll(或libhello.so)。如果不能加载 hello 库,会抛出 UnsatisfiedLinkError 异常。
然后我们使用 native 关键字声明了 native 方法 sayHello() ,这表明该方法是使用其它语言实现的。
最后 main() 方法实例化了 HelloJNI 并调用了 sayHello().
然后我们编译这个类:

> javac HelloJNI.java

2.1.2  生成 C/C++ 头文件

使用 javah 创建头文件:

> javah HelloJNI

会得到如下 .h 文件 HelloJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */

#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     HelloJNI
 * Method:    sayHello
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

这份自动生成的头文件里声明一个 C 函数 Java_HelloJNI_sayHello:

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);

这个方法名是由javah转换来的,转换规则为:

Java_{package_and_classname}_{function_name}(JNI arguments)

包名中的 点 会被转换为 下划线
函数中带有两个参数(虽然我们在本例中并没有使用这两个参数)

  • JNIEnv*: 指向 JNI environment ,提供对 JNI 方法的访问
  • jobject: 指向 this Java 对象

2.1.3 C 代码的实现

HelloJNI.c

#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"

JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv * env, jobject obj)
{
    printf("Hello World!");
}

这个实现将在控制台打印万能的 “Hello World!”
文件中包含了 jni.h 头文件。这个头文件由 java 提供,位于 %JAVA_HOME%/include 路径下(Windows下还需要引用 %JAVA_HOME%/include/win32).

2.1.4 编译 C 库

接下来,编译这个 c 文件:

#Windows gcc
> gcc -Wl,--add-stdcall-alias -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" -shared -o hello.dll HelloJNI.c

#Windows VS cl.exe link.exe
> cl /c /I%JAVA_HOME%/include /I%JAVA_HOME%/include/win32 HelloJNI.c
> link HelloJNI.obj /DLL /OUT:hello.dll

2.1.5 检查成果

继续阅读

Apache Thrift

2016/05/26 4,772

1 简介

1.1 概览

Apache Thrift官网
Thrift是一个跨语言的服务开发部署 框架,其最初由Facebook于2007年开发,于2008年加入Apache开源项目。
Thrift 通过编译thrift文件(由Thrift Types 组织的中间语言,用于定义RPC数据结构),生成不同语言的类。这些类负责RPC协议层和传输层的实现。

1.2  优点

相对于XML、Json,Thrift在性能和传输数据压缩上有绝对优势。

2 简单使用

2.1 过程

  1. 下载并编译 thrift编译器 .
  2. 编写thrift文件.需要用到 thrift自定义数据类型 .包括
    1. 基本数据类型
    2. 特殊数据类型(binary)
    3. 结构体
    4. 容器(list,set,map).容器元素可以是服务以外的任何thrift type .
    5. 异常(Exceptions)
    6. 服务
  3. 编译thrift文件,生成可供服务端和客户调用的源码.命令语法如下:
thrift --gen <language> <thrift filename>

2.2 简单实例

2.2.1 编辑thrift文件

代码清单如下:

namespace cpp im
struct Msg
{
    1:i32 idFrom,
    2:i32 idTo,
    3:string content,
}

service transMsg
{
     void trans(1:Msg m);
}

2.2.2 生成cpp代码

使用thrift.exe :

thrift.exe -gen cpp transMsg.thrift

此时会在 “gen-cpp” 文件夹(默认文件夹)下生成相应的cpp代码:

msg_constants.cpp
msg_constants.h
msg_types.cpp
msg_types.h
transMsg.cpp
transMsg.h
transMsg_server.skeleton.cpp

其中 “msg_ types” 和 “msg_ constants” 对数据类型进行了定义,”transMsg” 实现了基本逻辑。”transMsg_ server.skeleton” 则简单实现了一个server端的实例。

2.2.3 实现业务逻辑

新建两个工程,一个做为服务端,一个做为用户端。 将 gen-cpp 文件夹下的文件分别添加到两个工程中,并在工程中添加thrift的静态库(或直接引用源码).
Server端的工程可以直接使用生成的代码。Client端的业务则需要自己实现。
下面简单实现Client端的业务:

int main(int argc, char **argv) {
  int port = 9090;
  std::string strHost("192.168.3.11");

  boost::shared_ptr<TTransport> socket(new TSocket(strHost.c_str(), port));
  boost::shared_ptr<TTransport> transport(new TBufferedTransport(socket));
  boost::shared_ptr<TProtocol> protocol(new TBinaryProtocol(transport));
  transMsgClient client(protocol);

  try
  {
          im::Msg m;
          m.__set_idFrom(10);
          m.__set_idTo(280);
          m.__set_content("Hi, I`m a message. ");

          transport->open();

          client.trans(m);

          transport->close();
  }
  catch (TException  e)
  {
          printf("%s", e.what());
  }

  system("pause");
  return 0;
}

其中strHost为服务器的IP.
继续阅读

了解Poco C++ Poco::Data 数据库基本操作(二)

2016/02/07 8,840

Statement

前面我们提到了Statement,但是到目前为止,我们只使用了session,至少从代码上来看是这样的。

而实际上,我们已经在使用statement了,只不过它在代码中隐藏了起来。我们来看看Session类中的<<操作符:

template <typename T>
Statement operator << (const T& t)
/// Creates a Statement with the given data as SQLContent
{
    return _statementCreator << t;
}

这里<<操作符创建了一个statement并将其返回。前面例子中我们使用<<操作符创建了statement,但是这个statement并没有分配到变量,一直到”now”部分executed了这个隐藏的statement,然后将其销毁。

我们将前面的例子拿一个出来修改一下:

std::string name("zrKing"); 
Statement stmt = (session << "INSERT INTO tbA VALUES(?)",use(name));
stmt.execute();
poco_assert(stmt.done());

当execute执行以后,数据已经插入了。可以使用stmt.done()来确认操作是否已经完全完成。

Prepared Statements

上例中省略的代码now子句创建了一个prepared statement.

prepared statement 的优势在于性能。我们假设有如下的循环:

std::string name;
Statement stmt = (session << "INSERT INTO tbA VALUES(?)", use(name));
for(int i = 0; i<100; i++)
{
    name.append("x");
    stmt.execute();
}

在上例中,如果不使用prepated statement,我们需要创建和解析Statement 100次。而现在我们只需要创建并解析一次Statement,然后使用占位符匹配不同的值,多次执行来将name插入到数据中。

当然,对于如何将一系列值插入到数据库,上例的代码并不是最优解。通过Poco::Data与STL容器协作可以更好的解决这个问题。具体的我们将在”STL容器”这一节来讲。 继续阅读

了解Poco C++ Poco::Data 数据库基本操作

2016/01/27 12,370

前言

POCO::Data 是Poco的数据库抽象层,为C++提供统一的结构化数据库访问接口,使得C++以简单而自然的方式访问关系型数据库。使用它可以方便地从多种数据库中存取数据。目前Poco::Data支持的数据库连接类型包括 SQLite,MySQL及ODBC。POCO框架支持扩展,所以你也可以添加其他的本地连接扩展。

下面是一个使用Poco::Data::SQLite的一个简单例子:

#include "Poco/Data/Session.h"
#include "Poco/Data/SQLite/Connector.h"
#include <vector>
#include <iostream>

using namespace Poco::Data::Keywords;
using Poco::Data::Session;
using Poco::Data::Statement;

struct Person
{
    std::string name;
    std::string address;
    int         age;
};

int main()
{
    //注册SQLite连接器
    Poco::Data::SQLite::Connector::registerConnector();

    // 创建传话 session
    Session session("SQLite", "sample.db");

    // 删除表
    session << "DROP TABLE IF EXISTS Person", now;

    // 网创建表
    session << "CREATE TABLE Person (Name VARCHAR(30), Address VARCHAR, Age INTEGER(3))", now;

    // 插入记录
    Person person = 
    {
        "Bart Simpson",
        "Springfield",
        12
    };

    Statement insert(session);
    insert << "INSERT INTO Person VALUES(?, ?, ?)",
        use(person.name),
        use(person.address),
        use(person.age);

    insert.execute();

    person.name    = "Lisa Simpson";
    person.address = "Springfield";
    person.age     = 10;

    insert.execute();

    // 简单查询
    Statement select(session);
    select << "SELECT Name, Address, Age FROM Person",
        into(person.name),
        into(person.address),
        into(person.age),
        range(0, 1); 

    while (!select.done())
    {
        select.execute();
        std::cout << person.name << " " << person.address << " " << person.age << std::endl;
    }

    return 0;
}

 

创建会话(Sessions)

通过Session 构造器可以创建Session:

Session session("SQLite","./sample.db");

这个例子创建了一个SQLite类型的session。

第一个参数是希望创建的Session类型,目前支持的连接类型有”SQLite”,”ODBC”,”MySQL”。

第二个参数是数据库连接字符串,根据不同的数据库使用不同的连接字符串:

  • 对于Sqlite数据库,数据库路径即可做为连接字符串
  • 对于ODBC数据库,连接字符串类似于 “DSN=MyDSNName”.具体参数请参考ODBC驱动文档
  • 对于MySQL数据,连接字符串由一系列键值对组成,例如 “host=localhist;port=3306;db=mydb;user=thename;password=thepwd;compress=true;auto-reconnect=true”;具体请教参MySQL官方文档。

存取数据

单数据集

假设我们有一个表 tbA,表中有一个字段 fN,插入记录时我们可以这么写:

std::string name="wandoer"; 
session << "INSERT INTO tbA VALUES("<<name<<")",now;

是不是很简单。然而这种写法并不提倡,我们有更好的方式:占位符!使用占位符(placeholders)可以匹配变量和占位符,在执行语句时自动用变量替换占位符。普遍公认的占位符是问号(?),有些数据库也使用冒号(:)来做占位符。具体使用哪一种,请参考所选用数据库的说明文档。

那么上面的例子可以写做:

std::string name="wandoer"; 
session << "INSERT INTO tbA VALUES(?)",use(name), now;

此例中使用use(name)来匹配占位符。use是Poco::Data::Keywords下提供的关键字,可以将变量和占位符绑定(Binding)。这么做真正的意义,是将变量与SQL语句分隔开,防止SQL注入式攻击。

从数据库中取数据的操作与之类似。into关键字将数据库返回的值与C++对象匹配起来。并且支持指定默认值以防数据库返回null值。

std::string name; 
session << "SELECT fN FROM tbA",into(name),now;
session << "SELECT fN FROM tbA",into(name,0,std::string("default")),now;

此处需要注意的是,指定默认值的into,其定义为:into(T& t, const Position& pos, const T& def),使用的是模样类。在有些编译器下,不能隐式转换数据类型,所以需要显示的让第一个参数和第三个参数的数据类型保持一致。

into还可以和use联合使用:

std::string name;
std::string match="wandoer";
session << "SELECT fN FROM tbA WHERE fN=?",into(name),use(match),now;

当然,现实中的数据库不会总像例子中这么简单。在一个表中往往有多列,这样需要使用多次into/use关键字:

std::string firstName("Peter");
std::string lastName("Junior");
int age = 0;
ses << "INSERT INTO PERSON VALUES (?, ?, ?)", use(firstName), use(lastName), use(age), now;
ses << "SELECT (firstname, lastname, age) FROM Person", into(firstName), into(lastName), into(age), now;

那么此时尤为重要的,就是into和use的顺序了。第一个占位符使用第一个use,第二个占位符使用第二个use,以此类推。into亦如是。

继续阅读

仿函数(function object,函数对象)

2016/01/12 8,895

引子

前一篇文章讲到了C++11的新特性lambda表达式

lambda表达式的本质是什么呢?为探究这个问题,我们将一段写有lambda表达式的代码反编译,可能会发现一些秘密.

首先写段程序:

//#include...
auto flambda = [](int x,int y)->int{return x + y;};
int main()
{
    cout<<flambda(3,2)<<endl;
    return 0;
}

在调试该代码时我们打开汇编视图,发现如下代码:

callq  0x100000d80 <$_0::operator()(int, int) const>
mov    0x2c4(%rip),%rdi        # 0x100001010
mov    %eax,%esi

其中 <$_0::operator()(int, int) const>      即对应原代码中的lambda表达式。这里一串重载了”()”(operator())操作符的代码,即本文将要了解的仿函数

Note:  此处的 const  ,也应证了前文 mutable 可变范围 中提到的,lambda的调用运算符为”const-by-value”的

  定义

仿函数(finctor)是一种早期的叫法,在C++11中,标准的叫法为函数对象(function object)。仿函数是一种行为类似函数、具有函数特性的对象。

仿函数是一个对象,而不是函数。仿函数的类通过重载函数操作符”()”(operator())来实现函数调用的特性。

通过下面的例子来简单说明如何使用仿函数

#include <functional>
//#include...

class myGreater
{
public:
    bool operator()(int x,int y)  //重载调用操作函数
    {return x>y;}
};

int main()
{
   greater<int> gt;  //lineA
   cout<<boolalpha<<gt(4,3)<<endl;  //lineB
   cout<<greater<int>()(3,4)<<endl; //lineC
   cout<<myGreater()(5,3)<<endl;    //lineD
    return 0;
}
//OUTPUT:
//true
//false
//true

这个例子中,lineA声明了一个对象。这是一个函数对象,其类(结构)定义在functional文件中,是STL内置的模板类。该类用于比较两个参数的大小。

lineB则像函数一样直接给该对象传入两个参数。

lineC是另一种写法。greater<int>() 声明了一个临时对象,然后传入参数(3,4)调用该临时对象。这种写法往往比lineB更加简洁。在STL源码里大量使用了这种写法。

lineD则是使用自定义的函数对象。

Note:lineB中使用了boolalpha,是iostream提供的一个操作符,这将使此后的标准输出中,将bool值输出为”true”或”false”,而不是1/0。于其相反的操作为noboolalpha.

继续阅读