zoukankan      html  css  js  c++  java
  • go操作mysql

    mysql常用语句

    操作 方法
    用户登录 mysql -u root -p
    退出 exit
    选中数据库 USE 数据库名 ; use RUNOOB
    列出 MySQL 数据库管理系统的数据库列表 SHOW DATABASES;
    显示某个数据库列表 SHOW TABLES;
    显示数据表的属性 SHOW COLUMNS FROM 数据表:
    显示数据表的详细索引信息 SHOW INDEX FROM 数据表;
    创建数据库 CREATE DATABASE 数据库名;
    删除数据库 drop database 数据库名;
    创建数据表 CREATE TABLE table_name (column_name column_type);
    删除数据表 DROP TABLE table_name ;
    插入数据 见下面
    查询数据 见下面
    WHERE 子句 SELECT * from runoob_tbl WHERE runoob_author='菜鸟教程';
    UPDATE 更新 见下面
    事务 见下面
    DELETE 语句 见下面
    先记录到这,以后应道再更新。。。。

    对上面几个重要的命令进行简单实例:

    创建数据表:

    root@host# mysql -u root -p
    Enter password:*******
    mysql> use RUNOOB;
    Database changed
    mysql> CREATE TABLE runoob_tbl(
       -> runoob_id INT NOT NULL AUTO_INCREMENT,
       -> runoob_title VARCHAR(100) NOT NULL,
       -> runoob_author VARCHAR(40) NOT NULL,
       -> submission_date DATE,
       -> PRIMARY KEY ( runoob_id )
       -> )ENGINE=InnoDB DEFAULT CHARSET=utf8;
    Query OK, 0 rows affected (0.16 sec)
    mysql>
    实例解析:
    
    如果你不想字段为 NULL 可以设置字段的属性为 NOT NULL, 在操作数据库时如果输入该字段的数据为NULL ,就会报错。
    AUTO_INCREMENT定义列为自增的属性,一般用于主键,数值会自动加1。
    PRIMARY KEY关键字用于定义列为主键。 您可以使用多列来定义主键,列间以逗号分隔。
    ENGINE 设置存储引擎,CHARSET 设置编码。
    

    插入数据

    #语法:
    INSERT INTO table_name ( field1, field2,...fieldN )
                           VALUES
                           ( value1, value2,...valueN );
    #实例:
    root@host# mysql -u root -p password;
    Enter password:*******
    mysql> use RUNOOB;
    Database changed
    mysql> INSERT INTO runoob_tbl 
        -> (runoob_title, runoob_author, submission_date)
        -> VALUES
        -> ("学习 PHP", "菜鸟教程", NOW());
    Query OK, 1 rows affected, 1 warnings (0.01 sec)
    

    查询数据:

    #语法:
    SELECT column_name,column_name
    FROM table_name
    [WHERE Clause]
    [LIMIT N][ OFFSET M]
    
    1.查询语句中你可以使用一个或者多个表,表之间使用逗号(,)分割,并使用WHERE语句来设定查询条件。
    2.SEECT 命令可以读取一条或者多条记录。
    3.你可以使用星号(*)来代替其他字段,SELECT语句会返回表的所有字段数据
    4.你可以使用 WHERE 语句来包含任何条件。
    5.你可以使用 LIMIT 属性来设定返回的记录数。
    6.你可以通过OFFSET指定SELECT语句开始查询的数据偏移量。默认情况下偏移量为0。
    
    #实例:
    select * from runoob_tbl;
    

    delete删除:

    语法

    以下是 SQL DELETE 语句从 MySQL 数据表中删除数据的通用语法:

    DELETE FROM table_name [WHERE Clause]
    
    • 如果没有指定 WHERE 子句,MySQL 表中的所有记录将被删除。
    • 你可以在 WHERE 子句中指定任何条件
    • 您可以在单个表中一次性删除记录。

    当你想删除数据表中指定的记录时 WHERE 子句是非常有用的。

    DELETE FROM runoob_tbl WHERE runoob_id=3;

    UPDATE 更新:

    #语法
    UPDATE table_name SET field1=new-value1, field2=new-value2
    [WHERE Clause]
    
    1.你可以同时更新一个或多个字段。
    2.你可以在 WHERE 子句中指定任何条件。
    3.你可以在一个单独表中同时更新数据。
    
    #实例
    mysql> UPDATE runoob_tbl SET runoob_title='学习 C++' WHERE runoob_id=3;
    Query OK, 1 rows affected (0.01 sec)
     
    mysql> SELECT * from runoob_tbl WHERE runoob_id=3;
    +-----------+--------------+---------------+-----------------+
    | runoob_id | runoob_title | runoob_author | submission_date |
    +-----------+--------------+---------------+-----------------+
    | 3         | 学习 C++   | RUNOOB.COM    | 2016-05-06      |
    +-----------+--------------+---------------+-----------------+
    1 rows in set (0.01 sec)
    

    事务:

    MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

    • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
    • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
    • 事务用来管理 insert,update,delete 语句

    MYSQL 事务处理主要有两种方法:

    1、用 BEGIN, ROLLBACK, COMMIT来实现

    • BEGIN 开始一个事务
    • ROLLBACK 事务回滚
    • COMMIT 事务确认

    2、直接用 SET 来改变 MySQL 的自动提交模式:

    • SET AUTOCOMMIT=0 禁止自动提交
    • SET AUTOCOMMIT=1 开启自动提交

    在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

    mysql> use RUNOOB;
    Database changed
    mysql> CREATE TABLE runoob_transaction_test( id int(5)) engine=innodb;  # 创建数据表
    Query OK, 0 rows affected (0.04 sec)
     
    mysql> select * from runoob_transaction_test;
    Empty set (0.01 sec)
     
    mysql> begin;  # 开始事务
    Query OK, 0 rows affected (0.00 sec)
     
    mysql> insert into runoob_transaction_test value(5);
    Query OK, 1 rows affected (0.01 sec)
     
    mysql> insert into runoob_transaction_test value(6);
    Query OK, 1 rows affected (0.00 sec)
     
    mysql> commit; # 提交事务
    Query OK, 0 rows affected (0.01 sec)
     
    mysql>  select * from runoob_transaction_test;
    +------+
    | id   |
    +------+
    | 5    |
    | 6    |
    +------+
    2 rows in set (0.01 sec)
     
    mysql> begin;    # 开始事务
    Query OK, 0 rows affected (0.00 sec)
     
    mysql>  insert into runoob_transaction_test values(7);
    Query OK, 1 rows affected (0.00 sec)
     
    mysql> rollback;   # 回滚
    Query OK, 0 rows affected (0.00 sec)
     
    mysql>   select * from runoob_transaction_test;   # 因为回滚所以数据没有插入
    +------+
    | id   |
    +------+
    | 5    |
    | 6    |
    +------+
    2 rows in set (0.01 sec)
     
    mysql>
    

    下面内容转载自Qimi老师的博客:

    Go语言操作MySQL

    go操作mysql

    连接

    Go语言中的database/sql包提供了保证SQL或类SQL数据库的泛用接口,并不提供具体的数据库驱动。使用database/sql包时必须注入(至少)一个数据库驱动

    我们常用的数据库基本上都有完整的第三方实现。例如:MySQL驱动

    下载依赖

    go get -u github.com/go-sql-driver/mysql
    

    使用MySQL驱动

    func Open(driverName, dataSourceName string) (*DB, error)
    

    Open打开一个dirverName指定的数据库,dataSourceName指定数据源,一般至少包括数据库文件名和其它连接必要的信息。

    import (
    	"database/sql"
    
    	_ "github.com/go-sql-driver/mysql"
    )
    
    func main() {
       // DSN:Data Source Name
    	dsn := "user:password@tcp(127.0.0.1:3306)/dbname"
    	db, err := sql.Open("mysql", dsn)
    	if err != nil {
    		panic(err)
    	}
    	defer db.Close()  // 注意这行代码要写在上面err判断的下面
    }
    

    思考题: 为什么上面代码中的defer db.Close()语句不应该写在if err != nil的前面呢?

    因为虽然defer语句是最后执行,但程序运行到该语句时,会先“登记”一下这个操作,放在if err != nil前面,当程序出现错误时,defer db.Close()语句就会出现错误,可能会出现一些空指针的调用等问题。

    初始化连接

    Open函数可能只是验证其参数格式是否正确,实际上并不创建与数据库的连接。如果要检查数据源的名称是否真实有效,应该调用Ping方法。

    原文解释如下

    First, you should understand that a sql.DB is not a connection. When you use sql.Open() you get a handle for a database. The database/sql package manages a pool of connections in the background, and doesn't open any connections until you need them. Therefore sql.Open() doesn't directly open a connection. As a result, sql.Open() does not return an error, if the server isn't available or the connection data (Username, Password) isn't correct. If you want to check this before making queries (e.g at application startup) you can use db.Ping().
    翻译:
    大致意思就是sql.DB怒视一个连接,他是一个管理连接池的句柄。如果不使用他,就不会建立来连接,因此即使你的dsn里的账号密码出错,程序也不会提醒,所以要主动使用db.Ping()去检测一下。
    

    返回的DB对象可以安全地被多个goroutine并发使用,并且维护其自己的空闲连接池。因此,Open函数应该仅被调用一次,很少需要关闭这个DB对象。

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"//只是用这个包力的init
    )
    func main() {
    	// DSN:Data Source Name
    	//数据库信息
    	dsn := "root:5210@tcp(127.0.0.1:3306)/gotest"
    	db, err := sql.Open("mysql", dsn) //生成句柄
    	if err != nil {
    		panic(err)
    	}
    	defer db.Close() // 注意这行代码要写在上面err判断的下面
    	err = db.Ping()//检测是否出现错误
    	if err != nil {
    		fmt.Printf("open %s invaild ,err'=", err)
    		return
    	}
    	fmt.Println("connect database succsed")
    }
    

    其中sql.DB是一个数据库(操作)句柄,代表一个具有零到多个底层连接的连接池。它可以安全地被多个goroutine同时使用。database/sql包会自动创建和释放连接;它也会维护一个闲置连接的连接池。

    SetMaxOpenConns

    func (db *DB) SetMaxOpenConns(n int)
    

    SetMaxOpenConns设置与数据库建立连接的最大数目。 如果n大于0且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。 如果n<=0,不会限制最大开启连接数,默认为0(无限制)。

    SetMaxIdleConns

    func (db *DB) SetMaxIdleConns(n int)
    

    SetMaxIdleConns设置连接池中的最大闲置连接数。 如果n大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。 如果n<=0,不会保留闲置连接。

    CRUD

    下面是CURD操作常用的的函数。

    mark

    建库建表

    我们先在MySQL中创建一个名为sql_test的数据库

    CREATE DATABASE gotest;
    

    进入该数据库:

    use gotest;
    

    执行以下命令创建一张用于测试的数据表:

    CREATE TABLE `user` (
        `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
        `name` VARCHAR(20) DEFAULT '',
        `age` INT(11) DEFAULT '0',
        PRIMARY KEY(`id`)
    )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 
    

    查询

    单行查询

    1. 调用db.QueryRow()
    2. 调用row.Scan()

    单行查询db.QueryRow()执行一次查询,并期望返回最多一行结果(即Row)。Scan将该行查询结果各列分别保存进dest参数指定的值中。如果该查询匹配多行,Scan会使用第一行结果并丢弃其余各行。如果没有匹配查询的行,Scan会返回ErrNoRows。

    func (db *DB) QueryRow(query string, args ...interface{}) *Row
    func (r *Row) Scan(dest ...interface{}) error
    

    具体示例代码:

    package main
    
    import (
    	"database/sql"
    	"fmt"
    	_ "github.com/go-sql-driver/mysql"
    )
    var db *sql.DB//初始化连接池
    
    //实例化一个结构体,用于接受收据库的返回值
    //数据库的每一行都对应一个结构体
    type user struct {
    	id int
    	name string
    	age int
    }
    func initDB()(err error){
    	// DSN:Data Source Name
    	//数据库信息
    	dsn := "root:5210@tcp(127.0.0.1:3306)/gotest"
    	db, err := sql.Open("mysql", dsn) //检查格式
    	if err != nil {
    		panic(err)
    	}
    	defer db.Close() // 注意这行代码要写在上面err判断的下面
    	err = db.Ping()
    	if err != nil {
    		fmt.Printf("open %s invaild ,err'=", err)
    		return
    	}
    	fmt.Println("connect database succsed")
    	return nil
    }
    func queryDemo(id int){
    	var  u1 user
    	//1. 写sql语句
    	sqlStr:="select id,name,age from user where id=?"//占位符
    	//2.调用查询函数
    	rowObj:=db.QueryRow(sqlStr,id)
    	//3.调用scan函数拿到结果,映射到结构体中
    	rowObj.Scan(&u1.id,&u1.name,&u1.age)
    	fmt.Printf("u1:%v
    ",u1)
    	//注:在redis中存入的都是字符串,若想取出来还原成结构体还需要进行反序列化
    	//这里是调用取出结果的一个方法来进行反向解析
    }
    func main() {
    	//初始化
    	err:=initDB()
    	if err != nil {
    		fmt.Printf("初始化连接失败 err=%v
    ",err)
    		return
    	}
    	//查询
    	queryDemo(2)	
    }
    

    多行查询

    多行查询db.Query()执行一次查询,返回多行结果(即Rows),一般用于执行select命令。参数args表示query中的占位参数。

    func (db *DB) Query(query string, args ...interface{}) (*Rows, error)
    

    核心示例代码:

    //查询多条
    func queryMore(id int){
    	//1. 写sql语句
    	sqlStr:= "select * from user where id>?"//这里会查询多条
    	//2.调用查询函数
    	rows,err:=db.Query(sqlStr,id)
    	if err != nil {
    		fmt.Printf("query err=", err)
    		return
    	}
    	defer rows.Close()//这个在多行查询时要手动关闭连接.
    	//3.使用rows.Next遍历rows
    	for rows.Next(){
    		var  u2 user
    		//4. scan解析每一行
    		err=rows.Scan(&u2.id,&u2.name,&u2.age)
    		fmt.Printf("u2:%v
    ",u2)
    	}
    }
    

    插入数据

    插入、更新和删除操作都使用Exec方法。

    func (db *DB) Exec(query string, args ...interface{}) (Result, error)
    

    Exec执行一次命令(包括查询、删除、更新、插入等),返回的Result是对已执行的SQL命令的总结。参数args表示query中的占位参数。

    具体插入数据示例代码如下:

    //插入
    func insert(){
    	//1. 写sql语句
    	sqlStr:="insert into user(name,age) value(?,?)"
    	//2. 执行
    	ret,err:=db.Exec(sqlStr,"韩非子",1200)
    	if err != nil {
    		panic(err)
    	}
    	//3. 查看插入行id
    	newId,err:=ret.LastInsertId()
    	fmt.Println(newId)
    }
    

    更新数据

    具体更新数据示例代码如下:

    func upDate(newage int, id int) {
    	//1 写sql语句
    	sqlStr := "update user set age=? where id=?;"
    	//2. exec
    	ret, err := db.Exec(sqlStr, newage, id)
    	if err != nil {
    		fmt.Printf("update failed, err:%v
    ", err)
    		return
    	}
    	n, err := ret.RowsAffected() //操作影响的行数
    	if err != nil {
    		fmt.Printf("get RowsAffected failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("update success, affected rows:%d
    ", n)
    }
    

    删除数据

    具体删除数据的示例代码如下:

    //删除操作
    func deleteDemo(id int) {
    	//1 写sql语句
    	sqlStr := "delete from user where id=?;"
    	//2. exec
    	ret, err := db.Exec(sqlStr, id)
    	if err != nil {
    		fmt.Printf("delete failed, err:%v
    ", err)
    		return
    	}
    	n, err := ret.RowsAffected()
    	if err != nil {
    		fmt.Printf("get RowsAffected failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("update success, affected rows:%d
    ", n)
    }
    

    MySQL预处理

    什么是预处理?

    普通SQL语句执行过程:

    1. 客户端对SQL语句进行占位符替换得到完整的SQL语句。
    2. 客户端发送完整SQL语句到MySQL服务端
    3. MySQL服务端执行完整的SQL语句并将结果返回给客户端。

    预处理执行过程:

    1. 把SQL语句分成两部分,命令部分与数据部分。
    2. 先把命令部分发送给MySQL服务端,MySQL服务端进行SQL预处理。
    3. 然后把数据部分发送给MySQL服务端,MySQL服务端对SQL语句进行占位符替换。
    4. MySQL服务端执行完整的SQL语句并将结果返回给客户端。

    为什么要预处理?

    1. 优化MySQL服务器重复执行SQL的方法,可以提升服务器性能,提前让服务器编译,一次编译多次执行,节省后续编译的成本。
    2. 避免SQL注入问题。

    Go实现MySQL预处理

    Go中的

    func (db *DB) Prepare(query string) (*Stmt, error)
    

    Stmt的几个方法:

    mark

    Prepare方法会先将sql语句发送给MySQL服务端,返回一个准备好的状态用于之后的查询和命令。返回值可以同时执行多个查询和命令。

    func prepare() {
       //1 写sql语句
       sqlStr := "select id, name, age from user where id > ?"
       //2.发送给mysql服务器进行准备,返回一个状态
       stem, err := db.Prepare(sqlStr)
       if err != nil {
          fmt.Printf("prepare failed, err:%v
    ", err)
          return
       }
       defer stem.Close()
    
       rows, err := stem.Query(1)
       if err != nil {
          fmt.Printf("query failed, err:%v
    ", err)
          return
       }
       defer rows.Close()
       //循环读取得到的结果
       for rows.Next() {
          var u user
          //逐行解析
          err = rows.Scan(&u.id, &u.name, &u.age)
          if err != nil {
    
             fmt.Printf("scan failed, err:%v
    ", err)
             return
          }
          fmt.Printf("id:%d name:%s age:%d
    ", u.id, u.name, u.age)
       }
    }
    

    Go实现MySQL事务

    与事务相关的几个操作:

    mark

    事务相关方法

    Go语言中使用以下三个方法实现MySQL中的事务操作。 开始事务

    func (db *DB) Begin() (*Tx, error)
    

    提交事务

    func (tx *Tx) Commit() error
    

    回滚事务

    func (tx *Tx) Rollback() error
    

    代码实例:

    // 事务操作示例
    // 事务操作示例
    func transactionDemo() {
    	tx, err := db.Begin() // 开启事务
    	if err != nil {
    		if tx != nil {
    			tx.Rollback() // 回滚
    		}
    		fmt.Printf("begin trans failed, err:%v
    ", err)
    		return
    	}
    	sqlStr1 := "Update user set age=30 where id=?"
    	ret1, err := tx.Exec(sqlStr1, 2)
    	if err != nil {
    		tx.Rollback() // 回滚
    		fmt.Printf("exec sql1 failed, err:%v
    ", err)
    		return
    	}
    	affRow1, err := ret1.RowsAffected()
    	if err != nil {
    		tx.Rollback() // 回滚
    		fmt.Printf("exec ret1.RowsAffected() failed, err:%v
    ", err)
    		return
    	}
    
    	sqlStr2 := "Update user set age=40 where id=?"
    	ret2, err := tx.Exec(sqlStr2, 3)
    	if err != nil {
    		tx.Rollback() // 回滚
    		fmt.Printf("exec sql2 failed, err:%v
    ", err)
    		return
    	}
    	affRow2, err := ret2.RowsAffected()
    	if err != nil {
    		tx.Rollback() // 回滚
    		fmt.Printf("exec ret1.RowsAffected() failed, err:%v
    ", err)
    		return
    	}
    
    	fmt.Println(affRow1, affRow2)
    	if affRow1 == 1 && affRow2 == 1 {
    		fmt.Println("事务提交啦...")
    		tx.Commit() // 提交事务
    	} else {
    		tx.Rollback()
    		fmt.Println("事务回滚啦...")
    	}
    
    	fmt.Println("exec trans success!")
    }
    

    sqlx使用

    第三方库sqlx能够简化操作,提高开发效率。

    安装

    go get github.com/jmoiron/sqlx
    

    连接数据库

    var db *sqlx.DB
    
    func initDB() (err error) {
    	dsn := "user:password@tcp(127.0.0.1:3306)/sql_test?charset=utf8mb4&parseTime=True"
    	// 也可以使用MustConnect连接不成功就panic
    	db, err = sqlx.Connect("mysql", dsn)
    	if err != nil {
    		fmt.Printf("connect DB failed, err:%v
    ", err)
    		return
    	}
    	db.SetMaxOpenConns(20)
    	db.SetMaxIdleConns(10)
    	return
    }
    

    查询

    查询单行数据示例代码如下:

    // 查询单条数据示例
    func queryRowDemo() {
    	sqlStr := "select id, name, age from user where id=?"
    	var u user
    	err := db.Get(&u, sqlStr, 1)
    	if err != nil {
    		fmt.Printf("get failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("id:%d name:%s age:%d
    ", u.ID, u.Name, u.Age)
    }
    

    查询多行数据示例代码如下:

    // 查询多条数据示例
    func queryMultiRowDemo() {
    	sqlStr := "select id, name, age from user where id > ?"
    	var users []user
    	err := db.Select(&users, sqlStr, 0)
    	if err != nil {
    		fmt.Printf("query failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("users:%#v
    ", users)
    }
    

    插入、更新和删除

    sqlx中的exec方法与原生sql中的exec使用基本一致:

    // 插入数据
    func insertRowDemo() {
    	sqlStr := "insert into user(name, age) values (?,?)"
    	ret, err := db.Exec(sqlStr, "沙河小王子", 19)
    	if err != nil {
    		fmt.Printf("insert failed, err:%v
    ", err)
    		return
    	}
    	theID, err := ret.LastInsertId() // 新插入数据的id
    	if err != nil {
    		fmt.Printf("get lastinsert ID failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("insert success, the id is %d.
    ", theID)
    }
    
    // 更新数据
    func updateRowDemo() {
    	sqlStr := "update user set age=? where id = ?"
    	ret, err := db.Exec(sqlStr, 39, 6)
    	if err != nil {
    		fmt.Printf("update failed, err:%v
    ", err)
    		return
    	}
    	n, err := ret.RowsAffected() // 操作影响的行数
    	if err != nil {
    		fmt.Printf("get RowsAffected failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("update success, affected rows:%d
    ", n)
    }
    
    // 删除数据
    func deleteRowDemo() {
    	sqlStr := "delete from user where id = ?"
    	ret, err := db.Exec(sqlStr, 6)
    	if err != nil {
    		fmt.Printf("delete failed, err:%v
    ", err)
    		return
    	}
    	n, err := ret.RowsAffected() // 操作影响的行数
    	if err != nil {
    		fmt.Printf("get RowsAffected failed, err:%v
    ", err)
    		return
    	}
    	fmt.Printf("delete success, affected rows:%d
    ", n)
    }
    
  • 相关阅读:
    [leetcode] Rotate Image
    64位Ubuntu14.04搭建ADT开发环境
    Android开源框架之SwipeListView导入及模拟QQ侧滑
    [LeetCode] Compare Version Numbers
    Android平台之不预览获取照相机预览数据帧及精确时间截
    Android开发之打开闪光灯录制视频
    Android设置选项开发及自定义Preference样式
    Android平台上使用气压传感器计算海拔高度
    JAVA语言对比C++语言的几个优点和自身的关键特性
    数据结构二、线性表
  • 原文地址:https://www.cnblogs.com/wind-zhou/p/12970468.html
Copyright © 2011-2022 走看看