摘要: 之所以撰写这篇文章是因为前段时间花费了很大的精力在已经成熟的代码上再去处理memory leak问题。写此的目的是希望我们应该养成良好的编码习惯,尽可能的避免这样的问题,因为当你对着一大片的代码再去处理此类的问题,此时无疑增加了解决的成本和难度。准确的说属于补救措施了。1. 什么是内存泄漏(memory leak)? 指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并非...
阅读全文
posted @
2013-05-02 18:29 王海光 阅读(7120) |
评论 (0) |
编辑 收藏
现在流行的进程线程同步互斥的控制机制,其实是由最原始最基本的4种方法实现的。由这4种方法组合优化就有了.Net和Java下灵活多变的,编程简便的线程进程控制手段。
这4种方法具体定义如下 在《操作系统教程》ISBN 7-5053-6193-7 一书中可以找到更加详细的解释
1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
2、互斥量:为协调共同对一个共享资源的单独访问而设计的。
3、信号量:为控制一个具有有限数量用户资源而设计。
4、事 件:用来通知线程有一些事件已发生,从而启动后继任务的开始。
临界区(Critical Section)
保证在某一时刻只有一个线程能访问数据的简便办法。在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
临界区包含两个操作原语:
EnterCriticalSection() 进入临界区
LeaveCriticalSection() 离开临界区
EnterCriticalSection()语句执行后代码将进入临界区以后无论发生什么,必须确保与之匹配的LeaveCriticalSection()都能够被执行到。否则临界区保护的共享资源将永远不会被释放。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
MFC提供了很多功能完备的类,我用MFC实现了临界区。MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的。只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。Lock()后代码用到的资源自动被视为临界区内的资源被保护。UnLock后别的线程才能访问这些资源。
//CriticalSection
CCriticalSection global_CriticalSection;
// 共享资源
char global_Array[256];
//初始化共享资源
void InitializeArray()
{
for(int i = 0;i<256;i++)
{
global_Array[i]=I;
}
}
//写线程
UINT Global_ThreadWrite(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
//进入临界区
global_CriticalSection.Lock();
for(int i = 0;i<256;i++)
{
global_Array[i]=W;
ptr->SetWindowText(global_Array);
Sleep(10);
}
//离开临界区
global_CriticalSection.Unlock();
return 0;
}
//删除线程
UINT Global_ThreadDelete(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
//进入临界区
global_CriticalSection.Lock();
for(int i = 0;i<256;i++)
{
global_Array[i]=D;
ptr->SetWindowText(global_Array);
Sleep(10);
}
//离开临界区
global_CriticalSection.Unlock();
return 0;
}
//创建线程并启动线程
void CCriticalSectionsDlg::OnBnClickedButtonLock()
{
//Start the first Thread
CWinThread *ptrWrite = AfxBeginThread(Global_ThreadWrite,
&m_Write,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
ptrWrite->ResumeThread();
//Start the second Thread
CWinThread *ptrDelete = AfxBeginThread(Global_ThreadDelete,
&m_Delete,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
ptrDelete->ResumeThread();
}
在测试程序中,Lock UnLock两个按钮分别实现,在有临界区保护共享资源的执行状态,和没有临界区保护共享资源的执行状态。
互斥量(Mutex)
互斥量跟临界区很相似,只有拥有互斥对象的线程才具有访问资源的权限,由于互斥对象只有一个,因此就决定了任何情况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其他线程在获得后得以访问资源。互斥量比临界区复杂。因为使用互斥不仅仅能够在同一应用程序不同线程中实现资源的安全共享,而且可以在不同应用程序的线程之间实现对资源的安全共享。
互斥量包含的几个操作原语:
CreateMutex() 创建一个互斥量
OpenMutex() 打开一个互斥量
ReleaseMutex() 释放互斥量
WaitForMultipleObjects() 等待互斥量对象
同样MFC为互斥量提供有一个CMutex类。使用CMutex类实现互斥量操作非常简单,但是要特别注意对CMutex的构造函数的调用
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL)
不用的参数不能乱填,乱填会出现一些意想不到的运行结果。
//创建互斥量
CMutex global_Mutex(0,0,0);
// 共享资源
char global_Array[256];
void InitializeArray()
{
for(int i = 0;i<256;i++)
{
global_Array[i]=I;
}
}
UINT Global_ThreadWrite(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
global_Mutex.Lock();
for(int i = 0;i<256;i++)
{
global_Array[i]=W;
ptr->SetWindowText(global_Array);
Sleep(10);
}
global_Mutex.Unlock();
return 0;
}
UINT Global_ThreadDelete(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
global_Mutex.Lock();
for(int i = 0;i<256;i++)
{
global_Array[i]=D;
ptr->SetWindowText(global_Array);
Sleep(10);
}
global_Mutex.Unlock();
return 0;
}
同样在测试程序中,Lock UnLock两个按钮分别实现,在有互斥量保护共享资源的执行状态,和没有互斥量保护共享资源的执行状态。
信号量(Semaphores)
信号量对象对线程的同步方式与前面几种方法不同,信号允许多个线程同时使用共享资源,这与操作系统中的PV操作相同。它指出了同时访问共享资源的线程最大数目。它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()创建信号量时即要同时指出允许的最大资源计数和当前可用资源计数。一般是将当前可用资源计数设置为最大资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就可以发出信号量信号。但是当前可用计数减小到0时则说明当前占用资源的线程数已经达到了所允许的最大数目,不能在允许其他线程的进入,此时的信号量信号将无法发出。线程在处理完共享资源后,应在离开的同时通过ReleaseSemaphore()函数将当前可用资源计数加1。在任何时候当前可用资源计数决不可能大于最大资源计数。
PV操作及信号量的概念都是由荷兰科学家E.W.Dijkstra提出的。信号量S是一个整数,S大于等于零时代表可供并发进程使用的资源实体数,但S小于零时则表示正在等待使用共享资源的进程数。
P操作 申请资源:
(1)S减1;
(2)若S减1后仍大于等于零,则进程继续执行;
(3)若S减1后小于零,则该进程被阻塞后进入与该信号相对应的队列中,然后转入进程调度。
V操作 释放资源:
(1)S加1;
(2)若相加结果大于零,则进程继续执行;
(3)若相加结果小于等于零,则从该信号的等待队列中唤醒一个等待进程,然后再返回原进程继续执行或转入进程调度。
信号量包含的几个操作原语:
CreateSemaphore() 创建一个信号量
OpenSemaphore() 打开一个信号量
ReleaseSemaphore() 释放信号量
WaitForSingleObject() 等待信号量
//信号量句柄
HANDLE global_Semephore;
// 共享资源
char global_Array[256];
void InitializeArray()
{
for(int i = 0;i<256;i++)
{
global_Array[i]=I;
}
}
//线程1
UINT Global_ThreadOne(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
//等待对共享资源请求被通过 等于 P操作
WaitForSingleObject(global_Semephore, INFINITE);
for(int i = 0;i<256;i++)
{
global_Array[i]=O;
ptr->SetWindowText(global_Array);
Sleep(10);
}
//释放共享资源 等于 V操作
ReleaseSemaphore(global_Semephore, 1, NULL);
return 0;
}
UINT Global_ThreadTwo(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
WaitForSingleObject(global_Semephore, INFINITE);
for(int i = 0;i<256;i++)
{
global_Array[i]=T;
ptr->SetWindowText(global_Array);
Sleep(10);
}
ReleaseSemaphore(global_Semephore, 1, NULL);
return 0;
}
UINT Global_ThreadThree(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
WaitForSingleObject(global_Semephore, INFINITE);
for(int i = 0;i<256;i++)
{
global_Array[i]=H;
ptr->SetWindowText(global_Array);
Sleep(10);
}
ReleaseSemaphore(global_Semephore, 1, NULL);
return 0;
}
void CSemaphoreDlg::OnBnClickedButtonOne()
{
//设置信号量 1 个资源 1同时只可以有一个线程访问
global_Semephore= CreateSemaphore(NULL, 1, 1, NULL);
this->StartThread();
// TODO: Add your control notification handler code here
}
void CSemaphoreDlg::OnBnClickedButtonTwo()
{
//设置信号量 2 个资源 2 同时只可以有两个线程访问
global_Semephore= CreateSemaphore(NULL, 2, 2, NULL);
this->StartThread();
// TODO: Add your control notification handler code here
}
void CSemaphoreDlg::OnBnClickedButtonThree()
{
//设置信号量 3 个资源 3 同时只可以有三个线程访问
global_Semephore= CreateSemaphore(NULL, 3, 3, NULL);
this->StartThread();
// TODO: Add your control notification handler code here
}
信号量的使用特点使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时可以为每一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,通过使用信号量对线程的同步作用可以确保在任一时刻无论有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程能够进行访问,而其他的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。
事件(Event)
事件对象也可以通过通知操作的方式来保持线程的同步。并且可以实现不同进程中的线程同步操作。
信号量包含的几个操作原语:
CreateEvent() 创建一个信号量
OpenEvent() 打开一个事件
SetEvent() 回置事件
WaitForSingleObject() 等待一个事件
WaitForMultipleObjects() 等待多个事件
WaitForMultipleObjects 函数原型:
WaitForMultipleObjects(
IN DWORD nCount, // 等待句柄数
IN CONST HANDLE *lpHandles, //指向句柄数组
IN BOOL bWaitAll, //是否完全等待标志
IN DWORD dwMilliseconds //等待时间
)
参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当所有对象都被通知时函数才会返回,为FALSE则只要其中任何一个得到通知就可以返回。dwMilliseconds在这里的作用与在WaitForSingleObject()中的作用是完全一致的。如果等待超时,函数将返回WAIT_TIMEOUT。
//事件数组
HANDLE global_Events[2];
// 共享资源
char global_Array[256];
void InitializeArray()
{
for(int i = 0;i<256;i++)
{
global_Array[i]=I;
}
}
UINT Global_ThreadOne(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
for(int i = 0;i<256;i++)
{
global_Array[i]=O;
ptr->SetWindowText(global_Array);
Sleep(10);
}
//回置事件
SetEvent(global_Events[0]);
return 0;
}
UINT Global_ThreadTwo(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
for(int i = 0;i<256;i++)
{
global_Array[i]=T;
ptr->SetWindowText(global_Array);
Sleep(10);
}
//回置事件
SetEvent(global_Events[1]);
return 0;
}
UINT Global_ThreadThree(LPVOID pParam)
{
CEdit *ptr=(CEdit *)pParam;
ptr->SetWindowText("");
//等待两个事件都被回置
WaitForMultipleObjects(2, global_Events, true, INFINITE);
for(int i = 0;i<256;i++)
{
global_Array[i]=H;
ptr->SetWindowText(global_Array);
Sleep(10);
}
return 0;
}
void CEventDlg::OnBnClickedButtonStart()
{
for (int i = 0; i < 2; i++)
{
//实例化事件
global_Events[i]=CreateEvent(NULL,false,false,NULL);
}
CWinThread *ptrOne = AfxBeginThread(Global_ThreadOne,
&m_One,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
ptrOne->ResumeThread();
//Start the second Thread
CWinThread *ptrTwo = AfxBeginThread(Global_ThreadTwo,
&m_Two,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
ptrTwo->ResumeThread();
//Start the Third Thread
CWinThread *ptrThree = AfxBeginThread(Global_ThreadThree,
&m_Three,
THREAD_PRIORITY_NORMAL,
0,
CREATE_SUSPENDED);
ptrThree->ResumeThread();
// TODO: Add your control notification handler code here
}
事件可以实现不同进程中的线程同步操作,并且可以方便的实现多个线程的优先比较等待操作,例如写多个WaitForSingleObject来代替WaitForMultipleObjects从而使编程更加灵活。
总结:
1. 互斥量与临界区的作用非常相似,但互斥量是可以命名的,也就是说它可以跨越进程使用。所以创建互斥量需要的资源更多,所以如果只为了在进程内部是用的话使用临界区会带来速度上的优势并能够减少资源占用量。因为互斥量是跨进程的互斥量一旦被创建,就可以通过名字打开它。
2. 互斥量(Mutex),信号灯(Semaphore),事件(Event)都可以被跨越进程使用来进行同步数据操作,而其他的对象与数据同步操作无关,但对于进程和线程来讲,如果进程和线程在运行状态则为无信号状态,在退出后为有信号状态。所以可以使用WaitForSingleObject来等待进程和线程退出。
3. 通过互斥量可以指定资源被独占的方式使用,但如果有下面一种情况通过互斥量就无法处理,比如现在一位用户购买了一份三个并发访问许可的数据库系统,可以根据用户购买的访问许可数量来决定有多少个线程/进程能同时进行数据库操作,这时候如果利用互斥量就没有办法完成这个要求,信号灯对象可以说是一种资源计数器。
疑问:
在 Linux 上,有两类信号量。第一类是由 semget/semop/semctl API 定义的信号量的 SVR4(System V Release 4)版本。第二类是由 sem_init/sem_wait/sem_post/interfaces 定义的 POSIX 接口。 它们具有相同的功能,但接口不同。 在2.4.x内核中,信号量数据结构定义为(include/asm/semaphore.h)。
但是在Linux中没有对互斥量的具体提法,只是看到说互斥量是信号量的一种特殊情况,当信号量的最大资源数=1同时可以访问共享资源的线程数=1 就是互斥量了。临界区的定义也比较模糊。没有找到用事件处理线程/进程同步互斥的操作的相关资料。在Linux下用GCC/G++编译标准C++代码,信号量的操作几乎和Windows下VC7的编程一样,不用改多少就顺利移植了,可是互斥量,事件,临界区的Linux移植没有成功。
本文所有事例程序在WindowsXp Sp2 + VC7 下编译通过。
本文转自:http://www.bccn.net/Article/kfyy/vc/jszl/200709/6380_2.html
posted @
2013-04-18 17:06 王海光 阅读(486) |
评论 (0) |
编辑 收藏
解释以下语句的含义:
1、new A;
2、new A();
也许很多人包括我自己,都可以马上给出第一种情况的答案:在堆上为A类分配内存,然后调用A的构造函数。这种说法被大家所熟知,因为包括《STL源码剖析》等大作在内也都是这么写的(但是你认为这种说法完全正确吗?其实不尽然,答案后面揭晓)
第二种情况,对象构造的时候初始化列表为空会和第一种有什么不同呢?对于这种在实际工程中很少使用的情况,我一时还真给不出确切的答案。
网上搜了一下,看到CSDN里面还有专门针对这个问题的一个帖子(原帖链接 http://bbs.csdn.net/topics/320161716)。
好像最终也没有可以信服的答案,认同度比较高的是这样的说法:“加括号调用没有参数的构造函数,不加括号调用默认构造函数或唯一的构造函数,看需求” (peakflys注:这种说法是错误的,答案后面揭晓)
既然没有特别靠谱的答案,不如自己动手找出答案。
构造以下示例:
/**
*\brief example1 difference between new and new()
*\author peakflys
*\data 12:10:24 Monday, April 08, 2013
*/
class A
{
public:
int a;
};
int main()
{
A *pa = new A;
A *paa = new A();
return 0;
}
查看main函数的汇编代码(编译器:gcc (GCC) 4.4.6 20120305 (Red Hat
4.4.6-4) )
int main()
{
4005c4: 55 push %rbp
4005c5: 48 89 e5 mov %rsp,%rbp
4005c8: 48 83 ec 10 sub $0x10,%rsp
A *pa = new A;
4005cc: bf 04 00 00 00 mov $0x4,%edi
4005d1: e8 f2 fe ff ff callq 4004c8 <_Znwm@plt> //调用new
4005d6: 48 89 45 f0 mov %rax,-0x10(%rbp) //rax寄存器内容赋给指针pa(rax寄存器里是new调用产生的A对象堆内存地址)
A *paa = new A();
4005da: bf 04 00 00 00 mov $0x4,%edi
4005df: e8 e4 fe ff ff callq 4004c8 <_Znwm@plt> //调用new
4005e4: 48 89 c2 mov %rax,%rdx //rax的内容放入rdx,执行之后,rdx里存放的即是通过new A()产生的内存地址
4005e7: c7 02 00 00 00 00 movl $0x0,(%rdx) //把rdx内存指向的内容赋为0值,即把A::a赋值为0
4005ed: 48 89 45 f8 mov %rax,-0x8(%rbp) //rax寄存器内容赋给指针paa(rax寄存器里是new()调用产生的A对象堆内存地址)
return 0;
4005f1: b8 00 00 00 00 mov $0x0,%eax
}
4005f6: c9 leaveq
4005f7: c3 retq
通过上面产生的汇编代码(对AT&T汇编不熟悉的可以看注释)可以很容易看出,new A()的执行,在调用完operator new分配内存后,马上对新分配内存中的对象使用0值初始化,而new A 仅仅是调用了operator new分配内存!
是不是这样就可以下结论 new A()比new A多了一步,即初始化对象的步骤呢?
我们再看看下面这种情况:/**
*\brief example2 difference between new and new()
*\author peakflys
*\data 12:23:20 Monday, April 08, 2013
*/
class A
{
public:
A(){a = 10;}
int a;
};
int main()
{
A *pa = new A;
A *paa = new A();
return 0;
}
这种情况是类显示提供含默认值的构造函数。
查看汇编实现如下:
int main()
{
4005c4: 55 push %rbp
4005c5: 48 89 e5 mov %rsp,%rbp
4005c8: 53 push %rbx
4005c9: 48 83 ec 18 sub $0x18,%rsp
A *pa = new A;
4005cd: bf 04 00 00 00 mov $0x4,%edi
4005d2: e8 f1 fe ff ff callq 4004c8 <_Znwm@plt>
4005d7: 48 89 c3 mov %rax,%rbx
4005da: 48 89 d8 mov %rbx,%rax
4005dd: 48 89 c7 mov %rax,%rdi
4005e0: e8 2d 00 00 00 callq 400612 <_ZN1AC1Ev>
4005e5: 48 89 5d e0 mov %rbx,-0x20(%rbp)
A *paa = new A();
4005e9: bf 04 00 00 00 mov $0x4,%edi
4005ee: e8 d5 fe ff ff callq 4004c8 <_Znwm@plt>
4005f3: 48 89 c3 mov %rax,%rbx
4005f6: 48 89 d8 mov %rbx,%rax
4005f9: 48 89 c7 mov %rax,%rdi
4005fc: e8 11 00 00 00 callq 400612 <_ZN1AC1Ev>
400601: 48 89 5d e8 mov %rbx,-0x18(%rbp)
return 0;
400605: b8 00 00 00 00 mov $0x0,%eax
}
40060a: 48 83 c4 18 add $0x18,%rsp
40060e: 5b pop %rbx
40060f: c9 leaveq
400610: c3 retq
上面的汇编代码就不在添加注释了,因为两种操作产生的汇编代码是一样的,都是先调用operator new分配内存,然后调用构造函数。
上面的情况在VS2010下验证是一样的情况,有兴趣的朋友可以自己去看,这里就不再贴出VS2010下的汇编代码了。
通过上面的分析,对于new A和 new A() 的区别,我们可以得出下面的结论:
1、类体含有显示适合地默认构造函数时,new A和new A()的作用一致,都是首先调用operator new分配内存,然后调用默认构造函数初始化对象。
2、类体无显示构造函数时,new A()首先调用operator new来为对象分配内存,然后使用空值初始化对象成员变量,而new A仅仅是调用operator new分配内存,对象的成员变量是无意义的随机值! (peakflys注:对于基本数据类型,如int等 适用此条)
注意到,现在很多书籍对new操作符的说明都存在纰漏,例如《STL源码剖析》中2.2.2节中有以下的描述:
事实证明,new Foo的操作是否有构造函数的调用是不确定的,具体要看Foo类体里是否有显示构造函数的出现。
/*****************************************华丽分割线**************************************
补充:刚才发现,在C++Primer第四版5.11节中,已经有了对于new A()的说明:
We indicate that we want to value-initialize the newly allocated object by following the type nameby a pair of empty parentheses. The empty parentheses signal that we want initialization but arenot supplying a specific initial value. In the case of class types (such as string) that define their own constructors, requesting value-initialization is of no consequence: The object is initialized by running the default constructor whether we leave it apparently uninitialized orask for value-initialization. In the case of built-in types or types that do not define any constructors, the difference is significant:
int *pi = new int; // pi points to an uninitialized int
int *pi = new int(); // pi points to an int value-initialized to 0
In the first case, the int is uninitialized; in the second case, the int is initialized to zero.
这里给出的解释和上面自己分析的new A()的行为是一致的。
/***************************************再次华丽分割线************************************
鉴于上面的结论是通过GCC和VS2010得出的,而且有朋友也提出同样的质疑,为了确定这种结果是否是编译器相关的,刚才特意查看了一下C++的标准化文档。
摘自:ISO/IEC 14882:2003(E) 5.3.4 - 15
— If the new-initializer is omitted:
— If T is a (possibly cv-qualified) non-POD class type (or array thereof), the object is default-initialized(8.5). If T is a const-qualified type, the underlying class type shall have a user-declared default constructor.
— Otherwise, the object created has indeterminate value. If T is a const-qualified type, or a (possibly cv-qualified) POD class type (or array thereof) containing (directly or indirectly) a member of const-qualified type, the program is ill-formed;
— If the new-initializer is of the form (), the item is value-initialized (8.5);
所以可以确定,这种情况完全是编译器无关的(当然那些不完全按照标准实现的编译器除外)。
但是通过上面标准化文档的描述,我们可以看出文中对new A在无显示构造函数时的总结并不是特别准确,鉴于很多公司都有这道面试题(撇去这些题目的实际考察意义不说),我们有必要再补充一下: 对于new A: 这样的语句,再调用完operator new分配内存之后,如果A类体内含有POD类型,则POD类型的成员变量处于未定义状态,如果含有非POD类型则调用该类型的默认构造函数。而 new A()在这些情况下都会初始化。
PS:估计很多公司的“正确答案“ 也不一定正确吧。
本文转自:http://www.cppblog.com/peakflys/archive/2013/04/08/199208.html
posted @
2013-04-18 11:03 王海光 阅读(445) |
评论 (0) |
编辑 收藏
来源于《敏捷软件开发——原则、模式与实践》常见的设计的臭味——腐化软件的气味。l 僵化性(Rigidity):很难对系统进行改动,因为每个改动都会迫使许多对系统其他部分的其他改动。
l 脆弱性(Fragility):对系统的改动会导致系统中和改动的地方在概念上无关的许多地方出现问题。
l 牢固性(Immobility):很难解开系统的纠结,使之成为一些可在其他系统中重用的组件。
l 粘滞性(Viscosity):做正确的事情比做错误的事情要困难。
l 不必要的复杂性(Needless Complexity):设计中包含有不具任何直接好处的基础结构。
l 不必要的重复(Needless Repetition):设计中包含有重复的结构,而该重复的结构本可以使用单一的抽象进行统一。
l 晦涩性(Opacity):很难阅读、理解。没有很好的表现出意图。
敏捷设计是一个过程,不是一个事件。它是一个持续的应用原则、模式以及实践来改进软件的结构和可读性的过程。它致力于保持系统设计在任何时间都尽可能得简单、干净以及富有表现力。
敏捷软件开发宣言:
我们正在通过亲身实践以及帮助他人实践,揭示更好的软件开发方法。通过这项工作,我们认为:
个体和交互 胜过 过程和工具
可以工作的软件 胜过 面面俱到的文档
客户合作 胜过 合同谈判
响应变化 胜过 遵循计划
虽然右项也具有价值,但我们认为左项具有更大的价值。
敏捷开发强调以人为中心,而不是以过程为中心,强调尽可能的沟通(与客户,与团队成员),尽可能地以最简单的设计解决问题(从而能够拥抱变化)。
敏捷宣言遵循的原则
我们遵循以下原则:
1。我们最优先要做的是通过尽早的、持续的交付有价值的软件来使客户满意。
规划迭代故事时必须按照优先级安排,为客户先提供最有价值的功能。通过频繁迭代能与客户形成早期的良好合作,及时反馈提高产品质量。敏捷小组关注完成和交 付具有用户价值的功能,而不是孤立的任务。以前我们都用需求规格说明书或者用例来编写详细的需求,敏捷使用用户故事来罗列需求。用户故事是一种表示需求的 轻量级技术,它没有
固定的形式和强制性的语法。但是有一些固定的形式可以用来参考还是比较有益的。敏捷估算中使用了这个模板:“作为【用户的类型】,我希 望可以【能力】以便【业务价值】“。使用基于用户故事的需求分析方法时,仍可能需要原型和编写文档,只是工作重点更多的转移到了口头交流。
2。即使到了开发的后期,也欢迎改变需求。敏捷过程利用变化来为客户创造竞争优势。
敏捷过程参与者不怕变化,他们认为改变需求是好事情,因为这些改变意味着我们更了解市场需求。
3。经常性的交付可以工作的软件,交付的间隔可以从几周到几个月,交付的时间间隔越短越好。
迭代是受实践框限制的,意味着即使放弃一些功能也必须按时结束迭代。只要我们可以保证交付的软件可以很好的工作,那么交付时间越短,我们和客户协作就越 紧密,对产品质量就更有益。虽然我们多次迭代,但并不是每次迭代的结果都需要交付给用户,敏捷开发的目标是让他们可以交付。这意味着开发小组在每次迭代中 都会增加一些功能,增加的每个功能都是经过编码、测试,达到了可发布的质量标准的。
另外敏捷开发项目中对开发阶段没有什么重要的分割,没有先期的需求阶段,然后是分析阶段,架构设计阶段,编码测试阶段等,在项目真正开始后,每次迭代中都会同时进
行所有的上述阶段工作。
4。在整个项目开发期间,业务人员和开发人员必须天天都在一起工作。
软件项目不会依照之前设定的计划原路执行,中间对业务的理解、软件的解决方案肯定会存在偏差,所以客户、需求人员、开发人员以及涉众之间必须进行有意义的、频繁
的交互,这样就可以在早期及时的发现并解决问题。
5。围绕被激励起来的人个来构建项目。给他们提供所需要的环境和支持,并且信任他们能够完成工作。
业务和技术是引起不确定的二个主要方面,人是第三个方面。而业务和技术又必须由人来执行,所以能够激励人来解决这些问题是解决不确定性的关键。只要个人的目标和团
队的目标一致,我们就需要鼓舞起每个人的积极性,以个人为中心构建项目,提供所需的环境、支持与信任。
6。在团队内部,最具有效果并且富有效率的传递信息的方法,就是面对面的交谈。
在十几或者二十几个人组成的大团队中,文档是一种比较合适的传递知识和交流的途径。而敏捷团队一般不会很多人(大团队实施敏捷时也会分成多个小的敏捷团队),所以
大量的文档交流其实并不是很经济的做法。此时面对面的交谈反而更快速有效。
7、可工作的软件是首要进度度量标准。
一般的工作都比较容易衡量任务进展,比如让你去搬运1吨的石头,我只要去称一下你已经搬运的石头重量就知道你完成多少了。而对于软件来说,在软件没有编 码、测试完
成之前,我们都不能因为代码编写了多少行,测试用例跑了多少个就去度量这个功能是否完成了。衡量这个功能是否完成的首要标准就是这个功能可以工 作了,对用户来说已经可
以应用了。
8。敏捷过程提可持续的开发速度。责任人、开发者和用户应该能够保持一个长期的、恒定的开发速度。
很多人都认为软件开发中加班是很正常的,不加班反而不正常,我对此有点不理解,这个可能是国情所致吧。敏捷过程希望能够可持续的进行开发,开发速度不会 随着迭代的任务不同而不同,不欣赏所谓的拼一拼也能完成的态度,开发工作不应该是突击行为。我们不能指望说突击这个项目后就可以轻松了,因为完成一个项目 后会接踵而来下一个项目,而只要还是拼拼的态度,下一个项目依旧会让你的组员再次突击。这时不知道有人会不会说,那我们就一直加班,也是“持续的开发速 度”啊,这时可要注意了,持续加班智
慧导致人疲劳、厌倦,保持长期恒定的速度也只是一种理想而已。
9。不断地关注优秀的技能和好的设计会增强敏捷能力。
敏捷过程有很多好的技术实践可以加强产品敏捷能力,很多原则、模式和实践也可以增强敏捷开发能力。 《敏捷软件开发-原则、模式与实践》一书中介绍了很多设计,感兴趣的可以去仔细看看。
10。简单----使未完成的工作最大化的艺术----是根本的。
我们不可能预期后面需求会如何变化,所以不可能一开始就构建一个完美的架构来适应以后的所有变化。敏捷团队不会去构建明天的软件,而把注意力放在如何通 过最简单的方法完成现在需要解决的问题。这时有人会说,我已经预计到了肯定存在哪些需求扩展点,我们在一开始是否需要考虑呢?这时团队需要根据自己的理解 去决定是否考虑,如果深信在明天发生了这个问题也可以轻易处理的话,那么就最好先不考虑。
11。最好的构架、需求和设计出自与自组织的团队。
敏捷中有很多种实践,大家都知道,迭代式开发是主要的实践方法,而自组织团队也是主要的实践之一。在自组织团队中,管理者不再发号施令,而是让团队自身寻找最佳的工作方式来完成工作。要形成一个自组织团队其实比较难。CSDN采访Mishkin Berteig中说到 自组织团队的第一个要素就是必须有一个团队,而不仅仅是一群人。一群人是一帮在一起工作的人,他们彼此之间并没有太多的沟通,他们也并不视彼此为一体。项目一开始,我们就会组建“团队”,但很多时候由构架师、需求人员、开发人员和测试人员组成的是一群人而已。他还认为,团队的形成必须经历几个时期。在 经历了初期的磨合后,成员才会开始对团队共同的工作理念与文化形成一个基本的认识和理解。团队内会逐渐形成规矩,而且这些规矩是不言而喻的。比如,每个人 都知道上午九点来上班,都会主动询问别人是否需要帮助,也都会去主动和别人探讨问题。如果团队成员之间能够达成这样的默契,那么这个团队将成为一个真正高 效的工作团队。在这样团队中,成员之间相互理解,工作效率非常高。在自组织团队中,团队成员不需要遵从别人的详细指令。他们需要更高层次的指导,这种指 导更像是一个目标,一个致力于开发出更好的软件的目标。总之,自组织团队是一个自动自发、有着共同目标和工作文化的团队,这样的团队总是在向它的组织做出 承诺。但是,实现这些承诺对于自组织团队来说非常重要。否则,一旦出现问题,团队成员之间就会出现信任危机。
虽然敏捷开发小组是以小组为整体 来工作的,但是还是有必要指明一些承担一定任务的角色。第一个角色是产品所有者(Product Owner)。产品所有者的主要职责包括:确认小组所有成员都在追求一个共同的项目前景,确定功能的优先级以便总是在处理最具有价值的功能,以及作出决定 使得对项目的投入可以产生良好的回报。可以对应为以前开发中的“产品经理”。另一角色是开发团队(developer),这里的开发人员包括了架构师、设计师、程序员、需求人员、测试人员、文档编写者等,有时产品所有者也可以被看作是
开发人员。还有一个重要角色就是项目经理(project manager)。敏捷开发的项目经理会更多的关注领导而不是管理。在某些项目中,项目经理可能同时也是开发人员,少数时候也会担任产品所有者。
12。每隔一定时间,团队会在如何才能更有效地工作方面进行反省,然后相应地对自己的行为进行调整。
由于很多不确定性因素会导致计划失效,比如项目成员增减、技术应用效果、用户需求的改变、竞争者对我们的影响等都会让我们作出不同的反应。 敏捷不是基于预定义的工作方式,而是基于经验性的方式,对以上这些变化,小组通过不断的反省调整来保持团队的敏捷性。
面向对象设计的原则:
SRP 单一职责原则
就一个类而言,应该仅有一个引起它变化的原因。
l 单一职责原则(The Single Responsibility Principle,简称SRP):就一个类而言,应该仅有一个引起它变化的原因。在SRP中,我们把职责定义为“变化的原因()”。如果你能够想到多于一个的动机去改变一个类,那么这个类就具有多于一个的职责。软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离。事实上,我们将要论述的其余原则都会以这样或那样的方式回到这个问题上。
l 开放封闭原则(The Open-Close Principle,简称OCP):软件实体(类、模块、函数等等)应该是可以扩展的,但是不可以修改的。遵循开放封闭原则设计出的模块具有两个主要的特征。它们是:(1)、对于扩展是开放的。这意味着模块的行为是可以扩展的。当应用的需求改变时,我们可以对模块进行扩展,使其具有满足那些改变的新行为。换句话说,我们可以改变模块的功能。(2)、对模块行为进行扩展时,不必改动模块的源代码或者二进制代码。模块的二进制可执行版本,无论是可链接的库、DLL或者Java的.jar文件,都无需改动。
l Liskov替换原则(The Liskov Substitution Principle,简称LSP):子类型必须能够替换掉它们的基类型。OCP原则是OOD中很多说法的核心。LSP是使OCP成为可能的主要原则之一。正式子类型的可替换性才使得使用基类类型的模块在无需修改的情况下就可以扩展。这种可替换性必须是开发人员可以隐式依赖的东西。
l 依赖倒置原则(The Dependency Inversion Principle,简称DIP):(1)、高层模块不应该依赖于底层模块。二者都应该依赖于抽象。(2)、抽象不应该依赖于细节。细节应该依赖于抽象。使用传统的过程化设计所创建出来的依赖关系结构,策略是依赖于细节的。面向对象的程序设计倒置了依赖关系结构,使得细节和策略都依赖于抽象,并且常常是客户拥有服务接口。事实上,这种依赖关系正式好的面向对象设计的标志所在。
l 接口隔离原则(The Interface Segregation Interface,简称ISP):不应该强迫客户依赖它们不用的方法。如果强迫客户程序依赖于那些它们不适用的方法,那么这些客户程序就面临着由于这些未使用方法的改变所带来的变更。这就无意中导致了所有客户程序之间的耦合。我们希望尽可能地避免这种耦合,因此我们希望分离接口。
REP 重用发布等价原则
重用的粒度就是发布的粒度
CCP 共用封闭原则
包中的所有类对于同一类性质的变化应该是共同封闭的。一个变化若对一个包产生影响,则将对该包中的所有类产生影响,而对于其他的包不造成任何影响。
CRP 共同重用原则
一个包中的所有类应该是共同重用的。如果重用了包中的一个类,那么就要重用包中所有类。
ADP 无环依赖原则
在包的依赖关系图中不允许存在环。
SDP 稳定依赖原则
朝着稳定的方向进行依赖。
SAP 稳定抽象原则
包的抽象程度应该和其稳定程度一致。
极限编程实践
完整团队
XP项目的所有参与者(开发人员、业务分析师、测试人员等等)一起工作在一个开放的场所中,他们是同一个团队的成员。
计划游戏
计划是持续的,循序渐进的。每2周,开发人员就为下2周估算候选特性的成本,而客户则根据成本和商务价值来选择要实现的特性。
客户测试
作为选择每个所期望的特性的一部分,客户定义出自动验收测试来表明该特性可以工作。
简单设计
团队保持设计恰好和当前的系统功能相匹配,它通过了所有的测试,不包含任何重复,表达出了编写者想表达的所有东西,并且包含尽可能少的代码。
结对编程
所有的产品软件都是由两个程序员,并排坐在一起在同一台电脑上构建的。
测试驱动开发
程序员以非常短的循环周期工作,他们先增加一个失败的测试,然后使之通过。
改进设计
随时改进糟糕的代码。保持代码尽可能的干净,具有表达力。
持续集成
团队总是使系统完整地被集成。
集体代码所有权
任何结对的程序员都可以在任何时候改进任何代码。
编码标准
系统中所有的代码看起来就好像是被单独一个--非常值得胜任的--人编写的。
隐喻
团队提出一个程序工作原理的公共景像。
可持续的速度
团队只有持久才有获胜的希望,他们以能够长期维持的速度努力工作,他们保存精力,他们把项目看作是马拉松长袍,而不是全速短跑。
测试驱动开发
极限编程(eXtreme Programming,简称XP)是敏捷方法中最著名的一个。它由一系列简单却相互依赖的时间组成。这些实践结合在一起形成了一个胜于部分结合的整体。其中一个非常重要的,当前也受到格外重视的实践就是TDD(测试驱动的开发方法)。
在测试驱动的开发方法中,编写所有的代码的目的都是为了使失败的单元测试能够通过。首先编写一个单元测试,由于它要测试的功能还不在,所以它会运行失败。然后编写代码使测试通过。
编写测试用例和代码之间的更迭速度是很快的,基本上几分钟左右。测试用例和代码共同演化,其中测试用例循序渐进地对代码的编写进行指导。作为结果,一个非常完整的测试用例集和代码一起发展起来。
测试粗略的可以分为单元测试和验收测试。单元测试是用来验证系统中个别机制的白盒测试。
单元测试用来验证系统的小的组成单元应该按照所期望的方式工作,但是它们没有验证系统作为一个整体时工作的正确性。所以,单元测试是必要的,但是不够充分。
验收测试是用来验证系统满足客户需求的黑盒测试。验收测试由不了解系统内部机制的人编写。验收测试是程序,因此是可运行的。通常通过使用专门为应用程序的客户创建的脚本语言来编写验收测试。正如单元测试作为可编译、运行的有关系统内部结构的文档那样,验收测试是有关系统特性的可编译、执行的文档。
编写代码前就编写单元测试会带来四个很明显的好处:
1、首先编写测试使得程序中的每一项功能都有测试来验证它的操作的正确性。这就可以给以后的开发提供支援,使我们可以更自由地对程序进行更改,因为测试可以告诉我们程序仍然具有正确的行为。
2、首先编写测试迫使我们必须从程序调用者的有利视角去观察我们将要编写的程序。这样,我们就会在关注程序的功能的同时,直接关注它的接口,我们也就可以设计出便于调用的软件。
3、首先编写测试迫使我们把程序设计为可测试的。为了把程序设计为易于调用和可测试的,程序必须和它周边环境解耦。这样首先编写测试迫使我们解除软件中的耦合。面向对象设计的原则在进行这种解除耦合方面具有巨大的帮助作用。
4、首先编写测试的另一个重要效果是,测试可以作为一种无价的文档形式。测试就像一套范例,它帮助其他程序员了解如何使用代码。这份文档是可编译、可运行的。它保持最新。它不会撒谎。
首先编写验收测试的行为对于系统的架构方面具有深远的影响。为了使系统具有可测试性,就必须要在很高的系统架构层面对系统进行解耦合。正如单元测试可以促使你在小的方面可以做出优良的设计决策一样,验收测试可以促使你在大的方面做出优良的系统架构决策。
软件大师、C++之父Bjarne Stroustrup曾经说过:设计和编程都是人的活动。忘记了这一点,将会失去一切。敏捷软件开发方法正是认识到软件开发的这一本质特征而提出的革新性开发方法。使用敏捷开发方法会给我们带来巨大的好处。当然要完全做到也是很困难的。这不仅需要对敏捷的深刻理解,更需要敏捷团队成员的共同努力。
本文参考:http://blog.csdn.net/open2job/article/details/6335000
posted @
2013-04-17 17:13 王海光 阅读(457) |
评论 (0) |
编辑 收藏
摘要: 最近准备做 Nokia 的 Symbian,Maemo 下触摸屏开发。考虑到程序的跨平台可移植性,最终选择使用 Qt 开发。相对来说,国内关于 Qt 相关文档并不算很多。作者将 Linux 下编译并安装配置 Qt 全过程总结了一下,只希望可以方便更多的朋友! 1、获得源代码  ...
阅读全文
posted @
2013-04-08 17:07 王海光 阅读(8435) |
评论 (0) |
编辑 收藏
摘要: 1、$表示普通用户,#表示超级用户(root user)。超级用户是Linux系统中权限最高的用户。 2、shell通常以#!起始,例如:#!/bin/bash 3、运行脚本的两种方式:1、将脚本作为sh的命令行参数。2、将脚本作为具有执行权限的可执行文件。 例如:$ sh scri...
阅读全文
posted @
2013-04-03 16:58 王海光 阅读(431) |
评论 (0) |
编辑 收藏
虽然网络上很多人使用 Redhat
或者fedora
作为上位机操作系统,但是我觉得使用Ubuntu
最为方便,因为需要的软件包大部分都可以通过 apt-get
方式来安装,而不必从源代码开始自己编译。要知道,自己编译源代码可不是一件轻松的事,因为在编译过程中经常会出现令人意外而且莫名其妙的错误。 我们使用的操作系统是 Ubuntu 8.04
,使用目前最新版本的QT 4.4.0
,安装起来即为简单,只要运行以下命令就行:sudo apt-get install qt4-dev-tools qt4-doc qt4-qtconfig qt4-demos qt4-designer
sudo apt-get install qtcreator
注意在这个版本的软件包中,qt4-dev-tools
包含了Qt Assistant
及Qt Linguist
等工具,因此不需要单独安装这两个工具。其它的,qt4-doc
是帮助文档,包含了Qt
中各个类库的详细说明以及丰富的例子程序,可以使用Qt Assistant
工具来打开阅读。qt4-qtconfig
是配置Qt
环境的一个对话框,一般默认就行了,很少有必要去更改。qt4-demos
包含很多可以运行起来的可执行文件以及源代码。qt4-designer
是用来设计GUI
界面的设计器。为了连接MySQL
数据库,需要安装连接MySQL
的驱动程序:sudo apt-get install libqt4-sql-mysql
比起在Windows
下安装和配置Qt
的MySQL
驱动来说,简直太方便了。如果还需要其它的没有默认安装的Qt
库,可以在命令行输入 sudo apt-get install libqt4-
然后按tab
键自动补全,就会列出所有以libqt4-
开头的软件包,如下图所示:这些都可以使用一个命令搞定,而不需要自己从源码开始编译。在记不准或不知道名字的情况下,使用tab
键列出所有可选的软件包是一个很实用的小技巧。在我的项目中,还需要画一些数据曲线和统计图表等,而第三方的QWT
库提供了这些功能。同样,只需要一个命令即可完成安装:sudo apt-get install libqwt5-qt4 libqwt5-qt4-dev
这时,打开Qt Designer
,就会发现左边的Widget
列表里面多了“Qwt Widget”
这一组。最后,关于集成开发环境我觉得QDevelop
很不错,它跟Qt Designer
结合的很好,而且有提示类成员函数的功能。运行以下命令安装: sudo apt-get install qdevelop
这样,使用Qdevelop
编写代码和编译、调试,使用Qt Designer
设计界面,开发效率较高
本文转自:http://blog.csdn.net/zhoufanking/article/details/3278790
posted @
2013-04-02 14:06 王海光 阅读(585) |
评论 (0) |
编辑 收藏
今天在安装软件的时候出现了Package has no installation candidate的问题,如:
# apt-get install <packagename>
Reading package lists... Done
Building dependency tree... Done
Package aptitude is not available, but is referred to by another package.
This may mean that the package is missing, has been obsoleted, or
is only available from another source
E: Package <packagename> has no installation candidate
解决方法如下:
# apt-get update
【更新同步安装列表。在这一步之前可能还需要添加新的源,比如
vim /etc/apt/sources.list 我添加了2个
deb http://mirrors.163.com/ubuntu/ hardy main universe
deb http://tw.archive.ubuntu.com/ubuntu/ hardy main universe
】
# apt-get upgrade【升级所有可升级的已安装包?我没做这一步,也可以】
# apt-get install <packagename>
这样就可以正常使用apt-get了~
本文转自:http://hi.baidu.com/lozard/item/03b854f5a8630015e2e3bd0a
posted @
2013-04-02 13:41 王海光 阅读(9631) |
评论 (0) |
编辑 收藏
安装SCIM输入法的步骤
打开终端,在终端输入执行以下命令:
1).终端输入:sudo apt-get remove scim
删除安装系统的时候装的那个scim(个人觉得没什么用,因为一般新安装是操作系统没有装这个)
2)sudo apt-get install scim
然后下载安装scim输入法。下载完了系统会自己装上,中间会有提示,直接y就OK了
3)sudo apt-get install scim-chinese
这一步是最重要的,这是下载安装中文输入法,前面那一步只是下载一个可以运行输入法的平台 ,现在是在这个具体平台上安装
中文输入法
4) sudo nano /etc/X11/Xsession.d /95xinput
这个步骤就利用nano新建一个95xinput的文件 ,该文件的具体内容如下:
/usr/bin/scim -d
XMODIFIERS="@im=SCIM"
export XMODIFIERS
export GTK_IM_MODULE=scim
然后,确定无误后,保存,按ctrl + o,输入文件名保存 ,ctrl+x 退出
5)exit 退出终端
6)在系统管理中:语言支持的键盘输入方式调整为scim
7)重新启动电脑,进入系统通过Crtl+Space切换输入法
本文转自:http://blog.csdn.net/caodesheng110/article/details/7896481
posted @
2013-04-02 13:40 王海光 阅读(443) |
评论 (0) |
编辑 收藏
.h文件:
#include <string>
using namespace std;
class ZBase64
{
public:
/*编码
DataByte
[in]输入的数据长度,以字节为单位
*/
string Encode(const unsigned char* Data,int DataByte);
/*解码
DataByte
[in]输入的数据长度,以字节为单位
OutByte
[out]输出的数据长度,以字节为单位,请不要通过返回值计算
输出数据的长度
*/
string Decode(const char* Data,int DataByte,int& OutByte);
};
.cpp文件:
#include "stdAfx.h"
#include "ZBase64.h"
string ZBase64::Encode(const unsigned char* Data,int DataByte)
{
//编码表
const char EncodeTable[]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
//返回值
string strEncode;
unsigned char Tmp[4]={0};
int LineLength=0;
for(int i=0;i<(int)(DataByte / 3);i++)
{
Tmp[1] = *Data++;
Tmp[2] = *Data++;
Tmp[3] = *Data++;
strEncode+= EncodeTable[Tmp[1] >> 2];
strEncode+= EncodeTable[((Tmp[1] << 4) | (Tmp[2] >> 4)) & 0x3F];
strEncode+= EncodeTable[((Tmp[2] << 2) | (Tmp[3] >> 6)) & 0x3F];
strEncode+= EncodeTable[Tmp[3] & 0x3F];
if(LineLength+=4,LineLength==76) {strEncode+="\r\n";LineLength=0;}
}
//对剩余数据进行编码
int Mod=DataByte % 3;
if(Mod==1)
{
Tmp[1] = *Data++;
strEncode+= EncodeTable[(Tmp[1] & 0xFC) >> 2];
strEncode+= EncodeTable[((Tmp[1] & 0x03) << 4)];
strEncode+= "==";
}
else if(Mod==2)
{
Tmp[1] = *Data++;
Tmp[2] = *Data++;
strEncode+= EncodeTable[(Tmp[1] & 0xFC) >> 2];
strEncode+= EncodeTable[((Tmp[1] & 0x03) << 4) | ((Tmp[2] & 0xF0) >> 4)];
strEncode+= EncodeTable[((Tmp[2] & 0x0F) << 2)];
strEncode+= "=";
}
return strEncode;
}
string ZBase64::Decode(const char* Data,int DataByte,int& OutByte)
{
//解码表
const char DecodeTable[] =
{
, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
, // '+'
, 0, 0,
, // '/'
, 53, 54, 55, 56, 57, 58, 59, 60, 61, // '0'-'9'
, 0, 0, 0, 0, 0, 0,
, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12,
, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // 'A'-'Z'
, 0, 0, 0, 0, 0,
, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // 'a'-'z'
};
//返回值
string strDecode;
int nValue;
int i= 0;
while (i < DataByte)
{
if (*Data != '\r' && *Data!='\n')
{
nValue = DecodeTable[*Data++] << 18;
nValue += DecodeTable[*Data++] << 12;
strDecode+=(nValue & 0x00FF0000) >> 16;
OutByte++;
if (*Data != '=')
{
nValue += DecodeTable[*Data++] << 6;
strDecode+=(nValue & 0x0000FF00) >> 8;
OutByte++;
if (*Data != '=')
{
nValue += DecodeTable[*Data++];
strDecode+=nValue & 0x000000FF;
OutByte++;
}
}
i += 4;
}
else// 回车换行,跳过
{
Data++;
i++;
}
}
return strDecode;
}
使用示例(结合CxImage库):CString CScanDlg::EncodeImage()
{//对图片进行Base64编码
ZBase64 zBase;
//图片编码
CxImage image; // 定义一个CxImage对象
image.Load(this->m_strImgPath, CXIMAGE_FORMAT_JPG); //先装载jpg文件,需要指定文件类型
long size=0;//得到图像大小
BYTE* buffer=0;//存储图像数据的缓冲
image.Encode(buffer,size,CXIMAGE_FORMAT_JPG);//把image对象中的图像以type类型数据copy到buffer
string strTmpResult=zBase.Encode(buffer,size);
CString result;
result = strTmpResult.c_str();
return result;
}
void CScanDlg::DecodeImageData(CString strData)
{//对Base64编码过的数据解码并显示原图片
ZBase64 zBase;
int OutByte=0;
string strTmpResult=zBase.Decode(strData,strData.GetLength(),OutByte);
int i,len = strTmpResult.length();
BYTE *buffer = new BYTE[len];
for (i=0;i<len;++i)
{
buffer[i] = strTmpResult[i];
}
CxImage image(buffer,len,CXIMAGE_FORMAT_JPG);//把内存缓冲buffer中的数据构造成Image对象
delete [] buffer;
CDC* hdc = m_picture.GetDC();
m_bitmap = image.MakeBitmap(hdc->m_hDC);
HBITMAP h0ldBmp = m_picture.SetBitmap(m_bitmap);
if(h0ldBmp) DeleteObject(h0ldBmp);
if(hdc->m_hDC) m_picture.ReleaseDC(hdc);
if(m_bitmap) DeleteObject(m_bitmap);
}
本文转自:http://www.cnblogs.com/phinecos/archive/2008/10/10/1308272.html
posted @
2013-03-20 14:00 王海光 阅读(11204) |
评论 (0) |
编辑 收藏