欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

指针

程序员文章站 2024-01-01 17:07:40
...

理解

指针数组
数组指针
函数指针
函数指针数组
指向函数指针数组的指针

1.指针数组

指针数组到底是指针还是数组呢?
(其实它是一个数组,只是这个数组中的每个元素都是指针类型)
eg:

int *arr1[3];
char *arr2[3];
char **arr3[3];

这些都是指针数组,为了大家更清楚的理解,我们把它在内存中的排布画出来:
指针

2.数组指针

数组指针,是指针
eg:

    int(*p)[10];
    //p先和*结合([]的优先级要高于*的,所以必须加上()来保证p和*先结合),说明p是一个指针变量,然后指针指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针。

这里的p是指针,是指向一个数组的指针。

数组指针的使用

eg:

int main()
{
    int *arr[10];
    int(*p)[10] = arr;//这是错误的,因为arr在这里代表的是数组首元素的地址,而arr数组中每个元素的类型都是int*,所以arr代表的是int**,不能把一个int**类型的赋给一个int*类型的
    int(*p)[10] = &arr;//(&arr)表示取的是数组的地址,arr中每个元素的类型是int*,int(*p)[10]表示的是p指针指向一个数组,这个数组中的每个元素都是int型,赋值就是说让p指向arr,可是它两类型不同,所以也不能赋值
    int*(*p)[10] = &arr;//这样是可以赋值的

    return 0;
}
#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

//void test(int arr[3][5],int row,int col)
void test(int(*arr)[5], int row, int col)//int (*arr)[5],arr是一个指针,指向一个数组,这个数组中有5个元素,每个元素的类型为int型,arr恰好指向的就是二维数组的第一行
                                         //二维数组首元素的地址就是第一行的地址
{
    int i = 0;
    int j = 0;
    for (i = 0; i < row; i++)
    {
        for (int j = 0; j < col; j++)
        {
            printf("%d ", *(*(arr + i) + j));//在二维数组中数组名代表的是第一行的地址,*(arr+i)只是得到了第i行的数组名,数组名并没有单独放在sizeof()内部,所以要降级变为首元素地址,就是第i行第一个元素的地址,*(arr + i) + j)就是第i行第j个元素的地址,*(*(arr + i) + j))通过解引用就得到了第i行第j列的元素
        }
        printf("\n");
    }
}


int main()
{
    int arr[3][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
    test(arr, 3, 5);

    return 0;
}
//#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

int main()
{
    int arr[10] = { 0 };
    //arr--表示数组首元素地址
    //&arr--表示数组的地址
    printf("%p\n", arr);
    printf("%p\n", arr + 1);
    printf("%p\n", &arr + 1);

    return 0;
}

指针
我们可以看到这三个输出结果是截然不同的,这是因为数组的地址和数组首元素的地址值虽然是相同的,但意义不同。

数组的地址如何来存储?

int arr[10] = {0};
int (*p)[10] = &arr;//数组地址要放到数组指针中
                    //int(*)[10]--是数组指针类型

我们看看下面的代码:


#include<stdio.h>

int main()
{
    int arr[5];//整型数组 
    int *parr1[10];//指针数组
    int (*parr2)[10];//数组指针
    int(*parr3[10])[5];//存放指针数组的数组

    return 0;
}

首先,parr3是一个数组,这个数组存放的是指针,指针指向的是一个数组,该数组有5个元素,每个元素是int型,这个数组最终能存10个元素。给大家画图理解一下吧。
指针
指针和数字的定义与声明
eg:
test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

extern char arr[];//声明外部变量arr

int main()
{
    printf("%s\n", arr);

    return 0;
}

sum.c

#define _CRT_SECURE_NO_WARNINGS 1

char arr[] = "abcdef";

指针
通过这个例子我们是想说明声明和定义其实使用的是同一块空间,不然在test.c函数中输出的结果就不一定是”abcdef”了。
test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

extern char* arr;//声明外部变量arr

int main()
{
    printf("%s\n", arr);

    return 0;
}

sum.c

#define _CRT_SECURE_NO_WARNINGS 1

char arr[] = "abcdef";

在这里我们把arr声明成了指针,我们发现程序崩了,这是为什么呢?我们来画图帮大家分析一下:
指针
这说明数组不是指针。
既然已经错了,那我们如何能让字符串正常输出呢?

    printf("%s\n", (char*)&arr);

我们只要把输出函数改成上边的样子,就可以正常输出了。虽然arr指针只占了数组arr的4个字节,但它们指向的是同一个空间,对它取地址,同样能取到a的地址。我们为什么还要对它进行强制类型转换呢?因为我们在声明的时候,我们把它声明成了char*,再对它进行取地址的话就是char**类型,我们用%s输出,希望它是char*类型的,所以我们要强制类型转换。

现在我们再把它定义成指针,并声明成指针,我们来看看它是否能正常输出?
test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

extern char* p;//声明外部变量p

int main()
{
    printf("%s\n", p);

    return 0;
}

sum.c

#define _CRT_SECURE_NO_WARNINGS 1

char *p = "abcdef";

我们发现程序可以正常输出“abcdef“。

那我们就再来改一下,把它声明成数组,定义成指针。
test.c

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

extern char p[];//声明外部变量p

int main()
{
    printf("%s\n", p);

    return 0;
}

sum.c

#define _CRT_SECURE_NO_WARNINGS 1

char *p = "abcdef";

它到底能正常输出吗?
指针
我们可以发现这里输出的是一个随机值,到底是为什么呢?
我们再用画图的方式,给大家详解一下吧:
指针
我们只要把perintf()函数改成下边这个样子就可以正常输出”abcdef“了:

    printf("%s\n", (char*)*(int*)p);

为什么要写成这个样子呢?
因为p指向的是存放a的地址的空间(4个字节),所以我们对它进行强制类型转换(int占4个字节),对它进行解引用,就是从它向后取4个字节,就取出了a的地址,因为已经强转成了(int)型,取出的就相当于是整型,所以我们要再把它强转成(char*)。
数组参数,指针参数
在写代码的时候难免要把【数组】或【指针】传给函数,那函数的参数该如何设计呢?
一维数组传参
eg:

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test2(int *arr2[20])
{}
void test2(int **arr2)
{}

int main()
{
    int arr[10] = { 0 };
    int *arr2[20] = { 0];
    test(arr);
    test2(arr2);

    return 0;
}

以上的函数传参形式都是正确的。
二维数组参数
eg:

#define _CRT_SECURE_NO_WARNINGS 1

#include<stdio.h>

void test(int arr[3][4])
{}
void test(int arr[][])//不可以,二维数组传参时只能省略第一个[]的数字。
{}
void test(int arr[][5])
{}





void test(int *arr)//不可以,第一行的地址不能放到一个整型指针里边(太小)。
{}
void test(int *arr[5])//错误,arr是一个地址,只能放到一个指针里,不能放到一个数组中。
{}
void test(int (*arr)[5])//正确,一个数组的地址就应该放到一个数组指针中
{}
void test2(int **arr)//错误,这应该放的是一级指针变量的地址,或者直接把二级指针放在这
{}

int main()
{
    int arr[3][5] = { 0 };
    test(arr);//arr是一个地址(第一行的地址)

    return 0;
}

一级指针传参
eg:

#include<stdio.h>

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

int main()
{
    int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
    int *p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    printf(p, sz);

    return 0;
}

通过这个例子我们思考一下:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?
它可以接收一个整型数组名,一级指针变量,一个整型变量地址。
eg:

#include<stdio.h>

void test(int *p)
{}

int main()
{
    int a = 0;
    int *q = &a;
    int arr[10] = { 0 };
    test(&a);
    test(q);
    test(arr);

    return 0;
}

二级指针参数
eg:

#include<stdio.h>

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

int main()
{
    int n = 10;
    int *p = &n;
    int **pp = &p;
    test(pp);
    test(&p);

    return 0;
}

那我们再来想一下,当是的参数为二级指针的时候,可以接收什么参数?
它可以接收一个指针变量的地址,(int/char/..)型指针的数组数组名,二级指针变量本身。
eg:

#include<stdio.h>

void test(int** ptr)
{

}

int main()
{
    char c = 'b';
    char *pc = &c;
    char **ppc = &pc;
    char *arr[10];
    test(&pc);
    test(ppc);
    test(arr);

    return 0;
}

3.函数指针

我们先来看一段代码:

#include<stdio.h>

void test()
{
    printf("hello\n");
}

int main()
{
    printf("%p\n", test);
    printf("%p\n", &test);

    return 0;
}

指针
我们可以看到输出结果是两个地址,这两个地址就test函数的地址。
那我们是如何保存函数的地址的呢?我们来看看下面这段代码:

#include<stdio.h>

void test()
{
    printf("hello\n");
}

int main()
{
    void (*p)() = &test;//p是一个指针,指向了一个函数,p里存的是函数名的地址。
    (*p)();//对p进行解引用,就相当于取出了函数名(=test)
           //()--函数调用操作符,详细解释在我的“C语言中操作符的总结”那篇博客中
    return 0;
}

指针
我们发现这样调用函数,结果也能正常输出。

void (*p)() = test;

这句代码也是正确的,函数名和取地址也函数名代表的都是函数地址(函数的地址要存到函数指针中),函数没有首元素地址,而数组有,如果只写一个数组名,则代表的是数组首元素地址。
我们来看看具体的代码实现:

#include<stdio.h>

int Add(int x, int y)
{
    return x + y;
}

int main()
{
    int(*p)(int ,int) = Add;
    printf("%d\n", (*p)(1, 2));

    return 0;
}

我们来看看输出结果:
指针

4.函数指针数组

数组是一个存放相同类型数据的存储空间,我们已经学习了指针数组,比如:

int *arr[10];//数组的每个元素都是int*类

那把函数的地址存到一个数组中,这个数字就叫函数指针数字,那函数指针数字是如何定义的呢?

int (*p[10])();

p先和[]结合,说明p是数组,那数组的内容是说明呢?
是int(*)()类型的函数指针。
函数指针数组的用途:转移表
eg:(计算器)

#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;
}

int main()
{
    int x, y;
    int input = 0;
    int ret = 0;
    int(*pFun[5])(int, int) = { 0, Add, Sub, Mul, Div };//转移表
    while (input)
    {
        printf("***************************\n");
        printf("  1.Add            2.Sub   \n");
        printf("  3.Mul            4.Div   \n");
        printf("***************************\n");
        printf("请选择:\n");
        scanf("%d", &input);
        if ((input<4 && input>1))
        {
            printf("输入操作数:");
            scanf("%d %d", &x, &y);
            ret = (*pFun[input])(x, y);
        }
        else
        {
            printf("输入有误\n");
        }
        printf("ret = %d\n", ret);
    }

    return 0;
}

5.指向函数指针数组的指针

指向函数指针数组的指针是一个【指针】
指针指向一个【数组】,数组的元素都是【函数指针】

如何定义?

#include<stdio.h>

int main()
{
    int(*pFunArr[5])(int, int);//pFunArr--函数指针数组
    int (*(*ppFunARR)[5])(int, int) = &pFunArr;//ppFunArr--指向函数指针数组的指针

    return 0;
}

好了,今天的指针就先讲到这里,如果有什么不对的地方,还希望各位大佬指出来,大家互相学习。

上一篇:

下一篇: