2、在类的头文件中尽量少引入其他头文件

  与C和C++一样,Objective-C也使用“头文件”(header file)与“实现文件”(implementation file)来区隔代码。用Objective-C语言编写“类”(class)的标准方式为:以类名做文件名,分别创建两个文件,头文件后缀用.h,实现文件后缀用.m。创建好一个类之后,其代码看上去如下所示:

  //EOCPerson.h

  #import <Foundation/Foundation.h>

  @interface EOCPerson:NSObject

  @property (nonatomic, copy) NSString *firstName;

  @property (nonatomic, copy) NSString *lastName;

  @end

  //EOCPerson.m

  #import "EOCPerson.h"

  @implementation EOCPerson

  //Implementation of methods

  @end

  用Objective-C语言编写任何类几乎都需要引入Foundation.h。如果不在该类本身引入这个文件的话,那么就要引入与其超类所属框架相对应的“基本头文件”(base header file)。例如,在创建iOS应用程序时,通常会继承UIViewController类,而这些字类的头文件需要引入UIKit.h。

  现在看来,EOCPerson类还好。其头文件引入了整个Foundation框架,不过这并没有问题。如果此类继承自Foundation框架中的某个类,那么EOCPerson类的使用者(consumer)可能会用到其基类中的许多内容。继承自UIViewController的那些类也是如此,其使用者可能会用到UIKit中的大部分内容。

  过段时间,你可能又创建了一个名为EOCEmployer的新类,然后可能觉得每个EOCPerson实例都应该有一个EOCEmployer。于是,直接为其添加一项属性:

  //EOCPerson.h

  #import <Foundation/Foundation.h>

  @interface EOCPerson:NSObject

  @property (nonatomic, copy) NSString *firstName;

  @property (nonatomic, copy) NSString *lastName;

  @property (nonatomic, strong) EOCEmployer *employer;

  @end

  然而这么做有个问题,就是在编译引入了EOCPerson.h的文件时,EOCEmployer类并不可见,不便强迫开发者在引入EOCPerson时必须一并引入EOCEmployer.h,所以,常见的办法是在EOCPerson.h中加入下面这行:

  #import "EOCEmployer.h"

  这种办法可行,但不够优雅。在编译一个使用EOCPerson类的文件时,不需要知道EOCEmployer类的全部细节,只需要知道有一个类名叫EOCEmployer就好。所幸有个办法能把这一情况告诉编译器:

  @class EOCEmployer;

  这叫做“向前声明”(forward declaring)该类。现在EOCPerson的头文件变成了这样:

  //EOCPerson.h

  #import <Foundation/Foundation.h>

 

  @class EOCEmployer;

 

  @interface EOCPerson:NSObject

  @property (nonatomic, copy) NSString *firstName;

  @property (nonatomic, copy) NSString *lastName;

  @property (nonatomic, strong) EOCEmployer *employer;

  @end

  EOCPerson类的实现文件则需引入EOCEmployer类的头文件,因为若要使用后者,则必须知道其所有接口细节。于是,实现文件就是: 

  //EOCPerson.m

  #import "EOCPerson.h"

  #import "EOCEmployer.h"

  @implementation EOCPerson

  //Implementation of methods

  @end

  将引入头文件的时机尽量延后,只在确有需要时才引入,这样就可以减少类的使用者所需引入的头文件数量。假设本例把EOCEmployer.h引入到EOCPerson.h,那么只要引入EOCPerson.h,就会一并引入EOCEmployer.h的所有内容。此过程若持续下去,则要引入许多根本用不到的内容,这当然会增加编译时间。

  向前声明也解决了两个类互相引用的问题。假设要为EOCEmployer类加入新增及删除雇员的方法,那么其头文件中会加入下述定义:

  - (void)addEmployee:(EOCPerson *)person;

  - (void)removeEmployee:(EOCPerson *)person;

  此时,若要编译EOCEmployer,则编译期必须知道EOCPerson这个类,而要编译EOCPerson,则又必需知道EOCEmployer。如果在各自头文件中引入对方的头文件,则会导致“循环饮引用”(chicken-and-egg situation)。当解析其中一个头文件时,使用#import而非#include指令虽然不会导致死循环,但这意味着两个类里有一个无法被正确编译。

  但是有时候必须要在头文件中引入其他头文件。如果你写的类继承自某个超类,则必须引入定义那个超类的头文件。同理,如果要声明你写的类遵从某个协议(protocol),那么该协议必须有完整定义,且不能使用向前声明。向前声明只能告诉编译器有某个协议,而此时编译期却要知道该协议中定义的方法。

  例如,要从图形类中继承一个矩形类,且令其遵循绘制协议:

  //EOCRectangle.h

  #import "EOCShape.h"

  #import "EOCDrawable.h"

  @interface EOCRectangle : EOCShape <EOCDrawable>

  @property (nonatomic, assign) float width;

  @property (nonatomic, assign) float height;

  @end

  第二条#import是难免的。鉴于此,最好是把协议单独放在一个头文件中,要是把EOCDrawable协议放在了某个大的头文件里,那么只要引入此协议,就必定会引入那个文件中的全部内容,如此一来,就像上面说的那样,会产生相互依赖问题,而且还会增加编译时间。

  然而有些协议,例如“委托协议”(delegate protocol),就不用单独写一个头文件了。在那种情况下,协议只有与接收协议委托的类放在一起定义才有意义。此时最好能在实现文件中声明此类实现了该委托协议,并把这段实现放在“class-continuation”分类(class-continuation category)里。这样的话,只要在实现文件中引入包含委托协议的头文件即可,而不需要将其放在公共头文件(public header file)里。

  每次在头文件中引入其他头文件之前,都要先问问自己这样做是否有必要。如果可以用向前声明取代引入,那么就不要用引入,若因为要实现属性、实例变量或者要遵循协议而必须引入头文件,则应尽量将其移至“class-continuation分类”中。这样做不仅可以缩减编译时间,而且还能降低彼此依赖程度。若是依赖关系过于复杂,则会给维护带来麻烦,而且,如果只想把代码的某个部分开发为公用API的话,太复杂的依赖关系也会出问题。

 

*********************************************************************要点

  1、除非确有必要,否则不要引入头文件。一般来说,应在某个类的头文件中使用向前声明来提及别的类,并在实现文件中引入那些类的头文件。这样做可以尽量降低类之间的耦合(coupling)。

  2、有时无法使用向前声明,比如要声明某个类遵循一项协议。这种情况下,尽量把“该类遵循某协议”的这条声明移至“class-continuation分类”中。如果不行的话,就把协议单独放在一个头文件中,然后将其引入。

转载于:https://www.cnblogs.com/liuguanlei/p/5232594.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在Keil使用sprintf函数需要包含stdio.h头文件。它是标准C库头文件之一,提供了一系列输入输出函数的定义,包括printf、scanf、sprintf等。在Keil,需要在程序加入以下代码: ```c #include <stdio.h> ``` 这样就可以使用printf、scanf、sprintf等函数了。同时需要注意,在使用printf函数的时候,需要将输出重定向到UART或者LCD等外设上。 ### 回答2: sprintf是一个可变参数函数,用于将格式化的字符串输出到一个字符数组。在Keil编程,可以通过引入stdio.h头文件来使用sprintf函数。标准输入输出头文件stdio.h是C语言提供的用于输入输出处理的头文件,其包含了一些标准的输入输出函数,如printf,scanf以及sprintf等。 在Keil编程,sprintf函数的使用方法是将输出的格式字符串和要输出的非型参数依次传入该函数,函数会根据格式字符串将非型参数逐个转换成对应的字符串,并按照格式字符串的格式将它们输出到指定的字符数组。 例如: ```c #include <stdio.h> int main() { char str[20]; int num = 123; sprintf(str, "The number is %d", num); printf("%s", str); return 0; } ``` 上述代码,通过sprintf函数将整数num转换为字符串,并按照格式字符串的格式输出到字符数组str。最后,通过printf函数将字符数组str输出到屏幕上。 需要注意的是,sprintf函数有一定的安全性问题,若格式字符串或参数出错可能会导致内存溢出等问题。在使用sprintf函数时,需要确保格式字符串和参数的正确性,尽量避免出现格式字符串的控制字符与参数型不匹配或参数数量与格式字符串不匹配的情况,以防止程序崩溃或产生未知错误。 ### 回答3: sprintf 是一个 C 库函数,它可以将字符串和其他型的数据格式化成指定格式,并将结果存储在字符串。在 Keil 编译器使用 sprintf 函数,需要包含 <stdio.h> 头文件。 stdio.h 是 C 语言输入输出相关函数及宏定义的头文件。其包含了格式化输出函数 sprintf 的函数原型及必要的宏定义。 sprintf 函数格式如下: int sprintf(char *str, const char *format, ...); 参数解释: - str 是一个字符指针,用来接收格式化后的字符串。 - format 是一个常量字符指针,指定输出格式。 - ... 是格式化参数列表,可以包含多个参数。 sprintf 函数的作用是将 format 格式的数据格式化成字符串,并将结果存储在 str 指向的数组。 例如,可以使用 sprintf 函数将整数格式化成字符串: ``` #include <stdio.h> int main() { char str[20]; int num = 123; sprintf(str, "%d", num); printf("格式化后的字符串: %s\n", str); return 0; } ``` 以上代码将整数 123 格式化成字符串,并存储在 str 数组。最后,通过 printf 函数将格式化后的字符串输出到控制台。 综上所述,sprintf 头文件 <stdio.h> 是 Keil 编译器使用 sprintf 函数所需的头文件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值