C++连接数据库有很多种方法,ODBC,ADO等等。我这里就采用了ADO的方法。
工具/原料
VS2010,SqlServer2005
步骤/方法
1、既然都涉及到数据库了,就直接做个带界面的吧。先打开VS2010,新建一个“MFC应用程序”,在里面记得选择“基于对话框”,因为这个比较简单,我就用这个举例了,其他的自己研究一下吧,然后一直下一步就可以了。下面正式开始,先把对话框上的静态文本控件删了,没有什么用。再添加一个“ListControl”控件,现在应该是这个样子的。
2、然后右键单击这个ListControl,选择属性。在属性里找到View,改成Report,如图:
3、然后是数据库,我们需要一个测试用的数据库,我随便设计了一个,用的是SqlServer2005,其他的类似,执行SQL语句:usemastercreatedatabasedb_testusedb_testcreatetablestudent(stu_idintidentity(1,1)primarykey,stu_numvarchar(8)notnulldefault'00000000',stu_namevarchar(20)notnull,stu_classvarchar(50)notnull)insertintostudentvalues('20101611','测试1','计算机')insertintostudentvalues('20101612','测试2','数学')insertintostudentvalues('20101613','测试3','计算机')insertintostudentvalues('20101614','测试4','中文')上面的SQL语句的结果是创建了一个名称为db_test的数据库,并且建立了一个表,名称为student,里面有四列,第一列id为自增。后面添加了四个测试数据。
4、下面是程序代码部分:添加一个类,我叫做CDataBaseADO,在DataBaseADO.h文件中的#pragmaonce后加上一句#import"C:\ProgramFiles\commonfiles\system\ado\msado15.dll"no_namespacerename("EOF","adoEOF")因为ADO是COM组件的一个,这句是引入一个库文件,否则的话,下面的都不能使用。然后在类里添加几个变量:Code:private://_ConnectionPtr通常被用来创建一个数据连接或执行一条不返回任何结果的SQL语句,如一个存储过程。_ConnectionPtrm_pConnection;数据类型_ConnectionPtr实际上就是由类模板_com_ptr_t而得到的一个具体的实例类,其定义可以到msado15.tlh、comdef.h和comip.h这三个文件中找到。在msado15.tlh中有:Code:_COM_SMARTPTR_TYPEDEF(_Collection,__uuidof(_Collection));经宏扩展后就得到了_ConnectionPtr类。_ConnectionPtr类封装了Connection对象的Idispatch接口指针,及一些必要的操作。我们就是通过这个指针来操纵Connection对象。类似地,后面用到的_CommandPtr和_RecordsetPtr类型也是这样得到的,它们分别表示命令对象指针和记录集对象的指针。
5、然后在构造函数和析构函数里写上如下代码:Code:CDataBaseADO::CDataBaseADO(void){::CoInitialize(NULL);//初始化OLE/COM库环境m_pConnection=NULL;}CDataBaseADO::~CDataBaseADO(void){if(m_pConnection)m_pConnection->Close();m_pConnection=NULL;::CoUninitialize();//释放程序占用的COM资源}然后我们来写打开数据库连接和关闭。数据库只有打开了才能使用。Code://打开数据库连接boolCDataBaseADO::Open(_bstr_tstrConnection){if(FAILED(m_pConnection.CreateInstance(__uuidof(Connection))))//初始化Connection指针returnfalse;try{m_pConnection->Open(strConnection,"","",0);}catch(_com_errore){AfxMessageBox(e.Description());returnfalse;}returntrue;}//关闭数据库连接voidCDataBaseADO::Close(void){if(m_pConnection)m_pConnection->Close();m_pConnection=NULL;}这些都很简单了,我注释里面也写得很清楚了。在_ConnectionPtr使用前都需要初始化,然后打开,用完之后关闭。记住Open()和Close()一定要成对出现,一次打开就要有一次关闭,而且不能多了,也不能少了。
6、下面就是重头戏了,sel娣定撰钠ect操作的实现。这个就是执行了一条select语句后,返回一个记录集,然后我们把记玲膛宀耱录集处理一下,放到一个容器里,而不是返回记录集指针,这样以后我们在用这个类的时候,就可以不用在每个里面都去写那句#import了,类相对更独立一些。看看代码先:Code://查询//strSql:查询语句//strName:要返回的记录集内的列名vector<vector<_variant_t>>CDataBaseADO::Select(BSTRstrSql,vector<_variant_t>strName){_RecordsetPtrpRecordset;//定义数据集对象vector<vector<_variant_t>>vRecord;//这是C++0x新标准,不是VS2010或者不支持新标准的,//要写成vector<vector<_variant_t>>,因为>>会被认为是右移操作符if(FAILED(pRecordset.CreateInstance(__uuidof(Recordset))))//初始化Recordset指针returnvector<vector<_variant_t>>();try{pRecordset->Open(strSql,(IDispatch*)m_pConnection,adOpenDynamic,adLockOptimistic,adCmdText);//adOpenDynamic:动态adLockOptimistic乐观封锁法adCmdText:文本查询语句pRecordset->MoveFirst();while(!pRecordset->adoEOF)//遍历所有记录{//取记录字段值vector<_variant_t>vTheValue;//VARIANT数据类型的泛型for(inti=0;i<strName.size();++i){vTheValue.push_back(pRecordset->GetCollect(strName.at(i)));//得到字段的值,并添加到容器的最后}vRecord.push_back(vTheValue);pRecordset->MoveNext();//移动到下一条记录}pRecordset->Close();//关闭连接pRecordset=NULL;}catch(_com_errore){AfxMessageBox(e.Description());}returnvRecord;}到这里有好多东西要说明的。一个一个来说。_variant_t记录有数据的类型和数据的值,它封闭了VARIANT数据类型,VARIANT是一个结构体类型,具体的定义可以上网搜一下,也可以看MSDN,我就不介绍了。简单说,它可以是任何类型,在它里面的vt属性表示了它的类型。具体怎么使用,在后面会说到,现在还没有涉及到转化问题。Vector是一个容器,一个能够存放任意类型的动态数组,能够增加和压缩数据。你可以随时增加或减少里面的数据。因为我们从数据库中获取的数据数量是未知的(在你运行完程序之前)所以要用动态数组来存储。它的定义方法为vector<类型>变量名,而我们的函数的返回类型呢,是容器的容器,相当于二维数组吧,因为记录集就是那样的,有行有列。里面的是每列的内容,外面的容器存储的是行的内容,每行里面都有需要的列数。还有一点,关于C++0x的标准问题。因为VS2010是支持C++0x的,所以我直接写成了vector<vector<_variant_t>>这样的,>>不会被认为成右移运算符,但是不支持新标准的编译器里这样写就是错误的,因为>>会被认为成右移运算符,所以要在中间加空格,成为vector<vector<_variant_t>>这样的。关于C++0x也不是我们的重点,大家注意一下自己的编译器,看看支持新标准不。我们将_RecordsetPtr类型的数据集对象定义为局部变量,然后初始化,然后打开。Open()函数的原型如下:HRESULTRecordset15::Open(const_variant_t&Source,const_variant_t&ActiveConnection,enumCursorTypeEnumCursorType,enumLockTypeEnumLockType,longOptions)参数说明:Source是数据查询字符串ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象)CursorType光标类型,它可以是以下值之一,请看这个枚举结构:enumCursorTypeEnum{adOpenUnspecified=-1,///不作特别指定adOpenForwardOnly=0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用adOpenKeyset=1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。adOpenDynamic=2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。adOpenStatic=3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。};LockType锁定类型,它可以是以下值之一,请看如下枚举结构:enumLockTypeEnum{adLockUnspecified=-1,///未指定adLockReadOnly=1,///只读记录集adLockPessimistic=2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制adLockOptimistic=3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作adLockBatchOptimistic=4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。};option可以取以下值adCmdText:表明CommandText是文本命令adCmdTable:表明CommandText是一个表名adCmdProc:表明CommandText是一个存储过程adCmdUnknown:未知在我们执行了查询操作后,要先将记录集移动到第一个,然后遍历,把所有的结果都放到容器中。遍历结束后,关闭数据集,并且返回。
7、好吧,到现在为止,数据集已经畛粳棠奈获取到了,数据也能成功返回了,但是在程序里是显示不出来的,因为我们还没做显示部分。不过这些不是我们的主要内容,附带着端姗跬轵讲一下吧。在资源视图里,打开对话框,然后在ListControl上点右键,添加变量,如图:在新对话框里,输入名称,我的叫m_ListCtrl,如图:当然VC6的要在ClassWizard里设置了。注意的是控件ID是否正确。一般的说,不用设置了,类别选成Control。然后我们就要将这个ListControl设置成四列,并且设置一些小格式。见代码:Code:CRectrc;m_ListCtrl.GetWindowRect(&rc);//获取控件大小//设置了四列,大小是一样的m_ListCtrl.InsertColumn(0,_T("序号"),LVCFMT_CENTER,rc.Size().cx/4,0);m_ListCtrl.InsertColumn(1,_T("学号"),LVCFMT_CENTER,rc.Size().cx/4,1);m_ListCtrl.InsertColumn(2,_T("姓名"),LVCFMT_CENTER,rc.Size().cx/4,2);m_ListCtrl.InsertColumn(3,_T("班级"),LVCFMT_CENTER,rc.Size().cx/4,3);//LVS_EX_GRIDLINES是希望显示网格;LVS_EX_FULLROWSELECT是希望被选中时整行反色显示;LVS_EX_HEADERDRAGDROP是让其支持点击表头排序;LVS_EX_TWOCLICKACTIVATE是希望有鼠标在未被选中的行上移动的时候有一些效果m_ListCtrl.SetExtendedStyle(m_ListCtrl.GetExtendedStyle()|LVS_EX_GRIDLINES|LVS_EX_FULLROWSELECT|LVS_EX_HEADERDRAGDROP/*|LVS_EX_TWOCLICKACTIVATE*/);把这些代码放在BOOLCMFCDataBaseDlg::OnInitDialog()函数的return前,运行就有效果了。然后,双击一下对话框里的确定按钮,我就不再添加新按钮了。进入到函数里,把里面的OnOK()注释了,添加如下代码:Code:voidCMFCDataBaseDlg::OnBnClickedOk(){//TODO:在此添加控件通知处理程序代码//CDialogEx::OnOK();if(!m_DataBase.Open("Provider=SQLOLEDB.1;Password=aaa;PersistSecurityInfo=True;UserID=sa;InitialCatalog=db_test;DataSource=YJN-PC\\SQLEXPRESS2005"))return;vector<_variant_t>vName;//设置要返回的列名vName.push_back("stu_id");vName.push_back("stu_num");vName.push_back("stu_name");vName.push_back("stu_class");//查询结果vector<vector<_variant_t>>vResult(m_DataBase.Select(::SysAllocString(L"select*fromstudent"),vName));m_ListCtrl.DeleteAllItems();//删除所有的项目//通过循环添加所有的内容for(inti=0;i<vResult.size();++i){m_ListCtrl.InsertItem(i,VariantToCString(vResult.at(i).at(0)));//插入一行,每行的第一列是序号m_ListCtrl.SetItemText(i,1,VariantToCString(vResult.at(i).at(1)));//设置该行的后面列的内容m_ListCtrl.SetItemText(i,2,VariantToCString(vResult.at(i).at(2)));m_ListCtrl.SetItemText(i,3,VariantToCString(vResult.at(i).at(3)));}m_DataBase.Close();//记得要关闭连接}
8、看注释应该就明白了吧,嗯,连接字符串的查找方式,有一个简单的方法,随便新建一个文本文档,茧盯璜阝就是txt的,然后改扩展名,改成udl的,双击打开。在提供程序里选择合适的程又挨喁钒序,我选的是“MicrosoftOLEDBProviderforSQLServer”,因为我连接的是SQL2005,然后点下一步,根据提示选择,把允许保存密码钩上,选好数据库,测试一下,如果成功了,就OK了。最后点确定。然后把这个文件改回.txt的,用记事本打开,里面就有连接字符串。看看我的是这样的Code:[oledb];EverythingafterthislineisanOLEDBinitstringProvider=SQLOLEDB.1;Password=aaa;PersistSecurityInfo=True;UserID=sa;InitialCatalog=db_test;DataSource=YJN-PC\SQLEXPRESS2005第三行就是连接字符串。这里还有一个地方要注意,就是\的问题,在C++里\\才表示一个\,转义字符嘛。一定要注意啊。还有一个转换函数:Code://转换字符串CStringCMFCDataBaseDlg::VariantToCString(_variant_tvar){CStringstr;//转换以后的字符串switch(var.vt){caseVT_BSTR://varisBSTRtypestr=var.bstrVal;break;caseVT_I2://varisshortinttypestr.Format(L"%d",(int)var.iVal);break;caseVT_I4://varislonginttypestr.Format(L"%d",var.lVal);break;caseVT_R4://varisfloattypestr.Format(L"%10.6f",(double)var.fltVal);break;caseVT_R8://varisdoubletypestr.Format(L"%10.6f",var.dblVal);break;caseVT_CY://varisCYtypestr=COleCurrency(var).Format();break;caseVT_DATE://varisDATEtypestr=COleDateTime(var).Format();break;caseVT_BOOL://varisVARIANT_BOOLstr=(var.boolVal==0)?L"FALSE":L"TRUE";break;default:str.Format(L"Unktype%d\n",var.vt);TRACE(L"Unknowntype%d\n",var.vt);}returnstr;}这个函数实现了将_variant_t类型的转换为CString类型。现在来说说吧,看看函数内容大约也明白了吧,根据_variant_t里的vt属性可以知道里面的内容是什么类型的,然后对应的显示就OK了。再来看看添加,修改,删除的实现。这个非常简单,都在一个函数里就可以实现了。看看代码吧:Code://执行SQL语句,并返回影响的行数//IsText:是否是文本命令,是:文本命令,否:存储过程intCDataBaseADO::ExcuteSQL(_bstr_tCommandText,boolIsText){_variant_tRecordsAffected;//记录影响的行数try{if(IsText)m_pConnection->Execute(CommandText,&RecordsAffected,adCmdText);elsem_pConnection->Execute(CommandText,&RecordsAffected,adCmdStoredProc);}catch(_com_errore){return-1;}returnRecordsAffected.intVal;}这是运用了_ConnectionPtr里的Execute方法,具体如下:_RecordsetPtrConnection15::Execute(_bstr_tCommandText,VARIANT*RecordsAffected,longOptions) 其中CommandText是命令字串,通常是SQL命令。 参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: adCmdText:表明CommandText是文本命令 adCmdTable:表明CommandText是一个表名 adCmdProc:表明CommandText是一个存储过程 adCmdUnknown:未知我这个函数只能执行SQL语句和存储过程,不过一般也就够用了。具体想要怎么改数据,自己写SQL语句吧,去拼接字符串,然后执行,当然,如果出错了,就返回-1,执行失败了就是0喽。到这就结束了。以后有机会封装个更好的类,这个类可以拿出来用,功能虽然简单,也够用了。测试一下?应该的。给对话框加个按钮先,添加单击事件,在里面写上如下代码:Code:voidCMFCDataBaseDlg::OnBnClickedButton1(){//TODO:在此添加控件通知处理程序代码if(!m_DataBase.Open("Provider=SQLOLEDB.1;Password=aaa;PersistSecurityInfo=True;UserID=sa;InitialCatalog=db_test;DataSource=YJN-PC\\SQLEXPRESS2005"))return;intr=m_DataBase.ExcuteSQL("insertintostudentvalues('20101616','测试6','中文')");CStringstr;str.Format(L"%d",r);MessageBox(str);m_DataBase.Close();//记得要关闭连接}运行一下,弹出个1吧,那就对了。当然了像Open()这样的,你可以写到别的地方,不用每次都打开,关闭。这些自己发挥吧,我只是抛砖引玉罢了。
注意事
1、代码下载:http://download.csdn.net/source/2978261