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开发实践4

  在VC中使用ADO开发数据库应用程序
  
  
  1.引入ADO库文件
  使用ADO前必须在工程的stdafx.h文件里用直接引入符号#import引入ADO库文件,
  以使编译器能正确编译。代码如下所示:
  #import “c:program filescommon filessystemadomsado15.dll”
  no—namespaces rename(“EOF” adoEOF”)
  这行语句声明在工程中使用ADO,但不使用ADO的名字空间,并且为了避免冲突,将EOF改
  名为adoEOF。
  
  2.初始化OLE/COM库环境
  必须注意的是,ADO库是一组COM动态库,这意味应用程序在调用ADO前,必须初始
  化OLE/COM库环境。在MFC应用程序里,一个比较好的方法是在应用程序主类的
  InitInstance成员函数里初始化OLE/COM库环境。
  
    //初始化OLE/COM库环境
  
    BOOL CADOApp::InitInstance()
    {
  if(!AfxOleInit())
  {
  AfxMessageBox(“OLE初始化出错!”);
  return FALSE;
  }
  ……
  }
  
  函数AfxOleInit在每次应用程序启动时初始化OLE/COM库环境。
  
  3.ADO接口简介
  ADO库包含三个基本接口:
  __ConnectionPtr接口、
  __CommandPtr接口和、
  __RecordsetPtr接口,
  
  __ConnectionPtr接口返回一个记录集或一个空指针。通常使用它来创建一个数据连接或
  执行一条不返回任何结果的SQL语句,如一个存储过程。用__ConnectionPtr接口返回一
  个记录集不是一个好的使用方法。通常同CDatabase一样,使用它创建一个数据连接,然
  后使用其它对象执行数据输入输出操作。
  
  __CommandPtr接口返回一个记录集。它提供了一种简单的方法来执行返回记录集的存储
  过程和SQL语句。在使用__CommandPtr接口时,可以利用全局__ConnectionPtr接口,也
  可以在__CommandPtr接口里直接使用连接串。如果只执行一次或几次数据访问操作,后
  者是比较好的选择。但如果要频繁访问数据库,并要返回很多记录集,那么,应该使用
  全局__ConnectionPtr接口创建一个数据连接,然后使用__CommandPtr接口执行存储过程
  和SQL语句。
  
  __RecordsetPtr是一个记录集对象。与以上两种对象相比,它对记录集提供了更多
  的控制功能,如记录锁定,游标控制等。同__CommandPtr接口一样,它不一定要使用一
  个已经创建的数据连接,可以用一个连接串代替连接指针赋给__RecordsetPtr的
  connection成员变量,让它自己创建数据连接。如果要使用多个记录集,最好的方法是
  同Command对象一样使用已经创建了数据连接的全局—ConnectionPtr接口,然后使用
  __RecordsetPtr执行存储过程和SQL语句。
  
  4.使用__ConnectionPtr接口
  
  __ConnectionPtr是一个连接接口,它类似于CDatabase和CDaoDatabase。首先创建一个
  __ConnectionPtr接口实例,接着指向并打开一个ODBC数据源或OLE DB数据提供者
  (Provider)。以下代码分别创建一个基于DSN和非DSN的数据连接。
  
    //使用__ConnectionPtr(基于DSN)
  
    __ConnectionPtr MyDb;
  
    MyDb.CreateInstance(__uuidof(Connection));
  
    MyDb-〉Open(“DSN=samp;UID=admin;PWD=admin”,””,””,-1);
  
    //使用—ConnectionPtr (基于非DSN)
  
    __ConnectionPtr MyDb;
  
    MyDb.CreateInstance(__uuidof(Connection));
  
    MyDb-〉Open(“Provider=SQLOLEDB;SERVER=server;DATABASE=samp;UID=admin;
  
    PWD=admin”,””,””,-1);
  
  
  5.使用__RecordsetPtr接口
  __RecordsetPtr接口的使用方法和CDaoDatabase类似,通过以下代码的比较,你会发现
  使用—RecordsetPtr接口非常简单(以下代码使用上面已经创建的数据连接):
  
    //使用CDaoDatabase执行SQL语句
  
    CDaoRecordset MySet = new CDaoRecordset(MyDb);
  
    MySet-〉Open(AFX__DAO__USE__DEFAULT__TYPE,”SELECT  FROM t__samp”);
  
    Now using ADO:
  
    //使用__RecordsetPtr执行SQL语句
  
    __RecordsetPtr MySet;
  
    MySet.CreateInstance(__uuidof(Recordset));
  
    MySet-〉Open(“SELECT  FROM some__table”,
  
    MyDb.GetInterfacePtr(),adOpenDynamic,adLockOptimistic,adCmdText);
  
    现在我们已经有了一个数据连接和一个记录集,接下来就可以使用数据了。从以下
  代码可以看到,使用ADO的__RecordsetPtr接口,就不需要像DAO那样频繁地使用大而复
  杂的数据结构VARIANT,并强制转换各种数据类型了,这也是ADO的优点之一。假定程序
  有一个名称为m__List的ListBox控件,下面代码我们用__RecordsetPtr接口获取记录集
  数据并填充这个ListBox控件:
  
    //使用ADO访问数据
  
    __variant__t Holder
  
    try{while(!MySet-〉adoEOF)
  
    { Holder = MySet-〉GetCollect(“FIELD__1”);
  
    if(Holder.vt!=VT__NULL)
  
    m__List.AddString((char)__bstr__t(Holder));
  
    MySet-〉MoveNext();} }
  
    catch(__com__error  e)
  
    { CString Error = e-〉ErrorMessage();
  
     AfxMessageBox(e-〉ErrorMessage());
  
    } catch(…)
  
    { MessageBox(“ADO发生错误!”);}
  
    必须始终在代码中用try和catch来捕获ADO错误,否则ADO错误会使你的应用程序崩
  溃。当ADO发生运行错误时(如数据库不存在),OLE DB数据提供者将自动创建一个
  __com__error对象,并将有关错误信息填充到这个对象的成员变量。
  
  6.使用__CommandPtr接口
  __CommandPtr接口返回一个Recordset对象,并且提供了更多的记录集控制功能,以下代
  码示例使用__CommandPtr接口的方法:
  
    //使用__CommandPtr接口获取数据
  
    __CommandPtr pCommand;
  
    __RecordsetPtr MySet;
  
    pCommand.CreateInstance(__uuidof(Command));
  
    pCommand-〉ActiveConnection=MyDb;
  
    pCommand-〉CommandText=”select  from some—table”;
  
    pCommand-〉CommandType=adCmdText;
  
    pCommand-〉Parameters-〉“refresh”();
  
    MySet=pCommand-〉Execute(NULL,NULL,adCmdUnknown);
  
    __variant__t TheValue = MySet-〉GetCollect(“FIELD__1”);
  
    CString sValue=(char)__bstr__t(TheValue);
  
  7.关于数据类型转换
    由于COM对象是跨平台的,它使用了一种通用的方法来处理各种类型的数据,因此
  CString 类和COM对象是不兼容的,我们需要一组API来转换COM对象和C++类型的数
  据。__vatiant__t和__bstr__t就是这样两种对象。它们提供了通用的方法转换
COM对象
  和C++类型的数据。
  
  
  

VC++ADO开发实践3

  在Visual C++中如何利用UDL文件来建立ADO连接
  
   使用通用数据连接文件(*.UDL,以下简称文件)来创建ADO连接,可以和ODBC一样可视化地定义要连接的数据源,从而实现数据访问的透明性。
  
  
  1.使用UDL文件来创建ADO连接
  创建ADO的连接,首先要设置ADO连接对象的ConnectionString属性,该属性提供所要连接的数据库类型、数据所处服务器、要访问的数据库和数据库访问的安全认证信息。比较专业的方法是在ConnectionString中直接提供以上信息,下面是访问不同类型数据源设置ConnectionString的标准:
  访问ODBC数据
  ”Provider=MSDASQL;DSN=dsnName;UID=userName;PWD=userPassword;”
  访问ORACLE数据库
  ”Provider=MSDAORA;Data Source=serverName;User ID=userName; Password=userPassword;”
  访问MS SQL数据库
  ”Provider=SQLOLEDB;Data Source=serverName;Initial Catalog=databaseName; User ID=userName;Password=userPassword;”
  访问ACCESS 数据库
  ”Provider=Microsoft.Jet.OLEDB.4.0;Data Source=databaseName;User ID=userName;Password=userPassword;”
  上述的连接属性设置标准随着数据源的类型不同而变化,软件用户常常不习惯这种设置方式,都希望有可视化的数据源设置方法。为此Microsoft提供了通用数据连接文件(.UDL)来建立和测试ADO连接属性。ADO连接对象可以很方便地使用UDL文件来连接数据源,下面例子使用my_data1.udl来创建ADO连接。
  _ConnectionPtr m_pDBConn;
  m_pDBConn.CreateInstance(__uuidof(Connection));
  m_pDBConn->ConnectionString =”File Name=c:mydirmy_data1.udl”;
  m_pDBConn->Open(“”,””,””,NULL);
  这样一来无论数据源如何变化,在软件中都可以用统一的方法编程。当数据源改变时,只要双击相应的udl文件即可可视化地设置数据源,无需更改软件。
  因为ADO是COM接口,为了软件的可靠性,打开ADO连接时,可以加入异常处理代码。
  try{
  m_pDBConn->Open(“”,””,””,NULL);
  }catch(_com_error &e){
  //处理异常的代码
  . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
  m_pDBConn=NULL;
  }
  因为_ConnectionPtr m_pDBConn是智能指针,应在处理异常代码时将智能指针设为NULL后将自动将引用计数降为0。
  如果不出现异常,只要在使用完m_pDBConn,只要引用Close方法即可。
  2.创建你所需的UDL文件
  在你所想创建UDL文件的目录中单击右键,选择从菜单 新建|Microsoft 数据连接,然后将新创建的UDL文件更改为你所希望的文件名(.UDL扩展名不能改变)。
  注:如果操作系统是Window 2000,先创建一个文本文件,再将该文本文件的扩展名改为 “udl”。
  然后双击所创建的UDL文件,即可视化地完成数据源的设定。
  使用UDL文件必须在系统中先安装Microsoft MDAC,Win 98第二版,Win 2000中都自动包含了该组件,需要该组件最新版本时可以到Microsoft网站去下载。