C语言基础入门到进阶:变量、函数、指针与内存管理一文讲透
本文是一篇面向零基础读者的 C 语言基础入门教程,系统讲解 C 语言开发环境、编译运行、变量与数据类型、分支循环、函数、数组、字符串、指针和动态内存管理。文章以“概念 + 可运行代码 + 常见错误 + 完整小项目”的方式组织,适合初学者从 0 开始建立 C 语言基础。
0. 学习目标:先知道学完能干什么
学完本文,你应该能做到:
写出并运行 C 程序
用分支和循环处理逻辑
用函数拆分代码
用数组和字符串处理数据
理解指针的基本含义
使用
malloc/free管理动态内存独立完成一个“成绩统计器”小程序
1. 开发环境与运行方式
Windows
推荐安装:
MSYS2 + gcc
或 MinGW-w64
安装后打开终端,输入:
gcc --version如果能看到版本号,说明编译器可用。
macOS
安装 Xcode Command Line Tools:
xcode-select --install验证:
gcc --versionmacOS 上gcc通常会映射到clang,能正常编译 C 程序即可。
Linux(Ubuntu)
sudo apt update sudo apt install build-essential gcc --version编译与运行
假设文件名是main.c:
gcc main.c -o main ./mainWindows 下可能生成main.exe,可以运行:
main.exe常见问题:
gcc: command not found:没有安装编译器,或环境变量没配置好找不到文件:终端当前目录不是源码所在目录
2. 第一个 C 程序:先跑起来
#include <stdio.h> int main(void) { printf("Hello, C!\n"); return 0; }先理解三件事:
main是程序入口printf负责输出return 0表示程序正常结束
头文件是什么?
你会经常看到这样的代码:
#include <stdio.h> #include <stdlib.h> #include <string.h>可以先简单理解为:头文件是在告诉编译器,我接下来要使用哪些现成的函数或类型。
常见头文件:
| 头文件 | 常见用途 |
|---|---|
stdio.h | 输入输出,如printf、scanf、fgets |
stdlib.h | 动态内存、程序退出,如malloc、free、exit |
string.h | 字符串处理,如strlen、strcspn、strcmp |
stdbool.h | 布尔类型,如bool、true、false |
刚入门不用背全部,先知道:用某个库函数前,通常要包含对应头文件。
常见错误:
忘记分号
;把
printf写错忘记包含
stdio.h
3. 变量、常量、基本数据类型
变量可以理解为:给一块内存起名字。
数据类型决定这块内存能放什么值。
#include <stdio.h> #include <stdbool.h> int main(void) { int age = 18; double score = 95.5; char grade = 'A'; bool passed = true; const double PI = 3.14159; printf("age=%d\n", age); printf("score=%.1f\n", score); printf("grade=%c\n", grade); printf("passed=%d\n", passed); printf("PI=%.5f\n", PI); return 0; }C 语言常见基本数据类型
| 类型 | 含义 | 常见格式符 | 示例 |
|---|---|---|---|
char | 字符/小整数 | %c | char c = 'A'; |
short | 短整型 | %hd | short s = 10; |
int | 整型,最常用 | %d | int age = 18; |
long | 长整型 | %ld | long n = 1000L; |
long long | 更长的整型 | %lld | long long big = 100000LL; |
float | 单精度小数 | %f | float f = 3.14f; |
double | 双精度小数,常用 | printf用%f,scanf用%lf | double d = 3.14; |
_Bool/bool | 布尔值 | %d | bool ok = true; |
signed 和 unsigned
整数类型还可以分为有符号和无符号:
int a = -10; // 可以表示负数 unsigned int b = 10; // 只表示非负数注意:unsigned不适合随便用来“防止负数”。它参与运算时可能产生不直观的结果。初学阶段,大多数普通整数先用int即可。
sizeof:查看类型占多少字节
不同平台上类型大小可能不同,可以用sizeof查看:
#include <stdio.h> int main(void) { printf("char: %zu\n", sizeof(char)); printf("int: %zu\n", sizeof(int)); printf("long: %zu\n", sizeof(long)); printf("double: %zu\n", sizeof(double)); return 0; }sizeof的结果类型是size_t,打印时用%zu。
常见错误:
单个字符用单引号:
'A'字符串用双引号:
"A"小数默认是
double,写float f = 3.14f;更规范不要假设
int永远是 4 字节,具体大小和平台有关
4. 运算符与判断
#include <stdio.h> int main(void) { int a = 10, b = 3; printf("a+b=%d\n", a + b); printf("a-b=%d\n", a - b); printf("a*b=%d\n", a * b); printf("a/b=%d\n", a / b); printf("a%%b=%d\n", a % b); if (a == b) { printf("equal\n"); } else { printf("not equal\n"); } return 0; }几个必须分清的符号:
=:赋值==:比较是否相等%:取余&&:并且||:或者!:取反
常见错误:
if (a = b) { // 错误:这是赋值,不是比较 }应该写成:
if (a == b) { // 正确:比较是否相等 }5. 分支与循环
if-else
#include <stdio.h> int main(void) { int score = 78; if (score >= 90) { printf("优秀\n"); } else if (score >= 60) { printf("及格\n"); } else { printf("不及格\n"); } return 0; }for 循环
for (int i = 1; i <= 5; i++) { printf("%d ", i); }while 循环
int n = 3; while (n > 0) { printf("n=%d\n", n); n--; }switch
char grade = 'A'; switch (grade) { case 'A': printf("优秀\n"); break; case 'B': printf("良好\n"); break; default: printf("继续努力\n"); break; }常见错误:
while忘记更新条件,导致死循环switch忘记break,导致继续执行下一个case
6. 函数:代码不再全堆在 main 里
#include <stdio.h> int add(int x, int y) { return x + y; } int main(void) { int result = add(3, 5); printf("result=%d\n", result); return 0; }函数的好处:
复用逻辑
降低复杂度
更容易调试
让代码结构更清晰
常见错误:
函数有返回值,却忘记
return参数类型和返回值类型写混
函数调用时参数数量不对
7. 输入:scanf 的基础用法
scanf可以从键盘读取输入:
#include <stdio.h> int main(void) { int age; printf("请输入年龄: "); scanf("%d", &age); printf("你的年龄是: %d\n", age); return 0; }注意这里是&age,不是age。
原因是:scanf需要把输入结果写回变量,所以要传入变量的地址。
常见写法:
int n; double score; char ch; scanf("%d", &n); scanf("%lf", &score); scanf(" %c", &ch); // 前面的空格用于跳过换行或空白字符不过,scanf处理字符串和非法输入时比较容易踩坑。入门阶段可以先用它读数字;读一整行字符串时,更推荐fgets。
8. 数组与字符串
8.1 数组
数组用来保存一组相同类型的数据:
#include <stdio.h> int main(void) { int nums[5] = {10, 20, 30, 40, 50}; int sum = 0; for (int i = 0; i < 5; i++) { sum += nums[i]; } printf("sum=%d\n", sum); return 0; }注意:数组下标从0开始。nums[0]是第一个元素,nums[4]是第五个元素。
常见错误:
int nums[5]; nums[5] = 100; // 错误:越界8.2 字符串
C 语言里的字符串,本质是以'\0'结尾的字符数组。
char name[20] = "Tom";字符串输入推荐使用fgets:
#include <stdio.h> #include <string.h> int main(void) { char name[20]; printf("请输入名字: "); if (fgets(name, sizeof(name), stdin) != NULL) { name[strcspn(name, "\n")] = '\0'; // 去掉换行 printf("你好,%s\n", name); } return 0; }为什么不推荐gets?
因为gets不知道数组有多大,容易写越界,已经不应再使用。
常见错误:
字符串数组空间不够
忘记字符串结尾的
'\0'用
==比较字符串内容
字符串内容比较要用strcmp:
#include <string.h> if (strcmp(name, "Tom") == 0) { printf("same\n"); }9. 指针入门:理解地址与解引用
#include <stdio.h> int main(void) { int num = 100; int *p = # printf("num=%d\n", num); printf("address of num=%p\n", (void*)&num); printf("p=%p\n", (void*)p); printf("*p=%d\n", *p); *p = 200; printf("num after *p=200 => %d\n", num); return 0; }理解三者关系:
num:变量本身,里面存的是值&num:变量的地址p:保存地址的变量,也就是指针*p:取出这个地址里存放的值
一句话记忆:
&:取地址*p:解引用,取地址里的值
这一节只要求你先理解三件事:地址、指针变量、解引用。
二级指针、函数指针、复杂声明可以后面再学,不要一开始就把自己绕进去。
常见错误:
int *p; *p = 10; // 危险:p 没有指向有效地址指针使用前,必须先指向一个有效对象,或者明确赋值为NULL。
10. 动态内存:运行时申请空间
有时候数组长度在程序运行时才知道,这时可以用动态内存。
#include <stdio.h> #include <stdlib.h> int main(void) { int n = 5; int *arr = malloc(n * sizeof *arr); if (arr == NULL) { printf("内存申请失败\n"); return 1; } for (int i = 0; i < n; i++) { arr[i] = i * 10; } for (int i = 0; i < n; i++) { printf("%d ", arr[i]); } printf("\n"); free(arr); arr = NULL; return 0; }这里使用了:
int *arr = malloc(n * sizeof *arr);它的好处是:如果以后arr的类型改了,sizeof *arr会自动跟着变,不容易因为手写sizeof(int)而出错。
你也可能看到这种写法:
int *arr = malloc(n * sizeof(int));它也能用,但前一种更不容易写错。
动态内存黄金法则:
malloc后要判断是否为NULL有
malloc就要有freefree后不要继续使用旧指针谁申请,谁负责释放
11. 完整实战:成绩统计器
需求:
输入 5 个成绩
输出平均分
输出最高分和最低分
对输入做基本校验
#include <stdio.h> #define N 5 double calc_avg(const int arr[], int n) { int sum = 0; for (int i = 0; i < n; i++) { sum += arr[i]; } return (double)sum / n; } int calc_max(const int arr[], int n) { int max = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] > max) { max = arr[i]; } } return max; } int calc_min(const int arr[], int n) { int min = arr[0]; for (int i = 1; i < n; i++) { if (arr[i] < min) { min = arr[i]; } } return min; } int main(void) { int scores[N]; printf("请输入%d个成绩(0-100):\n", N); for (int i = 0; i < N; i++) { while (1) { printf("score[%d] = ", i); if (scanf("%d", &scores[i]) != 1) { while (getchar() != '\n') { // 清理非法输入 } printf("输入无效,请输入整数。\n"); continue; } if (scores[i] < 0 || scores[i] > 100) { printf("范围应在0~100,请重新输入。\n"); continue; } break; } } double avg = calc_avg(scores, N); int max = calc_max(scores, N); int min = calc_min(scores, N); printf("\n平均分:%.2f\n", avg); printf("最高分:%d\n", max); printf("最低分:%d\n", min); return 0; }这个小程序已经包含了:
输入
校验
数组
循环
函数
计算
输出
这就是一个完整的基础 C 程序闭环。
12. 常见报错与排查
1.expected ';'
通常是上一行漏了分号。
2.undefined reference to ...
可能是:
函数声明了但没有实现
编译多个
.c文件时漏了某个源文件
3.Segmentation fault
常见原因:
指针没有初始化
数组越界
访问了已经释放的内存
4. 程序结果不对
建议先检查:
输入是否符合预期
循环边界是否正确
数组下标是否越界
变量是否初始化
13. 练习题
建议你一定要动手写。
输入一个整数
n,输出1~n的和写函数判断一个数是否为素数
输入一行字符串,统计其中字母个数
用数组实现“找第二大值”
用动态内存创建长度为
n的数组并求平均值
练习是从“看懂”到“会用”的唯一捷径。
14. 学到这里,你应该会什么
如果你现在能做到下面几点,就算完成了 C 语言的入门到基础阶段:
能写完整
main程序并编译运行能用变量、分支、循环表达基本逻辑
能用函数拆分代码
能用数组和字符串处理数据
能理解指针的地址和解引用
能使用
malloc/free做简单动态内存管理能定位常见编译错误和运行错误
下一步可以继续学习:
结构体
文件读写
枚举
多文件编译
链表、栈、队列等数据结构
C 语言真正的学习方式不是背概念,而是不断写小程序、改错误、看输出。只要你坚持把每个示例都手敲一遍,基础会越来越稳。
相关推荐
C语言基础知识(一)-字节、变量、常量、数据类型以及进制-CSDN博客文章浏览阅读8.5k次,点赞16次,收藏70次。本文深入浅出地介绍了C语言的基础概念,包括字节、位、数据类型、变量、常量等核心内容,并详细讲解了不同进制之间的转换及应用。https://shuaici.blog.csdn.net/article/details/118601915C语言基础知识(二)-输入、输出以及运算符-CSDN博客文章浏览阅读1.2w次,点赞8次,收藏53次。本文详细介绍了C语言中的输入输出函数,包括printf和scanf的使用方法,以及各种格式控制符和标志字符。同时,文章还探讨了算术、关系、逻辑和位运算符,以及赋值运算符和杂项运算符的应用。通过实例代码加深了对这些概念的理解。
https://shuaici.blog.csdn.net/article/details/118798852
