热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

iOSBlock浅浅析

Block真的难,笔者静下心来读《Objective-C高级编程iOS与OSX多线程和内存管理》,读

Block真的难,笔者静下心来读《Objective-C 高级编程 iOS与OS X多线程和内存管理》,读的时候顺便记录下来自己的心得,方便以后再翻回,也希望能带给大家一些帮助。

本文将以一个菜dog的角度,从 Block 不截获变量、截获变量不修改、截获并修改变量 、 截获对象 四个层次 浅浅探究Block的实现。

Block的语法就不回顾了,不好记Block语法可以翻这篇 How Do I Declare A Block in Objective-C? 。

Block实现

转成C++ 的源代码学习,笔者加了适当的注释方便理解。

不截获自动变量值

int main()
{
    void (^blk)(void) = ^{printf("Block\n");};
    
    blk();
    
    retrun 0;
}

将转为

// block中通用的成员变量 结构体
// 文章后面的代码不再给出,但都有用到
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 代表Block 的结构体
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成员变量
    struct __main_block_desc_0* Desc;// block 的大小
    
    // 构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 原本的代码块 转到一个C函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    printf("Block\n");
}

// 计算block大小的结构体
// 声明的同时,初始化一个变量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

int main()
{
    // 声明定义block
    // 用到了构造函数方法
    void (*blk)(void) = (void (*)(void))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
    /*
        相当于以下
        struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
        
        struct __main_block_impl_0 *blk = &tmp;
        
        栈上生成的结构体实例的指针,赋值给变量blk。
    */
    
    // 调用block
    // 第一个参数为 blk_>FuncPtr,即C函数
    // 第二个参数为 blk本身
    ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
    /*
        相当于以下
        普通的C函数调用
        (*blk->impl.FuncPtr)(blk);
    */
    
    return 0;
}

即把原本的代码块,转到一个C函数中。并且创建一个 代表Block 的结构体,最后一个构造函数,Block对象把函数和成员绑定起来。

截获自动变量不修改的情况

和以上区别在于,Block结构体中的成员变量多了截获的自动变量,并且构造函数参数也是。

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val);};
    
    val = 2;
    fmt = "These values were changed.val = %d\n";
    
    blk();
    
    return 0;
}

将转为

// 跟上面一样
struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

// 代表Block 的结构体
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成员变量
    struct __main_block_desc_0* Desc;// block 的大小
    
    // 截获的自动变量
    // 结构体中有名字一样的成员变量
    const char *fmt;
    int val;
    
    // 构造函数
    // 参数多了截获的自动变量
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags = 0) : fmt(_fmt), val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 原本的代码块 转到一个C函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    const char *fmt = __cself->fmt;
    int val = __cself->val;
    
    printf(fmt, val);
}

// 计算block大小的结构体
// 声明的同时,初始化一个变量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

int main()
{
    int dmy = 256;
    int val = 10;
    const char *fmt = "val = %d\n";
    void (*blk)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, fmt, val);
    /*
        结构体初始化如下:
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = 0;
        impl.FuncPtr = __main_block_func_0;
        Desc = &__main_block_desc_0_DATA;
        fmt = "val = %d\n";
        val = 10;
    */
    
    return 0;
}

根据以上,我们知道截获变量后,实质上是Block结构体中有一个成员变量存了起来。调用Block时,是访问取结构体成员变量,而不是外面的局部变量。

iOS Block浅浅析

Block中修改值

Block不允许修改外部变量的值。Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。于是栈区变成了红灯区,堆区变成了绿灯区。

iOS Block不能修改外部变量的值,指的是栈中指针的内存地址。下面举几个例子理解。

  • 非OC对象,修改会编译错误。
int val = 0;
void (^blk)(void) = ^{
    val = 1;
};
  • OC对象,发送消息可以,但改指针内存地址不行。

以下没问题

id array = [[NSMutableArray alloc] init];
    
    void (^blk)(void) = ^{
        id obj = [[NSObject alloc] init];
        [array addObject:obj];
    };

以下编译报错

id array = [[NSMutableArray alloc] init];
    
    void (^blk)(void) = ^{
        array = [[NSMutableArray alloc] init];
    };
  • C 数组 截获自动变量的方法没有实现对 C语言 数组的截获。

以下编译错误

const char text[] = "hello";
    
    void (^blk)(void) = ^{
        printf("%c\n", text[2]);
    };

需改成指针

const char *text = "hello";
    
    void (^blk)(void) = ^{
        printf("%c\n", text[2]);
    };

那么Block 要怎么修改变量呢?

方法一:用到静态或全局变量

  • C 中有一个变量,允许Block改写值。

    • 静态变量
    • 静态全局变量
    • 全局变量
  • 例子

int global_val = 1;// 全局变量
static int static_global_val = 2;// 静态全局变量

int main()
{
    static int static_val = 3;// 静态变量
    
    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    }
    
    return 0;
}

转换后

int global_val = 1;
static int static_global_val = 2;

// 代表Block 的结构体
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成员变量
    struct __main_block_desc_0* Desc;
    // 成员变量只多了静态变量,原因在后面分析
    int *static_val;
    
    // 构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_staitc_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackblock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 原本的代码块 转到一个C函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int *static__val = __cself->static_val;
    
    global_val *= 1;
    static_global_val *= 2;
    (*static_val) *=3;
}

// 计算block大小的结构体
// 声明的同时,初始化一个变量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0)
};

int main()
{
    static int static_val = 3;
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_val);
    
    return 0;
}

为什么成员变量只多了静态变量呢?

这就要先了解 iOS 内存区域。 iOS-MRC与ARC区别以及五大内存区

  • 栈:
    • 由系统管理分配和释放
    • 存放函数参数值,局部变量值
    • iPhone 的栈区只有512K
    • 局部变量在程序运行期间不是一直存在,而是只在函数执行期间存在,函数的一次调用结束后,变量就被撤销,其所占用的内存也被收回。
  • 堆:
    • 由程序猿管理
    • 存放程序猿创建的对象
    • C用malloc/calloc/relloc分配的区域
  • 代码区:
    • 存放函数的二进制代码
  • 全局区(又称静态区):
    • 存放全局变量和静态变量
    • 程序运行时一直存在
    • 由编译器管理(分配释放),程序结束后由系统释放

全局区又分为 BSS段 和 数据段(data)。

BSS段:BSS段(bss segment)通常是指用来存放程序中未初始化的或者初始值为0的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

数据段:数据段(data segment)通常是指用来存放程序中已初始化的全局变量的一块内存区域。数据段属于静态内存分配。

但不同的是C++中,不区分有没有初始化,都放到一块去。

  • 文字常量区
    • 存放常量字符串
    • 为了节省内存,C/C++/OC把常量字符串放到单独的一个内存区域。当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址。
iOS Block浅浅析

再回到刚刚的代码上,为什么block结构体中成员变量只多了静态变量呢?

int global_val = 1;// 全局变量
    static int static_global_val = 2;// 静态全局变量
    static int static_val = 3;// 静态变量

关于它们的区别—— 全局变量/静态全局变量/局部变量/静态局部变量的异同点

静态局部变量虽然程序运行时一直存在,但只对定义自己的函数体始终可见。

编译后,调用block实质上是在 一个新定义的函数 中访问静态局部变量,不能直接访问,所以需要保存其指针。而全局变量可以访问到,所以没有加到成员变量中。

方法二:用到__block 说明符

int main()
{
    __block int val = 10;
    
    void (^blk)(void) = ^{val = 1;};
    
    return 0;
}

转换后

// 变量将会变成的结构体
// 即val不是int类型,变成此结构体实例
struct __Block_byref_val_0 {
    void *__isa;// __block变量转化后所属的类对象
    __Block_byref_val_0 *__forwarding;//指向__block变量自身的指针,后面解释
    int __flags;// 版本号
    int __size;// 结构体大小
    int val;// 原本的int数值
};

// 代表Block 的结构体
struct __main_block_impl_0 {
    struct __block_impl impl;// block通用的成员变量
    struct __main_block_desc_0* Desc;// block 的大小
    __Block_byref_val_0 *val;// val转成成员变量,类型为结构体
    
    // 构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->_forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 原本的代码块 转到一个C函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
    __Block_byref_val_0 *val = __cself->val;
    
    // 这里通过__forwarding赋值?后面解释
    (val->__forwarding->val) = 1;
}

// 当Block从栈复制到堆时
// 通过此函数把截获的__block变量移到堆或者引用数+1
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
    _Block_object_assign(&dst->val, src->val, BLOCK_FIELD_IS_BYREF);
}

// 当Block从堆被废弃时
// 通过此函数把截获的__block变量引用数-1
// 相当于对象的delloc方法
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
    _Block_object_dispose(src->val, BLOCK_FIELD_IS_BYREF);
}

// 计算block大小的结构体
// 该结构体有两个函数
// copy 和 dispose
// 声明的同时,初始化一个变量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};

int main()
{
    /*
        val变成了__Block_byref_val_0结构体实例
    */
    __Block_byref_val_0 val = {
        0,// isa指针
        &val,//forwarding成员,指向自己
        0,// 版本号
        sizeof(__Block_byref_val_0),
        10 //原来int val的值
    };
    
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);
    
    return 0;
}

从main函数中,我们可以发现,Block转换成Block的结构体类型**__main_block_impl_0的自动变量,__block变量val转换为block变量的结构体类型__Block_byref_val_0**的自动变量。它们都在栈上。所以Block的isa指针指向NSConcreteStackBlock。

除了NSConcreteStackBlock,还有两种类型 NSConcreteGlobalBlock 和 NSConcreteMallocBlock。

设置对象的存储域
NSConcreteStackBlock
NSConcreteGlobalBlock 全局区
NSConcreteMallocBlock
  • NSConcreteGlobalBlock 在全局变量的地方生成的Block为NSConcreteGlobalBlock,如下。在全局变量的地方不能使用自动变量,也就不存在截获的问题。
void (^blk)(void) = ^{printf("Global Block\n");};

int main
{
    return 0;
}

另外只要没有截获自动变量,Block类型就是NSConcreteGlobalBlock。

  • NSConcreteMallocBlock 栈上的Block,在出了作用域后会被摧毁,__block变量也是。那么如果我们要在别的地方调用Block,就需要把它们移到堆中,手动管理它们的生命周期。这种Block类型就是NSConcreteMallocBlock。

先来理解为什么有个forwarding指向自己。

试想,Block如果截获了自动变量,然后移到堆上,在别的作用域调用(很常见)。如果__block变量在栈上已经释放了,Block访问__block变量会失败。所以系统需要在Block变成NSConcreteMallocBlock时,截获的__block变量也复制到堆上。

Block什么时候会复制到堆上呢?

  • 调用Block的copy方法
  • 将Block作为函数返回值时
  • 将Block赋值给__strong修饰的变量时
  • 向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时
__block变量的配置存储域 Block从栈赋值到堆时的影响
从栈复制到堆并被Block持有
被Block持有

当Block从栈复制到堆时,__block变量的 forwarding 会重新指向其在堆中的内存地址。

这样,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或对上,都可以顺利地访问同一个__block变量。

笔者在书上刚看到这句话时,有点晕,后来想了一段时间应该是以下意思,如果有误,欢迎大神批斗。

如下代码,有注释

__block int val = 0;
    
    void (^blk)(void) = [^{++val;} copy];
    
    ++val;// 转换为++(val.__forwarding->val);即(栈上的val).__forwarding->val,最终指向堆上的val
    
    blk();// 转换为++(val.__forwarding->val);即(堆上的val).__forwarding->val,最终指向堆上的val
    
    NSLog(@"%d", val);

截获对象

  • __strong 修饰的对象
blk_t blk;

{
    id array = [[NSMutablArray alloc] init];
    blk = [^(id obj) {
        [array addObject:obj];
        NSLog(@"array count = %ld", [array count]);
    } copy];
}

blk([NSObject alloc] init]);
blk([NSObject alloc] init]);
blk([NSObject alloc] init]);

还记得上面提到的截获变量不修改,转为C++,Block结构体中的成员变量多了截获的自动变量。

这里,变量作用域结束时,理论上array被废弃,但执行输出结果为数组count123。

这意味着array超出作用域而存在。

会不会也是Block结构体中的成员变量多了截获的自动变量呢?

转换为C++后

struct __main_block_impl_0 {
    struct __block_impl impl;// Block通用的成员变量
    struct __main_block_desc_0* Desc;// Block的大小
    // 指向数组的成员变量
    id __strong array;
    
    // 构造函数
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc,id __strong _array, int flags=0) : array(_array) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

// 原本的代码块 转到一个C函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
    id __strong array = __cself->array;
    
    [array addObject:obj];
    
    NSLog(@"array count = %ld", [array count]);
}

// 当Block从栈复制到堆时
// 通过此函数把截获的对象引用数+1
// 相当于retain
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src)
{
    _Block_object_assign(&dst->array, src->array, BLOCK_FIELD_IS_OBJECT);
}

// 当Block从堆被废弃时
// 通过此函数把截获的对象release引用数-1
// 相当于对象的delloc方法
static void __main_block_dispose_0(struct __main_block_impl_0 *src)
{
    _Block_object_dispose(src->array, BLOCK_FIELD_IS_OBJECT);
}

// 计算block大小的结构体
// 该结构体有两个函数
// copy 和 dispose
// 声明的同时,初始化一个变量__main_block_desc_0_DATA
static struct __main_block_desc_0 {
    unsigned long reserved;
    unsigned long Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = {
    0,
    sizeof(struct __main_block_impl_0),
    __main_block_copy_0,
    __main_block_dispose_0
};

调用block转换如下。

blk_t blk;

{
    id __strong array = [[NSMutableArray alloc] init];
    
    // 构造函数
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, array, 0x22000000);
    
    blk = [blk copy];
}

// 调用,第一个参数为blk本身,第二个参数为id类型对象
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);
(*blk->impl.FuncPtr)(blk, [[NSObject alloc] init]);

可以看到,和猜测一样,Block结构体中确实多了一个 id __strong array

我们知道,我们写的 C语言结构体不能带有__strong修饰符的变量 。原因是编译器不知道何时进行C语言结构体的初始化和废弃操作。

但是 OC运行时库把握 Block从栈复制到堆以及堆上的Block被 废弃的时机 ,因此Block用结构体中可以 管理好

那么同时用__block 和 __strong 修饰的对象呢?

上面提到过__block int val,val将变为一个结构体,对象也一样。

__block id obj = [[NSObject alloc] init];
// 相当于__block id __strong obj = [[NSObject alloc] init];

转换为

// 对象将会变成的结构体
struct __Block_byref_obj_0 {
    void *__isa;// __block变量转化后所属的类对象
    __Block_byref_val_0 *__forwarding;//指向对象自身的指针,后面解释
    int __flags;// 版本号
    int __size;// 结构体大小
    void (*__Block_byref_id_object_copy)(void*, void*);// retain对象
    void (*__Block_byref_id_object_dispose)(void*);// release对象
    __strong id obj;//指向对象
};

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

// 对象变成了__Block_byref_obj_0结构体实例
__Block_byref_obj_0 obj = {
    0,
    &obj,
    0x2000000,
    sizeof(__Block_byref_obj_0),
    __Block_byref_id_object_copy_131,
    __Block_byref_id_object_dispose_131,
    [[NSObject alloc] init]
};
  • __weak 修饰的对象
blk_t blk;

{
    id array = [[NSMutableArray alloc] init];
    __block id __weak array2 = array;
    
    blk =[^(id obj) {
        [array2 addObject:obj];
        NSLog(@"array count = %ld", [array2 count]);
    } copy];
}

blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);

输出结果为数组数目0。

这是由于array在作用域结束时被释放、废弃,nil被赋值在array2中。

结论:Block中持有weak声明的对象,对象引用数不会增加。

问题

  • Block中是否需要对弱引用的对象强引用?

到底什么时候才需要在ObjC的Block中使用weakSelf/strongSelf

  • Block属性中内存语义用copy 还是strong?

    在ARC下,这两种效果都会把Block 从栈上压到堆上。但事实上,copy更接近Block的本质。

block 使用 copy 是从 MRC 遗留下来的“传统”,在 MRC 中,方法内部的 block 是在栈区的,使用 copy 可以把它放到堆区.在 ARC 中写不写都行:对于 block 使用 copy 还是 strong 效果是一样的,但写上 copy 也无伤大雅,还能时刻提醒我们:编译器自动对 block 进行了 copy 操作。如果不写 copy ,该类的调用者有可能会忘记或者根本不知道“编译器会自动对 block 进行了 copy 操作”,他们有可能会在调用之前自行拷贝属性值。这种操作多余而低效。你也许会感觉我这种做法有些怪异,不需要写依然写。如果你这样想,其实是你“日用而不知”。

  • 在这篇文章iOS-Block本质,看到许多关于Block理解的问题,对照着实现看挺有帮助。

参考

  • [1] Kazuki Sakamoto,Tomohiko Furumoto.Objective-C高级编程 iOS与OS X多线程和内存管理[M].北京:人民邮电出版社,2013:79-136.

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 我们


推荐阅读
  • 本文讨论了clone的fork与pthread_create创建线程的不同之处。进程是一个指令执行流及其执行环境,其执行环境是一个系统资源的集合。在调用系统调用fork创建一个进程时,子进程只是完全复制父进程的资源,这样得到的子进程独立于父进程,具有良好的并发性。但是二者之间的通讯需要通过专门的通讯机制,另外通过fork创建子进程系统开销很大。因此,在某些情况下,使用clone或pthread_create创建线程可能更加高效。 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 预备知识可参考我整理的博客Windows编程之线程:https:www.cnblogs.comZhuSenlinp16662075.htmlWindows编程之线程同步:https ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了基于c语言的mcs51单片机定时器计数器的应用教程,包括定时器的设置和计数方法,以及中断函数的使用。同时介绍了定时器应用的举例,包括定时器中断函数的编写和频率值的计算方法。主函数中设置了T0模式和T1计数的初值,并开启了T0和T1的中断,最后启动了CPU中断。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • C# 7.0 新特性:基于Tuple的“多”返回值方法
    本文介绍了C# 7.0中基于Tuple的“多”返回值方法的使用。通过对C# 6.0及更早版本的做法进行回顾,提出了问题:如何使一个方法可返回多个返回值。然后详细介绍了C# 7.0中使用Tuple的写法,并给出了示例代码。最后,总结了该新特性的优点。 ... [详细]
  • 本文介绍了解决二叉树层序创建问题的方法。通过使用队列结构体和二叉树结构体,实现了入队和出队操作,并提供了判断队列是否为空的函数。详细介绍了解决该问题的步骤和流程。 ... [详细]
  • 本文详细介绍了Java中vector的使用方法和相关知识,包括vector类的功能、构造方法和使用注意事项。通过使用vector类,可以方便地实现动态数组的功能,并且可以随意插入不同类型的对象,进行查找、插入和删除操作。这篇文章对于需要频繁进行查找、插入和删除操作的情况下,使用vector类是一个很好的选择。 ... [详细]
  • Go GUIlxn/walk 学习3.菜单栏和工具栏的具体实现
    本文介绍了使用Go语言的GUI库lxn/walk实现菜单栏和工具栏的具体方法,包括消息窗口的产生、文件放置动作响应和提示框的应用。部分代码来自上一篇博客和lxn/walk官方示例。文章提供了学习GUI开发的实际案例和代码示例。 ... [详细]
  • Go语言实现堆排序的详细教程
    本文主要介绍了Go语言实现堆排序的详细教程,包括大根堆的定义和完全二叉树的概念。通过图解和算法描述,详细介绍了堆排序的实现过程。堆排序是一种效率很高的排序算法,时间复杂度为O(nlgn)。阅读本文大约需要15分钟。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
author-avatar
淘宝休丶闲女-装店
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有