学完指针初阶之后,现在进行指针进阶
- 1.字符指针
- 2.数组指针
- 3.指针数组
- 4.数组传参和指针传参
- 5.函数指针
- 6.函数指针数组
- 7.指向函数指针数组的指针
- 8.回调函数
- 9.指针和数组面试题的解析
在学之前我回忆一下指针的概念
指针:
指针是一个变量,存放变量地址,地址唯一标识一块空间(内存单元的编号就是地址,将地址存放到指针后指针就成了一个指针变量)
指针的大小:是固定的4/8个字节(32位平台/64位平台)
指针的类型:1.指针+-整数的步长
2.指针解引用操作的时候的权限
具体的操作由俺的(初阶指针)这篇博客已经阐述。
了解完指针的概念之后,开始进行进阶指针
1.字符指针 char *
const char*pa="abcdeef"
上述的字符串是常量字符串,需要用const,如果*pa想改变字符串,是不可以的,因为这个字符串是常量字符串,所以用const来表明一下这个不可以修改的
如果不加const并且对pa进行修改,编译器不会报错,最终运行不出结果,极大程度上不容易debug
如果加上const并且对pa进行修改,编译器会直接报错,这样容易debug
-----不可以通过指针,改变常量字符串的任意字符。
总结:
1.一个指针是可以指向一个字符串(字符串可以理解为数组都是连续存放),存放的是首字母的地址(字符指针变量)
2.指针指向的字符串是常量字符串,在前面加上const更能说明,放在左边会表示这个是不可以更改的。
一道笔试题可以展示出加const和不加const的区别
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";//常量字符串
const char *str4 = "hello bit.";
if(str1 ==str2)//比较的是首元素的地址
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");//占用俩个内存块
if(str3 ==str4)
printf("str3 and str4 are same\n");常量表达式,占用同一个内存块
else
printf("str3 and str4 are not same\n");
return 0;
}
char *pa="hello bit";
*pa='w';//error
常量字符串是无法改的
str3和str4指向的是同一个常量字符串,c/c++会把常量的字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,它们实际上会指向同一个内存区域
const已经将字符串弄成不可修改的了,如果再进行const定义同一个字符串的话,都还是指向的是同一个内存区域。所以俩者是相同的。
ps:字符串打印
字符串打印直接打印即可,由于字符串有个结束标志是'\0',字符指针变量指向了这个字符串首地址即可,就可以将整个字符串全部打印出来,直到遇到'\0'结束,
但是字符串打印也是可以遍历打印,但是直接打印显得简单。
- 字符指针里面存放的是常量字符串首字符的地址,可以通过地址找到字符串每个字符。
- 整型数组需要从头遍历的打印,由于整型数组是没有结束标志的
2.指针数组
整型数组——存放整型的数组
字符数组-------存放字符的数组
指针数组-------存放指针(地址)的数组
强调的是数组
定义:存放地址(指针)的数组(多用于一维数组)这个数组的所有元素都是指针 ,int *a[10];
每个元素都是一个地址,不能是普通数据
<1>存放字符指针的数组
arr[0] 存放的是”abcdef“首字符的地址
arr[1] 存放的是”qwer“首字符的地址
arr[2] 存放的是”hello bit"首字符的地址
arr[3] 存放的是"hehe"首字符的地址
这个数组的每个字符的类型是char*类型
<2>存放整型指针的数组
int a=10,b=20,c=30;
int *p[3]={&a,&b,&c};
结果:p[0]=&a;p[1]=&b;p[2]=&c;
等价:*(p+0)=&a;*(p+1)=&b;*(p+2)=&c;
取值:*p[0]=10; *p[1]=20; *p[2]=30;
等价于
**(p+0)=10; **(p+1)=20; **(p+2)=30
int main()
{
//存放整型指针的数组
int arr1[5] = { 1,2,3,4,5 };
int arr2[5] = { 2,3,4,5,6 };
int arr3[5] = { 3,4,5,6,7 };
int arr4[5] = { 0,0,0,0,0 };
指针数组--存放指针(地址)的数组
int* arr[4] = { arr1,arr2,arr3,arr4 };
int i = 0;
for (int i = 0; i <= 3; i++)
{
int j = 0;
for (int j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));//arr[i][j]
//arr[i]是每一行元素的起始地址,+j之后是那一行往后元素的地址
//然后对地址的解引用就是那一行的元素
}
printf("\n");
}
return 0;
}
以上的代码,存放整型指针的数组,存放四个指针数组,每个元素都是地址,arr1是第一个指针数组的首元素的地址。。。。,依次遍历。
这样我们对指针数组有了一个认识,那就是指针数组存放的都是地址,每个元素都是指针,将这些指针都存放再一个数组里,就拟化成一个二维数组的样子。
3.数组指针
能够指向数组的指针
字符指针--------存放字符地址的指针----指向字符的指针 char*
整型指针--------存放整型地址的指针----指向整型的指针 int *
数组指针--------存放数组地址的指针----指向数组的指针
数组指针类型是 ( int(*)[10] )
int main()
{
int arr[10]={1,2,4,5};指向数组的指针
int (*pa)[10]=&arr;
解释:pa先和*结合---表示pa是指针,然后指向一个大小为10的数组。
所以 pa是指针,指向一个数组,称之为数组指针------其中存放的是数组的地址
return 0;
}
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
pa先与*结合来表示pa是指针,然后指向一个大小为10的整型数组
pa是指针指向了一个数组,称为数组指针,就是存放的是数组的地址
这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。
void print1(int arr[3][5], int r, int c)
{
int i = 0;
int j = 0;
for (int i = 0; i < r; i++)
{
for (int j = 0; j < c; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
}
//数组指针--指向二维数组的指针
void print2(int(*p)[5], int r, int c)
{
int i = 0;
int j = 0;
for (int i=0; i < r; i++)
{
for (int j=0; j < c; j++)
{
printf("%d ", *(*(p + i) + j));//*(p+i)是指某一行的第一个元素地址
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,5},{3,4,5,6,7} };
print1(arr, 3, 5);
print2(arr, 3, 5);//arr数组名,表示数组首元素的地址
//二维数组的数组名表示 首行的地址
//二维数组的首元素是:第一行的元素
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
return 0;
}
二维数组的arr(数组名)指的是数组的第一行的地址, arr[0][0]才是第一行第一个元素的地址
一维数组的arr(数组名)指的是数组的第一个元素的地址
指向数组的指针(多用于二维数组)强调的是指针
这个指针存放着一个数组的首地址
int (*p)[10]
4.&数组名VS数组名
数组名--数组首元素的地址
&数组名---数组的地址
数组首元素的地址和数组的地址从值得角度看是一样的,但是意义不一样
数组名是数组首元素的地址(俩个例外)
1.sizeof(数组名)-----数组名表示整个数组,计算的是整个数组的大小,单位是字节
2.&数组名---------------数组名表示整个数组,取出的是整个数组的地址
实际上: &arr 表示的是数组的地址,而不是数组首元素的地址。
整型指针arr+1,arr+1相对于arr的差值是4;
数组的地址+1,跳过整个数组的大小(p2存的是数组的地址)
所以 &arr+1 相对于 &arr 的差值是40
(p2+1)与(p2)的差是40
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);//int *
printf("%p\n", arr+1);//4
printf("%p\n", &arr[0]);//int*
printf("%p\n", &arr[0]+1);//4
printf("%p\n", &arr);//int (*)[10]指向数组的指针,加1会跳过整个数组
printf("%p\n", &arr+1);//40
return 0;
}
5. 复习
int arr[5];//整型数组
int *parr1[10];//整型指针的数组
int (*parr2)[10];//数组指针,该指针能够指向一个数组,数组10个元素,每个元素类型是int
int (*parr3[10])[5];//存放数组指针的数组,存放10个数组指针,每个数组指针都指向一个数组,该数组有5元素,每个元素是int类型
//解释:int (* )[5];数组指针
//int (*parr3[10])[5];存放10个数组指针
5. 数组参数、指针参数
<1>一维数组传参
void test(int arr[]);
void test(int arr[10]);
void test(int *arr);
int main()
{
int arr[10]={0};
test(arr);
}
void test2(int *arr2[20]);
void test2(int *arr2[]);//指针数组
void test2(int **arr2);存放int *数组
//不能写成int *arr2//error
int main()
{
int *arr2[20]={0};//指针数组
test2(arr2);
}
<2>二维数组传参
void test(int arr[][]);//error
总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素
void test(int arr[][5]);
void test(int arr[3][5]);
void test(int (*arr)[5]);//数组指针// 每一行有5个元素
int main()
{
int arr[3][5] = {0};
test(arr);
}
二维数组传参用二维数组接收,一维数组传参用一维数组传参.
数组指针适用于二维数组,所以在二维数组传参的时候可以用数组指针传参,int(*arr)[5],也关联了在传参的时候必须给出列。
指针数组适用于一维数组,所以在一维数组传参的时候可以用指针数组传参
<3>一级指针传参
id print(int *ptr,int sz)//用一级指针接收
{
int i=0;
for(int i=0;i<sz;i++)
{
printf("%d ",*(ptr+i));
}
}
int main()
{
int arr[10]={0,1,2,3,4,5,6,7,8,9};
int *p=arr;//数组名是数组首元素的地址,将首元素的地址给p,p存了arr数组首元素的地址
//p是一级指针
int sz=sizeof(arr)/sizeof(arr[0])
print(p,sz);//指针变量传过去,只用将指针变量来接收即可
//如果传&p,则需要传二级指针 int **p
return 0;
}
int *p=arr,数组名是数组首元素的地址,将首元素的地址给p,p存了arr数组首元素的地址
p是一级指针,指针变量传过去,只用将指针变量来接收即可
如果传&p,则需要传二级指针int **p.
一维数组名,一级指针,变量的地址
<4>二级指针传参
void test(int** p2)
{
**p2 = 20;
}
int main()
{
int a = 10;
int* pa = &a;//pa是一级指针
int** ppa = &pa;//ppa是二级指针
//把二级指针进行传参
test(ppa);
test(&pa);
printf("%d\n", a);
return 0;
}
传过去的是一级指针,就用一级指针接收
传过去的是二级指针,就用二级指针接收
二级指针,一级指针的地址,指针数组的数组名 (因为指针数组是存放指针的数组)
6.函数指针
函数指针--存放函数地址的指针
&函数名--取到的就是函数的地址
printf("%p\n", &ADD); printf("%p\n", ADD);
&数组名!=数组名
&函数名==函数名
int main()
{
int (*pf)(int ,int) = &ADD;//pf是函数指针变量 ADD===pf
//int(*p)[10] = &arr;//数组指针
int ret=(*pf)(3, 5);
int ret = pf(3, 5);
int ret = ADD(3, 5);
printf("%d\n", ret);
}
有趣的代码
(* (void (*)()) 0)();
//调用0地址处的函数
//该函数无参,返回类型是void
1.void(*)()-----函数指针类型
2.(void (*)()) 0----对0进行强制类型转换,转换成函数指针类型,0当作函数的地址,
参数是无参,返回类型的是void
3.* (void (*)()) 0 ----对0地址进行解引用操作
4.(* (void (*)()) 0)()----调用0地址处的函数
调用的时候要传参
该代码是一次函数调用,调用0地址处的一个函数
首先代码中将0强制类型转换为类型为void(*)()的函数指针
然后去调用0地址的函数
《c陷阱和缺陷》
void(*signal(int,void(*)(int)))(int)
对于指针重定义需要将重定义的名称放在*旁边
比如 上述例子
但是对于其他只用将要重定义的放在要改的后面即可typedef unsigned int uint;
void (*signal ( int , void(*)(int) ) )(int);
简化代码
void(*) (int) signal(int ,void(*)(int));//语法不支持这样写,但这样很易懂
typedef--对类型进行重定义
简化代码如下:
对指针重定义一般放在*的旁边
typedef void(* pfun_t)(int);//对void(*) (int) 函数指针类型重命名为pfun_t
pfun_t signal(int,pfun_t);
typedef unsigned int uint;//对unsigned int命名为unint
1.signal和()先结合,说明signal是个函数名
2.signal函数的第一个参数类型是int,第二个参数类型是函数指针
//该函数指针,指向一个参数为int,返回类型是void函数
3.signal函数的返回类型也是一个函数指针
//该函数指针,指向一个参数是int,返回类型是void函数
//signal是一个函数的声明
7. 函数指针数组
里面存放的是函数指针(地址)
整型指针 int *pa;
整型数组指针 int (*pa)[]=&arr;
整型指针数组 int *arr[5]; 数组的每个元素都是int*;
整型函数指针数组 int (*parr1[10])();
整型数组
int arr[5];
int (*p1)[5]=&arr;
整型指针数组
int* arr[5];
int* (*p2)[5]=&arr; p2是指向【整型指针数组】的指针
整型函数指针数组
int (*p)(int int);函数指针
int (*p2[4])(int int);//函数指针数组
int (*(*p3)[4]))(int int)=&p2函数指针数组的地址
p3就是指向【函数指针的数组】的指针
计算器
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 Dev(int x, int y)
{
return x / y;
}
menu()
{
printf("****************************\n");
printf("******1.Add 2.Sub******\n");
printf("******3.Mul 4,Dev******\n");
printf("****** 5.Exit ********\n");
printf("****************************\n");
}
//利用函数指针
int main()
{
int input = 0;
do
{
int x = 0;
int y = 0;
menu();
int (*p[5])(int, int) = { NULL,Add,Sub,Mul,Dev };
printf("请选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入俩个操作数:>");
scanf("%d %d", &x, &y);
int ret = (p[input])(x, y);
printf("%d\n", ret);
}
else if (input == 0)
{
printf("退出程序\n");
}
else
{
printf("选择错误\n");
}
} while (input);
return 0;
}
8.指向函数指针数组的指针
指向函数指针数组的指针是一个指针
指针指向一个数组,数组的元素都是函数指针
p是函数指针数组
int (*p[5])(int ,int)
pp是指向函数指针数组的指针
int (*(*pp)[5])(int ,int)=&p
pp=&p
*pp=p
类型是 int(*(*)[5])(int,int)
目录