什么是指针?

​ 在计算机中内存是以字节为单位的连续编址空间,每一个字节单元对应着一个独一的编号,这个编号被称为内存单元的地址。

在特定的CPU架构下,内存地址的大小是固定的。

32位平台下地址为32个bit,即4个字节;

64位平台下地址为64个bit,即8个字节。

地址和指针:系统的内存好比带编号的小房间,地址对应着内存中的每个字节的编号,而指针也就是内存地址,它描述了数据在内存中的位置。

指针变量:指针变量本身不存储实际数据,而存储数据在内存中的地址,口语中的指针通常指的是指针变量。

指针的作用

使程序简洁、紧凑、高效、有效表示复杂的数据结构、动态分配内存、能直接访问内存、方便处理字符串、得到多于一个的函数返回值

image-20240715234825643

指针变量的一般形式

格式:**<数据类型> * <指针变量名>;,如:int * p;**

数据类型要和这个地址中保存的数据的数据类型要保持一致其中 “ * ” 表示声明该变量是一个指针变量

指针变量的初始化可以是:已定义变量的地址、已初始化的同类型指针变量、NULL(空指针)。

注意:指针的使用一定要初始化,否则指针的地址值是不确定的(造成野指针),会造成非法访问等异常问题。

指针变量的赋值和引用

指针变量和普通变量一样,使用之前不仅需要声明,而且需要赋值(必须是地址常量或指针变量),未经赋值的指针变量不能使用。

在给指针变量赋值只能赋予地址,不能赋予任何其他数据,否则会引起错误。如:int *p = 2;(这样赋值是错误的)

(1) 定义指针变量,同时进行赋值

1
2
int a;			// 声明变量a,开辟a变量的地址空间
int *p = &a; // 声明指针变量p,存储a变量的地址

(2) 定义指针变量后再赋值

1
2
3
int a;
int *p;
p = &a; // 取a的地址赋值给指针变量p

指针的目标:指针指向的内存区域中的数据称为指针的目标

(3) 指针变量的引用

格式:* 指针变量,含义是引用指针变量所指向目标的值

注意:定义变量时的 “ * “ 和引用时的 “ * “ 不同,定义时表示是个指针,在非定义时使用是解引用

什么是野指针?

野指针是指指针指向的位置是不可知的,所以当我们使用解引用去访问野指针时可能会产生不可知的结果。

野指针的危害

  1. 非法访问内存发生段错误,异常退出;
  2. 程序错误被掩盖,指向一个可用但无意义的空间;
  3. 程序出现离奇错误,指向可用空间,但该空间已被其他变量使用,造成程序崩溃或数据被损坏;

如何避免野指针

  1. 定义指针时应该初始化为NULL;
  2. 在使用指针之前,赋值绑定一个可用的地址空间;
  3. 在指针解引用之前,先判断指针是否为NULL;
  4. 小心指针越界;
  5. 指针使用完将其赋值为空;
  6. 避免返回局部变量的地址;

指针的基本运算符

运算符 名称 作用 运算符类型 结合性
& 取地址 获得变量的地址 单目运算符 自右向左
* 指针 返回指定地址内的变量值 单目运算符 自右向左

指针的算术运算

指针的运算是以指针所存的地址作为运算量而进行的,实质上就是地址的计算。

运算符 计算形式 意义
+ px + n 指针地址大的方向移动n个数据
- px - n 指针地址小的方向移动n个数据
++ px++ 指针地址大的方向移动1个数据
px– 指针地址小的方向移动n个数据
- px - py 两个指针直接相隔数据元素的个数

指针的关系运算(指向的地址位置之间的关系)

运算符 说明 例子
> 大于 px > py
< 小于 px < py
>= 大于等于 px >= py
<= 小于等于 px <= py
!= 不等于 px != py
== 等于 px == py

注意:不同类型的指针不能进行比较

小端序和大端序

小端序:数据低位字节保存在内存的低地址,高位字节保存在内存的高地址

大端序:数据低位字节保存在内存的高地址,高位字节保存在内存的低地址

内存地址 小端序 大端序
0x7ffffcf75c68 78 12
0x7ffffcf75c69 56 34
0x7ffffcf75c6a 34 56
0x7ffffcf75c6b 12 78

数组指针和指针数组

数组指针(行指针)

存储行地址的指针变量,叫做行指针变量。形式如下:**<数据类型> (变量名)[大小];*

1
2
// 如这行语句定义了一个指向包含5个整型数组的指针
int (*p)[5];

指向数组的指针,其本质是指针,可赋值一维数组名取地址或二维数组名。

image-20240718114229593

指针数组

由若干个相同数据类型的指针变量构成的集合,其形式为:**<数据类型> 变量名[大小];*

是一维数组,常结合二维数组使用,存储每行首个元素的地址,其本质是数组,存储内容为指针

1
2
// 如这行语句定义了数组p,其存储的数据类型为int *类型
int *p[5];

image-20240718123333180

数组指针是指针数组在内存中的起始地址,数组名代表数组指针的起始地址

指针与数组的使用事项:因为指针变量和数组名都是地址量,一定条件下其使用的方法具有相同形式。

含义不同:指针代表存储地址的变量,而数组名代表一个数组

使用不同:指针是变量,可以存储其他地址值,也可以递增,数组名是地址常量,不能修改

长度不同:数组名的长度是整个数组的总空间、指针变量长度取决于操作系统4字节(32),8字节(64)。

本质不同:指针变量是地址变量,数组名是地址常量

const和指针

在C语言中,const可以使变量常量化,即被它修饰的变量的值是不可修改的

1
2
3
// 以下两种写法都是合法的,其中a的值为不可修改的
const int a = 100;
int const a = 100;

当使用const修饰指针时,可进行以下定义:

  • 常量化指针目标:该指针是可以修改的,即指针可以改变指向,但不可以通过该指针对其指向的目标进行修改
1
const int * p;
  • 常量化指针变量:该指针是不可以修改的,即指针不可以改变指向,但可以通过该指针对其指向的目标进行修改。
1
int * const p;
  • 常量化指针变量和指针目标:该指针既不可以修改指向,同时也不能修改该指针指向的目标值
1
const int * const p;

void指针

void指针是不确定数据类型的指针变量,它可以通过强制转换让该变量指向任何数据类型的变量或数组

1
2
3
4
5
6
7
int a = 100;
// 如定义一个void指针变量s
void * s = NULL;
// 在开发中,如未确定指针变量,可使用void指针变量,在使用时进行强制类型转换
s = (void *)a;
// 在对指针进行解引用时,需要将其强制转换
printf("%d\n", *(int *)s);

在没有进行强制类型转换之前,void型指针不能进行任何指针的算术运算,因为void *相当于类型不确定,此时若不进行强制转换的话,我们只知道该指针的首地址,而不知道其占用的字节数,因此无法知道该取多少或者偏移多少。

字符串指针

字符串指针就是存储字符变量的地址,在C语言中,通常借助字符数组来存储字符串,字符指针可以存储字符串的起始地址。

在使用字符指针指向字符数组时:

虽然数组名表示数组的首地址,但由于数组名是指针常量,其值是不能改变的,可通过指针移动来访问和修改。

使用scanf时如果指针变量不是字符数组的首地址,则需要使用取地址符&。

注意:当一个字符指针初始化为一个字符常量时,不能对字符指针变量的目标进行赋值。

1
char *s = "Hello World!";   // 这是定义字符串常量,其目标值不可修改,相当于省略了const