涵曦的博客 分享编程的点滴
[原创]单消息多处理函数实现
Posted by 涵曦 at 2013-08-26 with tags 游戏, 协议处理, Lua
上星期跟同事讨论到现在的游戏框架的网络协议处理模块是只支持单消息单处理函数的.就是接受到一个协议只会进入一个处理函数.这对于服务端来说挺正常的.实现也是很简便的,只需要绑定协议Id和handler函数即可.可客户端就没有这么简单了.可能客户端收到一个消息后需要执行多个模块的处理.而各个模块又可能不是同一个人做的.所以就出现了经常需要在一个模块的处理函数中调用另一个模块的某些处理函数.比如我添加了一个新的功能,然后实现了之后需要告诉之前某些实现的人调用一下我的函数.这样就造成遗漏的情况和增加工作负担.
实现单消息多处理函数后,我只需要知道需要添加一个处理函数,自己实现完后注册一下而不需要通知其他同事来调用我的接口.如果涉及到优先执行关系只需在注册的时候写好就行了.
[原创]lua嵌入C中实例
Posted by 涵曦 at 2013-07-26 with tags C/C++, lua
目录结构
编译出来为我所需的文件,以及代码文件
├── bin │ ├── test │ └── test.lua ├── build ├── CMakeLists.txt ├── lua │ ├── include │ │ ├── lauxlib.h │ │ ├── lua.h │ │ ├── lua.hpp │ │ ├── luaconf.h │ │ └── lualib.h │ └── lib │ └── liblua.a └── src └── main.cpp
源代码
//main.cpp #include <stdio.h> extern "C"{ #include <lua.h> #include <lualib.h> #include <lauxlib.h> } int main(int argc, char ** argv) { lua_State * L = luaL_newstate() ; //创建lua运行环境 if (!L) { printf("error for luaL_newstate\n"); } luaopen_base(L); // 加载Lua基本库 luaL_openlibs(L); // 加载Lua通用扩展库 int ret = luaL_loadfile(L,"test.lua") ; //加载lua脚本文件 if (ret) { printf("error for luaL_loadfile\n"); } ret = lua_pcall(L,0,0,0) ; if (ret) { printf("error for lua_pcall\n"); } lua_getglobal(L,"printmsg"); ret = lua_pcall(L,0,0,0); if (ret) { printf("error for lua_pcall\n"); } return 0; }
-- test.lua function printmsg() print("hello word") end
#CMakeLists.txt PROJECT(test) CMAKE_MINIMUM_REQUIRED(VERSION 2.6) set(EXECUTABLE_OUTPUT_PATH ../bin) ## libs link_directories(/usr/local/lib) link_directories(/usr/lib) ## includes include_directories(/usr/local/include) include_directories(./src SRC_DIR) include_directories(./lua/include) aux_source_directory(./src SRC_DIR) ## apps add_executable(test ${SRC_DIR} ) ## link libs target_link_libraries(test lua m dl)
编译运行
$ cd build $ cmake .. $ make $ cd ../bin $ ./test
[原创]构造函数的语意
Posted by 涵曦 at 2013-06-21 with tags C++, 对象模型, 读书笔记, NRV
默认构造函数
默认构造函数在编译器需要的时候被编译器产生出来。
编译器会自动生成的四种有用的默认构造函数:
带有default constructor 的member class object(组合结构)
如果一个类没有任何构造函数,而它含有一个对象成员,且该成员有默认构造函数,那么编译器需要为该类合成一个默认构造函数,不过合成操作只在构造函数真正需要被调用的时候才会发生。 被合成的默认构造函数只满足编译器的需要,而不是程序的需要。如:
class Foo {public: Foo(), Foo(int) ...}; class Bar {public: Foo foo; char* str;} // 组合 void foo_bar() { Bar bar; // Bar::foo成员必须在此处初始化 if (str) {} ... }
被合成的默认构造函数看起来可能时这样的:
inline Bar::Bar() { //C++伪代码 foo.Foo:Foo(); }
生成的代码中没有提供str的初始化操作,需要程序员自己完成。如果程序员定义构造函数如下:
Bar::Bar() { str=0;}
编译的做法是:如果类内含一个或多个对象成员,那么类的每一个构造函数必须调用每一个对象成员的默认构造函数。编译器会扩张已存在的构造函数,在user code之前安插一些代码。上面的构造函数会扩张成下面的样子:
Bar::Bar() { foo.Foo:Foo(); str=0; }
如果有多个对象成员都要求构造初始化操作,C++语言要求以对象在类中的声明次序来调用各个构造函数。
带有default constructor 的base class
类似于第一种组合情形。如果基类含有默认构造函数,而派生类没有,则编译器会自动为派生类合成一个默认构造函数用于完成对基类的初始化。 如果派生自多个基类,则按声明次序依次调用。 如果派生类中包含其他类对象成员,则在所有的base class Constructor 都被调用之后调用类对象成员的构造函数。
带有一个virtual function 的 class
编译器会做下面两个扩张:
-
产生一个vtbl,里面存放类的所有虚函数的地址。
-
在每个类对象中,添加一个额外的vptr指针,内含一个相关的类vtbl的地址。
带有一个virtual base class 的 class
virtual base class 的实现法在不同的编译器之间有极大差异,然而,每种实现法的共通点是:必须让virtual base class在其每一个派生类对象中的位置能在执行期准备妥当。 一般在派生类对象中安插一个指向virtual base classes的指针,所有经由引用或指针来存取一个virtual base class 的操作都可以通过相关指针完成。
总结: 在合成的默认构造函数中,只有base class subobjects和member class objects会被初始化,所以其他的非静态成员数据,如整数,整数指针,整数数组等等都不会被初始化,这些初始化操作需要程序员自己完成。
拷贝构造函数
有三种情况会以一个对象的内容作为另一个对象的初值:
-
对一个对象做明确的初始化操作
-
当对象被当做参数交给某个函数时
-
当函数传回一个类对象时
不使用逐位拷贝的情况
类中含有类对象成员,而该成员的类声明有一个拷贝构造函数
class Word { public: Word(const String& ); ~Word(); // ... private: int cnt; String str; }; class String { public: String(const char*); String(const String&); ~String(); // ... }; Word noun("book"); void foo() { Word verb = noun; // ... }
在这中情况下,编译器会合成出一个拷贝构造函数以便调用member class String object的拷贝构造函数:
inline Word:Word( const Word& wd) { str.String::String(wd.str); cnt = wd.cnt; }
在这被合成出来的拷贝构造函数中,如整数,指针,数组等等的nonclass members 也都会被复制。
类继承子一个基类而基类存在一个拷贝构造函数
这里不举例了,和第一种情况类似。
类声明了一个或多个虚函数
class ZooAnimal { public: ZooAnimal(); virtual ~ZooAnimal(); virtual void animate(); // .. private: // data ... }; class Bear: public ZooAnimal { public: Bear(); void animate(); virtual void dance(); // ... private: // data ... }; Bear yogi; ZooAnimal franny = yogi; // 切割行为
当一个基类对象以派生类的对象做初始化操作时,其bptr复制操作也必须保证安全,合成出来的 ZooAnimal 拷贝构造函数会明确设定对象的vptr指向 ZooAnimal 的 vtbl。
类派生子一个继承串链,其中有一个或多个虚基类
class Raccoon : public virtual ZooAnimal { }; class RedPanda : public Raccoon { }; Raccoon rocky; Raccoon little_critter = rocky; // 简单的逐位拷贝足以完成 RedPanda red; Raccoon little_critter = red; // 需要编译器完成vbcPtr(虚基类指针)的初始化
为了正确的完成 little_critter 的初值设定,编译器必须合成一个拷贝构造函数,安插一些代码以设定vbcPtr的处置。
程序转换
明确的初始化操作
X x0; void foo_bar() { X x1(x0); X x2=x0; X x3 = X(x0); } // 可能的程序转换 void foo_bar() { X x1; X x2; X x3; x1.X::X(x0); x2.X::X(x0); x3.X::X(x0); }
####参数的初始化
void foo(X x0); X xx; foo(xx); // 可能转换 void foo(X& x0); X _tmp0; _tmp0.X::X(xx); foo(_tmp0);
返回值的初始化
X bar() { X xx; // ... return xx; } X xx = bar(); bar().memfunc(); X ( *pf )(); pf = bar; // 有可能的转换 void bar(X& _ret) { X xx; xx.X::X(); // ... _ret.X::X(xx); return; } X xx; bar(xx); X _tmp0; (bar(_tmp0),_tmp0).memfunc(); void ( *pf ) (X&); pf=bar;
在使用者层面做优化
X bar(const T &y, const T &z) { X xx; // 以y和z来处理xx return xx; }
定义另一个构造函数,可以直接计算xx的值:
X bar(const T &y, const T &z) { return X(y,z); }
C++伪代码
void bar (X & _ret,const T &y, const T &z) { _ret.X::X(y,z); return; }
在编译器层面做优化(NRV-named return value)
X bar() { X xx; // 处理xx return xx; } void bar(X & _ret) { _ret.X:X(); // 直接处理_ret return; }
总结:当一个函数以传值(by value)的方式返回一个类对象时,而该class有一个拷贝构造函数(不论时合成的还是明确定义的)时,这将导致深奥的程序转化(不论时函数定义还是使用),此外编译器也将拷贝构造函数的调用做优化,一个额外的第一参数(数值被直接存放其中)取代NRV。
成员初始化列表
什么情况下必须使用成员初始化列表:
-
当初始化一个引用成员时。
-
当初始化一个const成员时。
-
当调用一个基类的构造函数,而它拥有一组参数时。
-
当调用一个成员对象的构造函数,而它拥有一组参数时。
编译器生成的初始化代码的次序不是按照成员初始化列表的顺序,而是有类中成员声明次序决定。编译器生成的初始化代码会放在user code之前。生成的基类构造函数的调用代码在生成的成员初始化代码之前。
class FooBar:public X { int _fval; public: int fval() {return _fval); FooBar(int val):_fval(val),X(fval()){} }; // 会生成如下代码: FooBar::FooBar(int val) { X::X(this, this->fval()); _fval = val; } // 想要得到正确的结果,应该这样做: class FooBar:public X { int _fval; public: int fval() {return _fval); FooBar(int val):_fval(val),X(val){} }; // 会生成如下代码: FooBar::FooBar(int val) { X::X(this,val); _fval = val; }
简单的说,编译器会对初始化列表一一处理并可能重新排序,以反映出成员的声明次序,它会安插一些代码到构造函数体内,并置于任何user code之前。
[原创]C++对象模型概述
Posted by 涵曦 at 2013-06-19 with tags C++, 对象模型, 读书笔记
###C++额外负担 C++在布局以及存取时间上主要的额外负担是由virtual引起,包括:
- virtual function机制,用以支持一个有效率的执行期绑定(runtime binding)
- virtual base class,用以实现多次出现在继承体系中的base class,有一个单一而被共享的实体
###3种对象模型 ####简单对象模型 一个对象是一系列的slots,每一个slots指向一个members,members按其声明次序,各被指定一个slot。每个成员数据或成员函数都有自己的一个slot。 该观念用在C++的指向成员的指针(point-to-member)观念之中。
####表格驱动对象模型 对象本身内含一个指向data member table 的指针和一个指向member function table的指针,member function table 是一系列的slots,每一个slot指向一个member function; data member table 直接含有data本身。
- 缺点:时间和空间效率低
- 优点:有弹性
member function table 这个观念用在virtual function。
####C++对象模型 非静态成员数据(nonstatic data members)被配置在每一个对象(class object)之内,静态成员数据(static data members)则被存储在对象之外。静态(static)和非静态成员函数(nonstatic function members)被放在所有的对象之外。虚函数则根据下面两个步骤实现:
1.每个类产生一堆指向虚函数的指针,放在vtbl(virtual table)表中。
2.每个对象被添加一个指针,指向相关的vtbl表,通常这个指针被称为vptr,vptr的设定(setting)和重置(resetting)都由每个类的构造函数,析构函数和赋值运算符自动完成,每个类所关联的type_info对象(用于支持RTTI)也经由vtbl被指出来,通常放在表中第一个slot处。
-
缺点:如果应用程序代码没有改变,但所用到的对象的类非静态成员有所修改(增加,移除或更改),那么应用程序的代码同样需要重新编译。
-
优点:空间和存取时间的效率高。
###struct 为兼容c而存在的struct关键字,将C与C++组合在一起的做法: 1.从C struct 中派生C++部分:
struct C_point { ... }; class Point : public C_point { ... };
于是C和C++两种用法都可以得到支持:
extern void draw_line( Point, Point); extern "C" void draw_rect(C_point, C_point); draw_line(Point(0,0),Point(100,100)); draw_rect(Point(0,0),Point(100,100));
2.第一种方法已不再推荐,而使用组合代替继承:
struct C_point { ... }; class Point { public: operator C_point() { return _c_point;} // ... private: C_point _c_point; // ... };
故,C struct在C++中的一个合理用途,是你要传递“一个复杂的class object的全部或部分”到某个C函数中去,struct声明可以将数据封装起来,并保证拥有与C兼容的空间布局。然而这项保证只在组合(composition)的情况下存在。如果是继承而不是组合,编译器会决定是否应该有额外的data members 被安插在base struct subobject之中。
###三种程序设计典范(programing paradigms)
程序模型
函数式编程,就像C一样。
抽象数据类型模型(abstract data type model,ADT)
抽象是指和一组表达式一起提供,而其运算定义仍然隐而未明。封装类型使其像基本类型一样使用,如string类。
面向对象模型(object-oriented model, OO)
一些相关类型通过一个抽象的基类(共通接口)被封装起来。只有通过指针和引用的间接处理,才支持OO程序设计所需要的多态性质。
###C++通过下列方法支持多态:
- 经由一组隐含的转化操作,例如把一个derived class指针转化为一个指向其public base type的指针:
shape *ps = new circle();
- 经由virtual function机制:
ps->rotate();
- 经由dynamic_cast和typeid运算符:
if (circle *pc = dynamic_cast<circle* >(ps) ) ...
###对象的内存大小
- 非静态成员数据的总和大小。
- 加上由于alignment(将数值调整到某数的倍数)的需求而填补上去的空间(存在members之间或集合体末尾)。
- 加上为支持virtual而由内部产生的任何额外负担。
C++通过class的指针和引用来支持多态,这种程序设计风格就称为面向对象。 在弹性(OO)和效率(OB)之间存在取舍,一个人在能够有效选择其一之前,必须先清楚两者的行为和应用领域的需求。
[原创]Qt基本编程
Posted by 涵曦 at 2013-04-22 with tags Qt, 入门教程, Linux
计划了好久想要写个入门教程的,今天才开始写。
###环境搭建(以ubuntu12.04lts 为例)
- 执行下面命令:
$ sudo apt-get install qt4-dev-tools qt4-doc qt4-qtconfig qt4-demos qt4-designer
- 当代码中含有
#include <QSqlDatabase>
需要用到mysql数据库需要执行下面命令安装(自带sqlite驱动),
$ sudo apt-get install libqt4-sql-mysql
###来一个helloworld程序
- 建立工程目录:我的目录在/home/hanxi/tmp/
$ mkdir -p helloworld/src helloworld/ui helloworld/build helloworld/bin $ touch helloworld/src/main.cpp helloworld/src/mainwindowimpl.h helloworld/src/mainwindowimpl.cpp
- 打开qt designer(qt 设计器),新建一个helloworld.ui文件。(菜单:文件->新建,弹出新建窗体对话框,点击创建,保存为helloworld/ui/helloworld.ui)。 生成后的目录格式是这样的
helloworld ├── bin │ └── helloworld ├── build ├── CMakeLists.txt ├── src │ ├── main.cpp │ ├── mainwindowimpl.cpp │ └── mainwindowimpl.h └── ui └── helloworld.ui
- 编辑src/main.cpp
#include <QApplication> #include "mainwindowimpl.h" int main(int argc, char ** argv) { QApplication app(argc, argv); MainWindowImpl win; win.show(); app.connect( &app, SIGNAL( lastWindowClosed() ), &app, SLOT( quit() ) ); app.exec(); return 0; }
- 编辑src/mianwindowimpl.h
#ifndef MAINWINDOWIMPL_H #define MAINWINDOWIMPL_H #include <QDialog> #include "ui_helloworld.h" class MainWindowImpl : public QDialog, public Ui::Dialog { Q_OBJECT public: MainWindowImpl( QWidget * parent = 0, Qt::WFlags f = 0 ); virtual ~MainWindowImpl() {}; }; #endif
- 编辑src/MainWindowImpl.cpp
#include "mainwindowimpl.h" MainWindowImpl::MainWindowImpl( QWidget * parent, Qt::WFlags f) : QDialog(parent, f) { setupUi(this); }
- 编辑helloworld/CMakeLists.txt
PROJECT(helloworld) CMAKE_MINIMUM_REQUIRED(VERSION 2.6) FIND_PACKAGE(Qt4 REQUIRED) ######################################### set(EXECUTABLE_OUTPUT_PATH ../bin) set(LIBRARY_OUTPUT_PATH ../libs) add_definitions (${QT_DEFINITIONS}) # 使用net模块 # set(QT_USE_QTNETWORK true) ## libs link_directories(/usr/local/lib) link_directories(/usr/lib) ## includes include(${QT_USE_FILE}) # 包含文件夹:${CMAKE_CURRENT_BINARY_DIR}和${CMAKE_CURRENT_SOURCE_DIR}用于确保moc产生的文件能正确编译。 include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) include_directories(./src SRC_DIR) ## sources aux_source_directory(./src SRC_DIR) # 对于含有Q_OBJECT一类宏的代码(主要是头文件),需要列出以备交给moc处理 set(qt_HDRS ./src/mainwindowimpl.h) # UI文件 set(qt_UI ./ui/helloworld.ui) ## QT qt4_wrap_cpp(qt_MOCS ${qt_HDRS}) qt4_wrap_ui(qt_UIS ${qt_UI}) ## apps add_executable(helloworld ${SRC_DIR} ${qt_MOCS} ${qt_UIS} ) ## link libs target_link_libraries(helloworld ${QT_LIBRARIES})
- 进入build目录,执行
$ cmake .. $ make $ ../bin/helloworld
###使用qt助手
- 类似于windows下用过vc/vs的msdn,java的jdk-api文档
- 使用qt助手的搜索功能
- 配合qt设计器,设计好界面之后需要知道控件的属性什么的就在qt助手里面搜索
- 例如:
- 我需要知道文本框QLineEdit:的属性
- 在qt助手中搜索edit,找到QLineEidt,然后就可以查看方法和属性了
- 比如我想修改文本框大小,我会按Ctrl+F搜素size
- 就这样,一步一步的实现
- 同样的,更快的方法是使用google。一般google不到的东西就只有使用自带的qt助手了。
###后记
因为我个人不需要深入学习qt,只是用来实现一个客户端。客户端可以是多种多样的,如web实现客户端。所以不要在客户端花过多的时间,不过基本的东西还是要好好学习的。只要深入学习了一种,其他的都会很类似的。
[转载]编译Linux内核
Posted by 涵曦 at 2013-04-08 with tags Linux, 内核, 编译源码
这几天实验室交给任务,编译linux内核。然后就开始做了。
师兄给了两篇参考文章,这篇和这篇。第一篇比较简略,说了总体的过程,而第二篇比较而言就比较详细了。
好了,现在说说自己的编译的过程吧。
一、下载源码并解压
源码从这里下载,我下载的是最新的3.5-rc6。具体那个版本都是差不多的。然后需要把源码解压到/usr/src这个目录下,我是ubuntu用户,其他发行版相似
sudo cp /path/to/file/linux-3.5-rc6.tar.bz2 /usr/src
sudo tar jxvf linux-3.5-rc6
cd linux-3.5-rc6/ # 进入源码的目录,以后所有操作均在该目录下完成
二、删除之前编译的残留文件
sudo make mrproper
如果是第一次编译的话,可以不用执行这一步。而如果之前曾经编译过,务必执行这一步,清除残留文件,否则可能会导致编译失败。
三、配置编译选项
sudo make config
sudo make menuconfig
sudo make xconfig
这三个命令任选一个执行就好了。区别是
-
make config 问答式的配置,选错之后无法更改,所以一般人都不会用这个。
-
make menuconfig 这个命令需要ncurses库的支持,ncurses是字符界面下的图形界面库。可以通过
apt-get install libncurses5-dev
来安装。ncurses是字符界面下的图形界面库,所以如果使用的字符控制台,就可以用选用menuconfig了 -
make xconfig 这个命令需要Qt,这是个跨平台的图形界面库。可以通过
sudo apt-get install libqt...
来安装,具体是那个包忘记了。xconfig就是图形界面下的配置环境了。
xconfig用着最舒服,menugconfig也可以,不过界面难看了些。config自己没用过,也不推荐用。
这一步其实应该是很重要的一步,不过由于选项众多,且众多选项和硬件相关,需要有足够的硬件知识,并且对自己电脑的硬件配置有足够的了解才可能完全配置的好。网上有许多专门讲这一步的文章,比如金步国的这篇经典文章,不过由于版本还是2.6,所以有许多选项文章里面没有讲到。
由于本人是个菜鸟,对于硬件以及其他的知识都不是很清楚,再加上这么多的选项,所以大部分都是默认的选项。当然,这样就使得modules过多,使得之后的make modules的时候花费了大量的时间。所以应该尽量的减少不需要的模块,特便是其中的许多针对不同的硬件的驱动。
四、编译内核
sudo make bzImage
在我参考的第二篇文章中说,他编译出来的内核大约在800k-900k左右,可是实际上我编译出来的内核一般在4500k左右。可能是由于版本的差异吧,不用纠结在这个上面。
在编译结束后,会告诉你bzImage的位置,然后执行
sudo cp /path/to/bzImage /boot/vmlinuz-3.5-rc6
把其放到/boot目录下。这里我把它重命名为vmlinuz-3.5-rc6?为什么是这个名字?自己看看/boot,更深层原因我也不知道了。
五、编译模块
sudo make modules
上边也说了,这个阶段的时间是和配置选项那一步中选择的模块的多少直接相关的。有时可能会遇到rts5139.ko undefined的错误,可以在配置选项时去掉rts5139这个模块。
六、安装模块
sudo make modules_install
这一步没什么说的。有时可能会出错,并让你make CONFIG_MISMATCH=y(这个选项记不清了,不过差不多了),可以不用理。
七、安装内核
sudo make install
这一步比较快。在这一步,系统会自动更新grub。
如果以上完全弄好了,那么就可以重启进入新的内核体验了。不过有的同学在重启后可能会遇到这样一个问题,就是无法进入图形界面了。
提示类似如”ubuntu run in a low-graphic mode”,”Your screen, graphic card, input devices cannot detected correctly. You need configure it yourself.”等等。
这时可以Ctrl-Alt-F1或者Alt-F1进入控制台,sudo apt-get install gdm
之后选择gdm即可。如果还不能解决问题,对于ATI显卡,可以安装fglrx。由于我不是ATI的显卡,所以是否可行我就不是很清楚了。更具体的可以google之。