函数与指针(接录总结)

<!-- /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:华文楷体; panose-1:2 1 6 0 4 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:647 135200768 16 0 262303 0;} @font-face {font-family:Cambria; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:-1610611985 1073741899 0 0 159 0;} @font-face {font-family:"/@华文楷体"; panose-1:2 1 6 0 4 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:647 135200768 16 0 262303 0;} @font-face {font-family:"/@宋体"; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-parent:""; margin:0cm; margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:Cambria; mso-fareast-font-family:华文楷体; mso-bidi-font-family:宋体;} a:link, span.MsoHyperlink {color:blue; text-decoration:underline; text-underline:single;} a:visited, span.MsoHyperlinkFollowed {color:purple; text-decoration:underline; text-underline:single;} @page Section1 {size:612.0pt 792.0pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:35.4pt; mso-footer-margin:35.4pt; mso-paper-source:0;} div.Section1 {page:Section1;} -->

http://www.pconline.com.cn/pcedu/empolder/gj/c/0503/566020.html

函数存放在内存的代码区域内,它们同样有地址,我们如何能获得函数的地址呢?

如果我们有一个 int test(int a)的函数,那么,它的地址就是函数的名字,这一点如同数组一样,数组的名字就是数组的起始地址。

int (*fp)(int a);//这里就定义了一个指向函数的指针;

cout <<test<<endl;// 显示函数地址

int  (*fp)(int  a);  fp=test;// 将函数 test 的地址赋给函数学指针 fp

typedef定义可以简化函数指针的定义,在定义一个的时候感觉不出来,但定义多了就知道方便了;

typedef  int  (*fp)(int  a);// 注意 , 这里不是生命函数指针 , 而是定义一个函数指针的类型 , 这个类型是自己定义的 , 类型名为 fp 
    fp fpi;// 这里利用自己定义的类型名 fp 定义了一个 fpi 的函数指针
    fpi=test; 

变量如整型的定义如果在下面,可以先 extern声明再引用;但 struct却不行,报错为 local classes cannot be used to declare 'extern' variables,为什么??可能和 extern的用法有关……

定义 struct结构体: struct 结构体名 {

                                    包含的成员;

                                      ... } 变量名;

//这里的结构体名如果后面不用的话 ,可以省略;如果暂时不急定义变量来使用的话 ,那么这里的变量名也可以省略;其他的一个都不能少。※注意 :结构体定义是不可以递归的;

注意在调用成员的时候格式为两种:
1:tm->min;//最常用的
2:(*tm).min;//很少用了

typedef struct tagNode

{

  char *pItem;

  pNode pNext;

} *pNode; //编译错误,因为:新结构建立的过程中遇到了 pNext域的声明,类型是 pNode,要知道 pNode表示的是类型的新名字,那么在类型本身还没有建立完成的时候,这个类型的新名字也还不存在,也就是说这个时候编译器根本不认识 pNode

解决方法:

 

   1) typedef struct tagNode {

                    char *pItem;

                    struct tagNode *pNext; } *pNode;

 

   2) typedef struct tagNode *pNode;

              struct tagNode{

                   char *pItem;

                    pNode pNext; }; //此处用 typedef给一个还未完全声明的类型起新名字。 C语言编译器支持这种做法

 

        3)、规范做法: struct tagNode{

                                   char *pItem;

                                  struct tagNode *pNext;};

                               typedef struct tagNode *pNode;

 

typedef 原类型名    新类型名 ; //功能 :将原类型名表示的数据类型用新类型名代表;

例如: typedef char string[12]; // string text; 表明 text为含 12个字符的数组;

int f(int &a,int&b){return a+b;}

int(*p)(int &,int&);

 

这是 VC对变量存储的一个特殊处理。为了提高 CPU的存储速度, VC对一些变量的起始地址做了“对齐”处理。在默认情况下, VC规定各成员变量存放的起始地址相对于结构的起始地址的偏移量必须为该变量的类型所占用的字节数的倍数。下面列出常用类型的对齐方式 (vc6.0,32位系统 )

类型

  对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)

   Char

  偏移量必须为 sizeof(char) 1的倍数

   int

  偏移量必须为 sizeof(int) 4的倍数

   float

  偏移量必须为 sizeof(float) 4的倍数

   double

  偏移量必须为 sizeof(double) 8的倍数

   Short

  偏移量必须为 sizeof(short) 2的倍数

但是,还有更奇怪的,

struct MyStruct{

                char c3;

                double d;

                char c2;

                int i;

                char c;

}s;这是 32字节;

struct MyStruct{

                char c3;

                char c;

                int i;

                double d;

                char c2;

}s;这是 24字节;

struct MyStruct

   {

   char dda;//偏移量为 0,满足对齐方式, dda占用 1个字节;

   double dda1;//下一个可用的地址的偏移量为 1,不是 sizeof(double)=8

   //的倍数,需要补足 7个字节才能使偏移量变为 8(满足对齐

   //方式),因此 VC自动填充 7个字节, dda1存放在偏移量为 8

   //的地址上,它占用 8个字节。

   int type //下一个可用的地址的偏移量为 16,是 sizeof(int)=4的倍

   //数,满足 int的对齐方式,所以不需要 VC自动填充, type

   //放在偏移量为 16的地址上,它占用 4个字节。

   } //所有成员变量都分配了空间,空间总的大小为 1+7+8+4=20,不是结构

   //的节边界数(即结构中占用最大空间的类型所占用的字节数 sizeof

   //(double)=8)的倍数,所以需要填充 4个字节,以满足结构的大小为

   //sizeof(double)=8的倍数。

  所以该结构总的大小为: sizeof(MyStruc) 1+7+8+4+4=24。其中总的有 7+4=11个字节是 VC自动填充的,没有放任何有意义的东西。

n字节的对齐方式

   VC对结构的存储的特殊处理确实提高 CPU存储变量的速度,但是有时候也带来了一些麻烦,我们也屏蔽掉变量默认的对齐方式,自己可以设定变量的对齐方式。

   VC中提供了 #pragma pack(n)来设定变量以 n字节对齐方式。 n字节对齐就是说变量存放的起始地址的偏移量有两种情况:第一、如果 n大于等于该变量所占用的字节数,那么偏移量必须满足默认的对齐方式,第二、如果 n小于该变量的类型所占用的字节数,那么偏移量为 n的倍数,不用满足默认的对齐方式。结构的总大小也有个约束条件,分下面两种情况:如果 n大于所有成员变量类型所占用的字节数,那么结构的总大小必须为占用空间最大的变量占用的空间数的倍数;

  否则必须为 n的倍数。下面举例说明其用法。

   #pragma pack(push) //保存对齐状态

   #pragma pack(4)//设定为 4字节对齐

   struct test

   {

   char m1;

   double m4;

   int m3;

   };

   #pragma pack(pop)//恢复对齐状态

  以上结构的大小为 16,下面分析其存储情况,首先为 m1分配空间,其偏移量为 0,满足我们自己设定的对齐方式( 4字节对齐), m1占用 1个字节。接着开始为 m4分配空间,这时其偏移量为 1,需要补足 3个字节,这样使偏移量满足为 n=4的倍数(因为 sizeof(double)大于 n ,m4占用 8个字节。接着为 m3分配空间,这时其偏移量为 12,满足为 4的倍数, m3占用 4个字节。这时已经为所有成员变量分配了空间,共分配了 16个字节,满足为 n的倍数。如果把上面的 #pragma pack(4)改为 #pragma pack(16),那么我们可以得到结构的大小为 24。(请读者自己分析)

 

虚拟内存空间就是虚拟地址空间。在 32位操作系统中,应用被分配到 4GB的属于自己的虚拟地址空间( 2GB给应用, 2GB给操作系统)。因此每个进程都有 2G的虚拟内存空间,空间大小只是寻址能力范围。

 

http://blog.163.com/liuyunfeng484/blog/static/66831715200971101314369/

问题:声明与函数

 

  有一段程序存储在起始地址为 0的一段内存上,如果我们想要调用这段程序,请问该如何去做?

 

  答案

 

  答案是 (*(void (*)( ) )0)( )。看起来确实令人头大,那好,让我们知难而上,从两个不同的途径来详细分析这个问题。

 

  答案分析:从尾到头

 

  首先,最基本的函数声明: void function (paramList);

 

  最基本的函数调用: function(paramList);

 

  鉴于问题中的函数没有参数,函数调用可简化为 function();

 

   其次,根据问题描述,可以知道 0是这个函数的入口地址,也就是说, 0是一个函数的指针。使用函数指针的函数声明形式是: void (*pFunction)(),相应的调用形式是: (*pFunction)(),则问题中的函数调用可以写作: (*0)( )

 

  第三,大家知道,函数指针变量不能是一个常数,因此上式中的 0必须要被转化为函数指针。

 

  我们先来研究一下,对于使用函数指针的函数:比如 void (*pFunction)( ),函数指针变量的原型是什么?这个问题很简单, pFunction函数指针原型是 ( void (*)( ) ),即去掉变量名,清晰起见,整个加上()号。

 

  所以将 0强制转换为一个返回值为 void,参数为空的函数指针如下: ( void (*)( ) )

 

   OK,结合 2) 3)的分析,结果出来了,那就是: (*(void (*)( ) )0)( )

 

  答案分析:从头到尾理解答案

 

   (void (*)( )) ,是一个返回值为 void,参数为空的函数指针原型。

   (void (*)( ))0,把 0转变成一个返回值为 void,参数为空的函数指针,指针指向的地址为 0.

   *(void (*)( ))0,前面加上 *表示整个是一个返回值为 void的函数的名字

   (*(void (*)( ))0)( ),这当然就是一个函数了。

 

  我们可以使用 typedef清晰声明如下:

 

   typedef void (*pFun)( );

 

这样定义之后, pFun就是一个返回类型为 void无参数的函数指针变量了。

 

  这样函数变为 (*(pFun)0 )( );

 

问题:三个声明的分析

 

  对声明进行分析,最根本的方法还是类比替换法,从那些最基本的声明上进行类比,简化,从而进行理解,下面通过分析三个例子,来具体阐述如何使用这种方法。

 

1 int* (*a[5])(int, char*);

 

   首先看到标识符名 a "[]"优先级大于 "*" a "[5]"先结合。所以 a是一个数组,这个数组有 5个元素,每一个元素都是一个指针,指针指向 "(int, char*)",很明显,指向的是一个函数,这个函数参数是 "int, char*",返回值是 "int*" OK,结束了一个。:)

 

2 void (*b[10]) (void (*)());

 

   b是一个数组,这个数组有 10个元素,每一个元素都是一个指针,指针指向一个函数,函数参数是 "void (*)()"【注 10】,返回值是 "void"。完毕!

 

  注意:这个参数又是一个指针,指向一个函数,函数参数为空,返回值是 "void"

 

3. doube(*)() (*pa)[9];

 

   pa是一个指针,指针指向一个数组,这个数组有 9个元素,每一个元素都是 "doube(*)()"(也即一个函数指针,指向一个函数,这个函数的参数为空,返回值是 "double")。

 

函数在内存中有一个物理位置,而这个位置是可以赋给一个指针的。一零点函数的地址就是该函数的入口点。因此,函数指针可被用来调用一个函数。函数的地址是用不带任何括号或参数的函数名来得到的。(这很类似于数组地址的得到方法,即,在只有数组名而无下标是就得到数组地址。)

 

怎样说明一个函数指针变量呢 ?

为了说明一个变量 fn_pointer 的类型是 "返回值为 int 的函数指针 ", 你可以使用下面的说明语句 :

int (*fn_pointer) ();

为了让编译器能正确地解释这句语句 , *fn_pointer 必须用括号围起来。若漏了这对括号 , :

int *fn_pointer ();

的意思完全不同了。 fn_pointer 将是一个函数名 , 其返回值为 int 类型的指针。

 

在C语言中规定,一个函数总是占用一段连续的内存区,   而函数名就是该函数所占内存区的首地址。   我们可以把函数的这个首地址 ( 或称入口地址 ) 赋予一个指针变量,   使该指针变量指向该函数。然后通过指针变量就可以找到并调用这个函数。   我们把这种指向函数的指针变量称为 " 函数指针变量 "

函数指针变量定义的一般形式为:

类型说明符 (* 指针变量名 )();

其中 " 类型说明符 " 表示被指函数的返回值的类型。 "(* 指针变量名 )" 表示 "*" 后面的变量是定义的指针变量。   最后的空括号表示指针变量所指的是一个函数。

例如: int (*pf)();

表示 pf 是一个指向函数入口的指针变量,该函数的返回值 ( 函数值 ) 是整型。

 

从上述程序可以看出用,函数指针变量形式调用函数的步骤如下:

 

1. 先定义函数指针变量,如后一程序中第 9 int (*pmax)(); 定义 pmax 为函数指针变量。

 

2. 把被调函数的入口地址 ( 函数名 ) 赋予该函数指针变量,如程序中第 11 pmax=max;

 

3. 用函数指针变量形式调用函数,如程序第 14 z=(*pmax)(x,y);  调用函数的一般形式为: (* 指针变量名 ) ( 实参表 ) 使用函数指针变量还应注意以下两点:

 

a. 函数指针变量不能进行算术运算,这是与数组指针变量不同的。数组指针变量加减一个整数可使指针移动指向后面或前面的数组元素,而函数指针的移动是毫无意义的。

 

b. 函数调用中 "(* 指针变量名 )" 的两边的括号不可少,其中的 * 不应该理解为求值运算,在此处它只是一种表示符号。

 

C语言中函数是一种 function-to-pointer的方式 ,即对于一个函数 ,会将其自动转换成指针的类型 .

&fun, fun, *fun这三个值的结果是一样的 . 其实对于最后的那个 *fun, 即使前面加上很多个 * , 其结果也不变 , **fun, ***fun的结果都是一样的 . 对于这个问题 , 因为之前讲过函数是一种
function-to-pointer方式 , 其会自动转换成指针的类型 , &fun是该函数的地址 , 为指针类型 , fun是一个函数 , 会转换成其指针类型 , 而对于 *fun, 由于 fun已经变成了指针类型 , 指向这个函数 , 所以 *fun就是取这个地址的函数 , 而又根据 function-to-pointer, 该函数也转变成了一个指针 , 所以以此类推 , 这三个值的结果是相同的 .

int (*p[3])(int, int);

p[0] = max;

int (*p)();
这是一个函数指针 , p所指向的函数是一个不带任何参数 , 并且返回值为 int的一个函数 .
int (*fun())();
这个式子与上面式子的区别在于用 fun()代替了 p, fun()是一个函数 ,所以说就可以看成是 fun()这个函数执行之后 ,它的返回值是一个函数指针 ,这个函数指针 (其实就是上面的 p)所指向的函数是一个不带任何参数 ,并且返回值为 int的一个函数 .

void (*signal(int signo, void (*fun)(int)))(int);

就可以看成是 signal()函数 (它自己是带两个参数 ,一个为整型 ,一个为函数指针的函数 ), 而这个 signal()函数的返回值也为一个函数指针 ,这个函数指针指向一个带一个整型参数 ,并且返回值为 void的一个函数 .

#include<signal.h>
#include<stdlib.h>
#include<stdio.h>

void sig_fun2(int signo)
{
        printf("in sig_fun2:%d/n", signo);
}

void sig_fun1(int signo)
{
        printf("in sig_fun1:%d/n", signo);
}

int main()
{
        unsigned long i;
        if (signal(SIGUSR1, sig_fun1) == SIG_ERR)
        {
                printf("signal fun1 error/n");
                exit(1);
        }

        (signal(SIGUSR1, sig_fun2))(30);

        printf("done/n");
        return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值