前言
相信大家经过指针初级的学习对指针逐渐有着一定的了解,而小编接下来给大家带来的就是指针的进阶教学,希望大家好好学习。
1.字符指针
这里我给大家特殊介绍一下字符指针,对于字符指针,我有一点需要说明一下,下面看代码
#include <stdio.h> int main() { const char* pstr = "hello bit."; printf("%s\n", pstr); return 0; }
对于以上字符指针的使用,大家会不会有以下困惑:
对于pstr这个字符指针,存储的是整个常量字符串“hello bit”,还是该常量字符串 hello bit. 首字符的地址呢?
而对于该本质,实际上是将该常量字符串的首地址存储到字符指针中
那就可能出现以下面试题
#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; }
对于以上面试题,这里还有一点需要说明一下
C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际指向的是的同一块内存,但是用相同的常量字符串去初始化不同的数组的时候得到是不同的内存块
所以上面一道题的结果是:
2.数组指针
2.1 数组指针的概念
之前在初级的时候给大家介绍了指针数组,那什么又是数组指针呢?接下来我们看概念
数组指针:存放数组地址的指针——指向数组的指针
对于数组的声明和指针数组有着很大的相似性,大家要加以区分,声明如下
类型名 (*指针名)[对应数组的大小]
对于指针数组和数组指针类型的声明的区别,这里我给大家举例说明
//[]的优先级要高于*号的
int *p1[10];
//所以这里是[]和p1先结合达到的是一个数组的类型,再和*结合构成指针数组
int (*p2)[10];
//解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个
指针,指向一个数组,叫数组指针。
2.2 &数组名VS数组名
我们都知道数组名表示的是数组首元素的地址,那对于&数组名呢?相信大概率来讲大家都没见过,大家这里先看一段代码
#include <stdio.h> int main() { int arr[10] = {0}; printf("%p\n", arr); printf("%p\n", &arr); return 0; }
这里得到的结果是:
看到这里大家是不是觉得&数组名和数组名表示的是同一个东西,那请大家继续看下一段代码
#include <stdio.h> int main() { int arr[10] = { 0 }; printf("arr = %p\n", arr); printf("&arr= %p\n", &arr); printf("arr+1 = %p\n", arr+1); printf("&arr+1= %p\n", &arr+1); return 0; }
结果如下:
这里我们可以看到arr和&arr是一致的,但两者+1得到的却是差别极大的数值,这里我们可以看到&arr+1跳过了40个字节,而arr+1跳过了4个字节,这里结论是&数组名表示的是整个数组的地址,这也就为下面的数组指针的使用做好的铺垫
2.3 数组指针的使用
数组指针存放的就是数组的地址,既然知道这个概念,那我们就直接上实例以便大家理解
这里给大家补充一个小知识,以便大家的理解:二维数组的数组名代表的是该第一行数组的整个数组地址的地址
void print_arr2(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("%d ", arr[i][j]);//这里大家也可以理解为*(*(arr+i)+j)这样就相当于arr[i][j] } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr2(arr, 3, 5); return 0; }
结果展示:
3. 数组参数,指针参数
3.1 一维数组传参
由于一维数组传参传的是数组首地址,那么对于该形参我们就可以写作 :类型名 数组名 [],类型名 数组名[数组大小],一级指针
实例演示:
#include <stdio.h> void print_arr1(int arr[],int n) { int i = 0; for (i = 0; i < n; i++) { printf("%d ", arr[i]); } } void print_arr2(int arr[10],int n) { int i = 0; for (i = 0; i < n; i++) { printf("%d ", arr[i]); } } void print_arr3(int* arr, int n) { int i = 0; for (i = 0; i < n; i++) { printf("%d ", arr[i]); } } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9 ,10}; int n = sizeof(arr) / sizeof(arr[0]); print_arr1(arr,n); printf("\n"); print_arr2(arr, n); printf("\n"); print_arr3(arr, n); printf("\n"); return 0; }
运行结果:
3.2 二维数组传参
由于二维数组传参,传的是第一行数组的整个数组地址的地址,所以对于二维数组,形参一般写为:类型名 数组名[行][列] (这里可以省略行不可以省略列),数组指针类型
实例演示:
#include <stdio.h> void print_arr1(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("%d ", arr[i][j]); } printf("\n"); } } void print_arr2(int arr[3][5], int row, int col) { int i = 0; for (i = 0; i < row; i++) { int j = 0; for (j = 0; j < col; j++) { printf("%d ", arr[i][j]); } printf("\n"); } } void print_arr3(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("%d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; print_arr1(arr, 3, 5); printf("\n"); print_arr2(arr, 3, 5); printf("\n"); print_arr3(arr, 3, 5); return 0; }
结果如下:
这里对于一级指针二维指针,传参时我们直接使用对应的指针类型就行。
4. 函数指针
函数指针顾名思义也是指针,但该指针存储的是函数的地址
对于该声明与定义如下
函数返回类型 (*p)(参数类型)=&函数名 (这里&函数名和函数名指的都是函数地址)
由于这里函数指针保存了函数的地址所以通过该函数指针,我们也能实现对函数的调用
调用方法:(*p)(参数),p(参数)
实例演示:
#include <stdio.h> void print_arr1(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("%d ", arr[i][j]); } printf("\n"); } } int main() { int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 }; void (*p) (int(*arr)[5], int row, int col) = print_arr1; p(arr, 3, 5); return 0; }
结果如下:
在这里我再给大家介绍两个又意思的代码
(*(void (*)())0)();
讲解:(void (*)())进行强制类型转换为函数指针类型,把0当作一个函数地址,(*(void (*)())0)进行把0地址处进行解引用,(*(void (*)())0)(),进行一次函数调用,调用0地址处的一个函数
void(* signal(int,void(*)(int)))(int);
该代码是一次函数声明,函数名字叫做signal,sianal函数的参数有两个,第一个是int类型,第二个是函数指针类型,该指针能够指向那个函数的参数是int,返回值是void,signal函数返回值类型是一个函数指针,该函数指针能够指向的那个函数的参数是int,返回值是int
5.函数指针数组
之前我们学习了指针数组,根据指针数组我们可以联想到函数指针数组的声明和应用,这里我们可以把函数指针数组理解为: 存放函数指针的数组,存放的是函数的地址
声明:函数返回类型 (*数组名[数组大小])(形参);
对于函数指针数组的应用,这里我们用一个模拟计算机的代码带大家感受一下
#include <stdio.h> int add(int a, int b) { return a + b; } int sub(int a, int b) { return a - b; } int mul(int a, int b) { return a * b; } int div(int a, int b) { return a / b; } int main() { int x, y; int input = 1; int ret = 0; int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表 while (input) { printf("*************************\n"); printf(" 1:add 2:sub \n"); printf(" 3:mul 4:div \n"); printf("*************************\n"); printf("请选择:"); scanf("%d", &input); if ((input <= 4 && input >= 1)) { printf("输入操作数:"); scanf("%d %d", &x, &y); ret = (*p[input])(x, y); } else printf("输入有误\n"); printf("ret = %d\n", ret); } return 0; }
运行结果如下:
6.指向函数指针数组的指针
这里大家通过数组指针理解一下概念就行,我就不给大家演示
这里给大家举个例子让大家理解一下,比如首先给出一个函数指针数组int (*pa[10])(char*)
则该指向函数指针数组的指针可以写作:int (*(*paa)[10])(char*)=&pa,这里我们也通过解引用对函数进行调用,但目前大家接触不怎么到,这里就一笔带过了。
7.回调函数
回调函数这里有一个比较重要的函数我需要给大家介绍模拟一下,在指针中我就给大家介绍一下回调函数的概念,后面我会出一篇文件独立介绍一下这个函数。
这里回调函数我们可以理解为就是通过函数指针调用的函数,如果把函数指针作为参数传递给另外一个函数,当整个指针被用来调用其所指向的函数时,我们就说这是回调函数,
此外,回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。