精华内容
下载资源
问答
  • 1、先来回答这个奇奇怪怪的名字,为啥叫回调函数,调就调,咋还回调,那先看它是怎么使用的: int main() { F1(F2); } mian函数要调用F1,如果程序立马执行了F1,那么就是正常的调用,但是在这里不是。在这里,F1...

    1、先来回答这个奇奇怪怪的名字,为啥叫回调函数,调就调,咋还回调,那先看它是怎么使用的:

    int main()
    {
        F1(F2);
    }
    

    mian函数要调用F1,如果程序立马执行了F1,那么就是正常的调用,但是在这里不是。在这里,F1里面有个参数:函数指针F2,程序会先去执行F2指向的函数,回头再来执行F1。
    说得通俗点,你妈让你去买菜,但是不知道买什么好,让你去问你奶奶,你问完了再回来买菜。你妈是mian函数,你奶奶就是F2,你是F1,目的就是要得到“菜”这个返回值。所谓回调,就是回头再买。

    2、用是这么用的

    #include <stdio.h>
    
    int function(int a,int (*callback)(int))
    {
    	callback(a);
    	return 0;
    }
    
    int callback_1(int a)
    {
    	printf("callback_1 is %d\n",a);
    	return 0;
    }
    int main(int argc, char* argv[])
    {
    	function(5,callback_1);
    	return 0;
    }
    

    输出结果
    在这里插入图片描述
    3、但是问题来了,上述操作怎么有一种脱裤子放屁的感觉,F2是个定参,完全没体现把函数作为参数的优越性。所以我们要引入一个东西:指针数组。把多个函数指针放在数组里【注册】一下,调用的时候想用哪个用哪个,函数不就成了一个变参了吗?肥肠的方便。

    (1)反手先来三个奶奶函数,等着被注册调用,其实应该叫奶奶的三个锦囊(这个地方在c++里可以重载奶奶,c我就直接写了)

    #include <stdio.h>
    
    int function(int a,int (*callback)(int))
    {
    	callback(a);
    	return 0;
    }
    
    int callback_1(int a)//回调函数1
    {
    	printf("callback_1 is %d\n",a);
    	return 0;
    }
    int callback_2(int a)//回调函数2
    {
    	printf("callback_2 is %d\n",a);
    	return 0;
    }
    int callback_3(int a)//回调函数3
    {
    	printf("callback_3 is %d\n",a);
    	return 0;
    }
    

    (2)现在掏出奶奶82年的酱缸把三个锦囊装起来,你想拿哪个拿哪个

    int (*p[3])();//指针数组,存放函数指针
    p[0] = callback_1;//注册回调函数
    p[1] = callback_2;
    p[2] = callback_3;
    
    

    (3)拿哪个锦囊呢,小孩子才做选择题,我全都要

    int main(int argc, char* argv[])
    {
    	int i;
    	for(i=0;i<3;i++)
    	{
    		function(5,p[i]);
    	}
    	return 0;
    }
    

    (4)菜买好了,可以了,哈拉绍
    在这里插入图片描述

    展开全文
  • C语言——函数指针

    2020-05-20 10:20:25
    由于整型变量只需要定义变量的类型即可完全规定这个变量的格式(个人理解,变量声明限定了变量的格式,变量定义规定了其内容)。但是对于一个函数来说(将函数也看成一种复杂的变量),函数的格式由哪些东西唯一确定...

    目录

    1. 函数指针概念

    1.1 函数指针的声明

    1.2 函数指针的定义

    1.3 使用typedef定义函数指针的别名

    1.4 将常数转换为函数指针

    1.5 函数指针的调用

    1.6 将函数指针作为函数的传入参数

    2. 简单的例子


    1. 函数指针概念

    1.1 函数指针的声明

    类似变量在内存中会分配一个空间,函数在内存中也会分配一个空间,这个空间的入口(或者叫首地址)称为函数的地址。用整型指针可以保存整形变量的地址,同样地,用函数指针可以保存函数的地址。

    我们回顾一下怎么定义一个整型指针:

    int *p_int;

    由于整型变量只需要定义变量的类型即可完全规定这个变量的格式(个人理解,变量声明限定了变量的格式,变量定义规定了其内容)。但是对于一个函数来说(将函数也看成一种复杂的变量),函数的格式由哪些东西唯一确定呢?函数返回值,函数参数个数和参数类型

    那么我们是不是可以通过函数返回值,函数参数个数和参数类型来声明函数指针变量的格式?

    int (*p_int);//声明了一个整型指针变量
    int (*p)(int,int);//声明了一个函数指针变量,该变量指向一个函数,该函数的返回值为int类型,并且接受两个int类型的形参。

    上面的代码声明了一个函数指针,将其与整型指针类比,是不是可以理解为:由于函数的格式的确定需要返回值,参数个数以及参数类型这些东西唯一确定,那么声明一个函数指针就需要这三个东西来唯一确定函数指针的格式

    1.2 函数指针的定义

    下面来看看,已经声明了函数指针,怎么定义这个函数指针。

    类似的,还是用整形变量的定义来类比。

    int *p_int;    //声明一个整型指针变量
    int arr[2]={1,2};//定义一个整型数组
    *p_int=arr;    //整型指针变量的定义(赋值)

    不过这次用的是整形数组,见上面的代码。整型数组arr[],其名称代表数组的入口,因此*p_int=arr; 可以给整型指针变量赋值,赋值的过程相当于规定了变量的内容,上面1.1中声明的过程相当于规定了变量的格式,变量的格式和内容都规定了,就可以使用这个变量了。此时通过p_int这个指针就可以访问arr数组。

    int Max(int x, int y);//声明max函数
    int main(void){
        int (*p)(int,int);  //定义函数指针
        p=Max;              //类似的,函数名也代表函数的入口(函数地址),可以用其初始化函数指针,但是前提是函数指针的格式与用来初始化的函数的格式相同
        return 0;
    }

    上面的代码先定义了一个函数指针,其指向一个函数,该函数的返回值是int,并且传入参数为两个int类型的参数。而Max函数本身就是返回值是int,并且传入参数为两个int类型的参数的函数,因此可以用Max函数初始化函数指针p。而函数名Max就是函数的入口,因此p=Max就对函数指针p进行了初始化。

    1.3 使用typedef定义函数指针的别名

    我们都知道使用typedef关键字可以定义数据类型的别名,最常见的是给结构体定义别名,如下:

    //嵌入式开发中最常见的使用typedef定义unsigned int的别名为uint32_t
    typedef unsigned  int uint32_t;
    
    //定义node结构体,Node是其别名
    typedef struct node{
        int val;
        struct node* next;
    }Node;
    typedef Node* pNode;//定义Node*类型的别名为pNode
    pNode head;//使用类型的别名声明struct node*类型的变量;

    同样地,使用typedef关键字可以定义函数指针的别名,如下:

    //HANDLER是函数指针类型的别名,该类型的函数指针指向一个返回值为int,参数为两个int的函数
    typedef int (*HANDLER)(int,int);

    HANDLER表示一个函数指针,指向一个返回值是int,参数为两个int类型的函数。

    使用HANDLER可以定义函数指针类型的变量,

    typedef int (*HANDLER)(int,int);//定义一个函数指针类型的别名
    int Max(int x, int y);//声明max函数
    
    int main(void){
    
        int (*p)(int,int);  //定义函数指针
        HANDLER q;//使用函数指针的别名声明q这个函数指针
        q=Max;
        p=Max;  //给函数指针赋值(其值就是函数名)
        int res1=(*p)(a,b);  //赋值完成之后,通过函数指针调用函数
        int res2=q(a,b);
        printf("res1:%d; res2:%d ",res1,res2);   //打印res1和res2的值,就是a和b的最大值,1
    }

    1.4 将常数转换为函数指针

    嵌入式开发经常会遇到的问题,将一个常数,比如0x0001转换为一个“指向返回值为void的函数的指针”,我们先看看一个指向返回值为void的函数的指针,这种类型的变量怎么定义。

    void (*p)();//定义了一个函数指针p,其指向一个返回值为void的函数

    然后由于常数0x0001不是指针类型的变量,我们先需要强制类型转换,将常数类型转为函数指针类型,如下:

    (void (*)())0x0001;//将0x0001强制类型转换为一个函数指针,其指向一个返回值为void类型的函数

    这个时候我们成功将常数0x0001转为了函数指针。如果想让程序跳转到0x0001的地方执行,那么只需要通过函数指针调用函数就行了,如下:

    (void (*)())0x0001;//将0x0001强制类型转换为一个函数指针,其指向一个返回值为void类型的函数
    (*(void (*)())0x0001)();//函数指针前加“*”,解引用,然后调用函数

    结合上一节说的typedef关键字,可以将上面两行代码写成如下的形式:

    typedef void (*funcptr)();//定义函数指针类型的别名
    (funcptr)0x0001;//使用funcptr将0x0001强制类型转换为一个函数指针
    ((*funcptr)0x0001)();//函数指针前加“*”,解引用,然后调用函数

    1.5 函数指针的调用

    知道了如何定义和声明函数指针,下面就是如何使用函数指针了。这里还是用整型数组的使用来类比。

    int *p_int;    //声明一个整型指针变量
    int arr[2]={1,2};//定义一个整型数组
    *p_int=arr;    //整型指针变量的定义(赋值)
    printf("%d",(*p_int)[0]);//该行代码打印(*p_int)[0],就是arr[0],即arr数组第一个元素

    通过上面的代码可以看出,使用“*+指针名”可以达到与使用数组名一样的访问数组元素的目的。那么,看看下面的代码:

    int Max(int x, int y);//声明max函数
    int main(void){
        int a=0, b=1;
        int (*p)(int,int);  //定义函数指针
        p=Max;              //类似的,函数名也代表函数的入口(函数地址),可以用其初始化函数指针,但是前提是函数指针的格式与用来初始化的函数的格式相同
        int res=(*p)(a,b);//通过*+函数变量名,可以访问p指向的函数,与res=Max(a,b)相同
        return 0;
    }

    使用“*+指针名”可以通过函数指针访问该指针指向的那个函数。是不是跟数组的访问很类似?如果把函数也当成变量(只不过这个“变量”,其结构有比较多的因素决定,其内容可以占据很大的空间),那么这样的函数指针调用是不是就更容易理解了。

    1.6 将函数指针作为函数的传入参数

    有了函数指针之后,是不是就可以将函数指针作为参数传递给另一个函数?我们给函数传递参数的目的是什么?为了使用外部的数据来改变函数的操作或者调用逻辑,影响函数的返回值或者通过传入变量的指针,在函数中通过指针修改变量的内容。

    函数指针作为函数的传入参数,可以给函数传递一个函数供其内部调用,这个传入的函数,我们可以在外部完成函数的实现,这样可以在保证主逻辑不变的情况下,通过改变传入函数的逻辑,最终影响主逻辑的输出

    int Max(int x, int y);//声明max函数
    int GetValue(int x,int y,int(*p)(int,int));//声明getvalue函数
    int main(void){
        int a=1,b=0;        //定义两个变量a,b
        int maxValue=GetValue(a,b,Max);//将函数指针作为参数传递给函数
        return 0;
    }
    
    //这个函数通过函数指针p调用它的传入参数(p指向的函数的入口)
    int GetValue(int x,int y,int(*p)(int,int)){
        return (*p)(x,y);//通过函数指针,调用传入的函数参数,得到函数参数的返回值,然后返回该函数的返回值
    }
    //定义max函数,该函数返回两个变量中较大的那个变量的值
    int Max(int x, int y){
        return x>y?x:y;
    }

    这样将外部定义的函数通过函数指针传递给内部函数,可以在满足一定内部算法封装的情况下,通过外部定义的函数来改变算法的具体输出形式。比如C标准库的qsort排序算法,需要我们自己定义比较函数,来决定排序的结果是升序还是降序

    2. 简单的例子

    举一个简单的例子,该例子先声明一个函数指针,然后定义该函数指针,最后通过函数指针访问该指针指向的函数,实现函数调用的功能

    #include <stdio.h>
    #include <stdlib.h>
    //声明max函数
    int Max(int x, int y);
    int main(void){
    
        int a=1,b=0;        //定义两个变量a,b
        int *q;
        int (*p)(int,int);  //定义函数指针
        p=Max;  //给函数指针赋值(其值就是函数名)
        int res=(*p)(a,b);  //赋值完成之后,通过函数指针调用函数
        printf("%d",res);   //打印res的值,就是a和b的最大值,1
        return 0;
    }
    //定义max函数,该函数返回两个变量中较大的那个变量的值
    int Max(int x, int y){
        return x>y?x:y;
    }

    下面是通过给函数传递函数指针,在函数中调用外部定义的函数的例子。

    #include <stdio.h>
    #include <stdlib.h>
    
    int Max(int x, int y);//声明max函数
    int Min(int x, int y);//声明min函数
    int GetValue(int x,int y,int(*p)(int,int));//生命getvalue函数
    int main(void){
    
        int a=1,b=0;        //定义两个变量a,b
        int maxValue=GetValue(a,b,Max);
        int minValue=GetValue(a,b,Min);
    
        printf("The smaller value between a and b is:%d\n",minValue);   //打印res的值,就是a和b的最大值,1
        printf("The larger value between a and b is:%d\n",maxValue);   //打印res的值,就是a和b的最大值,1
    
        return 0;
    }
    //这个函数通过函数指针p调用它的传入参数(p指向的函数的入口)
    int GetValue(int x,int y,int(*p)(int,int)){
        return (*p)(x,y);//通过函数指针,调用传入的函数参数,得到函数参数的返回值,然后返回该函数的返回值
    }
    //定义max函数,该函数返回两个变量中较大的那个变量的值
    int Max(int x, int y){
        return x>y?x:y;
    }
    //定义max函数,该函数返回两个变量中较小的那个变量的值
    int Min(int x, int y){
        return x<y?x:y;
    }
    

     

    展开全文
  • 其中这个 (int CJ[],int n) 怎么理解 (1)在程序中定义一个全局变量N用于存放学生人数,再定义一个一维的全局数组CJ[100],用于存放学生成绩。 (2)编写一个成绩录入函数lr(),lr()是没有返回值的无参函数。...
  • 你必须知道的495个C语言问题

    千次下载 热门讨论 2015-05-08 11:09:25
    1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 1.4 新的64位机上的64位类型...
  • 难道在C语言中一个结构不能包含指向自己的指针吗? o 2.7 怎样建立和理解非常复杂的声明?例如定义一个包含 N 个指向返回指向字符的指针的函数的指针的数组? o 2.8 函数只定义了一次, 调用了一次, 但编译器提示...
  • 《你必须知道的495个C语言问题》

    热门讨论 2010-03-20 16:41:18
    《你必须知道的495个C语言问题》以问答的形式组织内容,讨论了学习或使用C语言的过程中经常遇到的一些问题。书中列出了C用户经常问的400多个经典问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预...
  • 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 1.4 新的64位机上的64位类型...
  • 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16和int32。然后根据实际的机器环境把它们定义为int、short、long等类型。这样看来,所有的问题都解决了,是吗? 2  1.4 新的64位机上的64位...
  • [你必须知道的495个C语言问题] 第1章 声明和初始化 基本类型 1.1 我该如何决定使用哪种整数类型? 1.2 为什么不精确定义标准类型的大小? 1.3 因为C语言没有精确定义类型的大小,所以我一般都用typedef定义int16...
  • 你必须知道的495个C语言问题(PDF)

    热门讨论 2009-09-15 10:25:47
    难道在C语言中一个结构不能包含指向自己的指针吗? . . . . 3 1.7 怎样建立和理解非常复杂的声明?例如定义一个包含N 个指向返 回指向字符的指针的函数的指针的数组? . . . . . . . . . . . . . . 3 1.8 函数只定义...
  • 本书以问答的形式组织内容,讨论了学习或使用C语言的过程中经常遇到的一些问题。书中列出了C用户经常问的400多个经典问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预处理器等各个方面的主题,并分别...
  • 本书以问答的形式组织内容,讨论了学习或使用C语言的过程中经常遇到的一些问题。书中列出了C用户经常问的400多个经典问题,涵盖了初始化、数组、指针、字符串、内存分配、库函数、C预处理器等各个方面的主题,并分别...
  • 理解 objc_msgSend 的角色

    千次阅读 2014-04-25 11:34:30
    由于Objective-C是C的一个延展,那么我们首先来看一下C语言中的函数是怎么一回事儿。 C语言中的函数调用被称为static binding,(静态or静止)绑定;意味着被调用的函数在编译时知道。 #import void printHe

    对象调用方法,这个在Objective-C里面叫做传递信息(passing a message)。信息有名称,有方法,接收参数,还可能有返回值。

    由于Objective-C是C的一个延展,那么我们首先来看一下C语言中的函数是怎么一回事儿。

    C语言中的函数调用被称为static binding,(静态or静止)绑定;意味着被调用的函数在编译时知道。

    #import <stdio.h>

    void printHello(){

    printf("hello");

    }

    void printByebye(){

    printf("byebye");

    }

    void doOneThing(int type){

    if(type==0){

    printHello();

    }else{

    printByebye();

    }


    }

    上述代码在编译的时候,printHello与printByebye都是知道的,编译器发出指定,直接来调用函数。 这些函数的地址已经被有效的硬编程在这些指令中。

    换一种方式,如下展示:

    #import <stdio.h>

    void printHello(){

    printf("hello");

    }

    void printByebye(){

    printf("byebye");

    }

    void doSomething(int type){

    void (*func)();

    if(type==0){

    func=printHello;

    }else{

    func=printByebye;

    }

    func();

    }

    如上所示,在这里,动态绑定被使用上了,因为要调用哪个函数是不知道的;只有在 runtime时。


    上述两种情形有何区别呢?

    编译器发出指令:

    1.在第一种情形下,函数会在 if 和else 语句中都被调用。

    2.在第二种情形下,函数只会被调用一次,付出的代价仅是读取这个函数的地址,而不用将其硬编码。


    动态绑定是一种机制, 方法被调用,当一个信息传递给了一个object.  所有的方法都是,对于特定的信息哪一个方法被调用,完全决定于runtime;甚至可以改变,这一机制让Objective-C 真正意义上dynamic.

    如下所示:

    id returnValue=[object  messageName: parameter];

    object :接收者 (信息的接收者)

    messageName:parameter  (方法selector与参数结合) 这个被称为一个信息。


    当看这个叫信息时,编译器将其转化成标准的C函数,objc_msgSend:

    void objc_msgSend(id self,SEL cmd,...)


    这是一个可变的函数,接收不同个数的参数。第一个参数是 消息的接收者,第二个参数方法,后面的参数就是 原来信息中的参数形表,按照其出现的顺序依次罗列。

    上面的:

    id returnValue=objc_msgSend(object,@selector(messageName:),parameter);

    objc_msgSend函数调用正确的方法,取决于接收者的类型和方法。 为了做到这些,objc_msgSend首先会在接收者的方法列表中去找这个方法,如果找到,将会跳到这个方法的实现中去;如果没有找到,objc_msgSend会一路向上找其父类是否有这个方法。如果还是没有找到方法,message forwarding kicks in. 哈哈!item 12


    看起来好像是当一个方法被调用时,有很多事情要做。幸运地是,objc_msgSend 会缓存这个结果,所以在未来给相同的类发消息时,会执行地很快。

    fast path -----会慢一些------  statically bound function 调用;

    但是如果cach下来了,比静态绑定的函数慢不了多少。

    实际情况下,信息调用派遣在一个应用中并不是瓶颈。如果它是的话,那么我们还不如去直接用C语言去写呢,将oc 对象的任何状态传递给它。


    前述方法仅适用于某些信息。额外的函数 暴露给了 Objective-C的  runtime 来去处理某些情况:

    objc_msgSend_stret

    对于某些信息,返回一个struct(结构体)。 这个函数只能处理 适应CPU 寄存器类型 的返回值。 

            objc_msgSend_fpret

    对于那些返回值为浮点数的信息。一些architectures需要在函数调用之间对浮点寄存器进行特殊处理,意味首objc_msgSend不是足够好。 这个函数存在的意义是:处理这些奇怪的事情,如x86上面。

            objc_msgSendSuper

     如果将信息传给了父类,例如:[super message:parameter]; 


    在上面,我暗指了 objc_msgSend

    //todo! 

    展开全文
  • 11、那么Block到底是怎么实现的呢?试一试通过将Block 的代码转换成普通C语言代码来查看它的实现过程。 要将OC代码转换成C语言代码,可以使用clang编译的一个命令: 通过这个命令能把指定文件中的OC代码改写成...

    11、那么Block到底是怎么实现的呢?试一试通过将Block 的代码转换成普通C语言代码来查看它的实现过程。

    要将OC代码转换成C语言代码,可以使用clang编译的一个命令:

    通过这个命令能把指定文件中的OC代码改写成C++代码(其中主要部分还是普通的C语言代码),通过这些代码就能看到Block是如何使用C++语言实现的。

     

    12、首先编写一个最简单的Block,没有返回值和参数列表,执行它会输出“Shayne Yeorg”。代码如下:

    然后使用11的命令转换这个文件,可以得到文件main.cpp。main.cpp的内容很多,将其中相关的代码提取整理后如下:

    这便是这个Block转换后的主要内容了。

     

    13、第一次看到这段代码会觉得有点乱,毕竟使用的变量名虽然有规律但是比较长,为了更好地理解这段代码,试着将主要的命名替换成伪代码,如下:

    这时候这段代码的内容就很清晰了:

    (1)、它首先声明了一个struct来存放Block对象的基础结构(下文记为struct1),这个struct还不是完整的Block,只是组成部分,它主要包含了一个isa指针和一个函数指针FuncPtr;

    (2)、然后声明了一个带有编号的具体函数(匿名函数),可以注意到函数里的内容其实就是源码中Block中要执行的内容了;

    (3)、再声明一个struct用来存放Block对象的size等其他相关信息(下文记为struct2),同时定义一个此结构体的具体实例;

    (4)、有了以上的准备后,就可以声明一个完整的Block对象的struct了,它由上面的struct1和struct2组成。

    除此之外,还对这个完整的Block对象的struct声明了一个构造函数,使用这个构造函数不仅可以为其内部的struct2赋值,还能为struct1的函数成员FuncPtr赋值;

    (5)、最后就是main函数了,在main函数内,首先调用完整Block对象的struct的构造函数定义了一个Block 对象,构造函数使用的参数是(2)的函数和(3)中声明的struct2的实例,达到了源码中定义Block的效果。

    生成这个Block对象后,下一步就是调用这个Block对象内部的struct1的函数成员FuncPtr,达到了源码中调用这个Block的效果。

    (6)、这就是一个最简单的Block对象的完整实现过程。

     

    14、从13的实现过程可以分析出:

    (1)、在定义FuncPtr函数的时候,可以注意到它是有参数的,参数是一个Block完整struct类型的变量__cself。

    这类似于消息发送中,通过方法调用表找到对应方法的实现后,传递给方法实现的两个隐藏参数self和_cmd,在方法实现中通过self和_cmd就可以获取到objc_messageSend()函数的两个必要参数。

    类似的,在这里,函数中通过__cself就可以访问到调用这个函数的Block本身了。

    这个参数在13的例子中还没有用到,那只是一个最简单的Block,下文会涉及到使用这个参数的情况;

    (2)、Block内部是有一个isa指针的,说明了它也是对象。并且在构造这个Block的时候,这个指针被指向了_NSConcreteStackBlock,关于这个将在下文详解;

    (3)、Block的回调功能,本质上是通过调用函数实现的。

     

    15、通过13的简单Block例子明白了Block的3个主要功能点之一的回调功能的实质,那么接下来来研究一下另一个功能点:截获变量。看一看有截获变量的Block 在转换为普通C语言后是什么样的。编写以下代码:

    这段代码在定义Block之前先定义了两个变量,并且在Block中使用了这两个变量。执行这个Block 会打印出“Shayne Yeorg's shirt number is 10”。

     

    16、使用clang命令重写代码,得到的C++代码中提取出的主要部分如下:

    其中__block_impl结构的声明部分省略了,这部分是不会变的。

     

    17、分析16的代码:

    (1)、在Block的完整结构体__main_block_impl_0中,多了两个成员,成员的类型和命名和截获的变量name和shirtNum一样,并且它的构造函数也增加了对这两个成员变量的初始化;

    (2)、这时__main_block_func_0函数就使用到__cself了,它通过__cself能获取到对应的__main_block_impl_0对象,然后就能取得两个成员变量name和shirtNum的值来使用了;

    (3)、在main函数中,构造Block对象的时候,把定义的name和shirtNum作为参数传递进去了,所以在下一句代码调用funcPtr的时候,就能通过(2)的方法取得这两个变量的值了;

    (4)、所以,Block截获变量的处理方法其实就是在其结构体内增加和截获变量相同类型的成员变量,然后将截获变量的值保存在成员变量里。这也就解释了,为什么一个变量被Block截获后还去修改它的值,也不会影响到Block中已截获到的值。

     

    18、明白了Block的回调和截获变量的功能的实现方法后,再来看一下Block的最后一个功能:截获可变变量。

    在Block里可以修改的变量,在声明的时候需要附上__block修饰符,这种变量和普通的变量是有很大区别的,首先来看一看在没有涉及到Block的情况下,__block变量的实质。编写以下代码来查看区别:

    使用clang命令转换这段代码后,提取出主要部分如下:

     

    19、在18中,可以注意到:

    (1)、附上__block的变量,被转换成了一个全局的结构体,结构体中不仅包含了变量的值blockVar,还包含了一个__forwarding指针,从初始化的代码来看,这个指针指向了blockVar结构体自身;

    (2)、修改blockVar变量的值的时候,并不是直接修改blockVar结构体中的blockVar成员的值,而是通过结构体中的__forwarding指针重新定位到blockVar结构体(在这里其实就是它自己),然后修改找到的这个blockVar结构体里的blockVar成员的值。

     

    20、为什么修改修饰为__block的变量的值的时候不直接修改,要使用__forwarding指针绕了一圈回来修改呢?

    这是因为在某些情况下(具体情况后文详解),截获了__block变量的Block会被从栈上复制到堆上,同时__block变量也会被复制一份到堆上,这时留在栈上的__block变量的__forwarding指针就会被置为指向堆上的__block变量。

    这个时候,如果在Block之外使用这个__block变量,虽然使用的却仍是存放在栈上的变量,但是通过__forwarding指针却能顺利访问到堆上的__block变量,也就实现了Block内外访问的都是同一个__block变量的效果。

     

    21、明白了__block变量的实质后,来看一看截获了可变变量的Block究竟是怎么实现的。编写以下代码:

    (图J截获可变变量的Block1)

    这段代码在Block中修改了__block变量的值,将这段代码进行转换:

     

    22、分析21转换后的代码:

    (1)、和18一样,__block变量shirtNum被转换成了一个全局结构体__Block_byref_shirtNum_0;

    (2)、类似16的截获不可变变量的代码的处理方式,Block的完整结构体__main_block_impl_0也会增加一个__Block_byref_shirtNum_0结构体类型的成员shirtNum,通过构造函数可以看到它会被赋值为一个__Block_byref_shirtNum_0结构体的__forwarding成员指针;

    (3)、在__main_block_func_0函数中,在通过__cself参数获取到__main_block_impl_0对象的shirtNum成员变量的值后,是通过shirtNum->__forwarding->shirtNum这样的方式去修改它的值的;

    (4)、增加了__main_block_copy_0函数和__main_block_dispose_0函数,这两个函数的作用类似于NSObject的retain和release方法,只不过它们是用来管理Block对象的,用于Block对象从栈上复制到堆上以及从堆上废弃的时候;

    (5)、在main函数中,通过定义__Block_byref_shirtNum_0结构体的对象,来达到定义__block变量的效果,这点也和18一样;

    (6)、这就是截获了可变变量的Block的实质。

     

    转载于:https://www.cnblogs.com/shayneyeorg/p/5876371.html

    展开全文
  • C语言中的函数到了java中叫作方法。方法只能在类里定义,且同一个类里只能有一个main方法,不同的类里可以有多个main方法。 方法怎么定义 以及语法机制 [修饰符列表] 返回值类型 方法名 (形式参数列表){ 方法体 }...
  • Python之面向对象

    2018-09-14 18:40:09
    面向对象oop(onject oriented programming),与之对应的还有面向过程,像C语言就是面向过程的 理解过程和函数? 过程:是早期的一个编程概念,过程类似于函数,只能执行,但是没有返回值 函数:不仅能执行,还...
  • 面向对象oop(onject oriented programming),与之对应的还有面向过程,向C语言就是面向过程的 理解过程和函数? 过程:是早期的一个编程概念,过程类似于函数,只能执行,但是没有返回值 函数:不仅能执行,还...
  • Java中的方法

    2019-10-17 22:01:40
     方法就是一个代码片段,类似于c语言中的“函数” 方法存在有什么用呢?   ①能够模块化的组织代码(当代码规模比较复杂的时候)   ②做到代码能够被重复使用,一份代码可以在多个位置使用   ③让代码更好...
  • 这里的问题是主函数的类型 有的编译器(比如vc6)可以用void main的类型,然而dev c++不吃这一套,空类型没有返回值(也有一种说法是返回空值,然而我不理解,空值在二进制代码中怎么表达?要知道代码也是有质量的,...
  • 关于代码的一些问题

    2020-12-01 16:57:35
    不管UP怎么消减行数,最终所编译出来可执行文件几乎都是一样的(可能根据编译器的优化策略会有细小差距)。这样就更不存在什么代码精简与否的问题了。 更糟糕的是,这样的代码会带来诸多...
  • C 标准I/O库粗略实现

    2020-12-08 19:40:13
    写这篇文章主要是帮助自己理解下标准I/O库大体是怎么工作的。 <h3>fopen与open之间的关系 操作系统提供的接口即为系统调用。而C语言为了让用户更加方便的编程,自己封装了一些函数,组成了C库。而且不同...
  • 如果非得要用指针参数去申请内存,那么应该改用“指向指针的指针”,由于“指向指针的指针”这个概念不容易理解,我们也可以用函数返回值来传递动态内存。 常常有人把return语句用错了。这里强调不要用return语句...
  • C++程序员面试宝典

    热门讨论 2013-04-01 13:36:19
    许多开发者对C/C++语言及其底层原理掌握不牢固,在面试过程中经常漏洞百出,无法取得好成绩。而招聘单位为了得到高素质的员工往往采用各种形式的面试考察求职者,这让面试难度大大增加。求职者要想成功应聘,不仅...
  • 立的运行部分,这样的程序会利于理解和修改。 其他情况都使用单线程。 11.Windows是内核级线程么。 答:见下一题 12.Linux有内核级线程么。 答:线程通常被定义为一个进程中代码的不同执行路线。从实现方式上划分...
  •  7.1 一切指针都是纸老虎:彻底理解指针  7.1.1 指针的运算  7.1.2 灵活的void类型和void类型指针  7.1.3 指向指针的指针  7.1.4 指针在函数中的应用  7.1.5 引用  7.2 程序中的异常处理  7.2.1 异常处理  ...
  • 在设计的过程中遇到问题,可以说得是困难重重,这毕竟第一次做的,难免会遇到过各种各样的问题,同时在设计的过程中发现了自己的不足之处,对以前所学过的知识理解得不够深刻,掌握得不够牢固,比如说结构体……通过...

空空如也

空空如也

1 2
收藏数 38
精华内容 15
关键字:

c语言返回值怎么理解

c语言 订阅