【20天学C语言】Day 11: 指针基础
📅 学习时间:4-5小时
🎯 学习目标:理解指针的概念,掌握指针的基本操作
💡 难度:★★★★☆(核心难点开始)
📝 Day 10 练习题答案
练习1答案:递归求幂
#include <stdio.h>
long long power(int base, int exp) {
if (exp == 0) return 1;
if (exp == 1) return base;
return base * power(base, exp – 1);
}
int main(void)
{
printf("2^10 = %lld\\n", power(2, 10)); // 1024
return 0;
}
练习2答案:递归逆序打印
#include <stdio.h>
void print_reverse(int arr[], int len) {
if (len <= 0) return;
printf("%d ", arr[len – 1]);
print_reverse(arr, len – 1);
}
int main(void)
{
int arr[] = {1, 2, 3, 4, 5};
print_reverse(arr, 5); // 5 4 3 2 1
printf("\\n");
return 0;
}
练习3答案:递归判断回文
#include <stdio.h>
#include <string.h>
int is_palindrome(char str[], int left, int right) {
if (left >= right) return 1;
if (str[left] != str[right]) return 0;
return is_palindrome(str, left + 1, right – 1);
}
int main(void)
{
char str[] = "level";
int len = strlen(str);
if (is_palindrome(str, 0, len – 1)) {
printf("'%s' 是回文\\n", str);
}
return 0;
}
练习4答案:归并排序
#include <stdio.h>
void merge(int arr[], int left, int mid, int right) {
int n1 = mid – left + 1;
int n2 = right – mid;
int L[n1], R[n2];
for (int i = 0; i < n1; i++) L[i] = arr[left + i];
for (int i = 0; i < n2; i++) R[i] = arr[mid + 1 + i];
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
arr[k++] = (L[i] <= R[j]) ? L[i++] : R[j++];
}
while (i < n1) arr[k++] = L[i++];
while (j < n2) arr[k++] = R[j++];
}
void merge_sort(int arr[], int left, int right) {
if (left < right) {
int mid = left + (right – left) / 2;
merge_sort(arr, left, mid);
merge_sort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
}
int main(void)
{
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int len = 7;
merge_sort(arr, 0, len – 1);
for (int i = 0; i < len; i++) printf("%d ", arr[i]);
printf("\\n");
return 0;
}
1. 知识点概述
今天我们将学习:
- 内存与地址的概念
- 指针变量的定义
- 取地址运算符(&)
- 解引用运算符(*)
- 指针的初始化与赋值
- 空指针与野指针
- 指针的大小
2. 详细讲解
2.1 内存与地址
内存可以想象成一排连续的格子:
地址: 0x1000 0x1001 0x1002 0x1003 0x1004 …
+——-+——-+——-+——-+——-+
内存: | | | | | |
+——-+——-+——-+——-+——-+
当我们定义一个int变量时(假设int占4字节):
int num = 100;
地址: 0x1000 0x1001 0x1002 0x1003
+——-+——-+——-+——-+
内存: | 100 (占4个字节) |
+——-+——-+——-+——-+
^
num的地址是0x1000
2.2 什么是指针
#include <stdio.h>
int main(void)
{
int num = 100;
// &num 获取num的地址
printf("num的值: %d\\n", num);
printf("num的地址: %p\\n", (void*)&num);
// 指针就是存储地址的变量
int *p = # // p存储num的地址
printf("p的值(即num的地址): %p\\n", (void*)p);
printf("p指向的值(*p): %d\\n", *p);
return 0;
}
输出示例:
num的值: 100
num的地址: 0x7ffd12345678
p的值(即num的地址): 0x7ffd12345678
p指向的值(*p): 100
2.3 指针变量的定义
#include <stdio.h>
int main(void)
{
int *p1; // 指向int的指针
char *p2; // 指向char的指针
double *p3; // 指向double的指针
float *p4; // 指向float的指针
// *号靠近类型还是变量都可以
int* p5; // 风格1
int *p6; // 风格2(推荐)
int * p7; // 风格3
// 注意:同时定义多个指针
int *a, *b; // a和b都是指针
int *c, d; // c是指针,d是int
return 0;
}
2.4 取地址运算符 &
#include <stdio.h>
int main(void)
{
int num = 100;
int *p = # // &取地址
char ch = 'A';
char *pc = &ch;
double d = 3.14;
double *pd = &d;
printf("num地址: %p\\n", (void*)&num);
printf("ch地址: %p\\n", (void*)&ch);
printf("d地址: %p\\n", (void*)&d);
return 0;
}
2.5 解引用运算符 *
#include <stdio.h>
int main(void)
{
int num = 100;
int *p = #
// *p 访问p指向的内存
printf("*p = %d\\n", *p); // 读取:100
*p = 200; // 写入:修改num的值
printf("num = %d\\n", num); // 200
// 等价关系
// *(&num) == num
// &(*p) == p
return 0;
}
2.6 指针与变量的关系
int num = 100;
int *p = #
内存图:
+———-+
num (0x1000)| 100 |<–+
+———-+ |
|
+———-+ |
p (0x2000)| 0x1000 |—+ (p存储num的地址)
+———-+
关系:
– p 存储 num 的地址
– *p 和 num 是同一块内存
– 修改 *p 就是修改 num
– 修改 num 也会影响 *p 的读取结果
#include <stdio.h>
int main(void)
{
int num = 100;
int *p = #
printf("初始: num=%d, *p=%d\\n", num, *p);
*p = 200;
printf("修改*p后: num=%d, *p=%d\\n", num, *p);
num = 300;
printf("修改num后: num=%d, *p=%d\\n", num, *p);
return 0;
}
2.7 指针的初始化
#include <stdio.h>
int main(void)
{
// 方式1:定义时初始化
int num = 100;
int *p1 = #
// 方式2:先定义,后赋值
int *p2;
p2 = #
// 方式3:初始化为NULL
int *p3 = NULL; // 空指针
// ❌ 危险:未初始化的指针(野指针)
int *p4; // p4是随机值,指向未知地址
// *p4 = 100; // 危险!
return 0;
}
2.8 空指针 NULL
#include <stdio.h>
int main(void)
{
int *p = NULL; // 空指针,不指向任何有效地址
// 使用前检查
if (p != NULL) {
printf("*p = %d\\n", *p);
} else {
printf("p是空指针\\n");
}
// 简写形式
if (p) { // 非空
printf("p有效\\n");
}
if (!p) { // 为空
printf("p为空\\n");
}
return 0;
}
2.9 野指针
#include <stdio.h>
int main(void)
{
// 野指针的产生场景:
// 1. 未初始化
int *p1; // 野指针
// 2. 指向已释放的内存(后续动态内存章节)
// 3. 指向超出作用域的变量
int *p2;
{
int num = 100;
p2 = #
}
// 此时p2是野指针,num已经销毁
// 预防:使用后置空
p2 = NULL;
return 0;
}
2.10 指针的大小
#include <stdio.h>
int main(void)
{
printf("sizeof(char*) = %zu\\n", sizeof(char*));
printf("sizeof(short*) = %zu\\n", sizeof(short*));
printf("sizeof(int*) = %zu\\n", sizeof(int*));
printf("sizeof(long*) = %zu\\n", sizeof(long*));
printf("sizeof(float*) = %zu\\n", sizeof(float*));
printf("sizeof(double*) = %zu\\n", sizeof(double*));
printf("sizeof(void*) = %zu\\n", sizeof(void*));
// 所有指针大小相同:
// 32位系统:4字节
// 64位系统:8字节
return 0;
}
3. 代码示例
示例1:通过指针交换变量
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main(void)
{
int x = 10, y = 20;
printf("交换前: x=%d, y=%d\\n", x, y);
swap(&x, &y); // 传递地址
printf("交换后: x=%d, y=%d\\n", x, y);
return 0;
}
示例2:指针与不同类型
#include <stdio.h>
int main(void)
{
char c = 'A';
int i = 100;
double d = 3.14;
char *pc = &c;
int *pi = &i;
double *pd = &d;
printf("c = %c, *pc = %c\\n", c, *pc);
printf("i = %d, *pi = %d\\n", i, *pi);
printf("d = %.2f, *pd = %.2f\\n", d, *pd);
// 指针类型决定了解引用时读取多少字节
printf("sizeof(char) = %zu, sizeof(*pc) = %zu\\n", sizeof(char), sizeof(*pc));
printf("sizeof(int) = %zu, sizeof(*pi) = %zu\\n", sizeof(int), sizeof(*pi));
printf("sizeof(double) = %zu, sizeof(*pd) = %zu\\n", sizeof(double), sizeof(*pd));
return 0;
}
示例3:多级指针
#include <stdio.h>
int main(void)
{
int num = 100;
int *p = # // 一级指针
int **pp = &p; // 二级指针
int ***ppp = &pp; // 三级指针
printf("num = %d\\n", num);
printf("*p = %d\\n", *p);
printf("**pp = %d\\n", **pp);
printf("***ppp = %d\\n", ***ppp);
// 它们都能访问到num的值
return 0;
}
4. 常见错误(踩坑指南)
错误1:未初始化就使用
// ❌ 危险代码
int *p;
*p = 100; // 野指针,可能崩溃
// ✅ 正确代码
int num;
int *p = #
*p = 100;
错误2:类型不匹配
// ❌ 错误代码
int num = 100;
double *p = # // 类型不匹配
// ✅ 正确代码
int num = 100;
int *p = #
错误3:混淆地址和值
// ❌ 混淆
int num = 100;
int *p;
p = num; // 错误:把值当地址
*p = # // 错误:把地址当值
// ✅ 正确
p = # // p存储地址
*p = 100; // *p存储值
错误4:解引用空指针
// ❌ 危险代码
int *p = NULL;
printf("%d\\n", *p); // 崩溃!
// ✅ 正确代码
int *p = NULL;
if (p != NULL) {
printf("%d\\n", *p);
}
5. 练习题目
练习1:指针基础(难度:★☆☆☆☆)
定义一个int变量和指向它的指针,通过指针修改变量的值。
练习2:交换函数(难度:★★☆☆☆)
实现swap函数,使用指针参数交换两个变量的值。
练习3:指针计算(难度:★★☆☆☆)
编写函数,通过指针参数返回两个整数的和与差。
void sum_diff(int a, int b, int *sum, int *diff);
练习4:找最大最小值(难度:★★☆☆☆)
编写函数,使用指针参数返回数组的最大值和最小值。
6. 小结
今日核心要点
[核心概念]
1. 指针是存储地址的变量
2. & 取地址
3. * 解引用(访问指向的内存)
[定义与初始化]
4. 类型 *指针名;
5. 初始化为有效地址或NULL
[关系]
6. *(&变量) == 变量
7. &(*指针) == 指针
[注意事项]
8. 避免野指针
9. 使用前检查NULL
10. 类型要匹配
11. 所有指针大小相同(4或8字节)
指针图示
变量: int num = 100;
指针: int *p = #
+——-+ +——-+
num | 100 | <——– |0x1000 | p
(0x1000) +——-+ points +——-+ (0x2000)
to
*p 就是 num
&num 就是 p 的值
下一篇预告: Day 12 – 指针与数组
我们将学习指针与数组的关系,以及如何用指针操作数组。
练习题答案将在 Day 12 开头公布


