C语言学习记录(一):从数组到指针入门
初学者视角的完整笔记:知识点整理 + 代码示例 + 踩坑复盘 + 后续计划。
前言
最近在系统学习 C 语言,今天集中攻克了「数组 → 字符串 → 二维数组 → 函数 → 指针入门」这一串内容。作为初学者,我明显感觉到这几个知识点是连在一起的:数组是基础,字符串是特殊的数组,二维数组是数组的数组,函数用来打包逻辑,而指针则是理解 C 语言「地址」思维的第一步。
这篇博客既是学习记录,也是踩坑复盘,希望能帮到同样在入门的同学。
一、数组:一排从 0 开始编号的格子
1.1 为什么需要数组
如果一个变量只能存一个值:
int a = 10; int b = 20; int c = 30;那要存 100 个学生的成绩,难道写 100 个变量?数组就是「一排连续的小格子」,每个格子存同类型的数据,用一个名字管理一整排。
成绩数组 score: ┌────┬────┬────┬────┬────┐ │ 90 │ 85 │ 78 │ 92 │ 66 │ └────┴────┴────┴────┴────┘ [0] [1] [2] [3] [4] ← 下标1.2 声明与初始化
int score[5]; // 5 个格子 int score[5] = {90, 85, 78, 92, 66}; // 声明时直接给值 int score[] = {90, 85, 78, 92, 66}; // 不写个数,让编译器自己数1.3 下标从 0 开始(新手最容易错)
score[5]一共有 5 个格子,编号是 0~4,没有 score[5]。
score[0] = 90; // 第 1 个格子 score[4] = 66; // 第 5 个(最后一个) // score[5] = 10; // ❌ 越界,没有这个格子1.4 数组 + for 循环
#include <stdio.h> int main() { int score[5] = {90, 85, 78, 92, 66}; for (int i = 0; i < 5; i++) // i 从 0 到 4 { printf("第 %d 个学生:%d 分\n", i + 1, score[i]); } return 0; }注意是i < 5而不是i <= 5,因为最后一个下标是 4。
1.5 例子:算平均分
#include <stdio.h> int main() { int score[5] = {90, 85, 78, 92, 66}; int sum = 0; for (int i = 0; i < 5; i++) sum += score[i]; printf("平均分 = %d\n", sum / 5); return 0; }1.6 必须记住的三点
- 下标从 0 开始,
int a[5]的下标是 0~4。 - 不能越界,
a[5]是错的,程序不一定报错,但会出现诡异的 bug。 - 数组里所有元素类型相同,不能又装 int 又装 float。
二、踩坑:中文输出乱码
2.1 现象
程序里的中文全变成了乱码,但数字正常:
绗?1 涓鐢燂細90 鍒?2.2 原因
源文件是 UTF-8 编码,但 Windows 终端默认用 GBK 显示。UTF-8 的中文字节被 GBK 解读,就成了乱码。数字和字母没事,是因为 ASCII 在两种编码里一致。
2.3 解决
方法一(最简单):把文件用 GBK 编码保存。VS Code 右下角点击UTF-8→ 通过编码保存 → 选 GBK。
方法二:运行前在终端执行chcp 65001,让终端用 UTF-8。
方法三:程序里自己切换:
#include <stdio.h> #include <stdlib.h> int main() { system("chcp 65001 > nul"); // 切换成 UTF-8 // ... }前提是源文件本身是 UTF-8。
一句话:文件编码和终端编码要一致,要么都用 GBK,要么都用 UTF-8。
三、字符串:其实就是字符数组
3.1 字符数组
char c[5]; c[0] = 'h'; c[1] = 'i';'h'是字符,"hi"是字符串,别搞混。
3.2 关键约定:末尾有\0
C 语言没有专门的字符串类型,字符串就是 char 数组,并且约定以\0结尾。
"hi"在内存里其实是 3 个格子:
┌───┬───┬────┐ │ h │ i │ \0 │ └───┴───┴────┘ [0] [1] [2]那个\0告诉程序「字符串到这里结束」。
3.3 读入和输出
#include <stdio.h> int main() { char name[20]; printf("请输入名字:"); scanf("%s", name); // 注意:name 前面不用 & printf("你好,%s\n", name); return 0; }两个要点:
scanf("%s", name)不加&,因为数组名本身就是地址。%s读入遇到空格就停。
3.4 求长度与比较(理解\0)
自己写求长度:
int len = 0; while (s[len] != '\0') // 没遇到 \0 就一直数 len++;自己写比较:
int i = 0; while (a[i] == b[i] && a[i] != '\0') i++;实际开发直接用<string.h>:
#include <string.h> strlen(s); // 求长度 strcmp(a, b); // 相等返回 0,a<b 返回负数,a>b 返回正数 strcpy(a, b); // 把 b 复制到 a注意:strcmp(a, b) == 0才是相等,不是== 1。
四、二维数组:表格
4.1 概念
二维数组就是「数组的数组」,有行有列。比如 3 个班、每班 5 人的成绩:
学生0 学生1 学生2 学生3 学生4 1班 90 85 78 92 66 2班 88 76 95 70 81 3班 79 83 91 68 87int score[3][5]; // 3 行 5 列4.2 访问与初始化
score[0][1] = 85; // 第 0 行、第 1 列 int score[3][5] = { {90, 85, 78, 92, 66}, // 第 0 行 {88, 76, 95, 70, 81}, // 第 1 行 {79, 83, 91, 68, 87} // 第 2 行 };4.3 双层循环遍历
for (int i = 0; i < 3; i++) // 外层:i 走行 { for (int j = 0; j < 5; j++) // 内层:j 走列 printf("%d ", score[i][j]); printf("\n"); }4.4 例子:求各班平均分
#include <stdio.h> int main() { int score[3][5] = { {90, 85, 78, 92, 66}, {88, 76, 95, 70, 81}, {79, 83, 91, 68, 87} }; for (int i = 0; i < 3; i++) { int sum = 0; // 关键:写在每个班开始处,每行重置 for (int j = 0; j < 5; j++) sum += score[i][j]; printf("第 %d 班的平均分 = %d\n", i + 1, sum / 5); } return 0; }关键点:sum必须声明在外层循环内部,这样每换一行就重置为 0,不会把上一行的和累加进来。
五、函数:把代码打包
5.1 函数结构
返回值类型 函数名(参数列表) { 函数体 return 值; }5.2 例子:求最大值
#include <stdio.h> int max(int a, int b) { if (a > b) return a; else return b; } int main() { printf("%d\n", max(3, 7)); // 7 return 0; }5.3 三个关键点
- 参数是「输入」,
return是「输出」。 - 函数要写在
main前面(先定义后使用)。 - 没有返回值就用
void。
5.4 例子:阶乘函数
#include <stdio.h> int fact(int n) { int ret = 1; for (int i = 1; i <= n; i++) ret *= i; return ret; } int main() { int t; printf("请输入一个整数:"); scanf("%d", &t); printf("%d! = %d\n", t, fact(t)); return 0; }这里t传给fact是值传递,函数内部改的是副本,外面的t不受影响——这正是函数的好处。
六、指针入门
6.1 地址
每个变量在内存里都有地址,就像宿舍房间号。
int n = 10;6.2&和*
int n = 10; int *p = &n; // p 存 n 的地址&n:取 n 的地址。int *p:声明一个指针变量。*p:通过地址访问/修改 p 指向的变量。
6.3 完整例子
#include <stdio.h> int main() { int n = 10; int *p = &n; printf("n = %d\n", n); // 10 printf("&n = %p\n", &n); // 一串十六进制地址 printf("p = %p\n", p); // 和 &n 一样 printf("*p = %d\n", *p); // 10 *p = 20; // 通过 p 修改 n printf("n = %d\n", n); // 20 return 0; }6.4 解开心结:scanf 为什么加&
scanf需要知道「把读到的值写到哪个地址」,所以要&n:
int n; scanf("%d", &n);而数组名本身就是首元素地址,所以不用&:
char name[20]; scanf("%s", name);七、我踩过的坑(重点复盘)
- for 循环三段式写错:
for(n>1; n--)把条件当成了初始化,缺了真正的条件。正确写法是for(初始化; 条件; 步进)。 - 打印了被循环改坏的变量:循环把变量改成了别的值,还拿它打印。要用副本保存原始值。
- 变量命名不达意:把「最大值」命名成
sum。名字要反映它装的是什么。 - 遍历数组从 i=1 开始,漏掉第 0 个元素:求和、统计两遍都从 1 开始,漏了
score[0]。遍历数组基本都从 0 开始。 - 没看清题目要求:题目要「个数」,却打印了每个元素。动手前先确认要计数、求和还是打印每个值。
- 中文乱码:文件编码和终端编码不一致。
八、练习题清单
- 输入 5 个整数,倒序打印。
- 输入 5 个整数,求最大值。
- 输入 5 个整数,求平均分并统计高于平均分的人数。
- 二维数组
score[3][5],求每个班的平均分。 - 写函数
fact(n)求阶乘。
九、下一步计划
- 先不看答案,把上面的 5 道练习重写一遍。
- 重点巩固数组下标范围、
for三段式。 - 再学指针进阶:
- 值传递 vs 地址传递;
- 经典例子:写
swap函数交换两个数; - 指针和数组的关系(
a[i]和*(a+i))。
- 之后再看结构体
struct。
总结
今天最大的收获不是记住了多少语法,而是理解了几个核心思想:
- 数组是「同类型数据 + 下标」;
- 字符串是「char 数组 +
\0」; - 二维数组是「数组的数组 + 双层循环」;
- 函数是「输入参数 + 返回结果」的打包;
- 指针是「地址」的思维。
也踩了不少坑,但每个坑都让我对 C 语言的细节理解更深了一层。继续加油。