本文通过简单的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 = ⊂
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);//一个指针 指向 函数指针的数组