当前位置: 首页 > ai >正文

C语言精讲(视频教程)

概述

  • C以高效、灵活、可移植为核心特点,是操作系统、嵌入式系统、驱动程序开发的基石,也是学习计算机底层原理(如内存管理、指针机制)的“入门钥匙”。
  • 视频教程:https://pan.quark.cn/s/e258d116f7f2

一、C语言基础:构建程序的“砖瓦”

C语言程序的基本结构由头文件包含、函数定义、语句块组成,所有程序的入口都是main()函数。

1.1 基本语法规则

(1)程序结构示例

一个最简单的C语言程序(输出“Hello, C!”):

#include <stdio.h>  // 头文件包含:引入标准输入输出库// 主函数:程序入口,int表示返回值类型,argc/argv为命令行参数(可省略)
int main(int argc, char *argv[]) {printf("Hello, C!\n");  // 语句:调用printf函数输出内容,末尾需加“;”return 0;  // 主函数返回0,表示程序正常结束
}
  • 头文件:以#include开头,用于引入库函数的声明(如stdio.h包含printf的声明),分为标准库头文件(<xxx.h>)和自定义头文件("xxx.h")。
  • 函数:C语言的基本执行单元,格式为返回值类型 函数名(参数列表) { 语句块 }main()是唯一必须定义的函数。
  • 语句:每条执行指令以;结尾,printf是标准库中的输出函数,\n表示换行符。
(2)数据类型:变量的“身份标识”

C语言的数据类型分为基本类型构造类型,基本类型是程序的“最小数据单元”:

类型分类具体类型占用内存(32位系统)用途说明
整数类型char1字节存储字符(本质是ASCII码)或小整数
short2字节短整数
int4字节常用整数类型
long4字节(32位)/8字节(64位)长整数
浮点数类型float4字节单精度浮点数(精度约6-7位)
double8字节双精度浮点数(精度约15-17位)
布尔类型_Bool(C99新增)1字节存储0(假)或1(真),需包含<stdbool.h>
无类型void表示“无返回值”或“无类型指针”

变量定义规则:必须先定义、后使用,格式为类型名 变量名;,可初始化(如int a = 10;)。
常量:不可修改的值,分为字面常量(如103.14'A')和const修饰的常量(如const int MAX = 100;)。

(3)运算符:变量的“计算工具”

C语言运算符按功能分为算术、赋值、关系、逻辑、位运算等,需注意优先级(如*高于+)和结合性(如赋值运算符“从右到左”)。

运算符类型常用运算符示例优先级(高→低)
算术运算符+ - * / %(取余)3 + 25 % 2 = 1
赋值运算符= += -= *= /=a += 3a = a+3
关系运算符> < >= <= == !=a > 5(返回0或1)
逻辑运算符&&(与)、`(或)、!`(非)
位运算符&(与)、`(或)、^`(异或)、`<<`(左移)、`>>`(右移)3 << 1 = 6(二进制11110

注意

  • 整数除法(如5 / 2)结果为整数(2),需转换为浮点数(如5.0 / 2)才能得到2.5
  • 位运算仅适用于整数类型,是嵌入式开发中操作硬件寄存器的核心手段。
(4)控制流:程序的“执行路线”

控制流决定程序的执行顺序,分为顺序结构(默认)、分支结构(条件判断)、循环结构(重复执行)。

① 分支结构:if-elseswitch
  • if-else:处理二分支或多分支判断
    int score = 85;
    if (score >= 90) {printf("优秀\n");
    } else if (score >= 80) {printf("良好\n");
    } else {printf("合格\n");
    }
    
  • switch:处理多分支(基于整数/字符匹配),break用于跳出分支,default处理默认情况
    char grade = 'B';
    switch (grade) {case 'A': printf("90-100\n"); break;case 'B': printf("80-89\n"); break;default: printf("其他\n");
    }
    
② 循环结构:forwhiledo-while
  • for:适合已知循环次数的场景,格式为for(初始化; 条件; 更新)
    // 输出1-5
    for (int i = 1; i <= 5; i++) {printf("%d ", i);
    }
    
  • while:适合未知循环次数的场景,先判断条件再执行
    int i = 1;
    while (i <= 5) {printf("%d ", i);i++;
    }
    
  • do-while:先执行一次循环体,再判断条件(至少执行一次)
    int i = 1;
    do {printf("%d ", i);i++;
    } while (i <= 5);
    
③ 跳转语句:breakcontinuereturn
  • break:跳出当前循环或switch
  • continue:跳过本次循环剩余语句,直接进入下一次循环;
  • return:结束当前函数,返回指定值(无返回值函数用return;)。

1.2 数组:同类型数据的“集合”

数组是连续存储的同类型变量集合,通过“数组名+下标”访问元素(下标从0开始)。

(1)一维数组
  • 定义格式:类型名 数组名[数组长度];(长度必须是常量)
  • 初始化:可部分初始化(未初始化元素默认为0
    int arr1[5] = {1, 2, 3};  // 等价于{1,2,3,0,0}
    int arr2[] = {1,2,3,4};   // 长度自动推导为4
    
  • 访问元素:arr[0](第一个元素)、arr[4](第五个元素),注意避免下标越界(如访问arr[5]会导致内存越界,程序崩溃)。
(2)二维数组

可理解为“数组的数组”,常用于存储表格类数据,定义格式:类型名 数组名[行长度][列长度];

int matrix[2][3] = {{1,2,3},  // 第一行{4,5,6}   // 第二行
};
printf("%d", matrix[1][2]);  // 输出6(第二行第三列)

二、C语言核心:指针与函数(难点与重点)

指针和函数是C语言的“灵魂”,指针直接操作内存地址,函数实现代码复用,二者结合是C语言灵活性的核心来源。

2.1 指针:内存地址的“名片”

(1)指针的本质
  • 内存地址:计算机中每个字节的唯一编号(如0x0012FF44),指针变量就是存储“内存地址”的变量。
  • 定义格式:类型名 *指针名;*表示“指针类型”,类型名表示指针指向的数据类型。
(2)指针的基本操作
int a = 10;        // 定义普通变量a,内存地址假设为0x0012FF44
int *p = &a;       // 定义指针p,存储a的地址(&是“取地址符”)printf("%d", a);   // 输出10(直接访问a的值)
printf("%p", &a);  // 输出0x0012FF44(a的地址,%p是地址格式化符)
printf("%p", p);   // 输出0x0012FF44(p存储的地址)
printf("%d", *p);  // 输出10(*是“解引用符”,通过p的地址访问a的值)*p = 20;           // 通过指针修改a的值
printf("%d", a);   // 输出20
(3)指针的核心应用场景
  1. 函数传参:实现“引用传递”
    C语言默认是“值传递”(函数接收参数的副本,修改副本不影响原变量),通过指针可实现“修改原变量”:

    // 交换两个整数的值
    void swap(int *x, int *y) {int temp = *x;*x = *y;*y = temp;
    }int main() {int a = 1, b = 2;swap(&a, &b);  // 传入a和b的地址printf("a=%d, b=%d", a, b);  // 输出a=2, b=1return 0;
    }
    
  2. 操作数组:数组名本质是“常量指针”
    数组名存储数组首元素的地址(不可修改),因此arr[i]等价于*(arr + i)

    int arr[] = {1,2,3,4};
    int *p = arr;  // p指向数组首元素(等价于&arr[0])
    printf("%d", *(p+2));  // 输出3(访问arr[2])
    
  3. 动态内存分配
    结合malloc/free函数(需包含<stdlib.h>),可在程序运行时动态申请内存(数组长度可动态确定):

    int n = 5;
    int *p = (int*)malloc(n * sizeof(int));  // 申请5个int的内存(20字节)
    if (p == NULL) {  // 必须检查内存是否申请成功printf("内存申请失败\n");return 1;
    }
    for (int i = 0; i < n; i++) {p[i] = i + 1;  // 给动态数组赋值
    }
    free(p);  // 释放内存,避免内存泄漏
    p = NULL;  // 避免野指针(指向已释放内存的指针)
    

2.2 函数:代码复用的“单元”

(1)函数的定义与调用

函数的核心是“输入参数→处理逻辑→输出返回值”,需满足“声明与定义一致”(参数类型、返回值类型匹配)。

  • 函数声明:告诉编译器函数的“接口”,通常放在头文件中
    // 声明:返回值int,参数为两个int
    int add(int x, int y);
    
  • 函数定义:实现函数的具体逻辑
    // 定义:与声明一致
    int add(int x, int y) {return x + y;  // 返回两数之和
    }
    
  • 函数调用:在main()或其他函数中使用
    int main() {int result = add(3, 5);  // 调用add,传入3和5printf("%d", result);    // 输出8return 0;
    }
    
(2)函数的高级特性
  1. 函数参数默认值:C语言标准(C89/C99)不支持默认参数,需通过“函数重载”的模拟实现(如定义多个参数个数不同的函数)。
  2. 函数指针:指向函数的指针,可实现“函数回调”(如排序函数qsort中传入比较函数):
    // 比较函数:用于qsort排序(升序)
    int compare(const void *a, const void *b) {return *(int*)a - *(int*)b;
    }int main() {int arr[] = {3,1,2};qsort(arr, 3, sizeof(int), compare);  // 传入比较函数的地址for (int i = 0; i < 3; i++) {printf("%d ", arr[i]);  // 输出1 2 3}return 0;
    }
    

三、C语言进阶:结构体与文件操作

3.1 结构体:自定义复杂数据类型

结构体(struct)用于将不同类型的数据打包成一个整体,解决“单一类型无法描述复杂对象”的问题(如描述“学生”需要姓名、年龄、成绩等)。

(1)结构体的定义与使用
// 定义结构体类型(struct Student)
struct Student {char name[20];  // 姓名(字符数组)int age;        // 年龄float score;    // 成绩
};int main() {// 定义结构体变量并初始化struct Student stu1 = {"Zhang San", 20, 90.5};// 访问结构体成员(用“.”)printf("Name: %s\n", stu1.name);    // 输出Zhang Sanprintf("Age: %d\n", stu1.age);      // 输出20// 结构体指针(用“->”访问成员)struct Student *p = &stu1;printf("Score: %.1f\n", p->score);  // 输出90.5return 0;
}
(2)结构体数组

存储多个结构体对象(如多个学生信息):

struct Student class[2] = {{"Zhang San", 20, 90.5},{"Li Si", 19, 88.0}
};
printf("%s", class[1].name);  // 输出Li Si

3.2 文件操作:数据持久化

文件操作是将数据存储到磁盘(或从磁盘读取)的核心手段,C语言通过文件指针FILE*)操作文件,需包含<stdio.h>

(1)文件打开与关闭
  • 打开文件:FILE *fopen(const char *filename, const char *mode);
    mode(打开模式)常用值:
    • "r":只读(文件必须存在);
    • "w":只写(文件不存在则创建,存在则清空);
    • "a":追加(在文件末尾写入,文件不存在则创建);
    • "rb"/"wb":二进制文件的读/写。
  • 关闭文件:int fclose(FILE *stream);(必须关闭,避免资源泄漏)。
(2)文件读写操作
① 文本文件读写
  • 写入:fprintf(FILE *stream, const char *format, ...);(类似printf,但输出到文件)
  • 读取:fscanf(FILE *stream, const char *format, ...);(类似scanf,但从文件读取)

示例:将学生信息写入文件,再读取并输出:

int main() {struct Student stu = {"Zhang San", 20, 90.5};FILE *fp = fopen("student.txt", "w");  // 打开文件(写模式)if (fp == NULL) {  // 检查文件是否成功打开perror("fopen failed");  // 输出错误信息return 1;}// 写入数据到文件fprintf(fp, "%s %d %.1f", stu.name, stu.age, stu.score);fclose(fp);  // 关闭文件// 读取文件数据struct Student read_stu;fp = fopen("student.txt", "r");  // 重新打开(读模式)fscanf(fp, "%s %d %.1f", read_stu.name, &read_stu.age, &read_stu.score);printf("%s %d %.1f", read_stu.name, read_stu.age, read_stu.score);  // 输出写入的数据fclose(fp);return 0;
}
② 二进制文件读写

适合存储结构体等复杂数据(避免文本格式转换的损耗):

  • 写入:size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
  • 读取:size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

四、C语言工程实践:规范与避坑

4.1 常见错误与避坑

  1. 野指针:指针未初始化(指向随机地址)或指向已释放的内存,访问野指针会导致程序崩溃。
    避坑:指针定义时初始化(如int *p = NULL;),释放内存后将指针置为NULL

  2. 内存泄漏:使用malloc/calloc申请的内存未用free释放,长期运行会耗尽内存。
    避坑:申请内存后,确保在所有退出路径(如returnbreak)中调用free

  3. 数组下标越界:访问数组时下标超过“长度-1”,会破坏其他变量的内存,导致程序行为异常。
    避坑:用常量定义数组长度(如#define LEN 5),循环中检查下标范围。

  4. 函数声明与定义不一致:如声明时参数为int,定义时为float,编译器会报错或产生未定义行为。
    避坑:将函数声明放在头文件中,定义与声明严格匹配。

4.2 编码规范

  1. 命名规范:变量/函数名用“小写+下划线”(如student_score),常量用“大写+下划线”(如MAX_SIZE)。
  2. 注释规范:关键逻辑用单行注释(//),函数功能用多行注释(/* ... */),说明参数和返回值。
  3. 头文件保护:自定义头文件中添加“防止重复包含”的宏(避免编译错误):
    // student.h
    #ifndef STUDENT_H  // 如果未定义STUDENT_H
    #define STUDENT_H  // 定义STUDENT_Hstruct Student {char name[20];int age;float score;
    };#endif  // 结束条件编译
    

五、总结

C语言的核心优势在于对内存的直接控制和极高的执行效率,它的难点(指针、内存管理)正是其灵活性的来源。学习C语言不仅是掌握一门编程语言,更是理解计算机底层工作原理(如内存布局、指令执行)的过程。

对于初学者,建议按“基础语法→函数与指针→结构体与文件→工程实践”的顺序学习,通过大量编程练习(如实现排序算法、链表、小型文件管理系统)巩固知识点,同时注重编码规范和错误排查能力的培养。掌握C语言后,再学习C++、Java、Python等语言,会对“内存管理”“面向过程/面向对象”等概念有更深刻的理解。

http://www.xdnf.cn/news/20236.html

相关文章:

  • 打包 Uniapp
  • Redisson分布式锁:看门狗机制与续期原理
  • nginx安装部署(备忘)
  • ecplise配置maven插件
  • 【知识点讲解】稀疏注意力与LSH技术:从基础到前沿的完整指南
  • MHA高可用架构
  • 多线程(六) ~ 定时器与锁
  • 驱动开发系列71 - GLSL编译器实现 - 指令选择
  • python 逻辑运算练习题
  • HttpClient、OkHttp 和 WebClient
  • 贪心算法应用:交易费优化问题详解
  • OpenLayers常用控件 -- 章节七:测量工具控件教程
  • 《sklearn机器学习——聚类性能指标》Fowlkes-Mallows 得分
  • Java学习笔记二(类)
  • 【3D图像算法技术】如何在Blender中对复杂物体进行有效减面?
  • 【EXPLAIN详解:MySQL查询优化师的显微镜】
  • MacOS 使用 luarocks+wrk+luajit
  • Docker 本地开发环境搭建(MySQL5.7 + Redis7 + Nginx + 达梦8)- Windows11 版 2.0
  • Mac Intel 芯片 Docker 一键部署 Neo4j 最新版本教程
  • 【Android 消息机制】Handler
  • PDF教程|如何把想要的网页保存下来?
  • docker 推送仓库(含搭建、代理等)
  • 服务器线程高占用定位方法
  • 使用 Shell 脚本监控服务器 IOWait 并发送邮件告警
  • Python带状态生成器完全指南:从基础到高并发系统设计
  • C#实现导入CSV数据到List<T>的完整教程
  • 【基础-单选】用哪一种装饰器修饰的struct表示该结构体具有组件化能力?
  • Playwright携手MCP:AI智能体实现自主化UI回归测试
  • 第26节:GPU加速计算与Compute Shader探索
  • Homebrew执行brew install出现错误(homebrew-bottles)