C语言指针详解:从入门到精通
目录
1. 指针的基本概念
什么是指针?
指针是一个变量,其值为另一个变量的内存地址。
核心概念:
- 每个变量都有内存地址
- 指针存储的是地址,而不是值
- 通过指针可以间接访问和修改变量的值
指针是编程中的一个核心概念,特别是在底层语言如 C 或 C++ 中。它本质上是一个变量,但其值不是直接的数据,而是另一个变量在内存中的地址。这意味着指针“指向”内存中的某个位置,从而允许间接访问或操作该位置存储的数据。
核心特点
基本定义:指针是一个变量,其值为另一个变量的内存地址。例如,如果有一个变量 int x = 10;,那么一个指针 int *p; 可以存储 x 的地址,如 p = &x;。这里,&x 表示 x 的地址值(如
0
x
7
f
f
e
e
000
0x7ffee000
0x7ffee000)。
作用:指针提供了一种高效的内存管理方式,常用于动态内存分配、数组操作、函数参数传递等场景。它减少了数据复制开销,但使用不当可能导致错误。
解引用:通过指针访问实际数据时,需要使用解引用运算符(如 *p)。例如,如果 p 指向 x,那么 *p = 20; 会将 x 的值改为 20。
#include <stdio.h>
int main() {
int var = 42;
printf("变量var的值: %d\n", var);
printf("变量var的地址: %p\n", &var);
return 0;
}
输出:
变量var的值: 42
变量var的地址: 0x7ffd4f4a5a4c
2. 指针的声明与初始化
2.1 指针声明
#include <stdio.h>
int main() {
int num = 100;
float f = 3.14;
char c = 'A';
// 指针声明
int *int_ptr; // 指向整型的指针
float *float_ptr; // 指向浮点型的指针
char *char_ptr; // 指向字符型的指针
// 指针初始化
int_ptr = #
float_ptr = &f;
char_ptr = &c;
printf("变量值: num=%d, f=%.2f, c=%c\n", num, f, c);
printf("变量地址: &num=%p, &f=%p, &c=%p\n", &num, &f, &c);
printf("指针值: int_ptr=%p, float_ptr=%p, char_ptr=%p\n",
int_ptr, float_ptr, char_ptr);
return 0;
}
输出:
变量值: num=100, f=3.14, c=A
变量地址: &num=0x7ffd4f4a5a4c, &f=0x7ffd4f4a5a50, &c=0x7ffd4f4a5a4b
指针值: int_ptr=0x7ffd4f4a5a4c, float_ptr=0x7ffd4f4a5a50, char_ptr=0x7ffd4f4a5a4b
2.2 空指针和野指针
#include <stdio.h>
int main() {
// 空指针
int *null_ptr = NULL;
// 未初始化的指针(野指针)- 危险!
int *wild_ptr;
// 正确的指针初始化
int value = 50;
int *valid_ptr = &value;
printf("空指针: %p\n", null_ptr);
printf("未初始化指针: %p\n", wild_ptr); // 随机值
printf("有效指针: %p\n", valid_ptr);
// 检查空指针
if(null_ptr == NULL) {
printf("这是一个空指针\n");
}
if(valid_ptr != NULL) {
printf("有效指针指向的值: %d\n", *valid_ptr);
}
return 0;
}
输出:
空指针: (nil)
未初始化指针: 0x7ffd4f4a5a60
有效指针: 0x7ffd4f4a5a4c
这是一个空指针
有效指针指向的值: 50
3. 指针运算符
3.1 取地址(&)和解引用(*)
#include <stdio.h>
int main() {
int number = 123;
int *ptr = &number;
printf("基本概念演示:\n");
printf("变量number的值: %d\n", number);
printf("变量number的地址: %p\n", &number);
printf("指针ptr的值: %p\n", ptr);
printf("指针ptr自己的地址: %p\n", &ptr);
printf("通过ptr访问number的值: %d\n", *ptr);
// 通过指针修改变量值
printf("\n通过指针修改值:\n");
*ptr = 456;
printf("修改后number的值: %d\n", number);
printf("通过ptr访问修改后的值: %d\n", *ptr);
return 0;
}
输出:
基本概念演示:
变量number的值: 123
变量number的地址: 0x7ffd4f4a5a4c
指针ptr的值: 0x7ffd4f4a5a4c
指针ptr自己的地址: 0x7ffd4f4a5a50
通过ptr访问number的值: 123
通过指针修改值:
修改后number的值: 456
通过ptr访问修改后的值: 456
3.2 指针算术运算
#include <stdio.h>
int main() {
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 指向数组首元素
printf("数组元素: ");
for(int i = 0; i < 5; i++) {
printf("%d ", arr[i]);
}
printf("\n\n");
printf("指针算术运算演示:\n");
printf("ptr = %p, *ptr = %d\n", ptr, *ptr);
ptr++; // 移动到下一个整数
printf("ptr++后: ptr = %p, *ptr = %d\n", ptr, *ptr);
ptr += 2; // 向后移动两个整数
printf("ptr+=2后: ptr = %p, *ptr = %d\n", ptr, *ptr);
ptr--; // 向前移动一个整数
printf("ptr--后: ptr = %p, *ptr = %d\n", ptr, *ptr);
// 指针相减
int *ptr1 = &arr[1];
int *ptr2 = &arr[4];
printf("\n指针相减:\n");
printf("ptr2 - ptr1 = %ld (元素个数)\n", ptr2 - ptr1);
return 0;
}
输出:
数组元素: 10 20 30 40 50
指针算术运算演示:
ptr = 0x7ffd4f4a5a50, *ptr = 10
ptr++后: ptr = 0x7ffd4f4a5a54, *ptr = 20
ptr+=2后: ptr = 0x7ffd4f4a5a5c, *ptr = 40
ptr--后: ptr = 0x7ffd4f4a5a58, *ptr = 30
指针相减:
ptr2 - ptr1 = 3 (元素个数)
4. 指针与数组
4.1 数组名作为指针
#include <stdio.h>
int main() {
int numbers[] = {1, 2, 3, 4, 5};
printf("数组名就是指针的证明:\n");
printf("numbers = %p\n", numbers);
printf("&numbers[0] = %p\n", &numbers[0]);
printf("两者相等: %s\n", numbers == &numbers[0] ? "是" : "否");
printf("\n通过不同方式访问数组:\n");
for(int i = 0; i < 5; i++) {
printf("numbers[%d] = %d, *(numbers + %d) = %d\n",
i, numbers[i], i, *(numbers + i));
}
printf("\n指针遍历数组:\n");
int *ptr = numbers;
for(int i = 0; i < 5; i++) {
printf("ptr[%d] = %d, *ptr = %d\n", i, ptr[i], *ptr);
ptr++; // 移动到下一个元素
}
return 0;
}
输出:
数组名就是指针的证明:
numbers = 0x7ffd4f4a5a50
&numbers[0] = 0x7ffd4f4a5a50
两者相等: 是
通过不同方式访问数组:
numbers[0] = 1, *(numbers + 0) = 1
numbers[1] = 2, *(numbers + 1) = 2
numbers[2] = 3, *(numbers + 2) = 3
numbers[3] = 4, *(numbers + 3) = 4
numbers[4] = 5, *(numbers + 4) = 5
指针遍历数组:
ptr[0] = 1, *ptr = 1
ptr[1] = 2, *ptr = 2
ptr[2] = 3, *ptr = 3
ptr[3] = 4, *ptr = 4
ptr[4] = 5, *ptr = 5
4.2 指针数组 vs 数组指针
#include <stdio.h>
int main() {
int a = 10, b = 20, c = 30;
// 指针数组:存储指针的数组
int *ptr_array[3] = {&a, &b, &c};
// 数组指针:指向数组的指针
int arr[3] = {100, 200, 300};
int (*array_ptr)[3] = &arr;
printf("指针数组演示:\n");
for(int i = 0; i < 3; i++) {
printf("ptr_array[%d] = %p, *ptr_array[%d] = %d\n",
i, ptr_array[i], i, *ptr_array[i]);
}
printf("\n数组指针演示:\n");
printf("array_ptr = %p\n", array_ptr);
printf("*array_ptr = %p\n", *array_ptr);
printf("(*array_ptr)[0] = %d\n", (*array_ptr)[0]);
printf("(*array_ptr)[1] = %d\n", (*array_ptr)[1]);
// 通过数组指针访问元素
printf("\n通过数组指针遍历:\n");
for(int i = 0; i < 3; i++) {
printf("(*array_ptr)[%d] = %d\n", i, (*array_ptr)[i]);
}
return 0;
}
输出:
指针数组演示:
ptr_array[0] = 0x7ffd4f4a5a4c, *ptr_array[0] = 10
ptr_array[1] = 0x7ffd4f4a5a50, *ptr_array[1] = 20
ptr_array[2] = 0x7ffd4f4a5a54, *ptr_array[2] = 30
数组指针演示:
array_ptr = 0x7ffd4f4a5a58
*array_ptr = 0x7ffd4f4a5a58
(*array_ptr)[0] = 100
(*array_ptr)[1] = 200
通过数组指针遍历:
(*array_ptr)[0] = 100
(*array_ptr)[1] = 200
(*array_ptr)[2] = 300
5. 指针与函数
5.1 指针作为函数参数
#include <stdio.h>
// 传值调用
void swap_by_value(int a, int b) {
int temp = a;
a = b;
b = temp;
printf("函数内交换: a=%d, b=%d\n", a, b);
}
// 传址调用
void swap_by_reference(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
printf("函数内交换: *a=%d, *b=%d\n", *a, *b);
}
// 通过指针返回多个值
void calculate(int a, int b, int *sum, int *product) {
*sum = a + b;
*product = a * b;
}
int main() {
int x = 5, y = 10;
printf("传值调用演示:\n");
printf("交换前: x=%d, y=%d\n", x, y);
swap_by_value(x, y);
printf("交换后: x=%d, y=%d\n", x, y);
printf("\n传址调用演示:\n");
printf("交换前: x=%d, y=%d\n", x, y);
swap_by_reference(&x, &y);
printf("交换后: x=%d, y=%d\n", x, y);
printf("\n返回多个值演示:\n");
int s, p;
calculate(4, 5, &s, &p);
printf("和: %d, 积: %d\n", s, p);
return 0;
}
输出:
传值调用演示:
交换前: x=5, y=10
函数内交换: a=10, b=5
交换后: x=5, y=10
传址调用演示:
交换前: x=5, y=10
函数内交换: *a=10, *b=5
交换后: x=10, y=5
返回多个值演示:
和: 9, 积: 20
5.2 返回指针的函数
#include <stdio.h>
// 返回较大值的指针
int* find_max(int *a, int *b) {
return (*a > *b) ? a : b;
}
// 危险:返回局部变量的地址
int* dangerous_function() {
int local_var = 100;
return &local_var; // 错误!局部变量在函数结束后销毁
}
// 安全:返回静态变量的地址
int* safe_function() {
static int static_var = 200;
return &static_var; // 正确:静态变量在程序结束时才销毁
}
int main() {
int num1 = 25, num2 = 40;
printf("返回较大值的指针:\n");
int *max_ptr = find_max(&num1, &num2);
printf("较大的值是: %d\n", *max_ptr);
printf("\n安全返回指针:\n");
int *safe_ptr = safe_function();
printf("安全返回的值: %d\n", *safe_ptr);
// 危险示例(注释掉以避免未定义行为)
// int *danger_ptr = dangerous_function();
// printf("危险返回的值: %d\n", *danger_ptr); // 未定义行为
return 0;
}
输出:
返回较大值的指针:
较大的值是: 40
安全返回指针:
安全返回的值: 200
6. 多级指针
6.1 二级指针
#include <stdio.h>
int main() {
int value = 100;
int *ptr = &value;
int **pptr = &ptr; // 指向指针的指针
printf("多级指针演示:\n");
printf("value = %d, 地址 = %p\n", value, &value);
printf("ptr = %p, *ptr = %d, 地址 = %p\n", ptr, *ptr, &ptr);
printf("pptr = %p, *pptr = %p, **pptr = %d\n", pptr, *pptr, **pptr);
// 通过二级指针修改变量值
printf("\n通过二级指针修改值:\n");
**pptr = 200;
printf("修改后 value = %d\n", value);
printf("**pptr = %d\n", **pptr);
// 二级指针的实际应用
printf("\n二级指针应用:\n");
char *names[] = {"Alice", "Bob", "Charlie"};
char **name_ptr = names;
for(int i = 0; i < 3; i++) {
printf("names[%d] = %s\n", i, names[i]);
printf("*(name_ptr + %d) = %s\n", i, *(name_ptr + i));
}
return 0;
}
输出:
多级指针演示:
value = 100, 地址 = 0x7ffd4f4a5a4c
ptr = 0x7ffd4f4a5a4c, *ptr = 100, 地址 = 0x7ffd4f4a5a50
pptr = 0x7ffd4f4a5a50, *pptr = 0x7ffd4f4a5a4c, **pptr = 100
通过二级指针修改值:
修改后 value = 200
**pptr = 200
二级指针应用:
names[0] = Alice
*(name_ptr + 0) = Alice
names[1] = Bob
*(name_ptr + 1) = Bob
names[2] = Charlie
*(name_ptr + 2) = Charlie
7. 动态内存分配
7.1 malloc, calloc, realloc, free
#include <stdio.h>
#include <stdlib.h>
int main() {
int size;
printf("请输入数组大小: ");
scanf("%d", &size);
// 使用malloc分配内存(未初始化)
int *arr1 = (int*)malloc(size * sizeof(int));
if(arr1 == NULL) {
printf("内存分配失败!\n");
return 1;
}
// 使用calloc分配内存(初始化为0)
int *arr2 = (int*)calloc(size, sizeof(int));
if(arr2 == NULL) {
printf("内存分配失败!\n");
free(arr1);
return 1;
}
printf("malloc分配的内容(未初始化): ");
for(int i = 0; i < size; i++) {
printf("%d ", arr1[i]); // 可能是随机值
}
printf("\n");
printf("calloc分配的内容(初始化为0): ");
for(int i = 0; i < size; i++) {
printf("%d ", arr2[i]);
}
printf("\n");
// 初始化arr1
for(int i = 0; i < size; i++) {
arr1[i] = (i + 1) * 10;
}
printf("初始化后arr1: ");
for(int i = 0; i < size; i++) {
printf("%d ", arr1[i]);
}
printf("\n");
// 重新调整arr1的大小
int new_size;
printf("请输入新的数组大小: ");
scanf("%d", &new_size);
int *temp = (int*)realloc(arr1, new_size * sizeof(int));
if(temp == NULL) {
printf("内存重新分配失败!\n");
} else {
arr1 = temp;
// 初始化新增的元素
for(int i = size; i < new_size; i++) {
arr1[i] = (i + 1) * 5;
}
printf("调整大小后arr1: ");
for(int i = 0; i < new_size; i++) {
printf("%d ", arr1[i]);
}
printf("\n");
}
// 释放内存
free(arr1);
free(arr2);
printf("内存已释放\n");
return 0;
}
输出示例:
请输入数组大小: 3
malloc分配的内容(未初始化): 0 0 0
calloc分配的内容(初始化为0): 0 0 0
初始化后arr1: 10 20 30
请输入新的数组大小: 5
调整大小后arr1: 10 20 30 20 25
内存已释放
8. 函数指针
8.1 函数指针的基本使用
#include <stdio.h>
// 简单的数学函数
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return b != 0 ? a / b : 0; }
int main() {
// 声明函数指针
int (*operation)(int, int);
int x = 10, y = 5;
printf("函数指针演示:\n");
// 指向add函数
operation = add;
printf("%d + %d = %d\n", x, y, operation(x, y));
// 指向subtract函数
operation = subtract;
printf("%d - %d = %d\n", x, y, operation(x, y));
// 指向multiply函数
operation = multiply;
printf("%d * %d = %d\n", x, y, operation(x, y));
// 指向divide函数
operation = divide;
printf("%d / %d = %d\n", x, y, operation(x, y));
return 0;
}
输出:
函数指针演示:
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50
10 / 5 = 2
8.2 函数指针数组
#include <stdio.h>
// 数学运算函数
double add(double a, double b) { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b) { return b != 0 ? a / b : 0; }
int main() {
// 函数指针数组
double (*operations[4])(double, double) = {
add, subtract, multiply, divide
};
char *operation_names[] = {"加法", "减法", "乘法", "除法"};
double a = 12.0, b = 4.0;
printf("函数指针数组演示:\n");
for(int i = 0; i < 4; i++) {
double result = operations[i](a, b);
printf("%s: %.1f %c %.1f = %.1f\n",
operation_names[i], a,
(i == 0 ? '+' : (i == 1 ? '-' : (i == 2 ? '*' : '/'))),
b, result);
}
// 使用函数指针作为回调
printf("\n回调函数演示:\n");
void calculator(double x, double y, double (*op)(double, double)) {
double result = op(x, y);
printf("计算结果: %.1f\n", result);
}
calculator(15.0, 3.0, multiply);
calculator(15.0, 3.0, divide);
return 0;
}
输出:
函数指针数组演示:
加法: 12.0 + 4.0 = 16.0
减法: 12.0 - 4.0 = 8.0
乘法: 12.0 * 4.0 = 48.0
除法: 12.0 / 4.0 = 3.0
回调函数演示:
计算结果: 45.0
计算结果: 5.0
9. 高级指针应用
9.1 常量指针 vs 指针常量
#include <stdio.h>
int main() {
int a = 10, b = 20;
printf("常量指针 vs 指针常量:\n\n");
// 常量指针:指向常量的指针
// 指针可以改变,指向的值不能改变
const int *ptr1 = &a;
printf("常量指针:\n");
printf("ptr1指向: %d\n", *ptr1);
// *ptr1 = 30; // 错误:不能修改指向的值
ptr1 = &b; // 正确:可以改变指针的指向
printf("ptr1现在指向: %d\n", *ptr1);
// 指针常量:指针本身是常量
// 指向的值可以改变,指针不能改变
int *const ptr2 = &a;
printf("\n指针常量:\n");
printf("ptr2指向: %d\n", *ptr2);
*ptr2 = 30; // 正确:可以修改指向的值
printf("修改后ptr2指向: %d\n", *ptr2);
// ptr2 = &b; // 错误:不能改变指针的指向
// 指向常量的指针常量
const int *const ptr3 = &a;
printf("\n指向常量的指针常量:\n");
printf("ptr3指向: %d\n", *ptr3);
// *ptr3 = 40; // 错误:不能修改指向的值
// ptr3 = &b; // 错误:不能改变指针的指向
return 0;
}
输出:
常量指针 vs 指针常量:
常量指针:
ptr1指向: 10
ptr1现在指向: 20
指针常量:
ptr2指向: 10
修改后ptr2指向: 30
指向常量的指针常量:
ptr3指向: 30
9.2 复杂指针声明解析
#include <stdio.h>
int main() {
int arr[3] = {1, 2, 3};
int *ptr_arr[3]; // 指针数组
int (*array_ptr)[3]; // 数组指针
// 初始化指针数组
for(int i = 0; i < 3; i++) {
ptr_arr[i] = &arr[i];
}
array_ptr = &arr;
printf("复杂指针声明解析:\n\n");
printf("int *ptr_arr[3] - 指针数组:\n");
for(int i = 0; i < 3; i++) {
printf("ptr_arr[%d] = %p, *ptr_arr[%d] = %d\n",
i, ptr_arr[i], i, *ptr_arr[i]);
}
printf("\nint (*array_ptr)[3] - 数组指针:\n");
printf("array_ptr = %p\n", array_ptr);
printf("*array_ptr = %p\n", *array_ptr);
for(int i = 0; i < 3; i++) {
printf("(*array_ptr)[%d] = %d\n", i, (*array_ptr)[i]);
}
// 函数指针的复杂声明
printf("\n复杂函数指针:\n");
int (*func_ptr)(int, int); // 函数指针
int *(*func_ptr_arr[3])(int, int); // 函数指针数组
printf("int (*func_ptr)(int, int) - 函数指针\n");
printf("int *(*func_ptr_arr[3])(int, int) - 返回int指针的函数指针数组\n");
return 0;
}
输出:
复杂指针声明解析:
int *ptr_arr[3] - 指针数组:
ptr_arr[0] = 0x7ffd4f4a5a4c, *ptr_arr[0] = 1
ptr_arr[1] = 0x7ffd4f4a5a50, *ptr_arr[1] = 2
ptr_arr[2] = 0x7ffd4f4a5a54, *ptr_arr[2] = 3
int (*array_ptr)[3] - 数组指针:
array_ptr = 0x7ffd4f4a5a4c
*array_ptr = 0x7ffd4f4a5a4c
(*array_ptr)[0] = 1
(*array_ptr)[1] = 2
(*array_ptr)[2] = 3
复杂函数指针:
int (*func_ptr)(int, int) - 函数指针
int *(*func_ptr_arr[3])(int, int) - 返回int指针的函数指针数组
9.3 实际应用:通用排序函数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 通用比较函数类型
typedef int (*CompareFunction)(const void*, const void*);
// 整数比较函数
int compare_int(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
// 字符串比较函数
int compare_string(const void *a, const void *b) {
return strcmp(*(const char**)a, *(const char**)b);
}
// 通用排序函数
void generic_sort(void *array, size_t count, size_t size, CompareFunction compare) {
// 简单的冒泡排序实现
for(size_t i = 0; i < count - 1; i++) {
for(size_t j = 0; j < count - i - 1; j++) {
void *elem1 = (char*)array + j * size;
void *elem2 = (char*)array + (j + 1) * size;
if(compare(elem1, elem2) > 0) {
// 交换元素
char temp[size];
memcpy(temp, elem1, size);
memcpy(elem1, elem2, size);
memcpy(elem2, temp, size);
}
}
}
}
int main() {
printf("通用排序函数演示:\n\n");
// 整数数组排序
int numbers[] = {64, 34, 25, 12, 22, 11, 90};
int num_count = sizeof(numbers) / sizeof(numbers[0]);
printf("排序前整数数组: ");
for(int i = 0; i < num_count; i++) {
printf("%d ", numbers[i]);
}
printf("\n");
generic_sort(numbers, num_count, sizeof(int), compare_int);
printf("排序后整数数组: ");
for(int i = 0; i < num_count; i++) {
printf("%d ", numbers[i]);
}
printf("\n\n");
// 字符串数组排序
char *names[] = {"Charlie", "Alice", "Bob", "David"};
int name_count = sizeof(names) / sizeof(names[0]);
printf("排序前字符串数组: ");
for(int i = 0; i < name_count; i++) {
printf("%s ", names[i]);
}
printf("\n");
generic_sort(names, name_count, sizeof(char*), compare_string);
printf("排序后字符串数组: ");
for(int i = 0; i < name_count; i++) {
printf("%s ", names[i]);
}
printf("\n");
return 0;
}
输出:
通用排序函数演示:
排序前整数数组: 64 34 25 12 22 11 90
排序后整数数组: 11 12 22 25 34 64 90
排序前字符串数组: Charlie Alice Bob David
排序后字符串数组: Alice Bob Charlie David
总结
关键知识点回顾:
- 指针基础:存储地址的变量,&取地址,*解引用
- 指针运算:算术运算、比较运算
- 指针与数组:数组名即指针,指针数组,数组指针
- 函数指针:指向函数的指针,支持回调
- 多级指针:指向指针的指针
- 动态内存:malloc/calloc/realloc/free
- 常量指针:const的不同位置含义不同
最佳实践:
- 总是初始化指针
- 检查malloc/calloc的返回值
- 释放内存后立即设为NULL
- 理解const的正确使用
- 避免指针越界访问
- 使用函数指针实现多态
常见错误:
- 使用未初始化的指针
- 访问已释放的内存
- 内存泄漏
- 数组越界
- 返回局部变量的地址
指针堪称C语言的核心精髓,能否驾驭指针将决定你能否成为真正的C语言高手。只要坚持实战演练,你就能灵活运用指针攻克各类编程难题!
5万+





