在开始总结指针的进阶之前,先说几个小知识点。
- 无论是几维数组,在传参时都会发生退化问题,退化成指针。
- 所有的数组都是一维的,可以把多维数组想象成一维数组。
- 任何一个函数,只要函数有参数,就要形参实例化,形成临时变量。
- 在函数内定义的变量为局部变量,局部变量都具有 临时性。
- 每调用函数都会形成栈帧,返回函数释放栈帧,所以递归如果写的不好会导致栈溢出。
下面我们开始这次总结的内容。
字符指针
在指针的类型中有一种指针类型为字符指针char*
;
一般是这样使用的:
int main(void)
{
char ch = 'w';
char* pc = &ch;
*pc = 'w';
return 0;
}
这两个变量在main的栈帧上开辟空间,ch的地址高,pc的地址低。
还有另一种使用方式:
int main(void)
{
char* pstr = "hello Gerald!";
printf("%s\n", *pstr);
return 0;
}
这里并不是将字符串放到了指针里,而是在字符串常量区创建了这个字符串,将这个字符串的首地址给pstr指针,而且,这个字符串是在只读区,所以不能修改此字符串。
面试题练习:
#include <stdio.h>
int main(void)
{
char str1[] = "hello Gerald!";
char str2[] = "hello Gerald!";
char* str3 = "hello Gerald!";
char* str4 = "hello Gerald!";
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是指针类型,这个字符串是在字符常量去创建好,并且将它的首地址给str3和str4,所以他们两个的首地址是相同的。
指针数组
指针数组是一个数组,数组里面的元素是指针。
例如:
int* arr1[10];
char* arr2[4];
char** arr3[5];
数组指针
数组指针是一个指针,这个指针指向数组的首元素地址。
我们先看一下数组指针和指针数组的区别:
int* p[10];//指针数组
int (*p)[10];//数组指针
解释一下:数组指针中()的优先级最高,所以p先和*结合,说明p是一个指针,在于[]结合,说明这个指针指向的是一个大小为10的整型数组。因为[]的优先级大于*所以要加圆括号。
&数组名 VS 数组名
我们先看一段代码:
#include <stdio.h>
int main(void)
{
int arr[10] = {0};
printf("%p\n", &arr);
printf("%p\n", arr);
return 0;
}
运行结果:
运行结果一样,那么他们两个的含义一样吗?
在看一段代码:
#include <stdio.h>
int main(void)
{
int arr[10] = {0};
printf("arr + 1 -> %p\n", arr);
printf("&arr + 1 -> %p\n", &arr);
printf("arr+1 -> %p\n", arr+1);
printf("&arr+1 -> %p\n", &arr+1);
return 0;
}
运行结果:
对指针+1是加上其所指向类型的大小。
对2级及其以上指针+1都是+4个字节,因为他们指向的都是指针。
所以根据上面的结果,我们可以确定数组名和**&数组名**是不一样的。
数组名代表的是首元素的地址,而&数组名代表的是整个数组的地址。
一般情况下数组名为首元素的地址,但是有两种情况代表整个数组:
- sizeof(数组名)
- &数组名
数组指针的使用
数组本身元素的类型加元素的个数共同决定数组类型。
数组传参时会降维成指向其内部元素类型的指针,前提是我们认为所有的数组都是一维的。
数组传参时只能省略一维数组的下标(第一个方括号)的值。
看代码:
#include <stdio.h>
void print1(int arr[][5], int row, int col)
{
int i_ = 0;
for (i_ = 0; i_ < row; i_++)
{
int j_ = 0;
for (j_ = 0; j_ <col; j_++)
{
printf("%3d ", arr[i_][j_]);
}
printf("\n");
}
printf("\n");
}
void print2(int(*arr)[5], int row, int col)
{
int i_ = 0;
for (i_ = 0; i_ < row; i_++)
{
int j_ = 0;
for (j_ = 0; j_ <col; j_++)
{
printf("%3d ", arr[i_][j_]);
}
printf("\n");
}
printf("\n");
}
int main(void)
{
int arr[3][5] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};
print1(arr, 3, 5);
/*数组名arr,表示首元素的地址
而二维数组的首元素是第一行的地址
所以这里传递arr,可以用数组指针来接收*/
print2(arr, 3, 5);
return 0;
}
运行结果:
所以这两种接收参数的方式都是对的,数组指针就是在接收参数时,接收一个数组。
int arr[5]; //数组
int* parr1[10];//指针数组
int (*parr2)[10];//数组指针
int (*parr3[10])[5];//指针数组的指针
//最后一个优点绕,意思就是一个数组有是个元素,每个元素都是一个指针,每个指针指向一个大小为5的数组
当一个函数的参数部分为一级指针的时候,函数可以接收三种参数:
- 数组
- 一级指针
- &整型
当一个函数的参数部分为二级指针的时候,可以接收的参数类型:
- 二级指针
- 数组指针
- &一级指针
函数指针
先看代码:
#include <stdio.h>
void test()
{
printf("LOLLLLLLLLLLLL!");
}
int main(void)
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
运行结果:
根据结果,我们可以看到函数名和**&函数名**在输出地址时都是代表函数的地址。
那么如果我们想把这个函数的地址保存起来,怎么保存呢?
void test(void)
{
;
}
void (*pfun1)(void);
pfun1可以存放函数的地址,pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参数,返回值类型为void。
函数指针数组
函数指针数组,顾名思义,它是一个数组,这个数组里的元素是函数指针类型。
那应该如何定义呢
int (*pfun[10])();
pfun先和[]结合表示为一个数组,然后在和*结合,意为指针数组,最后与()结合,表示为一个数组,数组的元素是指向函数的指针。
那么该怎么用呢?
看代码:
#include <stdio.h>
#include <Windows.h>
#include <stdlib.h>
void menu()
{
printf("*****************************\n");
printf("*** 1. add 2. sub ****\n");
printf("*** 3. mul 4. div ****\n");
printf("*** 0. exit ****\n");
printf("*****************************\n");
printf("Please Enter:");
}
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 div_(int x, int y)
{
if (y == 0)
exit(-1);
return x / y;
}
int main(void)
{
int x;
int y;
int input = -1;
int ret = 0;
int (*pfun[5])(int x, int y) = { 0, add, sub, mul, div_ };
while (input)
{
menu();
scanf("%d", &input);
switch (input)
{
case 0:
printf("ByeBye!\n");
break;
case 1:
printf("Please Enter Numbers:");
scanf("%d%d", &x, &y);
ret = (*pfun[1])(x, y);
break;
case 2:
printf("Please Enter Numbers:");
scanf("%d%d", &x, &y);
ret = (*pfun[2])(x, y);
break;
case 3:
printf("Please Enter Numbers:");
scanf("%d%d", &x, &y);
ret = (*pfun[3])(x, y);
break;
case 4:
printf("Please Enter Numbers:");
scanf("%d%d", &x, &y);
ret = (*pfun[4])(x, y);
break;
default:
printf("ERROR!\n");
break;
}
if (input == 0)
break;
printf("Result -> %d\n", ret);
}
system("pause");
return 0;
}
这样就可以将函数的地址放进一个数组里,需要调用哪个只需要访问下标就好。
指向函数指针数组的指针
指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针。
那么该如何定义呢?
void test(const char* str)
{
printf("%s\n", str);
}
int main(void)
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
回调函数
可以看上一篇博客《qsort函数排序字符串数组》。