大家好,时隔多月,《C语言番外》又迎来了一次更新。本次更新将介绍一下在 C 语言中如何连接数据库,并遍写一个程序作为示例。
在很多学校里,C语言和数据库是两门不同的课程,所以很多同学就无法把这两种技术关联起来,我上大学的时候也是这样,这就是我写这篇公众号的原因。
在计算机的世界里,各种技术都不是独立的孤岛,而是相互关联、相辅相成的。就像在一个球队里,有守门员,有前锋,有后卫。C语言和数据库之间的关系也是如此,在没有学习数据库的时候,我们把用户输入的数据保存在数组里,这有一个非常明显的缺陷就是数组是在内存中,在断电或程序退出的情况下,内存中保存的数据就丢失了。有同学可能会说保存到文件里就可以了,数据量很小的时候确实可以,但是当数据量较大或者数据结构较复杂的情况下,将数据保存到文件的做法也会变得非常复杂。
数据库技术正是为了解决数据持久化的问题而生的。数据库非常擅长保存结构化的数据。所谓结构化的数据,就是C语言中的结构体变量、面向对象编程语言中的对象;非结构化的对象则是像图片、音频这种的。所以绝大多数的程序都会连接数据库进行数据持久化,也因此催生出多样化的数据库技术来应对多样化的需求。常见的数据库比如 Oracle、MySQL、SQL Server、PostgreSQL 都可以运行在服务器上存储大量的数据,也有用于嵌入式系统、手机上的 SQLite 数据库。
操作步骤
本文将以 SQLite 数据库为例,但是连接其他数据库也是类似的步骤。数据库编程很简单,具体步骤如下:
- 引入库文件。
- 连接数据库。
- 执行SQL。
- 关闭数据库连接。
SQLite 是一个使用 C 语言实现的非常小巧、快速的数据库引擎,常被应用于嵌入式系统、手机App中,使用 SQLite 时,系统中不需要安装额外的软件,数据库中的数据会被保存到一个单独的文件中。基于这些优点,SQLite 非常适于我们在学习、练习中使用。读者可访问SQLite官网了解更多有关 SQLite 数据库的信息。
引入库文件
我们可以在 SQLite 的官网下载到 SQLite 的源文件,包含两个文件(SQLite.h/SQLite.c),可以通过两种方式将 SQLite 引入自己的工程(二选一):
- 将 SQLite 源文件编译安装到自己的系统中,这样 SQLite 就成为你电脑上的一个函数库了,可以像使用标准库那样使用它。
- 直接将 SQLite 的源文件复制到自己的项目中。
本教程附带的示例程序采用了第一种方式。然后在用到 SQLite 的地方只需要使用#include <sqlite3.h>把 SQLite 的头文件包含进来即可。这个头文件中提供了操作 SQLite 数据库的一些函数,这样,我们就可以通过调用这些函数来愉快的使用 SQLite 了。如果你使用方式二,则只需要把引入头文件的地方改为#include "sqlite3.h"即可。
在其他编程语言中也是类似的道理,比如在 Java 中,只需要把对应数据库提供的驱动 jar 包放到自己的项目中即可。
连接数据库
连接 SQLite 数据库很简单,只需要调用sqlite3_open()函数即可,这个函数接收两个参数,第一个参数是数据库文件的位置(如果指定的文件不存在,SQLite 会自动创建一个);第二个参数是一个sqlite3 *变量的地址,连接数据库成功后,这个变量就可以代表数据库连接,后面的其他操作也会用到这个变量。
sqlite3 *db;
char *dbPath = "/path/to/database/file";
sqlite3_open(dbPath, &db);;
这个步骤的主要作用就是告诉程序我们的数据库在哪里,对于 SQLite 来说,只要一个数据库文件的保存位置即可;而对于其他数据库而言,一般需要提供数据库服务器的 IP 地址、端口、用户名、密码 等信息。 由于频繁的连接数据库、关闭数据库连接的操作是非常消耗系统资源的,所以在 Demo 中,我们在程序开始运行的时候执行一次连接数据库的操作,然后在程序结束的时候做一次关闭数据库连接的操作即可;对于运行在服务器上的数据库来说,因为服务器需要同时响应很多客户端的请求,一般会使用数据库连接池来尽量避免频繁创建、销毁数据库连接。
执行SQL
连接好数据库后,就可以在这个数据库中执行 SQL 语句了。例如,我们新创建的数据库中需要先在里面把表创建好:
char *errMsg = 0;
char *sql = "create table student(name text, age int)";
int rc = sqlite3_exec(db, sql, NULL, 0, &errMsg);
if (rc != SQLITE_OK) {
fprintf(stderr, "SQL error: %s\n", errMsg);
sqlite3_free(errMsg);
}
通过调用sqlite3_exec()函数在 SQLite 中执行 SQL 语句。这里对这个函数的几个参数做一个简单的介绍:
- 第一个参数传我们在连接数据库那一步中创建的
sqlite3 *指针。 - 第二个参数是我们要执行的 SQL 语句,这里是
char *类型。 - 第三个参数传的是回调函数,后面会着重讲一下如何使用这个回调函数。
- 第四个参数可以传任意类型的指针,SQLite 在调用回调函数时会将这个指针传给回调函数的第一个参数,这样我们就可以借助这个指针来传递一些信息了。
- 第五个参数错误信息,如果执行 SQL 出错了,可以通过打印这个错误信息来知道错误原因。
再讲一下回调函数,SQL 大致可以分为两类:查询和执行。查询指主语句为SELECT的语句,执行则是除SELECT外的其他语句。他们区别就是查询语句有返回值而执行语句没有返回值。回调函数的作用就是用来处理返回值的,例如在SELECT name, age from student这条 SQL 语句的结果有5条记录,那么回调函数会被调用5次,每次传一条记录过来。下面是 SQLite 官网上提供的一个回调函数用法:
static int callback(void *NotUsed, int argc, char **argv, char **azColName){
int i;
for(i=0; i<argc; i++){
printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
}
printf("\n");
return 0;
}
当执行上述 SQL 语句时,这个回调函数会有如下输出:
1
2
3
4
5
6
7
8
name = zhangsan
age = 12
name = lisi
age = 15
name = wangwu
age = 22
在实际的项目开发中,一般还会用到一些 ORM(Object Relational Mapping对象关系映射) 框架。比如在 Java 中大名鼎鼎的 Hibernate、MyBatis,Golang 中的 GORM 等,这些 ORM 框架对数据库操作提供了进一步的封装,我们使用这种框架时,不需要去关心 SQL 怎么去写,只需要调用 ORM 框架提供的增删改查的函数,ORM 框架会自动生成 SQL 并执行。
关闭数据库连接
关闭数据库连接的操作也很简单:sqlite3_close(db);。
Demo
本文附带的 Demo 可自行访问GitHub下载。
在这个 Demo 中,我们实现了这样的功能:
- 循环从控制台输入学生信息(含姓名、年龄),并保存到数据库中;
- 循环结束后,读取数据库中保存到所有学生信息,并打印到控制台。
在 Demo 中,我创建了 3 个 C 文件,分别是main.c、student.c、student_dao.c。其中student.c文件中封装了 Student 结构的定义;student_dao.c文件中封装了与数据库操作相关的功能;main.c中写了 main 函数及程序主逻辑调用。
因为程序使用了数据库存储数据,所以每次程序执行时录入的学生信息都会被保存下来。
本期 C 语言番外到此结束,have fun!
