零基础小白从hello world到函数指针C语言入门自学完整学习资源大合集
嗨,朋友!想学C语言吗?那你还真来对地方了。
别被”函数指针”这四个字吓跑,我也是从写下第一行 printf("Hello World"); 到现在摸透指针的家伙,一步步走过来的。这篇内容不整那些虚头巴脑的,你就把它当成一个老学长坐在你对面,一点一点教你。
一、为什么学C语言?
我见过太多人问我:”现在都Python、Java满天飞了,还学C干嘛?”
说句实在话,C语言是所有编程语言的祖师爷。你看Python、Java、C#、Go、Rust,哪一个是没受C影响的?学C就像学武功要扎马步,基础不牢,后面的东西再炫也是空中楼阁。
更重要的是:嵌入式开发、操作系统、驱动程序、游戏引擎、编译器——这些高薪领域,C语言是绕不过去的硬通货。
二、学习路线总览
在正式开始之前,先给你一个地图,让你知道咱们要从哪儿走到哪儿:
阶段一:环境搭建 → 写出第一个程序
阶段二:基础语法 → 变量、数据类型、运算符
阶段三:流程控制 → if/else、switch、循环
阶段四:数组与字符串 → 处理批量数据
阶段五:函数 → 模块化编程
阶段六:指针 → C语言的灵魂
阶段七:函数指针 → 指针的进阶玩法
一共七步,每一步我都给你配上可以直接跑起来的代码,保证你跟着敲,就能学会。
三、阶段一:环境搭建 + Hello World
3.1 安装编译器
对于零基础的朋友,推荐最省心的方案:
Windows用户:下载 MinGW 或者用 Code::Blocks(自带编译器,开箱即用)
Mac用户:终端输入 xcode-select --install 就装好了
Linux用户:sudo apt install gcc (Ubuntu/Debian)
3.2 你的第一个程序
打开任何文本编辑器(VS Code、Notepad++、甚至记事本都行),写下这几行:
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
保存为 hello.c,然后编译运行:
gcc hello.c -o hello
./hello
输出:
Hello, World!
3.3 逐行拆解
别急着跳过,这几行代码里藏着C语言的骨架:
#include <stdio.h> // 包含标准输入输出库,printf就住在这儿
// 没有这行,编译器不知道printf是啥
int main() { // 程序入口,所有C程序都必须有且只有一个main函数
// int表示函数执行完会返回一个整数给操作系统
printf("Hello, World!\n");
// printf是"打印格式化"的缩写,把内容输出到屏幕
// \n 是换行符,相当于按了一下回车
return 0; // 返回0表示程序正常结束
// 返回非0值通常表示出错了
}
💡 小练习:把
Hello, World!改成你自己的名字,比如printf("你好,我是小明!\n");,然后重新编译运行。
四、阶段二:变量与数据类型
4.1 变量是什么?
想象你在搬东西,变量就像一个贴着标签的箱子,你可以往里面放东西,也可以把东西拿出来用。
#include <stdio.h>
int main() {
int age = 25; // 整数类型,存年龄
float height = 175.5; // 浮点数,存身高
char grade = 'A'; // 字符类型,存成绩等级
double salary = 8500.50;// 双精度浮点,存工资(更精确)
int count = 0; // 计数器
printf("年龄: %d\n", age);
printf("身高: %.1f cm\n", height);
printf("成绩: %c\n", grade);
printf("工资: %.2f 元\n", salary);
printf("数量: %d\n", count);
return 0;
}
输出:
年龄: 25
身高: 175.5 cm
成绩: A
工资: 8500.50 元
数量: 0
4.2 数据类型速查表
| 类型 | 占内存 | 范围 | 格式符 |
|---|---|---|---|
char |
1字节 | -128~127 或 0~255 | %c |
int |
4字节 | 约±21亿 | %d |
float |
4字节 | 约7位有效数字 | %f |
double |
8字节 | 约15位有效数字 | %lf |
long |
4或8字节 | 更大整数 | %ld |
short |
2字节 | -32768~32767 | %hd |
4.3 常量与变量修改
#include <stdio.h>
int main() {
int score = 90;
printf("初始分数: %d\n", score);
score = 95; // 修改变量的值
printf("更新后分数: %d\n", score);
// score = "abc"; // ❌ 这行会报错!int不能存字符串
// score = 3.14; // ⚠️ 虽然能编译,但会丢精度,变成3
return 0;
}
五、阶段三:运算符与表达式
5.1 算术运算符
#include <stdio.h>
int main() {
int a = 10, b = 3;
printf("a + b = %d\n", a + b); // 13
printf("a - b = %d\n", a - b); // 7
printf("a * b = %d\n", a * b); // 30
printf("a / b = %d\n", a / b); // 3 (整数除法!)
printf("a %% b = %d\n", a % b); // 1 (取余数)
// 注意:printf里要用%%才能打印%
printf("a %% b = %d\n", a % b);
return 0;
}
5.2 自增自减运算符
#include <stdio.h>
int main() {
int x = 5;
printf("x = %d\n", x++); // 先打印5,再变成6(后置++)
printf("x = %d\n", x); // 6
int y = 5;
printf("y = %d\n", ++y); // 先变成6,再打印6(前置++)
return 0;
}
⚠️ 重点注意:
x++和++x的结果虽然都是让x加1,但”先打印还是后打印”这个顺序,是面试常考题!
5.3 关系与逻辑运算符
#include <stdio.h>
int main() {
int a = 10, b = 20;
// 关系运算符:结果是1(真)或0(假)
printf("a > b: %d\n", a > b); // 0
printf("a < b: %d\n", a < b); // 1
printf("a == b: %d\n", a == b); // 0
printf("a != b: %d\n", a != b); // 1
// 逻辑运算符
printf("(a<b) && (a>0): %d\n", (a < b) && (a > 0)); // 1
printf("(a>b) || (a>0): %d\n", (a > b) || (a > 0)); // 1
printf("!(a==b): %d\n", !(a == b)); // 1
return 0;
}
六、阶段四:流程控制
6.1 if / else 判断
#include <stdio.h>
int main() {
int score;
printf("请输入成绩: ");
// scanf用于读取用户输入,&是取地址符号,后面讲指针时会详细说
scanf("%d", &score);
if (score >= 90) {
printf("优秀!🎉\n");
} else if (score >= 80) {
printf("良好!👍\n");
} else if (score >= 60) {
printf("及格,继续加油!💪\n");
} else {
printf("不及格,要努力了!📚\n");
}
return 0;
}
6.2 switch 语句
#include <stdio.h>
int main() {
int day;
printf("请输入星期几 (1-7): ");
scanf("%d", &day);
switch (day) {
case 1:
printf("星期一,新的一周开始了!\n");
break; // 别忘了break!否则会继续执行后面的case
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
case 5:
printf("星期五,快周末了!🎉\n");
break;
case 6:
printf("星期六,休息日!\n");
break;
case 7:
printf("星期日,好好休息~\n");
break;
default:
printf("输入无效,请输入1-7的数字\n");
}
return 0;
}
6.3 循环结构
while循环:
#include <stdio.h>
int main() {
int i = 1;
while (i <= 5) {
printf("第 %d 次循环\n", i);
i++; // 别忘了让循环条件 eventually 变成假!
}
return 0;
}
for循环(最常用):
#include <stdio.h>
int main() {
// for (初始化; 条件; 更新)
for (int i = 1; i <= 100; i++) {
if (i % 7 == 0) {
printf("%d 是7的倍数\n", i);
}
}
return 0;
}
do-while循环(至少执行一次):
#include <stdio.h>
int main() {
int num;
do {
printf("请输入一个正数: ");
scanf("%d", &num);
} while (num <= 0); // 只有输入正数才会退出循环
printf("你输入了: %d\n", num);
return 0;
}
七、阶段五:数组与字符串
7.1 数组
#include <stdio.h>
int main() {
// 定义数组:类型 数组名[大小]
int scores[5] = {85, 92, 78, 96, 88};
// 访问数组元素(下标从0开始!)
printf("第一个成绩: %d\n", scores[0]); // 85
printf("最后一个成绩: %d\n", scores[4]); // 88
// 遍历数组
for (int i = 0; i < 5; i++) {
printf("scores[%d] = %d\n", i, scores[i]);
}
// 计算平均分
int sum = 0;
for (int i = 0; i < 5; i++) {
sum += scores[i];
}
printf("平均分: %.2f\n", (float)sum / 5);
return 0;
}
7.2 字符串
C语言没有”字符串”这个类型,字符串本质上是字符数组,而且末尾必须有个 \0 作为结束标志。
#include <stdio.h>
#include <string.h> // 字符串处理函数库
int main() {
// 定义字符串的几种方式
char name1[] = "Hello"; // 推荐方式,自动加上\0
char name2[20] = "World"; // 指定大小
char name3[] = {'H','i','\0'}; // 手动写法
printf("name1: %s\n", name1);
printf("name2: %s\n", name2);
// 字符串长度
printf("name1长度: %d\n", strlen(name1)); // 5
// 字符串拼接
char greeting[50];
strcpy(greeting, name1); // 复制
strcat(greeting, " "); // 追加空格
strcat(greeting, name2); // 追加
printf("%s\n", greeting); // Hello World
// 字符串比较
if (strcmp(name1, "Hello") == 0) {
printf("name1 就是 Hello!\n");
}
return 0;
}
📌 关键知识点:
strlen返回的长度不包含\0,而sizeof返回的是实际分配的内存大小(包含\0)。
char s[] = "abc";
printf("%lu\n", strlen(s)); // 3(不含\0)
printf("%lu\n", sizeof(s)); // 4(含\0)
八、阶段六:函数
8.1 什么是函数?
函数就是把一段代码打包成一个”工具”,你想用的时候就叫它一声。
#include <stdio.h>
// 函数定义:返回类型 函数名(参数列表)
int add(int a, int b) {
return a + b;
}
void sayHello(char *name) {
printf("你好, %s!\n", name);
}
// 计算阶乘
long factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 递归调用自己
}
int main() {
printf("3 + 5 = %d\n", add(3, 5)); // 8
sayHello("小明"); // 你好, 小明!
printf("5的阶乘 = %ld\n", factorial(5)); // 120
return 0;
}
8.2 函数的三大要素
┌─────────────────────────────────────────┐
│ 返回类型 │ 函数名 │ 参数 │
│ int │ add │ (int a) │
│ void │ sayHello │(char *s) │
│ float │ calculate │(float x) │
└─────────────────────────────────────────┘
- 返回类型:函数执行完交出来的东西,
void表示不返回任何东西 - 函数名:调用时用的名字,要能体现函数的作用
- 参数:调用时传进去的数据
8.3 值传递 vs 指针传递
这是新手最容易混淆的地方:
#include <stdio.h>
// 值传递:副本,改不了原来的值
void swap_value(int a, int b) {
int temp = a;
a = b;
b = temp;
}
// 指针传递:操作原始数据,能改变原来的值
void swap_pointer(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int x = 10, y = 20;
swap_value(x, y);
printf("值传递后: x=%d, y=%d\n", x, y); // x=10, y=20(没变!)
swap_pointer(&x, &y);
printf("指针传递后: x=%d, y=%d\n", x, y); // x=20, y=10(变了!)
return 0;
}
💡 为什么值传递改不了? 因为函数里操作的是复制的副本,函数结束后副本就丢了。指针传递传的是地址,函数里通过地址直接操作原来的内存。
九、阶段七:指针——C语言的灵魂
9.1 指针是什么?
指针就是一个存储地址的变量。想象你有一个快递柜,变量是柜子里的东西,指针就是那张写着柜子编号的纸条。
#include <stdio.h>
int main() {
int age = 25;
// & 是取地址运算符,* 是指针运算符
int *p = &age; // p 存储了 age 的地址
printf("age 的值: %d\n", age); // 25
printf("age 的地址: %p\n", (void*)&age); // 某个内存地址
printf("p 的值(也就是age的地址): %p\n", (void*)p);
printf("通过指针访问age: %d\n", *p); // 25
// 通过指针修改值
*p = 30;
printf("修改后 age = %d\n", age); // 30
return 0;
}
9.2 指针的各种形式
#include <stdio.h>
int main() {
int num = 100;
int *p1 = # // 指向int的指针
float f = 3.14;
float *p2 = &f; // 指向float的指针
char c = 'A';
char *p3 = &c; // 指向char的指针
// 指针 Arithmetic(指针运算)
int arr[] = {10, 20, 30, 40, 50};
int *ptr = arr; // 数组名本身就是首元素地址
printf("*ptr = %d\n", *ptr); // 10
printf("*(ptr+1) = %d\n", *(ptr+1)); // 20
printf("*(ptr+2) = %d\n", *(ptr+2)); // 30
// 指针遍历数组
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, *(ptr + i));
}
return 0;
}
9.3 指针与数组的关系
#include <stdio.h>
int main() {
int arr[5] = {1, 2, 3, 4, 5};
printf("arr[0] = %d\n", arr[0]); // 1
printf("*arr = %d\n", *arr); // 1(数组名就是首元素地址)
printf("&arr[0] = %p\n", (void*)&arr[0]);
printf("arr = %p\n", (void*)arr); // 和上面相同!
// 指针遍历数组的三种等价写法
printf("\n--- 遍历数组 ---\n");
// 写法1:下标法
for (int i = 0; i < 5; i++) {
printf("arr[%d] = %d\n", i, arr[i]);
}
// 写法2:指针+下标
int *p = arr;
for (int i = 0; i < 5; i++) {
printf("*(p+%d) = %d\n", i, *(p + i));
}
// 写法3:指针++
for (int *q = arr; q < arr + 5; q++) {
printf("*q = %d\n", *q);
}
return 0;
}
9.4 多级指针(指针的指针)
#include <stdio.h>
int main() {
int value = 42;
int *p1 = &value; // p1 指向 value
int **p2 = &p1; // p2 指向 p1
int ***p3 = &p2; // p3 指向 p2
printf("value = %d\n", value); // 42
printf("*p1 = %d\n", *p1); // 42
printf("**p2 = %d\n", **p2); // 42
printf("***p3 = %d\n", ***p3); // 42
// 通过多级指针修改值
***p3 = 100;
printf("修改后 value = %d\n", value); // 100
return 0;
}
十、阶段八:函数指针——指针的终极形态
10.1 什么是函数指针?
之前学的指针都是指向数据的(变量、数组),而函数指针是指向函数的。
函数在内存里也有一段代码区域,它也有地址。函数指针就是存储这个地址的变量。
10.2 基本用法
#include <stdio.h>
// 先定义几个普通函数
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
void greet(const char *name) {
printf("你好, %s!\n", name);
}
int main() {
// 定义函数指针
int (*pAdd)(int, int) = add; // 指向add函数的指针
int (*pMul)(int, int) = multiply; // 指向multiply函数的指针
// 通过函数指针调用函数
printf("add(3, 5) = %d\n", pAdd(3, 5)); // 8
printf("multiply(3, 5) = %d\n", pMul(3, 5)); // 15
// 注意:(*pAdd)(3,5) 和 pAdd(3,5) 是等价的
printf("add(3, 5) = %d\n", (*pAdd)(3, 5)); // 8
return 0;
}
📌 记忆技巧:把函数名当成”指向函数的指针”来用,而定义函数指针时,用
()把*指针名括起来,优先级才正确。
10.3 函数指针数组——切换不同函数
#include <stdio.h>
int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) {
if (b == 0) return 0;
return a / b;
}
int main() {
// 函数指针数组:每个元素都是一个函数指针
int (*operations[4])(int, int) = {add, subtract, multiply, divide};
int a = 10, b = 3;
printf("%d + %d = %d\n", a, b, operations[0](a, b)); // 13
printf("%d - %d = %d\n", a, b, operations[1](a, b)); // 7
printf("%d * %d = %d\n", a, b, operations[2](a, b)); // 30
printf("%d / %d = %d\n", a, b, operations[3](a, b)); // 3
return 0;
}
10.4 函数指针作参数——回调函数的雏形
这是函数指针最强大的用法之一:把一个函数当参数传给另一个函数。
#include <stdio.h>
// 计算器函数:接收两个数和操作函数
int calculator(int a, int b, int (*op)(int, int)) {
return op(a, b);
}
int add(int a, int b) { return a + b; }
int multiply(int a, int b) { return a * b; }
int main() {
printf("10 + 5 = %d\n", calculator(10, 5, add)); // 15
printf("10 * 5 = %d\n", calculator(10, 5, multiply)); // 50
return 0;
}
10.5 完整实战:简易计算器
把所有学到的东西串起来,做一个真正能跑的小程序:
#include <stdio.h>
#include <string.h>
// ========== 函数声明 ==========
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
char* getOperatorName(int op);
double performOperation(double a, double b, int op);
void printMenu();
void runCalculator();
// ========== 函数实现 ==========
double add(double a, double b) {
return a + b;
}
double subtract(double a, double b) {
return a - b;
}
double multiply(double a, double b) {
return a * b;
}
double divide(double a, double b) {
if (b == 0) {
printf("\n⚠️ 错误:除数不能为0!\n");
return 0;
}
return a / b;
}
char* getOperatorName(int op) {
switch (op) {
case 1: return "+";
case 2: return "-";
case 3: return "×";
case 4: return "÷";
default: return "?";
}
}
// 函数指针数组,核心在这里!
double (*operations[5])(double, double) = {
NULL, // 占位,让下标从1开始
add,
subtract,
multiply,
divide
};
double performOperation(double a, double b, int op) {
if (op < 1 || op > 4) {
printf("\n⚠️ 无效的操作编号\n");
return 0;
}
return operations[op](a, b);
}
void printMenu() {
printf("\n========== 简易计算器 ==========\n");
printf(" 1. 加法 (+)\n");
printf(" 2. 减法 (-)\n");
printf(" 3. 乘法 (×)\n");
printf(" 4. 除法 (÷)\n");
printf(" 0. 退出\n");
printf("================================\n");
}
void runCalculator() {
int choice;
double num1, num2, result;
printf("\n👋 欢迎使用简易计算器!\n");
do {
printMenu();
printf("请选择操作 (0-4): ");
scanf("%d", &choice);
if (choice == 0) {
printf("感谢使用,再见!👋\n");
break;
}
printf("请输入第一个数字: ");
scanf("%lf", &num1);
printf("请输入第二个数字: ");
scanf("%lf", &num2);
result = performOperation(num1, num2, choice);
printf("结果: %.2f %s %.2f = %.2f\n",
num1, getOperatorName(choice), num2, result);
} while (1);
}
// ========== 主函数 ==========
int main() {
runCalculator();
return 0;
}
运行效果:
👋 欢迎使用简易计算器!
========== 简易计算器 ==========
1. 加法 (+)
2. 减法 (-)
3. 乘法 (×)
4. 除法 (÷)
0. 退出
================================
请选择操作 (0-4): 3
请输入第一个数字: 6
请输入第二个数字: 7
结果: 6.00 × 7.00 = 42.00
========== 简易计算器 ==========
1. 加法 (+)
2. 减法 (-)
3. 乘法 (×)
4. 除法 (÷)
0. 退出
================================
请选择操作 (0-4): 4
请输入第一个数字: 10
请输入第二个数字: 0
⚠️ 错误:除数不能为0!
结果: 10.00 ÷ 0.00 = 0.00
========== 简易计算器 ==========
1. 加法 (+)
2. 减法 (-)
3. 乘法 (×)
4. 除法 (÷)
0. 退出
================================
请选择操作 (0-4): 0
感谢使用,再见!👋
10.6 函数指针 vs 宏 vs 普通函数调用
| 方式 | 示例 | 特点 |
|---|---|---|
| 普通函数调用 | add(a, b) |
最简单,但调用方式固定 |
| 宏函数 | #define OP(a,b) ((a)+(b)) |
编译时展开,无函数调用开销,但可读性差 |
| 函数指针 | (*op)(a, b) |
灵活,可以在运行时切换行为 |
十一、学习路线与资源推荐
11.1 推荐学习顺序
第1周:环境搭建 + Hello World + 变量数据类型
第2周:运算符 + if/switch 分支语句
第3周:while/for/do-while 循环
第4周:数组 + 字符串处理
第5周:函数定义与调用
第6周:指针入门(这是最难的坎!)
第7周:指针进阶 + 函数指针
第8周:综合练习(把前面所有知识串起来)
11.2 免费学习资源
视频教程:
- 浙大翁恺C语言课程(B站可看)—— 国内最好的C语言入门课,没有之一
- 莫烦Python的C语言教程—— 短小精悍,适合快速上手
在线练习:
- Runoob C语言教程 —— 边学边练
- LeetCode —— 学完基础后可以刷简单题
书籍推荐:
- 《C Primer Plus》—— 新手圣经,讲解极其详细
- 《征服C指针》—— 专攻指针,日本作者写得超级易懂
11.3 常见坑点提醒
❌ 忘了给字符串加 \0 → 输出乱码
❌ 用 = 比较字符串 → 比较的是地址,不是内容,要用 strcmp
❌ 数组越界访问 → 程序崩溃或数据错乱
❌ 忘记 free 内存 → 内存泄漏(后面学动态内存分配时会遇到)
❌ printf 的 %d 和 %f 混用 → 输出结果完全不对
十二、给你的建议
- 不要只看不敲——编程是手艺活,看懂和会写是两码事
- 每学一个知识点,立刻写代码验证——哪怕只是打印个值
- 学会看错误信息——编译错误不是敌人,是老师在告诉你哪里错了
- 函数指针学不会很正常——这个本来就是C语言里比较难的部分,多写几个例子慢慢就懂了
- 保持耐心——我当年学指针的时候,也盯着代码看了半小时才反应过来,正常
结语
从 printf("Hello World"); 到函数指针,这条路说长不长,说短也不短。但只要你一步一个脚印地跟着敲代码、跑程序、改bug,两个月左右就能把C语言基础拿下。
C语言很难吗?不难。C语言简单吗?也不简单。它就像学骑自行车,刚摔几次就觉得难,一旦掌握了平衡的感觉,就再也忘不掉了。
加油,期待你的第一个完整C程序跑起来的那一刻!🚀
