C++ 数据库MySQL 学习笔记(3) - 数据库操作

news2024/11/22 11:07:57

C++ 数据库MySQL 学习笔记(3) - 数据库操作

视图操作

视图是从一个或多个表中导出来的表,是一种虚拟存在的表。视图就像一个窗口,通过这个窗口可以看到系统专门提供的数据,这样用户可以不看整个数据库表中的数据,而只关心对自己有用的数据。视图可以使用户的操作更方便,而且可以保障数据库系统的安全性。

为什么要使用视图

通过前面章节的知识可以发现,数据库中关于数据的查询有时非常复杂,例如表连接、子查询等,这种查询会让程序员感到非常痛苦,因为它的逻辑太复杂、编写语句比较多,当这种查询需要重复使用时,很难每次都编写正确,从而降低了数据库的实用性。

在具体操作表之前,有时候要求只能操作部分字段,而不是全部字段。例如,在学校里,学生的智商测试结果一般都是保密的,如果因为一时疏忽向查询中多写了关于“智商”的字段,则会让学生的智商显示给所有能够查看该查询结果的人,这时就需要限制使用者操作的字段。

为了提高复杂的SQL语句的复用性和表的操作的安全性,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储数据值的形式存在,行和列数据来自定义视图的查询所引用的基本表,并且在具体引用视图时动态生成。

视图使程序员只关心感兴趣的某些特定数据和他们所负责的特定任务。这样程序员只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高数据库中数据的安全性。

创建视图

虽然视图可以被看成是一种虚拟表,但是其物理上是不存在的,即MySQL并没有专门的位置为视图存储数据。根据视图的概念可以发现其数据来源于查询语句,因此创建视图的基本语法为:

    CREATE[OR REPLACE] VIEW viewname[columnlist]   
    AS SELECT statement                               

其中,CREATE表示创建新的视图;REPLACE表示替换已经创建的视图;viewname为视图的名称;columnlist为属性列;SELECT statement表示SELECT语句;

注意! 创建视图需要登陆用户有相应的权限,

查看权限方法

mysql>  use school;   #选择数据库school                                                                       
mysql>  select user, Select_priv, Create_view_priv FROM mysql.user;#查询数据库用户创建和选择视图权限 

在单表上创建视图

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                               
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student AS select id, class_id, name from student ;#为学生表创建视图 
mysql>  desc view_student;#查看视图 
mysql>  select * from view_student;   #根据视图进行查询  

在多表上创建视图

    CREATE[OR REPLACE] VIEW viewname[columnlist]   
    AS SELECT statement                               

其中,CREATE表示创建新的视图;REPLACE表示替换已经创建的视图;viewname为视图的名称;columnlist为属性列;SELECT statement表示SELECT语句;与单表上创建视图不同的是,SELECT子句是涉及到多表的联合查询语句。

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                                                                      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student_class AS select student.id, student.name, class.name,  class.teacher from class inner join student  on class.id = student.class_id;#为学生表创建视图 
mysql>  desc view_student_class;#查看视图 
mysql>  select * from view_student_class;   #根据视图进行查询  

查看视图

创建完视图后,像表一样,我们经常需要查看视图信息。在MySQL中,有许多可以实现查看视图的语句,如DESCRIBE、SHOW TABLES、SHOW CREATE VIEW。如果要使用这些语句,首先要确保拥有SHOW VIEW的权限。本节将详细讲解查看视图的方法。

使用DESCRIBE | DESC语句查看视图基本信息

前面我们已经详细讲解过使用DESCRIBE语句来查看表的基本定义。因为视图也是一张表,只是这张表比较特殊,是一张虚拟的表,所以同样可以使用DESCRIBE语句来查看视图的基本定义。DESCRIBE语句查看视图的语法如下:

    DESCRIBE | DESC viewname;

在上述语句中,参数viewname表示所要查看设计信息的视图名称。

使用SHOW TABLES语句查看视图基本信息

从MySQL 5.1版本开始,执行SHOW TABLES语句时不仅会显示表的名字,同时也会显示视图的名字。

下面演示通过SHOW TABLES语句查看数据库school中的视图和表的功能,具体SQL语句如下,执行结果如下图所示。

    SHOW TABLES;

在这里插入图片描述

使用show create view/table 语句查看视图创建信息

    SHOW CREATE TABLEVIEW viewname;   

更新视图数据

更新视图是指通过视图来插入(INSERT)、更新(UPDATE)和删除(DELETE)表中的数据。因为视图实质是一个虚拟表,其中没有数据,通过视图更新时都是转换到基本表更新。更新视图时,只能更新权限范围内的数据,超出范围就不能更新了。

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                                                                      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student AS select id, class_id, name from student ;#为学生表创建视图 
mysql>  desc view_student;#查看视图 
mysql>  select * from view_student;   #根据视图进行查询  
mysql>  update view_student set name='小花花' where name='小花'; #通过视图更新小花为小花花

不能更新的情况:

  1. 视图中包含SUM()、COUNT()、MAX()和MIN()等函数

  2. 视图中包含UNION、UNION ALL、DISTINCT、GROUP BY和HAVING等关键字

  3. 视图对应的表存在没有默认值的列,而且该列没有包含在视图里

  4. 包含子查询的视图

  5. 其他特殊情况

修改视图

修改视图是指修改数据库中存在的视图,当基本表的某些字段发生变化的时候,可以通过修改视图来保持与基本表的一致性。ALTER语句来修改视图。

使用ALTER语句修改视图

ALTER VIEW viewname[columnlist]   
AS SELECT statement                               

这个语法中的所有关键字和参数除了alter 外,其他都和创建视图是一样的,因此不再赘述。

mysql>  use school;   #选择数据库school 
mysql>  alter table student add privacy varchar(64);# 增加私隐列                                                                                      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  ALTER VIEW view_student_class AS select student.id, student.name, class.name, class.id as class_id, class.teacher from class inner join student  on class.id = student.class_id;#为学生班级表视图增加 class_id 字段 
mysql>  desc view_student_class;#查看视图 
mysql>  select * from view_student_class;   #根据视图进行查询  

删除视图

删除视图是指删除数据库中已存在的视图。删除视图时,只能删除视图的定义,不会删除数据。

在MySQL中,可使用DROP VIEW语句来删除视图,但是用户必须拥有DROP权限。删除视图的语法如下:

    DROP VIEW viewname [viewname]

在上述语句中,参数viewname表示所要删除视图的名称,可同时指定删除多个视图。

mysql>  use school;   #选择数据库school      
mysql>  ;#查询数据库用户创建和选择视图权限    
mysql>  CREATE VIEW view_student_class AS select student.id, student.name, class.name, class.id as class_id, class.teacher from class inner join student  on class.id = student.class_id;#为学生表创建视图 
mysql>  drop view view_student_class;#删除视图 

触发器

触发器(TRIGGER)是由事件来触发某个操作。这些事件包括INSERT语句、UPDATE语句和DELETE语句。当数据库系统执行这些事件时,就会激活触发器执行相应的操作。MySQL从5.0.2版本开始支持触发器。

通过本章的学习,我们将了解触发器的含义和作用、如何创建触发器、查看触发器和删除触发器的方法。同时,可以了解各种事件的触发器的执行情况。

创建触发器

在MySQL中创建触发器通过SQL语句CREATE TRIGGER来实现,其语法形式如下:

       CREATE trigger trigger_name BEFORE|AFTER trigger_EVENT     
       ON TABLE_NAME FOR EACH ROW trigger_STMT               

在上述语句中,参数trigger_name表示要创建的触发器名;

参数BEFORE和AFTER指定了触发器执行的时间,前者在触发器事件之前执行触发器语句,后者在触发器事件之后执行触发器语句;

参数trigger_EVENT表示触发事件,即触发器执行条件,包含DELETE、INSERT和UPDATE语句;参数TABLE_NAME表示触发事件的操作表名;参数FOR EACH ROW表示任何一条记录上的操作满足触发事件都会触发该触发器;

参数trigger_STMT表示激活触发器后被执行的语句。执行语句中如果要引用更新记录中的字段,对于INSERT语句,只有NEW是合法的,表示当前已插入的记录;对于DELETE语句,只有OLD才合法,表示当前删除的记录;而UPDATE语句可以和NEW(更新后)以及OLD(更新前)同时使用

**注意:**不能创建具有相同名字的触发器。另外,对于具有相同触发程序动作时间和事件的给定表,不能有两个触发器。因此,对于有经验的用户,在创建触发器之前,需要查看MySQL中是否已经存在该标识符的触发器和触发器的相关事件。

**【示例10-1】**执行SQL语句CREATE TRIGGER,在数据库school中存在两个表对象:学员表student和班级表 class,创建触发器实现向学员表中插入记录时,就会在插入之后更新班级表中的人数,当我们删除某条学员的记录时,就会在删除后更新班级表中的人数,具体步骤如下:

mysql>  use school;   #选择数据库school                                           
mysql>  CREATE TABLE class (                                                        
  `id` int NOT NULL AUTO_INCREMENT,                                              
  `name` varchar(128) DEFAULT NULL,                                               
  `teacher` varchar(64) DEFAULT NULL,  
  `count`  int DEFAULT 0,                                           
  UNIQUE KEY `id` (`id`)                                                              
);  #创建班级表 class                                                                 
mysql> insert into class values(101, '萌新一班', 'Martin', 0),(102, '萌新二班', 'Rock', 0),(103, '萌新三班', 'Janny', 0);  #创建成绩表 grade                                                 
mysql>  CREATE TABLE `student` (                                                  
  `id` int NOT NULL AUTO_INCREMENT UNIQUE,                                                            
  `name` varchar(64) DEFAULT NULL,                                                
  `class_id` int DEFAULT NULL,                                                      
  `sex` enum('F','M') DEFAULT NULL                                                  
); 
mysql> create trigger tri_insert_student after insert on student for each row update class set count=count+1 where class.id = NEW.class_id;   #创建触发器,新增学员班级人数增1
                                                                               
mysql> insert into student values(1,'小花',101,'M'),(2,'小红',102, 'F'),(3,'小军',102,'F'),(4,'小白',101,'F');  #插入多条记录   
mysql> select count from class  ;  #查询class 表人数  
mysql> create trigger tri_delete_student after delete on student for each row update class set count=count-1 where id = OLD.class_id; #创建触发器,删除学员班级人数减1

触发器包含多条执行语句

       CREATE   trigger trigger_name BEFORE|AFTER trigger_EVENT     
       ON TABLE_NAME FOR EACH ROW                                 
           BEGIN                                                        
            trigger_STMT                                                 
           END                                                           

在上述语句中,比“只有一条执行语句的触发器”语法多出来两个关键字BEGIN和END,在这两个关键字之间是所要执行的多个执行语句的内容,执行语句之间用分号隔开。

在MySQL中,一般情况下用“;”符号作为语句的结束符号,可是在创建触发器时,需要用到“;”符号作为执行语句的结束符号。为了解决该问题,可以使用关键字DELIMITER语句。例如,“DELIMITER ”可以将结束符号设置成“$$”。

mysql>  use school;   #选择数据库school         
mysql>  create table grade(id int UNIQUE AUTO_INCREMENT,  math tinyint unsigned, chinese tinyint unsigned, english tinyint unsigned);       #创建成绩表 grade   
mysql> insert into grade values(1, 80, 87, 91),(2, 72, 64, 89),(3, 54, 69, 87),(4, 78, 79, 89);  #插入多条记录                                      
mysql> DELIMITER $$                                                                                
mysql> create trigger tri_delete_student after delete on student for each row 
        BEGIN                                    
         Delete from grade where id = OLD.id;  #删除成绩表中的记录                                                        
         update class set count=count-1 where id = OLD.class_id; #更新班级表中的记录   
         END;                                    
         $$                                       
         DELIMITER ;                             

查看触发器

SHOW TRIGGERS语句查看触发器

那么如何查看MySQL软件中已经存在的触发器呢?在MySQL软件中查看已经存在的触发器,通过SQL语句SHOW TRIGGERS来实现,其语法形式如下,执行上面的SQL语句,执行结果如图9-10所示。

  SHOW TRIGGERS ;  

通过图9-10的执行结果可以发现,执行完“SHOW TRIGGERS”语句后会显示一个列表,在该列表中会显示出所有触发器的信息。其中,参数Trigger表示触发器的名称;参数Event表示触发器的激发事件;参数Table表示触发器对象触发事件所操作的表;参数Statement表示触发器激活时所执行的语句;参数Timing表示触发器所执行的时间。

在这里插入图片描述

查看系统表triggers实现查看触发器

在MySQL中,在系统数据库information_schema中存在一个存储所有触发器信息的系统表triggers,因此查询该表格的记录也可以实现查看触发器功能。系统表triggers的表结构

mysql>  use information_schema;   #选择数据库information_schema                  
mysql>  select * from triggers;                                                        
mysql>  select * from triggers where trigger_name=’tri_delete_student’; #查询系统表triggers中的触发器           

触发器的删除

在MySQL软件中,可以通过DROP TRIGGER语句或通过工具来删除触发器。

在MySQL中,删除触发器可以通过SQL语句DROP TRIGGER来实现,其语法形式如下:

    DROP TRIGGER trigger_name; 

在上述语句中,参数trigger_name表示所要删除的触发器名称。

存储过程和函数

存储过程和函数是在数据库中定义的一些SQL语句的集合,然后直接调用这些存储过程和函数来执行已经定义好的SQL语句。存储过程和函数可以避免开发人员重复编写相同的SQL语句。而且,存储过程和函数是在MySQL服务器中存储和执行的,可以减少客户器端和服务端的数据传输。

创建存储过程

创建存储过程和函数是指将经常使用的一组SQL语句组合在一起,并将这些SQL语句当作一个整体存储在MySQL服务器中。存储程序可以分为存储过程和函数。在MySQL中创建存储过程使用的语句CREATE PROCEDURE。其语法形式如下:

CREATE PROCEDURE procedure_name([proc_param[,]])    
         routine_body                                     

在上述语句中,参数procedure_name表示所要创建的存储过程名字,参数proc_param表示存储过程的参数,参数routine_body表示存储过程的SQL语句代码,可以用BEGIN…END来标志SQL语句的开始和结束。

提示: 在具体创建存储过程时,存储过程名不能与已经存在的存储过程名重名,实战中推荐存储过程名命名为procedure_xxx或者proc_xxx。

proc_param中每个参数的语法形式如下:

    [IN|OUT|INOUT] param_name type

在上述语句中,每个参数由三部分组成,分别为输入/输出类型、参数名和参数类型。其中,输入/输出类型有三种类型,分别为IN(表示输入类型)、OUT(表示输出类型)、INOUT(表示输入/输出类型)。param_name表示参数名;type表示参数类型,可以是MySQL软件所支持的任意一个数据类型。

mysql>  use school;   #选择数据库school                                             
mysql> DELIMITER $$                                                                                
mysql> create PROCEDURE  proc_delete_student (IN sid int )                                   
        BEGIN 
         declare cid  int ;   #定义变量cid                            
         Select class_id into cid from student where id = sid;    #通过查询语句设置变量                                                                       
         delete from grade where id = sid;  #删除成绩表中的记录  
         delete from student where id = sid;   #删除学生表中的记录                                                    
         update class set count=count-1 where id = cid; #更新班级表中的记录   
        END;                                                                        
        $$                                                                           
         DELIMITER ;                                                                 
mysql>  call proc_delete_student(2);    #调用存储过程                                                        

在存储过程中使用变量

在存储过程和函数中,可以定义和使用变量。用户可以使用关键字DECLARE来定义变量,然后为变量赋值。这些变量的作用范围是在BEGIN…END程序段中。

  1. 定义变量

在MySQL中,可以使用DECLARE关键字来定义变量。定义变量的基本语法如下:

    DECLARE var_name[,] type [DEFAULT value]   

其中,关键字DECLARE是用来声明变量的;参数var_name是变量的名称,可以同时定义多个变量;参数type用来指定变量的类型;DEFAULT value子句将变量默认值设置为value,没有使用DEFAULT子句时,默认值为NULL。

定义变量cid,数据类型为INT型,默认值为10,代码如下:

  DECLARE cid INT DEFAULT 10;    
  1. 为变量赋值

在MySQL中可以使用关键字SET来为变量赋值,SET语句的基本语法如下:

SET var_name=expr[,var_name=expr]

其中,关键字SET用来为变量赋值;参数var_name是变量的名称;参数expr是赋值表达式。一个SET语句可以同时为多个变量赋值,各个变量的赋值语句之间用逗号隔开。

例如,将变量tmp_id赋值为88,代码如下:

SET tmp_id = 88;

在MySQL中,还可以使用SELECT…INTO语句为变量赋值。其基本语法如下:

     SELECT col_name[,] INTO var_name[,]     
     FROM table_name WHERE condition         

其中,参数col_name表示查询的字段名称;参数var_name是变量的名称;参数table_name指表的名称;参数condition指查询条件。

从表student中查询id为4的记录,将该记录的id值赋给变量tmp_id,代码如下:

mysql>  use school;   #选择数据库school                                             
mysql>  drop  PROCEDURE if exists query_student_class_info;                                                                             
mysql> DELIMITER $$                                                                                
mysql> create procedure  query_student_class_info (IN sid int, OUT cname varchar(128), OUT ccount  int)                                   
        BEGIN   
            declare tmp_name varchar(128);
            declare tmp_count int;
            declare tmp_cid  int;
            select class_id into tmp_cid from student where id = sid;         
            select name, count into tmp_name, tmp_count from class where id = tmp_cid;
            set cname = tmp_name, ccount = tmp_count;
         END;    
         $$                                                                           
         DELIMITER ;                                                                 
mysql>  call query_student_class_info(4, @name, @count);    #调用存储过程  
mysql>  select @name, @count;                                                         

光标的使用

查询语句可能查询出多条记录,在存储过程和函数中使用光标来逐条读取查询结果集中的记录。有些书上将光标称为游标。光标的使用包括声明光标、打开光标、使用光标和关闭光标。光标必须声明在处理程序之前,并且声明在变量和条件之后

声明光标

在MySQL中,可以使用DECLARE关键字来声明光标,其基本语法如下:

    DECLARE cursor_name CURSOR         
    FOR select_statement;                  

其中,参数cursor_name表示光标的名称;参数select_statement表示SELECT语句的内容。

**【示例11-2】**下面声明一个名为cur_student的光标,代码如下:

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure  query_student (IN sid int, OUT cname varchar(128), OUT class_id  int )                                      
        BEGIN                                                  
            DECLARE cur_student CURSOR                     
                FOR SELECT name, class_id FROM  student;    
         END;                                                  
         $$                                                                           
         DELIMITER ;                                                                                        

在上面的示例中,光标的名称为cur_student;SELECT语句部分是从表student中查询出字段name和class_id的值。

打开光标

在MySQL中,使用关键字OPEN来打开光标,其基本语法如下:

OPEN cursor_name;

其中,参数cursor_name表示光标的名称。

下面代码打开一个名为cur_student的光标,代码如下:

OPEN cur_student;

使用光标

在MySQL中,使用关键字FETCH来使用光标,其基本语法如下:

FETCH cursor_name
INTO var_name[,var_name…];

其中,参数cursor_name表示光标的名称;参数var_name表示将光标中的SELECT语句查询出来的信息存入该参数中。var_name必须在声明光标之前就定义好。

**【示例11-3】**下面声明一个名为cur_student的光标,代码如下:

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure query_student (IN sid int, OUT cname varchar(128), OUT cid int)                                                                                    
        BEGIN                                                                             
            declare tmp_name varchar(128);    #必须定义在声明光标之前                                                                                             
            declare tmp_cid  int;                                                           
            declare  done int default 0;                                                                                                                                                
            declare cur_student CURSOR FOR SELECT name, class_id FROM  student where id = sid;                                                                                      
            declare continue handler for not found set done = 1; #将结束标志绑定到游标上                                                                             
            open  cur_student;                                                             
            select done;                                                                    
            fetch cur_student into tmp_name, tmp_cid;                                        
            select done;                                                                                      
            select tmp_name, tmp_cid;         #打印从光标中获取到的值                                                
            close cur_student;                                                              
            set cname = tmp_name, cid = tmp_cid;                                                                                                                      
         END;                                                                              
mysql>  $$                                                                                 
mysql>  DELIMITER ; 

关闭光标

在MySQL中,使用关键字CLOSE来关闭光标,其基本语法如下:

CLOSE cursor_name;

其中,参数cursor_name表示光标的名称。

例如: 关闭一个名为cur_student的光标,代码如下:

CLOSE cur_student;

在上面的示例中,关闭了这个名称为cur_student的光标。关闭了之后就不能使用FETCH来使用光标了。提示

如果存储过程或函数中执行了SELECT语句,并且SELECT语句会查询出多条记录,这种情况最好使用光标来逐条读取记录,光标必须在处理程序之前且在变量和条件之后声明,而且光标使用完毕后一定要关闭。

流程控制的使用

在存储过程和函数中,可以使用流程控制来控制语句的执行。在MySQL中,可以使用IF语句、CASE语句、LOOP语句、LEAVE语句、ITERATE语句、REPEAT语句和WHILE语句来进行流程控制

if语句

IF语句用来进行条件判断。根据条件执行不同的语句。其语法的基本形式如下:

IF search_condition THEN statement_list       
[ELSEIF search_condition THEN statement_list] ...
[ELSE statement_list]                           
END  IF                                      

参数search_condition表示条件判断语句;参数statement_list表示不同条件的执行语句。

**[示例11-4]**下面是一个IF语句的示例,代码如下:

 IF age>20 THEN SET @count1=@count1+1;  

  ELSEIF age=20 THEN @count2=@count2+1;

  ELSE @count3=@count3+1;        

 END IF;                   

该示例根据age与20的大小关系来执行不同的SET语句。如果age值大于20,将count1的值加1;如果age值等于20,就将count2的值加1;其他情况将count3的值加1。IF语句都需要使用END IF来结束。

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure proc_test_if (IN input int, OUT output int)
        begin
            if input>20 then set input=input+1;
            elseif input=20 then  set input=input+2;
            else  set input = input+3;
            end if;

            set output = input;
        end; 
mysql>  $$                                                                                 
mysql>  DELIMITER ;                                                                                         

CASE语句

CASE语句可实现比IF语句更复杂的条件判断,其语法的基本形式如下:

CASE case_value                                  
WHEN when_value THEN statement_list            
[ WHEN when_value THEN statement_list ]          
[ELSE statement_list]                               
END CASE                                         

其中,参数case_value表示条件判断的变量;参数when_value表示变量的取值;参数statement_list表示不同when_value值的执行语句

**[示例11-5]**下面是一个CASE语句的示例。代码如下:

CASE level                                    
      WHEN 20 THEN SET attack = attack + 5;  
      WHEN 30 THEN SET attack = attack + 10; 
      WHEN 40 THEN SET attack = attack + 15; 
      ELSE SET attack = attack + 1; 
END CASE            

当级别level值为20时,attack值加5;当级别level值为30时,attack值加10;当级别level值为40时,attack值加15;否则,attack + 1。CASE语句使用END CASE结束。

LOOP 语句

LOOP语句可以使某些特定的语句重复执行,实现一个简单的循环。LOOP语句本身没有停止循环,只有遇到LEAVE语句等才能停止循环。LOOP语句的语句形式如下:

   [begin_label:] LOOP             
   statement_list                  
   END LOOP [end_label]          

其中,参数begin_label和参数end_label分别表示循环开始和结束的标志,这两个标志必须相同,而且都可以省略;参数statement_list表示需要循坏执行的语句。

示例11-6】下面是一个LOOP语句的示例,代码如下:

add_num:LOOP                 
     SET @count = @count + 1; 
END LOOP add_num;            

该示例循环执行count加1的操作。因为没有跳出循环的语句,这个循环成了一个死循环。LOOP循环都以END LOOP结束。

LEAVE语句

LEAVE语句主要用于跳出循环控制,其语法形式如下:

LEAVE label                  

其中,参数label表示循环的标志。

示例11-7】下面是一个LEAVE语句的示例。代码如下:

add_num: LOOP             
SET @count=@count + 1;
Select @count;
IF @count = 100 THEN 
    LEAVE add_num;     
END IF;
END LOOP add_num;         

该示例循环执行count值加1的操作。当count的值等于100时,LEAVE语句跳出循环。

ITERATE 语句

ITERATE语句也是用来跳出循环的语句,但是ITERATE语句是跳出本次循环,然后直接进入下一次循环,ITERATE语句的语法形式如下:

  ITERATE label  

其中,参数label表示循环的标志。

示例11-8】下面是一个ITERATE语句的示例。代码如下:

add_num1:LOOP              
    Set @count = @count +1 
    IF @count=100 THEN     
        LEAVE add_num1       
    ELSE IF MOD(@count, 3) = 0 then
        ITERATE add_num1;   
     Select * from student;    
END LOOP add_num1;       

该示例循环执行count加1的操作,count的值为100时结束循环。如果count的值能够整除3,就跳出本次循环,不再执行下面的SELECT语句。

注意: LEAVE语句和ITERATE语句都用来跳出循环语句,但是两者的功能是不一样的。LEAVE语句是跳出整个循环,然后执行循环后面的程序,和C++ break 相似。ITERATE语句是跳出本次循环,然后进入下一次循环,和C++ continue 相似。使用这两个语句时一定要区分清楚。

REPEAT 语句

REPEAT语句是有条件控制的循环语句。当满足特定条件时,就会跳出循环语句。REPEAT语句的基本语法形式如下:

[begin_label:] REPEAT        
        statement_list;       
     UNTIL search_condition 
END REPEAT [end_label]     

其中,参数statement_list表示循环的执行语句;参数search_condition表示结束循环的条件,满足该条件时循环结束。

示例11-9】下面是一个REPEAT语句的示例。代码如下:

REPEAT                        
     SET @count=@count+1;   
     UNTIL @count=100        
END REPEAT;                   

该示例循环执行count加1的操作,count值为100时结束循环。REPEAT循环都用END REPEAT结束。

WHILE 语句

WHILE语句也是有条件控制的循环语句,但WHILE语句和REPEAT语句是不一样的。WHILE语句是当满足条件时执行循环内的语句。WHILE语句的基本语法形式如下:

[begin_label:] WHILE search_condition DO   
        Statement_list                      
END WHILE [end_label]                     

其中,参数statement_condition表示循环执行的条件,满足该条件时循环执行;参数statement_list表示循环的执行语句。

示例11-10】下面是一个WHILE语句的示例。代码如下:

WHILE @count<100 DO       
    SET @count = @count + 1;
END WHILE;                  

流程控制综合运用

**【示例11-11】**循环访问光标操作,访问光标中的所有记录,代码如下:

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure query_all_students (IN sid int, OUT cname varchar(128), OUT cid int)                                                                                    
        BEGIN                                                                             
            declare tmp_name varchar(128);    #必须定义在声明光标之前                                                                                             
            declare tmp_cid  int;                                                           
            declare  done int default 0;                                                                                                                                                
            declare cur_student CURSOR FOR SELECT name, class_id FROM  student ;                                                                                      
            declare continue handler for not found set done = 1; #将结束标志绑定到游标上                                                                             
            open  cur_student;                                                             
            read_loop:LOOP      #循环读取                                                                   
                fetch cur_student into tmp_name, tmp_cid;                                                                                                                              
                IF done=1 then                                                              
                    Leave read_loop;                                                                  
                END IF;                                                                     
                select tmp_name, tmp_cid;         #打印从光标中获取到的值                                                                                       
            END LOOP read_loop;                                                                                
            close cur_student;                                                              
            set cname = tmp_name, cid = tmp_cid;                                                                                                                      
         END;                                                                              
mysql>  $$                                                                                 
mysql>  DELIMITER ;                                  

【示例11-12】在学生表中插入一条记录,并返回记录的自增长id

mysql>  use school;   #选择数据库school                                                                                                                         
mysql> DELIMITER $$                                                                                
mysql> create procedure fetch_insert_student_id (IN p_name varchar(128), in p_class_id int, IN p_sex char(1), OUT rid int)                                                                                    
        BEGIN                                                                             
            Insert into student (name, class_id, sex) values(p_name, p_class_id, p_sex);                                                                                
            select last_insert_id() as rid;                                                                                                                     
         END;                                                                              
mysql>  $$                                                                                 
mysql>  DELIMITER ;                                                                                         

查看存储过程

存储过程创建以后,用户可以通过SHOW STATUS语句来查看存储过程的状态,也可以通过SHOW CREATE语句来查看存储过程的定义。用户也可以通过查询information_schema数据库下的Routines表来查看存储过程的信息。本节将详细讲解查看存储过程的状态与定义的方法。

SHOW STATUS语句查看存储过程

在MySQL中,可以通过SHOW STATUS语句。其基本语法形式如下:

SHOW PROCEDURE STATUS  [ like ‘pattern’ ] ;  

其中,参数PROCEDURE表示查询存储过程;参数LIKE 'pattern’用来匹配存储过程的名称。

图11-13的执行结果显示了存储过程的创建时间、修改时间和字符集等信息。

在这里插入图片描述

使用SHOW CREATE语句查看存储过程的定义

在MySQL中,可以通过SHOW CREATE语句查看存储过程的状态,语法形式如下:

SHOW CREATE PROCEDURE proc_name      

其中,参数PROCEDURE表示查询存储过程;参数proc_name表示存储过程的名称。

—【示例11-14】查询名为proc_delete_student的存储过程的状态,代码如下,执行结果如下图所示。

SHOW CREATE PROCEDURE proc_delete_student \G

在这里插入图片描述

从information_schema.Routine表中查看存储过程的信息

存储过程和函数的信息存储在information_schema数据库下的Routines表中。可以通过查询该表的记录来查询存储过程和函数的信息。其基本语法形式如下:

SELECT * FROM information_schema.Routines
               Where ROUTINE_NAME = ‘proc_name’;

其中,字段ROUTINE_NAME是Routines 存储存储过程和函数的列名称;参数proc_name表示存储过程或函数的名称。

—【示例11-15】下面从Routines表中查询名为proc_delete_student的存储过程信息,具体SQL代码如下,执行结果如下图所示。

select routine_definition from information_schema.Routines where routine_name='proc_delete_student';  

在这里插入图片描述

存储过程的删除

在MySQL中删除存储过程通过SQL语句DROP完成:

    DROP PROCEDURE proc_name;            

在上述语句中,关键字DROP PROCEDURE用来表示实现删除存储过程,参数proc_name表示所要删除的存储过程名称。

—【示例11-16】执行SQL语句DROP PROCEDURE,删除存储过程对象proc_delete_student,具体步骤如下:

    DROP PROCEDURE proc_delete_student;

数据库的存储引擎

MySQL中存在多种存储引擎的概念。简而言之,存储引擎就是指表的类型。在具体开发时,为了提高MySQL数据库管理系统的使用效率和灵活性,可以根据实际需要来选择存储引擎。因为存储引擎指定了表的类型,即如何存储和索引数据、是否支持事务等,同时存储引擎也决定了表在计算机中的存储方式。

MySQL支持的存储引擎

用户在选择存储引擎之前,首先需要确定数据库管理系统支持哪些存储引擎。在MySQL数据库管理系统,通过SHOW ENGINES来查看支持的存储引擎,语法如下:

SHOW ENGINES;           

在MySQL中执行SHOW ENGINES的结果如下图12-1所示。

在这里插入图片描述

在创建表时,若没有指定存储引擎,表的存储引擎将为默认的存储引擎。如果需要操作默认存储引擎,首先需要查看默认存储引擎。可以使用下面的SQL语句来查询默认存储引擎,执行结果如图12-2所示。

SHOW VARIABLES LIKE 'default_storage_engine';    

在这里插入图片描述

接下来简单介绍开发中常用的几种常见的存储引擎。

InnoDB存储引擎

InnoDB是MySQL数据库的一种存储引擎。InnoDB给MySQL的表提供了事务、回滚、崩溃修复能力和多版本并发控制的事务安全。MySQL从3.23.34a开始就包含InnoDB存储引擎。InnoDB是MySQL第一个提供外键约束的表引擎,而且InnoDB对事务处理的能力也是MySQL对其他存储引擎所无法与之比拟的。

MySQL 5.6版本之后,除系统数据库之外,默认的存储引擎由MyISAM改为InnoDB,MySQL 8.0版本在原先的基础上将系统数据库的存储引擎也改为了InnoDB。

InnoDB存储引擎中支持自动增长列AUTO_INCREMENT。自动增长列的值不能为空,且值必须唯一。MySQL中规定自增列必须为主键。在插入值时,如果自动增长列不输入值,那么插入的值为自动增长后的值;如果输入的值为0或空(NULL),那么插入的值也为自动增长后的值;如果插入某个确定的值,且该值在前面没有出现过,那么可以直接插入。

InnoDB存储引擎中支持外键(FOREIGN KEY)。外键所在的表为子表,外键所依赖的表为父表。父表中被子表外键关联的字段必须为主键。当删除、更新父表的某条信息时,子表也必须有相应的改变。

InnoDB存储引擎的优势在于提供了良好的事务管理、崩溃修复能力和并发控制;缺点是其读写效率稍差,占用的数据空间相对比较大。

MyISAM存储引擎

MyISAM存储引擎是MySQL中常见的存储引擎,曾是MySQL的默认存储引擎。MyISAM存储引擎是基于ISAM存储引擎发展起来的。MyISAM增加了很多有用的扩展。

MyISAM存储引擎的表存储成3个文件。文件的名字与表名相同,或站名包括frm、MYD和MYI。其中,frm为扩展名的文件存储表的结构;MYD为扩展名的文件存储数据,是MYData的缩写;MYI为扩展名的文件存储索引,是MYIndex的缩写。

基于MyISAM存储引擎的表支持3种存储格式,包括静态型、动态型和压缩型。其中,静态型为MyISAM存储引擎的默认存储格式,其字段是固定长度的;动态型包含变长字段,记录的长度不是固定的;压缩型需要使用myiampack工具创建,占用的磁盘空间较小。

MyISAM存储引擎的优势在于占用空间小,处理速度快;缺点是不支持事务的完整性和并发性。

MEMORY 存储引擎

MEMORY存储引擎是MySQL中一类特殊存储引擎。其使用存储在内存中的内容来创建表,而且所有数据也放在内存中。这些特性都与InnoDB存储引擎、MyISAM存储引擎不同。

每个基于MEMORY存储引擎的表实际对应一个磁盘文件,该文件的文件名与表名相同,类型为frm类型,该文件中只存储表的结构,而其数据文件都是存储在内存中的。这样有利于数据的快速处理,提供整个表的处理效率。值得注意的是,服务器需要有足够的内存来维持MEMORY存储引擎的表的使用。如果不需要使用了,可以释放这些内存,甚至可以删除不需要的表。

MEMORY存储引擎默认使用哈希(HASH)索引。其速度要比使用B型树(BTREE)索引快。如果读者希望使用B型树索引,可以在创建索引时选择使用。

MEMORY表的大小是受到限制的。表的大小主要取决于两个参数,分别是max_rows和max_heap_table_size。其中,max_rows可以在创建表时指定;max_heap_table_size的大小默认为16MB,可以按需要进行扩大。因此,其存在于内存中的特性,这类表的处理速度非常快。但是,其数据易丢失,生命周期短。基于这个缺陷,选择MEMORY存储引擎时需要特别小心。

选择存储引擎

在具体使用MySQL数据库管理系统时,选择一个合适的存储引擎是非常复杂的问题。因为每种存储引擎都有自己的特性、优势和应用场合,所以不能随便选择存储引擎。为了能够正确地选择存储引擎,必须掌握各种存储引擎的特性。

下面从存储引擎的事务安全、存储限制、空间使用、内存使用、插入数据的速度和对外键的支持等角度来比较InnoDB、MyISAM和MEMORY,如表12-3所示。

在这里插入图片描述

C/C++访问MySQL数据库

VS2019配置

第一步:打开mysql的安装目录,默认安装目录如下:C:\Program Files\MySQL\MySQL Server 8.0,确认 lib 目录和include 目录是否存在。

第二步:打开VS2019,新建一个空工程,控制台应用程序即可,注意:解决方案平台选择 X64

在这里插入图片描述

第三步:右击工程名,打开属性页

在这里插入图片描述

第四步:打开VC++目录,在包含目录中,将mysql安装文件中的include文件的路径添加到这里

在这里插入图片描述

**第五步:**打开VC++目录,在库目录中将mysql文件中的lib文件路径添加进来

在这里插入图片描述

第六步:在属性页的链接器中,点击“输入”,将mysql安装文件夹中lib目录下的libmysql.lib文件加到“附加依赖项”中,注意,这里直接把libmysql.lib这个依赖名加进去即可,不要加路径。

在这里插入图片描述

第七步:把mysql安装目录里的lib\libmysql.dll复制到c:\windows\system32下

第八步:编译如下代码,启动mysql 80, 将代码中连接数据库的用户名和密码改成自己的设定,顺利获取到student 表中的结果即表示连接成功!

#include <stdio.h>
#include <mysql.h> // mysql文件
int main(void)
{
	MYSQL mysql;    //数据库句柄
	MYSQL_RES* res; //查询结果集
	MYSQL_ROW row;  //记录结构体

	//初始化数据库
	mysql_init(&mysql);

	//设置字符编码
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");

	//连接数据库
	if (mysql_real_connect(&mysql, "127.0.0.1", "root", "123456qweQWE", "school", 3306, NULL, 0) == NULL) {
		printf("错误原因: %s\n", mysql_error(&mysql));
		printf("连接失败!\n");
		exit(-1);
	}

	//查询数据
	int ret = mysql_query(&mysql, "select * from student;");
	printf("ret: %d\n", ret);

	//获取结果集
	res = mysql_store_result(&mysql);

	//给ROW赋值,判断ROW是否为空,不为空就打印数据。
	while (row = mysql_fetch_row(res))
	{
		printf("%s  ", row[0]);  //打印ID
		printf("%s  ", row[1]);  //打印姓名
		printf("%s  ", row[2]);  //打印班级
		printf("%s  \n", row[3]);//打印性别
	}
	//释放结果集
	mysql_free_result(res);

	//关闭数据库
	mysql_close(&mysql);

	system("pause");
	return 0;
}

实战项目

数据库表设计

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

mysql>  create database box_man;   #创建数据库box_man                                                                                                                        
mysql>                                                                                     
mysql>  create table users(          #创建用户表                                                              
             id int(11) NOT NULL PRIMARY KEY AUTO_INCREMENT,                              
             username varchar(64) NOT NULL UNIQUE,                                                
             password varchar(32) NOT NULL ,                                                  
             level_id int  default 1                                                              
         );                                                                                  
mysql>  create table levels(         #创建关卡表                                                         
             id int NOT NULL PRIMARY KEY default 1,                                                 
             name varchar(64) NOT NULL UNIQUE,                                                    
             map_row int  NOT NULL,                                                               
             map_column   int NOT NULL,                                                           
             map_data     varchar(4096) NOT NULL,                                        
             next_level_id  int default 0                                                     
         );                                                                                  
mysql>   insert into users values(1000, 'martin', md5('123456qweQWE'), 1);                    
mysql>   insert into levels values(1, '牛刀小试', 9, 12,                                            
 '0,0,0,0,0,0,0,0,0,0,0,0|0,1,0,1,1,1,1,1,1,1,0,0|0,1,4,1,0,2,1,0,2,1,0,0|0,1,0,1,0,1,0,0,1,1,1,0|0,1,0,2,    
0,1,1,4,1,1,1,0|0,1,1,1,0,3,1,1,1,4,1,0|0,1,2,1,1,4,1,1,1,1,1,0|0,1,0,0,1,0,1,1,0,0,1,0|0,0,0,0,0,0,0,0,0,0,  
0,0',0);                                                                                      

推箱子游戏 - 代码优化

登录验证

database.cpp

#include "database.h"
#include <mysql.h>
#include <stdio.h>

#define DB_NAME  "box_man"
#define DB_HOST  "127.0.0.1"
#define DB_PORT  3306
#define DB_USER  "root"
#define DB_USER_PASSWD  "123456qweQWE"

static bool connect_db(MYSQL& mysql);

/***************************************************
 *功能:通过用户名和密码从数据库获取用户信息
 *输入:
 *      user - 用户信息结构体
 *
 *返回值:
 *       获取成功返回true, 失败false
 ***************************************************/
bool fetch_user_info(userinfo& user) {
    MYSQL mysql;
    MYSQL_RES* res; //查询结果集
    MYSQL_ROW row;  //记录结构体
    char sql[256];
    bool ret = false;


    //1.连接到数据库
    if (connect_db(mysql) == false) {
        return false;
    }

    //2.根据用户名和密码获取用户信息(id, level_id)
    snprintf(sql, 256, "select id, level_id from users where username='%s' and password=md5('%s');", user.username.c_str(), user.passwd.c_str());
    ret = mysql_query(&mysql, sql); //成功返回0

    if (ret) {
        printf("数据库查询出错,%s 错误原因: %s\n", sql, mysql_error(&mysql));
        mysql_close(&mysql);
        return false;
    }

    //3.获取结果
    res = mysql_store_result(&mysql);
    row = mysql_fetch_row(res);

    if (row == NULL) {//没有查找到记录
        mysql_free_result(res);
        mysql_close(&mysql);
        return false;
    }

    user.id = atoi(row[0]);
    user.level_id = atoi(row[1]);
    printf("userid: %d  level_id: %d\n", user.id, user.level_id);  //打印ID
 
    //4.返回结果

    //释放结果集
    mysql_free_result(res);

    //关闭数据库
    mysql_close(&mysql);

    return true;
}


bool connect_db(MYSQL& mysql) {

    //1.初始化数据库句柄
    mysql_init(&mysql);

    //2.设置字符编码
    mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "gbk");

    //3.连接数据库
    if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSWD, DB_NAME, DB_PORT, NULL, 0) == NULL) {
        printf("数据库连接出错, 错误原因: %s\n", mysql_error(&mysql));
        return false;
    }

    return true;
}

database.h

#pragma once

#include <string>

using namespace std;


//用户信息
typedef struct _userinfo{
	int id;            //用户id
	string username;   //用户名
	string passwd;     //密码
	int level_id;      //关卡id
}userinfo;



bool fetch_user_info(userinfo &user);

boxman.cpp

bool login(userinfo& user) {
	int times = 0;
	bool ret = false;


	do{
		cout << "请输入用户名: ";
		cin >> user.username;

		cout << "请输入密码: ";
		cin >> user.passwd;

		//返回 bool ,成功返回true ,失败返回false .
		ret = fetch_user_info(user);
		times++;

		if (times >= MAX_RETRY_TIMES) {
			break;
		}
		if (ret == false) {
			cout << "登陆失败,请重新输入!" << endl;
		}
	} while (!ret);

	return ret;
}
获取关卡
//database.h
typedef struct _levelinfo {
	int id;            //关卡的id
	string name;       //关卡的名字
	int map_row;       //地图总行数
	int map_column;    //地图总列数
	string  map_data;  //二维地图数据
	int next_level;    //下一关卡的id 
}levelinfo;

bool fetch_level_info(levelinfo &level, int level_id);

//database.cpp
/***************************************************
 *功能:根据关卡id 获取完整的关卡信息(如: 地图,下一关等)
 *输入:
 *      level - 保存关卡信息的结构体变量
 *      level_id - 要获取详细关卡信息的关卡id
 *返回值:
 *       获取成功返回true, 失败false
 ***************************************************/
bool fetch_level_info(levelinfo& level, int level_id) {
    MYSQL mysql;
    MYSQL_RES* res; //查询结果集
    MYSQL_ROW row;  //记录结构体
    char sql[256];
    bool ret = false;


    //1.连接到数据库
    if (connect_db(mysql) == false) {
        return false;
    }

    //2.根据关卡id查询数据库获取关卡地图信息
    snprintf(sql, 256, "select  name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
    ret = mysql_query(&mysql, sql); //成功返回0

    if (ret) {
        printf("数据库查询出错,%s 错误原因: %s\n", sql, mysql_error(&mysql));
        mysql_close(&mysql);
        return false;
    }

    //3.获取结果
    res = mysql_store_result(&mysql);
    row = mysql_fetch_row(res);

    if (row == NULL) {//没有查找到记录
        mysql_free_result(res);
        mysql_close(&mysql);
        return false;
    }

    level.id = level_id;
    level.name = row[0];
    level.map_row = atoi(row[1]);
    level.map_column = atoi(row[2]);
    level.map_data = row[3];
    level.next_level = atoi(row[5]);

    if(debug) printf("level id: %d  name: %s map row: %d  map column: %d map data: %s next level: %d\n", level.id, level.name.c_str(), level.map_row, level.map_column, level.map_data.c_str(), level.next_level);
    

    //释放结果集
    mysql_free_result(res);

    //关闭数据库
    mysql_close(&mysql);

    return  true;
}
地图适配
//database.h
bool transform_map_db2array(levelinfo &level, int map[LINE][COLUMN]);

//database.cpp
bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN]) {
    if (level.map_row > LINE || level.map_column > COLUMN) {
        printf("地图超大,请重新设置!\n");
        return false;
    }

    if (level.map_data.length() < 1) {
        printf("地图数据有误,请重新设置!\n");
        return false;
    }

    int start = 0, end = 0;
    int row = 0, column = 0;

    do {
        end = level.map_data.find('|', start);

        if (end < 0) {
            end = level.map_data.length();
        }

        if (start >= end) break;

        string line = level.map_data.substr(start, end - start);
        printf("get line: %s\n", line.c_str());

        //对行地图数据进行解析
        char *next_token = NULL;
        char* item = strtok_s((char*)line.c_str(), ",", &next_token);

        column = 0;

        while (item && column < level.map_column) {
            printf("%s ", item);
            map[row][column] = atoi(item);
            column++;

            item = strtok_s(NULL, ",", &next_token);
        }

        if (column < level.map_column) {
            printf("地图数据解析出错,终止!\n");
            return false;
        }

        printf("\n");
        row++;

        if (row >= level.map_row) {
            break;
        }

        start = end + 1;

    } while (1 == 1);

    if (row < level.map_row) {
        printf("地图行数少于设定, %d(need: %d),终止!\n", row, level.map_row);
        return false;
    }

    return true;
}

//boxman.cpp

//把数据库中的地图数据转换到map 中
	ret = transform_map_db2array(level, map);

下一关跳转
//database.h
bool update_user_level(userinfo& user, int next_level_id);

//database.cpp
bool update_user_level(userinfo& user, int next_level_id) {
    MYSQL mysql;
    MYSQL_RES* res; //查询结果集
    MYSQL_ROW row;  //记录结构体
    char sql[256];
    bool ret = false;


    //1.连接到数据库
    if (connect_db(mysql) == false) {
        return false;
    }

    //2.根据用户id 更新下一关的level_id
    snprintf(sql, 256, "update users set level_id = %d where id=%d;", next_level_id, user.id);

    ret = mysql_query(&mysql, sql);

    if (ret) {
        printf("数据库更新出错,%s 错误原因: %s\n", sql, mysql_error(&mysql));
        mysql_close(&mysql);
        return false;
    }

    //关闭数据库
    mysql_close(&mysql);

    return true;
}

//boxman.cpp

//...............前面省略N行代码....................

void gameNextScene(IMAGE* bg) {
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT };
	settextstyle(20, 0, _T("宋体"));
	drawtext(_T("恭喜您~ \n此关挑战成功,任意键跳转到下一关!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	::system("pause");
	cleardevice();
}

//...............中间省略N行代码....................

if (isGameOver()) {

	if (level.next_level < 1) {
		gameOverScene(&bg_img);
		quit = true;
		break;
	}

	gameNextScene(&bg_img);

	//更新用户下一关的关卡信息
	if (update_user_level(user, level.next_level)) {
		user.level_id = level.next_level;
	}
					
	break;
	//quit = true;
}
完整代码

boxman.cpp

#include <graphics.h>
#include <conio.h>
#include <iostream>
#include <string>
#include "database.h"

using namespace std;

#define SCREEN_WIDTH	800
#define SCREEN_HEIGHT	650
#define PROPS_RATIO		50
#define MAX_RETRY_TIMES	4


#define START_X	100
#define START_Y	100

#define KEY_UP		'w'
#define KEY_DOWN	's'
#define KEY_LEFT	'a'
#define KEY_RIGHT	'd'
#define KEY_QUIT	'q'
#define isValid(pos) pos.x>=0 && pos.x<LINE && pos.y>=0 &&pos.y <COLUMN 


/*int map[9][12] = {
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
	{0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0},
	{0, 1, 4, 1, 0, 2, 1, 0, 2, 1, 0, 0},
	{0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0},
	{0, 1, 0, 2, 0, 1, 1, 4, 1, 1, 1, 0},
	{0, 1, 1, 1, 0, 3, 1, 1, 1, 4, 1, 0},
	{0, 1, 2, 1, 1, 4, 1, 1, 1, 1, 1, 0},
	{0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0},
	{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
};*/

int map[LINE][COLUMN] = { 0 };

enum _PROPS
{
	WALL,
	FLOOR,
	BOX_DES,
	MAN,
	BOX,
	HIT,
	ALL
};

IMAGE props_img[ALL];

enum _DIRECTION
{
	UP,
	DOWN,
	LEFT,
	RIGHT
};

struct _POS
{
	int x;
	int y;
};

typedef enum _DIRECTION DIRECTION;
typedef enum _PROPS PROPS;
typedef struct _POS POS;
POS man;
bool step_on_DES = false;
bool end_flag = false;

void printWinScreen(IMAGE * bg)
{
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };
	settextstyle(20, 0, _T("Arial"));
	drawtext(_T("You win!!! Press any key to move to the next stage!!!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	system("pause");
	cleardevice();

}

void printOverScreen(IMAGE* bg)
{
	putimage(0, 0, bg);
	settextcolor(WHITE);
	RECT rec = { 0,0,SCREEN_WIDTH,SCREEN_HEIGHT };
	settextstyle(20, 0, _T("Arial"));
	drawtext(_T("You win!!!You finish all the stage for the game!!!"), &rec, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	system("pause");
}


bool winDefine()
{
	for(int i=0; i<LINE; i++)
		for (int j = 0; j < COLUMN; j++)
		{
			if (map[i][j] == BOX_DES)
			{
				return false;
			}
		}
	return true;
}

void update_map_position(POS position, PROPS props)
{
	map[position.x][position.y] = props;
	putimage(START_X + (position.y * PROPS_RATIO), START_Y + (position.x * PROPS_RATIO), &props_img[map[position.x][position.y]]);

}

void map_update(DIRECTION direction)
{

	POS next_pos, next_next_pos;
	next_pos = man;
	next_next_pos = man;
	if (direction == UP)
	{
		next_pos.x = man.x - 1;
		next_next_pos.x = man.x - 2;
	}
	else if (direction == DOWN)
	{
		next_pos.x = man.x + 1;
		next_next_pos.x = man.x + 2;
	}
	else if (direction == LEFT)
	{
		next_pos.y = man.y - 1;
		next_next_pos.y = man.y - 2;
	}
	else if (direction == RIGHT)
	{
		next_pos.y = man.y + 1;
		next_next_pos.y = man.y + 2;
	}

	if (map[next_pos.x][next_pos.y] == FLOOR || map[next_pos.x][next_pos.y] == BOX_DES)
	{
		if (isValid(next_pos) && map[next_pos.x][next_pos.y] == FLOOR)
		{
			update_map_position(next_pos, MAN);
			if (step_on_DES == false)
			{
				update_map_position(man, FLOOR);
			}
			else
			{
				update_map_position(man, BOX_DES);
				step_on_DES = false;
			}

			man = next_pos;
		}
		else if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX_DES)
		{
			update_map_position(next_pos, MAN);
			update_map_position(man, FLOOR);
			step_on_DES = true;
			man = next_pos;
		}
	}

	if (isValid(next_pos) && map[next_pos.x][next_pos.y] == BOX)
	{
		if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == FLOOR)
		{
			update_map_position(next_next_pos, BOX);
			update_map_position(next_pos, MAN);
			if (step_on_DES == false)
			{
				update_map_position(man, FLOOR);
			}
			else
			{
				update_map_position(man, BOX_DES);
				step_on_DES = false;

			}
			man = next_pos;
		}
		else if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == BOX_DES)
		{
			update_map_position(next_next_pos, HIT);
			update_map_position(next_pos, MAN);
			update_map_position(man, FLOOR);
			man = next_pos;
		}

	}
	if (isValid(next_pos) && map[next_pos.x][next_pos.y] == HIT)
	{
		if (isValid(next_next_pos) && map[next_next_pos.x][next_next_pos.y] == FLOOR)
		{
			update_map_position(next_next_pos, BOX);
			update_map_position(next_pos, MAN);
			step_on_DES = true;
			update_map_position(man, FLOOR);
			man = next_pos;
		}

	}

}

bool login(userinfo& user)
{
	int times = 0;
	bool ret = false;
	do {
		cout << "Please enter the username:" << endl;
		cin >> user.username;
		cout << "Please enter the password: " << endl;
		cin >> user.password;
		ret = fetch_user_info(user);
		times++;
		//Return as bool type to determine whether can connect to the userdatabase.
		if (times >= MAX_RETRY_TIMES)
		{
			break;
		}
		if  (ret == false)
		{
			cout << "Failed to login, please retry again." << endl;

		}
	} while (!ret);
	return ret;
}

void init_game_graphic(IMAGE& bg_img)
{
	
	initgraph(SCREEN_WIDTH, SCREEN_HEIGHT);
	loadimage(&bg_img, _T("blackground.bmp"), SCREEN_WIDTH, SCREEN_HEIGHT, true);
	putimage(0, 0, &bg_img);

	loadimage(&props_img[BOX], _T("box.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[FLOOR], _T("floor.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[MAN], _T("man.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[BOX_DES], _T("des.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[WALL], _T("wall_right.bmp"), PROPS_RATIO, PROPS_RATIO, true);
	loadimage(&props_img[HIT], _T("box.bmp"), PROPS_RATIO, PROPS_RATIO, true);
}

int main()
{
	//User verification 
	userinfo user;
	levelinfo level;
	bool ret = false;
	IMAGE bg_img;
	if (!login(user))
	{
		cout << "Failed to login... Please retry again." << endl;
		system("pause");
		exit(-1);
	}

	else
	{
		cout << "Welcome, "<<user.username<<". You are current at the stage: "<<user.level_id<<". Enjoy your game : )" << endl;
		system("pause");
	}

	//Initialized the stage level for the game
	init_game_graphic(bg_img);

	do
	{
		//Get the stage information from MYSQL
		ret = fetch_level_info(level, user.level_id);

		if (!ret)
		{
			cout << "Failed to get the stage information, please retry" << endl;
			system("pause");
			exit(-1);
		}

		//Convert the map information that query from MYSQL to MAP
		ret = transform_map_db2array(level, map);
		if (!ret)
		{
			cout << "Failed to get the stage information, please retry" << endl;
			system("pause");
			exit(-1);
		}

		char keyboard_input;

		for (int i = 0; i < level.map_row; i++)
		{
			for (int j = 0; j < level.map_column; j++)
			{
				if (map[i][j] == MAN)
				{
					man.x = i;
					man.y = j;
				}
				putimage(START_X + (j * PROPS_RATIO), START_Y + (i * PROPS_RATIO), &props_img[map[i][j]]);
			}
		}

		do
		{

			if (_kbhit)
			{
				keyboard_input = _getch();

				if (keyboard_input == KEY_UP)
				{
					map_update(UP);
				}
				else if (keyboard_input == KEY_DOWN)
				{
					map_update(DOWN);
				}
				else if (keyboard_input == KEY_LEFT)
				{
					map_update(LEFT);
				}
				else if (keyboard_input == KEY_RIGHT)
				{
					map_update(RIGHT);
				}
				else if (keyboard_input == KEY_QUIT)
				{
					end_flag = true;
				}
			}


			if (winDefine())
			{
				if (level.next_level < 1)
				{
					printOverScreen(&bg_img);
					end_flag = true;
					break;
				}
				//Update the next stage information
				printWinScreen(&bg_img);
				if (update_user_level(user, level.next_level))
				{
					user.level_id = level.next_level;
				}
				break;
				//end_flag == true
			}


		} while (end_flag == false);
	} while (end_flag == false);


	system("pause");
	return 0;

}

database.h

#pragma once
#include <iostream>
#include <string>

#define COLUMN	48
#define LINE	48
using namespace std;

typedef struct _userinfo
{
	int id;
	string username;
	string password;
	int level_id;
}userinfo;

typedef struct _levelinfo
{
	int id;
	string name;
 	int map_row;   //Total Map row number
	int map_column; //Total Map column number
	string map_data;
	int next_level;
}levelinfo;

bool fetch_user_info(userinfo &user);
bool fetch_level_info(levelinfo& level,int level_id);
bool transform_map_db2array(levelinfo& level,int map[LINE][COLUMN]);
bool update_user_level(userinfo& user, int next_level_id);

database.cpp

#include "database.h"
#include <mysql.h>
#include <stdio.h>

#define DB_NAME "box_man"
#define DB_HOST "127.0.0.1"
#define DB_PORT 3306
#define DB_USER "root"
#define DB_USER_PASSD "303771Chew!"

static int debug = 1;

static bool connect_db(MYSQL& mysql);

/***************************************
*Function: Use username and password to get the user info from MYSQL
*Input:
*	user - User information structure
* 
* Return:
*	sucessfully get info true, if failed return false;
***************************************/
bool fetch_user_info(userinfo& user)
{
	MYSQL mysql;
	MYSQL_RES* res;  //MYSQL return result
	MYSQL_ROW row;   //MYSQL return result in row
	char sql[256];
	bool ret = false;
	//1. Connect to the database
	if (connect_db(mysql) == false)
	{
		return false;
	}

	//2. According to the username and password, get the userinfo(id, level_id)
	snprintf(sql, 256, "select id, level_id from users where username ='%s' and password = md5('%s');", user.username.c_str(), user.password.c_str());
	//cout << sql << endl;
	ret = mysql_query(&mysql, sql); // This port if success will return 0;

	if (ret)
	{
		cout << "Database query have issue. Reason: " << mysql_error(&mysql) << endl;
		mysql_close(&mysql);
		return false;
	}

	//3. Get the result
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL)
	{
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}
	user.id = atoi(row[0]);
	user.level_id = atoi(row[1]);

	if (debug) cout << "User id: " << user.id << "\t" << "User level id:" << user.level_id << endl;

	//4. Return result
	mysql_free_result(res);
	mysql_close(&mysql);
	return true;

}

bool connect_db(MYSQL& mysql)
{
	//1. Initialized the database MYSQL
	mysql_init(&mysql);

	//2. Setting character encoding
	mysql_options(&mysql, MYSQL_SET_CHARSET_NAME, "latin1");

	//3. Connect to the MYSQL database
	if (mysql_real_connect(&mysql, DB_HOST, DB_USER, DB_USER_PASSD, DB_NAME, DB_PORT, NULL, 0) == NULL)
	{
		cout << "Connect failed... Reason:" << mysql_error(&mysql);
		return false;
	}

	return true;
}

/***************************************
*Function: According to the stage id get the completed information for the stage. (Map, next stage...)
*Input:
*	level - level information structure
*	level_id - the level id that we want to get the information
* Return:
*	sucessfully get info true, if failed return false;
***************************************/
bool fetch_level_info(levelinfo& level, int level_id)
{
	MYSQL mysql;
	MYSQL_RES* res;  //MYSQL return result
	MYSQL_ROW row;   //MYSQL return result in row
	char sql[256];
	bool ret = false;
	//1. Connect to the database
	if (connect_db(mysql) == false)
	{
		return false;
	}

	//2.According with the stage id to search for the information of the map
	snprintf(sql, 256, "select name, map_row, map_column, map_data, next_level_id from levels where id=%d;", level_id);
	ret = mysql_query(&mysql, sql); // This port if success will return 0;

	if (ret)
	{
		cout << "Database query have issue. Reason: " << mysql_error(&mysql) << endl;
		mysql_close(&mysql);
		return false;
	}
	//3. Get the result
	res = mysql_store_result(&mysql);
	row = mysql_fetch_row(res);

	if (row == NULL)
	{
		mysql_free_result(res);
		mysql_close(&mysql);
		return false;
	}

	level.id = level_id;
	level.name = row[0];
	level.map_row = atoi(row[1]);
	level.map_column = atoi(row[2]);
	level.map_data = row[3];
	level.next_level = atoi(row[4]);

	if (debug) cout << "The level id is:" << level.id << " The level name is:" << level.name << " The level map row is:" << level.map_row
		<< " The level map column is:" << level.map_column << " The map data is:" << level.map_data << " The level next level is:" << level.next_level << endl;

	//4. Return result
	mysql_free_result(res);
	mysql_close(&mysql);
	return true;
}


bool transform_map_db2array(levelinfo& level, int map[LINE][COLUMN])
{
	if(level.map_row > LINE || level.map_column > COLUMN)
	{
		cout << "The map is too big!" << endl;
		return false;
	}

	if (level.map_data.length() < 1)
	{
		cout << "The map is abnormal, please reset for the map!" << endl;
		return false;
	}

	int start = 0;
	int end = 0;

	int row = 0;
	int column = 0;
	do 
	{
		end = level.map_data.find('|', start);
		if (end < 0)
		{
			end = level.map_data.length();
		}
		if (start >= end) break;
		
		string line = level.map_data.substr(start, end - start);
		cout << line << endl;

		//Seperate the map information from all the ','
		char* next_token = NULL;
		char* item = strtok_s((char*)line.c_str(), ",", &next_token);

		column = 0;
		while (item && column < level.map_column)
		{
			cout << item<<" ";
			map[row][column] = atoi(item);
			column++;

			item = strtok_s(NULL, ",", &next_token);
		}

		if (column < level.map_column)
		{
			cout << "The map information abnormal. Will terminate the program" << endl;
			return false;
		}
		cout << endl;
		row++;

		if (row >= level.map_row)
		{
			break;
		}

		start = end + 1;

	} while (1);

	if (row < level.map_row)
	{
		cout << "The map row is less than the setup. Terminated the program now" << endl;
		return false;
	}
	return true;
}

bool update_user_level(userinfo& user, int next_level_id)
{
	MYSQL mysql;
	MYSQL_RES* res;  //MYSQL return result
	MYSQL_ROW row;   //MYSQL return result in row
	char sql[256];
	bool ret = false;

	//1. Connect to the database
	if (connect_db(mysql) == false)
	{
		return false;
	}

	//2. According to the user id, update the next level_id
	snprintf(sql, 256, "update users set level_id =%d where id=%d;", next_level_id, user.id);

	ret = mysql_query(&mysql, sql);

	if (ret)
	{
		cout << "Database query have issue. Reason: " << mysql_error(&mysql) << endl;
		mysql_close(&mysql);
		return false;
	}

	mysql_close(&mysql);
	return true;
}

实战 - 棋牌游戏数据库开发 (概念)

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1880168.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【热部署】✈️Springboot 项目的热部署实现方式

目录 &#x1f378;前言 &#x1f37b;一、热部署和手动重启 &#x1f37a;二、热部署的实现 2.1 手动启动热部署 2.2 自动检测热部署 2.3 关闭热部署 &#x1f49e;️三、章末 &#x1f378;前言 小伙伴们大家好&#xff0c;书接上文&#xff0c;通过Springboot 中的 actu…

【Python】已解决:IndexError: list index out of range

文章目录 一、分析问题背景二、可能出错的原因三、错误代码示例四、正确代码示例五、注意事项 已解决&#xff1a;IndexError: list index out of range 一、分析问题背景 在Python编程中&#xff0c;IndexError: list index out of range 是一个常见的错误。这个错误通常出现…

【Python从入门到进阶】59、Pandas库中Series对象的操作(二)

接上篇《58、Pandas库中Series对象的操作(一)》 上一篇我们讲解了Pandas库中Series对象的基本概念、对象创建和操作&#xff0c;本篇我们来继续学习Series对象的运算、函数应用、时间序列操作&#xff0c;以及Series的案例实践。 一、Series对象的运算 1. 数值型数据的算术运…

基于JSP的体育竞赛成绩管理系统

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;JSP 数据库&#xff1a;MySQL 技术&#xff1a;JSPJava 工具&#xff1a;MyEclipse, Tomcat, MySQL 系统展示 首页 管理…

Windows Ternimal

Windows Ternimal 安装 Windows 终端概述 | Microsoft Learn wt --help在当前目录打开 lextm/windowsterminal-shell: Install/uninstall scripts for Windows Terminal context menu items 打开指定目录 wt -d %USERPROFILE% ohmyposh 美化 1 安装 2 添加 ohmyposh bin…

数字签名解析

1. 概述 数字签名不是手写签名的数字图像&#xff1b; 数字签名是一种可以提供认证的加密形式&#xff0c;是转向完全无纸环境的一个途径&#xff1b; 数字签名机制用以解决伪造、抵赖、冒充和篡改、完整性保护等安全问题。 2. 公钥密码与数字签名的关系 要实现数字签名&#…

【python爬虫实战】爬取豆瓣top250(网站有反爬虫机制肿么办)

关于请求头headers: 值得注意的是&#xff0c;与上一篇 &#xff1a;​​​​​​【python爬虫实战】爬取书店网站的 书名&价格&#xff08;注释详解&#xff09;-CSDN博客 爬取书名不同&#xff0c;这次爬取豆瓣网站必须使用“请求头headers”&#xff0c;不然将没有输…

SSM学习2:依赖注入、依赖自动装配、集合注入、加载properties文件

依赖注入 依赖注入方式 setter注入——引用类型 setter注入——简单类型 public class BookDaoImpl implements BookDao {public void setDatabaseName(String databaseName) {this.databaseName databaseName;}public void setNum(int num) {this.num num;}private Stri…

【图像超分辨率】一个简单的总结

文章目录 图像超分辨率(Image Super-Resolution, ISR)1 什么是图像超分辨率&#xff1f;2 图像超分辨率通常有哪些方法&#xff1f;&#xff08;1&#xff09;基于插值的方法&#xff08;2&#xff09;基于重建的方法&#xff08;3&#xff09;基于学习的方法&#xff08;LR im…

jenkins 发布服务到linux服务器

1.环境准备 1.1 需要一台已经部署了jenkins的服务器&#xff0c;上面已经集成好了&#xff0c;jdk、maven、nodejs、git等基础的服务。 1.2 需要安装插件 pusblish over ssh 1.3 准备一台额外的linux服务器&#xff0c;安装好jdk 2.流程描述 2.1 配置jenkins&#xff0c;包括p…

统计是一门艺术(参数假设检验)

1.参数假设检验 在总体分布已知的情况下&#xff0c;对分布中未知参数的检验。 &#xff08;1&#xff09;相关基本概念 零假设/原假设与对立假设/备择假设&#xff1a; 任务&#xff1a;根据样本作出是否接受H0 复合假设与简单假设&#xff1a; 否定域/拒绝域与接受域&…

Python:谈谈常规滤波器(带通、低通、高通、带阻)的用法

一、滤波器的作用 滤波器在信号处理中用于移除或减少信号中的噪声&#xff0c;同时保持信号的某些特性。滤波器通常用于音频、视频和图像处理等领域。滤波器根据其 designed for different purposes and can be divided into several types, such as lowpass filters, highpass…

【Unity设计模式】✨使用 MVC 和 MVP 编程模式

前言 最近在学习Unity游戏设计模式&#xff0c;看到两本比较适合入门的书&#xff0c;一本是unity官方的 《Level up your programming with game programming patterns》 ,另一本是 《游戏编程模式》 这两本书介绍了大部分会使用到的设计模式&#xff0c;因此很值得学习 本…

Linux rpm与yum

一、rpm包管理 rpm用于互联网下载包的打包及安装工具&#xff0c;它包含在某些Linux分发版中。它生成具有.RPM扩展名的文件。RPM是RedHat Package Manager (RedHat软件包管理工具&#xff09;的缩写&#xff0c;类似windows的setup.exe&#xff0c;这一文件格式名称虽然打上了R…

Python pip install模块时C++编译环境问题

pip install模块时C编译环境问题 在接触和使用python后&#xff0c;常常会通过pip install命令安装第三方模块&#xff0c;大多数模块可以直接安装&#xff0c;但许多新同学仍会遇见某些模块需要实时编译后才能安装&#xff0c;如报错信息大概是缺乏C编译环境&#xff0c;本文则…

Golang-GMP

GMP调度 golang-GMP语雀笔记整理 GMP调度设计目的&#xff0c;为何设计GMP?GMP的底层实现几个核心数据结构GMP调度流程 设计目的&#xff0c;为何设计GMP? 无论是多进程、多线程目的都是为了并发提高cpu的利用率&#xff0c;但多进程、多线程都存在局限性。比如多进程通过时…

Python变量的命名规则与赋值方式

第二章&#xff1a;Python 基础语法 第一节&#xff1a;变量的命名规则与赋值方式 2.1.1 引言 在编程中&#xff0c;变量是存储数据的基本单元。变量的命名和赋值是编程语言中表达和操作数据的基础。了解和遵循变量命名规则对于编写清晰、可维护的代码至关重要。 2.1.2 变量…

嵌入式Linux系统编程 — 5.2 Linux系统时间与日期

目录 1 了解Linux系统时间 1.1 几种常用的时间 1.2 如何查看几种常用的时间 1.3 Linux 系统中的时间 2 time、gettimeofday获取时间 2.1 time函数 2.2 ​​​​​​​gettimeofday函数&#xff1a; 2.3 示例程序 3 时间转换函数 3.1 ctime与ctime_r函数 3.2 localti…

小白学python(第四天)顺序与分支篇

这几天因为个人原因&#xff0c;python篇会更新比较慢&#xff0c;还望大家谅解&#xff0c;那么废话不多说&#xff0c;我们现在就进入正题 顺序篇 这个没啥好说的&#xff0c;就是自上而下&#xff0c;依次执行 分支篇 条件&#xff08;if&#xff09;语句语法格式&#…

Listary(Windows 文件搜索工具)专业版值得购买吗?

说到经典的国货软件&#xff0c;有一款 Win 软件是一定绕不过去的。它就是知名的本地文件搜索工具 Listary&#xff01; 便捷的文件搜索窗口&#xff1b;快捷操作的体验&#xff1b;与系统更匹配的外观设计&#xff1b;更智能的排序和更可靠的索引。 便捷的文件搜索窗口 紧凑…