学习 C 语言绕不开指针,它也是很多初学者的难点。本篇结合实操代码、内存原理、易错点,从零带你吃透基础指针、指针运算、数组与指针联动,适合新手系统复习与收藏。
一、什么是指针 & 指针变量
1. 基础概念
内存中每一块空间都有地址,就像房间门牌号。
- 指针:本质就是内存地址。
- 指针变量:专门用来存放地址的变量。
定义格式:数据类型 * 变量名
int a = 10; int* p; // 定义int类型指针变量p p = &a; // & 取地址符:取出变量a的地址,存入指针p2. 两个核心操作符
&取地址符取出变量首字节地址,&变量名得到地址。*解引用操作符根据指针中存储的地址,找到对应内存空间,操作该空间的值。
示例演示:
#include<stdio.h> int main() { int c = 5; int* p = &c; printf("%d\n", *p); // 通过地址取值,输出 5 *p = 3; // 通过地址修改值 printf("%d\n", c); // 原变量被修改,输出 3 return 0; }简单理解:指针相当于变量的 **“第二入口”**,可以绕过变量名直接读写内存。
二、指针变量的大小
指针存储的是内存地址,指针占用字节数只由编译环境决定,和指针类型无关。
- 32 位环境 (x86):地址占 32 位 → 所有指针统一占4 字节
- 64 位环境 (x64):地址占 64 位 → 所有指针统一占8 字节
验证代码:
#include<stdio.h> int main() { printf("%zu\n", sizeof(char*)); printf("%zu\n", sizeof(int*)); printf("%zu\n", sizeof(double*)); return 0; }三、指针类型的真正作用
既然指针大小和类型无关,那前面的char*、int*有什么意义?指针类型决定两件事:解引用权限、指针 ± 整数时的偏移步长。
1. 解引用:决定一次能操作几个字节
int*:解引用一次访问4 字节char*:解引用一次访问1 字节
示例:
#include<stdio.h> int main() { int a = 0x11223344; char* p = &a; *p = 0; // 仅修改低1个字节 return 0; }调试可见:只有 1 字节数据被修改,这就是类型带来的访问权限差异。
2. 指针 ± 整数:决定地址偏移步长
指针+1不是地址单纯 + 1,而是向后偏移一个「指向类型」的大小:
int* + 1:地址向后偏移 4 字节char* + 1:地址向后偏移 1 字节
四、特殊指针:void* 空指针
void*是通用空指针,特点:
- 可以接收任意类型变量的地址,常用于泛型场景;
- 不能直接解引用,也不能做
±整数运算; - 原因:
void无具体类型,编译器不知道它一次该访问几个字节。
五、四大类指针运算(核心重点)
结合生活例子类比,方便记忆:
日期 + 天数 = 日期 | 日期 + 日期(无意义) 指针运算遵循相同逻辑:
- 指针 + 整数 = 指针(地址向后偏移)
- 指针 - 整数 = 指针(地址向前偏移)
- 指针 - 指针 = 整数(仅同一块连续内存有效,计算元素个数)
- 指针 + 指针:语法允许,但逻辑无意义,禁止使用
1. 指针 ± 整数:遍历数组(最常用)
数组是连续内存空间,配合指针遍历是 C 语言经典写法。
写法 1:指针不动,下标移动
#include<stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; // 数组名arr等价于 &arr[0] for (int i = 0; i < sz; i++) { printf("%d ", *(p + i)); } return 0; }写法 2:指针自身移动(p++)
#include<stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; for (int i = 0; i < sz; i++) { printf("%d ", *p); p++; } return 0; }写法 3:while 循环 + 地址比较(无额外循环变量)
利用指针可以比较大小的特性,终止条件:p < 数组末尾下一个地址
#include<stdio.h> int main() { int arr[10] = {1,2,3,4,5,6,7,8,9,10}; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; while (p < arr + sz) { printf("%d ", *p); p++; } return 0; }易错提醒:arr[10] 是越界元素(取值),遍历判断必须用 &arr[10](取地址)。
2. 指针 - 指针:模拟实现 strlen 求字符串长度
规则:两个指针必须指向同一块连续内存,差值 = 中间元素个数。
自定义字符串长度函数
#include<stdio.h> int my_strlen(const char* a) { const char* p = a; // 字符串以 '\0' 作为结束标志 while (*p != '\0') { p++; } // 结束指针 - 起始指针 = 字符个数 return (int)(p - a); } int main() { char s[] = "abc"; printf("%d", my_strlen(s)); return 0; }补充:C++ 环境建议参数加 const,匹配字符串常量 const char* 类型,避免编译警告。
六、高频易错点汇总
1.char s = "abc"; 错误
2.char 只能存单个字符(单引号 'a');字符串必须用 char[] 或 char*(双引号 "abc")。
正确写法:char s[] = "abc"; / char* s = "abc";
3.int* p = arr[0]; 错误
arr[0] 是数组元素值;指针需要存地址,正确:int* p = &arr[0]; / int* p = arr;
4.数组下标越界
int arr[10] 合法下标 0~9,arr[10] 是非法内存,严禁取值、解引用。
5.64 位环境指针相减警告
64 位下指针差值为 64 位整型,返回int会提示丢失数据,可加强制转换 (int)(p-a)。
七、学习总结
指针 = 内存地址,指针变量 = 存放地址的变量,核心操作 & 取地址、* 解引用;
指针大小由编译环境决定,类型决定访问字节数和偏移步长;
指针四大运算:±整数 遍历数组、-指针 求元素个数、指针可比较大小;
数组名本质是数组首元素地址,数组和指针可以灵活混用;
牢记边界问题:数组下标、字符串结束符 \0、内存越界是指针 bug 重灾区。