完整教程:C语言入门教程 | 第六讲:指针详解 - 揭开C语言最神秘的面纱

完整教程:C语言入门教程 | 第六讲:指针详解 - 揭开C语言最神秘的面纱

C语言入门教程 | 第六讲:指针详解 - 揭开C语言最神秘的面纱1. 前言:指针为什么这么重要?小伙伴们好!今天我们要学习C语言中最重要、也是最让初学者头疼的概念——指针(Pointer)。

很多人说:“学会了指针,就掌握了C语言的灵魂”。但同时,指针也是劝退无数C语言学习者的"拦路虎"。不过别担心,今天我会用最通俗易懂的方式,带你彻底搞懂指针!

先来个类比帮你理解:

想象一下,你的家有一个地址(比如"北京市朝阳区XX街XX号"),别人想找到你家,就需要知道这个地址。在C语言中:

你的家 = 变量你家的地址 = 变量的内存地址记录你家地址的本子 = 指针变量指针就是这样一个"记录地址的本子"!

2. 指针的核心概念(1)什么是内存地址?计算机的内存就像一个巨大的公寓楼,每个房间(字节)都有一个唯一的门牌号(地址)。

// 假设内存地址如下(实际地址是十六进制)

内存地址 存储的数据

0x1000 10

0x1004 20

0x1008 30

0x100C 40

(2)什么是指针?指针是一个特殊的变量,它存储的不是普通数据,而是另一个变量的内存地址。

int a = 50; // 普通变量a,存储值50

int *p = &a; // 指针变量p,存储a的地址

// 可以这样理解:

// a是一个房间,里面住着数字50

// p是一张纸,上面写着a房间的门牌号

(3)两个核心运算符

int a = 100; // 定义一个整数变量

int *p; // 定义一个指针变量

// 运算符1:& (取地址运算符)

p = &a; // 获取a的地址,赋值给p

// 可以理解为:p这张纸上写下了a的门牌号

// 运算符2:* (解引用运算符)

int value = *p; // 通过p找到它指向的变量,获取值

// 可以理解为:根据p上记录的门牌号,找到那个房间,看里面的数据

3. 指针的声明和使用(1)指针的声明语法

数据类型 *指针变量名;

// 示例:

int *p1; // 指向int类型的指针

char *p2; // 指向char类型的指针

float *p3; // 指向float类型的指针

重要提示:

int *p 中的 * 是声明符号,表示p是一个指针使用时的 *p 是解引用操作,表示获取p指向的值(2)完整示例:指针的基本操作

#include

int main()

{

// 第一步:定义普通变量

int a = 50;

// 第二步:定义指针变量并初始化

int *p = &a; // p指向a,即p中存储了a的地址

// 第三步:查看相关信息

printf("=== 变量a的信息 ===\n");

printf("a的值: %d\n", a); // 输出:50

printf("a的地址: %p\n", &a); // 输出:a在内存中的地址(十六进制)

printf("a占用的字节数: %lu\n", sizeof(a)); // 输出:4(int类型占4字节)

printf("\n=== 指针p的信息 ===\n");

printf("p的值(即a的地址): %p\n", p); // 输出:与&a相同

printf("p本身的地址: %p\n", &p); // 输出:p自己在内存中的地址

printf("p指向的值: %d\n", *p); // 输出:50(通过p访问a的值)

printf("p占用的字节数: %lu\n", sizeof(p)); // 输出:8(64位系统)或4(32位系统)

// 第四步:通过指针修改原变量的值

printf("\n=== 通过指针修改值 ===\n");

*p = 100; // 通过指针p修改a的值

printf("修改后,a的值: %d\n", a); // 输出:100

printf("修改后,*p的值: %d\n", *p); // 输出:100

// 第五步:指针的重新指向

int b = 200;

p = &b; // 现在p指向b

printf("\n=== 指针重新指向 ===\n");

printf("p现在指向b,*p = %d\n", *p); // 输出:200

return 0;

}

运行结果示例:

=== 变量a的信息 ===

a的值: 50

a的地址: 0x7ffeeb3c8a1c

a占用的字节数: 4

=== 指针p的信息 ===

p的值(即a的地址): 0x7ffeeb3c8a1c

p本身的地址: 0x7ffeeb3c8a20

p指向的值: 50

p占用的字节数: 8

=== 通过指针修改值 ===

修改后,a的值: 100

修改后,*p的值: 100

=== 指针重新指向 ===

p现在指向b,*p = 200

(3) 内存示意图让我们用图来理解上面的代码:

初始状态:

┌──────────────────┐

│ 变量a │

│ 地址: 0x1000 │

│ 值: 50 │

└──────────────────┘

│ 指向

┌──────────────────┐

│ 指针p │

│ 地址: 0x2000 │

│ 值: 0x1000 │ ← p中存储的是a的地址

└──────────────────┘

执行 *p = 100 后:

┌──────────────────┐

│ 变量a │

│ 地址: 0x1000 │

│ 值: 100 ✓ │ ← 通过指针修改了a的值

└──────────────────┘

4. 指针作为函数参数:值传递 vs 指针传递这是理解指针的关键应用场景!

(1)问题场景:交换两个变量的值

#include

// 方法1:值传递(失败的尝试)

void swapByValue(int x, int y)

{

printf(" [函数内] 交换前: x=%d, y=%d\n", x, y);

int temp = x; // 临时保存x的值

x = y; // x赋值为y

y = temp; // y赋值为原来的x

printf(" [函数内] 交换后: x=%d, y=%d\n", x, y);

// 注意:这里看似交换成功了,但只是交换了副本!

}

// 方法2:指针传递(成功的方法)

void swapByPointer(int *px, int *py)

{

printf(" [函数内] 交换前: *px=%d, *py=%d\n", *px, *py);

int temp = *px; // 临时保存px指向的值

*px = *py; // px指向的位置赋值为py指向的值

*py = temp; // py指向的位置赋值为原来px指向的值

printf(" [函数内] 交换后: *px=%d, *py=%d\n", *px, *py);

}

int main()

{

int a = 10, b = 20;

// 测试值传递

printf("=== 测试值传递 ===\n");

printf("[main函数] 调用前: a=%d, b=%d\n", a, b);

swapByValue(a, b);

printf("[main函数] 调用后: a=%d, b=%d\n", a, b); // 没有改变!

printf("结论:值传递无法修改原变量\n");

// 重新设置a和b的值

a = 10; b = 20;

// 测试指针传递

printf("\n=== 测试指针传递 ===\n");

printf("[main函数] 调用前: a=%d, b=%d\n", a, b);

swapByPointer(&a, &b); // 传递a和b的地址

printf("[main函数] 调用后: a=%d, b=%d\n", a, b); // 成功交换!

printf("结论:指针传递可以修改原变量\n");

return 0;

}

运行结果:

=== 测试值传递 ===

[main函数] 调用前: a=10, b=20

[函数内] 交换前: x=10, y=20

[函数内] 交换后: x=20, y=10

[main函数] 调用后: a=10, b=20

结论:值传递无法修改原变量

=== 测试指针传递 ===

[main函数] 调用前: a=10, b=20

[函数内] 交换前: *px=10, *py=20

[函数内] 交换后: *px=20, *py=10

[main函数] 调用后: a=20, b=10

结论:指针传递可以修改原变量

(2) 为什么会这样?深入理解值传递的过程:

main函数中:

a = 10 (地址: 0x1000)

b = 20 (地址: 0x2000)

调用 swapByValue(a, b):

创建副本 x = 10 (地址: 0x3000) ← 复制了a的值

创建副本 y = 20 (地址: 0x4000) ← 复制了b的值

在函数内交换 x 和 y:

x = 20, y = 10

但这只是修改了副本!原来的a和b没有变化

函数结束,x和y被销毁

a和b依然是原来的值

指针传递的过程:

main函数中:

a = 10 (地址: 0x1000)

b = 20 (地址: 0x2000)

调用 swapByPointer(&a, &b):

px = 0x1000 (指向a)

py = 0x2000 (指向b)

在函数内通过指针修改:

*px = *py → 把0x1000处的值改为20

*py = temp → 把0x2000处的值改为10

函数结束后:

a = 20 ✓ (成功修改)

b = 10 ✓ (成功修改)

5. 指针与数组:天生一对(1)数组名的本质这是一个重要概念:数组名就是指向数组第一个元素的指针!

#include

int main()

{

int arr[5] = {10, 20, 30, 40, 50};

// 验证:数组名就是首元素的地址

printf("=== 数组名与地址的关系 ===\n");

printf("arr的值: %p\n", arr); // 数组名

printf("&arr[0]的值: %p\n", &arr[0]); // 首元素地址

printf("它们相等吗?%s\n", arr == &arr[0] ? "是的!" : "不是");

// 用指针指向数组

int *p = arr; // 等价于 int *p = &arr[0];

printf("\n=== 用指针访问数组元素 ===\n");

printf("第一个元素:%d\n", *p); // 输出:10

printf("第二个元素:%d\n", *(p + 1)); // 输出:20

printf("第三个元素:%d\n", *(p + 2)); // 输出:30

return 0;

}

(2)访问数组元素的三种等价方式

#include

int main()

{

int arr[5] = {10, 20, 30, 40, 50};

int *p = arr;

printf("=== 三种等价的访问方式 ===\n");

printf("访问第2个元素(索引1):\n");

// 方式1:传统数组下标

printf(" arr[1] = %d\n", arr[1]);

// 方式2:指针算术(基于数组名)

printf(" *(arr + 1) = %d\n", *(arr + 1));

// 方式3:指针算术(基于指针变量)

printf(" *(p + 1) = %d\n", *(p + 1));

// 甚至可以这样(不推荐,但合法)

printf(" p[1] = %d\n", p[1]);

printf("\n所有方式的结果都相同!\n");

return 0;

}

(3) 指针算术详解指针加减运算是按照数据类型的大小来移动的,不是按字节!

#include

int main()

{

int arr[] = {10, 20, 30, 40, 50};

int *p = arr;

printf("=== 指针算术演示 ===\n");

printf("int类型占用字节数:%lu\n", sizeof(int));

printf("\n指针位置变化:\n");

printf("p指向arr[0],地址:%p,值:%d\n", p, *p);

p++; // 指针向后移动一个int的大小(4字节)

printf("p++后,指向arr[1],地址:%p,值:%d\n", p, *p);

p += 2; // 再向后移动2个int的大小(8字节)

printf("p+=2后,指向arr[3],地址:%p,值:%d\n", p, *p);

p--; // 向前移动一个int

printf("p--后,指向arr[2],地址:%p,值:%d\n", p, *p);

// 验证地址差值

p = arr;

printf("\n=== 地址差值计算 ===\n");

printf("&arr[0] 到 &arr[1] 的字节差:%ld\n",

(char*)&arr[1] - (char*)&arr[0]);

printf("p+1 与 p 的元素差:%ld\n", (p+1) - p);

return 0;

}

(4)用指针遍历数组

#include

int main()

{

int scores[] = {85, 92, 78, 96, 88};

int size = sizeof(scores) / sizeof(scores[0]); // 计算数组元素个数

// 方法1:使用指针加偏移量

printf("=== 方法1:指针 + 偏移量 ===\n");

int *p = scores;

for(int i = 0; i < size; i++)

{

printf("scores[%d] = %d (地址:%p)\n", i, *(p + i), p + i);

}

// 方法2:移动指针本身

printf("\n=== 方法2:移动指针 ===\n");

p = scores; // 重置指针到起始位置

int *end = scores + size; // 指向数组末尾的下一个位置

int index = 0;

while(p < end) // 当指针还没越界

{

printf("scores[%d] = %d\n", index, *p);

p++; // 指针移动到下一个元素

index++;

}

// 方法3:反向遍历

printf("\n=== 方法3:反向遍历 ===\n");

p = scores + size - 1; // 指向最后一个元素

for(int i = size - 1; i >= 0; i--)

{

printf("scores[%d] = %d\n", i, *p);

p--; // 指针向前移动

}

return 0;

}

6.⚠️ 指针的常见错误与防范(1)错误1:野指针(最危险!)

#include

int main()

{

// 错误示范:未初始化的指针

int *p1; // p1是野指针,指向未知内存

// *p1 = 10; // ❌ 危险!可能导致程序崩溃

// 正确做法1:初始化为NULL

int *p2 = NULL; // NULL表示空指针,不指向任何有效内存

// 正确做法2:初始化指向有效变量

int a = 100;

int *p3 = &a; // p3指向有效的变量a

// 使用前检查

if(p2 != NULL) // 检查指针是否为空

{

*p2 = 20; // 只有非空才操作

}

else

{

printf("p2是空指针,不能解引用\n");

}

// 安全地使用p3

if(p3 != NULL)

{

printf("p3指向的值:%d\n", *p3);

}

return 0;

}

(2)错误2:空指针解引用

#include

int main()

{

int *p = NULL;

// 错误示范

// *p = 10; // ❌ 空指针不能解引用,程序会崩溃

// 正确做法:使用前检查

if(p != NULL)

{

*p = 10; // 只有非空才解引用

printf("赋值成功:%d\n", *p);

}

else

{

printf("指针为空,无法赋值\n");

}

// 更安全的写法:先分配内存或指向有效变量

int value = 0;

p = &value; // 现在p指向有效内存

*p = 10; // 安全

printf("现在可以赋值了:%d\n", value);

return 0;

}

(3)错误3:指针类型不匹配

#include

int main()

{

int a = 100;

// 错误示范:类型不匹配

// char *p = &a; // ❌ 警告:将int*赋值给char*

// 正确做法:类型匹配

int *p_int = &a; // ✅ int指针指向int变量

char c = 'A';

char *p_char = &c; // ✅ char指针指向char变量

printf("int指针:%d\n", *p_int);

printf("char指针:%c\n", *p_char);

// 如果确实需要类型转换,使用强制类型转换

char *p_force = (char*)&a; // 强制转换,但要明白后果

printf("强制转换后访问第一个字节:%d\n", *p_force);

return 0;

}

(4)错误4:悬空指针

#include

int* dangerousFunction()

{

int local = 100; // 局部变量

return &local; // ❌ 危险!返回局部变量的地址

} // 函数结束后,local被销毁,指针变成悬空指针

int main()

{

int *p = dangerousFunction();

// *p的行为是未定义的!可能崩溃,可能输出垃圾值

// 正确做法:返回全局变量、静态变量或动态分配的内存

return 0;

}

7. 实战项目:指针应用(1)项目1:找出数组中的最大值和最小值

#include

// 找最大值,返回指向最大值的指针

int* findMax(int arr[], int size)

{

if(size <= 0) return NULL; // 数组为空,返回NULL

int *maxPtr = &arr[0]; // 假设第一个元素最大

for(int i = 1; i < size; i++)

{

if(arr[i] > *maxPtr) // 如果当前元素更大

{

maxPtr = &arr[i]; // 更新最大值指针

}

}

return maxPtr;

}

// 找最小值,返回指向最小值的指针

int* findMin(int arr[], int size)

{

if(size <= 0) return NULL;

int *minPtr = &arr[0];

for(int i = 1; i < size; i++)

{

if(arr[i] < *minPtr)

{

minPtr = &arr[i];

}

}

return minPtr;

}

int main()

{

int numbers[] = {45, 23, 67, 12, 89, 34, 56};

int size = sizeof(numbers) / sizeof(numbers[0]);

// 显示原数组

printf("=== 原始数组 ===\n");

for(int i = 0; i < size; i++)

{

printf("%d ", numbers[i]);

}

printf("\n");

// 找最大值

int *maxPtr = findMax(numbers, size);

if(maxPtr != NULL)

{

printf("\n=== 最大值信息 ===\n");

printf("最大值:%d\n", *maxPtr);

printf("最大值的地址:%p\n", maxPtr);

printf("最大值的索引:%ld\n", maxPtr - numbers);

}

// 找最小值

int *minPtr = findMin(numbers, size);

if(minPtr != NULL)

{

printf("\n=== 最小值信息 ===\n");

printf("最小值:%d\n", *minPtr);

printf("最小值的地址:%p\n", minPtr);

printf("最小值的索引:%ld\n", minPtr - numbers);

}

// 通过指针修改值

printf("\n=== 修改最大值和最小值 ===\n");

*maxPtr = 100; // 把最大值改为100

*minPtr = 0; // 把最小值改为0

printf("修改后的数组:\n");

for(int i = 0; i < size; i++)

{

printf("%d ", numbers[i]);

}

printf("\n");

return 0;

}

(2)项目2:用指针实现字符串反转

#include

// 找最大值,返回指向最大值的指针

int* findMax(int arr[], int size)

{

if(size <= 0) return NULL; // 数组为空,返回NULL

int *maxPtr = &arr[0]; // 假设第一个元素最大

for(int i = 1; i < size; i++)

{

if(arr[i] > *maxPtr) // 如果当前元素更大

{

maxPtr = &arr[i]; // 更新最大值指针

}

}

return maxPtr;

}

// 找最小值,返回指向最小值的指针

int* findMin(int arr[], int size)

{

if(size <= 0) return NULL;

int *minPtr = &arr[0];

for(int i = 1; i < size; i++)

{

if(arr[i] < *minPtr)

{

minPtr = &arr[i];

}

}

return minPtr;

}

int main()

{

int numbers[] = {45, 23, 67, 12, 89, 34, 56};

int size = sizeof(numbers) / sizeof(numbers[0]);

// 显示原数组

printf("=== 原始数组 ===\n");

for(int i = 0; i < size; i++)

{

printf("%d ", numbers[i]);

}

printf("\n");

// 找最大值

int *maxPtr = findMax(numbers, size);

if(maxPtr != NULL)

{

printf("\n=== 最大值信息 ===\n");

printf("最大值:%d\n", *maxPtr);

printf("最大值的地址:%p\n", maxPtr);

printf("最大值的索引:%ld\n", maxPtr - numbers);

}

// 找最小值

int *minPtr = findMin(numbers, size);

if(minPtr != NULL)

{

printf("\n=== 最小值信息 ===\n");

printf("最小值:%d\n", *minPtr);

printf("最小值的地址:%p\n", minPtr);

printf("最小值的索引:%ld\n", minPtr - numbers);

}

// 通过指针修改值

printf("\n=== 修改最大值和最小值 ===\n");

*maxPtr = 100; // 把最大值改为100

*minPtr = 0; // 把最小值改为0

printf("修改后的数组:\n");

for(int i = 0; i < size; i++)

{

printf("%d ", numbers[i]);

}

printf("\n");

return 0;

}

8. 指针与数组的深度对比

#include

int main()

{

int arr[5] = {10, 20, 30, 40, 50};

int *p = arr;

printf("=== 数组名 vs 指针变量 ===\n");

// 相同点1:都可以通过下标访问

printf("arr[2] = %d\n", arr[2]);

printf("p[2] = %d\n", p[2]);

// 相同点2:都可以进行指针运算

printf("*(arr + 2) = %d\n", *(arr + 2));

printf("*(p + 2) = %d\n", *(p + 2));

// 不同点1:数组名是常量,不能修改

// arr = arr + 1; // ❌ 编译错误!数组名不能修改

p = p + 1; // ✅ 正确!指针变量可以修改

printf("p移动后:*p = %d\n", *p); // 现在指向arr[1]

// 不同点2:sizeof行为不同

p = arr; // 重置指针

printf("\nsizeof(arr) = %lu (整个数组的大小)\n", sizeof(arr));

printf("sizeof(p) = %lu (指针变量的大小)\n", sizeof(p));

// 数组元素个数计算

int arrSize = sizeof(arr) / sizeof(arr[0]);

printf("数组元素个数:%d\n", arrSize);

return 0;

}

9. 总结指针是C语言的核心特性,虽然初学时可能觉得困难,但它是理解计算机底层工作原理的钥匙。通过本讲的学习,你应该掌握:

✅ 指针的基本概念和操作✅ 指针与函数参数传递✅ 指针与数组的关系✅ 常见错误的识别和避免✅ 指针的实际应用**记住:**指针不是魔法,它只是一个存储地址的变量。理解了内存地址的概念,指针就不再神秘。多写代码,多调试,多思考,你一定能够熟练掌握指针!

觉得有帮助?记得点赞收藏转发三连哦!有问题欢迎评论区交流讨论!

相关推荐

沈阳权威人流医院有哪些
英国最大赌博365网站

沈阳权威人流医院有哪些

⌛ 10-28 👁️ 8672
含有【风云】的成语
365bet娱乐场手机版

含有【风云】的成语

⌛ 08-06 👁️ 4812
培训班怎么推课?有哪些推课形式?
英国最大赌博365网站

培训班怎么推课?有哪些推课形式?

⌛ 01-19 👁️ 3104