C语言与数据库操作入门(Win版)
C语言与数据库操作入门(Win版)
数据库,DataBase,学C语言的是不是想说,很想爱她却并不容易呢?不用着急,C语言也可以操作数据库的,既使你不会Windows API,只要参照本文的方法,写数据库应用程序,你也行。本文以MySql和Access数据库为基础,简要介绍C语言如何操作数据库,本文部分知识点也适用于linux下面编程使用MySql数据库。
一、如果你只会一点C语言,那么还有哪些知识需要你自己去补充呢?
(1)了解一下数据库的基本情况,发展史,很有必要,选用一种数据库,数据库有很多,开源数据库据说多达35个,常用的数据库也有如下几个:MySQL、 PostgreSQL、MaxDB、Ingres、SQLite。这些大部分都是免费开源的数据库,都需要你去下载安装的,微软还有收费的MSSQL,Access数据库可以使用,当然,对于大多数中国用户来说,MSSQL, Access免费使用,本文以MySql和Access数据库为例,示例C语言如何操作数据库。关于Access数据库,安装过office的基本上都有,如果不会使用它创建mdb数据库的话,请到http://www.bccn.net/Article/sjk/access/Index.html 本站的技术文档中学习一下。
(2)了解SQL基本语法,所谓的SQL,就是指结构化查询语言,数据库操作的基本语言,至少有四个语句需要你学习和掌握,它们就是Select查询语句,Update更新语句,Insert插入语句和Delete删除语句,其它的还有建表,建库,建关系等等。。可以参考偶上传的资料《mysql中文参考手册》,有很多的SQL语言学习书,想要深入学习的还可以看老外的《SQL语言艺术》这本。
资料下载:http://115.com/file/be42nia8
文件:mysql5.1中文手册.rar
(3)了解C语言与数据库互动的方式,其实只有两种,一种是通过win32 API接口,一种就是直接通过数据库提供的C头文件和DLL库直接操作数据库。mySQL同时提供两种接口方式,SQLite提供直接访问的C语言头文件和DLL库,SQLite还可以把代码直接编译进程序当中,使数据库访问更加快徢。Windows常用数据库接口是odbc接口,另外还有ADO, OLE DB接口,当然,和odbc相比,它们的学习基本上是一通百通的,数据库互动,基本上成了一个定式,创建数据库链结->创建语句对象->执行SQL命令->访问执行结果集->关闭链结,不论是java语言还是C#,还是VB,javascript语言,都是相同或是相似的,所以学习也是一通百通,因为重点并不在这里,而是在SQL本身的掌握上,请将重点移到SQL语言本身,数据库设计原则,存储过程上面。
(4)数据源DSN,关于如何创建数据库的链结数据源DSN,这个是基础知识,在控制面板/管理工具/数据源(odbc)中,如果不会玩的话,还是在网上去搜索一下图文教程,学习一下,本文就不重复了。
二、准备本示例所需要的数据库。
(1)下载并安装:mySQL的官网:http://www.mysql.com/downloads/,目前最新版本为5.5.18,虽然已被人收购,但并不影响我们学习SQL,版本分为企业版和公众版,公众版对于学习的个人是免费的,而且也是足够了,所以我们下载上面的MySQL Community Server版,另一个需要下载的是Connector/ODBC,这个是ODBC驱动,可选下载为MySQL Workbench (GUI Tool),这个是用于Gui前端管理软件,就是命令行的可视版,说白了就是一个窗口界面,你可以通过界面操作mySql数据库,会命令行的都习惯于使用命令行操作,如果你想成为它的专家,使用命令行吧。不懂英文页面?没关系,下载最简单的方式就是在上点那个最大的图,mysql installer for windows,上面所说的三个部件包括使用文档资料都放在一个安装包里了,209MB。测试了一下,从National Sun Yat-Sen University, Taiwan 台湾站http下载比较快。安装额外需求,.NET4运行库。补充说明一下,MySQL Workbench的安装需要VC++ 2010运行库和.net4运行库,数据库系统MySQL Community Server版和Odbc驱动的安装无任何附加安装需求,所以可以单独下载所需部件进行安装,不用注册个人资料也可以直接下载。
安装过程中如果有需要,可以选择Developer Componets下面的C include files/Lib files这个,是用来直接和数据库相链的C语言头文件和库文件。安装完成后,在弹出的配置MySql对话框,选择standard Confiuration,下一步,勾选include Bin Director in Windows Path这项,下一步后,在Modify Securiti Settings那里填写新的登录安全密码,下一步Execute执行既可完成。
资料下载:上次提供的5.1.3版客户端中文显示有问题,5.1.0版虽然中文显示没有问题,但在创建表约束那里有问题。最新的版本经过测试没有这个二个问题。
http://115.com/file/an4yzdwc#
mysql-5.5.18-win32.rar (mysql 5.5.18官方版,31MB,)
http://115.com/file/c2bpnc9g
mysql-connector-odbc-5.1.9-win32.rar (mysql odbc 驱动32位 2.5MB)
(2)数据库示例:以简单的通讯录为例。
数据库:PersonAL
表1:Users(本表存放用户信息)
字段(共3个):uid (自动数字),name(char, 10)--姓名,pass(char, 15)--密码
表2:PersonInfo(本表存放联系人信息)
字段(共10个):pid (自动数字), name(char, 10) --姓名,gender(char, 2)--性别, birthday(date)--生日
city(char, 10)--所在城市, telphone(char, 20)--联系电话, qq(char, 15) -- QQ号,
web(char 30)--微博, gid(int)--所属组id,uid(int)--所属用户id
表3:GroupInfo(本表存放联系人分组信息)
字段(共2个):gid(自动数字), name(char, 20) --组名
(2.1)Access数据库创建:
请参照Access软件使用创建PersonAL.mdb数据库,并创建这三张对应的表,如果不会,请赶快学习,不重复了。同时这里也提供创建好的数据库。
建好的数据库:http://115.com/file/an4ntb4n#
PersonAL.rar
(2.2)MySql数据库创建:你可以先把上面的<mysql中文手册>第三节教程,打开学习一下,这里要提醒的是每个SQL语句都以分号为结束,和C语言一样,不要忘记打了, 微软的SQL Server中不需要这个分号。如果SQL命令输错了,请\c,然后回车返回。
(2.3)mySql的中文问题,默认的语言不符合要求,我们需要修改mysql目录下面的my.ini中的编码语言,这里一共有两地方需要修改,一个是[client]客户端那里,把里面的default-character-set=xxxx 改成 default-character-set=gbk或是gb2312(中文编码),可使mysql.exe客户端在查询的时候显示中文数据,另一个服务器端,修改成default-character-set=utf8,这样可以保证和网页的常用编码一致,保存配置,打开控制面板中的服务,重启mysql服务器,从开始菜单中到mysql程序组启动mysql命令行客户端,输入安装时设置的密码,进入到mysql命令行状态:
mysql->
在里面输入如下命令创建数据库,每个分行是一条。PersonAL.sql:
create database personal;
-- 选择数据库为当前操作
use PersonAL;
-- 创建相应的表
create table User(
uid int unsigned not null auto_increment,
name char(10) not null,
pass char(15) not null,
primary key(uid)
);
create table PersonInfo(
pid int unsigned not null auto_increment,
name char(10) not null,
gender char(2) not null,
birthday date,
city char(10),
telphone char(20),
qq char(15),
web char(30),
gid int unsigned not null,
uid int unsigned not null,
primary key(pid)
);
create table GroupInfo(
gid int unsigned not null auto_increment,
name char(20) not null,
primary key(gid)
);
-- 添加主外键约束
alter table personinfo
add constraint fk_gid
foreign key personinfo(gid)
references groupinfo(gid);
alter table personinfo
add constraint fk_uid
foreign key personinfo(uid)
references user(uid);
-- 添加测试数据
insert into user(name, pass) values('admin', '123');
insert into user(name, pass) values('hello', '456');
insert into groupinfo(name) values('家人');
insert into groupinfo(name) values('朋友');
insert into groupinfo(name) values('公司同事');
insert into groupinfo(name) values('同学');
insert into groupinfo(name) values('网友');
show create database personal;
当然,如果你不想输入这么多的东西,告诉你一个小巧门,直接复制语句到mysql命令窗口中,粘帖上去,然后回车,就OK了,但是我们还是需要随着学习的深入,了解并掌握SQL脚本的书写,一般做法是用一个文本文件来书写这些SQL语句,以备在不同的机器上使用或是恢复数据库用。微软的SQL Server一般把这些命令脚本存放在.sql文件中,可以直接导入导出。mysql也有数据库导入导出的功能,怎么使用这个问题作业就交给你了,学会上网搜索,这就是学习方法,别人告诉你有这么一回事存在,你就要自己去找,不要总等着别人来喂你。当你在这个命令行中再输入select * from groupinfo;后,显示5条中文的数据,就表示数据库创建成功。如下图所示。输入quit可以退出mysql命令行。更多SQL语法详解请参见手册第13章那里。
上面的脚本语言要说明的是注释行用--,这个和SQL Server的注释语法相同,不同的是mysql里--后面要加一个空格。没有添加其它表约束,留个作业给你,请查询手册,为PersonInfo添加数据约束,如性别只能输入男或是女,QQ号必须是数字,web必须符合网址要求,同时为groupInfo表添加组名的维一性约束,既不能输入相同的组名字。如果创建完数据库,发现最后几条中文数据插不进去,输入show create database personal;后显示数据库编码为非utf8的其它值,就是数据库编码的问题了,解决办法见上面所述,需要退出命令行,修改配置文件,然后重启服务器。如果发现你自己的数据插入不进PersonInfo表中,那是主外健约束引起的,在主表播入的数据,在子表中必须存在,所以如果你不明白这里所述的,说明你还不了解数据库,既使学会程序操作,也没有意义,静下心来认真拿本SQL书学习一下为上策。
三、SQLExt.h头文件与odbc32.lib库。
(1)了解ODBC接口以及为什么使用ODBC接口。
ODBC API 接口是(Open Database Connectivity)开放式数据库接口,它建立了一组规范,并提供了一组对数据库访问的标准API,这些API利用SQL来完成其大部分任务。ODBC本身也提供了对SQL语言的支持,用户可以直接将SQL语句送给ODBC。个基于ODBC的应用程序对数据库的操作不依赖任何DBMS,不直接与DBMS打交道,所有的数据库操作由对应的DBMS的ODBC驱动程序完成。也就是说,不论是FoxPro、Access还是Oracle数据库,均可用ODBC API进行访问。由此可见,ODBC的最大优点是能以统一的方式处理所有的数据库。至于为什么使用它,而不使用DAO接口,微软的网页是这么解释的:从 Visual C++ .NET 起,Visual C++ 环境和向导不再支持 DAO(不过提供了 DAO 类,仍可供您使用)。Microsoft 建议对新项目使用 OLE DB 模板或 ODBC。
(2)由于操作ODBC 需要win32 API接口,所以编程环境肯定就不能用tc2.0之类的老C语言编译系统了,这里推荐使用VC6.0以上的版本,或是使用MinGW(gcc)做为编译器的IDE:c-free, devcpp,codelite/codeblocks等编程环境,如果你想跟着本文练习,那么赶快下载装上喽。odbc 3.X版操作使用的头文件主要是SQLExt.h,所有的宏,数据类型,及接口均在这里定义,你可以打开浏览一下,odbc所需链结库为odbc32.lib,在Mingw里是libodbc32.a这个文件,所以需要在链结中添加这个库,否则将出现函数找不到的链结错误。
(3)ODBC的九类接口函数:也可以参见(手册26章1.16节详细)
I. 连接资料来源(Connecting to a Data Source)
1. SQLAllocEnv.
2. SQLAllocConnect.
3. SQLConnect.
4. SQLPriverConnect.
5. SQLBrowseConnect.
II. 取得驱动程序及资料来源的相关讯息
1. SQLDataSource.
2. SQLGetInfo.
3. SQLGetFunctions.
4. SQLGetTypeInfo.
III. 设定及取得驱动程序的选项
1. SQLSetConnectOption.
2. SQLGetConnectOption.
3. SQLSetStmtOption.
4. SQLGetStmtOption.
IV. 准备SQL指令之需求
1. SQLAllocStmt.
2. SQLPrepare.
3. SQLSetParam.
4. SQLParamOptions.
5. SQLGetCursorName.
6.SQLSetCursorName.
7. SQLSetScrollOptions.
V. 传送及执行需求
1. SQLExecute.
2. SQLExecDirect.
3. SQLNativeSql.
4. SQLDescribeParanl.
5. SQLNumParams.
6.SQLParamData.
7. SQLPutData.
VI. 取得执行结果及有关结果的讯息
1. SQLRowCount.
2. SQLNumResultCols.
3. SQLDescribeCol.
4. SQLColAttributes.
5. SQLBindCol.
6.SQLFetch.
7. SQLExtendedFetch.
8. SQLGetData.
9. SQLSetDos.
10. SQLMoreResults.
11. SQLError.
VII. 取得有关资料来源系统回录(System tables or Catalog)的讯息
1. SQLColumnPrivileges.
2. SQLColumns.
3. SQLForeignkeys.
4. SQLPrimaryKeys.
5.SQLProcedureColumns.
6. SQLProcedures.
7. SQLSpecialColumns.
8. SQLStatistics.
9. SQLTablePrivileges.
10. SQLTables.
VIII. 结束 SQL 指令需求
1. SQLFreeStmt.
2. SQLCancel.
3. SQLTransact.
IX. 结束与资料来源的连接
1. SQLDisconnect.
2. SQLFreeConnect.
3. SQLFreeEnv.
API学习的方法,和我们学习C语言的库函数没什么两样,准备好msdn,或是通过网络搜索功能,你可以查找到每个Api函数的使用,同时也可以学习msdn上面微软写的示例代码。这些API都是以SQL打头的函数,规范的函数命名也值得你我学习,看着很多,但本文不打算全部都用上,只用其中很少量的函数,所以不要害怕,需要其它功能的时候,有这个表格,也可以方便你查找函数使用。
四、SQL数据类型和C语言数据类型。
SQL_C_CHAR 是默认的 ODBC 传输类型,其它数据类型请参见手册中的ODBC数据类型(26章1.17节那里),不重述了。这一章里面还有关于Access数据库与Mysql数据库数据导入方法,用SELECT LAST_INSERT_ID();取得auto_increment值,以及ODBC应用基本步骤、API引用,及调用错误代码含义,初学时可以翻翻这些地方,说不定你的问题就在上面就解决了。
五、操作过程。
ODBC操作步子如下:
· 配置MyODBC DSN。(这一步不是必须的,可以直接连接)
· 连接到MySQL服务器或是Access数据库。
· 初始化操作。
· 执行SQL语句。
· 检索结果。(Select语句返回结果集,update/delete/insert返回受影响的行数)
· 执行事务。
· 断开与服务器的连接。
图解见手册26.1.15节。这个操作步子在Java桥连里是一样的,使用的是jdbc:odbc:数据源名,JavaScript也是同样的步子,偶以前写过网页JavaScript操作access数据库,所以一通百通。为了更好的理解数据库操作,我们采用传统的控制台界面来示例,其实做windows界面版的,基本数据库操作和这个控制台的一样,没什么两样,抛开界面,更有利于我们看到问题的实质。
C示例代码,操作PersonAL.mdb库。
#include <stdlib.h>
#include <windows.h>
#include <sqlext.h> //这个应该是必须的, 链结时需要odbc32.lib
int main()
{
SQLHENV henv = NULL; //环境句柄
SQLHDBC hdbc = NULL; //odbc连接句柄
SQLHSTMT hstmt = NULL; //语句句柄
SQLRETURN retcode;
SQLCHAR connout[256];
SQLSMALLINT connout_len;
SQLCHAR connstr[] = "Driver={Microsoft Access Driver (*.mdb)};Dbq=PersonAL.mdb;Uid=Admin;Pwd=;CharSet=gbk;";
//分配环境
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,&henv);
//设置环境属性
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
//分配链结
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
//创建数据库链结
retcode = SQLDriverConnect(hdbc, NULL, connstr, SQL_NTS, connout, sizeof(connout),
&connout_len, SQL_DRIVER_NOPROMPT);
if(retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
{
printf("connnect access ok!\n");
printf("%s\n", connout);
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
retcode = SQLPrepare(hstmt, (SQLCHAR *)"select * from user", SQL_NTS);//准备sql查询
retcode = SQLExecute(hstmt);//执行SQL命令
SQLINTEGER cbsatid=SQL_NTS;
while (SQLFetch(hstmt)!=SQL_NO_DATA_FOUND) //循环从结果集中取每一行数据
{
SQLCHAR name[20];
SQLCHAR pass[20];
SQLINTEGER id;
//实际上数字也可以用C_CHAR读出来。
SQLGetData(hstmt, 1, SQL_C_LONG, &id, sizeof(SQLINTEGER), &cbsatid);
//name 为接收数据的指针,20为数据缓冲区的大小,
SQLGetData(hstmt, 2, SQL_C_CHAR, name, 20, &cbsatid);
SQLGetData(hstmt, 3, SQL_C_CHAR, pass, 20, &cbsatid);
printf("id is:%ld, name is :%s, pass is:%s\n", id, name, pass); //打印取得的值
}
if(hstmt)
{
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);//释放语句句柄
hstmt = NULL;
}
if(hdbc)
{
SQLDisconnect(hdbc);//断开连接
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);//释放连接句柄
hdbc = NULL;
}
if (henv)
{
SQLFreeHandle(SQL_HANDLE_ENV, henv);//释放环境句柄
henv = NULL;
}
}
return 0;
}
程序编译说明,vc6下面,odbc32.lib默认是连接库,所以上述程序直接编译连接通过,生成执行文件。数据库放在工程项目中,既可显示正确的数据,如果是在debug/Release目录下执行,把数据库拷到当前exe目录下执行既可。如果是使用的codeblocks,请在菜单project(工程)/Build Option(生成选项)中,在Linker Setting(链结设置)点add(添加),输入odbc32,确定,既可使程序链结上odbc32运行库。在codeblocks中,用debug方式生成的exe,35KB左右,如果你选择Release方式生成可执行文件,才9.5KB,是不是很小很爽呢?有下图为证:(如果你不会使用codeblocks,可参考偶写的另一帖:http://bbs.bccn.net/thread-345867-1-1.html)
-------------- Build: Release in dbtest ---------------
Compiling: main.c
Linking console executable: bin\Release\dbtest.exe
Output size is 9.50 KB
Process terminated with status 0 (0 minutes, 0 seconds)
0 errors, 0 warnings
要连接使用MySQL,怎么做呢?将程序里面的SQLCHAR connstr[] 链拉字符串这里,改成SQLCHAR connstr[] = "DRIVER={MySQL ODBC 5.1 Driver};SERVER=localhost;UID=root;PWD=sa;DATABASE=personal;CharSet=gb2312;";程序其它部分不变,既可使用MySQL里我们创建的数据库,或是你也可以自己改动一下,将查询语句改成"Select * from GroupInfo",然后就可以看到从组表中查询出来的数据,比Access那个数据库多一条数据(偶故意的),所以证明你连结的是MySQL数据库,不是Access数据库,至于为什么是DRIVER={MySQL ODBC 5.1 Driver};,只要你打开控制面板中的odbc数据源,对比一下多个数据源驱动名,你就明白为什么这么写字符串。由此也可以看出,除了使用odbc接口程序的通用性,除了链结字符串的不同外,其它都是相同的,所以很多人把链结字符串写进一个文本文件中,做为数据库应用的配置文件,只要改动这个文件,就可以实现连接不同的数据库。
六、自己动手封装odbc操作接口,方便使用。
为什么要封装?当然是为了我们方便使用,观察上面的程序,
//分配环境
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,&henv);
//设置环境属性
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION, (void*)SQL_OV_ODBC3, 0);
//分配链结
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
这些对应的API调用,基本上是一个固定的格式,所有的参数,一些宏啊什么的,又长又难记,我们学习的时候,如果过久了,回头让你重写这些代码,必定你是记不住这么多的,所以既然当前你在学习数据库,所以把这些代码封装起来更好,例如写一个ODBC_GetConnection(char * string)函数用于获取数据库链结,用ODBC_CreateStatement(char * sql)来创建SQL语句,用ODBC_ExecuteCommand(),ODBC_ExecuteQuery(),分别用于执行增,删,改,查命令,具体代码就交给你自己写了,如果你不知道怎么用函数来封装这些东西,只能说你最基本的C语言都没有掌握,看本文也就毫无意义了。理解了这些API的调用后,实际上对于你理解MFC中的CDataBase类,CRecordSet类的使用和内部工作原理了解也是大有帮助。
七、链表与结果集
还记得偶的上一篇文章,关于链表的进一步深入学习么?链表的应用在本文中又一次发挥了重要的作用。观察上面的程序,在SQL语句执行后,实际上结果集中的数据在系统那里,我们只是通过SQLGetData从里面抓出数据而矣,然后一般情况下,操作完成就会关闭数据库,系统的结果集就消失了(当然,你可以使用链接池)所以,我们可以使用一个链表将里面的数据装出来,在关闭数据库后,我们也可以对结果集中的数据做进一步处理,这个时候,可能我们会查询多个表,得到多个结果集,所以在C中使用通用链表,将使我们的操作更加方便。C++中有STL的List类,更容易保存数据到类对象中。如果你还学习过C#,还可以发现一个更有趣的封装实例DataSet结果集类,可以直接从DataRow,产生新的结果集,这种封装应该说把数据的抽象提高到一个新的高度。另外要提提醒新手的是,如果使用链表做为数据结果集保存对象,请不要使用Select * from语句将数据库中的数据全部提取出来,如果是这样的话,表中有1000万行记录的时候,将让你的程序占用大量的内存,正确的方法是使用Sql分页语法,例如一次只取出10行记录,灵活使用SQL语句,将让你的程序运行更快,更有效,如果你不知道SQL分页,请搜索相关的知识或是看SQL书。
八、在其它机器上运行你的数据库应用程序。
一种办法是将你的程序和数据库一起打包进行安装,对于Access这样的数据库来说很方便的,直接把程序和数据库拷在一起发布给对方就行了,对于象MySQL这样的程序来说,让别人的机器上也安装MySQL是一个办法,然后通过程序或是mysql的客户端执行SQL脚本创建数据库。大多数时候,都是通过网络来链结MySQL的,也就是说连接字符串SERVER=这里改成对方的IP地址,xxxx.xxxx.xxx.xx,既可通过IP地址连上MySQL服务器,事实上,BBS论坛,网站,微博,这些都是以数据库为基础开发的,采用的依然是这种服务器和客户端方式。
总结:
C语言和数据库怎么操作?看到N多的人在问这个问题,但实际上,问的人本身是否了解数据库?了解C语言?了解API?还是仅仅感兴趣而矣?不用怀疑,写本文的时候,偶对MySQL并不了解,边看手册边写的,但以前学过SQL,因此一样可以写出来,所以学程序的不要局限于一种语言,有机会你多尝试一种新的语言,你会发现它们学习的共性,如果本文带给你一些学习的新思路和深思,本人将不胜荣幸,如果哪个地方写得有问题,请留言。
附:JavaScript脚本操作Access操作数据库代码,对比一下C代码,本质上不变。
var connstr ="{DRIVER={Microsoft Access Driver (*.mdb)};DBQ=E:\\Test\\Test.mdb";
var DBconn =new ActiveXObject("ADODB.Connection");
var rs = new ActiveXObject("ADODB.RecordSet");
var Sqls ="insert into table1(user,pass) values('bluewind','123')";
DBconn.open(connstr);//链结Test.mdb数据库
DBconn.execute(Sqls);//发送SQL命令插入一条记录
Sqls = "select * from table1 where user ='bluewind'";
rs.open(Sqls,DBconn,1);//查询数据库中用户名为bluewind的记录。
if(!rs.EOF)
{
document.write("User-Name="+rs("user").value);//打印读取出来的值
document.write("Pass-word="+rs("pass").value);
}
rs.close();//关闭记录集
DBconn.close();//关闭链结
</script>
。。。。。。。(全文完...)