一、指针的运算
指针本质上是一个地址值,但它的运算并不是简单的整数加减,而是以基类型大小为步长的。
1. 指针的加减运算
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; int *p = a; // p 指向 a[0]| 表达式 | 含义 |
|---|---|
p + 1 | 向后偏移1个基类型大小(即跳过一个int),指向a[1] |
p - N | 向前偏移 N 个基类型大小 |
p++ | 指针后移一个基类型大小 |
p-- | 指针前移一个基类型大小 |
关键:
p + 1不是地址 +1 字节,而是地址 +sizeof(基类型)字节。比如int *p,则p + 1实际地址增加了 4 字节(假设int为 4 字节)。
2. 指针相减
int *p = &a[2]; int *q = &a[7]; int diff = p - q; // 结果为 -5- 前提:两个指针必须是同类型指针
- 结果含义:两个地址之间差了几个基类型(而不是差几个字节)
3. 指针的关系运算
支持>、<、>=、<=、==、!=:
if (p > q) { // p 所指向的地址在 q 之后 }二、指针操作整型一维数组
数组名就是首元素地址
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};a(数组名)⇔&a[0](首元素地址),两者等价- 首元素
a[0]的数据类型是int,所以&a[0]的地址类型是int *
int *p = a; // 等价于 int *p = &a[0]; 说 p 指向了数组 a用指针访问数组元素
| 表达式 | 等价形式 | 含义 |
|---|---|---|
*p | a[0] | 首元素 |
*(p + 1) | a[1] | 第二个元素 |
*(p + i) | a[i] | 第 i+1 个元素 |
数组的下标运算,本质上就是指针运算。
a[i]等价于*(a + i)。
数组的特性与指针的关系
- 1.连续性—— 只需知道起始地址,就能通过
p + i定位到任意元素 - 2.有序性—— 元素在内存中按顺序排列
- 3.单一性—— 每个元素类型相同,大小一致
三、指针实战练习
练习1:找出数组最大值
int findMax(int *p, int len) { int max = *p; for (int i = 1; i < len; i++) { if (*(p + i) > max) { max = *(p + i); } } return max; }练习2:数组逆序
void reverse(int *p, int len) { int *left = p; int *right = p + len - 1; while (left < right) { int temp = *left; *left = *right; *right = temp; left++; right--; } }练习3:选择排序(迭代方式)
核心思想:外层循环确定当前位置begin,内层循环从剩余元素中找到最小值,然后交换。
void selectSort(int *p, int len) { for (int i = 0; i < len - 1; i++) { int minIdx = i; for (int j = i + 1; j < len; j++) { if (*(p + j) < *(p + minIdx)) { minIdx = j; } } if (minIdx != i) { int temp = *(p + i); *(p + i) = *(p + minIdx); *(p + minIdx) = temp; } } }选择排序(begin/end 指针风格)
void selectSortP(int *begin, int *end) { int *i = NULL, *j = NULL; for (i = begin; i < end; ++i) { int *min = i; for (j = i + 1; j <= end; ++j) { if (*j < *min) min = j; } if (min != i) swap(i, min); } }冒泡排序(外层--end,内层++p)
void bubbleSortP(int *begin, int *end) { int *p = NULL; for (; end > begin; --end) { for (p = begin; p < end; ++p) { if (*p > *(p + 1)) swap(p, p + 1); } } }插入排序(外层++i,内层--j)
void insertSortP(int *begin, int *end) { int *i = NULL; for (i = begin + 1; i <= end; ++i) { int temp = *i; int *j = i - 1; while (j >= begin && *j > temp) { *(j + 1) = *j; --j; } *(j + 1) = temp; } }快速排序(--right找小值,++left找大值)
int *partitionP(int *begin, int *end) { int pivot = *begin; int *left = begin; int *right = end; while (left < right) { while (left < right && *right >= pivot) --right; *left = *right; while (left < right && *left <= pivot) ++left; *right = *left; } *left = pivot; return left; } void quickSortP(int *begin, int *end) { if (begin >= end) return; int *pivotPos = partitionP(begin, end); quickSortP(begin, pivotPos - 1); quickSortP(pivotPos + 1, end); }练习4:二分查找(返回指针)
int *binaryFind(int *begin, int *end, int target) { while (begin <= end) { int *mid = begin + (end - begin) / 2; if (*mid == target) return mid; // 返回找到元素的地址 else if (*mid < target) begin = mid + 1; else end = mid - 1; } return NULL; // 没找到返回 NULL }四、指针操作字符型一维数组(字符串)
字符串在 C 语言中本质是字符数组,同样可以用指针操作:
char s[] = "hello"; char *p = s; // p 指向 s[0],即 'h' *(p + i) // 访问第 i+1 个字符标准库函数的典型签名:
int puts(const char *s); // 参数为 const char *五、const 与指针
const和指针搭配时,位置不同,含义完全不同。核心原则:const离谁近,就限定谁。
四种组合
const char *s; // const 限定 *s(基类型)→ 不能通过 *s 修改所指向的内容 char const *s; // 同上,写法不同,效果相同 char *const s; // const 限定 s(指针变量本身)→ s 不能再指向其他地址 const char *const s; // 两者都限定 → 既不能改指向,也不能改内容示例对比
const char *s = "hello"; // *s = 'c'; ✗ 编译报错,不能通过指针修改内容 s = "world"; ✓ 可以改变指向char buf[] = "hello"; char *const s = buf; *s = 'c'; ✓ 可以修改内容 s = NULL; ✗ 编译报错,不能改变指向为什么字符串函数推荐用const char *
char *strcpy(char *dest, const char *src); // ^^^^^好处有两点:
- 1.提前发现逻辑错误—— 防止意外修改源字符串
- 2.参数适用性更强—— 可以接受:
- 数组名(
char s[] = "hello") - 字符串常量(
"hello") const char *变量
- 数组名(
在编写字符串相关函数时,形参能写成
const char *的,尽可能写成const char *。
小结
| 知识点 | 核心要点 |
|---|---|
| 指针加减 | 以sizeof(基类型)为步长 |
| 指针相减 | 同类型指针,结果为基类型个数的差 |
| 数组名 | 等价于首元素地址&a[0] |
a[i] | 本质是*(a + i),即指针运算 |
const char * | 不能通过*s修改内容,推荐用于函数形参 |
char *const s | s 不能改指向,但可改内容 |