学习方向: C++ / 数据结构基础
今日主题: 指针遍历、双指针、while 循环、变量初始化、数组统计
一、使用指针寻找最小值及下标
今天的第一道练习是使用指针遍历数组,寻找数组中的最小值,并记录对应的下标。
给定数组:
int arr[5] = {8, -3, 12, -7, 4};
要求使用指针和 for 循环,输出最小值及其下标。
修正后的代码
#include <iostream>
using namespace std;
int main()
{
int arr[5] = {8, -3, 12, -7, 4};
int* p = arr;
int MiniValue = *p;
int Value;
int downsign = 0;
for (int i = 0; i < 5; i++)
{
Value = *p;
if (Value < MiniValue)
{
MiniValue = Value;
downsign = i;
}
p++;
}
cout << "最小值:" << MiniValue << endl;
cout << "下标:" << downsign;
return 0;
}
输出:
最小值:-7
下标:3
遇到的问题
1. 最小值变量没有初始化
最初写的是:
int MiniValue, Value, downsign;
随后直接使用:
if (Value < MiniValue)
由于 MiniValue 尚未初始化,直接读取它会产生未定义行为。
正确做法:
int MiniValue = *p;
int downsign = 0;
先把数组第一个元素作为初始最小值。
2. 使用固定数值作为初始最小值不够可靠
例如:
int MiniValue = 999999;
虽然对当前数组有效,但如果数组中全部元素都大于 999999,就无法得到正确的最小值。
这并不是内存占用的问题,而是算法对数据范围存在不必要的假设。
3. 没有按照题目要求使用 p++
最初通过:
Value = *(p + i);
访问数组元素。这种方式本身正确,但题目要求直接移动指针。
修改为:
Value = *p;
p++;
理解与收获
*p读取当前指针指向的元素。p++使指针移动到下一个元素。- 查找最小值时,应使用数组中的实际元素初始化比较基准。
- 当找到更小的值时,需要同时更新最小值和对应下标。
- 当前题目的循环边界
i < 5正确,没有出现上次的数组越界问题。
二、使用双指针反转数组
第二道练习要求使用两个指针和 while 循环,实现数组的原地反转。
给定数组:
int arr[5] = {10, 20, 30, 40, 50};
目标输出:
50 40 30 20 10
完整代码
#include <iostream>
using namespace std;
int main()
{
int arr[5] = {10, 20, 30, 40, 50};
int* left = arr;
int* right = arr + 4;
int temp = 0;
while (left < right)
{
temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
for (int i = 0; i < 5; i++)
{
cout << arr[i] << " ";
}
return 0;
}
这道题的代码独立完成,逻辑正确。
复习:while 循环
编写这道题时,一开始忘记了 while 的具体语法,因此重新复习:
while (条件)
{
// 循环体
}
与 for 循环相比,while 更适合根据某个条件决定何时结束循环。
在本题中:
while (left < right)
表示只要左指针的位置仍在右指针之前,就继续交换元素。
双指针交换原理
初始数组:
10 20 30 40 50
第一次交换:
50 20 30 40 10
第二次交换:
50 40 30 20 10
随后两个指针在中间元素处相遇,循环结束。
最核心的代码:
temp = *left;
*left = *right;
*right = temp;
这里的 temp 用于暂时保存左侧元素,防止交换过程中原来的值丢失。
完成交换后:
left++;
right--;
两个指针分别向数组中间移动。
理解与收获
left指向数组首元素。right指向数组最后一个元素。*left和*right分别访问两个指针指向的值。left++让左指针向右移动一个元素。right--让右指针向左移动一个元素。while (left < right)可以控制双指针何时停止交换。
另外:
int temp = 0;
在这里可以简化为:
int temp;
因为 temp 每次使用前都会被赋值。
这道题是今天完成得最好的一道练习。 在复习 while 后,能够独立写出完整的双指针反转算法。
三、数组总和、平均值与元素统计
第三道题是一道综合练习。
给定数组:
int arr[6] = {4, 8, 2, 10, 6, 12};
要求:
- 计算数组所有元素的总和。
- 计算平均值。
- 统计严格大于平均值的元素个数。
修正后的完整代码
#include <iostream>
#include <iterator>
using namespace std;
int main()
{
int arr[6] = {4, 8, 2, 10, 6, 12};
int* p = arr;
int sum = 0;
double average = 0;
int find;
int MaxAver = 0;
// 第一次遍历:计算总和
for (int i = 0; i < 6; i++)
{
sum += *p;
p++;
}
// 计算平均值
average = static_cast<double>(sum) / std::size(arr);
// 重新指向数组首元素
p = arr;
// 第二次遍历:统计大于平均值的元素
for (int i = 0; i < 6; i++)
{
find = *p;
if (find > average)
{
MaxAver++;
}
p++;
}
cout << "总和:" << sum << endl;
cout << "平均值:" << average << endl;
cout << "大于平均值的元素个数:" << MaxAver;
return 0;
}
输出:
总和:42
平均值:7
大于平均值的元素个数:3
遇到的问题
1. 错误地使用嵌套循环
最初将两个循环写成了嵌套结构:
for (int i = 0; i < 6; i++)
{
sum += *(p + i);
for (int n = 0; n < i; n++)
{
// 统计元素
}
}
问题在于,内层循环会随着外层循环不断重复执行。
这道题实际上只需要两次独立遍历:第一次计算总和,第二次统计符合要求的元素。
因此两个循环应该顺序排列,而不是互相嵌套。
2. 平均值计算时机错误
最初在累加过程中反复计算:
sum += *(p + i);
average = sum / std::size(arr);
此时 sum 还只是部分元素的总和。
更合理的做法是先完成累加:
for (int i = 0; i < 6; i++)
{
sum += *p;
p++;
}
然后在循环结束后计算:
average = static_cast<double>(sum) / std::size(arr);
使用 double 可以保留小数结果,避免整数除法造成精度丢失。
3. 计数器初始化及判断条件错误
原来使用:
int MaxAver = *p;
但 MaxAver 的作用是统计元素个数,因此应该从 0 开始。
int MaxAver = 0;
最初的比较条件也写错了:
if (MaxAver > average)
{
MaxAver++;
}
应当比较的是当前数组元素与平均值:
if (find > average)
{
MaxAver++;
}
4. 没有正确管理指针的位置
在遍历中混用了:
*(p + i)
和:
p++;
由于 p++ 会改变指针本身,再叠加 i 的偏移,很容易访问到错误的位置甚至越界。
本题采用直接移动指针的方式,并在第二次遍历前执行:
p = arr;
使指针重新指向数组首元素。
理解与收获
这道题让我进一步认识到,多个知识点组合起来时,需要先考虑程序整体的执行顺序。
比较合理的程序结构是:
初始化变量
↓
第一次循环:计算总和
↓
计算平均值
↓
重置指针
↓
第二次循环:比较与计数
↓
输出结果
编写代码前,先确定算法步骤,能够避免不必要的循环嵌套。
四、今日知识点总结
知识点作用int* p = arr创建指向数组首元素的指针*p解引用,访问指针指向的值p++指针移动到下一个元素*(p + i)读取偏移位置的元素,不改变 pleft++左指针向右移动right--右指针向左移动while (left < right)控制双指针交换sum += *p将当前元素累加到总和MaxAver++满足条件时计数加一p = arr将指针重置到数组开头std::size(arr)获取数组元素数量(C++17)
五、与第一次训练的对比
11 月 8 日的第一次训练主要学习了基本循环、数字三角形、指针遍历和最大值查找。
当时最突出的问题是:
- 对累加表达式理解不充分。
- 嵌套循环难以独立编写。
- 多次将数组遍历条件写成
i <= 5,发生越界。 - 对指针移动和偏移操作不熟悉。
今天的第二次训练中:
进步的地方:
- 三道题的主要数组遍历边界均写对。
- 能够使用指针访问数组元素。
- 独立完成了双指针反转数组。
- 开始主动使用
std::size()等标准库工具。 - 能够将变量更新、比较和指针操作结合起来。
仍然需要加强的地方:
- 变量初始化和算法初始状态。
- 多层循环与独立循环的区别。
- 多步骤算法的执行顺序。
- 指针移动后的地址变化。
- 累加器和计数器的区别。
六、下一阶段学习计划
下一次 Coding 训练暂时不增加新的复杂数据结构,继续巩固以下内容:
- 循环专项:
for、while、嵌套循环和执行顺序。 - 变量跟踪: 累加、计数、初始化与变量更新。
- 指针综合: 数组遍历、指针偏移和指针移动。
- 算法实践: 独立完成需要多次遍历的数组统计题。
在能够稳定独立完成这些练习之后,再进入单链表的节点创建、插入、删除和遍历。
七、今日总结
今天三道练习中,第一题主要暴露了变量初始化问题,第二题完整正确,第三题则暴露了复杂循环结构与算法步骤设计方面的不足。
相比第一次训练,目前已经能够相对熟练地使用指针完成简单数组操作,但将多个步骤组合起来时,仍需要更多独立实践。
接下来的重点不是学习更多语法,而是提高独立设计和实现算法的能力。
继续坚持:
理解原理 → 独立编写 → 运行调试 → 分析错误 → 整理学习日志。