VC的HAAR图像压缩(小波)

最近帮别人做图像处理的东西,开始涉及一些图像处理的工作,先说第一块吧,图像压缩之哈尔变换
哈尔小波,是比较常见的的小波中的一种,为了能够更加清澈的研究这部分的内容,我特地又跑去啃了一遍线性代数,把矩阵等一堆的东西又复习了一遍,然后google了一遍,发现众多的应用,被各种领域广泛的使用,当然,在科研领域,应用最广泛的还在于研究生的各种论文的翻版。下面就先科普一下数学的知识吧,其实和原来写数字逻辑实验一样,依据应用的场景,建立了公式以后,还得自己做化简从而得到一个终极公式,用于编程。这里也是同理。
9843995670833
依据哈尔的二维矩阵,我们发现,这个矩阵是完备正交的,所以就有很多的特性了,比如说他的转置和他的逆相等,所以我们可以借助以上的公式,来对我们的输入的二维的图像进行处理了。实际上对图像处理的本质,就是将你的图片,每个像素点,存到一个你所设计的内存空间当中,然后利用haar变换矩阵,来对你的矩阵化以后的图像数据,做矩阵的乘法,所得的结果,就是你的变换以后的内容,这里利用哈尔的压缩,有两种方式进行变换,一种是线性的,一种是塔式的,起结果都一样,只是计算当中的过程不一样。
26529608812183
下面贴code,code主要是参考codeproject上一个博士后的论文中用的,编译指令是在PC的体系下的cpu皆可.

  Harr.h
  
  
  #ifndef HAAR_H
  #define HAAR_H
  
  
  class BaseFWT2D;
  
  class Haar : public BaseFWT2D //generic 2D fwt
  {
  public:
   Haar();
  
  private:
   static const float tH[4];
   static const float tG[4];
  
   inline void addsub(__m64 even, __m64 odd, //addsub for transform
   __m64 *lo, __m64 *hi) const;
  
   void transrows(char** dest, char** sour, unsigned int w, unsigned int h) const;
   void transcols(char** dest, char** sour, unsigned int w, unsigned int h) const;
   void synthrows(char** dest, char** sour, unsigned int w, unsigned int h) const;
   void synthcols(char** dest, char** sour, unsigned int w, unsigned int h) const;
  
  
  };
  
  
  
  /*
   usage:
   Haar fwt;
   fwt.init(width,height); //init buffers
   fwt.trans(data,3,30); //fwt transform from data buffer J=3,TH=30, data is intact after fwt
   fwt.synth(data); //fwt synthesis to data buffer
   ...
   ... //keep going multiple times transforming and synthesing
   ...
   fwt.close();
  
  
  */
  inline void Haar::addsub(__m64 even, __m64 odd, __m64 *lo, __m64 *hi) const //even,odd - chars, lo,hi - chars
  {
   char *clo = (char *)lo;
   char *chi = (char *)hi;
  
   __m64 ma, mb, m7F, m01, m128;
   m01.m64_u64 = 0x0101010101010101;
   m7F.m64_u64 = 0x7F7F7F7F7F7F7F7F;
   m128.m64_u64 = 0x8080808080808080;
  
   //char to uchar
   even = _mm_add_pi8(even, m128);
   odd = _mm_add_pi8(odd, m128);
   //char to uchar
  
   ma = _mm_and_si64(even, m01);
   mb = _mm_and_si64(odd, m01);
   //m01 = _mm_or_si64( ma, mb ); // x.5 = x+1
   m01 = _mm_and_si64(ma, mb); // x.5 = x
  
   even = _mm_srli_si64(even, 1); // add/2
   even = _mm_and_si64(even, m7F);
   odd = _mm_srli_si64(odd, 1); // sub/2
   odd = _mm_and_si64(odd, m7F);
  
   ma = _mm_adds_pu8(even, odd);
   ma = _mm_adds_pu8(ma, m01);
   mb = _mm_subs_pi8(even, odd);
  
   //uchar to char
   *lo = _mm_sub_pi8(ma, m128);
   *hi = mb;
  
   _mm_empty();
  }
  
  #endif
  
  Haar.cppp
  #include "stdafx.h"
  
  #include "vec1d.h"
  #include "basefwt.h"
  #include "Haar.h"
  
  
  //haar filter////////////////////////////////////////////////////////////////////////////////////////
  // 0 1 0 1
  const float Haar::tH[4] = { 0.5f, 0.5f, 0.5f, 0.5f };
  // 0 1 0 1
  const float Haar::tG[4] = { 0.5f, -0.5f, 0.5f, -0.5f };
  
  //haar filter////////////////////////////////////////////////////////////////////////////////////////
  
  
  ///////////////////////////////////constructors/destructors///////////////////////////////////////////////////////////////////
  Haar::Haar() : BaseFWT2D(L"haar", tH, 4, 0, tG, 4, 0, tH, 2, 0, tG, 2, 0)
  {
  }
  ///////////////////////////////////constructors/destructors///////////////////////////////////////////////////////////////////
 ///////////////////////////////////////////////transforms/////////////////////////////////////////////////////////////////////
  void Haar::transrows(char** dest, char** sour, unsigned int w, unsigned int h) const
  {
   unsigned int w2 = w / 2;
  
   __m64 m00FF;
   m00FF.m64_u64 = 0x00FF00FF00FF00FF;
  
   for (unsigned int y = 0; y < h;="" y++)="">
  
   __m64 *mlo = (__m64 *) & dest[y][0];
   __m64 *mhi = (__m64 *) & dest[y][w2];
   __m64 *msour = (__m64 *) & sour[y][0];
  
   for (unsigned int k = 0; k < w2="" 8;="" k++)="" {="">
  
   __m64 even = _mm_packs_pu16(_mm_and_si64(*msour, m00FF), _mm_and_si64(*(msour + 1), m00FF)); //even coeffs
   __m64 odd = _mm_packs_pu16(_mm_srli_pi16(*msour, 8), _mm_srli_pi16(*(msour + 1), 8)); //odd coeffs
  
   addsub(even, odd, mlo++, mhi++);
   msour += 2;
   }
  
   if (w2 % 8) {
   for (unsigned int k = w2 - (w2 % 8); k < w2;="" k++)="">
   dest[y][k] = char(((int)sour[y][2*k] + (int)sour[y][2*k+1]) / 2);
   dest[y][k+w2] = char(((int)sour[y][2*k] - (int)sour[y][2*k+1]) / 2);
   }
   }
   }
   _mm_empty();
  }
  void Haar::transcols(char** dest, char** sour, unsigned int w, unsigned int h) const
  {
   unsigned int h2 = h / 2;
  
   for (unsigned int k = 0; k < h2;="" k++)="">
  
   __m64 *mlo = (__m64 *) & dest[k][0];
   __m64 *mhi = (__m64 *) & dest[k+h2][0];
   __m64 *even = (__m64 *) & sour[2*k][0];
   __m64 *odd = (__m64 *) & sour[2*k+1][0];
  
   for (unsigned int x = 0; x < w="" 8;="" x++)="">
  
   addsub(*even, *odd, mlo, mhi);
  
   even++;
   odd++;
   mlo++;
   mhi++;
   }
   }
   _mm_empty();
  
   //odd remainder
   for (unsigned int x = w - (w % 8); x < w;="" x++)="">
   for (unsigned int k = 0; k < h2;="" k++)="" {="">
   dest[k][x] = char(((int)sour[2*k][x] + (int)sour[2*k+1][x]) / 2);
   dest[k+h2][x] = char(((int)sour[2*k][x] - (int)sour[2*k+1][x]) / 2);
   }
   }
  }  

堆and栈(我承认我是看了以后才想起来的)

最近看com,发现一篇相当不错的文章,我承认我是看了以后才想起来原来的种种的,有代码,有汇编,有解释,完全符合个人的审美观点和胃口,今天的晚饭就是它了,相当丰盛。
---------------
堆和栈的区别
一、预备知识—程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)—,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 – 程序结束后有系统释放
4、文字常量区—常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区—存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = “abc”; 栈
char *p2; 栈
char *p3 = “123456”; 123456在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, “123456”); 123456放在常量区,编译器可能会将它与p3所指向的”123456″优化成一个地方。
}
二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。
2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。
2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。
2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。
2.6存取效率的比较
char s1[] = “aaaaaaaaaaaaaaa”;
char *s2 = “bbbbbbbbbbbbbbbbb”;
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = “1234567890”;
char *p =”1234567890″;
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。
2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。
—------------------------------------------
补充:看到这里,不禁会让人想起c#里面的装箱和拆箱,值类型,引用类型,以及.net里面的stack,和heap,从根本来说,他们都是
“堆”and “栈”。

VC ADO 小结

1.导入ado库
在StdAfx.h中,加入如下代码
#import “c:program filescommon filessystemadomsado15.dll”
no_namespace rename(“EOF”,”adoEOF”) rename(“BOF”,”adoBOF”)
2.Com 初试化
在app的InitInstance中,加入
AfxOleInit();(MFC)
或者
CoInitialize(NULL)
如果用了CoInitialize
退出时,要调用CoUninitialize()
注意,如果在线程中也使用了com,那么在线程中也要用CoInitialize初始
3.连接数据库
_ConnectionPtr m_pAppConn;
hResult = m_pAppConn.CreateInstance(_T(“ADODB.Connection”));///创建Connection对象
然后连接之
m_pAppConn->Open(“Provider=Microsoft.Jet.OLEDB.4.0 ;
Data Source = .DataBaseaa.mdb”,
“”,””,adModeUnknown);
BOOL OpenConnect()
{
HRESULT hResult;
CloseConnect();
try
{
hResult = m_pAppConn.CreateInstance(_T(“ADODB.Connection”));///创建Connection对象
if(SUCCEEDED(hResult))
{
m_pAppConn->Open(“Provider=Microsoft.Jet.OLEDB.4.0 ;
Data Source = .DataBaseaa.mdb”,
“”,””,adModeUnknown);
}
}
catch(_com_error e)///捕捉异常
{
CString errormessage;
errormessage.Format(_T(“连接数据库失败!rn错误信息:%s”),e.ErrorMessage());
AfxMessageBox(errormessage);
hResult = -1L;
}
return (SUCCEEDED(hResult) ? TRUE : FALSE);
}
这里连接的数据库是access数据库,在工程目录下的DataBaseaa.mdb
关键连接的字符窜,
如果是access
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=192.168.1.1DataBaseaa.mdb;
这是局域网上的文件
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=.DataBaseaa.mdb;
本机上的
如果是sql 2000
Provider=SQLOLEDB.1;Persist Security Info=True;User ID=sa;Password=sa;Initial Catalog=aa;Data Source=192.168.1.1;
数据库在192.168.1.1上,数据库名字是aa
4.关闭连接
BOOL CloseConnect()
{
HRESULT hResult=0;
try
{
if(m_pAppConn!=NULL)
{
if(m_pAppConn->State!=adStateClosed)
{
hResult=m_pAppConn->Close();
}
m_pAppConn.Release();
}
}
catch(_com_error e)
{
_bstr_t bstrSource(e.Source());
_bstr_t bstrDescription(e.Description());
TRACE(_T(“n Source : %s n Description : %s n”),(LPCSTR)bstrSource,(LPCSTR)bstrDescription);
hResult=-1L;
}
return (SUCCEEDED(hResult) ? TRUE : FALSE);
}
5.使用recodeset打开记录
_variant_t RecordsAffected;
_RecordsetPtr pRecordset = NULL;
strSql = _T(“SELECT field FROM table”);
pRecordset.CreateInstance(_uuidof(Recordset));
pRecordset = pConn->Execute (_bstr_t(strSql) , &RecordsAffected , adCmdUnknown);
其中&RecordsAffected 可以获得有多少记录返回,这是记录的影像数目
6.关闭记录集
if(pRecordset != NULL && pRecordset->State)
{
pRecordset->Close();
pRecordset = NULL;
}
7.判断是否为空
if (pRecordset->adoBOF && pRecordset->adoEOF)
{
//MessageBox(“没有符合条件的记录存在!”,”提示”);
if(pRecordset != NULL && pRecordset->State)
{
pRecordset->Close();
pRecordset = NULL;
}
return;
}
8,从记录集取数据
_variant_t var;
pRecordset->MoveFirst();
for(;!pRecordset->adoEOF;pRecordset->MoveNext())
{
var = pRecordset->GetCollect(_T(“field”));
}
9.几种常见数据的转换
如果是字符窜的字段
var = pRecordset->GetCollect(_T(“field”));
if(var.vt!=VT_NULL)
{
str= (LPCTSTR)_bstr_t(var);
}
if(var.vt!=VT_NULL)
判断是必须的,如果是空,转换会出错
如果是int形
int aa = atoi(str)
_variant_t是个可变类型,支持很多种类型,
10.使用command
利用Command对象来执行SQL命令
_CommandPtr m_pCommand;
m_pCommand.CreateInstance(“ADODB.Command”);
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数
m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它
m_pCommand->CommandText = “SELECT * FROM users”;///命令字串
m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
如果使用记录集的open来打开command对象.
如果在 Source 参数中传送 Command 对象并且同时传递 ActiveConnection 参数,那么将产生错误。Command 对象的 ActiveConnection 属性
必须已经设置为有效的 Connection 对象或者连接字符串。
所以
_variant_t vNULL;
vNULL.vt = VT_ERROR;
vNULL.scode = DISP_E_PARAMNOTFOUND;
_CommandPtr pCommand;

m_pRecordset->Open(_variant_t( (IDispatch*)pCommand, true),vNULL,CursorType, LockType, lOption )
11.关于数据中时间的处理
首先,sql语句中有很多时间处理的函数,可以拿来使用,如果不使用这些函数,那么直接用sql语句来拼写
CString strDate= “2006-8-11″;
CString strsql;
strsql.Format(“SELECT * FROM Table where Date=#%s#”,strDate);
m_pRecordset->Open(_bstr_t(strsql),
theApp.m_pConnection.GetInterfacePtr(),
adOpenDynamic,
adLockOptimistic,
adCmdText);
accee用#时间,其他大部分都是用”来括的
如果返回得到一个时间,那么
CString类型的变量转化成COleDateTime
COleDateTime::ParseDateTime
或者
CString str = “2004-07-08 11:22:33″;
COleVariant VariantTime;
VariantTime = str;
VariantTime.ChangeType(VT_DATE);
COleDateTime DataTime = VariantTime;
反过来转,更简单了,
COleDateTime有format 函数
,包括CTime也有这样的函数
12,插入或者删除记录
strSql.Format(_T(” INSERT INTO table VALUES(‘%s’,’%s’,’%s’,’%s’,#%s#)”),
strID,strName,strAuthor,strPublisher,strDate);
try
{
pConn->Execute (_bstr_t(strSql) , &RecordsAffected , adCmdUnknown);
}
删除也是类似的
添加删除的话,是不返回记录集的,其他的地方,和查询是一样的

自己做的简单的socket原始套接字截获ip数据包

  看了一些网络上的资料,做课程实习搞了个
  供大家参考
  
#include “stdafx.h”
#include
#include
#include
#include
#include “stdio.h”
  
  #pragma comment(lib,”ws2_32.lib”)
  #pragma comment(lib,”netapi32.lib”)
  #pragma comment(lib,”wsock32.lib”)
  
  #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
  #define BUFFER_SIZE 65535
  
  typedef struct _IP_HEADER //定义IP头
  {
   union
   {
   BYTE Version; //版本(前4位)
   BYTE HdrLen; //报头标长(后4位),IP头的长度
   };
   BYTE ServiceType; //服务类型
   WORD TotalLen; //总长度
   WORD ID; //标识
   union
   {
   WORD Flags; //标志(前3位)
   WORD FragOff; //分段偏移(后13位)
   };
   BYTE TimeToLive; //生命期
   BYTE Protocol; //协议
   WORD HdrChksum; //头校验和
   DWORD SrcAddr; //源地址
   DWORD DsrAddr; //目的地址
   BYTE Options; //选项
  }IP_HEADER;
  
  char buffer[BUFFER_SIZE];
  
  int main(int argc, char* argv[])
  {
  
   WORD wVersionRequested;
   WSADATA WsaData;
   int err;
   wVersionRequested = MAKEWORD( 2, 2 );
   err = WSAStartup(wVersionRequested,&WsaData);
   if ( err != 0 )
   {
   return 0;
   }
   if ( LOBYTE( WsaData.wVersion ) != 2 || HIBYTE( WsaData.wVersion ) != 2 )
   {
   WSACleanup();
   return 0;
   }
  
  
   ofstream ofs(argv[1],ios::app);
   /*创建原始套接字*/
   SOCKET sock;
   sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED);
  // sock=socket(AF_INET,SOCK_RAW,IPPROTO_IP);
  
   /*设置IP头操作选项*/
   BOOL flag=true;
   setsockopt(sock,IPPROTO_IP,IP_HDRINCL,(char*)&flag,sizeof(flag));
  
   /*socket初始化*/
   /*获取主机名*/
   char hostName[128];
   gethostname(hostName,128);
   /*获取本机IP地址*/
   hostent *pHostIP;
   pHostIP=gethostbyname(hostName);
   /*填充SOCKADDR_IN结构的内容*/
   //cout<><>
   sockaddr_in addr_in;
   addr_in.sin_addr=*(in_addr *)(pHostIP->h_addr_list[0]);
   addr_in.sin_family=AF_INET;
   addr_in.sin_port=htons(6000);
  /*绑定socket*/
   bind(sock,(PSOCKADDR)&addr_in,sizeof(addr_in));
  
  /*设置网卡为混杂模式*/
   DWORD dwBufferLen[10];
   DWORD dwBufferInLen=1;
   DWORD dwBytesReturned=0;
   WSAIoctl(sock,SIO_RCVALL,&dwBufferInLen,sizeof(dwBufferInLen),&dwBufferLen,
   sizeof(dwBufferLen),&dwBytesReturned,NULL,NULL);
  
  /*接收数据包*/
  while(true)
  {
   int DF;
   int MF;
   recv(sock,buffer,BUFFER_SIZE,0); //接收数据抱
   IP_HEADER ip=*(IP_HEADER *)buffer;
   /*解析数据包*/
   ofs<><(ip.version>>4)
   ofs<><(ip.hdrlen &="">
   ofs<><(ip.servicetype>>5)
   ofs<><((ip.servicetype>>1) & 0x0f)
   ofs<><>
   ofs<><>
   ofs<><(df=((ip.flags>>14)&0x01))
   ofs<><(mf=((ip.flags>>13)&0x01))<>
   ofs<><(ip.fragoff>
   ofs<><>
   ofs<><>
   ofs<><>
   ofs<><>
   ofs<><>
  
   cout<><(ip.version>>4)
   cout<><(ip.hdrlen &="">
   cout<><(ip.servicetype>>5)
   cout<><((ip.servicetype>>1) & 0x0f)
   cout<><>
   cout<><>
   cout<><(df=((ip.flags>>14)&0x01))
   cout<><(mf=((ip.flags>>13)&0x01))<>
   cout<><(ip.fragoff>
   cout<><>
   cout<><>
   cout<><>
   cout<><>
   cout<><>
  
  
  }
  return 0;
  }
  
  
  
  

分类:VC++ 相关 | 评论:0 | 浏览:1583 | 举报 | 收藏 |

网友评论:我要评论
暂无评论!

发布评论:









指针与引用的区别

    指针与引用看上去完全不同(指针用操作符’*’和’->’,引用使用操作符’.’),但是它们似乎有相同的功能。指针与引用都是让你间接引用其他对象。你如何决定在什么时候使用指针,在什么时候使用引用呢?
  
    首先,要认识到在任何情况下都不能用指向空值的引用。一个引用必须总是指向某些对象。因此如果你使用一个变量并让它指向一个对象,但是该变量在某些时候也可能不指向任何对象,这时你应该把变量声明为指针,因为这样你可以赋空值给该变量。相反,如果变量肯定指向一个对象,例如你的设计不允许变量为空,这时你就可以把变量声明为引用。
  
    “但是,请等一下”,你怀疑地问,“这样的代码会产生什么样的后果?”
  
    char *pc = 0; // 设置指针为空值
  
    char& rc = *pc; // 让引用指向空值
  
    这是非常有害的,毫无疑问。结果将是不确定的(编译器能产生一些输出,导致任何事情都有可能发生),应该躲开写出这样代码的人除非他们同意改正错误。如果你担心这样的代码会出现在你的软件里,那么你最好完全避免使用引用,要不然就去让更优秀的程序员去做。我们以后将忽略一个引用指向空值的可能性。
  
    因为引用肯定会指向一个对象,在C里,引用应被初始化。
  
    string& rs; // 错误,引用必须被初始化
    string s(“xyzzy”);
    string& rs = s; // 正确,rs指向s
  
    指针没有这样的限制。
    string *ps; // 未初始化的指针
     // 合法但危险
  
    不存在指向空值的引用这个事实意味着使用引用的代码效率比使用指针的要高。因为在使用引用之前不需要测试它的合法性。
  
    void printDouble(const double& rd)
    {
     cout < rd;="">
    }       // 肯定指向一个double值
  
    相反,指针则应该总是被测试,防止其为空:
  
    void printDouble(const double *pd)
    {
     if (pd)
     {// 检查是否为NULL
      cout <>
     }
    }
  
    指针与引用的另一个重要的不同是指针可以被重新赋值以指向另一个不同的对象。但是引用则总是指向在初始化时被指定的对象,以后不能改变。
  
    string s1(“Nancy”);
    string s2(“Clancy”);
  
   string& rs = s1; // rs 引用 s1
    string *ps = &s1; // ps 指向 s1
    rs = s2; // rs 仍旧引用s1
     // 但是 s1的值现在是”Clancy”
  
   ps = &s2; // ps 现在指向 s2;// s1 没有改变
  
    总的来说,在以下情况下你应该使用指针,一是你考虑到存在不指向任何对象的可能(在这种情况下,你能够设置指针为空),二是你需要能够在不同的时刻指向不同的对象(在这种情况下,你能改变指针的指向)。如果总是指向一个对象并且一旦指向一个对象后就不会改变指向,那么你应该使用引用。
  
    还有一种情况,就是当你重载某个操作符时,你应该使用引用。最普通的例子是操作符[]。这个操作符典型的用法是返回一个目标对象,其能被赋值。
  
    vector v(10); //建立整形向量(vector),大小为10
     //向量是一个在标准C库中的一个模板(见条款35)
    v[5] = 10; // 这个被赋值的目标对象就是操作符[]返回的值
  
    如果操作符[]返回一个指针,那么后一个语句就得这样写:
  
    *v[5] = 10;
  
    但是这样会使得v看上去象是一个向量指针。因此你会选择让操作符返回一个引用。  
    当你知道你必须指向一个对象并且不想改变其指向时,或者在重载操作符并为防止不必要的语义误解时,你不应该使用指针。而在除此之外的其他情况下,则应使用指针 。
   
  

分类:VC++ 相关 | 评论:0 | 浏览:326 | 举报 | 收藏 |

上一篇:COM技术纵横谈
网友评论:我要评论
暂无评论!

发布评论:









c++下的类型转换

  这四个操作符是, static_cast,const_cast, dynamic_cast, 和reinterpret_cast。在大多数情况下,对于这些操作符你只需要知道原来你习惯于这样写,(type)expression而现在你总应该这样写:
  
    static_cast(expression)
  
    例如,假设你想把一个int转换成double,以便让包含int类型变量的表达式产生出浮点数值的结果。如果用C风格的类型转换,你能这样写:
  
    int firstNumber, secondNumber;
    …
    double result = ((double)firstNumber)/secondNumber;
  
    如果用上述新的类型转换方法,你应该这样写:
  
    double result = static_cast(firstNumber)/secondNumber;
  
    这样的类型转换不论是对人工还是对程序都很容易识别。
  
    static_cast 在功能上基本上与C风格的类型转换一样强大,含义也一样。它也有功能上限制。例如,你不能用static_cast象用C风格的类型转换一样把struct转换成int类型或者把double类型转换成指针类型,另外,static_cast不能从表达式中去除const属性,因为另一个新的类型转换操作符const_cast有这样的功能。
  
    其它新的C++类型转换操作符被用在需要更多限制的地方。const_cast 用于类型转换掉表达式的const或volatileness属性。通过使用const_cast,你向人们和编译器强调你通过类型转换想做的只是改变一些东西的constness或者 volatileness属性。这个含义被编译器所约束。如果你试图使用const_cast来完成修改constness 或者volatileness属性之外的事情,你的类型转换将被拒绝。下面是一些例子:
  
    class Widget { … };
    class SpecialWidget: public Widget { … };
    void update(SpecialWidget *psw);
    SpecialWidget sw; // sw 是一个非const 对象
    const SpecialWidget& csw = sw; // csw 是sw的一个引用
     // 它是一个const 对象
  
    update(&csw); // 错误!不能传递一个const SpecialWidget* 变量
     // 给一个处理SpecialWidget*类型变量的函数
  
    update(const_cast(&csw));
       // 正确,csw的const被显示地转换掉(
       // csw和sw两个变量值在update
      // 函数中能被更新)
  
    update((SpecialWidget*)&csw);
     // 同上,但用了一个更难识别
     // 的C风格的类型转换
  
    Widget *pw = new SpecialWidget;
  
    update(pw); // 错误!pw的类型是Widget*,但是
     // update函数处理的是SpecialWidget*类型
  
    update(const_cast(pw));
     // 错误!const_cast仅能被用在影响
     // constness or volatileness的地方上。
     // 不能用在向继承子类进行类型转换。
  
    到目前为止,const_cast最普通的用途就是转换掉对象的const属性。
  
    第二种特殊的类型转换符是dynamic_cast,它被用于安全地沿着类的继承关系向下进行类型转换。这就是说,你能用dynamic_cast把指向基类的指针或引用转换成指向其派生类或其兄弟类的指针或引用,而且你能知道转换是否成功。失败的转换将返回空指针(当对指针进行类型转换时)或者抛出异常(当对引用进行类型转换时):
  
    Widget *pw;
    …
    update(dynamic_cast(pw));
     // 正确,传递给update函数一个指针
     // 是指向变量类型为SpecialWidget的pw的指针
     // 如果pw确实指向一个对象,
    // 否则传递过去的将使空指针。
    void updateViaRef(SpecialWidget& rsw);
    updateViaRef(dynamic_cast(*pw));
     //正确。 传递给updateViaRef函数
     // SpecialWidget pw 指针,如果pw
     // 确实指向了某个对象
    // 否则将抛出异常
  
    dynamic_casts在帮助你浏览继承层次上是有限制的。它不能被用于缺乏虚函数的类型上(参见条款24),也不能用它来转换掉constness:
  
    int firstNumber, secondNumber;
    …
    double result = dynamic_cast(firstNumber)/secondNumber;
     // 错误!没有继承关系
    const SpecialWidget sw;
    …
    update(dynamic_cast(&sw));
     // 错误! dynamic_cast不能转换
     // 掉const
  
    如你想在没有继承关系的类型中进行转换,你可能想到static_cast。如果是为了去除const,你总得用const_cast。
  
    这四个类型转换符中的最后一个是reinterpret_cast。这个操作符被用于的类型转换的转换结果几乎都是实现时定义(implementation-defined)。因此,使用reinterpret_casts的代码很难移植。
  
    reinterpret_casts的最普通的用途就是在函数指针类型之间进行转换。例如,假设你有一个函数指针数组:
  
    typedef void (*FuncPtr)(); // FuncPtr is 一个指向函数
     // 的指针,该函数没有参数
     // 也返回值类型为void
    FuncPtr funcPtrArray[10]; // funcPtrArray 是一个能容纳
     // 10个FuncPtrs指针的数组
  
    让我们假设你希望(因为某些莫名其妙的原因)把一个指向下面函数的指针存入funcPtrArray数组:
  
    int doSomething();
  
    你不能不经过类型转换而直接去做,因为doSomething函数对于funcPtrArray数组来说有一个错误的类型。在FuncPtrArray数组里的函数返回值是void类型,而doSomething函数返回值是int类型。
  
    funcPtrArray[0] = &doSomething; // 错误!类型不匹配
  
    reinterpret_cast
  
    可以让你迫使编译器以你的方法去看待它们:
  
    funcPtrArray[0] = // this compiles
    reinterpret_cast(&doSomething);
  
    转换函数指针的代码是不可移植的(C++不保证所有的函数指针都被用一样的方法表示),在一些情况下这样的转换会产生不正确的结果(参见条款31),所以你应该避免转换函数指针类型,除非你处于着背水一战和尖刀架喉的危急时刻。一把锋利的刀。一把非常锋利的刀。
  
    如果你使用的编译器缺乏对新的类型转换方式的支持,你可以用传统的类型转换方法代替static_cast, const_cast, and reinterpret_cast。也可以用下面的宏替换来模拟新的类型转换语法:
  
    #define static_cast(TYPE,EXPR) ((TYPE)(EXPR))
    #define const_cast(TYPE,EXPR) ((TYPE)(EXPR))
    #define reinterpret_cast(TYPE,EXPR) ((TYPE)(EXPR))
  
    你可以象这样使用使用:
  
    double result = static_cast(double, firstNumber)/secondNumber;
    update(const_cast(SpecialWidget*, &sw));
    funcPtrArray[0] = reinterpret_cast(FuncPtr, &doSomething);
  
    这些模拟不会象真实的操作符一样安全,但是当你的编译器可以支持新的的类型转换时它们可以简化你把代码升级的过程。
  
    没有一个容易的方法来模拟dynamic_cast的操作,但是很多函数库提供了函数,安全地在派生类与基类之间的进行类型转换。如果你没有这些函数而你有必须进行这样的类型转换,你也可以回到C风格的类型转换方法上,但是这样的话你将不能获知类型转换是否失败。当然,你也可以定义一个宏来模拟dynamic_cast的功能,就象模拟其它的类型转换一样:
  
    #define dynamic_cast(TYPE,EXPR) (TYPE)(EXPR)
  
    请记住,这个模拟并不能完全实现dynamic_cast的功能,它没有办法知道转换是否失败。
  
    我知道,是的,我知道,新的类型转换操作符不是很美观而且用键盘键入也很麻烦。如果你发现它们看上去实在令人讨厌,C风格的类型转换还可以继续使用并且合法。然而正是因为新的类型转换符缺乏美感才能使它弥补了在含义精确性和可辨认性上的缺点,并且使用新类型转换符的程序更容易被解析(不论是对人工还是对于工具程序),它们允许编译器检测出原来不能发现的错误。这些都是放弃C风格类型转换方法的强有力的理由,还有第三个理由:也许让类型转换符不美观和键入麻烦是一件好事。
  

分类:VC++ 相关 | 评论:0 | 浏览:310 | 举报 | 收藏 |

网友评论:我要评论
暂无评论!

发布评论:









windows的消息宏定义

    MFC的设计者们在设计MFC时,紧紧把握一个目标,那就是尽可能使得MFC的代码要小,速度尽可能快。为了这个目标,他们使用了许多技巧,其中很多技巧体现在宏的运用上,实现MFC的消息映射的机制就是其中之一。
  
    同MFC消息映射机制有关的宏有下面几个:
  
    DECLARE_MESSAGE_MAP()宏
  
    BEGIN_MESSAGE_MAP(theClass, baseClass)和END_MESSAGE_MAP()宏
  
    弄懂MFC消息映射机制的最好办法是将找出一个具体的实例,将这些宏展开,并找出相关的数据结构。
  
    DECLARE_MESSAGE_MAP()
  
    DECLARE_MESSAGE_MAP()宏的定义如下:
  
    #define DECLARE_MESSAGE_MAP()
  
    private:
  
    static const AFX_MSGMAP_ENTRY _messageEntries[];
  
    protected:
  
    static AFX_DATA const AFX_MSGMAP messageMap;
  
    virtual const AFX_MSGMAP* GetMessageMap() const;
  
  从上面的定义可以看出,DECLARE_MESSAGE_MAP()作下面三件事:
  
    定义一个长度不定的静态数组变量_messageEntries[];
  
    定义一个静态变量messageMap;
  
    定义一个虚拟函数GetMessageMap();
  
  在DECLARE_MESSAGE_MAP()宏中,涉及到MFC中两个对外不公开的数据结构
  
  AFX_MSGMAP_ENTRY和AFX_MSGMAP。为了弄清楚消息映射,有必要考察一下这两个数据结构的定义。
  
    AFX_MSGMAP_ENTRY的定义
  
    struct AFX_MSGMAP_ENTRY
  
    {
  
     UINT nMessage; // windows message
  
     UINT nCode; // control code or WM_NOTIFY code
  
     UINT nID; // control ID (or 0 for windows messages)
  
     UINT nLastID; // used for entries specifying a range of control id’s
  
     UINT nSig; // signature type (action) or pointer to message #
  
     AFX_PMSG pfn; // routine to call (or special value)
  
    };
  
  结构中各项的含义注释已经说明得很清楚了,这里不再多述,从上面的定义你是否看出,AFX_MSGMAP_ENTRY结构实际上定义了消息和处理此消息的动作之间的映射关系。因此静态数组变量_messageEntries[]实际上定义了一张表,表中的每一项指定了相应的对象所要处理的消息和处理此消息的函数的对应关系,因而这张表也称为消息映射表。再看看AFX_MSGMAP的定义。
  
    (2)AFX_MSGMAP的定义
  
    struct AFX_MSGMAP
  
    {
  
     const AFX_MSGMAP* pBaseMap;
  
     const AFX_MSGMAP_ENTRY* lpEntries;
  
     };
  
  不难看出,AFX_MSGMAP定义了一单向链表,链表中每一项的值是一指向消息映射表的指针(实际上就是_messageEntries的值)。通过这个链表,使得在某个类中调用基类的的消息处理函数很容易,因此,“父类的消息处理函数是子类的缺省消息处理函数”就“顺理成章”了。在后面的“MFC窗口的消息处理”一节中会对此作详细的讲解。
  
    BEGIN_MESSAGE_MAP()和END_MESSAGE_MAP()
  
    它们的定义如下:
  
    #define BEGIN_MESSAGE_MAP(theClass, baseClass)
  
    const AFX_MSGMAP* theClass::GetMessageMap() const
  
    { return &theClass::messageMap; }
  
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP theClass::messageMap =
  
    { &baseClass::messageMap, &theClass::_messageEntries[0] };
  
    AFX_COMDAT const AFX_MSGMAP_ENTRY theClass::_messageEntries[] =
  
    {
  
     #define END_MESSAGE_MAP()
  
     {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
  
     };
  
  对应BEGIN_MESSAGE_MAP()的定义可能不是一下子就看得明白,不过不要紧,举一例子就很清楚了。对于BEGIN_MESSAGE_MAP(CView, CWnd),VC预编译器将其展开成下面的形式:
  
    const AFX_MSGMAP* CView::GetMessageMap() const
  
    {
  
     return &CView::messageMap;
  
     }
  
    AFX_COMDAT AFX_DATADEF const AFX_MSGMAP CView::messageMap =
  
    {
  
     &CWnd::messageMap,
  
     &CView::_messageEntries[0]
  
    };
  
    AFX_COMDAT const AFX_MSGMAP_ENTRY CView::_messageEntries[] =
  
    {
  
    至于END_MESSAGE_MAP()则不过定义了一个表示映射表结束的标志项,我想大家对于这种简单的技巧应该是很熟悉的,无需多述。
  到此为止,我想大家也已经想到了象ON_COMMAND这样的宏的具体作用了,不错它们只不过定义了一种类型的消息映射项,看看ON_COMMAND的定义:
  
    #define ON_COMMAND(id, memberFxn)
  
    { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv, (AFX_PMSG)&memberFxn },
  
     根据上面的定义,ON_COMMAND(ID_FILE_NEW, OnFileNew)将被VC预编译器展开
  
     如下:
  
    {WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSig_vv,
  
    (AFX_PMSG)&OnFileNew},
  
  到此,MFC的消息映射机制已经清楚了,现在提出并解答两个问题以作为对这一节的小结。
  
    为什么不直接使用虚拟函数实现消息处理函数呢?这是一个GOOD QUESTION。前面已经说过,MFC的设计者们在设计MFC时有一个很明确的目标,就是使得“MFC的代码尽可能小,速度尽可能快”,如果采用虚拟函数,那么对于所有的窗口消息,都必须有一个与之对应的虚拟函数,因而对每一个从CWnd派生的类而言,都会有一张很大的虚拟函数表vtbl。但是在实际应用中,一般只对少数的消息进行处理,大部分都交给系统缺省处理,所以表中的大部分项都是无用项,这样做就浪费了很多内存资源,这同MFC设计者们的设计目标是相违背的。当然,MFC所使用的方法只是解决这类问题的方式之一,不排除还有其他的解决方式,但就我个人观点而言,这是一种最好的解决方式,体现了很高的技巧性,值得我们学习。
  
    至于这第二个问题,是由上面的问题引申出来的。如果在子类和父类中出现了相同的消息出来函数,VC编译器会怎么处理这个问题呢?VC不会将它们看作错误,而会象对待虚拟函数类似的方式去处理,但对于消息处理函数(带afx_msg前缀),则不会生成虚拟函数表vtbl。
  

c++cli

C++/CLI描绘的是一种多元组合,此处的 C++ 当然是指 Bjarne Stroustrup 在 Bell 实验室发明的C++编程语言。它支持速度和执行 文件的大小都得到优化的静态对象模型。但除了堆内存分配以外,它不支持运行时程序对对象的更改。它允许对底层机器进行无限制的访问,但对于正在运行的程序中的活动类型、以及相关的程序基础构造,它的访问能力却非常有限、或者根本就不可能。我在微软的同事 Herb Sutter,也是C++/CLI的主架构师,认为C++是一个混凝土语言。
“CLI”即公共语言基础结构(Common Language Infrastructure),这是一个支持动态组件编程模型的多层架构。在许多方面,它所表示的对象模型和C++的完全相反。它是一个运行时软件层 ,一个虚拟执行系统,运行在应用程序和底层操作系统之间。对底层机器的访问受到相当严格的限制。支持对运行中程序的活动类型以及关联程序的基础构造 进行存取——发现和建构。斜线“/”表示 C++ 和 CLI 之间的一种绑定(binding),有关这种绑定的细节构成本专栏的常规主题。
所以,对于“什么是C++/CLI?”这个问题第一个最近似的答案是:它是静态C++对象模型到动态CLI组件对象模型的一种绑定, 简言之,它就是你如何用C++进行.NET编程,而不是用C#或Visual Basic.NET。象C#和CLI自己一样,C++/CLI正在经历 ECMA(欧洲计算机制造商协会) 标准化并最终要经历ISO标准认证。
公共语言运行时(CLR)是微软版的CLI,专门用于 Windows 操作系统,同样,Visual C++ 2005是C++/CLI 的实现。
第二个近似答案是:我觉得C++/CLI在C++内集成.NET编程模型与以前贝尔实验室在当时的C++中用模板集成泛型编程一样有异曲同工之处。两种情况中,你在现有C++代码库上的投资以及你现有的C++专业技术都得到保护。这是C++/CLI设计的一个基本 要求。
学习C++/CLI
一种C++/CLI语言的设计有三个层面,这三个层面也适用于所有语言:语言层语法到公共类型系统(CTS) 的映射;选择为程序员直接操作而公开的底层CLI基本组织结构 的详细程度;以及选择要提供的超越CLI直接支持的附加功能。
第一个层面是所有CLI语言在很大程度上都共有的,第二个层面和第三层面是某一CLI语言区别于其它语言的地方。根据所要解决的问题,你可以选择某一 种语言,也可以将多种CLI语言结合起来。学习C++/CLI语言需要掌握这三个设计层面。
怎样将C++/CLI 映射到CTS?
了解底层CTS 对学习C++/CLI非常有帮助,它主要包括三个常规类类型:
多态引用类型,其用于所有的类继承;
非多态值类型,其用于实现需要运行时效率的具体类型,如数字类型;
抽象接口类型,其用于定义一个实现该接口的一组引用类型或值类型共同使用的公共操作集;
在设计方面,虽然CTS到一组内置的语言类型的映射对于所有CLI语言来说都是共同的,当然,每一种CLI语言的语法各不相同。例如,在C#中,我们可以 这样来定义一个抽象基类 Shape,从这个类派生特定的几何模型对象。
abstract class Shape { … } // C#
而在C++/CLI中,可以象下面这样写,以表示完全相同的底层引用类型:
ref class Shape abstract { … }; // C++/CLI
在底层 IL(中间语言)中,以上两种声明以完全相同的方式表示。同样,在C#中,我们可以用下面的代码来定义一个具体的 Point2D 类 :
struct Point2D { … } // C#
而在C++/CLI中写成:
value class Point2D { … }; // C++/CLI
借助 C++/CLI 支持的类类型家族表现了一种本机方式的 CTS 集成。它确定了你的语法选择,例如:
class native {};
value class V {};
ref class R {};
interface class I {};
CTS 也支持枚举类类型,其行为方式与本机枚举稍微有些区别,C++/CLI对二者都提供支持:
enum native { fail, pass };
enum class CLIEnum : char { fail, pass};
同样,CTS支持其自己的数组类型,其行为也与本机数组类型有一定差别,微软同样对二者提供支持:
int native[] = { 1,1,2,3,5,8 };
array<int>^ managed = { 1,1,2,3,5,8 };
那种认为任何一种 CLI 语言比另一种语言更接近或几乎就是到底层CLI的映射是不精确的。相反,每一种CLI语言都只是表达了自己对底层CLI对象模型的一种 见解。在下一节你将更清楚地看到这一点。
CLI 的细节标准
在设计CLI语言时必须考虑的第二个设计层面是要将什么程度的底层CLI实现模型结合到该语言中。这个语言解决什么样的问题?要解决这些问题必须要什么样的工具? 此外,该语言很可能吸引哪一类程序员?
下面,我们利用发生在托管堆中的值类型问题。在许多情况下,值类型可以在托管堆中找到自己:
通过隐式的框入/框出操作(boxing)——当值类型的某个实例被赋值给一对象时,或者通过某个未被改写的值类型调用一个虚拟方法时;
当值类型被当作为引用类类型的成员时;
当值类型被当作CLI数组元素存储时;
是否允许程序员处理这种值类型地址是设计CLI语言时必须要解决的问题。
存在的问题?
位于托管堆中的任何对象在垃圾回收器进行清扫收缩的过程中都有可能遭遇重新分配,指向这些对象的任何指针必须被追踪并在运行时得到更新,而程序员 无法自己手动追踪它们,因此,如果你被允许用某个可能在托管堆中的值类型的地址,那么除了本机指针外,还需要一个追踪形态的指针。
到底该怎样去权衡呢?一方面,需要考虑简洁和安全。直接引入对一个或一组追踪指针的支持会使语言变得更复杂。如果不提供这种支持,由于所需的复杂程度降低,从而可以找到的程序员人群就会增加。此外,允许程序员访问这些生命期短暂的值类型,则增加了程序员出错的可能性。她经意或不经意地对内存做一些危险动作。不支持追踪指针,可以潜在地创建较安全的运行时环境。
另一方面,必须考虑效率和灵活性。每次将值类型赋值给相同的对象,该值都会发生新的框入/框出操作。允许访问这种经过框入/框出操作的值类型 ,就允许在内存中进行更新操作,这样便可能提供重要的性能改进。没有某种形式的追踪指针,你将无法用指针算法遍历CLI数组,这意味着CLI数组将不能 融入STL(标准模板库)中的迭代器模式,也无法与泛型算法协同工作。允许访问框入/框出值类型将会大大提高设计的灵活性。
在C++/CLI 中,微软选择提供一系列在托管堆中处理值类型的寻址模式:
int ival = 1024;
int^ boxedi = ival;
array<int>^ ia = gcnew array<int>{1,1,2,3,5,8};
interior_ptr<int> begin = &ia[0];
value struct smallInt { int m_ival; … } si;
pin_ptr<int> ppi = &si.m_ival;
典型的 C++/CLI 开发人员是一个经验丰富的系统程序员,其任务是提供底层架构以及作为基础的核心应用,以此为基础来构建未来。她必须解决可伸缩性和性能相关的问题,并且必须从系统一级来看待底层 CLI。某种 CLI 语言的细节标准反映了其程序员的面貌。
复杂性本身并不是对质量的否定,人类生命比单核细胞复杂得多,这当然不是一件坏事,然而,当单一概念的表达变得复杂化以后,这常常被认为是一件坏事。在C++/CLI中,CLI开发团队已经 尝试提供一种优雅的方式来表达一个复杂的主体。
附加功能
第三个设计层面是特定语言层功能要超过被CLI直接支持的功能,这样就需要建立一种语言层支持与CLI底层实现模型之间的映射。 在某些情况下,这是做不到的,因为该语言无法调解CLI的行为,在基类的构造函数和析构函数中解决虚函数便是例子。为了在这种情况中反映ISO-C++语义,需要在每个基类的构造函数和析构函数中 重新安排虚表。这是不可能的,因为虚表操作是由运行时托管的,而非单独的语言托管。 因此,这一设计层面是优越性和可行性的折中。C++/CLI 提供的附加功能主要有三个方面:
引用类型的资源获取(Resource Acquisition)形式是Initialization(RAII), 尤其是为被称作占据稀有资源的垃圾回收类型确定性终止化(deterministic finalization)提供一个自动化的机制;
与C++拷贝构造函数和拷贝赋值操作符相关的深度拷贝语义形式,但它不能扩展到值类型;
除了 CLI泛型机制之外——这原来是我第一个专栏的主题,还为CTS类型提供C++模板的直接支持,另外,还提供用于 CLI 类型的 STL 可验证版本;
让我们看一个简单的例子:确定性终止化问题。与对象关联的内存被垃圾回收器回收之前,若存在与之相关连的 Finalize 方法,该方法将会被调用。你可以把 该方法看作是一种超级析构函数,因为它不依赖于该对象程序的生命期,它被称为终止化。调用 Finalize 方法的时间,甚至是否调用它是未定义的。这就是垃圾回收器不确定的终止化操作含义之所在。
不确定性终止化在进行动态内存管理时可以很有效地工作,当可用内存空间严重不足时,垃圾回收器会发挥作用并解决问题。但是当对象涉及的是某些重要资源,比如数据库连接、某种 类型的锁、本地堆内存时,

VC++ADO开发实践7

  一致的数据访问技术ADO/OLE DB
  
   Microsoft新近推出的UDA(Universal Data Access,一致数据访
  问技术)为关系型或非关系型数据访问提供了一致的访问接口,为企业
  级Intranet应用多层软件结构提供了数据接口标准。一致数据访问包
  括两层软件接口,分别为ADO(Active Data Object)和OLED B,对应于
  不同层次的应用开发,ADO提供了高层软件接口,可在各种脚本语言(Sc
  ript)或一些宏语言中直接使用;OLE DB提供了底层软件接口,可在C/C
  ++语言中直接使用。ADO以OL E DB为基础,它对OLE DB进行了封装。
  一致数据访问技术建立在Microsoft的COM(组件对象模型)基础上,它
  包括一组COM组件程序,组件与组件之间或者组件与客户程序之间通过
  标准的COM接口进行通讯。
   由于ADO建立在自动化(Automation)基础上,所以ADO的应用场合
  非常广泛,不仅可在Visual Basic这样的高级语言开发环境中使用,还
  可以在一些脚本语言中使用,这对于开发Web应用,在ASP(Active Serv
  er Page)的脚本代码访问数据库中提供了操作应用的捷径。本文将首
  先介绍ADO和OLE DB的结构模型,以便读者了解ADO与OLE DB之间的关
  系,然后介绍ADO的对象模型和ADO的一些特性,同时我们也将通过一些
  例子代码向读者展示ADO的用法。
   一、一致数据访问介绍
   随着网络技术和数据库技术的不断发展,现在的应用系统对数据
  集成的要求越来越高,这些数据有可能分布在不同的地方,并且使用不
  同的格式,例如关系型数据库和操作系统中的文件、电子表格、电子
  邮件、多媒体数据以及目录服务信息等等。传统的解决方案是使用大
  型的数据库系统,把所有这些数据都移到数据库系统中,然后按照操作
  数据库的办法对这些数据进行访问,这样做虽然能够按统一的方式对
  数据进行各种操作,但这种间接访问方式带来了很多问题,比如数据更
  新不及时、空间资源的冗余和访问效率低等等。
   Microsoft公司推出的一致数据访问技术则较好地解决了这些问
  题,它使得应用通过一致的接口来访问各种各样的数据,而不管数据驻
  留在何处,也不需要进行数据转移或复制、转换,在实现分布式的同时
  也带来了高效率。并且UDA技术在统一数据访问接口的同时,它的多层
  结构使数据使用方有了更多的选择机会,而它强大的扩展能力也给数
  据提供方留下了更多的扩展余地,这种开放型的软件结构使它具有极
  强的生命力,所以,这种技术从一推出便获得了广泛的欢迎,可以说,UD
  A技术是继ODBC之后的又一数据访问技术的飞跃。
   UDA技术包括OLE DB和ADO两层标准接口,OLE DB是系统级的编程
  接口,它定义了一组COM接口,这组接口封装了各种数据系统的访问操
  作,这组接口为数据使用方和数据提供方建立了标准,OLE DB还提供了
  一组标准的服务组件,用于提供查询、缓存、数据更新、事务处理等
  操作,因此,数据提供方只需实现一些简单的数据操作,在使用方就可
  以获得全部的数据控制能力。
   ADO是应用层的编程接口,它通过OLE DB提供的COM接口访问数据,
  它适合于各种客户机/服务器应用系统和基于Web的应用,尤其在一些
  脚本语言中访问数据库操作是ADO的主要优势。ADO是一套用自动化技
  术建立起来的对象层次结构,它比其他的一些对象模型如DAO(Data Ac
  cess Object)、RDO(Remote Data Object)等具有更好的灵活性,使用
  更为方便,并且访问数据的效率更高。
   图1显示了统一数据访问的软件层次模型。
  图1 UDA的层次结构图
   从图中我们可以看出,应用程序既可以通过ADO访问数据也可以直
  接通过OLE DB访问数据,而ADO则通过OLE DB访问底层数据。而且,OLE
   DB分成两部分,一部分由数据提供者实现,包括一些基本功能,如获取
  数据、修改数据、添加数据项等;另一部分由系统提供, 包括一些高
  级服务,如游标功能、分布式查询等等。这样的层次结构既为数据使
  用者即应用程序提供了多种选择方案,又为数据提供方简化了服务功
  能的实现手段,它只需按OLED B规范编写一个COM组件程序即可,使得
  第三方发布数据更为简便,而在应用程序方可以得到全面的功能服务,
  这充分体现了OLE DB两层结构的优势。
   由于OLE DB和ADO都以COM组件的形式实现,所以COM组件的各种特
  性也使得构建数据应用更为灵活,而不仅仅局限于一般的客户机/服务
  器或Web应用模型,它既适合于以数据为中心的应用,也适合于多层结
  构的分布式软件系统。通过对COM组件的配置,我们可以建立各种复杂
  的应用系统。利用从COM到DCOM的位置透明技术,我们可以很方便地建
  立分布式应用系统;利用MTS(Microsoft Transaction Server)运行环
  境,我们也可以在数据访问一层增加安全性控制,并利用MTS的对象管
  理使数据访问效率更高。所有这些功能都无需数据提供方编写代码实
  现,只需在DCOM或MTS环境中进行常规的配置即可。
   可以说一致的数据访问技术的核心是OLE DB,OLE DB建立了数据
  访问的标准接口,它把所有的数据源经过抽象形成行集(rowset)的概
  念。OLE DB模型主要包括以下一些COM对象:
   (1)数据源(Data Source)对象 数据源对象对应于一个数据提供
  者,它负责管理用户权限、建立与数据源的连接等初始操作。
   (2)会话(Session)对象 在数据源连接的基础上建立会话对象,会
  话对象提供了事务控制机制。
   (3)命令(Command)对象 数据使用者利用命令对象执行各种数据
  操作,如查询命令、修改命令等。
   (4)行集(Rowset)对象 提供了数据的抽象表示,它可以是命令执
  行的结果,也可以直接由会话对象产生,它是应用程序主要的操作对象
  。
   OLE DB的对象模型非常简单,这种简单性也带来了灵活性,从上面
  的几个COM对象也可以看出这一点。下面我们将从应用层角度出发,通
  过建立在OLE DB基础上的ADO对象模型结构的分析和使用以帮助读者
  进一步理解一致数据访问技术。
   二、ADO对象模型
   ADO对象模型定义了一组可编程的自动化对象,可用于Visual Bas
  ic、Visual C++、Java以及其他各种支持自动化特性的脚本语言。AD
  O最早被用于Microsoft Internet In formation Server中访问数据
  库的接口,与一般的数据库接口相比,ADO可更好地用于网络环境,通过
  优化技术,它尽可能地降低网络流量;ADO的另一个特性是使用简单,不
  仅因为它是一个面向高级用户的数据库接口,更因为它使用了一组简
  化的接口用以处理各种数据源。这两个特性使得ADO必将取代RDO和DA
  O,成为最终的应用层数据接口标准。
   从图1我们也看到了ADO实际上是OLE D
B的应用层接口,这种结构
  也为一致的数据访问接口提供了很好的扩展性,而不再局限于特定的
  数据源,因此,ADO可以处理各种OLE DB支持的数据源。
   图2是ADO的对象模型图。
  图2 ADO对象模型
   在ADO模型中,主体对象只有3个:Connection、Command和Records
  et,其他4个集合对象Errors、Properties、Parameters和Fields分别
  对应Error、Property、Parameter和Field对象,整个ADO对象模型由
  这些对象组成。
   一个典型的ADO应用使用Connection对象建立与数据源的连接,然
  后用一个Command对象给出对数据库操作的命令,比如查询或者更新数
  据等,而Recordset用于对结果集数据进行维护或者浏览等操作。Comm
  and命令所使用的命令语言与底层所对应的OLE DB数据源有关,不同的
  数据源可以使用不同的命令语言,对于关系型数据库,通常使用SQL作
  为命令语言。
   在Connection、Command和Recordset 3个对象中,Command对象是
  个可选对象,它是否有效取决于OLE DB数据提供者是否实现了IComman
  d接口。由于OLE DB可提供关系型数据源也可以提供非关系型数据源,
  所以在非关系型数据源上使用传统的SQL命令查询数据有可能无效,甚
  至Command命令对象也不能使用。
   从结构上看,ADO模型非常简单,但使用上又非常灵活,下面我们先
  从单个对象的角度进行讨论:
   (1) Connection对象 Connection对象代表与数据源之间的一个
  连接,ADO的Connec tion对象封装了OLE DB的数据源对象和会话对象
  。根据OLE DB提供者的不同性能,Conne ction对象的特性也有所不同
  ,所以Connection对象的方法和属性不一定都可以使用。利用Connect
  ion对象,我们可以完成以下一些基本设置操作。
   a.通过ConnectionString、ConnectionTimeOut和Mode属性设置
  连接串、超时信息、访问模式。
   b.还可以设置CursorLocation属性以便指定使用客户端游标,以
  便在客户程序中使用批处理修改方式。
   c.设置连接的缺省数据库属性DefaultDatabase。
   d.设置OLE DB提供者的属性Provider。
   e.通过Open和Close控制Connection对象与物理数据源的连接。
   f.通过Execute方法执行命令。
   g.提供事务机制,通过BeginTrans、CommitTrans和RollbackTran
  s方法实现事务控制。
   h.通过Errors集合属性检查数据源的错误信息。
   i.通过OpenSchema方法获取数据库的表信息。
   Connection对象是ADO的基本对象之一,它独立于所有其他的对象
  。如果我们要对数据库进行查询操作,既可以使用Execute方法,也可
  以使用Command对象。使用Execute方法

VC++ADO开发实践6

  在Visual C++中用ADO进行数据库编程
  作者:蒋东宇
  
  
  
    ActiveX数据对象(ADO)是OLE DB上面的高层数据库API。我们在C++程序中也可以调用ADO。本文将在VC 6.0环境下做一个小小的例子解释如何使用ADO。
  
    1. 生成应用程序框架并初始化OLE/COM库环境
  
    创建一个标准的MFC AppWizard(exe)应用程序,然后在应用程序类的InitInstance函数中初始化OLE/COM库(因为ADO库是一个COM DLL库)。
  
    BOOL CADOTestApp::InitInstance()
  
    { //初始化OLE/COM库环境
  
  AfxOleInit();}
  
    2. 引入ADO库文件
  
    使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,以使编译器能正确编译。代码如下:
  
     #include 〈comdef.h〉
  
     #import “c:program filescommon filessystemadomsado15.dll”
  
     no_namespace
  
     rename (“EOF”,”adoEOF”)
  
    头文件comdef.h使我们的应用程序能够使用Visual C++中的一些特殊COM支持类,这些类使得处理OLE自治更为容易一些,OLE自治是ADO使用的数据类型。后三行使用#import指令在我们的应用程序中输入ADO类库定义。
  
    ADO类的定义是作为一种资源存储在ADO DLL(msado15.dll)中,在其内部称为类型库。类型库描述了自治接口,以及C++使用的COM vtable接口。当使用#import指令时,在运行时Visual C++需要从ADO DLL中读取这个类型库,并以此创建一组C++头文件。这些头文件具有.tli 和.tlh扩展名,读者可以在项目的目录下找到这两个文件。在C++程序代码中调用的ADO类要在这些文件中定义。
  
    程序的第三行指示ADO对象不使用名称空间。在有些应用程序中,由于应用程序中的对象与ADO中的对象之间可能会出现命名冲突,所以有必要使用名称空间。如果要使用名称空间,则可把第三行程序修改为: rename_namespace(“AdoNS”)。第四行代码将ADO中的EOF(文件结束)更名为adoEOF,以避免与定义了自己的EOF的其他库冲突。
  
    3.利用智能指针进行数据库操作
  
    在CaboutDlg头文件中定义两个ADO智能指针类实例,并在对话框中加入一个ListCtrl。
  
  _ConnectionPtr m_pConnection;
  
  _RecordsetPtr m_pRecordset;
  
    ClistCtrl m_List;
  
    ADO库包含三个智能指针:_ConnectionPtr、_CommandPtr和_RecordsetPtr。
  
  _ConnectionPtr通常被用来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。
  
  _CommandPtr返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储过程和SQL语句。在使用_CommandPtr接口时,可以利用全局_ConnectionPtr接口,也可以在_CommandPtr接口里直接使用连接串。
  
  _RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多的控制功能,如记录锁定、游标控制等。
  
    在OnInitDialog()中加入以下代码:
  
    BOOL CAboutDlg::OnInitDialog()
  
    {
  
  CDialog::OnInitDialog();
  
  _variant_t TheValue;
  
  m_List.ResetContent();
  
    m_pConnection.CreateInstance(_uuidof(Connection));
  
    m_pRecordset.CreateInstance(_uuidof(Recordset));
  
    try{
  
  m_pConnection->Open(“DSN=ADOTest”,””,””,0); //连接叫作ADOTest的ODBC数据源
  
  m_pRecordset->Open(“SELECT * FROM BlockDefine”,(IDispatch*)m_pConnection,
  adOpenDynamic,
  adLockOptimistic,
  adCmdText);
  
    //执行SQL语句得到一个记录集
  
     while(!m_pRecordset->adoEOF)
  
    //遍历所有记录
  
     {
  
     TheValue = m_pRecordset->GetCollect(“BlockIndex”);
  
    //得到字段BlockIndex的值
  
     if(TheValue.vt!=VT_NULL)
  
     m_List.AddString((char*)_bstr_t(TheValue)); //将该值加入到列表控件中
  
     m_pRecordset->MoveNext();
  
    }
  
     m_pRecordset->Close();
  
  m_pConnection->Close();
  
     }
  
     catch(_com_error e) //异常处理
  
     {
  
  AfxMessageBox(e->ErrorMessage());
  
    }
  
    m_pRecordset = NULL;
  
  m_pConnection = NULL;
  
    return TRUE; // return TRUE unless you set the focus to a control
  
    }
  
    程序中通过_variant_t和_bstr_t转换COM对象和C++类型的数据, _variant_t类封装了OLE自治VARIANT数据类型。在C++中使用_variant_t类要比直接使用VARIANT数据类型容易得多。
  
    好,编译后该程序就能运行了,但记住运行前要创建一个叫ADOTest的ODBC数据源。该程序将把表BlockDefine中的BlockIndex字段值显示在列表控件中。