- 指针可以理解为一个变量,里面存放着地址,可以通过解引用来访问该地址上存储的数据。
- 指针是有类型的,指针的类型决定着一个指针在+1或者-1时跳过多少个字节;和指针解引用时能访问的内存的大小。
1、字符指针
字符指针中存储着一个地址,该地址的内存块上存储着一个字符;字符指针用char* 表示。 一般例如:
int main(){
char ch = 'x';
char *ptr = &ch;
printf("%c\n", *ptr);
return 0;
}
字符指针除了指向字符,还有另一个用法–指向字符串。通常 定义字符串char arr[] = "xupeng" ,其中arr存储的是字符串首个字符的地址然后寻找直至遇到'\0' 表示一个完整字符串,其关键在字符串首个字符的地址上,故也可以使用字符指针来指向字符串第一个字符的地址,如下:
int main() {
char *ptr = "xu peng";
printf("%s\n", ptr);
return 0;
}
通过vs监视窗口可以看到,char *ptr = "xu peng" 并不是把xu peng f放进变量ptr 里,因为ptr 是个指针类型只有4/8字节,压根放不下一整个字符串;可见是把首字符x 的地址存进了ptr 中。但char arr[] = "xu peng" 虽然数组名arr 代表的也是首字符x 的地址,但是在内存中的存储形式大不一样,如下代码可分析:
int main() {
char str1[] = "hello xp";
char str2[] = "hello xp";
const char *ptr1 = "hello xp";
const char* ptr2 = "hello xp";
if (str1 == str2) {
printf("str1 and str2 are same\n");
}
else {
printf("str1 and str2 are not same\n");
}
if (ptr1 == ptr2) {
printf("ptr1 and ptr2 are same\n");
}
else {
printf("ptr1 and ptr2 are not same\n");
}
return 0;
}
可见数组名str1与str2表示的不是同一片空间,而指针ptr1和ptr2指向同一片空间,其在内存中的状态如下: 数组是是实实在在地存下了一个字符串,用数组名表示首元素地址;而指针只是个变量,指向的同一片内存(存储的是同一片地址的首个字节的地址)
2、指针数组
指针数组的本质还是个数组,只不过数组里的每个元素是个指针,也就是每个元素是内存的地址。
有如下代码:
int main() {
int arr1[] = {1,2,3};
int arr2[] = {2,3,4};
int arr3[] = {3,4,5};
int *arr[3] = {arr1, arr2, arr3};
return 0;
}
数组名arr1 、arr2 、arr3 表示的是各自数组首元素的地址,因为数组元素是整型,故arr1 、arr2 、arr3 表示的是整形的地址,其也就叫做整型指针,也就是int * 类型。故创建数组arr ,里有三个元素,每个元素的类型是int * ,故arr 数组元素的类型正好跟arr1 、arr2 、arr3 的类型一致,故可以int *arr[3] = {arr1, arr2, arr3} 。
3、数组指针
数组指针本质是指针,也就是个存着一种地址变量,这种地址是数组的地址。(重点)跟数组首元素地址不同,数组首元素要么是int 类型要么是char ,所以其地址是整型指针或者字符指针,这跟数组的指针有本质区别,要区分开。
int main(){
int arr[4] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
可以看到arr 和&arr 的值是一样的,因为地址肯定都只是4字节,而计算机如何表示一个数组的呢?最方便的也就是指向首元素,故首元素arr 和数组地址&arr 的值是一样,但是两者意义完全不一样。打个比方,两个人A和B,A一次只能跨1个台阶,而B一次能跨4个台阶;虽然A和B的跨度(体量)不一样,但是他们起脚的地方肯定都是第一个台阶处,这就是arr 和&arr 一样的原因;但是跨一次后A和B所处的位置就完全不一样了。如下代码所示:
int main(){
int arr[4] = {0};
printf("%p\n", arr);
printf("%p\n", &arr);
printf("%p\n", arr+1);
printf("%p\n", &arr+1);
return 0;
}
可见A和B起脚处相等,但是跨一步后A只走了4字节(一个int型的大小),而B走了16字节(4个int型大小,arr数组中一共有4个int型元素,可知B跨过了一个数组)。
数组指针的表示方法
int (*ptr)[10] ;其中ptr先与*结合表示其是一个指针,[10]表示ptr指向一个有10个元素的数组,int表示每个数组的类型的int型。
数组名一般是首元素地址,但是有两个例外:
1、sizeof(数组名) ,数组名表示整个数组,计算的是整个数组的大小
2、&数组名 ,数组名表示整个数组,取出的是整个数组的地址
巩固
int arr[5];
int *ptr1[10];
int (*ptr2)[10];
int (*ptr3[10])[5];
4、数组传参和指针传参
一维数组传参
void test(int arr[10]){}
void test(int arr[]){}
void test(int *arr[]){}
int main() {
int arr[10] = {0};
test(arr);
return 0;
}
void test(int arr[10]) :在实参中传入数组,形参用相同类型数组接收,完全一致。void test(int arr[]) :实参传入数组,形参也用同类型数组接收,与上面不同的是少了参数10,形参中一维数组大小的参数可以不传,因为传了形参也不会立即开辟相应大小的空间。void test(int *arr[]) :实参传入数组,但换个角度理解其也是数组名,数组名表示数组首元素地址,数组首元素是int类型;形参中,*表示arr是个指针用来接收地址,前面的int 表示其接收的地址所存储元素的类型(指针指向的类型)是int类型,这也与数组首元素的类型一致,这样传参符合。
二维数组传参
void test(int arr[3][5]);
void test(int arr[][5]);
void test(int (*arr)[5]);
void test(int arr[][]);
void test(int *arr);
void test(int *arr[5]);
void test(int **arr);
int main(){
int arr[3][5] = {0};
test(arr);
return 0;
}
先解释正确的:
void test(int arr[3][5]) 实参传数组,形参用同类型的数组接收,完全一致,没问题。void test(int arr[][5]) 实参传数组,形参用同类型的数组接收,但是只能省去第一个[]中的数字,第二个[]中的数字不能省,也必须与传入数组的列数一致。void test(int (*arr)[5]) 实参另一个角度看传的是数组名,数组名表示数组首元素地址,二维数组的首元素是第一行的一维数组(是数组,是数组,是一整个数组的概念),故arr表示一维数组的地址(不是,不是,不是首元素地址,是数组概念级别的地址)。所以,形参要接收数组地址,*与arr结合表示arr是个指针(存储地址),[5] 与 ( *arr ) 一起表示指针指向的是一个数组,int 表示数组的元素是int类型;而实参传过来的地址也刚好是有5个int类型元素的数组的地址,故一致,可以这么表示。
再解释不正确的:
void test(int arr[][]) 规定,问就是规定!!!规定后一个表示有多少行的参数不能省略,对于一个二维数组,可以不知道有多少行,但是必须知道一行有多少个元素。void test(int *arr) 形参表示arr是一个指针,指向int类型的元素‘而传过来的不是一维数组首元素地址,而是一个数组的地址,传入和接受的不一致。void test(int *arr[5]) *的优先级比[ ]低,故arr首先与[ ]结合表示arr是个数组;实参传的是一维数组地址,形参是个数组;数组能和数组地址是一个概念?void test(int **arr) *与arr结合表示arr是一个指针,存放地址;int * 表示arr指针指向的元素的类型是int * ,而实参传入的是数组的地址,其应该指向一个数组,不一致,故不可以。
一级指针传参
void test(int *ptr){
return;
}
int main(){
int arr[10] = {1,2,3,4,5};
int *p = arr;
test(p);
return 0;
}
实参传过来的是一级指针,形参只能同类型一级指针接收。
二级指针传参
#include "stdio.h"
void test(int* *ptr){}
int main(){
int n = 10;
int *p = &n;
int *PP = &p;
test(pp);
return 0;
}
5、函数指针
指向一个函数的指针叫函数指针,其形式为void (*ptr)(int, int) 。ptr 是变量名,与* 结合表示其是个指针,存放着地址,再与(int, int) 结合,表示其指向一个函数,void 是指向的这个函数返回值类型。 1、首先我们来看函数的地址的特别之处: 可以看到,对于函数的地址,函数名 和&函数名 是等价的。 2、函数指针的解引用的特别之处:
int (*ptr)(int, int) = &test;
int ret1 = ptr(1, 2);
int ret2 = (*ptr)(1, 2);
以上ret1 和ret2 的对于函数指针的使用方式是等价的。函数指针调用时(*p) 中的* 只是一个摆设,为了更加容易理解而已,可以不写。
3、函数指针的typedef
typedef void(*p_func)(int, int) 表示将函数指针类型void (*)(int, int) 定义为p_func ;如下所示:
#include "stdio.h"
typedef void(*p_func)(int, int);
int test(int x, int y){
return x + y;
}
int main(){
int (*p1)(int, int) = test;
p_func p2 = test;
return 0;
}
上面的代码p1 和p2 是等价的。
6、函数指针数组
函数指针数组本质是个数组,是个存放着同类型的函数指针的数组;其形式如下:
int (*ptr[10])(int, int) ,ptr 首先与[10] 结合表示这是个数组,剔除数组ptr[10] 剩下的int (*)(int, int) 表示数组元素的类型为函数指针类型。
函数指针数组的一个用途的----转移表
7、指向函数指针数组的指针
指向函数指针数组的指针本质是个指针,所以形式里一定要确保*与变量名先结合;其具体形式如下:
void (*(*ptr)[10])(int, int) ,首先*ptr 表示ptr 是个指针变量名,然后 ( *ptr ) 与[10] 结合表示其指向一个数组,剔除数组,剩下的void ( * )(int, int) 表示数组的元素类型为函数指针类型。
8、回调函数
假设有函数A和函数B,在函数B中需要使用函数A的功能,不直接在函数B中使用函数A,而是将函数A的地址作为参数传递给函数B,在函数B中通过函数指针接收函数A的地址,来使用函数指针调用函数A进行运算,这样的形式称为回调函数机制。下面给出一个例子:
#include <stdio.h>
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) {
return x / y;
}
void menu()
{
printf("**************************\n");
printf("**** 1. add 2. sub ****\n");
printf("**** 3. mul 4. div ****\n");
printf("**** 0. exit ****\n");
printf("**************************\n");
}
int calcu(int (*ptr)(int, int)) {
int x = 0;
int y = 0;
printf("请输入参与运算的两个数:\n");
scanf("%d", &x);
scanf("%d", &y);
return ptr(x, y);
}
int main() {
int ret = 0;
int input = 0;
do {
menu();
printf("请输入选项:");
scanf("%d", &input);
switch (input)
{
case 1:
ret = calcu(Add);
printf("%d\n", ret);
break;
case 2:
ret = calcu(sub);
printf("%d\n", ret);
break;
case 3:
ret = calcu(Mul);
printf("%d\n", ret);
break;
case 4:
ret = calcu(Div);
printf("%d\n", ret);
break;
case 0:
break;
default:
break;
}
} while (input);
}
通过将加减乘除四个函数,以函数指针的形式在calcu中进行调用。
|