指针是什么
在计算机中,数据通常储存在内存中,不同类型的数据需要开辟不同大小的空间进行数据存储,
而在计算机中,每块内存都有着相应的编号,即相应的地址;当我们需要将一个数据的地址进行保存
时,则需要一个变量来指向这个地址,而在c语言中,指向一个地址的变量,即为指针变量。
基础指针类型
在c语言中,有许多比较基本的指针类型
int* - 指向整形地址的指针
char* - 指向字符地址的指针
float* - 指向浮点数地址的指针
…
举个例子
int main() { int a = 10;/*整型变量*/ char b = 'a';/*字符型变量*/ float c = 1.5f;/*浮点型变量*/ //-------------- int*pa = &a;//pa即为整形指针,指向整形变量a的地址 char*pb = &b;//pb即为字符指针,指向字符变量b的地址 float*pc = &c;//pc即为浮点型指针,指向浮点型变量c的地址 return 0; }
进阶指针类型
除了以上的较为简单的指针以外,还存在着一些比较复杂的指针。
- 数组指针
- 函数指针
根据类比法我们可知
整形指针即为指向整形数据地址的指针
字符指针即为指向字符数据地址的指针
同理可得
数组指针即为指向数组地址的指针
函数指针即为指向函数地址的指针
数组指针
从上面的类比法可以得到结论 - 数组指针即为指向数组的指针 但是数组指针应该如何表示?
假设存在一个数组 - int arr[10] = {0} ;
并要求利用一个变量名为pr的指针变量接收,应该怎么写?
int main() { //错误 int arr[10] = {0}; int *p[10] = &arr; return 0; }
可以利用上面的方式写吗? 当仔细分析,我们可知,这里的变量p先与[]进行结合,说明p其实为数组,
而我们需要表达的意思为采用一个名为p的指针来指向数组地址。 故上面的代码不合题意,故 ×
那如何去对该题进行解释呢? 根据操作符优先级我们不难发现,如果以上面的代码形式来创建数组指针
p都会先于[]进行结合成为一个数组,能不能在此使用一个操作符来改变顺序使p先与*结合
int main() { //正确 int arr[10] = {0}; int (*p)[10] = &arr; return 0; }
当我们使用()使强行先与p进行结合时,p即为指针 该段代码的理解则是: 根据操作符优先级,()内与p先进行结合,p即为指针
指针指向的为[]数组,数组内共有10个元素,每个元素的类型为int型 而该指针指向的即为数组arr的地址。
&数组名与数组名的区别
从上面的代码中,可以知道数组指针的表示形式为
- int (* )[ ] - 且其功能为指向数组地址
但其中有个疑问,指针中存在着这样的表达形式
假设存在一个数组 int arr[10] = {0}; int* p= arr;
即数组名为arr,数组元素个数为10,且每个元素的类型为int型
既然&数组名才是数组地址的话,数组名又是什么?
当把arr与&arr同时以地址的形式打印出时,可以观察到,其实两者的地址相同,
或许有些人就会理所应当认为&arr与arr等价,但若是同时再将两个地址进行+1, 将会打印什么?
从这里不难发现 当以地址打印arr与arr + 1时,两者相差了4
而以地址打印&arr与&arr + 1时,两者却相差了2 8
(因为这里的表示形式为地址,而为了更好的表现,地址以16进制进行展示)
十六进制的2 8换做十进制即为40
即为40 又因为数组的元素个数为10,可以发现当&arr + 1时,越过了整个数组的地址
再将arr[0]的地址与sizeof(arr),sizeof(arr[0])分别进行打印时
可以得出结论:
在大部分情况中,数组名即为首元素地址
其中有两个例外:
1、sizeof(数组名) 2、&arr
int main() { int arr[10] = { 0 }; printf("arr = %p\n",arr); //数组名为首元素地址,故这里打印的时候为数组首元素的地址 printf("arr + 1 = %p\n\n", arr+1); // int * //数组首元素地址+1,即数组首元素后一个元素的地址 //因为类型为int型,故地址中相差4个字节 printf("&arr = %p\n", &arr); //int(* )[10] //数组名为首元素地址,但是有两个例外 //一个是sizeof()数组名,一个是&数组名 printf("&arr + 1 = %p\n\n", &arr+1); //当&数组名+1时,共相差 "数组元素个数 * 每个元素字节" //从这里可知,在内存中,地址相差40个字节 //故 &数组名 为整个数组的地址 printf("&arr[0] = %p\n", &arr[0]); //int * //该处对数组单个元素的地址进行打印 printf("&arr[0] + 1 = %p\n\n", &arr[0]+1); //当对单个数组地址+1时,只越过了4个字节,即一个整形 printf("sizeof(arr) = %d\n", sizeof(arr)); //该处是对整个数组的元素大小进行计算 //故结果为 "数组元素个数 * 元素大小" printf("sizeof(arr[0]) = %d\n\n", sizeof(arr[0])); //该处只是对数组内单个元素进行计算 //故打印结果即为单个元素的大小 /*数组名为首元素地址是大部分的情况,除了两个特殊例外 分别为 &数组名 与 sizeof(数组名) 除此之外,数组指针是一个指向整个数组的指针 而普通指针只能存放数组单个元素的地址*/ return 0; }
数组指针该如何使用
假设存在一个数组 - int arr[10] = {1,2,3,4,5,6,7,8,9,10};
该如何访问数组中的每个元素?
1.for循环,利用下标访问操作符[]来访问每个元素
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; int sz = sizeof(arr) / sizeof(arr[0]); for (int i = 0; i < sz; i++) { printf("%d ", arr[i]); }*/ return 0; }
2.for循环,利用*解引用操作每个元素的地址进行访问
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; //使用指针来访问数组元素 ② int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (int i = 0; i < sz; i++) { //printf("%d ", *(arr + i)); printf("%d ", *(p + i)); } return 0; }
3.for循环利用数组指针访问数组内元素
int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,0 }; 使用数组指针对数组元素进行访问 int sz = sizeof(arr) / sizeof(arr[0]); int(*p)[10] = &arr;//p即为整个数组的地址 for (int i = 0; i < sz; i++) { printf("%d ", *((*p) + i)); printf("%d ", (*p)[i]); // - 对指针p进行解引用得到数组名 //数组名没有跟着sizeof或者&故为首元素地址 //对首元素地址进行加i进行解引用 // 冗余 - 不建议这样使用 //数组指针在使用时一般在逻辑比较复杂的情况 return 0; }
该方法为创建一个数组指针来指向该数组,再将数组指针解引用得到数组
再利用for循环访问各个元素,过于冗余,不建议使用。
数据传参
在了解数组指针后,我们来了解了解数据传参。
一维数组传参
存在以下代码
int main() { int arr[10] = {0}; int* arr2[20] = {0}; test(arr);//传入数组首元素地址 test2(arr2);//传入指针数组的数组首元素 return 0;
判断以下函数接收数据参数是否匹配:
void test(int arr[]) { } //传入的为数组,所以可以用数组进行接收 //该形参正确 ✔ void test(int arr[10]) { } //同理上面那段代码,在以一维数组形式的形参接收数组参数时 //可以采用一维数组进行接收,且数组内元素个数可有可无 //该形参正确 ✔ void test(int* arr) { } //数组传参时本质上传的为数组的首元素地址 //既然是地址那就说明可以用指针进行接收 //所以该函数的形参为指针 ✔ void test2(int* arr[20]) { } //test2在传参过程中传入的为指针数组 //当使用相应的指针数组进行接收时 //可以利用相应的指针数组作为形参 ✔ void test2(int** arr) { } //在传参过程中,传入的是一个一维指针的地址 //故可以利用二维指针的形参进行接收 **arr - arr即为二维指针 // ✔
可得出结论:
一维数组传参,形参可以是数组,也可以是指针
当形参为指针时,要注意类型
二维数组传参
存在以下代码
int main() { int arr[3][5] = { 0 }; test(arr); return 0; }
判断以下函数接收数据参数是否匹配:
void test(int arr[3][5]) {} //传入一个二维数组时 //可以利用二维数组作为形参来接收参数 //但是接收二维数组的形参在二维数组的规定一致 //只可忽略行,不可忽略列 ✔ void test(int arr[][]) {} //根据上一行代码的结论 //在利用二维数组作为形参来接收二维数组的实参时 //行可省略列不可省略,所以该行代码将列也一并省略了 //故该行代码 ❌ void test(int arr[][5]) {} //根据上两行代码可知该行代码 ✔ void test(int* arr) {} //传入一个二维数组,说明传入的值为二维数组的数组首元素地址 //二维数组的数组首元素地址即为数组第一行的元素地址 //故需要一个数组指针进行接收 //而该行代码形参只能接收普通类型的int*指针 //不能接收整个二维数组首行元素的地址 //代码能运行是因为在运行时编译器进行了处理 //本质上是不行的,故该段代码 ❌ void test(int* arr[5]) {} //传入的为一个二维数组,需要用数组指针进行接收 //改行代码形参为指针数组,故 ❌ void test(int(*arr)[5]) {} //传入的为一个二维数组,需要用数组指针接收 //该行形参即为数组指针,且对应的元素个数与传入参数相同 ✔ void test(int** arr) {} //该行形参为二阶指针 //二阶指针适合用来作为指针数组的形参 //但传入参数为二维数组,类型不匹配,故 ❌
可得出结论: 二维数组传参,函数形参的设计只能省略第一个[ ]的数字
因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
这样才方便运算。
一级指针传参
我们已经知道了,当为一个函数传入一维数组或二维数组为参数时应该怎么接收参数,
那我们可以反着来一次,
若是函数内能以一级指针作为参数时可以传入什么参数 存在以下函数,
函数的参数为一级指针,判断可以传入什么实参:
void print(int* p, int sz) { int i = 0; for (i = 0; i < sz; i++) printf("%d\n", * (p + i)); } int main() { int arr[10] = (1, 2, 3, 4, 5, 6, 7, 8, 9); int* p = arr; int sz = sizeof(arr) / sizeof(arr[0]);//一级指针p,传给函数 print(p,sz); return 0; }
根据上面的代码,可以推断出 当函数的参数为一级指针时,可以传入:
相同类型变量的地址、一维数组数组名、一级指针
二级指针传参
存在以下函数,函数的参数为一级指针,判断可以传入什么实参:
void test(int** ptr) { //二级指针在传参的过程中可用二级指针接收 //--------- //当函数的参数为二级指针的时候,可以接收什么参数 //二级指针、一级指针的地址、 //指针数组 - 数组名为首元素地址,而指针数组内元素也为指针 //故当函数的参数为二级指针时,可以接收指针数组名 printf("num = %d\n", **ptr); } int main() { int n = 10; int* p = &n; int** pp = &p; test(pp);//该处传参传的为二级指针 test(&p);//该处传参传的为一级指针的地址 return 0; }
由此可推断出 当函数的参数为二级指针时,可以传入:
二级指针、一级指针的地址 指针数组 -
数组名为首元素地址,而指针数组内元素也为指针
函数指针
从以上的内容我们认识到,任何数据都有相应的地址,即也就拥有相应的指针,
既然如此, 我们可以来讨论讨论,函数是否存在地址,是否存在指针。
通过类比法可得知
整型指针 - 指向整型的地址
字符指针 - 指向字符的地址
浮点数指针 - 指向浮点数的地址
数组指针 - 指向数组的地址
函数地址 - 指向函数的地址?
在了解函数指针前,我们先来了解函数是否有对应的地址
存在下列代码,代码的功能为打印函数的地址
int Add(int x, int y) { return x + y; } int main() { printf("%p\n", &Add); printf("%p\n", Add); //打印函数的地址 return 0; }
从打印结果得知,函数也存在地址
&函数名与函数名的地址相同,也由此得知,函数名与&函数名等价
既然函数也存在地址,那么函数指针应该如何表达
存在数组int arr[10] = {0}; 若需要创建数组指针p
该数组的数组指针即为
- int(*p)[10] = &arr;
类比数组指针,可得知:若存在函数int Add(int x,int y){;}
且需要创建函数指针变量pp指向该函数
函数指针即为 - int (*pp)(int , int) = &Add;
int Add(int x, int y) { return x + y; } int main() { printf("%p\n", &Add); printf("%p\n", Add); //打印函数的地址 int(*add)(int, int) = &Add; //*add说明add是一个指针 //指针指向( )说明指向函数 //函数参数的类型为两个int类型 //函数的返回值也为int类型 int(*adc)(int, int) = Add; int(*ad)(int, int) = &Add; //在创建函数指针时,&函数名与函数名为一致 //所以函数名本质上就是函数地址 //同时在创建函数指针时,不需要变量名,只需要类型名 /*int ret = (*ad)(3, 5);*/ //函数指针的调用,在创建函数指针时 //已经交代了函数返回值的类型以及传值的类型 //故函数调用时只需要解引用并进行传参即可 /*int ret = ad(3, 5);*/ int ret = (******ad)(3, 5); //并且函数指针在调用时,其*可省略,只是为了方便代码更加容易解读 printf("%d\n", ret); return 0; }
函数指针数组
根据类比法可得知,函数指针数组即为存放函数指针的数组
存在以下代码:
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; } int main() { //函数指针数组的创建与使用 int(*cul[])(int,int)= { Add,Sub,Mul,Div }; //cal[10]说明cal为函数指针数组的数组名 //每个元素的类型为int(* )(int,int) for (int i = 0; i < 4; i++) { printf("%d\n",cul[i](8, 4)); //根据[]下标访问操作符访问每个数组元素 //即访问每个函数的函数名 //函数名无论有无&都等价 //故有无解引用也一样 } return 0; }
函数指针数组的使用
分享一个函数指针数组的使用:
void menu() { printf("**************************\n"); printf("**** 1.add 2.sub *****\n"); printf("**** 3.mul 4.div *****\n"); printf("**** 0.exit *****\n"); printf("**************************\n"); } int main() { /*转移表*/ //利用函数指针数组写一个能完成整数+-*/的程序 int input = 0; int x = 0; int y = 0; //函数指针数组只能在所有的函数参数类型一样才能够使用 int (*pfArr[5])(int, int) = { NULL,Add,Sub,Mul,Div }; //创建一个函数指针数组 //数组名为pfArr - 元素个数为5个 //每个元素类型为int (* )(int, int) menu(); do { printf("请选择:>"); scanf("%d", &input); if (input == 0) { printf("退出计算器\n"); break; } else if (input > 0 && input <= 4) { printf("请输入两个整形参数>"); scanf("%d%d", &x, &y); printf("%d\n",pfArr[input](x, y)); } else { printf("输入错误\n"); } } while (input); return 0; }
本章完
分享不易,感谢支持。