Bootstrap

C语言指针详解数组指针与指针数组 函数指针与指针函数

本文通过简单的C语言代码的展示,深入理解指针、指针数组、数组指针等概念。

一、什么是指针

 C语言里,变量存放在内存中,而内存其实就是一组有序字节组成的数组,每个字节有唯一的内存地址。CPU 通过内存寻址对存储在内存中的某个指定数据对象的地址进行定位。

指针便是保存这个地址的变量。也就是说:指针是一种保存变量地址的变量

二、为什么使用指针

指针解决了一些编程中基本的问题,有些操作必须使用指针

第一,指针的使用使得不同区域的代码可以轻易的共享内存数据。

第二,指针使得一些复杂的链接性的数据结构的构建成为可能,比如链表,链式二叉树等等。

第三,C语言中的一切函数调用中,实参传递给形参的机理都是“按值传递(pass by value)”,如果我们要在函数中修改被传递过来的对象,就必须通过这个对象的指针来完成。

三、数组指针与指针数组

***数组指针

1、区分

两者我认为加个的更好理解,数组指针就是数组的指针,而指针数组就是指针的数组。

int (*p)[5];
int *p[5]

我们用上面的形式定义数组指针,下面的形式中p[5]是个数组,而int *则说明这个数组存放的是指针,代表指针的数组。

理解数组指针首先明确,数组名代表首元素的地址,数组指针代表数组整个元素的地址等价于&arr是取出整个数组,两者直接print虽然一样,但是进行+1操作就会发现其跨度不同。用下述代码段进行理解。

int demo2() {
	int arr[10] = { 9,8,7,6,5,4,3,2,1};
    int (*p)[10]=arr;
	//数组首位的元素和取出整个数组意义不一样
		printf(" %p",arr);
	printf(" %p", &arr[0]);
	printf(" %p", &arr);
	printf(" %p", p);
	printf("\n");
	printf(" %p", arr+1);
	printf(" %p", &arr[0]+1);
	printf(" %p", &arr+1);
	printf(" %p", p+1);
	printf("\n");
	return 0;
}

结果如下                     

2、多维数组指针

以二维数组为例,首先明确

二维数组的数组名代表的是首元素的地址a[0],其意义为首行元素的地址。

#include <stdio.h>
int main(){
    int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
    int (*p)[4] = a;
    printf("%d\n", sizeof(*(p+1)));

    return 0;
}

该代码段的运行结果是16 也就是16个字节,4个整型数,p是指向一维数组的指针变量,上面我们提到了,这里的p指向整个一维数组,而p+1则指向下一个一维数组,也就是{4,5,6,7}

记得一维数组中*p等价于数组名等价于首元素地址,在这里(*p)[]等价于a[][]也等价于了首元素地址,因此,*(p+1)就等价于a[1][0]首地址,所以 *(p+1)+1就是a[1][1]的地址,因此我们找到了一种遍历二维数组的方法。

#include <stdio.h>
int main(){
    int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
    int(*p)[4];
    int i,j;
    p=a;
    for(i=0; i<3; i++){
        for(j=0; j<4; j++) printf("%2d  ",*(*(p+i)+j));
        printf("\n");
    }

    return 0;
}

****指针数组 

指针数组:一个数组的元素均为指针类型数据,称为指针数组。

二级指针:指向指针数据的指针变量

int main()
{
	char *arr[]={"Hello","World"};
	char **p;
	int i;
	for(i=0; i<2; i++)
	{
		p=arr+i;
        //根据上面提到的,数组名是首元素地址,因此arr+i是数组第i个元素
        //但是数组中的元素是指针(C语言中的字符串常量是以首元素地址方式存储的),因此需要用指针p来存储
		printf("%s ",*p);
	}
	return 0;
}

四、函数指针与指针函数

指针函数

定义:指针函数,简单的来说,就是一个返回指针的函数,其本质是一个函数,而该函数的返回值是一个指针。
声明格式为:*类型标识符 函数名(参数表),这似乎并不难理解,再进一步描述一下。

int *fun(int x,int y);

这个函数的返回值不是int而是int*,一个int型指针

typedef struct _Data{
    int a;
    int b;
}Data;

//指针函数
Data* f(int a,int b){
    Data * data = new Data;
    data->a = a;
    data->b = b;
    return data;
}

int main()
{
    //调用指针函数
    Data * myData = f(4,5);
    return 0;
}

如上,该f函数的返回值是一个结构体变量的指针,在接收时也应用指针来接收

函数指针

函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,函数指针就是指向函数的指针。声明格式:类型说明符 (*函数名) (参数)  如下:

int (*fun)(int x,int y);
函数指针是需要把一个函数的地址赋值给它,有两种写法:

fun = &Function;
fun = Function;

取地址运算符&不是必需的,因为一个函数标识符就表示了它的地址,如果是函数调用,还必须包含一个圆括号括起来的参数表。调用函数指针时可以解引用或者直接调用。

int add(int x,int y){
    return x+y;
}
int sub(int x,int y){
    return x-y;
}
//函数指针
int (*fun)(int x,int y);

int main()
{
    //第一种写法
    fun = add;
	//第二种写法
    fun = &sub;

    return 0;
}

我们还可以将以上几个函数指针放到一个数组里面去,这样来定义

int (*cum[2])(int, int) = { add,sub;}这个数组就被叫做函数指针数组

回调函数

//如何理解回调函数 如果你把函数的指针作为参数传递给另一个函数时,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数;
void Print(char* str) {
	printf("hehe:%s", str);
}
void test1(void(*p)(char*)) {
	printf("test1");
	p("bit");

五、总结

    int brr[5]; //数组
	int(*p_brr)[5];//数组的指针
	int* (pbrr)[10];//数组(指针数组) 每个元素都是一个指针
	int(*pbrr2)[5];//指针指向了一整个数组 有5个元素
	int(*pbrr3[10])[5];//数组,里面的十个元素都是数组指针,指向pbrr2这样的数组
	int (*pc)(int, int);//函数指针
	int (*pcc[4])(int, int);//函数指针的数组;
	int (*(*p_pcc)[4])(int, int);//一个指针 指向 函数指针的数组

;