C++面试题集合

只熟悉C,对C++不甚了解啊,但是面试又基本只有C++和Java的。于是乎,整理一下自己遇到的C++面试/笔试题吧

———————————————–我是背景———————————————————————

题目一:一个C++空类建立以后,会产生哪些成员函数?

分析:当时我就只想到了构造和析构函数啊。答案是6个。

[cpp]
class Empty
{
public:
Empty(); // 缺省构造函数
Empty( const Empty&); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator=( const Empty&); // 赋值运算符
Empty* operator&(); // 取址运算符
const Empty* operator&() const; // 取址运算符 const
};
[/cpp]

但并不一定是6个,如果编译器发现我们只是申明了Empty,并没有发现创建Empty的实例,那么编译器是什么函数都不会生成的。

所有这些只有当被需要才会产生。比如,
Empty e;
编译器就会根据上面的实例,给类Empty生成构造函数和析构函数。
当使用
Empty  e2(e);
编译器就会生成类Empty的拷贝构造函数。
Empty   e3;
e3 = e;
编译器生成赋值运算符函数
Empty    &ee = e;
编译器生成取地址运算符函数。

经过我们的分析可以这样理解:对于一个没有实例化的空类,编译器是不会给它
生成任何函数的,当实例化一个空类后,编译器会根据需要生成相应的函数。这条理论同样
适合非空类(只声明变量,而不声明函数)。

———————————————————————————————————————————–

题目二:STL中,vector与list的区别?

分析:其实基本就是 数组 与 双向链表 的区别,所以就很显然啦。

vector能够很好的支持随机访问,即有下标操作[],但如果要插入或者删除一个数则需要较多次数的移动元素。

list能够很好的支持插入、删除操作,但由于没有下标操作[],所以查找一个元素的时候比较耗时。

———————————————————————————————————————————–

题目三:内联函数、宏的区别?

分析:

由于内联函数首先它是一个函数,所以可以从宏与普通函数的对比入手,先引用一下别人的总结。

(1)、宏只做简单的字符串替换,函数是参数传递,所以必然有参数类型检查(支持各种类型,而不是只有字符串)以及类型转换。
(2)、宏不经计算而直接替换参数,函数调用则是将参数表达式求值再传递给形参。
(3)、宏在编译前(即预编译阶段)进行,先替换再编译。而函数是编译后,在执行时才调用的。宏占编译时间,而函数占执行时间。
(4)、宏参数不占空间,因为只做字符串替换,而函数调用时参数传递是变量之间的传递,形参作为局部变量占内存空间。
(5)、函数调用需要保留现场,然后转入调用函数执行,执行完毕再返回主调函数,这些耗费在宏中是没有的。

使用宏和内联函数都可以节省在函数调用方面的时间和空间开销。二者都是为了提高效率,但是却有着显著的区别:

(1)、内联函数首先是函数,函数的许多性质都适用于内联函数(如内联函数可以重载)。
(2)、在使用时,宏只做简单的文本替换。内联函数可以进行参数类型检查,且具有返回值(也能被强制转换为可转换的合适类型)。
(3)、内联函数可以作为某个类的成员函数,这样可以使用类的保护成员和私有成员。而当一个表达式涉及到类保护成员或私有成员时,宏就不能实现了(无法将this指针放在合适位置)。

———————————————————————————————————————————–

题目四:实现一个算法,删除字符串重复字符

分析:当时笔试面试已经搞了一下午,头晕眼花,第一次还写错了。回来整理了一下,其实很简单。

用两个指针p, q遍历待处理的字符串str,p负责记录有效位,q负责往前扫描。初始化q指向p的下一个字符。

(1) 如果p的字符和q的字符不等,那么由p记录下来,p与q均往前移动一个字符

(2) 如果p的字符和q的字符相等,那么p不动,q往前移动一个字符

代码如下

[cpp]
// the input str must terminated by ‘\0’
char* str_rm_dup(char *str)
{
char *p = str;
char *q = p;

// if str is NULL, then return
if(!p)
return NULL;

/*
* Assign q to the next address of p.
* Before that, we use *q++, not *++q to check whether *p == ‘\0’ in the first loop,
* because if the str is an empty string, str[0] will be ‘\0’.
*/
while(*q++)
if(*p != *q)
*++p = *q;

return str;
}

int main()
{
char a[] = "aabbbccdeeeeee";
printf("%s\n", str_rm_dup(a)); // abcde
return 0;
}
[/cpp]

———————————————————————————————————————————–

题目五:要求写一个没有错误的二分

分析:主要就是注意两点

(1) 整数相加溢出,比如4位机器上,a和b都等于7,即0111,那么相加就悲剧了。

(2) 防止无限循环

代码如下

[cpp]

/*
* Find the index of v if it is in the array of interval [a, b), -1 if not.
* @para a, the first location of the NOT DESC array
* @para l, the lowest index of the interval to be searched
* @para h, the highest index of the interval to be searched
* @para v, the candicate value
* @return index of v, or -1 if not found
*/
int bs(int *a, int l, int h, int v)
{
int m;
while(l < h)
{
m = (unsigned)l + (unsigned)h >> 1; // avoid the overflow of integer
if(a[m] == v)
return m;
if(a[m] < v)
l = m + 1;
else
h = m;
}
return -1;
}

[/cpp]

———————————————————————————————————————————–

题目六:深拷贝、浅拷贝区别?

分析:我的理解就是,浅拷贝只简单赋值,深拷贝是在需要的时候申请新的内存空间。假设我们有一个简单类A,代码如下所示:

[cpp]
class A
{
public:
A(char *s) : str(s) {}
void display()
{
cout << str << endl;
}
public:
char *str;
};
[/cpp]

然后我们写一个简单的main函数来调用它,如下

[cpp]
int main(void)
{
char name[] = "David";

A a(name);
A b = a;

a.display();
b.display();

b.str[0]=’P’;

a.display();
b.display();

return 0;
}
[/cpp]

结果为

QQ图片20131031155933

这是由于我们在类A中并没有去实现拷贝构造函数,编译器则帮我们实现了一个默认的,默认的构造函数则是简单的变量赋值,所以对象a和b的str成员变量其实指向的是同一块儿内存。所以改了一个,另外一个也会跟着改变。

下面我们修改一下类A,代码如下

[cpp]
class A
{
public:
A(char *s) : str(s) {}
void display()
{
cout << str << endl;
}
A(const A &another)
{
str = (char*)malloc(sizeof(char) * (strlen(another.str) + 1));
strcpy(str, another.str);
}
public:
char *str;
};
[/cpp]

然后看运行结果

QQ图片20131031160544

这次对象a和b的str由于指向的不是同一块儿内存地址,所以改了b的str对a不会造成影响。

还有一个区别是,貌似在消除对象a或者b的时候,如果是浅拷贝,会删除两次str所指的内存空间,会造成不可预料的结果。但是我跑程序暂时还未出现崩溃。

———————————————————————————————————————————–

Linux 下 C++ 连接 Mysql

——国外有一篇博客写得挺好,有一些小错误,改正以后翻译过来,发这里了。

C++是一个效率非常高的编程语言

Linux 是一个类Unix操作系统,它促进了开源软件社区的发展,并且用这些开源软件就几乎足以构建一个复杂精妙的企业系统。

MySQL是一个流行的多线程、多用户的数据库管理系统,全球有超过1000万的安装量。

 

这篇文章将演示如何在Linux下用C++连接MySQL。

首先我们需要将所需要的头文件和库全部包含进来

#include <stdio.h>

#include <mysql.h>

然后我们来定义一下main函数

int main()

{

// code 

return 0;

};

然后我们定义一下需要用到的变量,你应该将这些写到main函数里去。

MYSQL_RES *result;

MYSQL_ROW row;

MYSQL *connection, mysql;

int state;

既然要使用MySQL数据库,我们首先就得连上它,使用下面的代码即可:

mysql_init(&mysql);

connection = mysql_real_connect(&mysql,host,usr,pswd,database,0,0,0);

其中

host – 数据库所在主机的名字,比如 “localhost” 或者 “192.168.250.100”

usr – 连接数据库时使用的用户名

pswd – 连接数据库时使用的密码

database – 数据库的名称

如果有错误发生,如用户名/密码不匹配,我们可以通过以下代码获取到错误信息:

if (connection == NULL)

{

printf(“%s\n”, mysql_error(&mysql));

return 1;

}

如果没有错误,那么证明我们连接好了,现在我们来执行一个简单的查询语句,其中mytable指的是你指定的数据库中的一张表名:

state = mysql_query(connection, “SELECT * FROM mytable”);

if (state !=0)

{

printf(“%s\n”, mysql_error(connection));

return 1;

}

如果成功执行,我们可以使用一个变量来存储查询结果:

result = mysql_store_result(connection);

使用mysql_num_rows函数,我们可以得到查询结果的条数:

printf(“Rows:%lld\n”,mysql_num_rows(result));

使用mysql_fetch_rowwhile循环中处理每一条查询结果:

while ( ( row=mysql_fetch_row(result)) != NULL )

{

printf(” %s, %s\n”, (row[0] ? row[0] : “NULL”), (row[1] ? row[1] : “NULL” ));

}

最后,别忘了释放内存:

mysql_free_result(result);

mysql_close(connection);

IMPORTANT!!! How to make this code under Linux?

重要!!!!在Linux中怎样编译上面的代码呢?

g++ test.cpp -I/usr/local/mysql/include /usr/local/mysql/lib/libmysqlclient.so

路径有可能不同,修改为自己的即可。

/usr/local/mysql/include 中含有mysql.h等头文件

/usr/local/mysql/lib/libmysqlclient.so 这个动态链接库也是需要的

Enjoy!

 

 

PHP调用C/C++动态链接库

摘要

有时候,单纯依靠 PHP “本身”是不行的。尽管普通用户很少遇到这种情况,但一些专业性的应用则经常需要将 PHP 的性能发挥到极致(这里的性能是指速度或功能)。由于受到 PHP 语言本身的限制,同时还可能不得不把庞大的库文件包含到每个脚本当中。因此,某些新功能并不是总能被顺利实现,所以我们必须另外寻找一些方法来克服 PHP 的这些缺点。

了解到了这一点,我们就到了应该接触一下 PHP 的心脏并探究一下它的内核——可以编译成 PHP 并让之工作的 C 代码——的时候了。

概述:

PHP调用动态链接库几个必要步骤为:

1. C/C++编写动态链接库,编译打包成.so文件

2. 初始化一个新的PHP扩展

3. 配置、编写PHP扩展内容,在扩展中使用C/C++调用.so

4. 编译并添加PHP扩展

5. 在PHP应用中直接调用PHP扩展里暴露出来的API

为了从能运行的最简单的例子开始,所以下面的叙述可能不会严格按照上面列的步骤来写,也有可能会重复穿插着写。但总体顺序是一致的。

一. C/C++编写动态链接库,编译打包成.so文件

如果还不会用C++调用自己写的.so库,请参考我的这篇文章:

http://keping.me/cpp_invoke_so/

文中从什么是Name Mangling开始,到如何用调用一个包含简单函数的so库,再到如何从so中加载类,都有详细叙述,并附有可运行示例代码。

二. 初始化PHP扩展

本文中,我们将创建一个叫“vehicle”的PHP扩展,其中包含一个“Car”类。

将要创建的扩展会涉及到以下文件需要修改,这些文件将会出现在vehicle目录下。后面会一一叙述这些文件是怎么来的,现在只是大致了解一下。

  • car.h —— 包含了C++写的类,即Car的定义
  • car.cc —— Car类的具体实现
  • php_vehicle.h —— 包含了PHP创建扩展所需要的一些头文件,外部变量定义等。
  • vehicle.cc —— 扩展的主要源码文件,这里面会调用到Car类
  • config.m4 —— PHP扩展的配置文件

1. 需要PHP源码包

如果你不是通过源码包方式安装的PHP,而是通过apt-get install 安装的,那么首先确保你安装了 php-devel 包,然后需要下载php源代码,然后可以跳到第2步。

如果想从源码包编译安装PHP,可以参考我写的另外一篇文章《Linux(Ubuntu12.10)搭建PHP开发环境(源码包方式)》,有详细的每一步的介绍。地址为:

http://keping.me/linux-php-dev-by-source-style/

安装完成以后,跳到第2步。

2. 制作PHP外部扩展

去到PHP源码包目录的ext目录下,我的在

/usr/local/src/php-5.3.22/ext

然后输入命令

sudo ./ext_skel –extname=vehicle

该命令会在ext目录下新建一个vehicle目录,并创建新模块“vehicle”目前所需的所有文件,包括

config.m4, php_vehicle.h, vehicle.php, CREDITS等。

还会列出应该执行哪些后续步骤来使用新的扩展,具体如下图所示。

Selection_190

图中我一共使用了三个命令

$ pwd 显示我的php源码包的ext目录所在位置

$ sudo ./ext_skel –extname=vehicle 前面已解释

$ ls 列出了执行上一条命令以后,在vehicle目录下,为我们创建的文件及目录。

如上图所示,在它的指导步骤1~8中,大致了解到我们需要编译config.m4文件,然后需要运行配置,然后需要使用make命令编译等。现在不用知道具体都干些什么,接下来会分别叙述。

三. 配置、搭建最基本的PHP扩展骨架

1. 配置PHP构建系统——编写config.m4

首先我们需要明确的是,为了在PHP扩展中使用C++,那么必须通知PHP构建系统使用C++编译器

通过在config.m4 文件中添加宏PHP_REQUIRE_CXX()来实现。

使用C++的过程中,肯定会用到C++的一些库,至少需要标准库(libstdc++ 大多系统都是这样的)

通过在config.m4 文件中添加宏PHP_ADD_LIBRARY()来实现。

将下面的代码添加到config.m4中

PHP_ARG_ENABLE(vehicle,
    [Whether to enable the "vehicle" extension],
    [  --enable-vehicle      Enable "vehicle" extension support])

if test $PHP_VEHICLE != "no"; then
    PHP_REQUIRE_CXX()
    PHP_SUBST(VEHICLE_SHARED_LIBADD)
    PHP_ADD_LIBRARY(stdc++, 1, VEHICLE_SHARED_LIBADD)
    PHP_NEW_EXTENSION(vehicle, vehicle.cc car.cc, $ext_shared)
fi

即去掉config.m4文件中某些行前面的注释符号“dnl”,然后添加我们需要的配置。如果还不清楚,可以参考我的配置文件,内容如下图

Selection_191

这里的宏PHP_SUBST()是标准autoconf的AC_SUBST()宏的php修改版, 它在将扩展构建为共享模块时需要。

PHP_NEW_EXTENSION宏中,第一个参数代表模块的名称;第二个参数是需要编译的文件,用空格隔开;第三个参数跟宏PHP_SUBST()是一样的。

2. 编写头文件php_vehicle.h

该头文件应该包含以下内容,其中PHP_VEHICLE_EXTNAME “vehicle” 代表该扩展的名称,PHP_VEHICLE_EXTVER则代表版本号,这些都会在php_info()中显示出来。

#ifndef PHP_VEHICLE_H
#define PHP_VEHICLE_H

#define PHP_VEHICLE_EXTNAME  "vehicle"
#define PHP_VEHICLE_EXTVER   "1.0"

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif 

extern "C" {
#include "php.h"
}

extern zend_module_entry vehicle_module_entry;
#define phpext_vehicle_ptr &vehicle_module_entry;

PHP_MINIT_FUNCTION(vehicle);
PHP_MSUTDOWN_FUNCTION(vehicle);
PHP_RINIT_FUNCTION(vehicle);
PHP_RSHUTDOWN_FUNCTION(vehicle);
PHP_MINFO_FUNCTION(vehicle);

#endif /* PHP_VEHICLE_H */

我的php_vehicle.h文件内容如下图所示。

Selection_193

要理解PHP_MINIT_FUNCTION()函数,就需要了解PHP的启动步骤。大体就是

  • 当我们启动Apache的时候,它就启动PHP的解释器
  • PHP会调用每一个扩展的MINIT函数,可以通过查看php.ini文件来看哪些扩展模块是激活的
  • MINIT就是Module Initialization,即模块初始化方法的简称,在每一个模块初始化方法里,会定义并初始化一系列在以后的页面请求中需要用到的函数、类、变量等。
  • 一个典型的MINIT方法框架如下所示

[php]
PHP_MINIT_FUNCTION(extension_name) {

/* Initialize functions, classes etc */

}
[/php]

以上是PHP启动的第一步。为了先跑通整个流程,这里就不在一一叙述每一个方法以及宏的作用了。

3. 编写需要编译的文件(vehicle.cc、car.cc)

使扩展能够运行的最基本的vehicle.cc框架应该包含以下内容

#include "php_vehicle.h"

PHP_MINIT_FUNCTION(vehicle)
{
    return SUCCESS;
}

zend_module_entry vehicle_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_VEHICLE_EXTNAME,
    NULL,                  /* Functions */
    PHP_MINIT(vehicle),
    NULL,                  /* MSHUTDOWN */
    NULL,                  /* RINIT */
    NULL,                  /* RSHUTDOWN */
    NULL,                  /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_VEHICLE_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_VEHICLE
extern "C" {
ZEND_GET_MODULE(vehicle)
}
#endif

我的vehicle.cc文件如下图所示

Selection_197

有了这个文件,我们还不能能创建最基本的PHP扩展,因为前面我们在配置文件config.m4中指定了需要编译的还有car.cc文件,所以必须给出,即使现在里面什么也没有。

所以还需要新建一个car.cc文件,暂时让它空着。

4. 配置、编译、安装

在当前文件目录(即/usr/local/src/php-5.3.22/ext/vehicle)下执行命令

$ sudo /usr/local/php/bin/phpize

$ sudo ./configure –enable-vehicle –with-php-config=/usr/local/php/bin/php-config

如下图所示

Selection_195

然后执行make & make install 命令

$ sudo make

$sudo make install

Selection_196

安装完成以后,会看到在

/usr/local/php/lib/php/extensions/no-debug-zts-20090626/

目录下有一个新生成的so文件,名为vehicle.so,将库该文件的路径添加到php.ini配置文件中,如下图所示。

Selection_199

即添加这句话

“extension=”/usr/local/php/lib/php/extensions/no-debug-zts-20090626/vehicle.so”

然后重启你的apache,在phpinfo()中就可以看到已经成功启动扩展vehicle了,如下图所示。

Selection_200

至此,最基本的框架已经搭起来了。接下来要做的工作就是如何使用PHP调用C/C++编写的so文件。先从简单的直接调用开始

四. 编写具体的PHP扩展内容

1.  编写car.h头文件

把头文件与源码文件分开是一个不错的习惯,特别是在别人不想知道你的具体实现的时候。这里我们也采取这种方式。

#ifndef VEHICLE_CAR_H
#define VEHICLE_CAR_H

// A very simple car class
class Car {
public:
    Car(int maxGear);
    void shift(int gear);
    void accelerate();
    void brake();
    int getCurrentSpeed();
    int getCurrentGear();
private:
    int maxGear;
    int currentGear;
    int speed;
};

#endif /* VEHICLE_CAR_H */

如上代码所示,我们先定义一个Car类,然后定义几个public的方法以及私有成员变量。然后在car.cc中实现它

#include "car.h"

Car::Car(int maxGear) {
    this->maxGear = maxGear;
    this->currentGear = 1;
    this->speed = 0;
}

void Car::shift(int gear) {
    if (gear < 1 || gear > maxGear) {
        return;
    }
    currentGear = gear;
}

void Car::accelerate() {
    speed += (5 * this->getCurrentGear());
}

void Car::brake() {
    speed -= (5 * this->getCurrentGear());
}

int Car::getCurrentSpeed() {
    return speed;
}

int Car::getCurrentGear() {
    return currentGear;
}

现在我们已经定义好了Car类,那么如何使其暴露给PHP用户空间,让PHP能够调用这些方法呢。

首先你需要定义一个包含有function_entry 表的PHP类来调用Car,这个function_entry 表里就包含了每一个你想暴露给PHP的C++方法。这里所指的这个PHP类就是前面提到的vehicle.cc。更新一下vehicle.cc的内容,如下所示。

#include "php_vehicle.h"

zend_class_entry *car_ce;

PHP_METHOD(Car, __construct)
{
}
PHP_METHOD(Car, p_shift)
{
}
PHP_METHOD(Car, p_accelerate)
{
}
PHP_METHOD(Car, p_brake)
{
}
PHP_METHOD(Car, p_getCurrentSpeed)
{
}
PHP_METHOD(Car, p_getCurrentGear)
{
}

function_entry car_methods[] = {
    PHP_ME(Car,  __construct,     NULL, ZEND_ACC_PUBLIC | ZEND_ACC_CTOR)
    PHP_ME(Car,  p_shift,           NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Car,  p_accelerate,      NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Car,  p_brake,           NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Car,  p_getCurrentSpeed, NULL, ZEND_ACC_PUBLIC)
    PHP_ME(Car,  p_getCurrentGear,  NULL, ZEND_ACC_PUBLIC)
    {NULL, NULL, NULL}
};

PHP_MINIT_FUNCTION(vehicle)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Car", car_methods);
    car_ce = zend_register_internal_class(&ce TSRMLS_CC);
    return SUCCESS;
}

zend_module_entry vehicle_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
    STANDARD_MODULE_HEADER,
#endif
    PHP_VEHICLE_EXTNAME,
    NULL,        /* Functions */
    PHP_MINIT(vehicle),        /* MINIT */
    NULL,        /* MSHUTDOWN */
    NULL,        /* RINIT */
    NULL,        /* RSHUTDOWN */
    NULL,        /* MINFO */
#if ZEND_MODULE_API_NO >= 20010901
    PHP_VEHICLE_EXTVER,
#endif
    STANDARD_MODULE_PROPERTIES
};

#ifdef COMPILE_DL_VEHICLE
extern "C" {
ZEND_GET_MODULE(vehicle)
}
#endif

可以看到在PHP_METHOD中的函数名称与Car.cc类里写的函数名称并不需要一样,你可以定义任意自己觉得合适的函数名,为了表示方便,我这里一律添加前缀”p_”。这里每一个函数都还没有具体实现。

前面提到的function_entry表在上诉代码中可以看到由很多PHP_ME组成,每一个都代表了想要暴露给PHP用户空间的方法,最后一定以{NULL,NULL,NULL}表示结束。

现在我们已经有了C++的类,也有了PHP类,那么如何把两者联系起来呢?

首先你需要做的是定义一个zend_object_hander。然后定义一个结构,该结构包含了这个hander和C++的类。在PHP5中一个object其实就是一个hander,可以如下定义。

zend_object_handlers car_object_handlers;

struct car_object {
    zend_object std;
    Car *car;
};

该结构就会把C++的对象与zend的对象联系起来,然后你需要把下列代码添加到你的vehicle.cc文件中去,在PHP_METHOD方法之前。

void car_free_storage(void *object TSRMLS_DC)
{
    car_object *obj = (car_object *)object;
    delete obj->car; 

    zend_hash_destroy(obj->std.properties);
    FREE_HASHTABLE(obj->std.properties);

    efree(obj);
}

zend_object_value car_create_handler(zend_class_entry *type TSRMLS_DC)
{
    zval *tmp;
    zend_object_value retval;

    car_object *obj = (car_object *)emalloc(sizeof(car_object));
    memset(obj, 0, sizeof(car_object));
    obj->std.ce = type;

    ALLOC_HASHTABLE(obj->std.properties);
    zend_hash_init(obj->std.properties, 0, NULL, ZVAL_PTR_DTOR, 0);
    zend_hash_copy(obj->std.properties, &type->default_properties,
        (copy_ctor_func_t)zval_add_ref, (void *)&tmp, sizeof(zval *));

    retval.handle = zend_objects_store_put(obj, NULL,
        car_free_storage, NULL TSRMLS_CC);
    retval.handlers = &car_object_handlers;

    return retval;
}

然后更新一下PHP_MINIT_FUNCTION,这个函数前面提到过,就是PHP的扩展的模块初始化函数,如下所示:

PHP_MINIT_FUNCTION(vehicle)
{
    zend_class_entry ce;
    INIT_CLASS_ENTRY(ce, "Car", car_methods);
    car_ce = zend_register_internal_class(&ce TSRMLS_CC);
    car_ce->create_object = car_create_handler;
    memcpy(&car_object_handlers,
        zend_get_std_object_handlers(), sizeof(zend_object_handlers));
    car_object_handlers.clone_obj = NULL;
    return SUCCESS;
}

可能这里对“TSRMLS_DC”这个宏比较疑惑,它其实是 “ , void ***tsrm_ls”

然后编写一下构造函数

PHP_METHOD(Car, __construct)
{
    long maxGear;
    Car *car = NULL;
    zval *object = getThis();

    if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &maxGear) == FAILURE) {
        RETURN_NULL();
    }

    car = new Car(maxGear);
    car_object *obj = (car_object *)zend_object_store_get_object(object TSRMLS_CC);
    obj->car = car;
}

接着实现一下前面定义好的,但没有写内容的PHP_METHOD函数。为了演示的简洁,我们就实现两个函数。

PHP_METHOD(Car, p_accelerate)
{
    Car *car;
    car_object *obj = (car_object *)zend_object_store_get_object(
        getThis() TSRMLS_CC);
    car = obj->car;
    if (car != NULL) {
        car->accelerate();
    }
}

PHP_METHOD(Car, p_getCurrentSpeed)
{
    Car *car;
    car_object *obj = (car_object *)zend_object_store_get_object(
        getThis() TSRMLS_CC);
    car = obj->car;
    if (car != NULL) {
        RETURN_LONG(car->getCurrentSpeed());
    }
    RETURN_NULL();
}

记得添加所需要的头文件car.h。

然后重新 make & make install 吧,做完以后重启Apache服务器。

五. 在PHP脚本中调用扩展暴露出来的方法

编写一个PHP脚本,去测试是否成功。测试代码如下

[php]

<?php
// create a 5 gear car
$car = new Car(5);
print $car->p_getCurrentSpeed(); // prints ‘0’
$car->p_accelerate();
print $car->p_getCurrentSpeed(); // prints ‘5’
?>

[/php]

运行结果如果是0 和 5就说明成功了。

自此一个简单的PHP调用C++类已经实现了。

六. 在扩展中调用C/C++写的so库

假设现在我们有一个冒泡排序的.so库文件,现在我们想在PHP扩展中调用这个库文件里的函数

[cpp]
extern "C" void bubble_sort(int *arr, int len)
{
int tmp;

for(int i = 0; i < len – 1; i++)
for(int j = i + 1; j < len; j++)
{
if(arr[i] > arr[j])
{
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
[/cpp]

1. 更新car.h文件以及car.cc文件,添加调用so的代码,如下图所示。

Selection_201

car.cc 中代码如下图所示。

Selection_202

2. 更新vehicle.cc文件

在function_entry car_methods添加

PHP_ME(Car, p_bubble_sort_so, NULL, ZEND_ACC_PUBLIC)

然后添加一个PHP_METHOD(Car, p_bubble_sort_so),代码如下

[php]
PHP_METHOD(Car, p_bubble_sort_so)
{
zval *arr;
zval *len;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &arr, &len) == FAILURE)
{
return;
}
switch (Z_TYPE_P(arr)) {
case IS_NULL:
php_printf("NULL\n");
break;
case IS_BOOL:
php_printf("Boolean: %s\n", Z_LVAL_P(arr) ? "TRUE" : "FALSE");
break;
case IS_LONG:
php_printf("Long: %ld\n", Z_LVAL_P(arr));
break;
case IS_DOUBLE:
php_printf("Double: %f\n", Z_DVAL_P(arr));
break;
case IS_STRING:
php_printf("String: ");
PHPWRITE(Z_STRVAL_P(arr), Z_STRLEN_P(arr));
php_printf("\n");
break;
case IS_RESOURCE:
php_printf("Resource\n");
break;
case IS_ARRAY:
php_printf("Type is Array\n");
break;
case IS_OBJECT:
php_printf("Object\n");
break;
default:
php_printf("Unknown\n");
}

HashTable *arr_hash;
HashPosition pointer;
int array_count;
zval **data;

arr_hash = Z_ARRVAL_P(arr);
array_count = zend_hash_num_elements(arr_hash);
php_printf("The array passed contains %d elements\n", array_count);

// pass to the so
int *arr_so = (int*)malloc(sizeof(int) * array_count);
int len_so = array_count;
int i= 0;

for(zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
zend_hash_move_forward_ex(arr_hash, &pointer))
{
if (Z_TYPE_PP(data) == IS_LONG)
{
php_printf("%ld\t%ld\n", Z_LVAL_PP(data), (**data).value.lval);
arr_so[i++] = Z_LVAL_PP(data);
}
}

Car *car;
car_object *obj = (car_object*)zend_object_store_get_object(
getThis() TSRMLS_CC);
car = obj->car;
if (car != NULL)
{
car->bubble_sort(arr_so, len_so);
}
for(i = 0; i < len_so; i++)
{
php_printf("%d\n", arr_so[i]);
}
}

[/php]

这段代码还是比较容易懂的,可能会对zval这个变量不熟悉。其实所有用户定义的变量在PHP中都是用zval类型来表示的,它的内部表示如下

[c]
typedef pval zval;

typedef struct _zval_struct zval;

typedef union _zvalue_value {
long lval; /* long value */
double dval; /* double value */
struct {
char *val;
int len;
} str;
HashTable *ht; /* hash table value */
struct {
zend_class_entry *ce;
HashTable *properties;
} obj;
} zvalue_value;

struct _zval_struct {
/* Variable information */
zvalue_value value; /* value */
unsigned char type; /* active type */
unsigned char is_ref;
short refcount;
};
[/c]

然后再看上面那段代码。

switch里面是写给大家的说明程序,为了展示出如何判断传入的参数类型,可以不写略过。下面这句printf代码也是为了向大家展示 Z_LVAL_PP的用法,它其实就是对指针的指针取值,你也可以写成后面一种形式,即使**data的形式。不过建议使用第一种。

php_printf("%ld\t%ld\n", Z_LVAL_PP(data), (**data).value.lval);

接着看代码。首先我们用zend_parse_parameters接收穿过来的参数,这里我们传递的是数组。PHP中数组在zval里都是以hash表的形式储存的,所以我们这里声明一个hash表来接收这个参数。通过zend_hash_num_elements()则可以得到hash表元素的个数,也就是数组的个数。

然后我们将传递过来的参数通过for循环复制给我们的数组,最后通过调用car的bubble_sort函数进行排序。而bubble_sort调用的就是写好的so库函数了。

3. 编译、安装、测试

重新 make & make install,然后写一个PHP脚本测试一下,可以如下写:

Selection_203

然后运行该PHP脚本,如果浏览器里出现了排序后的数列0,1,2,3,4,5,则代表成功了。

That’s all,

Enjoy!

参考文献

[1]       Al-Qahtani, S. S., Arif, R., Guzman, L. F., Pietrzynski, P., & Tevoedjre, A. (2010). Comparing selected criteria of programming languages java, PHP, C++, perl, haskell, AspectJ, ruby, COBOL, bash scripts and scheme revision 1.0.Cornell University.

[2]       Sterling Hughes. Extending PHP [J]. Web Techniques, 2001, 6(1), 56 – 60.

[3]       PHP, http://www.php.net/manual/en/internals2.structure.php

[4]       Wikipedia, PHP, https://en.wikipedia.org/wiki/PHP

[5]       C++ dlopen mini HOWTO, http://www.isotton.com/devel/docs/C++-dlopen-mini-HOWTO/C++-dlopen-mini-HOWTO.html#theproblem

[6]       Extension Writing Part I: Introduction to PHP and Zend, http://devzone.zend.com/303/extension-writing-part-i-introduction-to-php-and-zend/

[7]       Wrapping C++ Classes in a PHP Extension, http://devzone.zend.com/1435/wrapping-c-classes-in-a-php-extension/

[8]       PHP Extensions – How and Why?, http://abhinavsingh.com/blog/2008/12/php-extensions-how-and-why/

[9]       How does PHP echo’s a “Hello World”? – Behind the scene, http://abhinavsingh.com/blog/2008/11/how-does-php-echos-a-hello-world-behind-the-scene/

[10]      Zend API:Zend_parse_parameters, http://zhaojunjie.blog.51cto.com/5475365/945302

[11]     【翻译】PHP扩展编写第二步:参数,数组,以及ZVAL, http://weizhifeng.wordpress.com/2011/07/20/extension-writing-part2-parameters-arrays-and-zvals/

[12]      php扩展 c,传参,传数组,zvar类型,全局变量, http://donbe.blog.163.com/blog/static/1380480212010225113531433/

[13]      A Close Look Into PHP Zval, http://blog.jjyao.me/blog/2013/03/17/a-close-look-into-php-zval/

[14]      convert_to_xxx系列函数帮助我们进行类型转换, http://9212219.i.sohu.com/blog/view/175458610.htm

[15]       深入理解php内核 编写扩展 II:参数、数组和ZVALs, http://blog.csdn.net/hguisu/article/details/7377235

[16]       Zend API:深入 PHP 内核, http://xiaobin.net/wp-content/uploads/2011/09/zend/

PHP与C++性能比较

PHP是速度很快的脚本语言,但是用了框架以后好像感觉挺慢的。于是猜测会不会PHP本身也不是很快。如果不是很快,能否采用PHP调用本地动态链接库的形式来提升速度。 于是有了下面的对比实验。

测试环境

1. 硬件环境如下图所示。

Selection_186

2. 软件环境

系统: Ubuntu 12.10

gcc版本:

Thread model: posix
gcc version 4.7.2 (Ubuntu/Linaro 4.7.2-2ubuntu1)

php版本:

PHP 5.3.22 (cli) (built: Mar 14 2013 20:37:16)
Copyright (c) 1997-2013 The PHP Group
Zend Engine v2.3.0, Copyright (c) 1998-2013 Zend Technologies

php开发环境: LAMP,所有安装包均是通过源码编译安装而成,编译过程中会自动根据本机各项参数进行最优配置。性能比apt-get install命令直接安装好。 关于以源码包方式搭建LAMP请参考文章:http://keping.me/linux-php-dev-by-source-style/

测试方法

由于冒泡排序在时间复杂度上相当稳定——O(n2),在最大程度上减少了数据可能带来的影响,故采取计算冒泡排序的运行时间的方法来进行此次实验。

对比测试分组

分组1: C++直接调用程序内的函数

分组2: C++调用打包好的动态链接库文件(.so文件,该文件也是自己写好并打包)

分组3: PHP直接调用程序内的函数

分组4: PHP调用打包好的动态链接库文件(.so文件,该文件也是自己写好并打包)

测试数据

数据总体规模为5,500,000个0~999的整数。

每一实验组,循环执行次数为30250,000,000,000次。

测试所用数据可以从以下地址下载:

http://keping.me/david-uploads/data/data_cpp.tar.gz

测试数据生成代码如下

[cpp]
#include
#include
#include

using std::cout;
using std::endl;
int main (int argc, char *argv[])
{
int scale;

scale = atoi(argv[1]);      // argv[1] will be 10000, 20000 … 90000, 100000

srand(unsigned(time(0)));   // use the UNIX timestamp as seeds
for(int i = 1; i <= scale; i++)
{
cout << rand() % 1000 << "\t";
if(i % 10 == 0) cout << endl;
}
}
[/cpp]

可以看到是0~999的整数,一共有10种测试数据的规模,分别放在十个不同目录下,如下图所示

Selection_159

目录名称代表数据的规模,如data_90000 表示数据规模为90,000个,同理data_100000表示数据规模为100,000个,依次类推。每一个目录下面包含10组测试数据,以data_100000举例,如下图所示

Selection_161

每个文件则包含100,000个0~999的随机数

Selection_163

数据文件打包下载地址:  地址1(包含数据以及 “分组1” 的测试结果)

以下是对比测试。

首先是 “分组1” 的测试

为了尽量保证算法一致,所以没有采用指针等数据结构,变量的交换也采用最原始的设置中间变量机制,关键代码如下:

[cpp]
void bubble(int *arr, int len)
{
int tmp;

for(int i = 0; i < len – 1; i++)
{
for(int j = i + 1; j < len; j++)
{
if(arr[i] > arr[j])
{
tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
}
}
}

[/cpp]

以下是data_100000目录下num_1数据文件的运行结果文件result_1的部分截图, 用时13.74秒

Selection_164

第一行输出记录的是每一秒中所含的时钟数;第二行记录的是排序开始之前程序已经运行的时钟数;第三行记录的是排序结束以后程序运行的时钟数;第四行则可以根据前面的数据得出本次排序运行的时间,以秒记,保留两位小数便于对比。

下表则是data_100000目录下的所有数据文件(num_1 至 num_9)的运行结果

cpp_y

可以看到,对于100,000级别的数据,进行冒泡排序, “分组1” 用了大约13.8秒左右的时间。下面将给出对于不同数据规模(data_10000~data_100000)的平均测试数据

cpp_average

其中X轴方向上表示数据规模为 10K(即使10,000),20K,,,100K的测试数据。
其中Y轴方向上表示测试的平均时间,对每一数据规模均有10个测试数据,此处为平均值。
从图中可以看出,结果基本符合 y=x的函数曲线,冒泡排序还是相当稳定的。。。

分组2的测试 

目录结构如下,其中libbubble.so文件即为冒泡排序打包成动态链接库。将会在main函数中调用。分组2与分组1的主要区别也在此,分组1中是在main里调用本地的冒泡排序函数,而分组2则是通过调用.so文件中的冒泡排序函数。

Selection_180

运行的测试数据来源与“测试分组1”完全一致,测试结果储存在相应目录下,如下图所示。

Selection_179

将测试结果的数据处理以后,得到下图。

cpp_so_average

速度竟然比C++调用自己内部的函数更快,估计是打包成动态链接库的时候,编译器已经做了优化。

分组3的测试

接下来是PHP执行冒泡排序的测试。

目录结构如下图所示,将调用PHP写的冒泡排序函数进行数据测试。 关

Selection_181

关键代码如下

[php]
for($i = 0; $i < $len – 1; $i++)
{
for($j = $i + 1; $j < $len; $j++)
{
if($arr[$i] > $arr[$j])
{
$tmp = $arr[$i];
$arr[$i] = $arr[$j];
$arr[$j] = $tmp;
}
}
}
[/php]

可以看到,为了保证算法上的一致性,代码结构与实验分组一是一样的。

由于实在是比较慢,所以写了一个shell脚本来执行,shell脚本如下图所示(未完整截图)

Selection_182

将分组3的测试结果的数据处理以后,得到下图。

php_average

可以看到在相同数据规模下,分组3的运行时间要远远大于分组1以及分组2,并且随着数据规模的上升,总体呈现出上升趋势。

分组4的测试

分组4与分组3的区别在于:分组4则是调用以C++编写的动态链接库中的冒泡排序算法,并将该动态链接库以扩展的形势添加到了PHP系统中;分组3则是直接调用PHP写的冒泡排序算法。

将分组4的测试结果的数据处理以后,得到下图。

php_so_average

可以看到虽然还是PHP进行冒泡排序算法,但是效率得到了极大的提高。

最后是对比图

首先看一下数据表

QQ图片20130620232930

其中10000,20000, …, 100000代表数据规模,表中数据为执行的秒数。可以看到分组3,也就是PHP分组,大约是其他分组的100倍至170倍时间。以至于如果不采取log函数,将完全看不到其他三个分组的图。下图是对上表每个数据取以10为底的对数以后,得到的数值描绘的图。

QQ图片20130620230529

大致结论,PHP执行速度很慢,如果实在要采取PHP的方式,请采用将C/C++编写的动态链接库,经过Zend API的转化添加成PHP扩展,PHP再调用该扩展的形势,性能如分组4所示,是非常快的。

参考文献

[1]       Al-Qahtani, S. S., Arif, R., Guzman, L. F., Pietrzynski, P., & Tevoedjre, A. (2010). Comparing selected criteria of programming languages java, PHP, C++, perl, haskell, AspectJ, ruby, COBOL, bash scripts and scheme revision 1.0.Cornell University.

[2]       Sterling Hughes. Extending PHP [J]. Web Techniques, 2001, 6(1), 56 – 60.

[3]       PHP, http://www.php.net/manual/en/internals2.structure.php

[4]       Wikipedia, PHP, https://en.wikipedia.org/wiki/PHP

C++调用自己的.so

由于一些原因,需要在C++中动态加载自己写的动态链接库(.so)文件。网络上的资源挺多,我也看了不少,参考最多的是下面这三篇

1. dlopen加载c++ 函数及类: http://blog.csdn.net/lwj1396/article/details/5204484

2. 上一篇的英文版本: http://www.isotton.com/devel/docs/C++-dlopen-mini-HOWTO/C++-dlopen-mini-HOWTO.html#theproblem

3. 动态调用动态库方法 .so: http://blog.csdn.net/lbmygf/article/details/7401862

再说一说自己的心得吧。

首先介绍一下动态库和静态库之间的区别

静态库是指编译连接时,把库文件的代码全部加入到可执行文件中,所以生成的文件较大,但运行时,就不再需要库文件了。即,程序与静态库编译链接后,即使删除静态库文件,程序也可正常执行。

动态库正好相反,在编译链接时,没有把库文件的代码加入到可执行文件中,所以生成的文件较小,但运行时,仍需要加载库文件。即,程序只在执行启动时才加载动态库,如果删除动态库文件,程序将会因为无法读取动态库而产生异常。

那么如何调用动态库?如何在C语言下,其实是很简单的(调用dlopen、dlsym和dlclose就够了),但对C++来说,情况稍微复杂。动态加载一个C++库的困 难一部分是因为C++的name mangling

然后从介绍Name Mangling开始

在每个C++程序(或库、目标文件)中,所有非静态(non-static)函数在二进制文件中都是以“符号(symbol)”形式出现的。这些符号都是唯一的字符串,从而把各个函数在程序、库、目标文件中区分开来。
在C中,符号名正是函数名:strcpy函数的符号名就是“strcpy”,等等。这可能是因为两个非静态函数的名字一定各不相同的缘故。
而C++允许重载(不同的函数有相同的名字但不同的参数),并且有很多C所没有的特性──比如类、成员函数、异常说明──几乎不可能直接用函数名作符 号名。为了解决这个问题,C++采用了所谓的name mangling。它把函数名和一些信息(如参数数量和大小)杂糅在一起,改造成奇形怪状,只有编译器才懂的符号名。例如,被mangle后的foo可能 看起来像foo@4%6^,或者,符号名里头甚至不包括“foo”。
其中一个问题是,C++标准(目前是[ISO14882])并没有定义名字必须如何被mangle,所以每个编译器都按自己的方式来进行name mangling。有些编译器甚至在不同版本间更换mangling算法(尤其是g++ 2.x和3.x)。即使您搞清楚了您的编译器到底怎么进行mangling的,从而可以用dlsym调用函数了,但可能仅仅限于您手头的这个编译器而已, 而无法在下一版编译器下工作。

解决方案 extern “C”

C++有个特定的关键字用来声明采用C binding的函数:extern “C” 。 用 extern “C”声明的函数将使用函数名作符号名,就像C函数一样。因此,只有非成员函数才能被声明为extern “C”,并且不能被重载。尽管限制多多,extern “C”函数还是非常有用,因为它们可以象C函数一样被dlopen动态加载。冠以extern “C”限定符后,并不意味着函数中无法使用C++代码了,相反,它仍然是一个完全的C++函数,可以使用任何C++特性和各种类型的参数。

示例程序1. 加载简单函数

目录结构
Selection_141
示例程序1在test1目录下,这个例子也主要是参考第一篇博客写的。有一些修改。

main.cpp的代码如下

[cpp]
//———-
//main.cpp:
//———-
#include
#include

int main() {
using std::cout;
using std::cerr;

cout << "C++ dlopen demo\n\n";

// open the library
cout << "Opening hello.so…\n";
void* handle = dlopen("libhello.so", RTLD_LAZY);

if (!handle) {
cerr << "Cannot open library: " << dlerror() << ‘\n’;
return 1;
}

// load the symbol
cout << "Loading symbol hello…\n";
typedef void (*hello_t)();

// reset errors
dlerror();
hello_t hello = (hello_t) dlsym(handle, "hello");
const char *dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol ‘hello’: " << dlsym_error <<
‘\n’;
dlclose(handle);
return 1;
}

// use it to do the calculation
cout << "Calling hello…\n";
hello();

// load the symbol
cout << "Loading symbol add…\n";
typedef int (*add_t)(int, int);

// reset errors
dlerror();
add_t add = (add_t) dlsym(handle, "add");
if (dlsym_error) {
cerr << "Cannot load symbol ‘add’: " << dlsym_error <<
‘\n’;
dlclose(handle);
return 1;
}

// use it to do the calculation
cout << "Calling the add()…\n";
cout << add(98, 99) << " is the result\n";

// close the library
cout << "Closing library…\n";
dlclose(handle);
}

[/cpp]

然后是hello.cpp的代码,一会儿将会把这个源文件编译打包成.so文件。

[cpp]
//———-
// hello.cpp:
//———-
#include

extern "C" void hello() {
std::cout << "hello" << ‘\n’;
}

extern "C" int add(int a, int b){
return a + b;
}
[/cpp]

介绍一下上面用到的接口函数

1)       dlopen

函数原型:void *dlopen(const char *libname,int flag);

功能描述:dlopen必须在dlerror,dlsym和dlclose之前调用,表示要将库装载到内存,准备使用。如果要装载的库依赖于其它库,必须首先装载依赖库。如果dlopen操作失败,返回NULL值;如果库已经被装载过,则dlopen会返回同样的句柄。

参数中的libname一般是库的全路径,这样dlopen会直接装载该文件;如果只是指定了库名称,在dlopen会按照下面的机制去搜寻:

a.根据环境变量LD_LIBRARY_PATH查找

b.根据/etc/ld.so.cache查找

c.查找依次在/lib和/usr/lib目录查找。

flag参数表示处理未定义函数的方式,可以使用RTLD_LAZY或RTLD_NOW。RTLD_LAZY表示暂时不去处理未定义函数,先把库装载到内存,等用到没定义的函数再说;RTLD_NOW表示马上检查是否存在未定义的函数,若存在,则dlopen以失败告终。

2)       dlerror

函数原型:char *dlerror(void);

功能描述:dlerror可以获得最近一次dlopen,dlsym或dlclose操作的错误信息,返回NULL表示无错误。dlerror在返回错误信息的同时,也会清除错误信息。

3)       dlsym

函数原型:void *dlsym(void *handle,const char *symbol);

功能描述:在dlopen之后,库被装载到内存。dlsym可以获得指定函数(symbol)在内存中的位置(指针)。如果找不到指定函数,则dlsym会返回NULL值。但判断函数是否存在最好的方法是使用dlerror函数,

4)       dlclose

函数原型:int dlclose(void *);

功能描述:将已经装载的库句柄减一,如果句柄减至零,则该库会被卸载。如果存在析构函数,则在dlclose之后,析构函数会被调用。

好了,现在来编译打包,命令如下:

$ g++ -shared -fPIC -o libhello.so hello.cpp
$ g++ main.cpp -ldl

在上面dlopen函数中,看到我们传的第一个参数并没有指定路径,只给出了库的名称。那是因为已经在环境变量LD_LIBRARY_PATH中指定了 ./ 目录,如下图所示。
Selection_142

如果你想放在其他目录,修改该环境变量即可。

运行a.out,输入结果如下图所示

Selection_143

示例程序2. 加载类

几篇文章的意思基本都是这样:

加载类有点困难,因为我们需要类的一个实例,而不仅仅是一个函数指针。我们无法通过new来创建类的实例,因为类不是在可执行文件(这里即指由main.cpp编译成的a.out文件)中定义的,况且(有时候)我们连它的名字都不知道。
解决方案是:利用多态性! 我们在可执行文件中定义一个带虚成员函数的接口基类,而在模块中定义派生实现类。通常来说,接口类是抽象的(如果一个类含有虚函数,那它就是抽象的)。
因为动态加载类往往用于实现插件,这意味着必须提供一个清晰定义的接口──我们将定义一个接口类和派生实现类。
接下来,在模块中,我们会定义两个附加的helper函数,就是众所周知的“类工厂函数(class factory functions)(译者注:或称对象工厂函数)”。其中一个函数创建一个类实例,并返回其指针; 另一个函数则用以销毁该指针。这两个函数都以extern “C”来限定修饰。
为了使用模块中的类,我们用dlsym像示例1中加载hello函数那样加载这两个函数,然后我们就可以随心所欲地创建和销毁实例了。

———————————-polygon.hpp—————————————————————

[cpp]
//———-
//polygon.hpp:
//———-
#ifndef POLYGON_HPP
#define POLYGON_HPP

class polygon {
protected:
double side_length_;

public:
polygon()
: side_length_(0) {}

virtual ~polygon() {}

void set_side_length(double side_length) {
side_length_ = side_length;
}

virtual double area() const = 0;
};

// the types of the class factories
typedef polygon* create_t();
typedef void destroy_t(polygon*);

#endif

[/cpp]

这里把我小小的纠结了一下,函数名后面加 const=0是什么意思呢?两个typedef好像是要定义函数指针,但又不是在定义函数指针。google了一阵以后,大致都了解了。

首先,const 和 =0 没有关系,要分开理解
成员函数后面用 const 修饰,通俗的理解就是在这个函数内不能修改类的成员变量,除非那个成员变量是 mutable 的

=0表示这个成员函数是纯虚函数,也就是它可以没有定义,只有接口,由它的继承类具体定义它的行为,当然,你也可以给它定义缺省的函数体
一个类里如果包含 =0 的纯虚函数,那么这个类就是一个抽象类,它不能具体实例化(不能创建它的对象),而只能由它去派生子类

然后是两个typedef,这里其实是typedef了两个函数类型,在后面
create_t* create_triangle = (create_t*) dlsym(triangle, “create”);
的时候是加了指针符号的。
对比一下示例1中的typedef,示例1中是直接定义的函数指针,所以在dlsym这里就不用再添加指针符号。
[cpp]
typedef int (*add_t)(int, int);
add_t add = (add_t) dlsym(handle, "add";
[/cpp]

———————————————-main.cpp———————————————-

[cpp]
//———-
//main.cpp:
//———-
#include "polygon.hpp"
#include
#include

int main() {
using std::cout;
using std::cerr;

// load the triangle library
void* triangle = dlopen("triangle.so", RTLD_LAZY);
if (!triangle) {
cerr << "Cannot load library: " << dlerror() << ‘\n’;
return 1;
}

// reset errors
dlerror();

// load the symbols
create_t* create_triangle = (create_t*) dlsym(triangle, "create");
const char* dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol create: " << dlsym_error << ‘\n’;
return 1;
}

destroy_t* destroy_triangle = (destroy_t*) dlsym(triangle, "destroy");
dlsym_error = dlerror();
if (dlsym_error) {
cerr << "Cannot load symbol destroy: " << dlsym_error << ‘\n’;
return 1;
}

// create an instance of the class
polygon* poly = create_triangle();

// use the class
poly->set_side_length(10);
cout << "The area is: " << poly->area() << ‘\n’;

// destroy the class
destroy_triangle(poly);

// unload the triangle library
dlclose(triangle);
}
[/cpp]

———————————————-triangle.cpp———————————————-

[cpp]
//———-
//triangle.cpp:
//———-
#include "polygon.hpp"
#include

class triangle : public polygon {
public:
virtual double area() const {
return side_length_ * side_length_ * sqrt(3) / 2;
}
};

// the class factories
extern "C" polygon* create() {
return new triangle;
}

extern "C" void destroy(polygon* p) {
delete p;
}

[/cpp]

go on..

好了,现在来编译打包,命令如下:

$ g++ -shared -fPIC -o triangle.so triangle.cpp
$ g++ main.cpp -ldl
$ ./a.out

结果如下图所示

Selection_144

调用类成功。恩,大致就是这些啦。

C++ main:处理命令行参数

继续学习C++,,,

给main函数传递实参例子,自己写了一个小程序来测试一下,如下图所示。

这样,当我用命令

g++ main_with_arg.cpp -o main_with_arg

编译,然后

./main_with_arg david -love i don\’t know

运行的时候,屏幕就会输出

说明第一个argc参数会记录传递给main的参数,然后第二个参数argv是一个字符串数组,会把每一个命令字符串都记录下来,当然,需要用到转义字符‘\’的时候还是要使用。

这里顺便测试了一下我的机器上,size_t的大小,看来跟int一样。

C++ 复合赋值操作符性能

继续学习C++。

看到+=, -=, *=, /=以及其他复合赋值操作符号的时候,虽然以前学C的时候老师就讲过:这种方式的速度比相应的 A = A + B, A = A * B要快,因为对于前一种方式,左操作数只取了一次。

这不,正好前几天用到了clock()这个函数,那写个demo来看看到底快多少,于是有了下图所示的代码。

用三个变量记录了一下从程序开始,到调用clock()时,这段期间的时钟周期数。

对于常量“CLOCKS_PER_SEC”则是指每一秒的时钟周期个数,POSIX系统一般都是1,000,000

程序对两种方法分别执行了20亿次的加法运算记时,结果如下图所示,直接打印的是时钟周期数,而不是秒,如果要得到秒,用这个数除以CLOCKS_PER_SEC即可。

从结果看出,两种方式,20亿次加法运算,时间都是4.9X秒,差别非常小。并且,有可能第一种方式快,有可能第二种方式快,平均下来1:1。也有可能是哪里疏忽了,导致结果不对。

单从这次结果来看,无甚差别。

 

goodbye.