指针是C语言的灵魂,也是最让初学者感到困惑的概念之一。简单来说,指针就是存储内存地址的变量。 通过指针,我们可以直接访问和操作内存,实现高效的数组处理、动态内存分配、函数间数据共享等功能。 理解指针是深入掌握C语言的关键。本章将从最基础的概念开始,逐步揭开指针的神秘面纱。
{{-- 1. 内存地址与指针的概念 --}}计算机内存被划分为一个个字节,每个字节都有一个唯一的编号,称为内存地址。当我们定义一个变量时,系统会为其分配内存空间,变量名就是这块内存的别名。 指针变量专门用来存储这些内存地址。
内存地址 | 内容
1000 | 10 (变量 a)
1004 | 1000 (指针 p,存储了变量 a 的地址)
通过指针,我们可以间接访问和修改变量的值。
{{-- 2. 指针的声明与初始化 --}}指针变量的声明需要指定它所指向的数据类型,格式为:类型 *指针变量名;
int *p; // 声明一个指向整型的指针
double *q; // 指向双精度浮点数的指针
char *r; // 指向字符的指针
声明指针后,应将其初始化为某个变量的地址或 NULL(空指针),避免使用未初始化的指针。
int a = 10;
int *p = &a; // p 存储变量 a 的地址
int *q = NULL; // 初始化为空指针
int* p, q; 只声明了 p 是指针,q 是普通整型变量。建议每个指针变量单独声明,或使用 int *p, *q;。
&:取地址运算符,用于获取变量的内存地址。*:解引用运算符(间接访问运算符),用于访问指针所指向的变量。#include <stdio.h>
int main() {
int num = 42;
int *ptr = # // ptr 存储 num 的地址
printf("num 的值: %d\n", num);
printf("num 的地址: %p\n", &num);
printf("ptr 存储的地址: %p\n", ptr);
printf("ptr 指向的值: %d\n", *ptr); // 通过指针访问 num 的值
// 通过指针修改变量的值
*ptr = 100;
printf("修改后 num 的值: %d\n", num); // 输出 100
return 0;
}
{{-- 4. 指针的运算 --}}
指针可以进行加、减运算,其单位是所指向类型的大小。例如,int *p 加 1,地址增加 sizeof(int) 字节。
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *p = arr; // 指向数组第一个元素
printf("p 指向: %d\n", *p); // 10
p++; // 移动到下一个元素
printf("p++ 后指向: %d\n", *p); // 20
p += 2; // 移动两个元素
printf("p+=2 后指向: %d\n", *p); // 40
// 指针相减:得到元素个数差
int *q = &arr[4];
printf("q - p = %ld\n", q - p); // 输出 1(因为 p 指向 arr[3]? 实际上面 p 指向 arr[3](40),q 指向 arr[4](50))先确认位置
return 0;
}
在C语言中,数组名本质上是一个指向数组首元素的常量指针。因此,可以通过指针访问数组元素。
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 int *p = &arr[0];
// 使用指针遍历数组
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d, *(p+%d) = %d\n", i, arr[i], i, *(p + i));
}
// 下标运算符 [] 实际上是 *(arr + i) 的语法糖
printf("arr[2] = %d, *(arr+2) = %d\n", arr[2], *(arr + 2));
return 0;
}
arr[i] 等价于 *(arr + i),也等价于 *(i + arr),甚至 i[arr] 也是合法的(不推荐使用)。
由于C语言采用值传递,函数内部无法直接修改外部变量的值。通过传递指针,可以在函数内部修改外部变量,实现“传址调用”。
#include <stdio.h>
// 交换两个整数的值(正确版本)
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
int main() {
int a = 10, b = 20;
printf("交换前: a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("交换后: a=%d, b=%d\n", a, b);
return 0;
}
void bad_swap(int x, int y) { int t = x; x = y; y = t; } // 只交换了副本
字符串常量本质上是一个指向字符的指针,因此可以使用 char * 指向字符串。
#include <stdio.h>
int main() {
char *str = "Hello, World!"; // 指向字符串常量(只读)
printf("%s\n", str);
// 可修改的字符串应使用字符数组
char arr[] = "Hello";
arr[0] = 'h'; // 可以修改
printf("%s\n", arr);
// 遍历字符串
char *p = arr;
while (*p != '\0') {
putchar(*p);
p++;
}
putchar('\n');
return 0;
}
{{-- 8. 多级指针 --}}
指针变量本身也有地址,可以用另一个指针来存储,即指向指针的指针。
#include <stdio.h>
int main() {
int a = 10;
int *p = &a; // 一级指针
int **pp = &p; // 二级指针,存储 p 的地址
printf("a = %d\n", a);
printf("*p = %d\n", *p);
printf("**pp = %d\n", **pp); // 通过二级指针访问 a
return 0;
}
多级指针常用于函数中修改指针本身(如动态内存分配后返回指针)。
{{-- 9. const 与指针 --}}const 关键字可以限制指针所指向的内容或指针本身是否可修改。
int a = 10, b = 20;
const int *p1 = &a; // 指向常量的指针:不能通过 p1 修改指向的内容,但 p1 可以指向其他变量
// *p1 = 30; // 错误!
p1 = &b; // 正确
int * const p2 = &a; // 常量指针:p2 本身不可修改,但可通过 p2 修改指向的内容
*p2 = 30; // 正确
// p2 = &b; // 错误!
const int * const p3 = &a; // 两者都不可修改
{{-- 10. 常见错误与注意事项 --}}
int *p = NULL; *p = 10; 会导致段错误。int *p = &a; 给 char *q = p;)可能导致数据解释错误。scanf("%d", p); 如果 p 是普通变量,应写作 &p;如果 p 是指针,则直接写 p。#include <stdio.h>
// 交换两个整数
void swap(int *x, int *y) {
int temp = *x;
*x = *y;
*y = temp;
}
// 冒泡排序,使用指针操作数组
void bubbleSort(int *arr, int n) {
for (int i = 0; i < n - 1; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (*(arr + j) > *(arr + j + 1)) {
swap(arr + j, arr + j + 1);
}
}
}
}
int main() {
int nums[] = {64, 34, 25, 12, 22, 11, 90};
int size = sizeof(nums) / sizeof(nums[0]);
printf("排序前: ");
for (int i = 0; i < size; i++) {
printf("%d ", nums[i]);
}
printf("\n");
bubbleSort(nums, size);
printf("排序后: ");
for (int i = 0; i < size; i++) {
printf("%d ", nums[i]);
}
printf("\n");
return 0;
}
指针是C语言最强大的特性,也是许多高级数据结构(如链表、树)和动态内存管理的基础。掌握指针需要大量的实践,建议多动手编写代码,观察内存变化。 下一章我们将深入探讨指针与数组的关系,以及指针的高级应用。
{{-- 章节导航按钮 --}}