C语言之初阶指针
目录
- 前言
- 1. 什么是指针
- 代码展示
- 2. 计算机如何编址
- 3. 指针类型
- 指针类型的定义
- 指针类型的意义
- 4. 野指针
- 4.1. **指针未初始化**
- 4.2. **指针越界访问**
- 4.3. **指针指向的空间被释放**
- 5. 指针运算
- 6. 指针和数组
- 7. 二级指针
- 8. 指针数组
- 总结
前言
本篇将深入探讨C语言中至关重要的知识点——指针。鉴于指针内容的丰富性,我将其分为初阶和进阶两部分。初阶部分适合刚刚接触C语言的同学,许多人在学习指针时感到困惑,希望通过我的讲解,能让你们真正理解并掌握指针。现在,让我们立即开始这段指针之旅。
1. 什么是指针
在计算机科学中,指针(Pointer)是编程语言中的一个对象,它存储的是另一个变量的内存地址。通过这个地址,指针可以直接指向存储器中另一个地方的值。由于通过地址可以找到所需的变量单元,因此地址形象地被称为“指针”。换句话说,指针是通过它所存储的地址来找到对应的内存单元。
我们可以将指针类比为家的地址。如果想回家,就必须通过地址找到家的位置。指针就是存储地址的变量,通过指针可以访问对应的位置。
代码展示
#include <stdio.h>
int main()
{
int a = 10; // 在内存中开辟一块空间
int *p = &a; // 这里我们对变量a,取出它的地址,可以使用&操作符。
// 将a的地址存放在p变量中,p就是一个指针变量。
return 0;
}
总结:指针就是存储地址的变量。
2. 计算机如何编址
计算机如何为内存中的每个单元分配地址?一个单元的大小是多少?
经过仔细的计算和权衡,我们发现一个字节对应一个地址是最合适的。对于32位的机器,假设有32根地址线,每根地址线在寻址时产生一个电信号(正电/负电,即1或0),就会产生2的32次方个地址。
因此,在32位机器上,地址由32个比特位组成,用4个字节就能表示,所以一个指针占4个字节。对于64位机器,指针则占8个字节。
3. 指针类型
虽然指针的大小在32位机器上是4字节,在64位机器上是8字节,但指针并不是只有一种类型。为什么指针大小已经确定还要分整形指针、字符指针等数据类型呢?指针类型的意义又是什么呢?接下来我们用32位的机器来进行讲解。
指针类型的定义
int num = 10;
p = #
要将 &num
(num
的地址)保存到 p
中,我们知道 p
就是一个指针变量,那它的类型是怎样的呢?我们给指针变量相应的类型。
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的定义方式是:type + *
。例如,char*
类型的指针是为了存放 char
类型变量的地址,int*
类型的指针是为了存放 int
类型变量的地址。
指针类型的意义
指针±整数
结果显示,int
类型的指针加1移动了4个字节,而 char
型指针移动了1个字节。
总结:指针类型决定了指针向前或向后走一步的大小。
指针的解引用
我们对比这两张图可以发现,char
类型的指针在访问变量 n
时只改变了一个字节的值,而 int
类型指针改变了 n
的4个字节。
总结:指针类型决定了对指针解引用的权限(能操作几个字节)。
4. 野指针
概念:野指针是指指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。
野指针成因
4.1. 指针未初始化
#include <stdio.h>
int main()
{
int *p; // 局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
4.2. 指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
// 当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
4.3. 指针指向的空间被释放
这里a变量在text函数调用完就被释放了,指针pa得到了他的地址,但a的空间已经还给操作系统,所以pa就变成了野指针。
如何规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放后及时置
NULL
- 指针使用之前检查有效性
5. 指针运算
- 指针±整数
- 指针-指针
- 指针的关系运算
指针±整数
#define N_VALUES 5
float values[N_VALUES];
float *vp;
// 指针±整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
解释:
vp
指向了values
数组的第一个元素,vp
小于values
数组的最后一个元素后面的地址,vp
就等于0,再加1。
指针-指针
指针-指针的运算,只能是指向同一块空间的指针才能计算。计算结果为指针之间元素的个数。
指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
6. 指针和数组
指针和数组是两个不同的对象。
- 指针是一个变量,用于存放地址的变量,大小为4或8个字节。
- 数组是存放相同数据类型的集合,数组的大小由元素个数和元素类型决定。
数组名是数组首元素地址,地址可以放在指针变量里,通过指针来访问数组。
扩展:
7. 二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?这就是二级指针。
int a = 10;
int *pa = &a;
int **ppa = &pa;
也可以这样理解,pa
前的 *
是说 pa
是指针变量,指向的数据类型为 int
,对于 ppa
来说,*
指的是 ppa
是指针变量,指向的数据类型是 int*
。
对于二级指针的运算有:
int b = 20;
*ppa = &b; // 等价于 pa = &b;
**ppa = 30; // 等价于 *pa = 30; 等价于 a = 30;
8. 指针数组
指针数组是指针还是数组?答案:是数组。是存放指针的数组。
int* arr[5]; // arr3是一个数组,有五个元素,每个元素是一个整形指针。
总结
指针是C语言中一个强大而灵活的工具,掌握指针的基本概念和用法对于理解C语言的内存管理和高级编程技巧至关重要。通过本文的介绍,你应该对指针的定义、声明、初始化、解引用、运算、与数组的关系、与函数的关系、空指针以及常见错误有了初步的了解。希望这些知识能够帮助你在C语言编程中更好地使用指针。