【51CTO译文】目前在移动开发领域最重要的两个平台分别为Android平台和iOS,在两个平台开发应用分别要用Java和Objective-C语言。虽然Java和Objective-C就像是处在两个不同的世界,但这两种编程语言以及它们的平台库等等还是有许多相似的地方。本文为51CTO独家译文,讲述了外国开发者Genadiy Shteyman从Java开发转向Objective-C需要掌握技能。

以下为全部译文,(文章中的“我”指代"Genadiy Shteyman"):

最近一段时间,我从编写企业Java应用转向使用Objective-C。经过长时间的困扰之后,我发现两者的相似之处很多,如果能够早些读到相关的文章,转换工作会容易得多。

所以我写下这篇文章,想要帮助Java程序员快速的掌握Objective-C开发的主要特点。我使用一个社交网络应用作为例子,演示怎样用这两种语言建立开发环境。例子中会包括创建基本对象与比较两种语言的MVC设计模式,还会演示两种语言中数据的存储和获取。

Objective-C开发:从哪里开始

开发iPhone应用,首先最好要使用Mac电脑。最新的Mac OS X 10.6版本通常包含了一份Xcode IDE,以及使用Objective-C的配套iPhone开发软件工具套装(图表一)。

图表一:Xcode IDE开发环境,项目视图

2010年11月,苹果发布了期待已久的iOS SDK 4.2,其中包含了丰富的框架和功能,用来搭建互动iPhone应用。Xcode还包含了一个仿真器,可以让你在电脑中模拟程序运行在手机上的效果。

Objective-C是iPhone应用的主要开发语言。对Java开发者来说,幸运的是Objective-C是完全面向对象的,使用和其他OO语言相同的理念——继承、多态和封装等等。定义一个类(Objective-C中称为module或.m文件),首先要定义一个接口(一个header或.h文件),然后把它引入到类中。

我们来看这个社交网络应用的例子,这个应用需要建立一个联系册,让你和朋友们时常保持联系。朋友的档案存储在FriendProfile对象中,包含四个字段:朋友的名字、城市、国家和电话号码,如Listing One所示:

Listing One

//  FriendProfile.h

#import

#import

@interface FriendProfile : NSObject {

}

@property (nonatomic, retain) NSString * name;

@property (nonatomic, retain) NSString * country;

@property (nonatomic, retain) NSString * city;

@property (nonatomic, retain) NSString * phoneNbr;

@end

//FriendProfile.m

#import "FriendProfile.h"

@implementation FriendProfile

@synthesize name;

@synthesize country;

@synthesize city;

@synthesize phoneNbr;

@end

在这个例子中,接口FriendProfile:NSObject表示我们定义了一个叫做FriendProfile的接口,它从NSObject基类中继承各种功能。NSObject是Objective-C的根类,大多数Objective-C中用到的类都会从中继承,这和Java中的Object类相似。接下来,我们分配多个NSString类型变量(等同于Java中的String类型)用来存储朋友的数据。然后是建立FriendProfile类,使用@synthesize关键字自动创建各种get和set方法。建立一个FriendProfile对象可以使用如下的语句:

FriendProfile * profile = [[FriendProfile alloc] init];

这里的alloc和init就像Java里的new关键字,用来在内存中建立FriendProfile对象。接下来,就可以给对象的各种字段赋值了。

[profile setName:@"Albert"];

[profile setCountry:@"USA"];

[profile setCity:@"Houston"];

[profile setPhoneNbr:@"123-456-789"];

或者可以更简单一点:

profile.name = @"Albert";

profile.country = @"USA";

profile.city = @"Houston;

profile.phoneNbr = @"123-456-789";

想要充分了解Objective-C的语法和功能可以去苹果的开发者站点,那里的语言参考编写的非常好。

Java的构造

在Java中,如果我们想写一个FriendProfile类,所做的和Objective-C会非常相像,就像Listing Two所示:

Listing Two

packagecom.vo;

publicclassFriendProfile {

privateString name;

privateString country;

privateString city;

privateString phoneNbr;

publicString getName() {

returnname;

}

publicvoidsetName(String name) {

this.name = name;

}

publicString getCountry() {

returncountry;

}

publicvoidsetCountry(String country) {

this.country = country;

}

publicString getCity() {

returncity;

}

publicvoidsetCity(String city) {

this.city = city;

}

publicString getPhoneNbr() {

returnphoneNbr;

}

publicvoidsetPhoneNbr(String phoneNbr) {

this.phoneNbr = phoneNbr;

}

}

Listing Two中提供了相似的字段,但是那些get和set必须清楚的写出来。现在我们看看怎样在通讯录里添加一个新朋友,参加Listing Three:

Listing Three

publicclassFriendlyServletControllerextendsHttpServlet {

privatestaticfinallongserialVersionUID = 1L;

/**

* @see HttpServlet#doGet(HttpServletRequest request,

* HttpServletResponse response)

*/

protectedvoiddoGet(HttpServletRequest request,

HttpServletResponse response)

throwsServletException, IOException {

doPost(request, response);

}

/**

* @see HttpServlet#doPost(HttpServletRequest request,

* HttpServletResponse response)

*/

protectedvoiddoPost(HttpServletRequest request,

HttpServletResponse response)

throwsServletException, IOException {

response.setContentType("text/html");

PrintWriter out = response.getWriter();

finalString action =

request.getParameter("requestedAction");

if(action==null|| action.trim().length()==0){

out.println("invalid action requested");

return;

}

else

if(action.equalsIgnoreCase("addToContacts")){

String name = request.getParameter("name");

String country = request.getParameter("country");

String city = request.getParameter("city");

String phoneNbr = request.getParameter("phoneNbr");

//normally you have to validate browser-originated requests

booleanvalidParameters =

validateParameters(name, country, city, phoneNbr);

if(validParameters==false){

out.println(

"please verify and submit correct information");

return;

}

FriendProfile newProfile =newFriendProfile();

newProfile.setName(name);

newProfile.setCountry(country);

newProfile.setCity(city);

newProfile.setPhoneNbr(phoneNbr);

ProfileManager.getInstance().addToContacts(newProfile);

out.println("Your friend is added to contacts");

return;

}

else{

out.println("invalid action requested");

return;

}

}

}

在这个例子里,FriendlyServletController类从HTTPServlet中获取行为,HTTPServlet是Java的客户端组件类,负责处理浏览器的请求。当用户登入网站并且决定添加一个朋友时,他会在HTML表单的字段中填入数据,表单提交时,Servlet收到并验证请求的参数,并创建一个FriendProfile对象,在内存中存储数据。而ProfileManager类会把你的FriendProfile对象存储到数据库中。

Objective-C的MVC模式

在Java Web应用中常采用Model-View-Controller(MVC)设计模式,iPhone开发中也是如此。如果你在iOS Reference Library中查找UIViewController类的定义,你会发现这样的话:“UIViewController类为iPhone应用提供最基本的视图管理模型……你可以使用UIViewController实例来管理视图结构。”UIViewController实际上是一个控制器组件,用来触发业务逻辑,更新客户端的视图。

图表2:Model-View-Controller(MVC)设计模式

如果你想在Xcode中创建一个UIViewController类型的对象,可以选择通过XIB文件来创建。这种特殊的Xcode文件定义了图形用户界面或者说视图,包含了各种不同的控件,比如按钮、图表和标签等等。

回到我们的例子中来,假设你已经在联系列表中添加了几个朋友,现在想按下某个朋友的链接来看查看他的详细信息,这个功能可以通过定义控制器类来完成。代码请见Listing Four:

Listing Four

//  FriendProfileViewController.h

#import

@classFriendProfile;

@classDatabaseController;

@classMFriendProfile;

// define our custom controller to inherit from

// the UIViewController class

@interface FriendProfileViewController : UIViewController {

FriendProfile * profile;

MFriendProfile * mprofile;

DatabaseController *dbController;

}

@property(nonatomic, retain) IBOutlet UILabel *lname;

@property(nonatomic, retain) IBOutlet UILabel *lcountry;

@property(nonatomic, retain) IBOutlet UILabel *lcity;

@property(nonatomic, retain) IBOutlet UILabel *lphoneNbr;

-(IBAction)buttonPressed:(id)sender;

@end

#import "FriendProfileViewController.h"

#import "FriendProfile.h"

#import "DatabaseController.h"

#import "MFriendProfile.h"

@implementation FriendProfileViewController

...

// Implement viewDidLoad to do additional setup after

// loading the view, typically from a nib.

- (void)viewDidLoad {

[super viewDidLoad];

//create sample profile

profile = [[FriendProfile alloc] init];

profile.name = @"Albert";

profile.country = @"USA";

profile.city = @"Houston";

profile.phoneNbr = @"123-456-789";

//show profile on a screen

lname.text = profile.name;

lcountry.text = profile.country;

lcity.text = profile.city;

lphoneNbr.text = profile.phoneNbr;

}

//call the model to bring friend information from database

-(IBAction)buttonPressed:(id)sender{

NSLog(@"fetching friend profile by name.");

// name is hardcoded for demo purposes.

// Usually entered by user.

mprofile = (MFriendProfile*)

[dbController getFriendProfileObjectbyName:@"Albert"];

lname.text = mprofile.name;

lcountry.text = mprofile.country;

lcity.text = mprofile.city;

lphoneNbr.text = mprofile.phoneNbr;

}

这段代码中,我们创建了一个FriendProfileViewController实例,在我们定义的View Bundle中进行初始化,显示出朋友的各种信息。

Alloc和initWithNibName都是控制器类创建实例时使用的方法,和Java的new关键字功能一样。

模型在装载视图时开始启动。每个控制器都有一些从父类UIViewController继承而来的生命周期方法。比如ViewdidLoad方法就是其中之一,它负责在视图装载之后的额外设置,从数据库中取出信息,更新视图。在最简单的情况下,我们的视图包含一系列标签,或者是UILabel类型的对象,可以在应用运行时设置各种文本,用户可以立即看见朋友信息被更新了。

Java的MVC模式

下面来看看如何使用Java后台在浏览器窗口中显示出朋友的详细信息。我们稍微修改一下FriendlyServletController即可,代码请见Listing Five:

Listing Five

importjava.io.IOException;

importjava.io.PrintWriter;

importjavax.servlet.ServletException;

importjavax.servlet.http.HttpServlet;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

importcom.model.ProfileManager;

importcom.vo.FriendProfile;

/**

* Servlet implementation class FriendlyServletController

*/

publicclassFriendlyServletControllerextendsHttpServlet {

privatestaticfinallongserialVersionUID = 1L;

/**

* @see HttpServlet#doGet(HttpServletRequest request,

* HttpServletResponse response)

*/

protectedvoiddoGet(HttpServletRequest request,

HttpServletResponse response)

throwsServletException, IOException {

doPost(request, response);

}

/**

* @see HttpServlet#doPost(HttpServletRequest request,

*      HttpServletResponse response)

*/

protectedvoiddoPost(HttpServletRequest request,

HttpServletResponse response)

throwsServletException, IOException {

response.setContentType("text/html");

PrintWriter out = response.getWriter();

finalString action = request.getParameter("requestedAction");

if(action==null|| action.trim().length()==0){

out.println("invalid action requested");

return;

}

elseif(action.equalsIgnoreCase("showFriendProfile")){

String name = request.getParameter("name");

FriendProfile existProfile =newFriendProfile();

existProfile.setName(name);

existProfile =

ProfileManager.getInstance().lookupContact(existProfile);

if(existProfile==null){

out.println("profile was not found");

}else{

out.println("here is your contact information:"+

existProfile.getName() +" from "+

existProfile.getCity() +" in "+

existProfile.getCountry() +" at "+

existProfile.getPhoneNbr());

}

return;

}

elseif(action.equalsIgnoreCase("addToContacts")){

String name = request.getParameter("name");

String country = request.getParameter("country");

String city = request.getParameter("city");

String phoneNbr = request.getParameter("phoneNbr");

//normally you have to validate browser-originated requests

booleanvalidParameters =

validateParameters(name, country, city, phoneNbr);

if(validParameters==false){

out.println("please verify and submit correct information");

return;

}

FriendProfile newProfile =newFriendProfile();

newProfile.setName(name);

newProfile.setCountry(country);

newProfile.setCity(city);

newProfile.setPhoneNbr(phoneNbr);

ProfileManager.getInstance().addToContacts(newProfile);

out.println("Your friend is added to contacts");

return;

}

else{

out.println("invalid action requested");

return;

}

}

//basic parameter validation routine

privatebooleanvalidateParameters(String name, String country,

String city, String phoneNbr){

/basic validation to checkifall parameters are sent

if(name==null|| name.trim().length()==0||

country==null|| country.trim().length()==0||

city ==null|| city.trim().length()==0||

phoneNbr ==null|| phoneNbr.trim().length()==0){

returnfalse;

}

returntrue;

}

}

在这个例子中,FriendlyServletController接收表单产生的HTTP请求,我们特别编写了一个事件叫做showFriendProfile。这里我们的模型是一个ProfileManager对象,负责通过朋友姓名在数据库中查找记录。然后查找到的数据库记录会以FriendProfile对象的形式返回到控制器,其中包含了各种详细信息,组成视图显示在浏览器窗口中。

Objective-C的数据库访问

较复杂的应用都会用到某类数据存储方式,通常是一个数据库。苹果推荐开发者使用称为Core Data的Cocoa API框架进行数据库存取操作。Core Data框架能够直接与SQLite数据库相结合(我们例子中的数据库运行在移动设备上)。Core Data隐藏了复杂的SQL操作,取而代之的是非常方便的NSManagedObject界面,你可以直接操作整个对象实例的各种字段,这些字段可以自动存入数据库。Core Data框架的另一个方便之处是在数据库中创建表(以及向表中添加关联与限制),这些都可以在Core Data的用户界面中完成。

图表3:Core Data stack结构

现在回到我们的社交网络应用例子,看看怎么从数据库中取出朋友的信息。我们使用SQLite 和Core Data API,但首先我们要稍微修改一下FriendProfile类,代码请见Listing Six:

Listing Six

//FriendProfile.h interface file// MFriendProfile.h

#import

#import

@interface MFriendProfile : NSManagedObject {

}

@property (nonatomic, retain) NSString *name;

@property (nonatomic, retain) NSString * country;

@property (nonatomic, retain) NSString * city;

@property (nonatomic, retain) NSString * phoneNbr;

@end

// MFriendProfile.m

#import"MFriendProfile.h"

@implementation MFriendProfile

@dynamicname;

@dynamiccountry;

@dynamiccity;

@dynamicphoneNbr;

@end

这里的FriendProfile类与Listing One中的不同之处在于在这里我加入了Core Data框架的头文件。而且在这里我们的类是从NSManagedObject中扩展出来,带有了Core Data对象需要的全部基本行为。Core Data的NSManagedObject类中使用到的Accessor则在应用运行时动态创建。如果你想在FriendProfile类中声明或使用属性,但不想在编译时出现缺少方法的警告,可以使用@dynamic指令,而不是@synthesize指令。

使用NSManagedObject API有些复杂,但你理解之后就会变得很好用。Listing Seven是一个示例方法,从数据库的FRIENDPROFILE表中取得朋友的信息。表包含四列:NAME、COUNTRY、CITY和PHONE­NBR。

Listing Seven

// DatabaseController.m

#import"DatabaseController.h"

#import

#define kDatabaseName @"SocialNetworking.sqlite"

...

- (NSManagedObject *)getFriendProfileObjectbyName:(NSString *)name{

managedObjectContext = [self managedObjectContext];

//createsort descriptorstospecify preferred sortorder

NSSortDescriptor *sortDescriptor =

[[NSSortDescriptor alloc] initWithKey:@"name"ascending:YES];

NSArray *sortDescriptors =

[[NSArray alloc]  initWithObjects:sortDescriptor,nil];

//specifywhereclause

NSPredicate *predicate =

[NSPredicate predicateWithFormat:@"name == %d",name];

//fetchour friendís profilefromdatabasetable

NSEntityDescription *entity =

[NSEntityDescription entityForName:@"MFRIENDPROFILE"

inManagedObjectContext:managedObjectContext];

NSFetchRequest *request = [[NSFetchRequest alloc] init];

[request setEntity:entity];

//Setthe predicateforthecurrentview

if (predicate)

{

[request setPredicate:predicate];

}

if (sortDescriptors)

{

[request setSortDescriptors:sortDescriptors];

}

NSError *error = nil;

NSMutableArray *results = [[managedObjectContext

executeFetchRequest:request error:&error] mutableCopy];

if (error)

{

NSLog(@"error in getFriendProfileObjectbyName:%@",

[error userInfo]);

}

[sortDescriptor release];

[sortDescriptors release];

[predicate release];

if ([resultscount] > 0) {

return[results objectAtIndex:0];

}

returnnil;

}

getFriendProfileObjectbyName方法把朋友的姓名作为一个参数接收过来。通过使用Core Data API,我们可以指定在哪一个表中进行查询和排序,并且在后台执行SQL语句。

SQL>select*fromFriendProfilewherename="Albert";

Core Data API有许多种没有封装的“半成品”代码,可以访问NSManagedObjectContext、NSPersistentStoreCoordinator和NSManaged­ObjectModel对象。你可以复制这些代码,只要你取得了FriendProfile对象,就能以下面的形式取得它的属性:

NSString*name=        FriendProfile.name;

NSString* country = FriendProfile.country;

NSString* city =        FriendProfile.city;

NSString* phoneNbr =    FriendProfile.phoneNbr;

总的来说,Core Data是一个非常有用的功能,可以让你通过图表来定义数据表和管理,可以动态生成相应的对象,而且无需使用复杂的SQL语句。但不好的方面是这里有大量的没有经过封装的代码,这样你在使用它们与测试时需要非常小心。

Java:数据库存取

Java有许多数据库框架。在我看来,Hibernate是和Core Data API最相像的Java框架。Hibernate使用的是对象关系映射(Object-Relational Mapping,ORM)机制,这样你可以通过简单的在对象中设置字段并且直接映射成数据库中的表来把对象数据放入关系型数据库中。映射可以通过XML文件,也可以通过Java 5中的metadata annotation方法获得。Listing Eight是使用XML进行映射的一个例子。

Listing Eight

此例中,Listing Two中的FriendProfile对象被映射到数据库中的一个同名表,这是一种传统的数据映射做法。对象的四个字段被直接映射到表中的四列,通过映射,Hibernate可以使用SQL语句来完成各种操作。

另一个配置文件叫做hibernate.cfg.xml,包含了数据库连接设置的详细信息,包括数据库URL、数据库驱动以及用户名和密码等,代码请见Listing Nine:

Listing Eight

Listing Nine中我们导入了所有需要的Hibernate库,创建了一个Hibernate Session并且开始事务,接下来我们仅简单使用了Session对象的get方法就轻松检索到了FriendProfile对象,传递回所需要的对象类型并过滤出查询的字段——朋友的姓名。

结论

除去语法结构与运行平台的不同,使用Objective-C进行iPhone开发与使用Java进行网络应用开发在下面几个方面是相同的:

◆两种语言都是面向对象的

◆两种语言使用同样的设计模式,例如MVC

◆两种语言使用相似的数据库存储技术,例如ORM

然而,对于Java开发者,使用Objective-C时在有些地方要格外小心:

◆创建对象:Java对象是在运行时通过new关键字创建的。因此Java程序员无需担心内存分配问题。而在Objective-C中,一个对象可以由三个关键字创建,alloc、new或者copy,这三个关键字在创建对象时都会增加对象的持有计数(retain count),持有计数是Objective-C特有的内存管理方法,显示有多少个指针指向对象,是否可以被内存管理器回收。

◆销毁对象:由于强大的垃圾回收机制,Java的内存管理工作极度简单。Java的引用对象都存储在JVM的堆内存中,一旦不再被引用,就可以作为垃圾回收。Objective-C使用的是内存管理器,而不是垃圾回收器。如果你使用上面说的三种方法在内存中创建了一个对象,那么必须使用release方法来释放对象。release方法会减少持有计数,当计数降到0时,被引用的对象会接受一个来自高级类的dealloc方法,释放它占用的内存并重新分配。如果忘记了释放内存或释放失败,那么会造成内存泄露和不可预见的错误。

◆过多释放和过早重新分配内存:由于垃圾回收机制,Java程序员可以完全不考虑这些问题。但Objective-C程序员需要小心,不能释放出比分配的更多的内存。如果在已经重新分配的对象上过多释放内存,就会造成应用的崩溃。

上面这些例子说明了Objective-C和Java在语法和语言元素上有很多相同之处。更重要的是,它们解决问题的思路和用到的组件也是非常相似的。如果你是Java程序员,相信你在看完这篇文章后,转向Objective-C的道路会更加通顺。

【51CTO译稿,非经授权谢绝转载,合作媒体转载请注明原文出处、作者及51CTO译者!】

【编辑推荐】

【责任编辑:立方TEL:(010)68476606】

objective-c java_程序员转型指南 当Java遇见了Objective-C相关推荐

  1. 程序员开发指南!java面试常问问题

    正文 如果你参加过一些大厂面试,肯定会遇到一些开放性的问题: 1. 写一段程序,让其运行时的表现为触发了5次Young GC.3次Full GC.然后3次Young GC: 2. 如果一个Java进程 ...

  2. 写给想通过程序员转型为项目经理的人

    写了这么多硬技术文章,今天转一篇在blog.joycode.net看到的软技术文章,对想通过程序员转型为项目经理的人有一些用处. 广为流传的一个关于项目管理的通俗讲解 古老虾 发表于 2005-11- ...

  3. 视频教程-程序员入门指南-Python

    程序员入门指南 从事数据科学以及机器学习行业数年 宫聚仁 ¥49.00 立即订阅 扫码下载「CSDN程序员学院APP」,1000+技术好课免费看 APP订阅课程,领取优惠,最少立减5元 ↓↓↓ 订阅后 ...

  4. 「程序员做饭指南」霸榜GitHub,还用数学公式解决「吃什么」世纪难题,微软程序员出品...

    点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 鱼羊 发自 凹非寺 量子位 | 公众号 QbitAI 我也是没想到啊,GayHub啊不 ...

  5. 《程序员做饭指南》霸榜 GitHub!不仅有量筒、烧杯,还用上了数学公式?

    整理 | 郑丽媛 出品 | CSDN(ID:CSDNnews) 对于 GitHub,相信绝大多数程序员都再熟悉不过了. 作为目前全球最大的开源软件存储库,GitHub 托管了大量的软件代码,无数开源爱 ...

  6. 必看!程序员逃生指南

    作者 | 八宝粥 设计 | 章敏 出品 | CSDN(ID:CSDNnews) 2020 艰难一年已经过去 ,2021 究竟如何,谁也不知道. 作为程序员,前有「大小周」,后有高科技坐垫"监 ...

  7. Github热榜--《程序员做饭指南》

    公众号:尤而小屋 作者:Peter 编辑:Peter 最近在GitHub上发现了一个很有趣的项目:[程序员做饭指南],居然成为多日GitHub的热榜第一. 仓库具体地址: https://github ...

  8. 14K Star,「程序员做饭指南」冲上热榜

    来自公众号:OSC开源社区 作者:叶子 近日,GitHub上一个名为「HowToCook」的项目冲上热榜,没错就是一份程序员做饭指南,可它又不是一份普通的做饭指南,一起来看看吧. 首先,它拥有丰富的菜 ...

  9. 小程序获取头像试试水 02《 程序员变现指南之 微信QQ 小程序 真的零基础开发宝典》

    本系列教程是针对粉丝的变现教程,还不是粉丝的可以关注我并且到社区:https://bbs.csdn.net/topics/603436232 进行打卡,不是老粉的也可以获取最终的技术变现学习,最终还有 ...

最新文章

  1. VR的风口,让UWA借你一双翅膀
  2. mysql中删除标识列的语句_MySQL中一些常用的数据表操作语句笔记
  3. 线性表算法题库_数据结构与算法(线性表)练习题
  4. 今晚直播:GAN在网络特征学习中的应用 | PhD Talk #23
  5. android 交叉编译so,Android交叉编译htop和使用方法
  6. 华为否认削减手机产量;百度副总裁郑子斌将离职;开发者对苹果发集体诉讼 | 极客头条...
  7. java DTO循环_Java Stream与for循环比较
  8. Swift入坑系列—集合类型
  9. html字体样式(有中文兼英文实例)
  10. 联想昭阳E46G笔记本识别4G内存问题
  11. Hadoop分布式大数据平台
  12. 卫星互联网若干关键技术研究
  13. java 正态分布数_生成正态分布的数
  14. python中fun函数求1+2…+n_功能:编写函数fun求1!+2!+3!+ …… +n!的和,在main函 数中由键盘输入n值,并输出运算结果。请编写fun 函数...
  15. 零基础入门python好学么
  16. ThinkPad P1 Gen4 是否支持单条4T固态?
  17. 15款你可能不知道的精致Mac应用
  18. Swift上写百度地图记录
  19. 最长递增子序列问题(你真的会了吗)
  20. 怒怼软件测测试不良培训机构!痛斥招转培!

热门文章

  1. 使用事务代码MB51+Excel中的数据透视表实现 收发存报表
  2. 在导出本地文件时报了ABAP错误
  3. 获取日期对应的财务期间
  4. 我们都准备好进入数字货币+无现金世界了?
  5. linux删除目录排除,Linux中移动,复制,删除,打包排除某个目录或文件
  6. 制作碳排放强度的空间可视化_【科研成果】吴传清、宋子逸:长江经济带农业碳排放的时空差异特征分析...
  7. python降级pip_1.2 pip降级selenium3.0
  8. python中的cli模块_Python 快速实现CLI 应用程序的脚手架
  9. error: a label can only be part of a statement and a declaration is not a statement
  10. 【Python教程】七种创建对象的方式,你知道几种?