前言
指针是C 语言的灵魂,也是 C 语言最难、最重要的知识点。掌握指针,才算真正入门 C 语言。指针本质围绕地址、变量类型、指针运算、解引用、传参展开,同时日常开发中字符串函数底层全部依靠指针实现。本文从指针基础概念、地址类型、指针运算、数组传参、手写字符串函数、常量指针与指针常量完整区分,一步步详细讲解,附带原理、公式、代码案例、易错点总结,适合新手系统学习、期末复习、面试备考。
一、指针基础概念
a. 指针的本质
指针本身是一种独立的变量类型,这种变量唯一的用途就是:专门用来存储内存地址。
普通变量:存储数据值指针变量:存储内存地址
b. 地址的类型(核心重点)
很多新手只知道&可以取地址,却不知道地址本身也是有类型的,地址类型完全由被取地址的变量类型决定。
- 举例
int a;对变量a取地址&a,它的地址类型就是int *
结论:
任意变量取地址后的类型 = 变量类型 +
*
表格
| 原变量定义 | 变量类型 | 取地址 & 变量 | 地址类型 |
|---|---|---|---|
int a; | int | &a | int * |
char ch; | char | &ch | char * |
double d; | double | &d | double * |
c. 指针的运算能力(必考核心公式)
指针不是普通数字,指针进行+1/-1运算时,不是单纯数字 + 1,而是向后偏移对应数据类型的字节大小。指针偏移步长公式:
指针地址运算能力 = sizeof (去掉一个
*之后的类型)
详细举例:
int *p;去掉一个*→int运算能力:sizeof(int) = 4也就是p+1,地址向后偏移4 个字节int **p;(二级指针)去掉一个*→int *运算能力:sizeof(int *) = 8(64 位系统指针统一 8 字节)也就是p+1,地址向后偏移8 个字节char *p;去掉一个*→char运算能力:sizeof(char) = 1也就是p+1,地址向后偏移1 个字节
底层原理:指针 + 1,本质是跳转到下一个同类型元素的地址,所以偏移量由指向的数据类型决定。
d. 指针变量的定义与赋值
- 语法
int a = 10; // 定义指针变量p,类型 int * ,存储变量a的地址 int *p = &a;- 变量名:
p,我们称p为指针变量 - 变量类型:
int * - 存储内容:普通变量
a的内存地址 - C 语言中只有指针变量可以存储地址,普通变量无法存储地址。
e. 数组作为函数参数传递(指针经典应用)
数组在内存中所有元素地址连续存储。因此在函数传参时,数组不会整体拷贝,只传递数组首元素地址。
只要拿到三个信息:
- 数组首地址
- 元素的数据类型
- 数组元素个数
就可以遍历、访问整个数组的所有元素。
所以函数形参中:
void fun(int arr[])等价于
void fun(int *arr)arr本质就是指针,接收数组首地址。
二、指针实战:手写实现字符串库函数
结合指针语法,手动实现库函数string.h里面全部常用函数,彻底理解指针操作字符串的原理。
练习 1:手写mystrlen求字符串长度
功能:统计字符串有效长度,遇到\0结束
// char *p 接收字符串首地址 int mystrlen(char *p) { int len = 0; // 只要不是结束符\0,就计数 while (*p != '\0') { len++; p++; // 指针后移一位 } return len; }练习 2:手写mystrncpy字符串指定长度拷贝
功能:从源字符串src拷贝内容到目标空间dest,最多拷贝size个字节,返回目标字符串首地址。
char *mystrncpy(char *dest, char *src, int size) { // 保存目标起始地址,用于最后返回 char *start = dest; int i = 0; // 拷贝,同时限制最大长度size,并且不能提前遇到\0结束 while (*src != '\0' && i < size) { *dest = *src; dest++; src++; i++; } // 手动补充字符串结束符 *dest = '\0'; return start; }练习 3:手写mystrcat字符串指定长度拼接
功能:将源字符串src拼接到目标字符串dest末尾,限制目标空间最大容量size字节,返回目标首地址。
char *mystrcat(char *dest, char *src, int size) { char *start = dest; // 第一步:先找到dest字符串末尾\0的位置 while (*dest != '\0') { dest++; } // 第二步:从末尾开始拼接src内容,限制总容量不越界 while (*src != '\0' && (dest - start) < size - 1) { *dest = *src; dest++; src++; } // 补充结束符 *dest = '\0'; return start; }练习 4:手写mystrcmp字符串比较函数
功能:逐字符比较 ASCII 码值大小返回值规则:
s1 > s2,返回大于 0 的整数s1 < s2,返回小于 0 的负数s1 == s2,返回0
int mystrcmp(char *s1, char *s2) { // 逐字符比较,直到出现不同字符 或 到达结束符 while (*s1 != '\0' && *s2 != '\0' && *s1 == *s2) { s1++; s2++; } // 两者差值即为比较结果 return *s1 - *s2; }三、常量指针 & 指针常量(面试高频必考)
结合const关键字修饰指针,分为常量指针、指针常量,二者极易混淆,本文完整区分定义、写法、含义、区别。
a. 常量指针
本质:它是指针,指向的内容是常量(只读)两种完全等价的写法:
const char *p; char const *q;p、q本身依旧是指针变量,指针本身地址可以随意修改、指向可以改变- 指针指向的内容
*p、*q只读,不允许修改 - 口诀:const 修饰,内容不可改,指针可改*
示例:
char a = 'a'; char b = 'b'; const char *p = &a; p = &b; // 合法:指针本身可以修改指向 *p = 'c'; // 报错:指向的内容只读,不能修改b. 指针常量
本质:它是常量,指针本身是常量不可修改写法:
char *const m;m是指针常量,指针本身地址不可修改,指向不可更改- 指针指向的内容
*m可以正常修改 - 口诀:const 修饰指针变量本身,指针不可改,内容可改
示例:
char a = 'a'; char b = 'b'; char *const m = &a; m = &b; // 报错:指针本身是常量,不能修改指向 *m = 'c'; // 合法:指向的内容可以修改c. 终极区分总结口诀
const在*左边:修饰指向内容 → 常量指针,内容只读,指针可变const int *p; int const *p;const在*右边:修饰指针本身 → 指针常量,指针只读,内容可变int *const p;- 拓展(双向只读):
指针不可改,指向内容也不可改。const int *const p;
四、本章重点全部总结
- 指针是专门存储内存地址的变量类型,地址自带类型,由原变量类型决定。
- 指针运算核心公式:*指针偏移字节数 = sizeof (去掉一个)**
int *+1 偏移 4 字节char *+1 偏移 1 字节- 二级指针
int **+1 偏移 8 字节
- 数组函数传参,只传递首地址,形参本质是指针。
- 字符串全部库函数底层均依靠指针移动、解引用实现。
- 常量指针与指针常量区分:
- 常量指针:
const char *p,指向内容只读,指针可换指向 - 指针常量:
char *const p,指针本身不可改,指向内容可修改
- 常量指针:
- 区分核心:看
const到底修饰的是*(内容),还是指针变量本身。