目录
思维导图
指针前言
一:字符指针
二:指针数组
三:数组指针
四:数组参数 && 指针参数
五:函数指针
六:函数指针数组
七:函数指针数组的指针
八:回调函数
思维导图:
指针前言:
学过C语言的各位“铁汁们”,想必都知道,C语言的“灵魂”所在就是指针啦!
指针其实就是用来存放某个东西的“地址”(也可以理解为:指针是打开一个房间门的一把钥匙)!注意这是形象化的理解哈,站在计算机的角度理解就是:一串一串的数字。计算机的内存被划分无数个的小单元,每一个小单元(可以理解为一个房间)都对应一个唯一的地址标识,换言之就是指针(地址),通过指针就可以进入此房间,进行合理的一些操作。
1)关于地址的由来:(对于32为平台而言)地址是由32个 0 或者 1组成的一串二进制序列
2)指针大小是固定的:4个字节(32为平台下)或者是8个字节(64为平台下)
3)指针的类型决定了指针在 加上或者减去一个整数的时候,他解引用时候的权限
4)不管是一级指针还是二级指针亦或是高级指针,他们都是指针(注意哈:咱可不要站在门缝里看指针,把指针看扁了),是指针就是一个变量,都是用来存放地址的。
5)指针的运算:指针-指针的绝对值得到的是元素的个数
1.字符指针
可以借助类比的思想,对于整形指针想必我们应不陌生吧。
整形指针:用来存放整形数据的指针
那么顾名思义,字符指针就是用来存放字符的数据类型的指针呗。
那接下来,话不多说,举几个栗子就可以 get 到了 。
1)
2)
显然是不可能滴!
指针p的大小是4个字节(32位平台上)而字符串“hello world”所占大小是11个字节。
此时只是把首字符串h 的地址放在p指针里面了,通过记住此时字符h 的地址,以便后续的操作。
那接下来小试牛刀一把,以加强对此知识点的理解。
对于当前的程序,输出的结果是啥???
分析:
数组名表示数组首元素的地址,数组名 arr1和 数组名 arr2 是2个完全不一样的数组,所以 输出: arr1 and arr2 are not same
虽然这两个数组的首元素都是字符 h ,但是他们所表示的含义是不一样的。
对于常量字符串 hello ,是存放在代码区域的(只能指向读的操作),其中字符指针str1 ,str2 都是指向这个常量字符串的,站在计算机内存角度,相同的代码没有必要存放2份,所以输出:str1 and str2 are same
运行结果如下:
2.指针数组
2.1 定义
依然借助类比思想,通过对整形指针数组的理解,开头知道
指针数组:是存放指针的数组
数组:存储一组相同类型数据的集合
2.2 使用
3.数组指针
3.1定义
数组指针:首先他是指针,是指向数组的指针
3.2使用
1)
在这里,想必有不少的初学者对于数组指针 和 指针数组 这两个分不清吧!
其实只要把主语定位准确就好。
数组指针旨在强调指针,只不过是指向数组的指针罢了
指针数组旨在强调数组,只不过是用来存放指针的数组罢了
对于以下2种写法,各位看看哪一个是数组指针呢
第46行:这是指针数组;去掉p之后就是数组的类型,这个数组有3个元素。每个元素是int* 类型
第47行:是一个数组指针;去掉p之后,就是指针p的类型,指向一个数组的地址,这个数组有3个元素,每个元素都是int
2)数组指针具体场景的使用:二维数组
想必大家对这个二维数组的访问,应该感到并不陌生吧,甚至说再熟悉不过了。双层for循环,通过控制二维数组的行和列来便利访问当前数组罢了。
接下来看看借助数组指针如何来遍历
二维数组名的理解:
1)他依然表示数组首元素的地址;只不过是表示第一行元素的地址(换言之就是:以数组指针的形式表示的)
2)可以把二维数组看作是由一个个一维数组组成的元素
3.3 数组名 与 &数组名的不同
数组名:代表数组首元素的地址;但是有2 个特殊情况。
第一个:sizeof(数组名):求的是整个数组的地大小(单位是字节)
第二个:&数组名,取出的是整个数组的地址(注意只不过在打印的时候是从当前首元素地址开始打印,但二者含义是不一样是)。
结合下面的栗子,有助于更好的理解!
4.数组传参和指针传参
4.1一维数组传参
思考:实参传递 arr, 对应的形参应该以啥形式来接收???
第85行:形参以数组的形式接收,支持(注意:数组可以不指定大小)
第87行:形参以数组的形式接收只不过相比较上面指定了数组的大小,支持
第89行:形参以指针的形式接收,支持;数组名就是表示数组首元素的地址嘛
第91行:形参以指针数组的形式接收,不支持
注意:
当实参以数组名的形式传参的时候,形参要是写成数组的形式,可以指定数组大小也可以不指定数组的大小;因为此时虽然是以数组的形式传参,但是其实底层是以指针的方式来接收的
4.2二维数组传参
此时arr:是一个二维数组的数组名
第99行:当实参传递数组名,和对应的列数时候,形参也是以同样方式接受的,是支持的
第101行:未指定二维数组的列,不支持
第103行:支持
第105:不支持,形参此时是一个一级整形指针而实参是一个数组指针
第107:不支持,形参是一个二级指针,而实参是一个数组指针
第109行:支持,形参实参类型都是:数组指针
注意:
对于二维数组而言:在传参的时候 ,行可以省,但是二维数组的列不能省;二维数组名表示数组首元素的地址(也就是第一行元素的地址)
4.3一级指针传参
#include <stdio.h>
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;
}
分析:实参传递的是一个整形指针;
当形参的参数是指针的时候,对应的实参可以是什么类型???
1)指针
2)一个普通的变量
3)一维数组名
4)常量字符串
4.4二级指针传参
当形参是二级指针的时候,对应的实参可以传那些类型???
1)二级指针
2)二维数组名
3)存放一级指针的指针的地址
5.函数指针
还是老问题,借助类比的思想:
数组指针:是存放数组的指针
函数指针:就是存放函数的指针
5.1 对&函数名与函数名的理解
通过对结果的分析可知:&函数名和 函数名所代表的含义一样,都表示当前函数的地址
注意这里是不同于 &数组名 和 数组名的含义
有了对函数名的理解以及,接下来看一下下面2行代码所表示的含义吧!!!
分析:
第一行代码:调用0地址处的函数
第二行代码:是signal 函数的声明
5.2 typedef 关键字的使用
1)typedef 作用:对类型进行重新命名
比如说:unsigned char 这个数据类型名字过长,使用typedef 可以对unsigned char 进行重新命名为 :un_c(也就是 说,给unsigned char 起一个别名,只不过还是同一个数据类型,只不过称呼不一样啦)
2)举例
但是若是对函数指针进行重命名的话,稍许有点不太一样
6.函数指针数组
要想弄清楚函数指针数组,那就必须对函数 以及函数指针掌握透彻。
函数指针是啥???
函数名 和
&函数名 又是啥 ???
顾名思义:函数指针数组:是用来存放函数指针的一个数组,数组的元素是一个个函数指针
6.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 main()
{
//实现一个计算器的功能:一般写法
int input = 0;
int x, y, ret = 0;
do
{
Menu();
printf("输入您的选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("输入2个操作数:>");
scanf("%d %d", &x, &y);
switch (input)
{
case 1:
ret = add(x, y);
printf("ret = %d\n", ret);
break;
case 2:
ret = sub(x, y);
printf("ret = %d\n", ret);
break;
case 3:
ret = mul(x, y);
printf("ret = %d\n", ret);
break;
case 4:
ret = div(x, y);
printf("ret = %d\n", ret);
break;
}
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
{
printf("输入错误,重新输入\n");
}
} while (input);
return 0;
}
分析:随着用户需求的不断更改,对计算器的更能也需要不断完善,比如实现 2个数据的 按位与,按位或等等。这将会致使case 语句越来越冗余,而且复用率极低,不是一个很好的代码。
借助函数指针的版本:
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 main()
{
int input = 0,ret = 0;
int x, y;
do
{
Menu();
int (*p[5]) (int, int) = { NULL,add,sub,mul,div };
printf("输入您的选择:>");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("输入2个操作数:>");
scanf("%d %d", &x, &y);
ret = p[input](x, y);
printf("ret = %d\n", ret);
}
else if (input == 0)
{
printf("退出计算器\n");
}
else
printf("输入错误,重新输入\n");
} while (input);
return 0;
}
1)之所以把函数指针数组大小设置为5而不是4:就是为了保持与case 语句的一致。
2)函数名:表示函数的地址
7.指向函数指针数组的指针
还是老问题:函数指针数组的指针 是 指向函数指针数组的 指针
8.回调函数
8.1回调函数定义
首先回调函数是借助一个函数指针来调用的函数。当把函数的地址作为参数传递给一个指针,来间接调用要想调用的函数,此时这个被调用的函数就是回调函数。
8.2回调函数应用
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;
}
int Calc(int (*p)(int, int ))
{
int x, y;
printf("请输入2个操作数:>");
scanf("%d %d", &x, &y);
return p(x, y);
}
int main()
{
int input;
int ret = 0;
do
{
Menu();
printf("请输入你的选择:>");
scanf("%d", &input);
switch (input)
{
case 1:
ret =Calc(Add);//此时被间接调用的Add函数就成为回调函数
printf("ret = %d\n", ret);
break;
case 2:
ret = Calc(Sub);
printf("ret = %d\n", ret);
break;
case 3:
ret = Calc(Mul);
printf("ret = %d\n", ret);
break;
case 4:
ret = Calc(Div);
printf("ret = %d\n", ret);
break;
case 0:
printf("退出计算器\n");
break;
default:
printf("输入错误,重新输入\n");
break;
}
} while (input);
return 0;
}
使用回调函数模拟实现qsort()(注意是基于冒泡思想实现的qsort函数)
1)冒泡核心思想:两两相邻元素进行交换(满足一定条件的)
2)qsort( )函数的初步使用
使用此函数对int 类型数据进行排序(注意qsort函数默认是以升序来排列的)
对于compare()这个函数,需要注意以下几点:
第一:此函数返回类型是int 类型
第二:此函数的参数类型必须是const void *
第三:对于void*类型的指针是不能直接进行解引用的,需要先进行类型强转,再进行解引用
qsort()可以用来排序任意类型的数据
对int 类型数据排序
int cmp(void* p1, void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
int main()
{
int arr[] = { 7,4,1,2,5,8,9,6,3,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, 4, cmp);
for (int i = 0; i < sz; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
对结构体类型数据排序:
typedef struct student
{
int age;
char name[20];
char sex;
}stu;int Cmp_name(const void* p1, const void* p2)//注意对于void*类型指针不能直接解引用
{
return strcmp(((stu*)p1)->name, ((stu*)p2)->name);//注意字符串比较大小只能实用strcmp()函数
}
int main()
{
stu arr[] = { {10,"lisi",'m'},{5,"bai",'w'},{15,"duan",'w'}};
int sz = sizeof(arr) / sizeof(arr[0]);
qsort(arr, sz, sizeof(stu), Cmp_name);
}
8.3 基于冒泡函数思想模拟qsort() 实现
完整代码:
int cmp(const void* p1, const void* p2)
{
return (*(int*)p1 - *(int*)p2);
}
typedef struct student
{
int age;
char name[20];
char sex;
}stu;
int Cmp_age(const void* p1,const void* p2)
{
return (((stu*)p1)->age -( (stu*)p2)->age);// 注意 (stu*)p2->age错误的
}
int Cmp_name(const void* p1, const void* p2)//注意对于void*类型指针不能直接解引用
{
return strcmp(((stu*)p1)->name, ((stu*)p2)->name);//注意字符串比较大小只能实用strcmp()函数
}
void Swap(char* p1, char* p2, int size)
{
//注意是一个字节一个字节交换
for (int i = 0; i < size; ++i)
{
char tmp = *p1;
*p1 = *p2;
*p2 = tmp;
++p1;
++p2;
}
}
void Qsort(void* base, int num, int size, int(*cmp)(const void* p1, const void* p2))
{
for (int i = 0; i < num - 1; ++i)
{
for (int j = 0; j < num - i - 1; ++j)
{
//相邻2元素进行比较
//注意这里必须强转成char*类型指针
if (cmp(((char*)base + j * size) ,((char*)base + (j + 1) * size) ) > 0) //注意这里条件必须注明是>0 还是<0
{
//交换
Swap(((char*)base + j * size), ((char*)base + (j + 1) * size), size);
}
}
}
}
int main()
{
int arr[10] = { 7,4,1,2,5,8,9,6,3,10 };
Qsort(arr, 10, sizeof(arr[0]), cmp);
stu arr1[] = { {10,"lisi",'m'},{5,"bai",'w'},{15,"duan",'w'} };
//Qsort(arr1, 3, sizeof(stu), Cmp_name);
Qsort(arr1, 3, sizeof(stu), Cmp_age);//注意最后一个函数指针对应的函数需要使用者自己实现
for (int i = 0; i < 10; ++i)
{
printf("%d ", arr[i]);
}
return 0;
}
结语:
首先非常感谢各位老铁可以看到最后,想必收获满满吧!(有疑惑也是正常的,毕竟知识量太大了,俺也是整了不止一时半会才写完这篇博客的)对于指针这部分的学习,是真的及其重要!
对于后期数据结构以及C嘎嘎的学习,起着至关重要的作用,为了检验大家对指针这一知识点的学习,大家可以看看关于指针和数组相关面试的题型,链接在此,各位自取哈~~~
https://blog.csdn.net/X_do_myself/article/details/134366577
今日的share到此就结束了,希望各位友友们可以支持一下~~~