目录
指针基本概念
1.字符指针
2.指针数组
3.数组指针
3.1数组指针概念
3.2对数组名的理解
3.3数组指针的使用
4.数组参数,指针参数
4.1一维数组传参
4.2二维数组传参
5.函数指针
5.1函数指针的概念
5.2对函数名的李姐
5.3函数指针的使用
5.4小优化
6.函数指针数组
小结
在初阶指针中我们了解到一些指针的基本概念:
1.指针就是个变量,用来存放地址,地址唯一标识一块内存
2.指针的大小是固定的4/8个字节(32位/64位平台)
3指针是有类型的,指针的类型决定了指针的+整数的步长,指针解引用时操作的权限
例如,char*类型的指针解引用,它的权限(或步长)在32位平台下只有一个字节,而int*型的指针在32位平台下的权限(步长)为4个字节。
#include
int main()
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';//这里的pc就是一个字符指针
return 0;
}
这里的pc就是一个字符指针,可以通过解引用pc来对w进行操作。
#include
int main()
{
char arr[]="abcdef";//数组内容为字符串
//char *p="abcdef";//指针存储的内容为字符串"abcdef"的首个字母的地址
const char *p="abcdef";//abcdef为常量字符串,需要加const来修饰
return 0;
}
这两个定义有什么区别, char arr[]='' abcdef''只是一个对数组普通的初始化,将字符串''abcdef''放数组arr中
char *p="abcdef"中,abcdef是常量字符串,其前面需要用const来修饰,那么第二个操作是不是直接将整个字符串保存在字符指针中了呢?答案是否定的。
该操作实际上是将常量字符串''abcdef''的首个字符的地址保存到字符指针*p中
而我们应该如何验证呢?
#include
int main()
{
char arr[] = "abcdef";
const char* p = "abcdef";
printf("%s\n",p);
printf("%c",*p);
return 0;
}
若此时p中存储的是字符串"abcdef"首字符的地址,那么第一个printf打印的结果应为"abcdef"
第二个printf中对字符指针p进行*解引用的结果应该是字符串首字符a。
于是我们得到了结论:该操作实际上是将常量字符串''abcdef''的首字符的地址保存到字符指针*p中
有这样一道面试题来练手,如下:
#include
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;
}
可以看到str1与str2是不相同的,而str3与str4是相同的,那么这究竟是什么原因呢?
char str1[] = "hello bit.";
char str2[] = "hello bit.";
两个数组的内容是相同的,但是开辟的并不是同一块空间 ,str1和str2都是数组名,代表了数组首元素的地址,而两个数分别开辟了不同的内存空间,所以首元素的地址不同,所以str1与str2不同而对于str3和str4,"hello bit."由const修饰,为常量字符串同时str3与str4都是字符指针,存储的是字符指针"hello bit."的首字符的地址,且其是由const修饰的常量字符串,故str3与str4指向的空间的地址相同,所以str3==str4;
类比:
整型数组-存放整型的数组;
字符数组-存放字符的数组;
指针数组-存放指针的数组;
#include
int main()
{
int* arr1[10];//整形指针的数组
char *arr2[4];//一级字符指针的数组
char **arr3[5];//二级字符指针的数组
return 0;
}
使用指针数组模拟实现二维数组
#include
int main()
{
int arr1[] = { 1,2,3,4,5 };
int arr2[] = { 2,3,4,5,6 };
int arr3[] = { 3,4,5,6,7 };
int* arr[3] = {arr1,arr2,arr3};
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d", arr[i][j]);
}
printf("\n");
}
return 0;
}
用i遍历左边的数组,用j遍历右边的数组 ,结果如下图:
需要特别注意的是,指针数组模拟实现的二维数组与真正的二维数组不同的是,该指针数组中各元素地址的存放不一定是连续的。
首先来做一个区分
整形指针-指向整型变量的指针,存放整型变量的地址的指针变量;
字符指针-指向字符变量的指针,存放字符变量的地址的指针变量;
数组指针-指向数组变量的指针,存放数组变量的地址的指针变量;
int*p1[10];//指针数组(p1先与[]结合)
int(*p2)[10];//数组指针(p2先与*运算符结合)
p2是指针指向的是数组,称为数组指针变量。
数组名是数组首元素的地址,但是有两个例外,如下
1.sizeof(数组名),这里的数组名不是数组首元素的地址,数组名此时表示整个数组,sizeof(数组名)计算是整个数组的大小,单位是字节。
2.&数组名,这里的数组名表示整个数组,&数组名取出的是整个数组的地址
除此之外,所有地方的数组名都是数组首元素的地址。
#include
int main()
{
int arr[10]={0};
printf("%p\n",arr);
printf("%p\n",&arr[0]);
printf("%p\n",&arr);
return 0;
}
前两个输出都表示的是数组首元素的地址,而第三个应该表示的是整个数组的地址,但是我们可以观察到三者的输出结果是相同的,
#include
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);
printf("%p\n", &arr+1);
return 0;
}
观察运行结果
前两个输出+1,都只跳过了4个字节,而第三个+1跳过了40个字节,正好是数组十个元素所占的字节数,由此可见 &数组名 代表的是整个数组的地址。
再观察下图,首元素的地址和数组的地址在占用内存的情况,更加证实了我们上面的说法。
int(*p)[10]=&arr; 定义了一个数组指针,p的类型是int (*)[10](但是在监视窗口中,显示的类型为int[10]*,这大概是编译器为了方便我们理解才这样写的吧),下面是数组指针的写法。
#include
int main()
{
int(*p)[10]=&arr;
return 0;
}
下面是一个使用数组指针打印二维数组的例子。
下面是打印二维数组的一种较为普通常见的写法:二维数组传参,形参是二维数组的形式
#include
void Print(int arr[3][5], int r, int c)
{
int i = 0;
for (i = 0;i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d", arr[i][j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
Print(arr,3,5);
return 0;
}
首先,对于二维数组,我们可以这样来理解:二维数组的每一行可以理解为二维数组的一个元素,每一行又是一个一维数组,所以我们可以这样理解二维数组其实是一维数组的数组。除此之外,二维数组的数组名,代表的是首元素地址,而首元素为第一个一维数组,即为第一行的地址,也是第一行一维数组的地址
在学习数组指针之后,我们可以尝试二维数组传参,形参是指针的形式。 如下:
#include
void Print(int (*p)[5], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)
{
int j = 0;
for (j = 0; j < c; j++)
{
printf("%d",*(*(p+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,2,3,4,5,6,3,4,5,6,7 };
//arr为二维数组的数组名,为首元素的地址,指针传参
Print(arr, 3, 5);
return 0;
}
对于这里的*(*(p+i)+j)进行分析,p为传参所传来的二维数组首元素的地址,也就是第一行一维数组的地址,在对其解引用*(p+i),相当于某一个一维数组的数组名,同时在用另外一个数j来对这个一维数组中的其他元素进行遍历获得他们的地址也就是*(p+i)+j,最后得到这个地址后在进行解引用即可获得二维数组中的所有元素*(*(p+i)+j)
一维数组传参,形参的部分可以是数组也可以是指针,同样二维数组传参的部分可以是数组也可以是指针,下面以二维数组为例:
#include
void test1(char arr[3][5], int r, int c)
{}
void test2(char(*p)[5], int r, int c)
{}
int main()
{
char arr[3][5] = { 0 };
test1(arr, 3, 5);
test2(arr, 3, 5);
return 0;
}
该二维数组为3行5列,指针传参的时候传过去的是第一行的地址,第一行有五个元素,故为
char(*p)[5]。 补充:(p[i]==*(p+i));
例子如下:
#include
void test(int arr[]) {}//ok? ok
void test(int arr[10]){}//ok? ok
void test(int *arr){}//ok? ok
void test2(int *arr[20]){}//ok? ok
void test2(int **arr){}//ok? ok
int main()
{
int arr[10] = { 0 };
int* arr2[20] = { 0 };
test(arr);
test2(arr2);
return 0;
}
重点讲解void test2(int **arr){}, arr2的类型是int *[20],而arr2是一个指针,将int *类型的数据传过去,是一个二级指针,用int** arr接受,合理。
#include
void test(int arr[3][5]) {}//ok? √
void test(int arr[][]) {}//ok? ×
void test(int arr[][5]) {}//ok? √
void test(int* arr) {}//ok? ×
void test(int* arr[5]) {}//ok? ×
void test(int (*arr)[5]) {}//ok? √
void test(int **arr) {}//ok? ×
int main()
{
int arr[3][5] = {0};
test(arr);
}
二维数组的列是不能够省略的,传参过去后,可以用数组指针来接受,而不是指针数组。
最后一个,传参过去的是数组名,是一级指针,不能够用二级指针来接收。
1.当一个函数的参数部分为一级指针时,函数能接受什么参数?
void test(char *p){}
char ch='2';
char ptr=&ch;
char arr[]="abcdef";
test(&ch);
test(ptr);
test(arr);//传过去的参数为一级指针即可。
2.当函数的参数为二级指针时,可以接受什么参数?
void test(char** p){}
int n=10;
int *p=&n;
int **p=&p;
char* arr[5];
test(&p);
test(pp);
test(arr)
即为指向函数的指针。
int (*)(int,int)就是函数指针的类型
#include
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int,int)=&Add;//pf为函数指针
return 0;
}
#include
int Add(int x, int y)
{
return x + y;
}
int main()
{
printf("%p\n", &Add);
printf("%p\n", Add);
return 0;
}
函数名和&函数名的运算结果都是函数的地址。
练习填写函数指针
给出test函数的定义,用pf表示test。
#include
void test(char* pc, int arr[10])
{
}
int main()
{
void (*pf)(char*, int[10]) = &test;
return 0;
}
函数名的用法
#include
int Add(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = Add;
int r = Add(3, 5);
printf("%d\n", r);
int m = (*pf)(4, 5);//int m=pf(4,5)也可以。
printf("%d\n", m);
}
int m = (*pf)(4, 5);也可以实现Add函数,那么是怎么实现的呢?
int (*pf)(int, int) = Add;首先将Add存在了pf中,这时*pf相当于函数名。
下面有两段interesting的代码
int main()
{
(*( void (*)() )0 )();
return 0;
}
这一代码其实就是调用0地址处的函数。在调用前将0转换成了void(*)()类型的指针。
这一段代码是这样实现的:
1.将0强制类型转化为void(*)()类型的函数指针
2.调用0地址处的这个函数
第二段interesting的代码:
int main()
{
void(* signal(int,void(*)(int)) )(int);
}
通过分析我们知道了signal是一个函数名,参数是int型和void(*)(int)函数指针类型。
void(* signal(int,void(*)(int)) )(int);
signal(int,void(*)int)) ,整个代码是一个函数指针,再看看去掉这一大块之后留下的内容
void(* )(int);就是上面内容的函数返回类型。
#include
typedef unsigned int uint;
typedef int* prt_t;
typedef int(*parr_t)[10];//错误的写法typedef int(*)[10]parr_t;
typedef int (*pf_t)(int, int);//此时pf_t就是这个函数指针的类型
int main()
{
uint u1;
prt_t p1;
int* p2;
return 0;
}
再对上面signal函数进行小小优化:
#include
typedef void(*pf_t)(int);
int main()
{
void(*signal(int, void(*)(int)))(int);
pf_t signal(int, pf_t);
return 0;
}
可以看到啊,非常的简洁啊。
函数指针存放在数组中。
创作不易,留下你的赞可好?