C语言指针
指针是C语言操作内存的核心机制,本质是存储内存地址的变量,通过
&获取地址、*解引用访问值,支撑数组遍历(如*(arr+i)等价于arr[i])、函数传参(实现值修改)、动态内存分配(malloc/free)及链表/树等数据结构。关键用法包括指针运算(加减步长由类型决定)、函数指针回调(如排序算法通用化)、二级指针(管理指针数组)。使用时必须避免野指针(初始化为NULL)、指针越界(边界检查)和内存泄漏(分配与释放匹配),C++中则通过智能指针(unique_ptr/shared_ptr)提升安全性。掌握指针是精通C语言的基石,其高效性与灵活性使其成为底层开发的灵魂。
一、指针的基本概念
指针是C语言的核心特性之一,它本质上是一个变量,用于存储内存地址。指针赋予程序直接操作内存的强大能力,是实现高效数据传递、复杂数据结构(链表、树)的核心基础。
通俗比喻:
- 内存 = 快递仓库(每个存储单元有唯一编号,即地址)
- 变量 = 仓库里的包裹(存储具体数据)
- 指针 = 快递单(上面写着包裹的具体地址)
- 解引用(*)= 快递员按地址找到包裹并打开
二、指针的定义与初始化
1. 指针的定义
数据类型 *指针变量名;
示例:
int *p_int; // 指向整型的指针
char *p_char; // 指向字符的指针
float *p_float; // 指向浮点型的指针
2. 指针的初始化
int a = 10;
int *p = &a; // 初始化指针p,指向a的地址
三、指针的关键操作符
1. 取地址操作符 &
获取变量的内存地址。
int a = 10;
int *p = &a; // p存储a的地址
2. 解引用操作符 *
通过指针访问或修改指针指向的内存中的值。
int a = 10;
int *p = &a;
printf("%d", *p); // 输出:10
*p = 20; // 修改a的值为20
四、指针的类型与内存访问
指针类型决定了指针运算的步长和内存访问的粒度:
| 指针类型 | 每次指针加1的步长 | 示例 |
|---|---|---|
| int* | 4字节(32位系统) | p+1 地址增加4 |
| char* | 1字节 | p+1 地址增加1 |
| float* | 4字节 | p+1 地址增加4 |
| double* | 8字节 | p+1 地址增加8 |
#include <stdio.h>
int main() {
int a = 10;
char b = 'A';
int *int_ptr = &a;
char *char_ptr = &b;
printf("int_ptr初始地址: %p\n", int_ptr);
printf("int_ptr加1后的地址: %p\n", int_ptr + 1);
printf("char_ptr初始地址: %p\n", char_ptr);
printf("char_ptr加1后的地址: %p\n", char_ptr + 1);
return 0;
}
五、指针的运算
1. 指针加减整数
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // p指向数组第一个元素
printf("arr[0]: %d\n", *p); // 1
printf("arr[1]: %d\n", *(p + 1)); // 2
printf("arr[2]: %d\n", *(p + 2)); // 3
return 0;
}
2. 指针减指针
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int *p2 = &arr[3];
printf("p2 - p1 = %ld\n", p2 - p1); // 3,表示p2和p1之间有3个元素
return 0;
}
3. 指针的关系运算
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = &arr[0];
int *p2 = &arr[3];
if (p1 < p2) {
printf("p1指向的地址小于p2指向的地址\n");
}
if (p1 != p2) {
printf("p1和p2指向不同的地址\n");
}
if (p1 == NULL) {
printf("p1是空指针\n");
}
return 0;
}
六、指针与数组
1. 数组名与指针的关系
数组名在大多数情况下可以视为指向数组首元素的指针。
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
// 数组名和指针的等价性
printf("arr[0]: %d\n", arr[0]); // 1
printf("*(arr): %d\n", *(arr)); // 1
printf("arr[1]: %d\n", arr[1]); // 2
printf("*(arr + 1): %d\n", *(arr + 1)); // 2
return 0;
}
2. 使用指针遍历数组
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
int size = sizeof(arr) / sizeof(arr[0]);
// 使用指针遍历数组
for (int i = 0; i < size; i++) {
printf("arr[%d]: %d\n", i, *(p + i));
}
return 0;
}
3. 二维数组与指针
#include <stdio.h>
int main() {
int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
// 二维数组的指针表示
printf("arr[0][0]: %d\n", arr[0][0]); // 1
printf("*(arr[0]): %d\n", *(arr[0])); // 1
printf("*(arr[0] + 1): %d\n", *(arr[0] + 1)); // 2
printf("*(arr[1] + 2): %d\n", *(arr[1] + 2)); // 6
// 通过指针访问二维数组
int (*p)[3] = arr; // 指向包含3个整数的数组的指针
printf("p[0][0]: %d\n", p[0][0]); // 1
printf("p[1][2]: %d\n", p[1][2]); // 6
return 0;
}
七、指针与字符串
1. 字符串的指针表示
字符串在C语言中是以字符数组的形式存储的,以'\0'结尾。
#include <stdio.h>
int main() {
char str[] = "Hello";
char *p = str;
// 通过指针访问字符串
printf("字符串: %s\n", p); // Hello
printf("字符: %c\n", *p); // H
printf("字符: %c\n", *(p + 1)); // e
// 遍历字符串
for (int i = 0; i < 5; i++) {
printf("%c", *(p + i));
}
printf("\n");
return 0;
}
2. 字符串处理函数的指针实现
#include <stdio.h>
int strlen_custom(char *s) {
int count = 0;
while (*s != '\0') {
count++;
s++;
}
return count;
}
int main() {
char str[] = "Hello";
printf("字符串长度: %d\n", strlen_custom(str)); // 5
return 0;
}
八、指针与结构体
1. 结构体指针
#include <stdio.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student stu = {"Alice", 20, 95.5};
struct Student *p = &stu;
// 通过指针访问结构体成员
printf("姓名: %s\n", p->name); // Alice
printf("年龄: %d\n", p->age); // 20
printf("分数: %.1f\n", p->score); // 95.5
// 通过结构体指针修改成员
p->age = 21;
printf("修改后的年龄: %d\n", p->age); // 21
return 0;
}
九、函数指针
1. 函数指针的定义与使用
#include <stdio.h>
// 函数声明
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
// 函数指针类型定义
typedef int (*Operation)(int, int);
int main() {
// 函数指针初始化
Operation op = add;
// 通过函数指针调用函数
printf("10 + 5 = %d\n", op(10, 5)); // 15
op = subtract;
printf("10 - 5 = %d\n", op(10, 5)); // 5
return 0;
}
2. 回调函数
#include <stdio.h>
// 回调函数
void callback(int value) {
printf("回调函数被调用,值: %d\n", value);
}
// 接收回调函数的函数
void process(int value, void (*callback)(int)) {
printf("处理值: %d\n", value);
if (callback != NULL) {
callback(value);
}
}
int main() {
process(10, callback); // 调用回调函数
return 0;
}
3. 函数指针在排序中的应用
#include <stdio.h>
// 比较函数
int ascending(int a, int b) {
return a - b;
}
int descending(int a, int b) {
return b - a;
}
// 通用排序函数
void sort(int arr[], int len, int (*cmp)(int, int)) {
for (int i = 0; i < len - 1; i++) {
for (int j = 0; j < len - i - 1; j++) {
if (cmp(arr[j], arr[j + 1]) > 0) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
int main() {
int arr[5] = {5, 3, 1, 4, 2};
// 升序排序
sort(arr, 5, ascending);
printf("升序排序: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
// 降序排序
sort(arr, 5, descending);
printf("降序排序: ");
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
十、指针的常见错误与解决方法
1. 野指针
问题:指针未初始化或指向已释放的内存。
解决方法:初始化指针为NULL。
#include <stdio.h>
int main() {
int *p = NULL; // 正确:初始化为NULL
if (p != NULL) {
*p = 10; // 不会执行
}
return 0;
}
2. 指针越界
问题:访问指针指向范围之外的内存。
解决方法:始终检查指针访问范围。
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("%d ", *(p + i)); // 安全访问
}
return 0;
}
3. 释放错误
问题:使用free释放非原始指针地址。
解决方法:始终使用原始指针地址释放。
#include <stdio.h>
#include <stdlib.h>
int main() {
char *p = (char *)malloc(100);
// ... 使用p ...
free(p); // 正确:释放原始地址
return 0;
}
十一、指针的高级用法
1. 指针数组
#include <stdio.h>
int main() {
char *names[] = {"Alice", "Bob", "Charlie"};
for (int i = 0; i < 3; i++) {
printf("Name %d: %s\n", i, names[i]);
}
return 0;
}
2. 二级指针
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
int **pp = &p;
printf("a的值: %d\n", a); // 10
printf("p指向的值: %d\n", *p); // 10
printf("pp指向的值: %d\n", **pp); // 10
return 0;
}
3. 动态内存分配
#include <stdio.h>
#include <stdlib.h>
int main() {
int *arr = (int *)malloc(5 * sizeof(int));
if (arr == NULL) {
printf("内存分配失败\n");
return 1;
}
// 初始化数组
for (int i = 0; i < 5; i++) {
arr[i] = i + 1;
}
// 打印数组
for (int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n");
free(arr); // 释放内存
return 0;
}
4. 链表实现
#include <stdio.h>
#include <stdlib.h>
// 链表节点结构
typedef struct Node {
int data;
struct Node *next;
} Node;
// 创建新节点
Node *create_node(int data) {
Node *new_node = (Node *)malloc(sizeof(Node));
if (new_node == NULL) {
printf("内存分配失败\n");
exit(1);
}
new_node->data = data;
new_node->next = NULL;
return new_node;
}
// 在链表末尾添加节点
void append(Node **head, int data) {
Node *new_node = create_node(data);
if (*head == NULL) {
*head = new_node;
return;
}
Node *current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = new_node;
}
// 打印链表
void print_list(Node *head) {
Node *current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
int main() {
Node *head = NULL;
append(&head, 1);
append(&head, 2);
append(&head, 3);
print_list(head); // 输出: 1 2 3
return 0;
}
十二、C++中的智能指针(补充)
在C++中,原始指针会导致许多严重的编程错误,因此推荐使用智能指针:
1. unique_ptr
#include <memory>
#include <iostream>
int main() {
// 创建unique_ptr实例
std::unique_ptr<int> p1(new int(42));
// 访问值
std::cout << *p1 << std::endl; // 42
// 不能复制unique_ptr
// std::unique_ptr<int> p2 = p1; // 错误
return 0;
}
2. shared_ptr
#include <memory>
#include <iostream>
int main() {
// 创建shared_ptr实例
std::shared_ptr<int> p1(new int(42));
std::shared_ptr<int> p2 = p1;
// 两个指针都指向同一个对象
std::cout << *p1 << std::endl; // 42
std::cout << *p2 << std::endl; // 42
// 释放对象时,引用计数为0才会自动释放
p1.reset();
std::cout << *p2 << std::endl; // 42
return 0;
}
十三、指针使用场景总结
| 使用场景 | 适用指针类型 | 代码示例 | 优势 |
|---|---|---|---|
| 函数传参 | 普通指针 | void swap(int *a, int *b) | 实现"引用传递",修改实参值 |
| 数组操作 | 指针 | int *p = arr; | 高效遍历和访问数组元素 |
| 链表实现 | 结构体指针 | struct Node *next; | 实现动态数据结构 |
| 字符串处理 | 字符指针 | char *str = "Hello"; | 简洁处理字符串 |
| 函数回调 | 函数指针 | void (*callback)(int) | 实现灵活的回调机制 |
| 动态内存分配 | 普通指针 | int *arr = (int *)malloc(n * sizeof(int)); | 灵活管理内存 |
| 二维数组操作 | 数组指针 | int (*p)[3] = arr; | 便捷操作多维数组 |
| 排序算法 | 函数指针 | void sort(int arr[], int len, int (*cmp)(int, int)) | 实现通用排序算法 |
十四、指针使用最佳实践
-
始终初始化指针:避免野指针
int *p = NULL; -
使用typedef简化指针类型:
typedef int *IntPtr; IntPtr p; -
使用const保护指针:
const int *p; // 指向常量的指针 int *const p; // 常量指针 const int *const p; // 常量指针指向常量 -
避免指针越界:始终检查访问范围
for (int i = 0; i < size; i++) { // 安全访问 } -
正确管理动态内存:分配与释放匹配
void *p = malloc(size); // ... free(p); -
使用智能指针(C++):避免内存泄漏
std::unique_ptr<int> p(new int(10));
通过掌握这些指针知识和使用技巧,您将能够更高效、安全地使用C语言处理各种内存和数据结构操作。指针是C语言的灵魂,掌握它将使您在C语言编程中如虎添翼。
- 感谢你赐予我前进的力量

