第4章 感受(二)——4.6. Hello Database 控制台版

来源:互联网 发布:北京企业名录数据库 编辑:程序博客网 时间:2024/06/05 06:07

 [回到目录]

4.6.Hello Database 控制台版

在Code::Blocks中新建一个控制台应用项目——没错,我们的老朋友“控制台”——项目命名为“HelloDatabase_Console”。打开向导自动生成的“main.cpp”文件,通过主菜单“编辑 → 文件编码”,修改为“系统默认/System default”(在中文Windows下,默认编码为“GBK”)。

4.6.1.配置构建选项

在本项目中,我们将用到MySQL、及MySQL++库。现在我们需要通过一些项目配置,来“告诉”为项目上必须用到哪些扩展库,以及上哪里去找这些库,及库所需要的头文件。

步骤 1:点击主菜单:“项目→构建选项”,在左边栏中确保选中根节点:“HelloDatabase_Console”, 如下图:

图 90配置项目Build选项

图 90配置项目Build选项

 

hint〖小提示〗:配置项目公用构建选项

本例中,项目可以被编译成两个目标:Debug/调试版和Release/发行版,这两个目标可以分别有自己的构建选项,但多个目标也可以有一些“公用的构建选项”——这正是我们要选中根节点“HelloDatabase_Console”的意图。

 

步骤 2:在右边操作区中,切换到“Linker Setting/链接选项”Tab页。再点击“连接库”底下的 “添加”按钮,在弹出的对话框中输入“mysqlpp”,确认后,mysqlpp将被加入“链接库”列表中,如下图:

图 91 添加本项目需要的链接库:mysqlpp

图 91 添加本项目需要的链接库:mysqlpp

该项配置,用于“告诉”项目:需要额外使用到mysqlpp库(由于mysqlpp库静态链接了mysql的库,所以此处不需要添加mysql)。

步骤 3:切换Tab页为“Search directories/搜索路径”,选择其内的“编译器”子Tab页,点击“添加”,先加入“${#mysqlpp.include}”;再次点击“添加”,加入“${#mysql.include}”(均不含引号),结果如下:

图 92 添加mysql++及mysqlp 的头文件 搜索路径

图 92 添加mysql++及mysqlp 的头文件 搜索路径

 

hint〖小提示〗:理解 ${#path_var_name.fieldname}

之前我们辛苦配置的mysqlpp及mysql全局路径变量在这里派上用场。${#path_var_name.fieldname}中的path_var_name,正是之前我们配置的全局路径变量,而fieldname则可以是base、include或lib。当为base时,可以只写field_var_name。因此,${mysqlpp.include}在本例中,相当于“E:/cpp_ex_libs/iconvpp/include”。

 

本配置用以“告诉”编译器,当编译过程中在标准路径下找不到某个头文件时,请到mysqlpp.include及mysql.include变量所代表的路径下找找吧。

步骤 4:仍然在“Search directories”页下,切换到“连接器”子Tab页,用类似的方法,添加“${#mysqlpp.lib}”。

本配置用以“告诉”链接器,链接过程如果找不到某个库文件,可以上这里添加的路径里找一找。

步骤 5:点击“确认”按钮,退出 “项目build选项”对话框。然后,请点击Code::Blocks主菜单:“文件→保存项目”,完成配置。

4.6.2.编写代码

  • 头文件与名字空间
#include <iostream>
#include <string>
#include <cstdlib> //for system

005
#include <mysql++.h>

using namespace
std;

005 行包含了 “mysql++.h”头文件。如果编译时直接报找不到该文件,原因肯定是前面的“Search directories”配置有误,或者再往前的,全局路径变量“mysqlpp”配置有误。

  • 连接数据库
int main()
{

011
mysqlpp::Connection con(false);

013
con.set_option(new mysqlpp::SetCharsetNameOption("gbk"));

cout << "请输入数据库(root用户)连接密码:";
string pwd;
getline(cin, pwd);

019 if (!con.connect("d2school", "localhost", "root", pwd.c_str()))
{

cout << "无法连接上数据,请检查密码是否正确!" << endl;
return
-1;
}


要想从MySQL数据库中查询数据,首先需要一个“数据库连接对象”,这个概念类似打电话时,必须先拔通,在两个话机之间建立连接。

011 行,我们定义了一个“mysql 连接对象”。构造该对象时,需要一个“布尔值”,代码中使用了“false”,具体含义稍后再谈。

013 行代码,本可以啰嗦一点写成:

mysqlpp::SetCharsetNameOption opt = new
mysqlpp::SetCharsetNameOption("gbk");
con.set_option(opt);

即:创建一个类型为“SetCharsetNameOption”的堆对象,然后传给con对象的成员函数set_option()。

按说,我们应该用完一个堆对象之后,通过delete释放它,比如:

delete opt;

但set_option函数中,con对象会“记住”opt,并接管对它的生杀大权。即:con对象在自己临死前,会先释放opt;所以我们不必,也不允许再释放opt对象。

中间的设计大致是这样:首先,类型Connection存在一个私有成员,它在set_option函数中记住外面传入的opt参数,然后在Connection的析构函数中,释放掉,过程如下图。

图 93 Connection 接管opt的释放权

图 93 Connection 接管opt的释放权

 

import〖重要〗: “接口描述型”契约 vs. “文档描述型”契约

MySQL++此处的 set_option(Options* opt)函数,谈不上是一个好的设计——因为除非去查看文档(甚至是源代码),否则很难事先知道opt的生杀大权在set_option()之后已经转移。

一个库要求它的使用者在使用上有这样或那样的“规定”,这是很经常的事,我们称之为“契约编程”。好的“契约”通常在“接口”的形式就能体现出契约的内容——最次的,也得在接口函数的名字上有所体现。差的“契约”则让你在使用时毫无警觉,直接出了错,去查看文档才能有所发现。

现实中,虽然电视机或洗衣机,通常买回时附有厚厚的说明书,但通常我们可以不看文档就放心地开始使用,这也正是因电视机和洗衣机都实现了良好的“接口描述型”契约。

 

构造SetCharsetNameOption对象,也需要一个字符串类型的参数,本例采用“gbk”。它告诉con对象,一会儿连接建立之后,在这条连接上所传输的数据,编码为“gbk”。这类似在拨通电话之前,你要明确连接两端的人,准备说什么话?英语?汉语?在准备数据时,我们说过,本次数据在数据库中采用“gbk”编码。

019行,我们调用con对象的connect()成员函数,由它真正尝试去创建连接。该函数依次需要以下四个参数:

  • 数据库名称:这我们在准备数据时,事先就在MySQL数据服务中创建好了。
  • 数据库所在主机地址:localhost或者127.0.0.1,是操作系统固定用来表示本机的地址。如果你确实把MySQL安装在局域网中另一台电脑上了,那么这里请填写它的IP地址,并确保当前写程序的机器,和数据库服务器在网络上是可互通的。
  • 用户名: MySQL安装配置时,默认的用户名就是root。
  • 数据库连接密码:MySQL安装配置时,你所写的密码,还记得吗?在本例中,密并没有直接写在代码——这很英明,它在运行时接受输入。

“pwd.c_str()”是什么意思?当需要“字符串”时,C++标准库提供std::string我们用得最多,这里在输入pwd时,也不例外,不过此处的connect所需要的字符串,使用的的是C语言的字符串类型,还好,一个std::string的对象,提供c_str()函数,由于转换到C语言的字符串。

019行同时也是一个if语句,如果连接失败,MySQL++有两种处理方法:

其一、抛出“异常”——噢,我们现在对“异常”一点都没有概念,所以我们暂不用它;

其二、简单地返回“假”,这正是我们此时喜欢的。要得到这样的行为,只需在构造con对象时,传递“false”的值。

程序如果连接不上MySQL,将输出一条出错提示,然后直接退出主函数。

  • 查询并获取数据

025     mysqlpp::Query query = con.query("SELECT abs_index, day_index, name"
", province, sex, item, score FROM champions_2008 ORDER BY abs_index"
);

028 mysqlpp::StoreQueryResult res = query.store();

030
if (!res)
{

cout << "查无记录?请检查程序中query语句是不是写错了!" << endl;
return
-1;
}

建立连接之后,我们就可以开始查询了。类似打通电话,你就可以来一句:“喂,给我5公斤大米!”。哈哈,大米不可能顺着电话线过来,但对于“数据库连接”,只要客户端说对了话(正确的SQL),所要的数据就会迅速从连接上传回来。

laugh〖轻松一刻〗:什么是SQL

那么,什么是正确的,查询数据库的“话”呢?除了编码正确以外,语法、逻辑,也都要正确。不信,你打电话给米店老板,来一句:“你地!米,大大地,5公斤,我地,要!”。可能电话放下5分钟,就有愤青提着双节棍来“伺候”您了——别问我为什么。

数据库的查询语言称为:SQL(Structured Query Language/结构化查询语言),如果你完全不懂,那……开始学吧,先学到能看懂本书中出现的SQL就可以了,我认为只需要2.5天时间。

025行,程序只是准备好了一个“查询”对象,这个对象由“数据库连接对象”con产生。028行的query.store()函数才真正发起查询,并且将查询结果保存到res对象。res的类型是“StoreQueryResult”,非要翻译成中文的话,就是“存储查询结果”。

030行判断是否查无结果。“查无结果”本不算错误。但在本例,那肯定你是出什么差错,因为我明明准备了51块金牌记录呢。

  • 显示数据
036  for (unsigned int i = 0; i < res.num_rows(); ++i)
{

cout << "第" << res[i]["abs_index"] << "金";
cout << "/t收获于第" << res[i]["day_index"] << "天" << endl;

cout << "金牌获得者:" << res[i]["name"];

043
cout << "/t性别:" << ((res[i]["sex"] == "0")? "女" : "男") << endl;

cout << "冠军来自:" << res[i]["province"] << endl;
cout << "获奖项目:" << res[i]["item"] << endl;

cout << "成绩:";
049 if (res[i]["score"].is_null())
{

cout << "N/A" << endl;
}

else

{

cout << res[i]["score"] << endl;
}


cout << "----------------------------------------" << endl;
system("pause");
}


return
0;
}

res.num_rows()函数,返回查询结果记录行数。036行开始一个for循环。它将访问每一条记录。

res[i]["abs_index"] 返回查询结果中,“abs_index”字段的值,它保存的是金牌获将次序;其它的还有“name”字段保存冠军姓名、“province”保存所在省份……等等。

043行我们猜到是在输出冠军的性别,不过代码看上去有些奇怪,其实它可以写成相对如下形式:

if (res[i]["sex"] == "0")
{

cout << "/t性别:女" << endl;
}

else

{

cout << "/t性别:男" << endl;
}

缩写方式格式称为“: ? 表达式”,其语法为:

(条件)? 值1 : 值2

即,当条件为真,则表达式结果为值1;条件为假,则表达式结果为值2。

代码中的字符串,存在几个“/t”,它表示“制表位符”,即对应到键盘上的“Tab”键,如果还是不懂它作用,打开写字板程序,按一下Tab键或许能有启发,再不行,就实际运行本程序吧。

有些体育项目只输赢,不计录成绩,因此在数据库中,一些金牌记录就没有成绩这一项,049行通过is_null()函数判断这种区别,对于没有成绩的记录,输出一个“N/A”表示。

homework〖课堂作业〗: 练习使用 “? :”表达式

例中,采用if/else语句输出成绩,请改为 “: ?” 表达式。

 

最后,给出一个运行结果截图:

图 94 “Hello Database 控制台版”运行结果

图 94 “Hello Database 控制台版”运行结果

 

 [回到目录]

白话C++

作者:庄严/www.d2school.com

原创粉丝点击