[C语言]指针进阶

阅读: 评论:0

[C语言]指针进阶

[C语言]指针进阶

目录

1.字符指针 

2.指针数组

 3.数组指针

思考一下数组指针的形式

 有什么内涵呢

 实际应用:打印二维数组中的每个元素

判断下面代码的意思 

4. 数组参数、指针参数 

一维数组传参

二维数组传参 

一级指针传参

二级指针传参

5.函数指针 

6.函数指针数组 

7.指向函数指针数组的指针 

8.回调函数

实例1——计算器的实现 

 实例2——qsort函数的学习和模拟实现

先来学习下qsort函数的内涵

模拟实现 

需要注意的点: 


1.字符指针 

以下代码输出的结果是什么?

回答:h            hello the         hello the

分析:说明字符指针变量可以存放字符串。

第一个h是因为*ps指针指向的就是hello的首字符h,故输出h;后两个因为数组名arr和ps一样都是hello的首字符地址,故均输出hello。

int main()
{//本质上是把"hello the"这个字符串的首字符地址存储在了ps中char* ps = "hello the";char arr[] = "hello the";printf("%cn", *ps);//hprintf("%sn", ps);printf("%sn", arr);return 0;
}

 以下代码输出的结果是什么?

回答:应该是1和2不同,3和4相同

分析:数组名代表数组首元素的地址,str1和str2是两个不同的数组,分别需要开辟两个不同的内存空间,str1和str2分别指向两个hello的首地址,因此不同;

"hello bit"是常量字符串,指针变量无法通过解引用操作改变它。对于内存而言既然"hello bit"是常量,那么只需为它开辟一个内存空间即可,地址编号确定,因此3和4指向了同一个地址即常量字符串"hello bit"的首元素地址

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 samen");elseprintf("str1 and str2 are not samen");if (str3 == str4)printf("str3 and str4 are samen");elseprintf("str3 and str4 are not samen");return 0;
}

2.指针数组

定义:指针数组是一个存放指针的数组,本质上是数组,其中存的内容是指针

如下代码中:arr数组中可以存放三个整形指针int*,而a,b,c分别为三个整形数组首元素地址放入其中arr[0],arr[1],arr[2]

int main()
{//指针数组//数组——数组中存放的是指针(地址)//int* arr[3];//存放整型指针的数组int a[] = { 1,2,3,4,5 };int b[] = { 2,3,4,5,6 };int c[] = { 3,4,5,6,7 };int* arr[3] = { a,b,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("%d ", arr[i][j]);}printf("n");}return 0;
}

 3.数组指针

定义:是一种指针,应该能够指向数组的指针,数组指针中存放的应该是数组的地址

接下来会与之前的指针数组区别:

  • int *p1[10];——指针数组

[ ]的优先级高于*号的,所以必须加上()来保证p先和*结合 

  • int (*p2)[10];

p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。所以p是一个指针,指向一个数组,叫数组指针 

思考一下数组指针的形式

首先得是指针(*)→指针指向的数组有多少元素呢([ ]) →指向的数组又是什么类型

int main()
{int arr[10] = { 1,2,3,4,5 };//arr——是arr[0]的地址int(*parr)[10] = &arr;//取出的是数组地址//parr就是一个数组指针——其中存放的是数组的地址double* p[10];double* (*pd)[10] = &p;return 0;
}

 有什么内涵呢

观察以下代码:

int main()
{int arr[10] = {1,2,3,4,5,6,7,8,9,10 };int(*pa)[10] = &arr;//数组指针int i = 0;for (i = 0; i < 10; i++){printf("%dn", (*pa + i));printf("%dn", *(*pa + i));}return 0;
}

运行后我们看一下:

  1. *说明pa是指针变量,pa应该存放一个数组的地址,pa+1会跳过一个数组的地址大小;
  2. *pa是首元素地址,也就是arr数组名,*pa+1 也就是地址前进4位字节(与int指针类型匹配),反映数组中每个元素的地址
  3. 解引用操作:*(*pa)由此得出arr数组中的元素

 实际应用:打印二维数组中的每个元素

p是数组指针,指向一个数组,*p保存首元素地址(arr数组名),[5]反映指向数组中有5个元素,int表示数组元素类型

对于二维数组的首元素是二维数组的第一行,这里传递的arr,其实相当于第一行的地址,是一维数组的地址

  • (p+i)得到每一行数组的地址
  • *(p+i)得到每一行数组的首元素地址
  • (*(p+i)+j)获取每一行数组中每个元素的地址
  • *(*(p+i)+j)获得每一行数组中的元素数组内容
void print(int(*p)[5],int r,int c)
{int i = 0;int j = 0;for (i = 0; i < r; i++){for (j = 0; j < c; j++){printf("%p ", (p + i) );printf("%p ", *(p + i) );printf("%p ", *(p + i)+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} };print(arr,3,5);return 0;
}

验证一下结果:

判断下面代码的意思 

int arr[5];——有着5个整型元素的数组

int *parr1[10];——存放10个整型指针的数组

int (*parr2)[10];——该指针能指向一个数组,一个数组中10个元素,每个元素类型是int

int (*parr3[10])[5];——parr3是一个存储数组指针的数组,该数组能存放10个指针,每个数组指针能够指向一个数组,数组5个元素,每个元素是int类型

4. 数组参数、指针参数 

一定要分析清楚,实参传过去的是什么。是地址的话是谁的地址,而形参对应又应该用什么形式接收。

一维数组传参

判断哪些可以传参,哪些不能?

#include <stdio.h>
void test(int arr[])//ok?
{}
void test(int arr[10])//ok?
{}
void test(int *arr)//ok?
{}
void test2(int *arr[20])//ok?
{}
void test2(int **arr)//ok?
{}
int main()
{int arr[10] = {0}int *arr2[20] = {0};test(arr);test2(arr2);
}

这里形参的形式都是正确的。

  • test()

1,2:当作数组接收                                                                                              √正确

3:arr为数组名,此时传递的是首元素地址,用int*接收                                     √正确

  • test2()——int* arr2[20]是一个指针数组,存放int*类型的指针

4:传递指针数组,相应类型接收                                                                        √正确

5:arr2是指针数组首元素地址,首元素是int*类型的指针,也就成了二级指针 √正确

二维数组传参 

判断哪些可以传参,哪些不能?

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);
}
  • arr是一个二维数组,二维数组可以不知道行数,但必须知道列数,也就是一行有多少元素

因此1√正确,2×错误,3√正确

  • arr为首元素地址,代表第一行元素的地址,理解为数组指针,应该指向一个数组

1:理解arr为数组指针(能够指向第一行数组,第一行是有5个整型的数组 ),对应的形参格式错误,只是一级指针                                                                                             ×错误

2:形参接收的应为一个指针数组                                                                         ×错误

3:arr为数组指针,指向的数组内有5个int类型元素                                             √正确

4:传过去的不是二级指针                                                                                    ×错误

一级指针传参

函数形参形式是接收一级指针时,实参传递的是地址即可

二级指针传参

函数形参形式是接收二级指针时,实参可以传递二级指针,即一级指针的地址,也可以传存放一级指针的数组名

void test(char **p)
{}
int main()
{char c = 'b';char*pc = &c;char**ppc = &pc;char* arr[10];test(&pc);test(ppc);test(arr);//Ok?return 0;
}

 分析:

pc是一级指针,ppc是二级指针,test(ppc)把二级指针ppc进行传参;

test(&pc)传一级指针变量的地址;

test(arr)传存放一级指针的数组,传递了arr数组名,数组名代表首元素的地址,而arr数组每个元素都是char *类型,char *的地址相当于char * *,符合 char(int ** p)中形参的接收要求

5.函数指针 

定义:指向函数的指针,存放函数地址的指针

函数指针的形式:

                                                    int(*pf)(int,int)

 首先应该是指针(例如*pf),指向一个函数,函数的类型如何?

函数包括函数形参的类型,函数的返回类型,因此函数指针也应该指明所指向函数包括的组成部分。

//函数指针——存放函数地址的指针
//&函数名——取到的就是函数的地址
int Add(int x, int y)
{return x + y;
}
int main()
{//&函数名等价于函数名printf("%pn",&(Add));printf("%pn",Add);//函数指针变量/*int(*pf)(int, int) = &Add;*/int(*pf)(int, int) = Add;//意味着Add===pfprintf("%dn", (*pf)(3, 5));printf("%dn", pf(3, 5));printf("%dn", Add(3, 5));return 0;
}

观察上述代码的结果:

  1. 函数名==&函数名,说明函数名就是函数的地址 !(同时记得数组名 != &数组名)
  2. 以往我们调用函数的形式Add(3,5)与int (*pf) (3,5)效果相同

pf是函数指针变量,存放函数的地址,由此可以得知pf==Add,函数还可以写成pf(3,5)

(*是对形式的理解,实际并没有起作用)

6.函数指针数组 

顾名思义——存放函数指针的数组,把函数的地址存到一个数组,同时是存放同类型的函数指针

int (*pf)(int,int)=Add;
int (*pf1)(int,int)=Sub;
int (*pfArr[2])(int,int);//函数指针数组pfArr

 设计一个计算器来理解:

//函数指针数组
//假设实现一个计算器,计算整型变量能够加减乘除
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("*************  1.Add  ***************n");printf("*************  2.Sub  ***************n");printf("*************  3.Mul  ***************n");printf("*************  4.Div  ***************n");printf("*************  0.退出 ***************n");
}
int main()
{int input = 0;do{int(*pfArr[5])(int, int) = {NULL,Add,Sub,Mul,Div};int x = 0;int y = 0;menu();//计算器界面printf("请进行你所需要的操作:>");scanf("%d", &input);if (input >= 1 && input <= 4){printf("请进行你所需要的计算的两个数值:>");scanf("%d%d", &x, &y);printf("%dn", pfArr[input](x, y));}else if (input == 0){printf("退出程序n");break;}else{printf("请重新选择n");}} while(input);return 0;
}

7.指向函数指针数组的指针 

//整型数组
int arr[5];
int (*p1)[5]=&arr;
//p1是指向(整型数组)的指针//整型指针的数组
int* arr[5];
int* (*p2) [5]=&arr;
//p2是指向(整型指针数组)的指针//函数指针
int (*p)(int,int);
//函数指针数组
int (*p2[4])(int,int);
p3=&p2;//取出的是函数指针数组的地址
//p3是一个指向(函数指针数组)的指针
int (*(*p3)[4])(int,int);

8.回调函数

回调函数就是一个通过函数指针调用的函数

如果你把函数的指针(地址)作为参数传递给另一个 函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

快速理解:一个函数本身没有直接被使用,其地址作为另一个函数的形参而被调用

实例1——计算器的实现 

//回调函数
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 Calc(int (*pf)(int, int))
{int x = 0;int y = 0;printf("请输入2个操作数>:");scanf("%d %d", &x, &y);return pf(x, y);
}int main()
{int input = 0;//计算器-计算整型变量的加、减、乘、除//a&b a^b a|b a>>b a<<b a>bdo {menu();int ret = 0;printf("请选择:>");scanf("%d", &input);switch (input){case 1:ret = Calc(Add);printf("ret = %dn", ret);break;case 2:ret = Calc(Sub);printf("ret = %dn", ret);break;case 3:ret = Calc(Mul);//printf("ret = %dn", ret);break;case 4:ret = Calc(Div);//printf("ret = %dn", ret);break;case 0:printf("退出程序n");break;default:printf("选择错误,重新选择!n");break;}} while (input);return 0;
}

 实例2——qsort函数的学习和模拟实现

qsort函数可以排列任何类型的元素,包括整型变量、字符串变量、结构体等

先来学习下qsort函数的内涵

base中存放的是待排序数组第一个元素的首地址num是排序待数据数组中元素的个数size一个数组元素字节的大小int (*compar)(const void*,const void*)比较待排序数据中两个元素的函数

 使用:

int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}void test1()
{//整型数据的排序int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);//排序qsort(arr, sz, sizeof(arr[0]), cmp_int);//打印int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}struct Stu
{char name[20];int age;
};int sort_by_age(const void* e1, const void* e2)
{return ((struct Stu*)e1)->age - ((struct Stu*)e2)->age;
}int sort_by_name(const void* e1, const void* e2)
{return strcmp(((struct Stu*)e1)->name,((struct Stu*)e2)->name);
}void test2()
{//使用qsort函数排序结构体数据struct Stu s[] = { {"zhangsan",30},{"lisi",40},{"wangwu",20}};int sz = sizeof(s) / sizeof(s[0]);//按照年龄排序/*qsort(s, sz, sizeof(s[0]), sort_by_age);*///按照名字排序qsort(s, sz, sizeof(s[0]), sort_by_name);
}int main()
{//test1();test2();return 0;
}

模拟实现 

//模仿qsort函数实现一个冒泡排序的通用算法
//不同类型数据比较方法交给使用者确定
void Swap(char* buf1, char* buf2,int size)
{int i = 0;for (i = 0; i < size; i++){char tmp = *buf1;*buf1 = *buf2;*buf2 = tmp;buf1++;buf2++;}
}void bubble_sort(void* base, int num, int size,int(*cmp)(const void* e1, const void* e2))
{int i = 0;//躺数for (i = 0; i <num-1; i++){//一趟的排序int j = 0;for (j = 0;j < num - 1 - i; j++){//两个元素的比较if (cmp((char*)base+j*size,(char*)base+(j+1)*size) > 0){//交换Swap((char*)base + j * size, (char*)base + (j + 1) * size,size);}}}
}int cmp_int(const void* e1, const void* e2)
{return *(int*)e1 - *(int*)e2;
}void test3()
{//整型数据的排序int arr[] = { 9,8,7,6,5,4,3,2,1 };int sz = sizeof(arr) / sizeof(arr[0]);//排序bubble_sort(arr, sz, sizeof(arr[0]), cmp_int);//打印int i = 0;for (i = 0; i < sz; i++){printf("%d ", arr[i]);}
}int main()
{test3();return 0;
}

需要注意的点: 

1.字节大小size的作用

作为qsort函数的设计者,字节大小size十分重要,因为不知道使用者是什么类型的数据进行比较;

2.前后两个数据进行比较时,如何得到地址?为什么要强制转换为char*类型的指针

因为使用者会通过形参size告知前一个元素与后一个元素差几个字节。而char*类型的指针为一个字节,因此跳过一个元素:(char*)地址+1*size,跳过两个元素:(char*)地址+2*size;

 3.内部的交换函数如何实现?不知道什么类型的元素,如何将两个元素的内容进行交换呢?

由于我们不知道元素的类型,所以不能将指针转为相对应类型的指针进行交换,但我们知道一个元素的字节大小。因此我们依旧传递为char*类型的指针,通过size一个字节一个字节的交换

 4.思考:为什么使用void*空指针 

因为void*p空指针可以存放任意类型的指针,qsort函数并不明确知道需要排序的是什么类型的数组,因此先用void*接收,具体什么类型由使用者告知。

本文发布于:2024-02-02 23:16:39,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/170688699747099.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:进阶   指针   语言
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23