栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

C语言指针详解

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

C语言指针详解

指针 指针的含义
  • 内存:

    • 内存是电脑上特别重要的存储器,计算机中所有程序的运行都是在内存中进行的

    • 为了更有效的使用内存,就把内存划分成一个个的内存单元,每个内存单元的大小是1个字节

    • 为了更有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地址

变量在内存空间中所占存储空间的首地址,称为该变量的地址;

变量在存储空间中存放的数据称为变量的值。

例如:

定义3个整型变量a, b, c,程序在编译时将这三个变量的值放入内存之中,系统可能分配3000~3003的4个字节给变量a。

  • 在程序中我们一般通过变量名来引用变量的值:printf("%d", a);

该语句通过变量名a找到存储单元的地址,继而对存储单元进行存储操作。

程序经过编译后将变量名转换为变量的地址,对变量值的存取都是通过地址进行的。

上述语句是直接按照变量名进行访问的,称为“直接访问”。

而另一种方式是:先将变量a的地址即3000放进另一个变量中,然后通过该变量来找到变量a的地址,从而访问变量a。

而存放变量的地址需要一种特殊的变量,即指针变量,也就是存放地址的变量。(存放在指针中的值都被当做地址处理)

或者是这样解释:我们通过&(取地址操作符)取出变量a的地址存放到一个变量中,而这个变量就是指针变量。

  • 指针的大小:

    在32位的机器上,地址是32个0或1组成的二进制序列,地址就用4个字节的空间来存储,所以一个指针变量的大小就是4个字节。

    在64位的机器上,地址是64个0或1组成的二进制序列,地址就用8个字节的空间来存储,所以一个指针变量的大小就是8个字节。

指针变量 指针变量的定义:
  • 指针变量定义的一般形式:类型定义符 * 指针变量名
  • 类型定义符:指针变量所指向的目标变量的数据类型,可以是C语言中规定的基本数据类型或用户自定义的数组、结构体、联合体等构造数据类型。
  • *:是一个说明符,说明其后定义的变量是指针变量。
指针变量的赋值:

指针变量的值是内存的地址,给指针赋值就是将内存地址放入指针变量,意味着指针指向了该内存单元

  • 给指针变量赋值通常有三种:

    • 使用取地址符(&)将变量的地址取出赋给指针变量。

      int a = 1;
      float b = 1.5;
      int* p1;    //定义p1为指向整型变量的指针
      float* p2;  //定义p2为指向单精度浮点型变量的指针变量
      p1 = &a;     //将变量a的地址赋值给p1,即让p1指向整型变量a
      p2 = &b;     //将变量b的地址赋值给p2,即让p2指向单精度浮点型变量b
      
    • 在定义指针变量时对它进行初始化

      int c = 2;
      float d = 2.5;
      int* p3 = &c;   //定义 指针变量p3,同时对它初始化
      float* p4 = &d; //定义 指针变量p4,同时对它初始化
      
    • 将一个已有具体指向的指针变量赋值给另一个指针变量

      int e = 3;
      int* p1, 
      int* p2;
      p1 = &e;
      p2 = p1;    //将变量p1的值赋给p2,让p2也指向同一个指针变量
      
指针变量的引用
  • 引用指针变量所指向的变量(指针的解引用 * )

    int i = 5;
    int* p1;
    p1 = &i; //把i的地址赋值给指针变量p1,指针变量p1的值是变量i的地址,即p1指向i
    * p1 = i; //指针的解引用,表示指针变量p1所指向的变量的值,即变量i的值
    
    • 解引用的访问权限

      解引用的访问权限式是由指针变量的类型所决定的。如char*的指针解引用只能访问一个字节,而int*可以访问4个字节。

  • 引用指针变量的值

    printf("%p", p1):表示以%p格式符输出指针变量p1的值,如果p1已经指向了变量i,则相当于输出了i的地址,即等价于输出&i。

  • 引用指针变量的地址(二级指针)

    printf("%p", &p1):表示以%p格式符输出指针变量p1的地址。

#include 
int main()
{
	int a = 5;
	int* p1;
	p1 = &a;
	printf("a = %dn", a);     //打印变量a的值
	printf("&a = %pn", &a);   //打印变量a的地址         
	printf("p1 = %pn", p1);   //打印指针变量p1的值      
	printf("&p1 = %pn", &p1); //打印指针变量p1的地址(二级指针)
    printf("a = %dn", * p1);  //打印指针变量所指向的变量的值
	return 0;
    //编译结果: a = 5   &a = 00B8F820  p1 = 00B8F820  &p1 = 00B8F814   a = 5
}
  • 特别说明一下二级指针

    #include 
    int main()
    {
    	int a = 10;
    	int* pa = &a;
    	int* ppa = &pa;
    	printf("%pn", &a);  //编译结果  0075F834
    	printf("%pn", pa);  //编译结果  0075F834  //变量a的地址存放在pa中
    	printf("%pn", &pa); //编译结果  0075F828
    	printf("%pn", ppa); //编译结果  0075F828  //指针变量pa的地址存放在ppa中
    	return 0;
    }//pa是一级指针,ppa是二级指针
    
    • 对于二级指针的运算:

      *ppa通过对ppa的地址进行解引用,这样找的是pa,*ppa访问的是指针变量pa。

      **ppa先通过*ppa找到pa,然后对pa进行解引用操作,**ppa访问的是变量a。

指针变量的类型
char* pc;
int* pi;
short* ps;
long* pl;
float* pf;
double* pd;

其实,char*类型的指针是为了存放char类型变量的地址;int*类型的指针是为了存放int类型变量的地址;short*类型的指针是为了存放`short类型变量的地址。那么指针类型的意义是什么?

  • 指针的类型决定了指针向前或者向后走一步有多大(步长)

    • 通过打印地址来说明

      #include 
      int main()
      {
      	int n = 10;
      	int* p1 = &n;
      	char* p2 = &n;
      	printf("%pn", p1);
      	printf("%pn", p1 + 1);
      	printf("%pn", p2);
      	printf("%pn", p2 + 1);
      	return 0;
      }//编译结果
      //00B3FBE8
      //00B3FBEC    //int类型的指针变量+1,p1的值(地址)加4个字节
      //00B3FBE8
      //00B3FBE9    //char类型的指针变量+1,p2的值(地址)加1个字节
      
    • 通过内存来说明:

      #include 
      int main()
      {
      	int arr[10] = { 0 };
      	char* p = arr;
      	int i = 0;
      	for (i = 0; i < 10; i++)
      	{
      		*(p + i) = 1;
      	}
      	return 0;
      }
      

  • 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字符)。

    如char*的指针解引用只能访问一个字节,而int*可以访问4个字节。

  • 指针类型是有意义的,我们选择指针的类型应该根据我们想要的功能来实现,比如,如果希望按照一个字节一个字节的访问,应该用char类型的指针,如果希望按照一个整型一个整型的访问,自然要用到int类型的指针。
野指针

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

  • 野指针的成因

    指针未初始化

    int main()
    {
        int* p;    //p是一个局部的指针变量,局部变量没有初始化,默认为随机值
        *p = 20;   //非法访问内存
        return 0;
    }
    

    指针越界访问

    int arr[10] = { 0 };
    int* p = arr;
    int i = 0;
    for(i=0; i<=10; i++)
    {
        *p = i;
        p++;
    }//当指针指向的范围超出arr数组时,p就是野指针
    

    指针指向的空间释放

    • 指针所指向的内存空间被释放之后,指针继续访问,即非法访问
    int* test
    {
    	int a = 10;
        return &a;//返回a的地址之后,局部变量a的内存被释放
    }
    int main()
    {
        int* p = test();
        *p = 20;  //非法访问
        return 0;
    }
    
  • 如何规避野指针

    指针初始化

    #include 
    int main()
    {
        int* p = NULL; //定义了一个空指针,指针初始化
        return 0;
    }//当不知道指针初始化为什么地址的时候,直接初始化为NULL(注意包含头文件)
    

    小心指针越界

    指针指向空间释放时及时置NULL

    避免返回局部变量的地址

    指针使用之前检查有效性

指针运算
  • 指针 + - 整数

    int arr[5];
    int* p;          //指针的关系运算
    for(p = &arr[0]; p < &arr[5];)
    {
        *p++ = 0;
    }//将arr数组中的5个元素赋值为0
    
  • 指针 - 指针

    #include 
    int main()
    {
       	int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int* p1 = arr;
        int* p2 = arr+9;
        printf("%dn", p2-p1);//指针变量相减得到的是两个指针之间的元素个数
        return 0;             //指针变量要指向同一数组的元素,否则无意义
    }  //编译结果:9
    
  • 指针的关系运算

    int main()
    {
        int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int* p = arr;
        int* pend = arr+9;
        while(p <= pend)  //指针的关系运算
        {
            printf("%dn", *p);
          	p++;   
        }
        return 0;
    }
    
    • 需要注意的是:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较
指针与数组 指针数组
  • 指针数组的概念:

    一个数组,如果其数组单元存放的是内存单元的地址,称为数组指针。(指针数组本质是一个数组)

    指针数组中每一个元素都存放一个地址,相当于一个指针变量

  • 指针数组的定义:

    一般形式:类型名 * 数组名 [ 数组长度 ]; 如定义一个整型指针数组:int* pointer [5];

    可以这么看:int*是数组存放元素的类型(指针类型),pointer是数组名,[5]是数组大小

指向数组的指针(数组指针)

我们已经知道:

  • 整型指针:int* pi能够指向整型数据的指针;

  • 浮点型指针:float* pf能够指向浮点型数据的指针;

  • 那么数组指针:能够指向数组的指针(数组指针存放的是数组的地址)

  • 数组指针定义:

    一般形式:类型名 (*指针变量名)[数组长度]; 如定义一个数组指针: int (*p) [10];

    可以这么看:

    • int (*) [10]是一种数组指针的类型,而p是指针变量。p指向了一个大小为10个整型的数组,也可以说 p 这个指针存放了大小为10个整型这个数组的地址。(看法一)
    • p先和*结合,表示p是一个指针,然后再和[10]结合,表示p这个指针指向了一个数组,这个数组有10个元素,这些元素的类型是整型。(看法二)
int main()
{
    int arr[5] = {1, 2, 3, 4, 5};
    int* p1 = arr;    //指向数组首元素地址的指针
    int  (*p2)[5];    //指向整个数组的指针,p2先和*结合,说明p2是一个指针变量,然后指向的是一个大小为5个整型的数组,所以p2是                         一个指针,指向一个数组,是数组指针。
                      //[]的优先级高于*,所以必须加上()保证p2优先和*结合
}
  • &数组名VS数组名

    #include 
    int main()
    {
    	int arr[10] = { 0 };
    	printf("%pt", arr);
    	printf("%pn", &arr);
    	return 0;
    }
    //编译结果: 0115F724    0115F724
    

    请看这一段代码

    #include 
    int main()
    {
    	int arr[10] = { 0 };
    	printf("%pt", arr);
    	printf("%pn", &arr);
    	printf("%pt", arr+1);
    	printf("%pn", &arr+1);
    	return 0;
    }//  编译结果: 004FF6F4     004FF6F4
     //            004FF6F8     004FF71C
     //     差值:  4            40
    
    • 可见虽然 arr 和 &arr 打印的地址是一样的,但是意义和本质不同

    • 实际上:&arr表示的是数组的地址,而不是数组首元素的地址;

      本例中&arr的类型是int(*)[10],是一种数组指针类型,数组的地址+1,跳过整个数组的大小,所以&arr相对于arr的差值是40。

  • 打印一个二维数组

    #include 
    void print(int(*p)[5], int r, int c)  //定义一个数组指针
    {
    	int i = 0;
    	int j = 0;
    	for (i = 0; i < r; i++)
    	{
    		for (j = 0; j < c; j++)
    		{
    			printf("%d  ", *(*(p + i) + j));
    		}
    		printf("n");
    	}
    }
    int main()
    {
    	int arr[3][5] = { {1,3,5,7,9},{2,4,6,8,10},{3,5,7,9,11} };
    	print(arr, 3, 5);   //二维数组传参
    	return 0;
    }
    
  • 辨析代码

    int arr[5];       
    int *p1[10];
    int (*p2)[10];
    int (*p3[10])[5];
    

  • 数组传参

    • 一维数组传参

      一维数组在传参时,编译器将其视为指向数组首元素的指针,也就是说传递的是数组首元素的地址

      实际传递的数组大小与函数形参指定的数组大小没有关系,所以形参的数组的 “[]” 里面可以不用填数组大小

      • 传递参数是普通数组 arr1(3种方式)

        #include 
        void function1(int arr[])   
        {}
        void function1(int arr[10]) 
        {}
        void function2(int* arr)    
        {}
        int main()
        {
        	int arr1[10] = { 0 };
        	function1(arr1);
        	return 0;
        }
        
      • 传递参数是指针数组 arr2(3种方式)

        #include 
        void function2(int* arr[])  
        {}
        void function2(int* arr[20])
        {}
        void function2(int** arr)   
        {}
        int main()
        {
        	int* arr2[20] = { 0 };  //arr2是一个存放 int* 的数组,数组名是一个一级指针的地址,所以需要用二级指针接收
        	function2(arr2);
        	return 0;
        }
        
    • 二维数组传参

      首先我们要明白二维数组名是什么?

      二维数组名是二维数组首行(序号为0)的地址,是一个一维数组的地址(可以用一个数组指针存放)。注意不能理解为指针的指针。

      #include 
      int main()
      {
      	int arr[2][3] = { 0 };
      	printf("%pt", arr);
      	printf("%pt", arr+1);
      	return 0;
      }//编译结果:0133FDE4     0133FDF0
       //两个地址差值是12,是3个元素的大小,恰好说明了二维数组名是首行元素的地址,不是首行元素首列的地址,所以不是指针的指针
      
      • 四种方法传参
      void function(int arr[3][5])   //直接传递数组
      {}
      void function(int arr[][5])    //直接传递数组,必须给定第2维长度,第1维长度可以省略,可以不知道有多少行,但必须知道
      {}                             //一行有多少元素
      void function(int (*arr)[5])   //传递数组指针,必须给定第2维长度
      {}
      int main()
      {
      	int arr[3][5];
      	function(arr);
      	return 0;
      }
      
      #include   
      void function(int n, char** arr)//形参为指针的指针,但是实参必须为指针,不能是数组名
      {
          int i;
          for (i = 0; i < n; i++) 
          {
              printf("arr[%d] = %sn", i, arr[i]);
          }
      }
      int main()
      {
          char* a[3];
          char arr[][5] = { "abc", "def", "ghi" };
          a[0] = arr[0];  //相当于a[0] = &arr[0][0];  
          a[1] = arr[1];
          a[2] = arr[2];
          function(3, a);    
          return 0;
      }
      

指向数组元素的指针 指针与一维数组
  • 首先需要了解数组名是什么?

    数组名是数组首元素的地址。

  • 但是又两个例外:

    • sizeof(数组名) – 数组名表示整个数组 – 计算的是整个数组的大小,单位是字节
    • &数组名 – 数组名表示整个数组 – 取出的是整个数组的地址
  • 通过指针引用一维数组

    我们知道一个数组包含了若干个元素,每个数组元素都在内存中连续存放,它们都有相应的地址

    指针变量可以指向变量,也可以指向数组元素,任何由数组下标完成的操作都可以由指针完成

    指向数组元素的指针也可以称为 “数组指针”,也就是说,所谓数组元素的指针,即指针变量中存放的是数组元素的地址。

int a[5] = {1, 2, 4, 6, 8};
int* pointer;
pointer = &a[0];//使指针变量pointer指向a数组中序号为0的元素
//在数组中,我们知道数组名可以代表数组中首元素(序号为0的元素)的地址,所以下面两个语句是等价的
pointer = &a[0];
pointer = a;

​ 对于指向数组元素的指针,在定义指针变量时,也可以对其进行初始化

int* pointer = &a[0];
  • 引用一维数组元素时指针的运算

    当指针指向数组元素时,例如p = &a[0];时,可以对指针进行如下运算:

    • 加、减一个整数:p+1,p-1;

      p+1指向同一数组中的下一元素,p-1指向同一数组中的上一元素。

      注意:执行p+1时不是将p的值简单的加1,而是加上一个数组元素所占用的字节数。不同类型的数组元素,所占用的字节数不同

      #include 
      int main()
      {
      	int arr[5] = { 1,2,3,4,5 };
      	int* p1 = &arr[1];
      	int* p2;
      	p2 = p1 + 1;
      	printf("%pn", p1);
      	printf("%pn", p2);
      	return 0;
      }//编译结果:00F2FAE8  00F2FAEC     
       //数组元素是int类型,每个数组元素占4个字节
      
    • 自加、减运算:p++, ++p,p–, --p;

      p++指向同一数组中的下一元素即:a[1]

    • 两个指针相减:p1-p2; 注意只有p1和p2都指向同一数组中的元素时才有意义

      若p1和p2都指向同一数组,则p2-p1的结果表示p2所指向的元素和p1所指向元素之间相差的元素个数

      #include 
      int main()
      {
      	int arr[5] = { 1,2,3,4,5 };
      	int* p1 = &arr[1];
      	int* p2 = &arr[5];
      	printf("%dn", p2 - p1);
      	printf("%pn", p1);
      	printf("%pn", p2);
      	return 0;
      }//编译结果:4   007FF724   007FF734
       //  4 = (007FF734-007FF724)/4  
      
  • 通过指针引用一维数组元素

    • 引用一个一维数组元素,有以下3种方法

      • 下标法。如a[0]

      • 通过数组名计算数组元素地址,找出元素的值。如*(a+i),其中a是数组名

      • 用指针变量指向数组元素。如*(p+i),其实找的是下标为 i 的地址,其中a是数组名,p是指向数组元素的指针变量

指针与二维数组

指针变量可以指向一维数组中的元素,也可以指向二维数组中的元素。

  • 二位数组元素的地址

    • 假设有一个二维数组a,它有3行5列。其定义如下:

      int a[3][5] = {{2, 4, 6, 8, 10}, {12, 14, 16, 18, 20}, {22, 24, 26, 28, 30}}

      其中,a是二维数组名,它的各元素是按行顺序存储的。

    • 二维数组包含3行,可将其看成是3个分数组:a[0], a[1], a[2],每个分数组是包含5个列元素的一维数组。

      a[0]所代表的一维数组又包含5个元素:a[0][0]、a[0][1]、a[0][2]、a[0][3]、a[0][4]

    • 对于二维数组,a代表的是首行(序号为0的行)的地址,a+1和a+2是分别代表序号为1和序号为2的行的首地址;

      假设二维数组首行首地址为1000,每个整型数据占用4个字节,则a+1的值为a[1]的首地址:1000+4*5=1020;

      a+2的值为a[2]的首地址:1040。

      a[0], a[1], a[2]分别是第0、1、2行分数组名,所以a[0]代表一维数组a[0]中第0列元素的地址,即a[0]的值为&a[0][0]

      同理:a[1]的值为&a[1][0],a[2]的值为&a[2][0]

    • a[0]是一维数组名,该一维数组中序号为1的元素的地址用a[0]+1表示,其中“1”表示1个列元素的字节数,即4个字节.

    • a[i]和* (a+i)等价;a[0]+1和* (a+0)+1的值都为&a[0][1]所以*(a[0]+1)和*(*(a+0)+1)是a[0][1]的值。

    • 总结下来:*(a[i]+j) 或 *(*(a+i)+j) 是 a[i][j] 的值

  • 指向二维数组元素的指针变量
指针与字符串(字符指针)
  • 指向字符的指针

  • 用字符数组存放字符串

    在C语言程序中,引用一个字符串,可以使用字符数组存放一个字符串,然后通过数组名和下标引用符引用字符串中的一个字符,还可以通过数组名和格式声明“%s”输出该字符串。

  • 指向字符串常量的指针

    可以用字符指针变量指向一个字符串常量,然后通过字符指针变量引用字符串常量

    #include 
    int main()
    {
    	char* string;   //定义一个 char* 型字符指针变量 string
    	string = "I am happy";   //把字符串第1个yua
    	printf("%sn", string);  //编译结果:I am happy
    	printf("%cn", *string); //编译结果:I
    	return 0;
    }
    

    C语言对字符串的处理是按照字符数组的方式进行的,即在内存中开辟了一个字符数组用来存放该字符串变量,然而这个字符数组是没有名字的,因此不能用数组名来引用,只能用指针变量来引用。

    对于字符指针变量 string初始化,其实是把字符串的第一个元素的地址,即存放字符串的字符数组首元素地址赋给指针变量string,使其指向字符串的第一个字符。

    • 一道面试题
    #include 
    int main()
    {
        char str1[] = "hello world.";
        char str2[] = "hello world.";
        const char* str3 = "hello world.";
        const char* str4 = "hello world.";
        if (str1 == str2)
            printf("str1 and str2 are samen");
        else
            printf("str1 and str2 are not samen");
        if (str3 == str4)
            printf("str3 and str4 are samen");
        else
            printf("str3 and str4 are not samen");
        return 0;
    }//编译结果:str1 and str2 are not same         str3 and str4 are same           
    

    这里 str3 和 str4 指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以 str1和 str2 不同,str3 和 str4 不同。

  • 指向字符数组的指针

    可以定义一个字符数组存放一个字符串。要操作某个字符串,可以定义一个指向字符数组开头的指针变量,然后通过操作该指针变量就可以访问字符串中的每一个字符。

指针与函数 指针做函数的参数(指针传参)

函数的参数可以是整型、字符型、浮点型,也可以是指针类型。它的作用是将一个变量的地址传递到另一个函数中(传址调用)。

  • 一级指针传参

    #include 
    void print(int* p, int sz)
    {
    	int i = 0;
    	for (i = 0; i < sz; i++)
    	{
    		printf("%dt", *(p + i));
    	}
    }
    int main()
    {
    	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
    	int* p = arr;
    	int sz = sizeof(arr) / sizeof(arr[0]);
    	print(p, sz);   //一级指针p,传给函数
    	return 0;
    }//编译结果: 1   2   3   4   5   6   7   8    9   10
    
    • 思考:当一个函数的参数部分为一级指针时,函数能接收什么参数?

      void function(int* p)  //函数形参为一级指针,则实参也需要是一级指针或者非指针变量的地址
      {}
      int main()
      {
      	int a = 10;
      	int* p1 = &a;
      	function(&a);
      	function(p1);
      }
      
  • 二级指针传参

    #include 
    void function(int** ptr)  
    {
    	printf("num = %dn", **ptr);
    }
    int main()
    {
    	int n = 10;
    	int* p = &n;   //p是一级指针
    	int** pp = &p; //pp是二级指针
    	function(pp);
    	function(&p);
    	return 0;
    }
    
    • 思考:当一个函数的参数为二级指针时,可以接收什么参数?

      void function(char** p)  //函数参数为二级指针,则实参也需要是二级指针或者一级指针变量的地址
      {}
      int main()
      {
          char c = 'p';
          char* pc = &c;
          char** ppc = &pc;
          char* arr[10];//定义了一个数组元素为char*的数组
          function(&pc);
          function(ppc);
          function(arr);//这种传参也是可以的
          return 0;
      }
      
指针函数

在C语言中,允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数。

定义:

指针型函数的一般形式:返回类型名* 指针变量名(函数参数类型) 例如:int* pointer(int, int);

这表示:pointer()这个函数返回的类型是整型指针,返回的指针指向一个整型变量

指向函数的指针(函数指针)

函数指针的定义:

  • 函数指针就是指向函数的指针(存放函数地址的指针),指向函数的指针变量存储的是一个函数在内存中的入口地址

  • 函数的入口地址:在程序中定义一个函数,在编译时,编译系统会为函数代码分配一段存储空间,指向存储这个函数第一条指令的地址。

#include 
int function(int x, int y)
{
	return x + y;
}
int main()
{
	printf("%pt", function);
	printf("%pt", &function);
	return 0;
}//编译结果:  005C13C5    005C13C5
  • 从上述代码中,看出 function == &function,但是值得注意的是在数组中 数组名 != &数组名(注意!)

  • 函数名和&函数名是等价的,但是数组名和&数组名不一样

函数指针的使用:

  • 定义:

    指向函数的指针变量的一般形式:返回类型名(* 指针变量名)(函数参数类型) 例如:int (*pointer)(int, int);

    定义了pointer 为一个指向函数的指针变量,它可以指向函数的类型为整型,并且为有2个整型参数的函数;

    pointer 的类型用int (*)(int, int)来表示。

    void function(char* str)
    {}
    int main()
    {
       (void)(*pf)(char*) = &function;   //函数指针的定义
        return 0;
    }
    
  • 函数调用:

    #include 
    int add(int x, int y)
    {
    	return x + y;
    }
    int main()
    {                                 
    	int (*pf)(int, int) = &add;   //函数定义  注意这里的 “*” 表示:说明pf是一个指针变量,不是一般的变量
    	int ret = (*pf)(3, 5);  //函数调用   而这里的 “*” 表示:对函数指针pf进行解引用
    	int ret = pf(3, 5);
    	int ret = add(3, 5); //这san
    	return 0;       
    }//这里也可以是:int (*pf)(int, int) = add  “&”可以省略  这说明:add == pf (add和pf本质上都是函数的地址)
     //int ret = (*pf)(3, 5) 等价于 int ret = pf(3, 5)
    
    • 不管是定义函数指针还是使用函数指针调用函数,对指针变量一定要打括号,要考虑操作符的结合性的优先级
  • 定义了指向函数的指针变量,并不表示这个指针变量能够指向任何函数,它只能指向在定义时指定类型的函数;

    例如:int (*pointer)(int, int);表示指针变量pointer只能指向函数返回值为整型并含有两个整型参数的函数。

  • 在一个程序中,一个指针变量可以先后指向同类型的不同函数。

  • 对指向函数的指针变量不能进行像p++,p--,p+n这样的算数运算。

  • 通过指针变量调用函数比较灵活,可以根据不同情况先后调用不同的函数,但是用函数名调用函数,只能调用所指定的一个函数。

函数指针数组

存放函数指针的数组

函数指针数组的一般定义方式:返回值类型 (*数组名[])(函数参数类型) 例如:int (*parr[10])(int, int)

  • 可以这样认为:parr先和 [] 结合表示它是一个数组,数组的元素是int(*)()类型的函数指

函数指针数组的初始化:和一般数组一样,函数指针数组可以在定义时进行赋值

函数指针数组的使用:

  • 运用函数指针数组编写计算机代码:
#include 
int add(int a, int b)
{
	return a + b;
}
int sub(int a, int b)
{
	return a - b;
}
int mul(int a, int b)
{
	return a * b;
}
int div(int a, int b)
{
	return a / b;
}
int main()
{
	int x = 0;
	int y = 0;
	int input = 1;
	int ret = 0;
	int (*parr[5])(int x, int y) = {NULL, add, sub, mul, div };  //函数指针数组的初始化
	while (input)
	{
		printf("***************************n");
		printf("**    1.add       2.sub  **n");
		printf("**    3.mul       4.div  **n");
		printf("***************************n");
		printf("请选择:");
		scanf("%d", &input);
		if ((input <= 4 && input >= 1))
		{
			printf("输入两个整型操作数:");
			scanf("%d%d", &x, &y);
			ret = (*parr[input])(x, y);  //函数指针数组的使用
			printf("ret = %dn", ret);
		}
		else
			printf("输入有误n");

	}
	return 0;
}

需要注意的是,函数指针数组本质上是一个数组,所以对函数的要求是:参数和参数类型、返回类型要相同才行。

指向函数指针数组的指针

指向【函数指针数组】的指针

一般定义:返回值类型 (*(*指针变量名)[])(函数参数类型) 例如:int (*(*parr)[10])(int, int)

  • 可以这样认为:parr先和 * 结合,表示parr是一个指针,然后再和 [10] 结合,表示parr这个指针是指向一个数组,这个数组有10个元素,每个元素的类型是函数指针 int (*)(int, int)。

函数指针数组 和 指向函数指针数组的指针的关系:

int(*p1[10])(int, int);    //函数指针数组,本质上是一个数组
int(*(*p2)[10])(int, int); //指向函数指针数组的指针,本质上是一个指针
p2 = &p1; //两个之间的关系,p1是一个数组,p2是一个指针
回调函数

回调函数就是一个通过函数指针调用的函数。

把一个函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

可以这样理解:回调函数的参数是另一个函数的指针(地址)。

  • 运用回调函数编写计算机代码:
int Add(int x, int y)
{
	return x + y;
}
int Sub(int x, int y)
{
	return x - y;
}
int Mul(int x, int y)
{
	return x * y;
}
int Div(int x, int y)
{
	return x / y;
}
void menu()
{
	printf("**************************n");
	printf("**** 1. add    2. sub ****n");
	printf("**** 3. mul    4. div ****n");
	printf("****     0. exit      ****n");
	printf("**************************n");
}
int Calc(int (*pf)(int, int))    //回调函数,回调函数的参数是一个函数指针
{
	int x = 0;
	int y = 0;
	printf("请输入2个操作数>:");
	scanf("%d%d", &x, &y);
	return pf(x, y);
}
int main()
{
	int input = 0;
	do {
		menu();
		int ret = 0;
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case 1:
			ret = Calc(Add);
			printf("ret = %dn", ret);
			break;
		case 2:
			ret = Calc(Sub);
			printf("ret = %dn", ret);
			break;
		case 3:
			ret = Calc(Mul);
			printf("ret = %dn", ret);
			break;
		case 4:
			ret = Calc(Div);
			printf("ret = %dn", ret);
			break;
		case 0:
			printf("退出程序n");
			break;
		default:
			printf("选择错误,重新选择!n");
			break;
		}
	} while (input);
	return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1025375.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号