目录
指针
1.指针就是一个变量(指的是指针变量),指针是用来存放地址的,地址唯一标识一块内存空间。(也就是说,内存会划分为小的内存单元,每个内存单元都有一个编号,这个编号就被称为地址,也把地址叫做指针)。
2.指针的大小是固定的,在32位系统下是4个字节,在64位系统下是8个字节。
3.指针是有类型的,指针的类型决定了指针的+-整数的步长(+1或者-1会跳过几个字节),指针解引用操作的时候的权限。
一、字符指针
指针类型有一种类型为char*(字符指针)
一般情况下,我们将一个字符的地址存放在一个字符指针变量中
int main()
{
char ch = 'a';
char* pc = &ch;
*pc = 'a';
return 0;
}
pc就是一个指针变量,里面存放的是ch的地址,当我们对指针变量pc进行解引用,就可以的到我们的字符'a';
这个我们很容易理解,但是我们还有一种常用的写法,
#include<stdio.h>
int main()
{
const char* str = "hello";
printf("%s\n",str);
return 0;
}
在这里,我们就会想到,这是将一个字符串放在str的指针变量里了吗?
在这里,我们并不是将一整个字符串放在str的指针变量中了,而是将字符串的首字符的地址存放在了str中,当我们去向后访问时,访问到\n就访问结束。
#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;
}
这里的运行结果应该是什么呢?
str1 str2是在内存中内存中开辟了两个空间,分别用来存储字符串str1 和 str2,因此str1!=str2。在str3 与 str4比较时,这里就需要注意了,这里是将hello bit这个字符串的地址分别存放在str3 和 str4中,因此这里str4 和 str3 是相同的。
二、指针数组
我们了解到数组,数组是一个存放相同类型元素的集合,我们之前学习到有
int* arr1[10];//整型指针数组
char* arr2[10];//字符指针数组
char* * arr3[10];//二级字符指针数组
指针数组,是一个用来存放指针的数组。
三、数组指针
1.数组指针的定义
当我们看到数组指针时,数组指针是数组还是指针?他应该是指针,也就是一个数组的地址,
int* p1[10];
int(*p2)[10];
在这里p1和p2分别是指的什么?
p1指的是一个整型指针数组,p1是一个数组,里面存放了10个int*类型的元素;
p2是一个指针,在这里表示p2是一个指针变量,P2指向的是一个大小为10的一个整型数组,p2因为指向一个数组,也是一个数组指针。
2.&数组名和数组名
在之前我们了解到,数组名是数组首元素地址,但是有两个情况例外,一个是将数组名在sizeof里面使用时,另一个就是&数组名,在这两种情况下数组名不代表数组首元素地址。
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
return 0;
}
上面一串代码,我们输出的时arr的地址,还有&arr的地址,当我们运行时,我们会看到
输出的结果是一样的,但是它的本质上是一样的吗?
接下来将arr与&arr分别加1我们来看一下结果
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
printf("%p\n", arr);
printf("%p\n", &arr);
printf("arr+1 = %p", arr + 1);
printf("&arr+1 = %p", &arr + 1);
return 0;
}
当我们对arr和&arr分别+1时,我们看到了不同,006FF8F0与006FF914之间相差了36个字节,因此我么们不由的想到arr和&arr的值是一样的,但是其本质的意义有所不同。本质上&arr表示的是数组的地址,当我们对&arr+1时,我们跳过了arr整个数组,arr数组里面存储了10个int类型的元素,因此&arr+1我们这个操作跳过了40个字节,&arr的类型是int(*)[10],是一种数组指针类型。
3.数组指针的使用
我们了解到了数组指针,但是数组指针具体有什么用处呢?
#include<stdio.h>
void print(int(*arr)[5], int row, int col)
{
for (int i = 0; i < row; i++)
{
for (int 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(arr, 3, 5);
return 0;
}
我们创建一个二维数组,写一个打印函数print 将二维数组打印出来
我们在传参时将二维数组arr传进去,当我们接收时可以用一个数组指针来接受,我们知道二维数组其实在存储是还是以一维数组的方式存储的(应该可以这样子理解);
只是我们理解为一个二维数组,但是编译器在处理时还是当作一个一维数组处理的,编译器是认为这样子的
数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,因此可以用数组指针来接收。
四、 数组传参、指针参数
1、一维数组传参
include<stdio.h>
int main()
{
int arr1[10] = { 0 };
int* arr2[20] = { 0 };
test1(arr1);
test2(arr2);
return 0;
}
当我们创建两个数组arr1 arr2 我们要将这两个数组当作实参传入到函数test1与test2中,这个我们应该怎样来写test1,2的形参呢?
void teat1(int arr[])
{}
void test1(int arr[10])
{}
void test1(int* arr)
{}
当test1时,我们用一个整型数组来接受,是没有任何问题的,当然这里我们可以不写数组的大小,因为在数组传参时我们本质上传入的是首元素的地址,在传参的时候并不会创建一个新的数组,因此不写大小,也可以直接遍历。当然我们也可以用一个指针的形式来接收
void test2(int* arr[20])
{}
void test2(int** arr)
{}
当test2时,我们用一个int*类型的数组来接收,也可以用一个指针变量来接收
2、二维数组传参
void test(int (*arr)[5])
{}
int main()
{
int arr[3][5] = { 0 };
test(arr);
return 0;
}
在二维数组传参时,我们可以用数组指针的形参来接收,但是这里需要注意的是,二维数组传参,函数形参的设计只能省略第一个[]中的数字,也就是只能省略行,但不能省略列,因为并不知道一行中有多少个元素,但是对于行,当我们对数组指针进行解引用时,我们对其加1,数组指针加1跳过的是一个数组,因此就可以找到第二行的首元素的地址,这样子我们就可以找到每一行的每一个元素,也就可以找到每一个元素,因此行可以省略,但是列不能省略。
3、一级指针传参与二级指针传参
这里的指针传参比较简单,当一级指针传参时,我们用一个一级指针变量来接收就好了,同理,二级指针用二级指针变量来接收就可以了
五、函数指针
函数指针,顾名思义,函数指针就是指向函数的指针。
#include<stdio.h>
void test()
{
printf("hello bit");
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
return 0;
}
上面一串代码,我们分别打印test的地址和&test的地址
我么看到,打印出来的结果竟然是一样的,那是不是和上面说的arr和&arr类似,只是地址一样,但是本质上不一样呢?
其实test==&test,他们两个在本质上是一样的(这也太神奇了,hahaha)。
那么这里我们应该将函数的地址保存起来,应该怎样保存呢?这时我们应该用到函数指针,函数指针应该是什么样子的?
首先创建一个变量p我们再将指针变量与*结合起来,表示p是一个指针,那么然后指针指向的是一个函数,指向的函数无参数(*p)(),函数的返回值类型为void因此应该为void(*p)();
例如
int ADD(int x,int y)
{
return x+y;
}
这个函数的指针应该为int (*ADD)(int x,int y)
六、函数的指针数组
将函数的地址存放在一个数组中,那么这个数组就叫做函数的指针数组。
如 int (*parr[10]) ();
parr首先和[10]结合,表示parr是一个数组,里面存放的是函数指针,而函数指针的类型是int (*) ();那么函数的指针数组有自己的用途:转移表
假设我们现在要做一个简易的加减乘除计算器,
#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("*******************\n");
}
int main()
{
int x, y;
int input = 0;
int ret = 0;
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = Add(x, y);
printf("ret = %d", ret);
break;
case 2:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = Sub(x, y);
printf("ret = %d", ret);
break;
case 3:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = Mul(x, y);
printf("ret = %d", ret);
break;
case 4:
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
int ret = Div(x, y);
printf("ret = %d", ret);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("输入错误\n");
}
} while (input);
return 0;
}
这个写法比较冗余;当我们以后要添加新的算法的时候,那就要写更多的case语句,但是,当我们使用函数指针数组后,就可以简化很多了:
#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("*******************\n");
}
int main()
{
int x, y;
int input = 0;
int red = 0;
int(*p[5])(int x, int y) = { 0,Add,Sub,Mul,Div };
while (input)
{
menu();
printf("请选择\n");
scanf("%d", &input);
if (input >= 1 && input <= 4)
{
printf("请输入两个操作数\n");
scanf("%d %d", &x, &y);
red = (*p[input])(x, y);
}
else
{
printf("输入有误\n");
printf("red = %d", red);
}
}
return 0;
}
这样子看起来,当我们用转移表的方式写起来这个功能,这样子代码量就少了很多,但是这里也有自己的不足,因为在函数指针数组中,只能存放相同类型的函数指针,假设当我们的要运算的数有三个数的时候,那么这个方法便不可取了;
七、函数的指针数组的指针
函数的指针数组的指针,就是一个数组里面存放的是函数的指针,这个数组的指针就叫做函数指针数组的指针,那么这样的话,我们的这个就有点套娃了,但是我们先在这里套一下(哈哈哈)
看上面的代码我们将其拿下来
#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;
}
int main()
{
void(*p[4])(int x,int y) = {Add,Sub,Mul,Div};
return 0;
}
void(*p[4])(int x,int y) = {Add,Sub,Mul,Div};就是一个数组,里面放的四个函数指针,我们取到这个数组的地址,这个地址也被称为函数指针数组的指针,
首先我们创建一个指针变量pp因为是一个数组,我们首先要和[]结合,因此是(pp[ ]),表示是一个数组,因为我们需要的是一个指针,(*(pp[ ])),它里面是函数的指针,这个数组里面有四个元素,因此最终创建为
viod(*(pp[4]))(int x,int y);
这里确实有点套娃的感觉,这样子的话,还会有函数指针数组的指针的数组了,又会有函数指针数组的指针的数组的指针了,那就巴拉巴拉没完没了了。
八、回调函数
回调函数就是通过一个函数指调用的函数,如果我们将函数的指针(也就是地址)作为一个参数传递给另外一个函数,当这个指针被用来调用其他所指向的函数时,我们就说这是回调函数。回调函数不是由这个函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行相应
这里我们写一个test函数;
void test()
{
printf("hehe\n");
}
void print(void(*p)())
{
if (1)
{
p();
}
}
int main()
{
print(test);
return 0;
}
我们在这里这个test函数是可以直接调用的,我们将test函数作为一个参数传递给print函数,这时候print函数就应该来接受这个函数,当我们给一个条件,我们的P就相当于这个test函数,我们的程序结果运行起来
我们就能打印出hehe,这个print函数就被称为回调函数,但是这个回调函数有什么用呢?
我们看函数的指针数组的第一串代码,那里还是比较冗余,我们可以这样子处理,我们写一个回调函数,当我们case1 的时候,我们将Add函数当作参数传进去,case2的时候,我们将Sub函数传进去同理我们就得到了这样子的
#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("*******************\n");
}
void call_back(void (*p)(int,int))
{
int x, y;
int ret = 0;
printf("请输入两个操作数\n");
scanf("%d %d",&x,&y);
ret = p(x,y);
printf("ret = %d",ret);
}
int main()
{
int input = 0;
int ret = 0;
do
{
menu();
printf("请选择\n");
scanf("%d", &input);
switch (input)
{
case 1:
call_back(Add);
break;
case 2:
call_back(Sub);
break;
case 3:
call_back(Mul);
break;
case 4:
call_back(Div);
break;
case 0:
printf("退出程序\n");
break;
default:
printf("输入错误\n");
}
} while (input);
return 0;
}
这样子的话我们的代码就会减少很多,更加的容易理解了。