内存与地址
在指针之前,我们应先理解内存与地址的概念。CPU在处理数据时需从内存中读取数据,经处理后再放回内存中。但往往内存中含有大量的数据,并且需要处理的也只是其中的一部分,那么计算机又该如何高效地管理内存呢?此时便不得不提到内存单元这一概念。就拿学生宿舍来举例,一片内存就相当于一栋宿舍楼,如果朋友要来找你玩,你仅仅告诉你在这栋楼,那他只能一间一间的找,直到找到你(当然这是不可能的),所以你还会告诉所在的宿舍编号,而这每个宿舍便相当于前面提到内存单元。宿舍编号就是内存单元的编号。计算机通过内存单元编号便可高效地处理数据,内存单元编号也被称之为地址,同时也就是今天提到的指针。
指针
在学习指针的使用之前我们需要先认识两个与指针息息相关的操作符:&(取地址操作符)与*(解引用操作符),正如其名,通过使用&a(a为变量)便可取出a的地址,而有时我们还需要将取出的地址储存起来,示例如下:
int main()
{
int a = 1;
int* pa = &a;
return 0;
}
通过上述的代码,便将a的地址存放到了变量p当中,而p就称之为指针变量,int*则是p的类型。存放地址后,我们肯定还会再使用这个指针变量,此时就需要用到解引用操作符,示例如下:
int main()
{
int b = 2;
int* pb = &b;
*pb = 0;
return 0;
}
这样我们就将pb指向的b变量改为了0,而*pb的意思便是通过pb中存放的地址找到了指向的b变量。
指针是有许多种类,其中包括整型指针、数组指针、函数指针等。
整型指针与字符指针
整型指针便是指向整型变量的指针,其示例便如前面所示。
字符指针也就是指向字符变量的指针,其示例如下:
char* p = "abc";
而p指向的是首字符a的地址。
数组指针
数组指针便是指向数组的指针,其示例如下:
int arr[6];
int (*p_arr)[6] = &arr;
此处的p_arr便是该数组指针的变量名,int (*)[6]是p_ar的类型。
此外,我们还需提到一个与数组指针名字相似,但完全不同的东西,那便是指针数组。指针数组本质上是数组,只不过数组中存放的是指针,示例如下:
int* arr[6] = {};
这里的arr数组中就可以存放int*的指针变量。
函数指针
函数指针则是指向函数的指针,示例如下:
int (*pf)(char*) = test;
其中pf是函数指针的变量名,test则是pf指向的函数的函数名,int为该test函数的函数返回值,char*则是test函数的参数的类型,其中**&+函数名与函数名**均为函数地址。
此外我们还可以将函数指针存放到数组中也就是函数指针数组,示例如下:
int (*pf_arr[])() = {};
其中大括号中放的是函数名,通过函数指针数组可以适用与代码冗杂的情况。
野指针
除了上述的指针之外,还存在一个危险的指针变量,那便是野指针,即指针指向的位置是不可知的,其形成的原因有以下几种:
1、指针未初始化
int main()
{
int* p;
*p = 5;
return 0;
}
2、指针越界访问
#include <stdio.h>
int main()
{
int arr[6] = { 0 };
int *p = arr;
for (int i = 0; i < 7; i ++)
{
*(p + i) = i; }
return 0;
}
3、指针指向的空间释放
#include <stdio.h>
int* test()
{
int n = 100;
return &n;
}
int main()
{
int*p = test();
printf("%d\n", *p);
return 0;
}
因此在指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性,从而避免野指针。对于指针有效性的检查可以使用assert函数进行断言,此函数需要包含assert头文件:
#include <stdio.h>
#include <assert.h>
int main()
{
int* p = NULL;
assert(p != NULL);
return 0;
}
当p不是空指针时,程序继续运行;否则程序在assert处终止运行,并给出报错信息的提示。当确认程序没有问题不在需要断言后,还可以在assert头文件前定义一个宏:NDEBUG。
二级指针
指针变量可以存放地址,而指针变量也存在地址,因此我们便可以使用二级指针来访问并使用指针变量的地址,即使用&取一级指针的地址,示例如下:
int main()
{
int a = 1;
int* pa = &a;
int** p = &pa;
return 0;
}
指针的运用
指针指向变量打印
对于打印整型指针指向的变量的示例如下:
#include <stdio.h>
int main()
{
int a = 1;
int* pa = &a;
printf("%d\n", *p);
return 0;
}
指针的加减
对于指针加减整型:指针的类型决定了指针在进行解引用操作时访问的空间大小,同时也决定了指针的步长,即向前或向后走的距离大小。type* p ,p+i便相当于跳过i个type类型的字节 。
而指针减去指针的绝对值便是两者之间的元素个数。
const
有时我们希望定义的变量不可被修改,此时,我们便可以用到const关键字,示例如下:
int a = 10;
const int* const p = &a;
*前的const可以使a变量无法被修改,而 * 后的const则使指针变量p指向的地址无法被修改。但在C语言中变量a在被const修饰后依然为变量。
回调函数
回调函数就是一个通过函数指针调用的函数,即将函数的指针作为参数传给另一个函数,当这个指针被用来调用其所指向的函数是,被调用的函数便被称之为回调函数,
示例如下:
#include <stdio.h>
int cam_int(int a, int b)
{
return a - b;
}
void test(int (*pf)(int, int))
{
int a = 3;
int b = 10;
int o = pf(a, b);
if (o > 0)
printf("a>b");
else if (o < 0)
printf("a<b");
else
printf("a=b");
}
int main()
{
test(cam_int);
return 0;
}