block的学习(block和timer的循环引用问题)

一、什么是回调函数?

回调函数,本质上也是个函数(搁置函数和方法的争议,就当这二者是一回事)。由“声明”、“实现”、“调用”三部分组成。

在上面的例子中,我可以看出,函数amount(其实是Block),的声明和调用在A类中,而实现部分在B类中。也就是说,B类实现了amount函数,但并没有权限调用,最终还是 由A类触发调用。我们称这样的机制为“回调”。意思是“虽然函数的实现写在B类中,但是真正的调用还是得由A类来完成。”正常函数“函数声明、实现均在一个类中完成。”

一句大白话理解“回调”的概念:“函数的实现部分虽然不在老家(A类),但是最终的调用还是由老家人完成”,这样的函数就叫做回调函数。“老家人调用你,就叫回调,因为你本来就属于老家。

用《无间道》理解“回调函数”概念:

香港警务处(类):

招聘了一名警察张三(声明函数),并培养、训练他(实现函数)。

招聘了一名警察陈仁贵(声明函数),但并没有培养他,而是被送进了三合会。但有任务的时候,警务处会调用陈仁贵(回调函数)。

廉政总署(类):使用警务处的张三(普通调用)。

三合会(类):培养、训练陈仁贵(实现函数)。

第二个问题:什么情况下使用回调函数?

假设有A、B两个类。

(1)A类有多种形态,要在B类中实现回调函数。如假设A类是网络请求开源类ASIHttpRequest,它可能请求成功,也可能请求失败。这个时候,B类就要针对以上两个情况,作不同的处理。

(2)A类的形态由B类决定时,要在B类中实现回调函数。如UITableView类就会提供很多回调函数(iOS专业术语称“委托”方法)

(3)A类需要向B类传递数据时,可以在B类中实现回调函数(A类一般是数据层比较耗时的操作类)。如举的那个发工资的例子。在实际编程中,这样的机制有个好处就是可以提升用户的操作体验。比如用户从X页面跳转到Y页面,需要向网络请求数据,而且比较耗时,那我们怎么办?有三种方案:第一种就是在X页面展示一个旋转指示器,当收到网络传回的数据时,在展现Y页面。第二种就是使用回调函数。用户从X页面直接跳转到Y页面,Y页面需要到数据让数据层去执行,当收到数据时,再在Y页面展现。第三种就是在Y页面中开启多线程。让一个子线程专门到后台去取数据。综合来说,第二种更加简介易懂,而且代码紧凑。

第三个问题:使用回调函数有什么好处?

(1)可以让实现方,根据回调方的多种形态进行不同的处理和操作。(ASIHttpRequest)

(2)可以让实现方,根据自己的需要定制回调方的不同形态。(UITableView)

(3)可以将耗时的操作隐藏在回调方,不影响实现方其它信息的展示。

(4)让代码的逻辑更加集中,更加易读。

什么是回调函数?——就是由声明函数的类来调用的函数叫做回调函数。普通函数可以让任何类调用。

“回调”的主语是谁?——声明“回调函数”的那个类。

Block、委托、通知、回调函数,它们虽然名字不一样,但是原理都一样,都是“回调机制”的思想的具体实现!

二、block 注意事项
1,block 在实现时就会对它引用到的它所在方法中定义的栈变量进行一次只读拷贝,然后在 block 块内使用该只读拷贝。

如下代码:

- (void)testAccessVariable
{
    NSInteger outsideVariable = 10;
    //__block NSInteger outsideVariable = 10;
    NSMutableArray * outsideArray = [[NSMutableArray alloc] init];

    void (^blockObject)(void) = ^(void){
        NSInteger insideVariable = 20;
        KSLog(@"  > member variable = %d", self.memberVariable);
        KSLog(@"  > outside variable = %d", outsideVariable);
        KSLog(@"  > inside variable = %d", insideVariable);

        [outsideArray addObject:@"AddedInsideBlock"];
    };

    outsideVariable = 30;
    self.memberVariable = 30;

    blockObject();

    KSLog(@"  > %d items in outsideArray", [outsideArray count]);
}

输出结果为:

member variable = 30
outside variable = 10
inside variable = 20
1 items in outsideArray

注意到没?outside 变量的输出值为10,虽然outside变量在定义 block 之后在定义 block 所在的方法 testAccessVariable 中被修改为 20 了。这里的规则就是:blockObject 在实现时会对 outside 变量进行只读拷贝,在 block 块内使用该只读拷贝。因此这里输出的是拷贝时的变量值 10。如果,我们想要让 blockObject 修改或同步使用 outside 变量就需要用 __block 来修饰 outside 变量。
说法二:(此处不能修改的原因是在编译期间确定的,编译器编译的时候把a的值复制到block作为一个新变量(假设是a‘ = 10),此时a’和a是没有关系的。这个地方就是函数中的值传递。如果要修改就要加关键字:__block或者static)

__block NSInteger outsideVariable = 10;

注意:

a),在上面的 block 中,我们往 outsideArray 数组中添加了值,但并未修改 outsideArray 自身,这是允许的,因为拷贝的是 outsideArray 自身。

b),对于 static 变量,全局变量,在 block 中是有读写权限的,因为在 block 的内部实现中,拷贝的是指向这些变量的指针。

c), __block 变量的内部实现要复杂许多,__block 变量其实是一个结构体对象,拷贝的是指向该结构体对象的指针。

2,非内联(inline) block 不能直接访问 self,只能通过将 self 当作参数传递到 block 中才能使用,并且此时的 self 只能通过 setter 或 getter 方法访问其属性,不能使用句点式方法。但内联 block 不受此限制。

typedef NSString* (^IntToStringConverter)(id self, NSInteger paramInteger);
- (NSString *) convertIntToString:(NSInteger)paramInteger
                 usingBlockObject:(IntToStringConverter)paramBlockObject
{
    return paramBlockObject(self, paramInteger);
}

typedef NSString* (^IntToStringInlineConverter)(NSInteger paramInteger);
- (NSString *) convertIntToStringInline:(NSInteger)paramInteger
                 usingBlockObject:(IntToStringInlineConverter)paramBlockObject
{
    return paramBlockObject(paramInteger);
}

IntToStringConverter independentBlockObject = ^(id self, NSInteger paramInteger) {
    KSLog(@" >> self %@, memberVariable %d", self, [self memberVariable]);

    NSString *result = [NSString stringWithFormat:@"%d", paramInteger];
    KSLog(@" >> independentBlockObject %@", result);
    return result;
};

- (void)testAccessSelf
{
    // Independent
    //
    [self convertIntToString:20 usingBlockObject:independentBlockObject];

    // Inline
    //
    IntToStringInlineConverter inlineBlockObject = ^(NSInteger paramInteger) {
        KSLog(@" >> self %@, memberVariable %d", self, self.memberVariable);

        NSString *result = [NSString stringWithFormat:@"%d", paramInteger];
        KSLog(@" >> inlineBlockObject %@", result);
        return result;
    };
    [self convertIntToStringInline:20 usingBlockObject:inlineBlockObject];
}

3,使用 weak–strong dance 技术来避免循环引用

在第二条中,我提到内联 block 可以直接引用 self,但是要非常小心地在 block 中引用 self。因为在一些内联 block 引用 self,可能会导致循环引用。如下例所示:

@interface KSViewController ()
{
    id _observer;
}

@end

@implementation KSViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    KSTester * tester = [[KSTester alloc] init];
    [tester run];

    _observer = [[NSNotificationCenter defaultCenter]
                 addObserverForName:@"TestNotificationKey"
                 object:nil queue:nil usingBlock:^(NSNotification *n) {
                     NSLog(@"%@", self);
                 }];
}

- (void)dealloc
{
    if (_observer) {
        [[NSNotificationCenter defaultCenter] removeObserver:_observer];
    }
}

在上面代码中,我们添加向通知中心注册了一个观察者,然后在 dealloc 时解除该注册,一切看起来正常。但这里有两个问题:

a) 在消息通知 block 中引用到了 self,在这里 self 对象被 block retain,而 _observer 又 retain 该 block的一份拷贝,通知中心又持有 _observer。因此只要 _observer 对象还没有被解除注册,block 就会一直被通知中心持有,从而 self 就不会被释放,其 dealloc 就不会被调用。而我们却又期望在 dealloc 中通过 removeObserver 来解除注册以消除通知中心对 _observer/block 的 retain。

b) 同时,_observer 是在 self 所在类中定义赋值,因此是被 self retain 的,这样就形成了循环引用。

上面的过程 a) 值得深入分析一下:

苹果官方文档中对 addObserverForName:object:queue:usingBlock: 中的 block 变量说明如下:

The block is copied by the notification center and (the copy) held until the observer registration is removed.

因此,通知中心会拷贝 block 并持有该拷贝直到解除 _observer 的注册。在 ARC 中,在被拷贝的 block 中无论是直接引用 self 还是通过引用 self 的成员变量间接引用 self,该 block 都会 retain self。

这两个问题,可以用 weak–strong dance 技术来解决。该技术在 WWDC 中介绍过:2011 WWDC Session #322 (Objective-C Advancements in Depth)


    __weak KSViewController * wself = self;
    _observer = [[NSNotificationCenter defaultCenter]
                 addObserverForName:@"TestNotificationKey"
                 object:nil queue:nil usingBlock:^(NSNotification *n) {
                     KSViewController * sself = wself;
                     if (sself) {
                         NSLog(@"%@", sself);
                     }
                     else {
                         NSLog(@"<self> dealloc before we could run this code.");
                     }
                 }];

下面来分析为什么该手法能够起作用。
首先,在 block 之前定义对 self 的一个弱引用 wself,因为是弱引用,所以当 self 被释放时 wself 会变为 nil;然后在 block 中引用该弱应用,考虑到多线程情况,通过使用强引用 sself 来引用该弱引用,这时如果 self 不为 nil 就会 retain self,以防止在后面的使用过程中 self 被释放;然后在之后的 block 块中使用该强引用 sself,注意在使用前要对 sself 进行了 nil 检测,因为多线程环境下在用弱引用 wself 对强引用 sself 赋值时,弱引用 wself 可能已经为 nil 了。

通过这种手法,block 就不会持有 self 的引用,从而打破了循环引用。

扩展:其他还需要注意避免循环引用的地方

与此类似的情况还有 NSTimer。苹果官方文档中提到”Note in particular that run loops retain their timers, so you can release a timer after you have added it to a run loop.”,同时在对接口

  • (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

的 target 说明文档中提到:

The object to which to send the message specified by aSelector when the timer fires. The target object is retained by the timer and released when the timer is invalidated.

结合这两处文档说明,我们就知道只要重复性 timer 还没有被 invalidated,target 对象就会被一直持有而不会被释放。因此当你使用 self 当作 target 时,你就不能期望在 dealloc 中 invalidate timer,因为在 timer 没有被invalidate 之前,dealloc 绝不会被调用。因此,需要找个合适的时机和地方来 invalidate timer,但绝不是在 dealloc 中。

4,block 内存管理分析

block 其实也是一个 NSObject 对象,并且在大多数情况下,block 是分配在栈上面的,只有当 block 被定义为全局变量或 block 块中没有引用任何 automatic 变量时,block 才分配在全局数据段上。 __block 变量也是分配在栈上面的。

在 ARC 下,编译器会自动检测为我们处理了 block 的大部分内存管理,但当将 block 当作方法参数时候,编译器不会自动检测,需要我们手动拷贝该 block 对象。幸运的是,Cocoa 库中的大部分名称中包含”usingBlock“的接口以及 GCD 接口在其接口内部已经进行了拷贝操作,不需要我们再手动处理了。但除此之外的情况,就需要我们手动干预了。

- (id) getBlockArray
{
    int val = 10;
    return [[NSArray alloc] initWithObjects:
            ^{ KSLog(@"  > block 0:%d", val); },    // block on the stack
            ^{ KSLog(@"  > block 1:%d", val); },    // block on the stack
            nil];

//    return [[NSArray alloc] initWithObjects:
//            [^{ KSLog(@"  > block 0:%d", val); } copy],    // block copy to heap
//            [^{ KSLog(@"  > block 1:%d", val); } copy],    // block copy to heap
//            nil];
}

- (void)testManageBlockMemory
{
    id obj = [self getBlockArray];
    typedef void (^BlockType)(void);
    BlockType blockObject = (BlockType)[obj objectAtIndex:0];
    blockObject();
}

执行上面的代码中,在调用 testManageBlockMemory 时,程序会 crash 掉。因为从 getBlockArray 返回的 block 是分配在 stack 上的,但超出了定义 block 所在的作用域,block 就不在了。正确的做法(被屏蔽的那段代码)是在将 block 添加到 NSArray 中时先 copy 到 heap 上,这样就可以在之后的使用中正常访问。

在 ARC 下,对 block 变量进行 copy 始终是安全的,无论它是在栈上,还是全局数据段,还是已经拷贝到堆上。对栈上的 block 进行 copy 是将它拷贝到堆上;对全局数据段中的 block 进行 copy 不会有任何作用;对堆上的 block 进行 copy 只是增加它的引用记数。

如果栈上的 block 中引用了__block 类型的变量,在将该 block 拷贝到堆上时也会将 __block 变量拷贝到堆上如果该 __block 变量在堆上还没有对应的拷贝的话,否则就增加堆上对应的拷贝的引用记数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
C 语言编程常见问题解答 【作者】[美]Paul S.R. Chisholm 译:张芳妮 吕 波 【出版社】清华大学出版社 C语言编程常见问题解答(目录) 第l章 C语言 1. 1 什么是局部程序块(local block)? 1. 2 可以把变量保存在局部程序块中吗? 1. 3 什么时候用一条switch语句比用多条if语句更好? 1. 4 switch语句必须包含default分支吗? 1. 5 switch语句的最后—个分支可以不要break语句吗? 1. 6 除了在for语句中之外,在哪些情况下还要使用逗号运算? 1. 7 怎样才能知道循环是否提前结束了? 1. 8 goto,longjmp()和setjmp()之间有什么区别? 1. 9 什么是左值(lvaule)? 1. 10 数组(array)可以是左值吗? 1. 11 什么是右值(rvaule)? 1. 12 运算符的优先级总能保证是“自左至右”或“自右至左”的顺序吗? 1. 13 ++var和var++有什么区别? 1. 14 取模运算符(modulusoperator)“%”的作用是什么? 第2章 变量和数据存储 2. 1 变量存储在内存(memory)中的什么地方? 2. 2 变量必须初始化吗? 2. 3 什么是页抖动(pagethrashing)? 2. 4 什么是const指针? 2. 5 什么时候应该使用register修饰符?它真的有用吗? 2. 6 什么时候应该使用volatile修饰符? 2. 7 一个变量可以同时被说明为const和volatile吗? 2. 8 什么时候应该使用const修饰符? 2. 9 浮点数比较(floating—point comparisons)的可靠性如何? 2. 10 怎样判断一个数字型变量可以容纳的最大值? 2. 11 对不同类型的变量进行算术运算会有问题吗? 2. 12 什么是运算符升级(operator promotion)? 2. 13 什么时候应该使用类型强制转换(typecast)? 2. 14 什么时候不应该使用类型强制转换(typecast)? 2. 15 可以在头文件中说明或定义变量吗? 2. 16 说明一个变量和定义一个变量有什么区别? 2. 17 可以在头文件中说明static变量吗? 2.18 用const说明常量有什么好处? 第3章 排序与查找 排 序 查 找 排序或查找的性能 3.1 哪一种排序方法最方便? 3.2 哪一种排序方法最快? 3.3 当要排序的数据集因太大而无法全部装入内存时,应怎样排序? 3.4 哪一种查找方法最方便? 3.5 哪一种查找方法最快? 3.6 什么是哈希查找? 3.7 怎样对链表进行排序? 3.8 怎样查找链表中的数据? 第4章 数据文件 4.1 当errno为一个非零值时,是否有错误发生? 4.2 什么是流(stream)? 4.3 怎样重定向—个标准流? 4.4 怎样恢复一个重定向了的标准流? 4.5 stdout能被强制打印到非屏幕设备上吗? 4.6 文本模式(text mode)和二进制模式(binary mode)有什么区别? 4.7 怎样判断是使用流函数还是使用低级函数? 4.8 怎样列出某个目录下的文件? 4.9 怎样列出—个文件的日期和时间? 4.10 怎样对某个目录下的文件名进行排序? 4.1l 怎样判断一个文件的属性? 4.12 怎样查看PATH环境变量? 4.13 怎样打开一个同时能被其它程序修改的文件? 4.14 怎样确保只有你的程序能存取一个文件? 4.15 怎样防止其它程序修改你正在修改的那部分文件内容? 4.16 怎样—次打开20个以上的文件? 4.17 怎样避开"Abort,Retry,Fail"消息? 4.18 怎样读写以逗号分界的文本? 第5章 编译预处理 5.1 什么是宏(macro)?怎样使用宏? 5.2 预处理程序(preprocessor)有什么作用? 5.3 怎样避免多次包含同—个头文件? 5.4 可以用#include指令包含类型名不是“.h”的文件吗? 5.5 用#define指令说明常量有什么好处? 5.6 用enum关键字说明常量有什么好处? 5.7 与用#define指令说明常量相比,用enum关键字说明常量有什么好处? 5.8 如何使部分程序在演示版中失效? 5.9 什么时候应该用宏代替函数? 5.10 使用宏更好,还是使用函数更好? 5.11 在程序中加入注释的最好方法是什么? 5.12 #include<file>和#include“file”有什么不同? 5.13 你能指定在编译时包含哪一个头文件吗? 5.14 包含文件可以嵌套吗? 5.15 包含文件最多可以嵌套几层? 5.16 连接运算符“##”有什么作用? 5.17 怎样建立对类型敏感的宏? 5.18 什么是标准预定义宏? 5.19 怎样才能使程序打印出发生错误的行号? 5.20 怎样才能使程序打印出发生错误的源文件名? 5.2l 怎样判断一个程序是用C编译程序环是用C++编译程序编译的? 5.22 预处理指令#pragma有什么作用? 5.23 #line有什么作用? 5.24 标准预定义宏_FILE_有什么作用? 5.25 怎样在程序中打印源文件名? 5.26 标准预定义宏_LINE_有什么作用? 5.27 怎样在程序中打印源文件的当前行号? 5.28 标准预定义宏_DATE_和_TIME_有什么作用? 5.29 怎样在程序中打印编译日期和时间? 5.30 怎样判断一个程序是否遵循ANSIC标准? 5.31 怎样取消一个已定义的宏? 5.32 怎样检查一个符号是否已被定义? 5.33 C语言提供哪些常用的宏? 第6章 字符串操作 6.l 串拷贝(strcpy)和内存拷贝(memcpy)有什么不同?它们适合于在哪种情况下使用? 6.2 怎样删去字符串尾部的空格? 6.3 怎样删去字符串头部的空格? 6.4 怎样使字符串右对齐? 6.5 怎样将字符串打印成指定长度? 6.6 怎样拷贝字符串的一部分? 6.7 怎样将数字转换为字符串? 6.8 怎样将字符串转换为数字? 6.9 怎样打印字符串的一部分? 6.10 怎样判判断两个字符串是否相同? 第7章 指针和内存分配 7.1 什么是间接引用(indirection)? 7.2 最多可以使用几层指针? 7.3 什么是空指针? 7.4 什么时候使用空指针? 7.5 什么是void指针? 7.6 什么时候使用void指针? 7.7 两个指针可以相减吗?为什么? 7.8 把一个值加到一个指针上意味着什么? 7.9 NULL总是被定义为0吗? 7.10 NULL总是等于0吗? 7.11 用指针作if语句的条件表达式意味着什么? 7.12 两个指针可以相加吗?为什么? 7.13 怎样使用指向函数的指针? 7.14 怎样用指向函数的指针作函数的参数? 7.15 数组的大小可以在程序运行时定义吗? 7.16 用malloc()函数更好还是用calloc()函数更好? 7.17 怎样说明一个大于64KB的数组? 7.18 far和near之间有什么区别? 7.19 什么时候使用far指针? 7.20 什么是栈(stack)? 7.21 什么是堆(heap)? 7.22 两次释放一个指针会导致什么结果? 7.23 NULL和NUL有什么不同? 7.24 为什么不能给空指针赋值?什么是总线错误、内存错误和内存信息转储? 7.25 怎样确定一块已分配的内存的大小? 7.26 free()函数是怎样知道要释放的内存块的大小的? 7.27 可以对void指针进行算术运算吗? 7.28 怎样打印一个地址? 第8章 函数 8.1 什么时候说明函数? 8.2 为什么要说明函数原型? 8.3 一个函数可以有多少个参数? 8.4 什么是内部函数? 8.5 如果一个函数没有返回值,是否需要加入return语句? 8.6 怎样把数组作为参数传递给函数? 8.7 在程序退出main()函数之后,还有可能执行一部分代码吗? 8.8 用PASCAL修饰符说明的函数与普通C函数有什么不同? 8.9 exit()和return有什么不同? . 第9章 数组 9.1 数组的下标总是从0开始吗? 9.2 可以使用数组后面第—个元素的地址吗? 9.3 为什么要小心对待位于数组后面的那些元素的地址呢? 9.4 在把数组作为参数传递给函数时,可以通过sizeof运算符告诉函数数组的大小吗? 9.5 通过指针或带下标的数组名都可以访问数组中的元素,哪一种方式更好呢? 9.6 可以把另外一个地址赋给一个数组名吗? 9.7 array_name和&array;_name有什么不同? 9.8 为什么用const说明的常量不能用来定义一个数组的初始大小? 9.9 字符串和数组有什么不同? 第10章 位(bit)和字节(byte) 10.1 用什么方法存储标志(flag)效率最高? 10.2 什么是“位屏蔽(bit masking)”? 10.3 位域(bit fields)是可移植的吗? 10.4 移位和乘以2这两种方式中哪一种更好? 10.5 什么是高位字节(high-order byte)和低位字节(low-order byte)? 10.6 16位和32位的数是怎样存储的? 第11章 调试 11.1 如果我运行的程序挂起了,应该怎么办? 11.2 如何检测内存漏洞(leak)? 11.3 调试程序的最好方法是什么? 11.4 怎样调试TSR程序? 11.5 怎样获得一个能报告条件失败的程序? 第12章 标准库函数 12.1 为什么应该使用标准库函数而不要自己编写函数? 12.2 为了定义我要使用的标准库函数,我需要使用哪些头文件? 12.3 怎样编写参数数目可变的函数? 12.4 独立(free—standing)环境和宿主(hosted)环境之间有什么区别? 12.5 对字符串进行操作的标准库函数有哪些? 12.6 对内存进行操作的标准库函数有哪些? 12.7 怎样判断一个字符是数字、字母或其它类别的符号? 12.8 什么是“局部环境(locale)”? 12.9 有没有办法从一个或多个函数中跳出? 12.10 什么是信号(signal)?用信号能做什么? 12.11 为什么变量名不能以下划线开始? 12.12 为什么编译程序提供了两个版本的malloc()函数? 12.13 适用于整数和浮点数的数学函数分别有哪些? 12.14 什么是多字节字符(multibyte characters)? 12.15 怎样操作由多字节字符组成的字符串? 第13章 时间和日期 13.1 怎样把日期存储到单个数字中?有这方面的标准吗? 13.2 怎样把时间存储到单个数字中?有这方面的标准吗? 13.3 为什么定义了这么多不同的时间标准? 13.4 存储日期的最好方法是哪一种? 13.5 存储时间的最好方法是哪一种? 第14章 系统调用 14.1 怎样检查环境变量(environment variables)的值? 14.2 怎样在程序中调用DOS函数? 14.3 怎样在程序中调用BIOS函数? 14.4 怎样在程序中存取重要的DOS内存位置? 14.5 什么是BIOS? 14.6 什么是中断? 14.7 使用ANSI函数和使用BIOS函数,哪种方式更好? 14.8 可以通过BIOS把显示模式改为VGA图形模式吗? 14.9 运算符的优先级总能起作用吗(从左至右,从右至左)? 14.10 函数参数的类型必须在函数头部或紧跟在其后说明吗?为什么? 14.11 程序应该总是包含main()的一个原型吗? 14.12 main()应该总是返回一个值吗? 14.13 可以通过BIOS控制鼠标吗? 第15章 可移植性 15.1 编译程序中的C++扩充功能可以用在C程序中吗? 15.2 C++和C有什么区别? 15.3 在C程序中可以用“∥”作注释符吗? 15.4 char,short,int和long类型分别有多长? 15.5 高位优先(big-endian)与低位优先(little—endian)的计算机有什么区别? 第16章 ANSI/ISO标准 16.1 运算符的优先级总能起作用吗? 16.2 函数参数类型必须在函数参数表中或紧跟其后的部分中说明吗? 16.3 程序中必须包含main()的原型吗? 16.4 main()应该总是返回一个值吗? 第17章 用户界面——屏幕和键盘 17.1 为什么直到程序结束时才看到屏幕输出? 17.2 怎样在屏幕上定位光标? 17.3 向屏幕上写数据的最简单的方法是什么? 17.4 向屏幕上写文本的最快的方法是什么? 17.5 怎样防止用户用Ctr+Break键中止程序的运行? 17.6 怎样才能只得到一种特定类型的数据,例如字符型数据? 17.7 为什么有时不应该用scanf()来接收数据? 17.8 怎样在程序中使用功能键和箭头键? 17.9 怎样防止用户向一个内存区域中输入过多的字符? 17.10 怎样用0补齐一个数字? 17.11 怎样才能打印出美元一美分值? 17.12 怎样按科学记数法打印数字? 17.13 什么是ANSI驱动程序? 17.14 怎样通过ANSI驱动程序来清屏? 17.15 怎样通过ANSI驱动程序来存储光标位置? 17.16 怎样通过ANSI驱动程序来恢复光标位置? 17.17 怎样通过ANSI驱动程序来改变屏幕颜色? 17.18 怎样通过ANSI驱动程序来写带有颜色的文本? 17.19 怎样通过ANSI驱动程序来移动光标? 第18章 程序的编写和编译 18.1 程序是应该写成一个源文件还是多个源文件? 18.2 各种存储模式之间有什么区别? 18.3 最常使用的存储模式有哪些? 18.4 应该使用哪种存储模式? 18.5 怎样生成一个".COM"文件? 18.6 ".COM"文件有哪些地方优于".EXE"文件? 18.7 当一个库被连接到目标上时,库中的所有函数是否都会被加到一个".EXE"文件中? 18.8 可以把多个库函数包含在同一个源文件中吗? 18.9 为什么要建立一个库? 18.10 如果一个程序包含多个源文件,怎样使它们都能正常工作? 18.11 连接过程中出现"DGROUP:group exceeds 64K"消息是怎么回事? 18.12 怎样防止程序用尽内存? 18.13 如果程序太大而不能在DOS下运行,怎样才能使它在DOS下运行呢? 18.14 怎样才能使DOS程序获得超过640KB的可用内存呢? 18.15 近程型(near)和远程型(far)的区别是什么? 第19章编程风格和标准 19.1 可以在变量名中使用下划线吗? 19.2 可以用变量名来指示变量的数据类型吗? 19.3 使用注释会影响程序的速度、大小或效率吗? 19.4 使用空白符会影响程序的速度、大小或效率吗? 19.5 什么是骆驼式命名法? 19.6 较长的变量名会影响程序的速度、大小或效率吗? 19.7 给函数命名的正确方法是什么? 19.8 使用大括号的正确方法是什么? 19.9 一个变量名应该使用多少个字母?ANSI。标准允许有多少个有效字符? 19.10 什么是匈牙利式命名法?应该使用它吗? 19.11 什么是重复处理(iterative processing)? 19.12 什么是递归(recursion)?怎样使用递归? 19.13 在C语言中,表示真和假的最好方法是什么? 19.14 空循环(null loops)和无穷循环(infinite loops)有什么区别? 19.15 continue和break有什么区别? 第20章 杂项(Miscellaneous) 20.1 怎样获得命令行参数? 20.2 程序总是可以使用命令行参数吗? 20.3“异常处理(exception handling)”和“结构化异常处理(structured exception handling)”有什么区别? 20.4 怎样在DOS程序中建立一个延时器(delay timer)? 20.5 Kernighan和Ritchie是谁? 20.6 怎样产生随机数? 20.7 什么时候应该使用32位编译程序? 20.8 怎样中断一个Windows程序? 20.9 为什么要使用静态变量? 20.10 怎样在一个程序后面运行另一个程序? 20.11 怎样在一个程序执行期间运行另一个程序? 20.12 怎样把数据从一个程序传给另一个程序? 20.13 怎样判断正在运行的程序所在的目录? 20.14 怎样找到程序中的重要文件(数据库,配置文件,等等)? 20.15 本书的有些例子程序有许多缺陷,为什么不把它们写得更好? 20.16 怎样使用Ctr+Break失效? 20.17 可以使热启动(Ctrl+Alt+Delete)失效吗? 20.18 怎样判断一个字符是否是一个字母? 20.19 怎样判断一个字符是否是一个数字? 20.20 怎样把一个十六进制的值赋给一个变量? 20. 21 怎样把一个八进制的值赋给一个变量? 20.22 什么是二进制? 20.23 什么是八进制? 20.24 什么是十六进制? 20.25 什么是换码符(escape characters)? 附 录 常用函数的包含文件

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值