目录
在C语言中,指针是一个非常重要的概念,它不仅为程序员提供了直接访问和操作内存的能力,还是实现数据结构、函数参数传递、动态内存管理等高级功能的基础。
一、指针的基本概念
1.1 什么是指针?
在计算机科学中,指针是编程语言中的一个对象,它利用地址来直接指向存储在电脑存储器中另一个地方的值。由于通过地址能找到所需的变量单元,因此将地址形象化为“指针”。在C语言中,每个内存单元都有一个唯一的编号,这个编号被称为地址,也就是指针。指针变量是用来存放地址的变量。
1.2 指针的创建
在C语言中,可以通过取地址运算符 &
来获取变量的地址,并将该地址赋值给指针变量。例如:
int a = 10; | |
int *p = &a; // p是指针变量,存储了变量a的地址 |
1.3 指针的大小
指针的大小在32位平台是4个字节,在64位平台是8个字节。这个大小并不会因为指针所指向的数据类型而改变。
二、指针类型
2.1 指针类型的解引用
指针的类型决定了在解引用指针时能访问的字节数。例如,char*
类型的指针解引用只能访问一个字节,而int*
类型的指针解引用能访问四个字节。
#include <stdio.h>
int main() {
int a = 10;
int *p = &a; // p 指向 a 的地址
printf("a 的值是: %d\n", a);
printf("p 指向的值是: %d\n", *p); // 通过指针 p 访问 a 的值
*p = 20; // 通过指针 p 修改 a 的值 解引用
printf("修改后,a 的值是: %d\n", a);
return 0;
}
三、野指针
3.1 野指针的成因
- 指针未初始化:如果指针变量在使用前没有被赋予有效的地址,它将指向一个随机的内存地址,这就是野指针。
- 指针越界访问:在数组操作中,如果指针指向了数组范围之外的地址,也会成为野指针。
- 指针指向的空间释放:如果指针指向的内存空间被释放(如通过
free
函数),而指针未被置为NULL
,再次使用该指针时就会访问无效的内存。
3.2 如何规避野指针
- 指针初始化:使用指针前要确保它被正确初始化。
- 注意指针越界:在遍历数组等操作中,要确保指针在合法范围内。
- 及时置为NULL:如果指针指向的内存空间被释放,应立即将指针置为
NULL
。 - 避免返回局部变量的地址:函数内部创建的局部变量在函数返回后会被销毁,返回其地址将导致野指针问题。
- 检查指针有效性:在使用指针前,检查其是否为
NULL
。
#include <stdio.h>
#include <stdlib.h>
int main() {
int *p = malloc(sizeof(int)); // 动态分配内存
if (p != NULL) {
*p = 10;
printf("p 指向的值是: %d\n", *p);
free(p); // 释放内存
p = NULL; // 避免野指针
}
// 注意:这里不能访问 *p,因为它已经是 NULL 了
return 0;
}
四、常量指针与指针常量
4.1 常量指针
常量指针指的是指针指向的值不能被修改,但指针本身可以指向其他地址。声明方式如const int *p
或 int const *p
。
4.2 指针常量
指针常量指的是指针本身的值(即地址)不能被修改,但指针指向的值可以被修改。声明方式如int *const p
。
#include <stdio.h>
int main() {
int a = 10;
const int *cp = &a; // 常量指针,不能通过 cp 修改 a 的值
// *cp = 20; // 这将导致编译错误
int b = 20;
int *const pc = &b; // 指针常量,pc 本身的值(即地址)不能改变,但可以通过 pc 修改 b 的值
// pc = &a; // 这将导致编译错误
*pc = 30; // 合法操作
printf("a 的值是: %d\n", a);
printf("b 的值是: %d\n", b);
return 0;
}
五、指针的运算
5.1指针的加减运算
指针类型还决定了指针进行加减运算时,地址变化的量。例如,int*
类型的指针加1,地址增加4个字节(int占用4个字节);而char*
类型的指针加1,地址只增加1个字节。
5.2 指针+-整数
可以通过给指针加上或减去一个整数来移动指针,使其指向新的地址。这种运算常用于遍历数组或字符串。
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr;
printf("p 指向的值是: %d\n", *p); // 10
printf("p+1 指向的值是: %d\n", *(p+1)); // 20
// 指针减法
int diff = (p + 4) - p; // 计算 p+4 和 p 之间的元素个数
printf("p 和 p+4 之间有 %d 个元素\n", diff); // 输出 4
return 0;
}
5.3 指针-指针
两个指针相减,得到的是它们之间元素的个数(基于指针的类型)。这种运算常用于计算数组长度或两个元素之间的距离。
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptr1 = arr; // ptr1 指向数组的第一个元素
int *ptr2 = arr + 3; // ptr2 指向数组的第四个元素(即值为4的那个元素)
// 计算ptr2和ptr1之间相隔的元素数量
// 注意:这里的差值是基于ptr1和ptr2所指向的数据类型的大小来计算的
// 在这个例子中,因为ptr1和ptr2都是指向int类型的指针,所以差值为1意味着相差4个字节(假设int占用4个字节)
// 但当我们进行ptr2 - ptr1的运算时,得到的是它们之间相隔的元素数量,而不是字节数
int diff = ptr2 - ptr1;
printf("ptr2 和 ptr1 之间相隔了 %d 个元素\n", diff); // 输出:ptr2 和 ptr1 之间相隔了 3 个元素
return 0;
}
5.4 指针的关系运算
指针之间可以进行关系运算(如<
、>
、==
等),但需要注意,这种运算通常只在指向同一数组的元素时才有意义。
六、指针与数组
6.1 数组名与指针
数组名在大多数情况下代表数组首元素的地址,因此可以作为指针使用。但数组名不是指针变量,其类型是指向数组元素类型的指针。
6.2 指针与数组的操作
通过指针可以方便地遍历和修改数组中的元素。例如,使用指针进行数组的初始化、赋值、打印等操作。
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *p = arr; // p 指向数组的首元素
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 使用指针 p 遍历数组
}
printf("\n");
// 或者使用指针算术
for (p = arr; p < arr + 5; p++) {
printf("%d ", *p);
}
return 0;
}