C语言 || 特殊的指针

目录

前言

二级指针

字符指针 

 指针数组 

数组指针

 数组参数和指针参数

一维数组传参

二维数组传参

一级指针传参

二级指针传参

 函数指针

函数指针数组

指向函数指针数组的指针 


前言

C语言中,指针(即指针变量)用来存放变量的地址

int a = 10;
int* pa = &a;    //pa是指针变量,用来存放整型变量a的地址

二级指针

int a = 10;        //整型变量a
int* pa = &a;      //一级指针变量pa,存放a的地址  
int* *ppa = &pa;   //二级指针变量ppa,存放pa的地址

如何访问a呢?试试运行如下代码 

int a = 10;        //整型变量a
int* pa = &a;      //一级指针变量pa,存放a的地址  
int* *ppa = &pa;   //二级指针变量ppa,存放pa的地址

printf("%d\n", *pa);
printf("%d\n", **ppa);

运行结果如下:

具体分析一下指针类型

//创建了一个变量a,类型为整型
int a = 10;        
//创建了一个变量pa,“*”代表变量pa是一个指针变量,“int”代表pa指向的变量是一个整型变量
int* pa = &a;       
//创建了一个变量ppa,“*”代表变量ppa是一个指针变量,“int*”代表ppa指向的变量是一个“int*”类型的变量
//int*类型变量可以理解为一级指针变量
int* * ppa = &pa;   

//这里的解释对之后理解指针的其他用法有很大帮助

字符指针 

//单个字符的访问与修改
char ch = 'a';
char* c1 = &ch;
printf("%c\n", *c1);

*c1 = 'b';
printf("%c\n", *c1);
//字符串的访问
char* p = "abcdefg";
printf("%s", p);
//指针变量p存放了字符串首元素的地址,当printf使用%s打印时给出首元素地址,会按照顺序打印整个字符串
//遇到'\0'停止

需要注意的一点是,我们不可以修改上述代码中字符串的内容 

char* p = "abcdefg";
//当这样写时,“”内的内容是常量字符串

//正确稳妥的写法如下
const char* p = "abcdefg";

//const放在最前面,修饰*p,则*p不会被改变

 指针数组 

首先要明确的是:指针数组是数组而非指针,指针数组是存放指针变量的数组

我们在学习数组之前,要想创建多个变量(这里以3个变量为例) ,我们的做法是:

int a;
int b;
int c;

学习完数组后:

int arr[3];    //“[]”代表arr是数组,“int”代表数组内的元素为整型

那么对于指针和指针数组也是如此:

int a;
int b;
int c;
int* pa = &a;
int* pb = &b;
int* pc = &c;

//指针数组
int* p[3] = { &a, &b, &c };    //“[]”代表p是数组,“int*”代表数组内的元素是“int*”类型
//(即一级整型指针)   
//此时p就是存放指针的数组,即指针数组                                  

按照上面的理解看一下这句代码 

char** arr[4];
//"[]"说明arr是一个数组,“char**”代表arr中的元素是“char**”类型(即二级字符指针)

 指针数组可以用来模拟二维数组,如下代码所示:

int row = 0;    //行
int col = 0;    //列

//二维数组
int arr[3][4] = { { 1, 2, 3, 4 }, { 2, 3, 4, 5 }, { 3, 4, 5, 6 } };
for(row = 0; row < 3; row++)
{
    for(col = 0; col < 4; col++)
    {
        printf("%d ", arr[row][col]);
    }
    printf("\n");
}

//指针数组
int arr1[4] = { 1, 2, 3, 4 };
int arr2[4] = { 2, 3, 4, 5 };
int arr3[4] = { 3, 4, 5, 6 };
int* arr[3] = { arr1, arr2, arr3 };    //数组名是数组首元素地址
for(row = 0; row < 3; row++)
{
    for(col = 0; col < 4; col++)
    {
        printf("%d ", arr[row][col]);
        //换成这样写试试:printf("%d ", *(arr[row] + col));

    }
    printf("\n");
}

运行结果都是一样的, 在这里就不附上了

由上图最后一条注释可知: arr[ i ]与*(arr + i)是等价的

上面的两种代码,区别在于:

二维数组元素在内存中是连续存放的,但指针数组并不一定

数组指针

我们知道:

整型指针是指向整型的指针

字符指针是指向字符的指针

那么数组指针就是指向数组的指针

int* arr1[10];
int (*arr2)[10];
//arr1和arr2分别是什么?

 arr1先与[10]结合,说明arr1是一个数组,剩下的int*是数组元素类型,所以arr1是指针数组

arr2先与*结合,说明arr2是一个指针,int [10]代表arr2指向的对象是一个有10个整型元素的数组([10]代表数组,int代表数组元素是整型)

即arr2是一个数组指针,可以指向一个数组,该数组有10个元素,每个元素是int类型

分别把数组名去掉就能看出类型

int* [10]         指针数组

int (*)[10]      数组指针

一般情况下,数组名代表的是数组首元素地址,而&数组名代表整个数组的地址

C语言 || 数组-CSDN博客

那么如何使用数组指针来存放数组的地址呢?

int arr[10] = { 0 };
//数组指针是一个指针
*p
//指针指向数组
*p[]    
//这样写p会先与[]结合,变成指针数组,所以应该这样写
(*p)[]
//指向的数组有10个元素
(*p)[10]
//每个元素的类型是整型
int (*p)[10] = &arr;

接下来做一个小练习:

char* arr[5];
pc = &arr;//此处pc应该怎么定义类型

答案如下:

char* arr[5];
//基于前面的讲解,这里直接告诉你这是一个指针数组

(*pc)[5] = &arr;
//&arr是整个数组的地址,所以需要一个指向数组的指针来接收

char* (*pc)[5] = &arr;
//由于指针数组中的元素是char*类型,所以在前面补上char*,意指arr中的元素类型

数组指针的使用常用于二维数组,虽然也能用于一维数组,但会增添不必要的麻烦

    int arr1[10]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int (*p)[10] = &arr1;
    int i = 0;
    for(i = 0; i < 10 ; i++)
    {
        printf("%d ", *(*p + i));
    }
    //*(*p + i)我们一步步来理解

    //首先p中存放了arr1整个数组的地址,*p代表取出了arr1数组首元素的地址

    //*p + i 即向数组后面的元素移动

    //但此时*p + i 仍是地址,想访问其中的元素仍需要解引用,即*(*p + i)

    //这段代码能打印数组所有元素的值,但是我们平常肯定不会这么写

我们平常一定会这么写:

    int arr1[10]={ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    int* p = arr1;
    int i = 0;
    for(i = 0; i < 10 ; i++)
    {
        printf("%d ", *(p + i));
    }
    //简单方便又好理解
    //数组指针用在二维数组比较好用

 那么来看看数组指针怎么用到二维数组中去:

还是以打印整个数组元素为例,我们写成函数的形式

//运用以前的知识,我们能写出这样的代码
void print(int arr1[][5], int row, int col);

int main()
{
    int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
    int row0 = sizeof(arr) / sizeof(arr[0]);    //利用数组名的特点计算行与列
    int col0 = sizeof(arr[0]) / sizeof(arr[0][0]);

    print(arr, row0, col0);
    return 0;
}

void print(int arr1[][5], int row, int col)
{
    int i = 0;
    for(i = 0; i < row; i++)
    {
        int j = 0;
        for(j = 0; j < col; j++)
        {
            printf("%d ", arr1[i][j]);
        }
        printf("\n");
    }
}

那我们如果用数组指针来作为形参如何呢?

//运用以前的知识,我们能写出这样的代码
void print(int (*p)[5], int row, int col);

int main()
{
    int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };
    int row0 = sizeof(arr) / sizeof(arr[0]);    //利用数组名的特点计算行与列
    int col0 = sizeof(arr[0]) / sizeof(arr[0][0]);

    print(arr, row0, col0);
    return 0;
}

void print(int (*p)[5], int row, int col)
{
    int i = 0;
    for(i = 0; i < row; i++)
    {
        int j = 0;
        for(j = 0; j < col; j++)
        {
            printf("%d ", *(*(p + i) + j));
        }
        printf("\n");
    }
}
//arr是一个3行5列的二维数组,print(arr)实际上是把3个有5个元素的数组的首地址传了过去
//(即第一个有5个元素的数组的地址),详解请看之前本人账号数组的讲解或自己查阅“数组名的含义”

我们来讲解一下 void print(int (*p)[5], int row, int col)和 *(*(p + i) + j)的含义

arr传递的是首元素的地址,arr是二维数组,所以arr实际传递的是第一行整个数组的地址

所以我们需要一个指向数组的指针来接收,数组中的每个元素又是整型,这就有了

int (*p)[5]

而*(p + i)取出的是第i + 1行数组中的首元素地址,想要具体访问第j + 1列元素的数值就要这样 *(*(p + i) + j)

由上面的讲解可知,p + 1实际上跳过了一行的元素,即20个字节,这是为什么呢?

有一种理解是这样的:

经过前面的学习我们知道
int* pp;
//*代表pp是一个指针变量,int代表pp指向的对象是整型


int (*p)[5];
//我们想知道为什么加1会跳过的字节数,就要先知道p是什么类型的变量
 int (*)[5]
//将变量名去掉就可以得到该变量的类型  
//  int (*)[5],*代表p为指针变量,int [5]代表p指向一个有5个元素的数组
//所以在这里加一会跳过整个数组,即20个字节

在进行下一个内容的学习之前,我们先来判断如下变量的类型,作为以上内容的一个小练习

int arr1[5];
int* arr2[5];
int (*arr3)[5];
int (*arr4[10])[5];

 arr1先与[5]结合,说明arr1是一个有5个元素的数组,int代表数组内的元素(即这5个元素)为整型

arr2先与[5]结合,说明arr2是一个有5个元素的数组,int*代表数组内的元素为整型指针

*说明arr3是一个指针变量,[5]说明arr3指向的对象是一个含有5个元素的数组,int代表数组内的元素为整型

arr4先与[10]结合,说明arr4是一个含有10个元素的数组,然后把arr4[10]去掉,剩下来的即为数组元素的类型,即int (*)[5],说明数组内的元素为数组指针

综上:

arr1为数组

arr2为指针数组

arr3为数组指针

arr4为存放数组指针的数组

 数组参数和指针参数

写代码时经常要把数组或者指针传给函数,这里我们来总结一下传参的方式

一维数组传参

int main()
{

   int arr1[10] = { 0 };
   test1(arr1);

   int* arr2[10];
   test2(arr2);
}
//以下为传参方式
void test1(int arr[]);
void test1(int arr[10]);
void test1(int* p);

void test2(int* arr[10]);
void test2(int** arr);       //二维指针存放的是一维指针的地址

二维数组传参

int main()
{
    int arr0[3][5] = { 0 };
    test(arr0);
    return 0;
}
void test(int arr[3][5]);
void test(int arr[][5]);
void test(int (*p)[5]);

一级指针传参

一级指针没什么特殊的,传过去的指针是什么类型,就用什么类型的指针来接收就OK

比如:

void print(int *p, int sz);

int main()
{
    int arr[5] = { 1, 2, 3, 4, 5 };
    int size = sizeof(arr) / sizeof(arr[0]);    
    int* a = arr;
    print(a, size);
    return 0;
}

void print(int *p, int sz)
{
    int i = 0;
    for(i = 0; i < sz; i++)
    {
        printf("%d ", *(p + i));      
    }
}

二级指针传参

void print(int **p);

int main()
{
    int num = 10;
    int* pa = &num;
    int* *ppa = &pa;
    print(&pa);
    print(ppa);
    return 0;
}

void print(int **p)
{
   printf("num = %d\n", **p);
} 

 函数指针

学习函数指针要与数组指针类比

数组指针是指向数组的指针

函数指针就是指向函数的指针

一般情况下,数组名代表数组首元素的地址,&数组名代表取出整个数组的地址

但对于函数来说,函数名和&函数名都代表函数的地址 (可以自行去编译器尝试)

那么怎样能像存放数组地址那样,来存放函数的地址呢?

int Add(int x, int y)
{
    return x + y;
}
int main()
{
    //想要存放地址,首先要创建一个指针变量,*pf
    *pf = &Add    //有没有&都一样   
    //法1:指针指向的对象是函数,该函数类型如何判断呢?
    //判断方法为int Add(int x, int y)去掉函数名和形参后,剩余的部分就是函数类型
    //即int (int , int )

    //法2:指针指向的对象是函数,那么函数的参数类型是什么呢?
     (*pf) (int, int) = &Add;
    //函数的参数的写完了,那么函数的返回值类型是什么呢?
    int (*pf) (int, int) = &Add;
    return 0;
}

我们可以通过函数指针来使用函数:

int Add(int x, int y)
{
    return x + y;
}
int main()
{
   
    int (*pf) (int, int) = &Add;
    int ret = (*pf)(3, 4);
    //写成这样也可以    int ret = (pf)(3, 4);
    //原因就是函数名和&函数名都代表函数的地址
    printf("%d", ret);
    return 0;
}

你一定会觉得这么麻烦有什么必要,但实际上函数指针在一些情况下能简化我们的代码,比如:

#include <stdio.h>


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.Exti********\n");
    printf("*******************\n");
}
int main()
{
    
    int input;
    int a;
    int b;
    int ret;
    do
    {
        menu();
        scanf("%d", &input);
        switch(input)
        {
            case 1:
                printf("请输入两个操作数\n");
                scanf("%d %d", &a, &b);
                ret = Add(a, b);
                printf("%d\n", ret);
                break;
            case 2:
                printf("请输入两个操作数\n");
                scanf("%d %d", &a, &b);
                ret = Sub(a, b);
                printf("%d\n", ret);
                break;
            case 3:
                printf("请输入两个操作数\n");
                scanf("%d %d", &a, &b);
                ret = Mul(a, b);
                printf("%d\n", ret);
                break;
            case 4:
                printf("请输入两个操作数\n");
                scanf("%d %d", &a, &b);
                ret = Div(a, b);
                printf("%d\n", ret);
                break;
            case 0:
                printf("退出成功\n");
                break;
            default:
                printf("输入错误,请重新输入\n");
                break;
        }
    } while (input);
    
    return 0;
}

这段代码实现了一个整数的加减乘除,但是switch的部分代码重复了好多,看着冗杂,函数指针可以帮助我们简化这部分的代码

在上面添加calc函数的声明

void calc(int (*pf)(int, int))
{
    int x;
    int y;
    printf("请输入两个操作数\n");
    scanf("%d %d", &x, &y);
    int ret = (*pf)(x, y);
    printf("%d\n", ret);
}

修改switch:

 switch(input)
        {
            case 1:
                 calc(Add);
                break;
            case 2:
                 calc(Sub);
                break;
            case 3:
                 calc(Mul);    
                break;
            case 4:
                 calc(Div);
                break;
            case 0:
                printf("退出成功\n");
                break;
            default:
                printf("输入错误,请重新输入\n");
                break;
        }

这样switch中的代码就显得很清晰,没有了之前的冗杂多余

附上完整版:

#include <stdio.h>
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.Exti********\n");
    printf("*******************\n");
}
void calc(int (*pf)(int, int))
{
    int x;
    int y;
    printf("请输入两个操作数\n");
    scanf("%d %d", &x, &y);
    int ret = (*pf)(x, y);
    printf("%d\n", ret);
}
int main()
{
    
    int input;
    int ret;
    do
    {
        menu();
        scanf("%d", &input);
       switch(input)
        {
            case 1:
                 calc(Add);
                break;
            case 2:
                 calc(Sub);
                break;
            case 3:
                 calc(Mul);    
                break;
            case 4:
                 calc(Div);
                break;
            case 0:
                printf("退出成功\n");
                break;
            default:
                printf("输入错误,请重新输入\n");
                break;
        }
    } while (input); 
    
    return 0;
}

函数指针数组

我们仍然以实现两个整数的加减乘除为例,我们上面运用函数指针的方法解决了case后面代码重复的问题,那么我们再来想一想,如果我想要在后面继续添加100个计算器的功能,岂不是要写100多个case吗?所以我们不用switch,用函数指针数组来处理这种情况

函数指针数组就是存放函数指针的数组 

//函数指针
int (*pf)(int, int);

//函数指针数组--存放函数指针的数组
int (*pf[5])(int, int);

//分析方法如下:
//pf先与[5]结合,说明pf是一个含有5个元素的数组
//然后把分析过的部分(即pf[5])去掉,剩下的部分就是数组元素的类型
//因此int(*)(int, int)就是数组元素的类型,是一个函数指针类型

在主函数中添加 

 int (*arr[5])(int, int) = { 0, Add, Sub, Mul, Div };
    do
    {
        menu();
        scanf("%d", &input);
        if(input == 0)
        {
            printf("退出成功\n");
        }
        else if(input >= 1 && input <= 4)
        {
            calc(arr[input]);
        }
        else
        {
            printf("输入错误,请重新输入\n");
        }
    } while (input); 

这样我们想要添加新的功能时只需要修改数组中的元素和else if中的数字即可,不需要写100多个case了

附上完整代码:

#include <stdio.h>
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.Exti********\n");
    printf("*******************\n");
}
void calc(int (*pf)(int, int))
{
    int x;
    int y;
    printf("请输入两个操作数\n");
    scanf("%d %d", &x, &y);
    int ret = (*pf)(x, y);
    printf("num = %d\n", ret);
}
int main()
{   
    int input;
    int ret;
    int (*arr[5])(int, int) = { 0, Add, Sub, Mul, Div };
    do
    {
        menu();
        scanf("%d", &input);
        if(input == 0)
        {
            printf("退出成功\n");
        }
        else if(input >= 1 && input <= 4)
        {
            calc(arr[input]);
        }
        else
        {
            printf("输入错误,请重新输入\n");
        }
    } while (input); 
    
    return 0;
}

指向函数指针数组的指针 

这个用的极少,在这里只简单的介绍一下 

//函数指针
int (*pf)(int, int);

//函数指针数组--存放函数指针的数组
int (*pf[5])(int, int);

//指向函数指针数组的指针
int (*(*ppf)[5])(int, int) = &pf;

//分析方法如下:
//ppf先与*结合,*说明ppf是一个指针变量,[5]代表ppf指向一个有5个元素的数组
//把分析过的部分(即(*ppf)[5])去掉,剩下的就是数组元素的类型
//剩余部分即int(*)(int, int),说明数组元素的类型是函数指针
  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值