SQLite学习手册(一)

article/2024/4/20 16:09:10


一、获取表的Schema信息:

    1). 动态创建表。
    2). 根据sqlite3提供的API,获取表字段的信息,如字段数量以及每个字段的类型。
    3). 删除该表。
    见以下代码及关键性注释:

#include <sqlite3.h>#include <string>using namespace std;void doTest(){sqlite3* conn = NULL;//1. 打开数据库int result = sqlite3_open("E:/myFirstTest.db",&conn);if (result != SQLITE_OK) {sqlite3_close(conn);return;}const char* createTableSQL = "CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)";sqlite3_stmt* stmt = NULL;int len = strlen(createTableSQL);//2. 准备创建数据表,如果创建失败,需要用sqlite3_finalize释放sqlite3_stmt对象,以防止内存泄露。if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) {if (stmt)sqlite3_finalize(stmt);sqlite3_close(conn);return;}


     //3. 通过sqlite3_step命令执行创建表的语句。对于DDL和DML语句而言,sqlite3_step执行正确的返回值
     //只有SQLITE_DONE,对于SELECT查询而言,如果有数据返回SQLITE_ROW,当到达结果集末尾时则返回
     //SQLITE_DONE。
     if (sqlite3_step(stmt) != SQLITE_DONE)

 {
         sqlite3_finalize(stmt);
         sqlite3_close(conn);
         return;
     }
     //4. 释放创建表语句对象的资源。
     sqlite3_finalize(stmt);
     printf("Succeed to create test table now.\n");
     //5. 构造查询表数据的sqlite3_stmt对象。
     const char* selectSQL = "SELECT * FROM TESTTABLE WHERE 1 = 0";
     sqlite3_stmt* stmt2 = NULL;
     if (sqlite3_prepare_v2(conn,selectSQL,strlen(selectSQL),&stmt2,NULL) != SQLITE_OK) {
         if (stmt2)
             sqlite3_finalize(stmt2);
         sqlite3_close(conn);
         return;
     }
     //6. 根据select语句的对象,获取结果集中的字段数量。
     int fieldCount = sqlite3_column_count(stmt2);
     printf("The column count is %d.\n",fieldCount);
     //7. 遍历结果集中每个字段meta信息,并获取其声明时的类型。    
     for (int i = 0; i < fieldCount; ++i) {
         //由于此时Table中并不存在数据,再有就是SQLite中的数据类型本身是动态的,所以在没有数据时
         //无法通过sqlite3_column_type函数获取,此时sqlite3_column_type只会返回SQLITE_NULL,
         //直到有数据时才能返回具体的类型,因此这里使用了sqlite3_column_decltype函数来获取表声
         //明时给出的声明类型。
         string stype = sqlite3_column_decltype(stmt2,i);
         stype = strlwr((char*)stype.c_str());
         //下面的解析规则见该系列的“数据类型-->1. 决定字段亲缘性的规则”部分,其链接如下:
         //http://www.cnblogs.com/stephen-liu74/archive/2012/01/18/2325258.html
         if (stype.find("int") != string::npos) {
             printf("The type of %dth column is INTEGER.\n",i);
         } else if (stype.find("char") != string::npos
             || stype.find("text") != string::npos) {
             printf("The type of %dth column is TEXT.\n",i);
         } else if (stype.find("real") != string::npos 
             || stype.find("floa") != string::npos 
             || stype.find("doub") != string::npos ) {
             printf("The type of %dth column is DOUBLE.\n",i);
         }
     }
     sqlite3_finalize(stmt2);
     //8. 为了方便下一次测试运行,我们这里需要删除该函数创建的数据表,否则在下次运行时将无法
     //创建该表,因为它已经存在。
     const char* dropSQL = "DROP TABLE TESTTABLE";
     sqlite3_stmt* stmt3 = NULL;
     if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt3,NULL) != SQLITE_OK) {
         if (stmt3)
             sqlite3_finalize(stmt3);
         sqlite3_close(conn);
         return;
     }
     if (sqlite3_step(stmt3) == SQLITE_DONE) {
         printf("The test table has been dropped.\n");
     }
     sqlite3_finalize(stmt3);
     sqlite3_close(conn);
 }
 
 int main()
 {
     doTest();
     return 0;
 }
 //输出结果为:
 //Succeed to create test table now.
 //The column count is 3.
 //The type of 0th column is INTEGER.
 //The type of 1th column is DOUBLE.
 //The type of 2th column is TEXT.
 //The test table has been dropped.

二、常规数据插入:

    1). 创建测试数据表。
    2). 通过INSERT语句插入测试数据。
    3). 删除测试表。
    见以下代码及关键性注释:

 #include <sqlite3.h>#include <string>#include <stdio.h>using namespace std;void doTest(){sqlite3* conn = NULL;//1. 打开数据库int result = sqlite3_open("D:/myFirstTest.db",&conn);if (result != SQLITE_OK) {sqlite3_close(conn);return;}const char* createTableSQL = "CREATE TABLE TESTTABLE (int_col INT, float_col REAL, string_col TEXT)";sqlite3_stmt* stmt = NULL;int len = strlen(createTableSQL);//2. 准备创建数据表,如果创建失败,需要用sqlite3_finalize释放sqlite3_stmt对象,以防止内存泄露。if (sqlite3_prepare_v2(conn,createTableSQL,len,&stmt,NULL) != SQLITE_OK) {if (stmt)sqlite3_finalize(stmt);sqlite3_close(conn);return;}//3. 通过sqlite3_step命令执行创建表的语句。对于DDL和DML语句而言,sqlite3_step执行正确的返回值//只有SQLITE_DONE,对于SELECT查询而言,如果有数据返回SQLITE_ROW,当到达结果集末尾时则返回//SQLITE_DONE。if (sqlite3_step(stmt) != SQLITE_DONE) {sqlite3_finalize(stmt);sqlite3_close(conn);return;}//4. 释放创建表语句对象的资源。sqlite3_finalize(stmt);printf("Succeed to create test table now.\n");int insertCount = 10;//5. 构建插入数据的sqlite3_stmt对象。const char* insertSQL = "INSERT INTO TESTTABLE VALUES(%d,%f,'%s')";const char* testString = "this is a test.";char sql[1024];sqlite3_stmt* stmt2 = NULL;for (int i = 0; i < insertCount; ++i) {sprintf(sql,insertSQL,i,i * 1.0,testString);if (sqlite3_prepare_v2(conn,sql,strlen(sql),&stmt2,NULL) != SQLITE_OK) {if (stmt2)sqlite3_finalize(stmt2);sqlite3_close(conn);return;}if (sqlite3_step(stmt2) != SQLITE_DONE) {sqlite3_finalize(stmt2);sqlite3_close(conn);return;}printf("Insert Succeed.\n");}sqlite3_finalize(stmt2);//6. 为了方便下一次测试运行,我们这里需要删除该函数创建的数据表,否则在下次运行时将无法//创建该表,因为它已经存在。const char* dropSQL = "DROP TABLE TESTTABLE";sqlite3_stmt* stmt3 = NULL;if (sqlite3_prepare_v2(conn,dropSQL,strlen(dropSQL),&stmt3,NULL) != SQLITE_OK) {if (stmt3)sqlite3_finalize(stmt3);sqlite3_close(conn);return;}if (sqlite3_step(stmt3) == SQLITE_DONE) {printf("The test table has been dropped.\n");}sqlite3_finalize(stmt3);sqlite3_close(conn);}int main(){doTest();return 0;}//输出结果如下://Succeed to create test table now.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//Insert Succeed.//The test table has been dropped.

三、sqlite3语句

目录:
1、sqlite3 基础语句
2、sqlite3 API
3、sqlite3 线程安全
 

1、基础语句:
学习sqlite3的基础在于SQL语句,开始前请输入$ sqlite3 验证你的电脑是否已经安装了sqlite3
首先我们需要创建一个数据库文件,打开终端,在合适的目录下,输入:

$ sqlite3 studyDB.db

.database


1.1、创建表
以id为主键并自动增加,创建一个名为book的表:
create table if not exists book (id integer primary key autoincrement, bookNumber integer, bookName text, authorID integer, pressName text);

输入如下命令查看数据表是否创建成功:
.tables

1.2、insert
有了数据表以后,就可以愉快地写SQL语句来测试了,先从基础的增删改查开始:
输入如下指令,向数据表中插入一条记录:
insert into book (bookNumber, bookName, authorID, pressName) values (1001, '三国演义', 10, '长江出版社');

1.3、select
然后做查询操作,看上一条记录是否插入成功:
select * from book;
然而在终端练习SQL语句,看起来并不那么清晰,所以接下来我们用一个可视化工具MesaSQLite来练习SQL语句,解压之后请阅读Serial.txt,如图:


安装MesaSQLite
接下来我们多插入几条记录,以便演示操作:
insert into book (bookNumber, bookName, authorID, pressName) values (1002, '水浒传', 11, '黄河出版社');
insert into book (bookNumber, bookName, authorID, pressName) values (1003, '西游记', 12, '长沙出版社');
insert into book (bookNumber, bookName, authorID, pressName) values (1004, '红楼梦', 13, '武汉出版社');
insert into book (bookNumber, bookName, authorID, pressName) values (1005, '琅琊榜', 14, '黄河出版社');
insert into book (bookNumber, bookName, authorID, pressName) values (1006, '伪装者', 15, '长江出版社');
insert into book (bookNumber, bookName, authorID, pressName) values (1007, '简爱', 16, '长江出版社');
insert into book (bookNumber, bookName, authorID, pressName) values (1008, '大主宰', 14, '武汉出版社');
执行以上SQL语句以后,我们的数据表中的数据应该是这样的:
book

1.4、where
条件语句where,查询bookNumber为1003的记录:
select * from book where bookNumber = 1003;


1.5、update
修改bookNumber为1002的记录,然后查询所有记录:
update book set pressName = '清华大学出版社' where bookNumber = 1002;
select * from book;


1.6、delete
删除红楼梦,然后查询:
delete from book where bookName = '红楼梦';
select * from book;


1.7、and
逻辑运算符and,查询pressName为黄河出版社,并且authorID为14的记录:
select * from book where pressName = '黄河出版社' and authorID = 14;


1.8、or
逻辑运算符or,查询authorID为14,或者pressName为长江出版社的记录:
select* from book where pressName= '长江出版社' or authorID = 14;


1.9、like
模糊查询指令like:
select * from book where pressName like '长%';
select * from book where authorID like '_4';


1.10、in
查询authorID为14或16的记录:
select * from book where authorID in (14, 16);


1.11、not in
查询pressName不为“长江出版社”的记录:
select * from book where pressName not in ('长江出版社');


1.12、between
查询authorID在14到20之间的记录:
select * from book where authorID between 14 and 20;


1.13、count
查询pressName为“长江出版社”的记录条数:
select count(pressName) from book where pressName = '长江出版社';


为了方便演示,我们创建另一个数据表author:
create table if not exists author (id integer primary key autoincrement, authorName text, authorID integer, age integer);

执行如下SQL语句:

insert into author (authorName, authorID, age) values ('jack', 21, 45);
insert into author (authorName, authorID, age) values ('dave', 10, 33);
insert into author (authorName, authorID, age) values ('rose', 14, 24);
insert into author (authorName, authorID, age) values ('jim', 16, 56);
insert into author (authorName, authorID, age) values ('ivan', 13, 22);
最后我们的author表中的数据应该是这样的:
author

1.14、sum
查询所有作者的年龄总和:
select sum(age) from author;

1.15、avg
查询作者的平均年龄:
select avg(age) from author;


1.16、max
查询最大的作者年龄:
select max(age) from author;


1.17、min
查询最小的作者年龄:
select min(age) from author;


1.18、order by
查询所有的记录,并按年龄升序排列:
select * from author order by age asc;
查询所有的记录,并按年龄降序排列:
select * from author order by age desc;


1.19、语句嵌套
查询年龄小于平均年龄的记录:
select * from author where age < (select avg(age) from author);


1.20、多表联合查询
查询年龄小于平均年龄的作者姓名、图书名、出版社:
select author.authorName, book.bookName, book.pressName from author, book where author.authorID = book.authorID and age< (select avg(age) from author);


2、sqlite3 API
要使用sqlite3 API,需要导入libsqlite3.tbd,然后#import  就可以使用sqlite了。
使用的过程根据使用的函数大致分为如下几个过程:
sqlite3_open()
sqlite3_prepare()
sqlite3_step()
sqlite3_column()
sqlite3_finalize()
sqlite3_close()
这几个过程是概念上的说法,而不完全是程序运行的过程,如sqlite3_column()表示的是对查询获得一行里面的数据的列的各个操作统称,实际上在sqlite中并不存在这个函数。

2.1、sqlite3_open
函数定义:
SQLITE_API int SQLITE_STDCALL sqlite3_open(
const char *filename,        /* Database filename (UTF-8) */
sqlite3 **ppDb                 /* OUT: SQLite db handle */
);

在操作数据库之前,首先要打开数据库。这个函数打开一个sqlite数据库文件,并且返回一个数据库连接对象。假如这个要被打开的数据文件不存在,则一个同名的数据库文件将被创建。如果使用sqlite3_open和sqlite3_open_v2的话,数据库将采用UTF-8的编码方式,sqlite3_open16采用UTF-16的编码方式。如果sqlite数据库被成功打开(或创建),将会返回SQLITE_OK,否则将会返回错误码。
filename:需要被打开的数据库文件的文件名,在sqlite3_open和sqlite3_open_v2中这个参数采用UTF-8编码,而在sqlite3_open16中则采用UTF-16编码。
ppDb:一个数据库连接句柄被返回到这个参数,即使发生错误。唯一的异常是如果sqlite不能分配内存来存放sqlite对象,ppDb将会被返回一个NULL值。

2.2、sqlite3_prepare
函数定义:
SQLITE_API int SQLITE_STDCALL sqlite3_prepare(
sqlite3 *db,                       /* Database handle */
const char *zSql,               /* SQL statement, UTF-8 encoded */
int nByte,                          /* Maximum length of zSql in bytes. */
sqlite3_stmt **ppStmt,      /* OUT: Statement handle */
const char **pzTail           /* OUT: Pointer to unused portion of zSql */
);

这个函数将sql文本转换成一个准备语句(prepared statement)对象,同时返回语句对象的句柄。这个接口需要一个数据库连接指针以及一个要准备的包含SQL语句的文本。它实际上并不执行(evaluate)这个SQL语句,它仅仅为执行准备这个sql语句。
db:数据库连接指针。
zSql:sql语句,使用UTF-8编码。
nByte:如果nByte小于0,则函数取出zSql中从开始到第一个0终止符的内容;如果nByte不是负的,那么它就是这个函数能从zSql中读取的字节数的最大值。如果nBytes非负,zSql在第一次遇见‘/000/’或‘u000’的时候终止。
pzTail:上面提到zSql在遇见终止符或者是达到设定的nByte之后结束,假如zSql还有剩余的内容,那么这些剩余的内容被存放到pZTail中,不包括终止符。
ppStmt:能够使用sqlite3_step()执行的编译好的准备语句的指针,如果错误发生,它被置为NULL,如假如输入的文本不包括sql语句。调用过程必须负责在编译好的sql语句完成使用后使用sqlite3_finalize()删除它。

2.3、sqlite3_step
函数定义:
SQLITE_API int SQLITE_STDCALL sqlite3_step(sqlite3_stmt*);
这个过程用于执行有前面sqlite3_prepare创建的准备语句。这个语句执行到结果的第一行可用的位置。继续前进到结果的第二行的话,只需再次调用sqlite3_setp()。继续调用sqlite3_setp()直到这个语句完成,那些不返回结果的语句(如:INSERT,UPDATE,或DELETE),sqlite3_step()只执行一次就返回。

2.4、sqlite3_column
函数定义:
SQLITE_API const void* SQLITE_STDCALL sqlite3_column_blob(sqlite3_stmt*,intiCol);
SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes(sqlite3_stmt*,intiCol);
SQLITE_API int SQLITE_STDCALL sqlite3_column_bytes16(sqlite3_stmt*,intiCol);
SQLITE_API double SQLITE_STDCALL sqlite3_column_double(sqlite3_stmt*,intiCol);
SQLITE_API int SQLITE_STDCALL sqlite3_column_int(sqlite3_stmt*,intiCol);
SQLITE_API sqlite3_int64 SQLITE_STDCALL sqlite3_column_int64(sqlite3_stmt*,intiCol);
SQLITE_API const unsigned char* SQLITE_STDCALL sqlite3_column_text(sqlite3_stmt*,intiCol);
SQLITE_API const void* SQLITE_STDCALL sqlite3_column_text16(sqlite3_stmt*,intiCol);
SQLITE_API int SQLITE_STDCALL sqlite3_column_type(sqlite3_stmt*,intiCol);
SQLITE_API sqlite3_value* SQLITE_STDCALL sqlite3_column_value(sqlite3_stmt*,intiCol);

这个过程从执行sqlite3_step()执行一个准备语句得到的结果集的当前行中返回一个列。每次sqlite3_step得到一个结果集的列停下后,这个过程就可以被多次调用去查询这个行的各列的值。对列操作是有多个函数,均以sqlite3_column为前缀。

第一个参数为从sqlite3_prepare返回来的prepared statement对象的指针。
第二参数指定这一行中的想要被返回的列的索引。最左边的一列的索引号是0,行的列数可以使用sqlite3_colum_count()获得。


2.5、sqlite3_finalize
函数定义:
SQLITE_API int SQLITE_STDCALL sqlite3_finalize(sqlite3_stmt *pStmt);
这个过程销毁前面被sqlite3_prepare创建的准备语句,每个准备语句都必须使用这个函数去销毁以防止内存泄露。


2.6、sqlite3_exec
函数定义:
SQLITE_API int SQLITE_STDCALL sqlite3_exec
(
sqlite3*,                                                            /* An open database */
const char*sql,                                                  /* SQL to be evaluated */
int(*callback)(void*,int,char**,char**),                /* Callback function */
void*,                                                                /* 1st argument to callback */
char **errmsg                                                    /* Error msg written here */
);

sqlite3_step 和 sqlite3_exec 都可以用于执行SQL语句,他们的区别在于后者是sqlite3_prepare()、sqlite3_step() 和 sqlite3_finalize() 的封装,能让程序多次执行sql语句而不要写许多重复的代码,然后提供一个回调函数进行结果的处理。
第1个参数:数据库连接指针。
第2个参数:是一条sql语句。
第3个参数:是一个函数指针,当这条语句执行之后,sqlite3会去调用你提供的这个函数。
第4个参数:是你所提供的指针,你可以传递任何一个指针参数到这里,这个参数最终会传到回调函数里面,如果不需要传递指针给回调函数,可以填NULL。等下我们再看回调函数的写法,以及这个参数的使用。
第5个参数:是错误信息。注意是指针的指针。sqlite3里面有很多固定的错误信息。执行sqlite3_exec 之后,执行失败时可以查阅这个指针。


2.7、sqlite3_close
函数定义
SQLITE_API int SQLITE_STDCALL sqlite3_close(sqlite3*);
这个过程用于关闭数据库
代码演示:
NSString *select_stmt = [NSString stringWithFormat:
@"select * from people where name = \"%@\"",
self.name.text
];

sqlite3_stmt *stmt;
sqlite3_prepare(db,select_stmt.UTF8String,-1,&stmt,NULL);
while(sqlite3_step(stmt) ==SQLITE_ROW) 
{
NSString*name = [NSString stringWithUTF8String:(const char*)sqlite3_column_text(stmt,1)];
NSString*address = [NSString stringWithUTF8String:(const char*)sqlite3_column_text(stmt,2)];
NSString*age = [NSString stringWithUTF8String:(const char*)sqlite3_column_text(stmt,3)];
self.name.text= name;
self.address.text= address;
self.age.text= age;
}

sqlite3_finalize(stmt);
sqlite3_close(db);
完整的SQLite3 API 代码戳这里


3、线程安全
在iOS开发时,为了不阻塞主线程,数据库访问必须移到子线程中。从3.3.1版本开始,SQLite就是线程安全的了。而iOS的SQLite版本没有低于这个版本的:
3.4.0 - iPhone OS 2.2.1
3.6.12 - iPhone OS 3.0 / 3.1
3.6.22 - iPhone OS 4.0
3.6.23.2 - iPhone OS 4.1 / 4.2
3.7.2 - iPhone OS 4.3
3.7.7 - iPhone OS 5.0

SQLite支持3种线程模式:

单线程:禁用所有的mutex锁,并发使用时会出错。当SQLite编译时加了SQLITE_THREADSAFE=0参数,或者在初始化SQLite前调用sqlite3_config(SQLITE_CONFIG_SINGLETHREAD)时启用。

多线程:只要一个数据库连接不被多个线程同时使用就是安全的。源码中是启用bCoreMutex,禁用bFullMutex。实际上就是禁用数据库连接和prepared statement(准备好的语句)上的锁,因此不能在多个线程中并发使用同一个数据库连接或prepared statement。当SQLite编译时加了SQLITE_THREADSAFE=2参数时默认启用。若SQLITE_THREADSAFE不为0,可以在初始化SQLite前,调用sqlite3_config(SQLITE_CONFIG_MULTITHREAD)启用;或者在创建数据库连接时,设置SQLITE_OPEN_NOMUTEX flag。

串行:启用所有的锁,包括bCoreMutex和bFullMutex。因为数据库连接和prepared statement都已加锁,所以多线程使用这些对象时没法并发,也就变成串行了。当SQLite编译时加了SQLITE_THREADSAFE=1参数时默认启用。若SQLITE_THREADSAFE不为0,可以在初始化SQLite前,调用sqlite3_config(SQLITE_CONFIG_SERIALIZED)启用;或者在创建数据库连接时,设置SQLITE_OPEN_FULLMUTEX flag。

而这里所说的初始化是指调用sqlite3_initialize()函数,这个函数在调用sqlite3_open()时会自动调用,且只有第一次调用是有效的。

另一个要说明的是prepared statement,它是由数据库连接(的pager)来管理的,使用它也可看成使用这个数据库连接。因此在多线程模式下,并发对同一个数据库连接调用sqlite3_prepare_v2()来创建prepared statement,或者对同一个数据库连接的任何prepared statement并发调用sqlite3_bind_*()和sqlite3_step()等函数都会出错(在iOS上,该线程会出现EXC_BAD_ACCESS而中止)。这种错误无关读写,就是只读也会出错。安全使用规则是:没有事务正在等待执行的话,所有prepared statement都要被finalized。
调用sqlite3_threadsafe()可以获得编译期的SQLITE_THREADSAFE参数。标准发行版是1,也就是串行模式;而iOS上是2,也就是多线程模式。
一段存在线程安全隐患的代码:


iOS上的SQLite默认是多线程模式,多个线程同时使用同一个数据库连接对象,将会产生异常,解决办法的一种就是在sqlite3_open前加上sqlite3_config(SQLITE_CONFIG_SERIALIZED)。

sqlite新建表时设置某个字段自增的问题

create table if not exists checkInfo1("ID" INTEGER PRIMARY KEY AUTOINCREMENT,"FileName" VARCHAR(500),"FilePath" VARCHAR(500))

注意:分析 "ID"   INTEGER PRIMARY KEY AUTOINCREMENT

INTEGER -不能设置长度限制,只能放一个独立的INTEGER ,比如设置INTEGER(10),是错误的 

PRIMARY KEY--主键

AUTOINCREMENT--自增

设置 PRIMARY KEY autoincrement 属性的字段类型必须为 integer 


http://www.ngui.cc/article/show-862306.html

相关文章

DPDK开发之KNI与内核交互的代码实现

KNI与内核交互的代码实现背景环境配置代码实现编译和执行总结背景 DPDK接管NIC之后&#xff0c;网卡接收到的网络数据都交由DPDK处理&#xff0c;但在开发过程中&#xff0c;我们可能只关注其中某一个协议&#xff0c;而其他协议并不需要我们处理&#xff1b;DPDK提供了KNI模块…

《NFL橄榄球》:纽约喷气机·橄榄1号位

纽约喷气机 纽约喷气机 成立于1960年所在地新泽西州东拉瑟福德 联赛/联盟隶属关系 美国美式橄榄球联盟&#xff08;1960&#xff0d;1969年&#xff09; Eastern Division&#xff08;1960&#xff0d;1969年&#xff09;全国橄榄球联盟 (1970–至今) 美国橄榄球联合会&am…

【苹果iMessage家庭推日历推群发】服务器与 APNs 之间pushchatkey.pem和pushchatcert.pem作为单独的文件使用

推荐内容IMESSGAE相关 作者✈️IMEAX推荐内容iMessage苹果推软件 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容1.家庭推内容 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容2.相册推 *** 点击即可查看作者要求内容信息作者✈️IMEAX推荐内容3.日历推 *** …

快速排序和归并排序哪个快?

两个排序的基本思想都是分治&#xff08;分而治之&#xff09;,实现一般都使用递归实现。1.快速排序双边指针&#xff08;交换法&#xff09;&#xff1a;记录分界值 &#xff0c;创建左右指针&#xff08;记录下标&#xff09;。以第一个元素为分界值&#xff0c;先从右向左找…

arm32栈回溯原理学习以及示例代码

arm32栈回溯原理学习栈回溯原理缺点简单介绍下传统栈回溯原理&#xff0c;方便理解。栈回溯原理 如上图所示&#xff0c;是一个传统的arm架构下函数栈数据分布&#xff0c;需要编译选项-fno-omit-fram-pointer -mapcs -mno-sched-prolog 函数进入时&#xff0c;首先会 mov ip…

go如何实现服务优雅关闭

为什么需要优雅关闭 什么叫优雅关闭&#xff1f;先说不优雅关闭&#xff0c;就是什么都不管&#xff0c;强制关闭进程&#xff0c;这会导致有些正在处理中的请求被强行中断 这样做有什么问题&#xff1f; 用户本次请求会失败&#xff0c;降低用户体验没有事务的数据库操作&a…

详解python之反射机制

一、前言 def f1():print(f1)def f2():print(f2)def f3():print(f3)def f4():print(f4)a 1 import test as ssss.f1() ss.f2() print(ss.a) 我们要导入另外一个模块,可以使用import.现在有这样的需求,我动态输入一个模块名&#xff0c;可以随时访问到导入模块中的方法或者变…

【c++】STL教程

文章目录学习链接1. list 代码测试2. stack 代码测试3. queue 代码测试3.1 priority_queue 优先队列&#xff0c;最大先出3.2 改变出队优先级4. deque 代码测试5. vector 代码测试6. set 代码测试7. map 代码测试7.1 multimap 一键对多值8. sort 代码测试9.反转和随机代码测试学…

2022黑马SpringBoot跟学笔记(一)

2022黑马SpringBoot跟学笔记一SpringBoot1.SpringBoot简介1.1 SpringBoot快速入门1.1.1 开发步骤1.1.1.1 创建新模块1.1.1.2 创建 Controller1.1.1.3 启动服务器1.1.1.4 进行测试1.1.2 对比1.1.3 官网构建工程1.1.3.1 进入SpringBoot官网1.1.3.2 选择依赖1.1.3.3 生成工程1.1.4…

【Java】int和Integer的区别?为什么有包装类?

int和Integer的区别&#xff1f;为什么有包装类&#xff1f; java是一种强类型的语言&#xff0c;所以所有的属性都必须要有一个数据类型。 PS&#xff1a;java10有了局部变量类型推导&#xff0c;可以使用var来代替某个具体的数据类型&#xff0c;但是在字节码阶段&#xff0…