前言:本篇文章回忆了指针的基本知识,在基本知识的基础上,加深了对指针的理解,然后引出指针数组、数组指针、函数指针灯一系列概念。如果那里写的有问题,欢迎指出。
指针是什么
指针是一个变量,用来存放地址的。
#include<stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//&a取出变量a的地址,存到指针变量p里
*p = 20;//对指针变量p解引用,找到a的那块空间,改变a的值
return 0;
}
指针的大小
- 32位机器,地址是32个0或1组成的二进制序列,也就是32个比特位,而一个字节恰好是8个比特位,所以在32位机器上指针大小是4个字节
- 64位机器,地址是64个0或1组成的二进制序列,也就是64个比特位,而一个字节恰好是8个比特位,所以在64位机器上指针大小是8个字节
指针的类型
char *p=NULL;
int *p=NULL;
short *p=NULL;
float *p=NULL;
...
去掉变量名,剩下的就是它的类型,所以指针的类型为
char * ,int *
,等等。char *
类型的指针是为了存放char
类型变量的地址,其他的可以以此类推。
指针的运算
- 指针+-整数
- 指针的解引用
- 指针-指针
指针+-整数
#include<stdio.h>
int main()
{
int n = 10;
char *pc = (char *)&n;
int *pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc+1);//加1地址偏移1个字节
printf("%p\n", pi);
printf("%p\n", pi+1);//加1地址偏移4个字节
return 0;
}
指针的解引用
对指针进行解引用操作能找到指针所指向的那块空间,指针的类型决定了解引用时能操作多大的空间,例如:对
char *
的指针解引用只能访问一个字节,而对int *
指针解引用能访问4个字节。
指针-指针
指针-指针的结果是两个指针之间元素的个数,是一个整数。
例如:模拟实现库函数strlen就可以用指针-指针的方法
int my_strlen(char *s)
{
char *p = s;//保留字符串的起始地址
while (*p != '\0')
{
p++;
}//循环结束,指针p指向的是'\0'
return p - s;
//'\0'的地址和首元素地址中间的长度刚好是字符串的长度
}
注意:给定一个arr[10]={0};p1=&arr[0],p2=&arr[9]
,p2-p1
是9而不是10.
二级指针
二级指针是用来存放指针的地址的
对上边的例子,对ppa
解引用得到pa
,对pa
在进行解引用得到a的那块空间。
指针表达式解析
假设有下面代码
char ch='a';
char *cp=&ch;
判断下列表达式能不能做左值,能不能做右值。
回顾指针和数组的相关运算
前面写了关于数组和指针运算的一篇博客,在下面的指针和数组概念的了解很重要,所以贴出链接——>数组和指针的基本运算
数组代表整个数组有两种情况
sizeof(数组名)
,这里的数组名代表整个数组(一定是数组名单独放在sizof内部)
&sizeof
,这里的数组名代表整个数组
指针数组
指针数组是一个数组,用来存放指针的数组
例如:int *arr[10];
是一个指针数组。
注意:[]的优先级高于*,arr先和[]结合,所以它是一个数组,它的类型是int *[10]
,所以它是一个存放指针的数组,既指针数组
数组指针
数组指针是一个指针,用来指向数组的指针,也就是存放数组的地址
例如:int (*arr)[10];
是一个数组指针
注意:[]的优先级高于,但是*arr上了括号,所以arr先和结合,arr首先是一个指针,它的类型是int (*)[10]
,所以它是一个指向大小为10个整型数组的指针,用来存放数组的地址
我们知道数组名代表首元素的地址,&数组名代表的是数组的地址,那么数组的地址如何存储?数组指针就是用来存放数组的地址。
例如:
int arr[10]={0};
int (*p)[10]=&arr;
了解了【数组指针】和【指针数组】,我们看一下下边的代码代表什么
int arr[5];//一维数组
int arr[3][5];//二维数组
int *arr[10];//指针数组
int(*arr)[10];//数组指针
int(*arr[10])[5];
//它首先是一个存放10个元素的指针数组,每一个指针又指向了一个存放5个元素的数组
数组和指针传参
一维数组传参
#include<stdio.h>
void test1(int arr[])//用一维数组接收
void test1(int arr[10])//用一维数组接收
void test1(int *arr)//用指针接收数组首地址
void test2(int *arr[20])//用指针数组接收
void test2(int **arr)//用一个二级指针接收
int main()
{
int arr1[10] = { 0 };//一维数组
int *arr2[10] = { 0 };//指针数组
test1(arr1);
//传参数组首元素的地址
test2(arr2);
//arr2是一个指针数组,存放的是指针,传参arr2代表数组首元素的地址,是指针的地址,既二级指针
return 0;
}
二维数组传参
#include<stdio.h>
void test(int arr[3][5])//用二维数组接收数组
void test(int arr[][])//用二维数组接收数组,但是列不能省略,要知道一行有几个元素,这种方法错误
void test(int arr[][5])//用二维数组接收数组,行可以省
void test(int *arr)//整型指针接收二维数组不合适
void test(int *arr[5])//这是一个指针数组,用来存放一级指针的,存放数组地址不合适
void test(int (*arr)[5])//数组指针接收第一行数组的地址,合适
void test(int **arr)//二级指针用来存放一级指针的地址,存放数组地址不合适
int main()
{
int arr[3][5] = { 0 };
test(arr);//二维数组名代表第一行的地址
}
一级指针传参
一级指针传参,用一级指针接收。但当一个函数参数部分为一级指针,函数能接受什么参数
例如:
void test(int *p)
//test函数可以接收指向int类型的一级指针、int类型的数组、int类型变量的地址
二级指针传参
二级指针传参,用二级指针接收。但当一个函数参数部分为一级指针,函数能接受什么参数
例如:
void test(char **p)
//test函数可以接受二级指针、一级指针的地址和指针数组
//指针数组是存放指针的数组,传参数组名是传的第一个指针的地址
函数指针
函数指针,顾名思义,就是一个指向函数的指针
例如:
void (fun)()就是一个函数指针,fun先和结合,说明fun是一个指针,然后它指向的类型为void(*)(),指向的是一个函数,这个函数无返回值,无参数。
#include<stdio.h>
void test()
{
printf("hello,world\n");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
这段代码的输出结果都是00A7107D
,可以说明函数名和&函数名都代表的是函数的地址,那么函数的地址存在哪里呢?其实就是函数指针了。
注意:理解下边的两个代码
- 代码一:
(*(void(*)())0)()
首先代码(void(*)())0
是将0强制装换为函数指针类型,然后对它解引用得到0地址处的函数。这个函数无参数和返回类型,这是一个函数的调用。
- 代码二:
void (*signal(int,void(*)(int)))(int)
首先这是一个是一个函数的声明,signal函数的有两个参数一个是int类型的,一个是函数指针类型的,这个函数指针有一个int类型的参数,返回值是void。然后signal函数的返回值为void(*)(int)
这也是一个函数指针,有一个int类型的参数,返回值为void。
对上边的代码二我们可以简化一下
typedef void(*pfun_t)(int)
pfun_t signal(int ,pfun_t)
//将signal函数的返回类型void(*)(int)重命名为pfun_t,而signal函数的其中一个参数和signal函数的返回参数是一样的,所以可以写成上述表达式
函数指针数组
函数指针数组,首先它是一个数组,用来存放函数指针的数组,函数指针又是存放函数的地址,那么这个数组就是存放函数地址的数组
例如:
int (*parr[10])();
//parr先和[]结合,它首先是一个数组,然后它存储元素的类型是一个函数指针`int (*) ()`,这个数组存储了10个函数指针。
指向函数指针数组的指针
指向函数指针数组的指针首先是一个指针,它指向的是一个存放函数地址的数组,用来存放函数指针数组的地址。
例如:
#include<stdio.h>
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
void(*ptest)(const char*) = test;
//定义一个函数指针ptest,它指向函数参数为const char*类型的,返回值为void,用来存放test函数的地址
void(*ptestarr[5])(const char*);
//定义一个函数指针数组,这个数组存放5个函数指针
ptestarr[0] = test;
//ptestarr[0]存放的是test函数的地址
void(*(*pptestarr)[10])(const char*) = &ptestarr;
//定义了一个指向函数指针数组的指针,它指向函数指针数组,存放的是函数指针数组的地址
return 0;
}