最近几天整了下Sqlite3,也就是iOS的另外一种储存方式,那么coreData是有什么不足么,不是,一般数据比较简易的时候是不会用coreData的,反而会用自身的sqlte3来实现本地的存储,这就需要用到了点SQL语句了,一般都会用第三方FMDB(第三方库)来简化使用,但第三方的应用是下一次博客的事情了,这次用自带的sqlite3来实现一个简易的通讯录,能够实现保存,并完成增删改查即可,毕竟是最基础的嘛,用第三方虽然简单,但是底层的基础还是要回的。首先看一下如何导入自身的sqlite3的库吧

如果细看这个图,那么也就知道,这个demo是按照MVC模式写的,既然是MVC模式写的,那么必然就会出现Model(模型类),View(视图类)以及Controller(控制器类)

这次的页面没有进行细作,只是为了能够了解Sqlite3的用法,首先就是模型类,看看数据库结构

上面是一个第三方工具,虽然能够快速建立表,但是作为程序员,不建议用它直接建表,建议用sql语句,毕竟还能回顾以及锻炼自己的SQL语句嘛,岂不是一举两得,一般工具是用来查看数据库的。

这里的布局非常简单,只是为了介绍sqlite3的用法,如果想要比较好的视觉可以去之前的博客iOS学习-------简单通讯录(UITableView和CoreData的应用),其他的都是一样的,只是存储的方法变了而已

Model(模型)

这个Model的作用就是存储数据库中表的信息即可,属性如下
//
//  Humen.h
//  Sqilte3
//
//  Created by YueWen on 15/10/6.
//  Copyright (c) 2015年 YueWen. All rights reserved.
//#import <Foundation/Foundation.h>@interface Humen : NSObject@property(nonatomic,strong)NSString * name;//姓名@property(nonatomic,assign)NSInteger age;//年龄@property(nonatomic,strong)NSString * tele;//电话@property(nonatomic,strong)NSString * address;//地址@property(nonatomic,assign)NSInteger humenId;//id/***  便利初始化方法**  @param name    初始name*  @param age     初始age*  @param tele    初始tele*  @param address 初始地址**  @return 初始化好的Humen对象*/
-(instancetype)initWithName:(NSString *)name Age:(NSInteger)age Tele:(NSString *)tele Address:(NSString *)address;

实现便利初始化方法

-(instancetype)initWithName:(NSString *)name Age:(NSInteger)age Tele:(NSString *)tele Address:(NSString *)address
{if (self = [super init]){self.name = name;//初始化nameself.age = age;//初始化ageself.tele = tele;//初始化teleself.address = address;//初始化address}return self;
}
接下来就是最重点的Sqlite3Manager(数据库管理员),首先他要有增删改查的功能,所以定义四个方法
/***  增加人**  @param humen 需要增加的Humen模型*/
-(void)addHumenToSqlite:(Humen *)humen;/***  更新数据**  @param humen 需要更新的Humen模型*/
-(void)updateHumenFromSqlite:(Humen *)humen withIndex:(NSInteger)index;/***  根据下标即id删除**  @param index 当前的id*/
-(void)deleteHumenFromSqlite:(NSInteger)index;/***  根据姓名查询名字**  @param name 查询的名字*/
-(void)selectHumenFromWithName:(NSString *)name;

其次,Manager一般都是单例,这次也不例外

/***  单例方法**  @return 返回单例*/
+(instancetype)shareSqlite3Manager;

此外,还应该有一个方法能够加载所有的数据,即刷新类似功能的时候,从新从数据库中加载一般数据

/***  加载数据库中所有的数据*/
-(void)loadMHumen;

如果外界想获得所有的数据,那么必然会有一个对外开放的属性,但是外界是不能更改的,所以是readOnly的属性

/***  存储数据库中的数据数组*/
@property(nonatomic,strong,readonly)NSArray * humen;

然后是最麻烦的实现方法,先从最简单的东西开始,不要忘记导入自带的sqlite3的头文件

#import <sqlite3.h>

延展中的属性,其中之一必须有导入sqlite3的对象,第二个就是可变数组,用来存储处理好的对象,也就是存储将字典处理好Humen对象,延展如下

@interface Sqlite3Manager ()@property(nonatomic)sqlite3 * humendate;//不要加strong@property(nonatomic,strong)NSMutableArray * mHumen;@end

接着实现重写init方法,以及单例方法,这个相信是再熟悉也不过了

- (instancetype)init
{self = [super init];if (self) {//初始化数据数组self.mHumen = [NSMutableArray array];//打开数据库[self openSqlite_db];//创建表[self creatSqlite_db];//加载数据[self loadMHumen];}return self;
}//单例方法
+(instancetype)shareSqlite3Manager
{static Sqlite3Manager * sqliteManager = nil;static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{sqliteManager = [[Sqlite3Manager alloc]init];});return sqliteManager;
}

再用这个苹果打包好的Sqlite3的时候,实时要注意objc对象和C语言类型的转换,因为SQL是用C语言进行操作的,不认NSString等对象,打开数据库如下

/***  打开数据库*/
-(void)openSqlite_db
{//获取沙盒目录NSString * path1 = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];//追加数据库名称NSString * path = [path1 stringByAppendingPathComponent:@"humen.db"];//打开数据库sqlite3_open([path UTF8String],&self->_humendate);//导入的包中的API,因为底层是C语言的东西,所以path需要转成C语言的字符串,后面传一个sqlite3指针的地址}

接下来就是创建本地的数据库了,用SQL的create table 语句,记得还是C语言的字符串

/***  创建本地的sqlite文件数据库,格式为db*/
-(void)creatSqlite_db
{//创建表的SQL语句,用C语言的字符串,如果用了NSString,记得转成C语言字符串即可char * createTableSQL = "create table if not exists t_humen (id integer primary key,name varchar(30),age integer,tele varchar(100),address varchar(100))";//执行,第一个参数是sqlite3对象指针,第二个是C语言类型的SQL语句,后面3个参数填NULL即可sqlite3_exec(self->_humendate, createTableSQL, NULL, NULL, NULL);}

加载数据是什么,就是select * table 语句,将所有的数据库的中的数据全部加载出来,并进行模型的转化

/***  加载数据*/
-(void)loadMHumen
{//清空之前的所有的数据[self.mHumen removeAllObjects];//访问数据库获取数据,根据id,默认为升序char * selectSQL = "select * from t_humen order by id";//创建sqlite3_stmt对象sqlite3_stmt * stmt;//准备加载/***  参数的意义:*  1:sqlite3的属性指针*  2:执行的sql语句*  3: 这里填的是搜说的字节数,-1表示全部*  4: 表示填入一个sqlite3_stmt的对象*/sqlite3_prepare_v2(self->_humendate, selectSQL, -1, &stmt, NULL);//循环加载while (sqlite3_step(stmt) ==SQLITE_ROW){//初始化一个Humen对象Humen * humen = [[Humen alloc] initWithName:[NSString stringWithUTF8String:(char *)(sqlite3_column_text(stmt, 1))]//第一列是名字Age:sqlite3_column_int(stmt, 2)//第二列是年龄Tele:[NSString stringWithUTF8String:(char *)(sqlite3_column_text(stmt, 3))]//第三列是电话Address:[NSString stringWithUTF8String:(char *)(sqlite3_column_text(stmt, 4))]];//第四列是地址humen.humenId = sqlite3_column_int(stmt, 0);//添加一个数组[self.mHumen addObject:humen];}sqlite3_finalize(stmt);//表示完成,回馈一下
}

如何完成增删改查呢,这里需要了解一个机制叫做预编译,如果不用预编译,那么输入的某些SQL语句中的特殊字符,比如#等,会造成一些不必要而且还很难解决的问题,最麻烦的就是数据库的数据的安全会很危险,所以下面的方法都是用的自带的预编译语句进行操作

首先是增加人,如何增加,直接传入一个Humen对象,其他的操作在方法里操作即可,用SQL语句中的insert into

/***  增加人**  @param humen 需要增加的Humen模型*/
-(void)addHumenToSqlite:(Humen *)humen
{//为数组添加数据[self.mHumen addObject:humen];//预编译语句char * insertSQL = "insert into t_humen values(NULL,?,?,?,?)";sqlite3_stmt * stmt;//准备sqlite3_prepare_v2(self->_humendate, insertSQL, -1, &stmt, NULL);//为stmt预编译语句绑定数据,从1开始sqlite3_bind_text(stmt, 1, [humen.name UTF8String], -1, NULL);//为name绑定sqlite3_bind_int(stmt, 2, (int)humen.age);//为age绑定sqlite3_bind_text(stmt, 3, [humen.tele UTF8String], -1, NULL);//为tele绑定sqlite3_bind_text(stmt, 4, [humen.address UTF8String], -1, NULL);//为address绑定//单步调试int rst = sqlite3_step(stmt);if (rst == SQLITE_DONE){NSLog(@"插入成功!");}else{NSLog(@"失败!编号为:%d",rst);}sqlite3_finalize(stmt);
}

删除对象,即需要知道数据库中属性的主键,在创建表的时候,primary key 表示主键,即 不能重复,就像人的身份证一样,唯一标识符,所以必须传入一个主键的数字,这个数字就是对象中的humen_id属性,因为加载的顺序就是按照主键顺序的

/***  删除指定id的人对象**  @param index 下标 + 1*/
-(void)deleteHumenFromSqlite:(NSInteger)index
{//获取当前的对象idNSInteger ID = ((Humen *)self.mHumen[index - 1]).humenId;[self.mHumen removeObjectAtIndex:index - 1];//从数据库中删除//预编译语句char * deleteSQL = "delete from t_humen where id=?";sqlite3_stmt * stmt;//准备sqlite3_prepare_v2(self.humendate, deleteSQL, -1, &stmt, NULL);//绑定数据sqlite3_bind_int(stmt, 1, (int)ID);//分布调试int rst = sqlite3_step(stmt);//如果完成if (rst == SQLITE_DONE){NSLog(@"删除成功!");}else{NSLog(@"删除失败!");}sqlite3_finalize(stmt);
}

更新对象,需要知道一个修改后的对象以及修改对象的id方便在数据库中进行修改,用update table语句即可

/***  更新数据**  @param humen 需要更新的Humen模型*/
-(void)updateHumenFromSqlite:(Humen *)humen withIndex:(NSInteger)index
{//获取当前的对象的IDNSInteger ID = ((Humen *)self.humen[index - 1]).humenId;//数组替换[self.mHumen replaceObjectAtIndex:index - 1 withObject:humen];//预编译语句char * updateSQL = "update t_humen set name=?,age=?,tele=?,address=? where id=?";sqlite3_stmt * stmt;//准备sqlite3_prepare_v2(self.humendate, updateSQL, -1, &stmt, NULL);//绑定参数数据sqlite3_bind_text(stmt, 1, [humen.name UTF8String], -1, NULL);sqlite3_bind_int(stmt, 2, (int)humen.age);sqlite3_bind_text(stmt, 3, [humen.tele UTF8String], -1, NULL);sqlite3_bind_text(stmt, 4, [humen.address UTF8String], -1, NULL);sqlite3_bind_int(stmt, 5, (int)ID);//分部调试int rst = sqlite3_step(stmt);//如果更新完成if (rst == SQLITE_DONE){NSLog(@"更新成功!");}else{NSLog(@"失败!%d",rst);}sqlite3_finalize(stmt);
}

最后一个功能就是查,查的话在这个方案里是不需要再对数据库进行操作的,因为增删改的时候对本地的数组进行了操作,所以本地的数组存的数据就是实时的数据库里存的对象,整个工程只需要在初始化的时候对数据库进行一次加载即可,查询如下

/***  根据姓名查询名字**  @param name 查询的名字*/
-(void)selectHumenFromWithName:(NSString *)name
{//可变数组存储符合条件的对象NSMutableArray * mutableHumen = [NSMutableArray array];//只需在数组中查询即可,不需要在执行数据库for (Humen * humen in self.mHumen){if ([humen.name isEqualToString:name]){//添加到数组[mutableHumen addObject:humen];}}//改变当前的数组self.mHumen = mutableHumen;
}

既然打开了数据库,那么必然存在关闭数据库,这个demo的思路是在创建的时候打开数据库,直到程序结束的时候才会关闭,所以必然有一个关闭数据库的方法

/***  关闭数据库*/
-(void)closeSqlite_db
{sqlite3_close(self->_humendate);
}

重写dealloc中的方法,说到dealloc中的方法,在ARC环境下,是不需要写[super dealloc]的,写上也会报错

/***  消除对象的时候关闭数据库*/
-(void)dealloc
{//关闭数据库[self closeSqlite_db];
}

由此,Sqlite3Manager就完成了

View(视图)

页面的布局依旧选择了storyboard,简单布局如下,root视图控制器为一个导航控制器,连接着能够显示的cell,后面的页面负责添加以及更新

Controller(控制器)

首先创建一个显示信息的主页面,为一个tableViewController,取名为:HumenTableViewController
延展中只需定义一个sqlite3Manager即可
@interface HumenTableViewController ()@property(nonatomic,strong)Sqlite3Manager * sqliteManager;@end

加载viewDidLoad

- (void)viewDidLoad {[super viewDidLoad];//初始化单例self.sqliteManager = [Sqlite3Manager shareSqlite3Manager];//添加导航栏的右按钮,回调方法为 toAddViewControllerself.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(toAddViewController)];//添加导航栏的左按钮,回调方法为 toSearchViewControllerself.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(toSearchViewController)];
}

这里需要一个方法,叫做viewWillApear,比如在当前页面即将展现的时候触发的,需要刷新一下列表,比如更新,添加页面,调回的时候,需要刷新一下列表

//页面将要跳回动画结束时
-(void)viewWillAppear:(BOOL)animated
{//重新加载数据库中的所有数据[self.sqliteManager loadMHumen];//刷新列表[self.tableView reloadData];
}

为添加按钮设置回调方法,只负责跳转一个页面即可

#pragma mark - barButton target action
-(void)toAddViewController
{//获取storyboardUIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];//根据storyboard创建控制对象UIViewController * viewController = [storyboard instantiateViewControllerWithIdentifier:@"AddViewController"];//跳转界面[self.navigationController pushViewController:viewController animated:YES];
}

为搜索按钮设置一个回调方法,这里不再进行页面跳转,只需弹出一个alertController即可,详细可以去之前的博客iOS学习-------UIAlertController(弹出视图控制器)

-(void)toSearchViewController
{//准备alertControllerUIAlertController * alert = [UIAlertController alertControllerWithTitle:@"搜索" message:@"请填入搜索的名字" preferredStyle:UIAlertControllerStyleAlert];//添加文本框[alert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {}];//添加搜索的动作按钮[alert addAction:[UIAlertAction actionWithTitle:@"搜索" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {//获取搜索的名字NSString * name = alert.textFields[0].text;//在数据库对象中进行搜索[self.sqliteManager selectHumenFromWithName:name];//刷新列表[self.tableView reloadData];//跳回[self dismissViewControllerAnimated:YES completion:nil];}]];//添加取消的动作按钮[alert addAction:[UIAlertAction actionWithTitle:@"取消" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {//跳回[self dismissViewControllerAnimated:YES completion:nil];}]];//跳出[self presentViewController:alert animated:YES completion:nil];}

效果如下:

接下来是最为熟悉的三个数据源代理方法,不做赘余
#pragma mark - Table view data source
/***  返回组数,分组的情况下,因为不分组,所以返回1**  @param tableView 当前的tableView**  @return 组数*/
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {return 1;
}/***  组的行数**  @param tableView 当前的tableView*  @param section   组的个数**  @return 返回每部分的行数*/
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {return self.sqliteManager.humen.count;}/***  cell显示的内容**  @param tableView 当前的tableView*  @param indexPath 当前的位置**  @return 打包好的cell*/
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {//获取模型Humen * humen = self.sqliteManager.humen[indexPath.row];//创建表格cellUITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath];//简单赋值cell.textLabel.text = humen.name;return cell;
}

因为修改界面是通过点击当前的行来跳入的,因为需要实现如下代理方法

/***  点击行的时候**  @param tableView 当前的tableView*  @param indexPath 当前的位置*/
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{//获取当前的模型Humen * humen = self.sqliteManager.humen[indexPath.row];//获得当前的storyboardUIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]];//获得跳转的ViewControllerUIViewController * vc = [storyboard instantiateViewControllerWithIdentifier:@"AddViewController"];//KVC赋值[vc setValue:humen forKey:@"humen"];[vc setValue:indexPath forKey:@"index"];//跳转[self.navigationController pushViewController:vc animated:YES];}

增删改查已经完成了增,改,查的页面,删除如何呢,想点击按住一拖进行删除来完成,那么需要完成两个代理方法

首先将可以编辑设置为YES,默认为NO
/***  列表视图能够编辑**  @param tableView 当前的tableView*  @param indexPath 当前的位置**  @return YES表示可以编辑  NO表示不可以编辑*/
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {return YES;
}

接着实现编辑模式下的不同编辑风格进行的操作,因为只完成删除,因此只完成了删除操作

/***  当前的编辑模式进行相应的操作**  @param tableView    当前的tableView*  @param editingStyle 编辑的风格*  @param indexPath    当前的位置*/
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {if (editingStyle == UITableViewCellEditingStyleDelete) {//从数据库中删除当前的对象[self.sqliteManager deleteHumenFromSqlite:indexPath.row + 1];[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];} else if (editingStyle == UITableViewCellEditingStyleInsert) {// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view}
}

删除展示图如下

接着完成添加用户以及更新用户页面,因为页面相似,所以不想再做两个相同页面,只需一个属性,即Humen,判断依据是,只有更新的时候他才会有值,添加的时候是nil,延展如下
#import "AddViewController.h"
#import "Sqlite3Manager.h"
#import "Humen.h"@interface AddViewController ()@property (strong, nonatomic) IBOutlet UITextField *nameText;
@property (strong, nonatomic) IBOutlet UITextField *ageText;
@property (strong, nonatomic) IBOutlet UITextField *teleText;
@property (strong, nonatomic) IBOutlet UITextField *addressText;@property (nonatomic,strong) Sqlite3Manager * sqliteManager;//更新时专用
@property(strong,nonatomic)Humen * humen;
@property(nonatomic,strong)NSIndexPath * index;@end

实现viewDidLoad,根据属性中的human的有无来判断进入何种页面

- (void)viewDidLoad {[super viewDidLoad];//实例化单例属性self.sqliteManager = [Sqlite3Manager shareSqlite3Manager];//设置导航的右上角按钮self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemDone target:self action:@selector(addHumenToSqliteFromAddViewController)];//表示代表更新视图if (self.humen){self.navigationItem.title = @"更新视图";//赋值self.nameText.text = self.humen.name;self.ageText.text = [NSString stringWithFormat:@"%ld",self.humen.age];self.teleText.text = self.humen.tele;self.addressText.text = self.humen.address;}else{self.navigationItem.title = @"添加视图";}
}
点击完成的时候,也是需要判断是添加还是更新的,毕竟SQL语句是不一样的,因此,回调方法如下
-(void)addHumenToSqliteFromAddViewController
{//初始化humen实例Humen * humen = [[Humen alloc]initWithName:self.nameText.text Age:[self.ageText.text intValue] Tele:self.teleText.text Address:self.addressText.text];//表示更新if (self.humen){//更新到数据库[self.sqliteManager updateHumenFromSqlite:humen withIndex:self.index.row + 1];}//表示添加else{//添加到数据库sqlite[self.sqliteManager addHumenToSqlite:humen];}//调回原页面[self.navigationController popViewControllerAnimated:YES];}
这样基本的增删改查就已经完成了,看一下效果吧
增加效果:
更新效果:
查找效果:
删除效果:
再次打开模拟器,还是可以保存的,如下

iOS开发-------Sqlite3实现本地存储简易通讯录相关推荐

  1. iOS开发UI篇—实现一个私人通讯录小应用(一)

    iOS开发UI篇-实现一个私人通讯录小应用(一) 一.该部分主要完成内容 1.界面搭建                        2.功能说明 (1).只有当账号和密码输入框都有值的时候,登录按钮 ...

  2. iOS查看手机app本地存储的文件

    审计过程中,经常会检查程序是否在本地存储了一些敏感文件,可以通过以下方法来查看手机上本地存储的文件: 1.选择xcode上面的window下面的Devices 2.先在左边选中你当前的设备,然后在右下 ...

  3. [转]苹果iOS 5限制应用本地存储问题

    参考:http://www.williamlong.info/archives/2865.html 苹果 iOS 5 系统增加了一个新的机制--在设备容量空间不足的情况下自动清除高速缓存文件或临时目录 ...

  4. [ios2]苹果iOS 5限制应用本地存储问题 【转】

    苹果 iOS 5 系统增加了一个新的机制--在设备容量空间不足的情况下自动清除高速缓存文件或临时目录的内容.这意味着,如果你设备的容量快到极限了,应用存储的很多离线内容,包括文章.杂志.图书.漫画以及 ...

  5. 苹果iOS 5限制应用本地存储问题

    苹果 iOS 5 系统增加了一个新的机制--在设备容量空间不足的情况下自动清除高速缓存文件或临时目录的内容.这意味着,如果你设备的容量快到极限了,应用存储的很多离线内容,包括文章.杂志.图书.漫画以及 ...

  6. (0080)iOS开发之上传本地项目到github

    转载自:http://www.cocoachina.com/ios/20160212/15024.html 半小时学会上传本地项目到github 一.注册github账号 首先需要注册一个github ...

  7. iOS开发UI篇—实现一个私人通讯录小应用(二)

    一.实现功能说明 (1)点击注销按钮,弹出一个对话框,点击确定后移除当前栈顶的控制器,返回开始界面,点击取消,不做任何操作. 注意:注销按钮的单击事件已经进行了连线.实现-(void)actionSh ...

  8. 学习ios(必看经典)牛人40天精通iOS开发的学习方法

     亲爱的学员们: 如今,各路开发者为淘一桶金也纷纷转入iOS开发的行列.你心动了吗?想要行动吗?知道如何做嘛?速来学习由51CTO学院整理的iOS精品视频集,42个视频课程一步步引领你成为一名iO ...

  9. 学习ios牛人40天精通iOS开发的学习方法

    学习ios(必看经典)牛人40天精通iOS开发的学习方法 描述 这是一套从一个对iOS开发感兴趣的学员到iOS开发高手的系统.专业的课程体系.以培养企业开发真正需要的人才为目标,每个知识点都用案例来讲 ...

最新文章

  1. 对象特性---->深拷贝与浅拷贝
  2. 【iOS与EV3混合机器人编程一系列五个】iOS_WiFi_EV3_Library 解剖连接EV3
  3. mllib调参 spark_从Spark MLlib到美图机器学习框架实践
  4. XCTF-高手进阶区:Training-WWW-Robots
  5. QNX下挂载USB设备
  6. Diccuz!NT的dll版本号控制技巧
  7. 小鱼易连全系新品正式发布 引爆音视频会议行业核聚变
  8. 游戏开发之多态及虚函数(C++基础)
  9. CVPR2020-深度图超分辨率DSR新方法| Channel Attention based Iterative Residual Learning for Depth Map SR
  10. [数论]JZOJ 5946 时空幻境
  11. origin 画热图
  12. C语言:链表(动态)创建之头插法和尾插法
  13. 【北大】计算机课程资料
  14. C. Equalize
  15. linux peek,Peek - Gif 录制软件
  16. vscode Trace/breakpoint trap 问题
  17. java技术交流群532101200
  18. 入侵FBI(www.fbi.gov)核心网络全过程
  19. 数据库配置口令复杂度策略和口令有效期策略
  20. HashMap 中 hash 冲突的解决方法及原理分析

热门文章

  1. OpenGL 视差贴图 Parallax Mapping
  2. listview条目有下划线
  3. Ehab and Path-etic MEXs
  4. 深度学习08 - 决策树
  5. python随机出现外星人飞船_《外星人入侵》项目飞船和外星人相撞后飞船不返回屏幕底部,依然在外星人中间的问题解决方法...
  6. 数据通信基础 - 调制技术
  7. Atitit 怎么阅读一本书 消化 分析 检索 attilax总结 1 读书的本质 是数据的处理 大量的数据 处理能力
  8. my97datepicker日历展示出现中文乱码
  9. 专享策略04 | 商品通用套利模型(二)
  10. Windows_PC端微信双开/多开方法汇总(不改软件和插件,批处理)[微信技术教程]