1. 引言
今天对 C 语言中的指针内容进行了进一步的学习,从基础概念到实际应用,逐步深入理解了指针的本质与用法。本文记录今天的学习心得与关键知识点,帮助自己巩固记忆,也希望能给同样在学习 C 语言指针的读者一些参考。
2. 指针的基本概念回顾
指针是 C 语言的灵魂,它存储的是变量的内存地址。通过指针,我们可以间接访问和操作内存中的数据。
int a = 10;
int *p = &a; // p 指向变量 a 的地址
printf("%d\\n", *p); // 通过解引用访问 a 的值,输出 10
核心要点:
- & 取地址运算符,获取变量的内存地址
- * 解引用运算符,通过地址访问对应内存中的值
- 指针变量本身也占用内存,存储的是地址值
3. 指针与数组的关系
4. 指针操作整型一维数组
今天重点练习了用指针操作整型一维数组,这是指针最常用的实战场景之一。下面通过几个典型例子来加深理解。
4.1 用指针遍历数组
#include <stdio.h>
int main() {
int arr[5] = {10, 20, 30, 40, 50};
int *p = arr; // 指向首元素
// 方式一:下标访问
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\\n", i, arr[i]);
}
// 方式二:指针偏移访问
for (int i = 0; i < 5; i++) {
printf("*(p + %d) = %d\\n", i, *(p + i));
}
// 方式三:指针自增遍历
for (int *q = arr; q < arr + 5; q++) {
printf("%d ", *q);
}
printf("\\n");
return 0;
}
三种方式结果完全一致,方式三最能体现指针的“移动”特性。
4.2 指针作为函数参数操作数组
把数组传给函数时,实际传递的是指向首元素的指针,函数内可以通过指针修改原数组。
#include <stdio.h>
// 用指针遍历并打印数组
void print_array(int *p, int n) {
for (int i = 0; i < n; i++) {
printf("%d ", *(p + i));
}
printf("\\n");
}
// 用指针把数组每个元素翻倍
void double_array(int *p, int n) {
for (int i = 0; i < n; i++) {
*(p + i) *= 2;
}
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
print_array(arr, 5); // 输出 1 2 3 4 5
double_array(arr, 5); // 通过指针修改原数组
print_array(arr, 5); // 输出 2 4 6 8 10
return 0;
}
要点:
- 形参 int *p 与 int p[] 在函数参数中是等价的
- 通过指针修改的是原数组,调用方无需返回值即可看到变化
- 函数内无法直接得知数组长度,需要额外传入元素个数 n
4.3 指针与数组名的区别
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
// 数组名是常量地址,不能重新赋值
// arr = p; // 错误!数组名不是左值
// 指针变量可以重新指向
p = arr + 2; // 合法,p 现在指向 arr[2]
printf("%d\\n", *p); // 输出 3
关键区别:
- 数组名是常量指针,不能改变指向
- 指针变量可以随时改变指向
- sizeof(arr) 得到整个数组的字节数,而 sizeof(p) 只得到指针本身的大小
今天重点学习了指针与数组的紧密联系。数组名在大多数表达式中会退化为指向首元素的指针。
int arr[5] = {1, 2, 3, 4, 5};
int *p = arr; // 等价于 int *p = &arr[0]
// 以下三种写法等价
printf("%d\\n", arr[2]);
printf("%d\\n", *(arr + 2));
printf("%d\\n", *(p + 2));
指针算术的要点:
- p + 1 不是简单地地址加 1,而是加上 sizeof(int) 个字节
- 指针的加减运算以所指向类型的大小为单位
- 数组越界访问是未定义行为,使用指针时要格外小心
5. 指针与函数
今天还学习了指针作为函数参数的用法,这是实现函数修改外部变量的关键手段。
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 3, y = 5;
swap(&x, &y); // 传地址,函数内修改生效
printf("x=%d, y=%d\\n", x, y); // 输出 x=5, y=3
return 0;
}
理解要点:
- C 语言函数参数默认是值传递
- 传入指针后,函数内通过解引用可以修改调用方的变量
- 这是 C 语言实现“引用传递”效果的标准方式
今天还学习了 const 在函数中的用法,它主要用于保护数据不被意外修改,是编写安全代码的重要手段。
#include <stdio.h>
// 用 const 修饰形参,表示函数内不能通过该指针修改所指向的数据
void print_array(const int *p, int n) {
for (int i = 0; i < n; i++) {
printf("%d ", *(p + i));
}
printf("\\n");
}
int main() {
int arr[5] = {1, 2, 3, 4, 5};
print_array(arr, 5); // 只读访问,不会修改数组
return 0;
}
const 修饰函数形参的要点:
- const int *p 表示指针指向的数据是只读的,函数内不能通过 *p 修改它
- 这样既能把数组传给函数,又能防止函数意外改动原数据,提高代码安全性
- 如果函数确实需要修改数据,就不加 const,如前面的 double_array 例子
5. 指针与字符串
今天还学习了用指针表示字符串的方式,这是 C 语言中非常常见的用法。字符串在 C 语言中本质上是字符数组,因此指针操作字符串与操作数组的思路一脉相承。
5.1 用字符指针指向字符串常量
#include <stdio.h>
int main() {
char *str = "Hello, C!"; // str 指向字符串常量首字符
printf("%s\\n", str); // 输出 Hello, C!
return 0;
}
要点:
- 字符串常量存储在只读区,char *str 只是保存了首字符的地址
- 通过 %s 打印时,从首地址开始逐个输出,直到遇到 '\\0' 结束符
- 这种方式不能修改字符串内容,因为指向的是只读的字符串常量
5.2 用字符数组与指针遍历字符串
#include <stdio.h>
int main() {
char str[] = "Hello"; // 字符数组,内容可修改
char *p = str; // p 指向数组首元素
// 用指针逐个访问字符
while (*p != '\\0') {
printf("%c", *p);
p++;
}
printf("\\n");
return 0;
}
要点:
- 字符数组 str[] 在栈上分配内存,内容可以修改
- 指针 p 从首字符开始,通过自增逐个移动,直到遇到 '\\0' 结束
- 这种方式与之前遍历整型数组的思路完全一致,只是结束条件变成了判断结束符
5.3 字符指针与字符数组的区别
char str[] = "Hello"; // 字符数组:内容可修改
char *p = "Hello"; // 字符指针:指向字符串常量,内容只读
str[0] = 'h'; // 合法,数组内容可修改
// p[0] = 'h'; // 错误!p 指向只读的字符串常量,修改会导致未定义行为
关键区别:
- 字符数组在内存中分配了实际空间,可以修改其中的字符
- 字符指针只是保存了字符串常量的地址,指向的内容是只读的
- 用 sizeof(str) 得到整个数组的大小(含 '\\0'),而 sizeof(p) 只得到指针本身的大小
6. 今日学习总结
今天对指针的学习收获颇丰,从基础概念到数组、函数,逐步建立了对指针的整体认识。指针虽然初学时容易困惑,但理解了“地址”与“解引用”的本质后,很多问题都能迎刃而解。
后续还需要继续深入学习的方面:
- 多级指针(指针的指针)
- 函数指针
- 指针与结构体的结合使用
- 指针与动态内存分配
- 常见指针错误的排查与调试
7. 参考资料
- 《C Primer Plus》指针相关章节
- C 语言标准文档中关于指针的说明
- 各类 C 语言在线教程与练习平台




