编写Objective-C的代码

编写Objective-C的代码

如果你没有在iOS或Mac OS X上开发过,那么你需要去熟悉一下Objective-C这个编程语言.Objective-C并不是一个很难的语言,而且你一旦花些事件来学习它,你就会觉得它很优雅.Objective-C可以进行高级(sophisticated)的面向对象编程.它是在ANSI C编程语言的基础上扩展的,提供了定义类和方法的语法.它也提升了类和接口的动态扩展性,可以让任何类都可以采用.

如果你熟悉ANSI C的话,那么下面的信息会帮助你学习Objective-C的语法.并且如果你还用其他的面向对象语言进行开发过, 你就会发现有很多传统面向对象的概念,比如 封装,继承,多态 都会在Objective-C中展现.如果你还不熟悉ANSI C的话,那么你在尝试阅读这篇文章之前,至少应该去阅读一下C语言的概述.

The Objective-C Programming Language文章中完全解释了Objective-C语言.

Objective-C是C语言的超集

Objective-C语言指定了 定义类与方法,对象方法的调用,以及类(class)采用所创建的编程接口(interface)来进行动态扩展的这些语法,以便来解决一些问题.作为C语言的超集,Objective-C支持和C语言相同的语法.你可以拿所有你熟悉的要素进行开发,例如基本类型(int,float 等等), 结构体, 函数, 指针,以及流程控制结构 例如if...elsefor语句.你还可以去访问通用的标准C语言库, 例如声明在stdlib.hstdio.h中所声明的内容.

Objective-C在ANSI C功能的基础上添加了如下语法:

  • 定义一个新的类

  • 类方法 和 实例方法

  • 方法的调用(称做 messaging 发送消息)

  • 声明属性 (并自动通过属性生成了存取器方法的实现部分,也就是synthesize,不过在源码中是看不到的.)

  • 静态与动态类型

  • 封装了代码段的语句块,它可以让你在任何时候执行它

  • 基本语言的扩展,例如协议(protocol)和类别(category)

如果你现在对Objective-C的这些方面并不熟悉,请不要担心. 在你随着你一步一步地阅读文章剩下的部分的时候,你将会学习到更多关于这些方面的知识.如果你是一个初次接触面向对象概念的程序员的话,可以把它先想象成 一个对象本质上是一个结构体和函数 相关联, 也许对你有帮助.这个想法实际上并不是太离谱,尤其是对于运行时实现方面而言.

另外提供了在其他面向对象语言中的大多数的抽象概念和机制, Objective-C一个非常灵活的语言,这种动态性是它最大的优势.Objective-C的动态性,可以在应用正在运行的时候再来确定它的行为(也就是 运行时) 而不是在程序构建的时候就已经定好了的. 

Thus the dynamism of Objective-C frees a program from constraints imposed when it’s compiled and linked; instead it shifts much of the responsibility for symbol resolution to runtime, when the user is in control.

这样Objective-C的动态性 解决了在已经编译和连接后的应用所强制约束的限制(比如类型);而不是当用户在控制的时候,过多的依赖符号解析到运行时.

类和对象

和大多数其他的面向对象语言一样,Objective-C中的类支持数据的封装和定义一个可以操作数据的动作(action).一个对象就是运行时的一个类的实例. 每一个实例变量在内存中都包含 通过它们各自的类所声明的实例变量和类的方法的指针的 拷贝.你创建一个对象需要两步, 分别叫做内存分配(allocation)初始化(initialization).

在Objective-C中规定了一个类必须要两个不同的部分:
接口(interface)和实现(implementation). 接口部分包含了类的声明和定义公有的类接口.和C语言代码一样,你定义头文件和源文件来将你代码中的公有声明和实现的细节单独分开.(如果它们是编程接口的一部分,那么你可以在实现文件中放置其他声明,但是这些声明是表示为私有的).

在下面的表格里列出了这些文件(上面所说的接口和实现文件等)可用的文件扩展名.

扩展名

源文件类型

.h

头文件.   头文件包含 类, 类型 , 方法 和 常量的声明.

.m

实现文件. 这个扩展名的文件可以包含Objective-C和C的代码. 它有的时候被称做 源文件(source file).

.mm

实现文件. 这个扩展名的文件不仅可以包含Objective-C和C的代码还可以包含C++的代码. 如果你实际要从Objective-C的代码中引用C++的类和功能,那么只能只用这个扩展名.

当你要在源代码中包含头文件的时候, 就要在一个源文件或者头文件的开头第一行指定一个 井号和import (#import)指令;
#import指令和C语言的#include指令很像, 唯一的区别就是#import指令确保相同的文件只被包含一次.如果你想到导入一个框架中大多数或者所有的头文件, 那么只要导入框架的包罗头文件即可(umbrella header file),包罗文件我的理解是,实际上就是一个包含了框架中所有主要头文件的集合文件,让其他人使用的时候,只需包含一个头文件即可使用,而无需使用者再自己包含其他内部接口的头文件.
这个文件和框架的名字是相同的.导入Gizmo(假设)框架的头文件的语法是:

#import <Gizmo/Gizmo.h>

下面的图示显示了声明一个名叫MyClass的类,在这里它是从基类(也可以叫做根类)NSObject 中继承的.(一个根类是一个可以被其他任何类直接或间接继承的类.).类的声明是通过@interface编译器指令开始,@end指令结束的.接下来的这个类名(并且是通过冒号来分隔的)是父类的名字.在Objective-C中,一个类只能有一个父类.

A class declaration


你编写属性和方法的声明要在@interface@end指令之间.这些所声明的(属性 接口)都会成为类的公有接口.一个分号标记代表着每一个属性和方法声明的结束.

如果一个类有自定义函数,常量或者和它的公有接口相关的数据类型(比如说函数参数,返回值)的话,那么就将他们放置在@interface . . . @end的外面.

 (
  注意: 方法(Method)和函数(Function) 是不同的, 方法就是类中的方法是以Objective-C形式来书写的.
 函数则是C语言的,类似于全局函数的那种形式 可以把上面这句话理解成 如果写成C的形式的话,可以这么做....
 有可能翻译错了,但是我实验过了 常量在类中是声明不了的,只能在外部才行,比如static const int test=5;

 )

一个类的实现语法是类似的. 它是通过一个@implementation编译器指令开始的(紧接着是类的名字) 和  @end指令结束的. 方法实现放是在内部.(函数实现应该放在@implementation . . . @end的外面) .
一个实现应该总在这个代码的第一行导入它的接口文件.

#import "MyClass.h"
 
@implementation MyClass
- (id)initWithString:(NSString *)aName
{
    // code goes here
}
 
+ (MyClass *)myClassWithString:(NSString *)aName
{
    // code goes here
}
@end

Objective-C 支持对象包含动态类型的变量, 而且也支持静态类型.
静态类型就是在变量类型声明中包含一个类名. (比如A*  这样就不能赋给B * 了)
动态类型的变量是使用类型 id 来声明这个对象的. (类似于 void *)
例如, 一个集合对象,如数组 (内部所包含的对象可能是未知的) 也许就要使用动态类型的变量.
这种变量会在Objective-C程序中,提供一个很大的灵活性并且可以更加动态.

这个例子展示了静态和动态类型变量的声明:

MyClass *myObject1;  // 静态类型
id       myObject2;  // 动态类型
NSString *userName;  // 在 你的第一个iOS应用 中出现的 (静态类型)

请注意一下第一行声明的星号(*).在Objective-C中,对象的引用必须要用指针.如果这个规定,你并不能完全的理解,请不要担心.你并不需要去成为一个指针专家才能去开始Objective-C的编程.你仅仅需要记住在声明静态对象变量的时候要在变量名的前面加上星号(*).这个 id 类型就意味着一个指针.

方法(Method)和消息传递(Messaging)

如果你初次进行面向对象编程, 那么将它想象成 一个方法就是一个函数的作用域在一个指定的对象中 也许对你有帮助.通过发送一个消息到-或者消息传递(messaging)-一个对象,  也就是你调用了一个对象的方法.在Objective-C中有两种方法 : 实例方法 和 类方法.

  • 一个实例方法(instance method)就是 执行这个方法的作用域在类的一个特定实例中 的方法.换句话说, 在你调用一个实例方法之前, 你必须首先创建类的一个实例才行.实例方法是最常用的方法类型..

  • 一个类方法(class method)就是 执行这个方法的作用域在这个方法所在的类中 的方法.它不需要一个对象的实例来作为一个消息的接收者.

一个方法是声明是由 方法类型标识, 一个返回值类型, 一个或更多的签名关键字(signature keywords),和参数类型和名字的信息 所组成的.
这里声明了 insertObject:atIndex: 实例方法.

Method declaration syntax

对于实例方法, 声明的时候在一开始要写一个减号(-); 对于类方法, 一开始则写的是一个加号(+).下面的 "类方法"  对类方法进行了更详细的描述.

一个方法的名字(insertObject:atIndex:)实际上是所有的签名关键字串联而成的, 包括冒号字符.一个冒号字符的声明表示存在一个参数.在上面的例子当中,这个方法带两个参数.如果方法没有参数, 那么你可以在第一个(当然这种情况 也只能有一个)签名关键字之后,省略冒号.

当你想去调用一个方法的时候, 你还可以发送一个消息到实现这个方法的对象当中.(虽然 短语 "发送一个消息" 通常是和 "调用一个方法" 是同义词, Objective-C在运行时,实际是发送消息的.)一个消息就是方法的名字带上方法所需要的参数信息(要正确地和参数类型对应).你发给一个对象的所有消息都是动态调度的, 这样会促进Objective-C类行为的多态性.(所谓多态性是指不同类型的对象可以去响应相同的消息).有的时候,所调用的方法是通过获取消息的对象的父类来实现的.

要调度一个消息, 运行时需要一个消息表达式.一个消息表达式就是用一对方括号([  ])将消息本身包起来, 方括号里最左边是获取这个消息的对象.例如, 要给myArray变量发送一个insertObject:atIndex: 消息来保存一个对象,你将要使用下面的语法:

[myArray insertObject:anObject atIndex:0];

要避免用声明很多变量的方式来存储临时的结果,Objective-C允许你进行嵌套的消息表达式. 每一个所嵌套表达式的返回值都是用来作为另一个消息的参数或者接收其他消息的对象的.例如, 你可以通过消息获取的值来替换掉上一个例子中的所有变量.这样的话, 如果有一个其他的对象myAppObject
有一个 访问数组对象(theArray) 和 获取要插入到数组的对象(objectToInsert) 的方法,你可以将上面所说例子中的代码改写成下面这样:

[[myAppObject theArray] insertObject:[myAppObject objectToInsert] atIndex:0];

Objective-C还提供了一个 点表达式 语法,用来调用存取器方法.存取器方法 获取(get)或设置(set) 一个对象的状态, 而且这个是封装的关键, 封装是所有对象的一个重要特性.对象隐藏,或封装, 它们的状态并且表示为一个 可以让所有实例都可以访问其状态的 公共接口.使用 点表达式的语法,你可以将之前的例子代码改写成这样:

[myAppObject.theArray insertObject:myAppObject.objectToInsert atIndex:0];

你还可以在赋值的时候使用 点表达式:

myAppObject.theArray = aNewArray;

这个语法仅仅是[myAppObject setTheArray:aNewArray]; 的另外一种编写方式.在点表达式中,你不能引用一个动态类型的对象(id 类型的对象).


你在 你的第一个iOS应用 中, 在给一个变量赋值的时候, 已经使用过点语法了. 

self.userName = self.textField.text;

Class Methods

虽然在之前所有的例子中发送了一个消息到一个类的实例中, 你还可以发送消息到类本身.(一个类是一个由运行时创建的Class类型的对象.) 当发送消息到一个类的时候, 方法必须定义为一个类方法,而不是一个实例方法.类方法的特征和C++的静态类函数很像.

你经常会使用类方法来 定义一个工厂方法用来创建类的新实例 或者 访问和类相关的一块共享信息.声明类方法的语法,和实例方法相比,除了是使用加号(+)而不使用减号的区别之外,其他都是相同的.

下面的例子说明了你如何使用类方法 来定义一个类的工厂方法. In this case, the array method is a class method on the NSArray class—and inherited by NSMutableArray—that allocates and initializes a new instance of the class and returns it to your code.

在这种情况下, 这个数组(array)方法是NSArray类中的类方法—并且被NSMutableArray类所继承-用来 对类的一个新的实例 进行内存分配 和 初始化 然后返回它到你的代码中.

NSMutableArray *myArray = nil;  //  nil本质上是和NULL相同的
 
// 创建一个新的数组 然后将它赋给myArray变量.
myArray = [NSMutableArray array];

声明属性和存取器方法

在通常意义上,一个属性就是封装或存储了对象的一些数据.它可以是一个属性-例如一个名字或者一个颜色-或者和另一个对象相关的(属性).一个对象的类定义了一个接口,这个接口可以让用户的对象去get或set属性所封装的值.执行这些动作的方法就叫做存取器方法.

存取器方法有两种类型, 并且每种方法都必须要遵守命名约定.一个 "getter" 存取器方法,  返回一个属性的值, 并且和属性的名字相同.一个 "setter" 存取器方法, 给一个属性设置新的值, 用setPropertyName:的结构形式, 属性名(PropertyName)的第一个单词要大写.属性来命名存取器方法在Cocoa和Cocoa Touch框架中的起着几个决定性的要素, 包含key-value编码(KVC), 这个是一个通过对象的属性名来间接访问一个对象属性的机制.

Objective-C提供了声明属性的方法,来方便存取器方法的声明和实现.在你的第一个iOS应用 中, 你已经声明过了userName 属性:

@property (nonatomic, copy) NSString *userName;

属性的声明不需要在类中显式地实现一个getter和setter方法.而是你要在使用属性声明的时候指定它的行为.编译器然后就可以创建它-或者说合成(synthesize)-实际上是基于声明来生成getter 和 setter方法的.声明属性可以降低 必须要编写的反复套用的代码(比如每个属性都要有getter和setter这种要反复套用的代码)数量, 这样的话,就可以让你的代码更间接而且减少了错误.使用声明的属性或者存取器方法来get和set一个对象的状态.

在你的类的接口中包括属性声明和方法声明.你可以在类的头文件中去声明公有的属性; 在源文件的类扩展中(class extension)去声明私有属性.(阅读 "协议和类别" 中的类扩展的简述以及它的一个例子) 控制器对象的属性,例如 代理和视图控制器,像这些通常都应该设置为私有的.

一个基本的属性声明要使用一个@property 编译器指令, 然后紧接着类型信息和属性的名字.你还可以使用自定义的选项来设置这个属性, 这个属性定义了存取器方法是一个怎么样的行为,比如属性是否是一个weak引用(弱引用),或者是否是只读(read-only)的.这些选项要写在@property指令后面的括号中的.

下面的代码中,举例说明了几个的属性声明:

@property (copy) MyModelObject *theObject;  // Copy the object during assignment.
@property (readonly) NSView *rootView;      // Declare only a getter method.
@property (weak) id delegate;               // Declare delegate as a weak reference

编译器会自动合成(synthesize)已经声明的属性.在合成(synthesize)一个属性的时候, 他不但会创建一个属性的存取器方法,而且还会创建这个属性的一个私有实例变量.这个实例变量和属性的名字相同,但是它有一个下划线前缀(_).你的应用应该只是在对象初始化(initialization)及重新分配内存地址(deallocation)的方法中才去直接访问一个实例变量(而不是它的属性).

如果你想要一个实例变量具有一个不同的名字,那么你就得忽略自动合成,然后显式地合成(synthesize)一个属性.在类的实现中使用@synthesize编译器指令来请求编译器用你所指定的实例变量名来生成存取器方法. 例如:

@synthesize enabled = _isEnabled;

顺便告诉你,当你声明一个属性的时候,可以去指定存取器方法的自定义名字, 通常要将一个布尔(Boolean)属性的getter方法 遵循下面所显示的形式:

@property (assign, getter=isEnabled) BOOL enabled; // 赋予(assign)新的值,改变了getter方法的名字

块对象

块对象(Blocks)是一个封装了操作的一个单元 - 也就是 一个代码段 - 它可以在任何时候被执行.他们本来是可移植且匿名的函数, 可以作为方法和函数的参数传入,或者通过方法和函数返回.块对象本身有一个类型参数列表和一个不确定或者一个已经声明的返回值类型.你还可以将一个块对象赋给一个变量,然后你就可以像调用一个函数那样来调用他.

一个脱字符号(^)是块对象的语法标记.还要熟悉参数,返回值以及块对象主体(body,也就是可执行的代码部分)的约定.下面的图示说明了 将一个块对象赋值给一个变量. 

image: ../Art/blocks.png

然后, 你就可以像调用函数那样,来调用这个块对象变量:

int result = myBlock(4); // result is 28

一个块对象可以在局部作用域之内共享数据.块对象的这个特性是很有用的, 因为如果你实现了一个方法,并定义一个此方法的块对象,这个块对象就可以访问方法局部变量和参数(包括栈变量),而且还可以访问函数和全局变量,甚至包括实例变量.这种访问是只读的, 但是使用了__block 修饰符来声明一个变量,那么就可以在块内部来修改它的值.甚至在方法或者函数封装的块对象已经返回并且已经将它的局部作用域销毁之后, 只要还有对块对象的引用,局部变量就可以作为块对象的一部分存在.

块对象可以用作回调(callback),来作为方法或函数的参数.当调用的时候, 这个方法或函数会执行一些操作,并且会在适当的片刻, 回调它来调用代码(通过块对象), 去请求附加信息,或者获取程序指定的行为.块对象允许让调用者在调用的时候提供回调回调代码.块对象不会将所需要的数据打包到一个“上下文”的结构中,而是在接收方法或函数的时候,从相同的作用域中去捕获数据.因为块对象不需要在一个单独的方法或函数中实现,因此你的实现代码能够更加的简洁而且可读性更强.

Objective-C的框架中有许多带有块对象参数的方法.例如, Foundation(基础)框架中的 NSNotificationCenter 类 声明了如下的方法,此方法有一个块对象参数:

- (id)addObserverForName:(NSString *)name object:(id)obj queue:(NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block

这个方法添加了一个观察者用来通知居中 (通知是在 “使用设计模式来简化你的应用” 中讨论的).当一个指定名字的通知被发送的时候, 块对象被调用去处理这个通知.

    opQ = [[NSOperationQueue alloc] init];
    [[NSNotificationCenter defaultCenter] addObserverForName:@"CustomOperationCompleted"
             object:nil queue:opQ
        usingBlock:^(NSNotification *notif) {
        // handle the notification
    }];

协议(Protocol)和类别(Category)

一个协议声明了一些可以让任何类都能够实现的方法, 即使实现协议的这些类没有共同一个父类也可以.
协议的一些方法定义了独立于类的一些单独的行为.协议仅仅去定义了接口,可以让其他类负责去实现它.当你的类实现一个协议的方法的时候,你的类就要遵守这个协议.

实际来说,一个协议定义了一个方法的列表,用来对象间建立一个契约,而且并不需要任何特定类的实例.这个契约允许在那些对象间进行通信.一个对象想告诉另外一个对象 它正要面临的事件 或者它想要去询问关于那些事件的建议.

UIApplication 类实现了应用程序中所必需的行为.你不需要去创建一个UIApplication的子类来获取当前应用程序的状态,UIApplication类会通过指定的代理对象的指定方法来传递那些通知的.一个实现了UIApplicationDelegate协议中方法的对象就可以获取那些通知并且提供适当的响应.

你要在一个接口块中来指定你的类要遵守(或者采用)的协议, 一个协议是在你所继承的类名的后面放置一个 尖括号(<...>) ,在尖括号中要放置协议的名字.在下面的一行代码中,是你在你的第一个iOS应用 中指示采用过的 UITextFieldDelegate 协议:

@interface HelloWorldViewController : UIViewController <UITextFieldDelegate> {

你不需要在声明协议方法的实现.

一个协议的声明看上去就像一个类的接口(interface),除了这个协议没有一个父类之外而且他们不定义实例变量(尽管它们可以声明属性). 下面的例子展示了一个简单的协议声明, 它带有一个方法:

@protocol MyProtocol
- (void)myProtocolMethod;
@end

对于许多的代理协议, 采用一个协议就是要实现协议中所定义的方法.有些协议需要你对所支持的协议进行显式地规定, 并且协议支持 必需(required) 和 可选(optional) 要实现的方法.

当你要开始去探索Objective-C框架的头文件时,你很快就会遇到类似下面这一行的语句:

@interface NSDate (NSDateCreation)

这一行通过用圆括号包住类别的名字的这个语法约定来声明了一个类别. 一个类别(category)是一个Objective-C语言的特性,它可以让你不用创建一个类的子类,就可以扩展这个类的接口.在类别中的方法将会成为类的一部分(在你程序的作用域范围内)并且被这个类的所有子类所继承.你可以发送一个消息到这个类(或者它的子类)的一个任何实例当中来调用在类别中所定义的方法.

你可以在头文件中使用多个类别将一些具有不同意义的方法声明进行相关的分组.你甚至可以将不同的类别声明放置在不同的头文件中.框架中的几乎所有头文件中都用到了这项技术,以此来让代码清晰明了.

你还可以使用一个匿名类别,这样协议中的属性和方法,就会被作为已知实现文件(.m)的类扩展中声明的私有属性和私有方法了.一个类扩展(class extension)看上去就像是类别只有圆括号,但是在圆括号中又没有任何文字的那种形式.例如,这里就是一个典型的类扩展(class extension):

@interface MyAppDelegate ()
@property (strong) MyDataObject *data;
@end

预定义类型和编码策略

在Objective-C中有几个保留的文字,你不应该使用他们作为变量的名字.有一些文字用是编译器指令,也就是 用at符号 (@) 做前缀的, 例如 @interface 和 @end .其他保留的词汇就是一些预定义的类型以及这些类型的文字. Objective-C使用了一些你在ANSI C中找不到的预定义类型和这些类型的文字.在某些情况下, 这些类型和类型的文字会取代ANSI C中对应的类型和文字.下面表格中描述了一些重要的类型,包括了每一个类型的正确文字.(文字就是当前类型中可用的值, 下面的否定文字 就是  不同类型中意义为0的值.)

类型

描述和文字

id

动态对象的类型.对于动态和静态类型对象,否定文字都是  nil.

Class

动态class类型. 它的否定文字为 Nil.

SEL

一个选择器的数据类型(typedef);  这个数据结构表示了一个运行时的方法的签名. 它的否定文字是NULL.

BOOL

一个Boolean类型. 它的文字值是YESNO.

你经常会在错误检查和流程控制代码中使用这些预定义的类型和文字.在你程序的控制流程语句中, 你可以测试文字是否适当,以来判断如何继续进行.
例如:

NSDate *dateOfHire = [employee dateOfHire];
if (dateOfHire != nil) {
    // handle this case
}

要改写这个代码,如果对象dateOfHire表示不是nil - 换句话说,它是一个有效的对象-然后在某些方向上用逻辑处理它.
下面这段代码和上面的代码效果相同,是它的简化版:

NSDate *dateOfHire = [employee dateOfHire];
if (dateOfHire) {
    // handle this case
}

你甚至可以进一步地来减少所编写的代码行数(假设你不需要将返回值引用到dateOfHire 对象中):

if ([employee dateOfHire]) {
    // handle this case
}

你已同样的方式来处理Boolean的值. 在这个例子中,isEqual: 方法返回一个Boolean值.

BOOL equal = [objectA isEqual:objectB];
if (equal == YES) {
    // handle this case
}

你可以像先前省略nil的那种方式,来省略这个(YES),这样你就能够让这个代码更少.

在Objective-C中,你可以调用一个消息到nil中,它会没有任何不好的影响.实际上,根本就不会有任何的效果, 除了在运行时方法会返回nil(如果这个方法是要返回一个对象的).发送消息到nil的所得到返回值只要是一个对象类型,代码就可以保证正常工作.

在Objective-C中,还有两个重要的保留词汇,selfsuper.第一个词汇, self, 你可以使用它在消息实现内部去引用到当前对象;它等同于C++的this.你可以用保留词汇super 来替换self, 但是它只能作为一个消息表达式的接收者.如果你发送一个消息到self, 运行时首先会当前对象所在的类中方法的实现; 如果它没有在这里找到方法, 他就会到它的父类查看 (或者 父类的父类 如此反复.).如果你发送一个消息到super, 运行时首先查看父类的方法的实现.

selfsuper主要是为了发送消息.当你所要调用的方法是通过self 所在的类实现的,你发送一个消息到 self. 例如:

[self doSomeWork];

self 还可以用点表达式来调用存取器方法(通过一个已声明的属性合成[synthesize]的),例如:

NSString *theName = self.name;

在重载(也就是重新实现)了父类所继承的方法中,你经常要发送一个消息到super.在这种情况下,这个被调用的方法和被重载的方法具有一样的签名(也就是子类重载的方法和父类对应的方法签名相同).

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值