天琴座酒馆

🏠主页
📖博客主页
📅时间轴
📁归档
🏷️标签
📊统计
📋文档中心
💎资源分享
💬留言板
👤关于
🏠主页
📝博客
📖博客主页
📅时间轴
📁归档
🏷️标签
📊统计
📚文档
📋文档中心
🎁八宝盒
💎资源分享
💬留言板
👤关于
© 2026 Lyra Ring. Github
关于本站|留言板
📂 C语言#C语言基础#程序设计#嵌入式#数据结构#算法#数据分析#计算机

C语言基础:从菜鸟到高手的奇妙之旅

📅 2023/4/26⏱️ 10 min read📝 1889 words

"C语言就像一位老朋友,看似严肃,但和它相处久了,你会发现它其实是个段子手!"

第一章 认识 C 语言

1.1 C 语言的历史

C 语言,这位"老前辈",最初是由丹尼斯·里奇(Dennis Ritchie)在贝尔实验室为开发 UNIX 操作系统而设计的。1972年,它在DEC PDP-11计算机上首次亮相,从此开启了系统编程的新纪元。

趣闻:C语言的命名其实源于B语言(B Language),而B语言的名字又源于BCPL语言。所以C其实是"BCPL的C",但为了听起来更酷,他们就叫它C了!(就像你给宠物取名"小可爱",其实它就是一只普通的猫)

1978年,布莱恩·柯林汉(Brian Kernighan)和丹尼斯·里奇(Dennis Ritchie)发布了C语言的第一个公开描述,也就是著名的 K&R标准。UNIX操作系统、C编译器和几乎所有的UNIX应用程序都是用C语言编写的。

1.2 C 语言的作用

C语言之所以如此流行,是因为它有以下几个超能力:

  • 易于学习:比Python难一点,但比汇编简单多了
  • 结构化语言:代码清晰,层次分明
  • 高效率:生成的代码几乎和汇编一样快
  • 底层操作:可以操控硬件,做你想做的任何事
  • 跨平台:在各种计算机上都能编译运行

C语言的用武之地:

  • 操作系统(UNIX、Linux、Windows内核等)
  • 编译器和解释器
  • 文本编辑器(Vim、Emacs)
  • 网络驱动
  • 数据库
  • 实体工具(比如计算器)

幽默小剧场:C语言就像一位"全能选手",既能写操作系统,又能写游戏引擎,还能在嵌入式设备上跑得飞快。相比之下,Python就像是个"社交牛逼症",在各种应用中都能见到,但关键时刻(比如系统底层)就掉链子了。

1.3 环境设置

想要学习C语言,你需要:

  1. 文本编辑器:VS Code、Sublime Text、Notepad++(别用记事本,那是给小学生用的!)
  2. C编译器:GCC(Linux/Mac)、MinGW(Windows)

小贴士:如果你在Windows上,推荐使用 MinGW 或 MSYS2,它们会让你的C语言学习之旅变得轻松愉快。


第二章 C 语言基础

2.1 C 语言程序结构

一个完整的C程序至少包含两部分:头文件和主函数。

#include <stdio.h>  // 正确的头文件,不是stido.h!
int main() {
    printf("Hello, world! 你终于来啦!\n");
    return 0;
}

重要提示:#include <stdio.h> 是标准输入输出头文件,它包含了printf、scanf等函数的声明。不要写成#include<stido.h>,那是C语言史上最著名的拼写错误之一!

2.2 注释

注释是程序员的"小秘密",它们不会被编译,但能让代码更易读。

// 单行注释:就像在代码中写个便条
/* 
   多行注释:适合写长篇大论
   甚至可以写个笑话:
   为什么程序员总是分不清万圣节和圣诞节?
   因为Oct 31 == Dec 25!
*/

程序员的幽默:在代码中写注释,就像在朋友的手机里留个"记得帮我买奶茶"的便签。

2.3 头文件和宏定义

2.3.1 头文件

头文件是.h文件,包含函数声明和宏定义。引用头文件有两种方式:

#include <stdio.h>    // 系统头文件,从标准路径找
#include "my_header.h" // 自定义头文件,从当前目录找

小知识:<stdio.h> 中的 "stdio" 是 "standard input output" 的缩写,意思是标准输入输出。

2.3.2 宏定义

宏定义是C语言的"魔法咒语",可以让你的代码更简洁。

#define PI 3.1415926  // 无参宏定义
#define SQUARE(x) ((x) * (x))  // 有参宏定义

// 用法:
int radius = 5;
int area = PI * SQUARE(radius);

警告:宏定义会直接替换文本,不会检查类型,所以使用有参宏时要小心括号,否则容易出错。

2.4 关键字和标识符

2.4.1 关键字

C语言的关键字是"保留字",不能用作变量名。

关键字 说明
int 整型变量
char 字符型变量
if 条件判断
for 循环
while 循环
return 函数返回

幽默小剧场:C语言的关键字就像"禁用词",你不能用它们来命名你的变量,否则编译器会像老师一样说:"同学,你这名字太奇怪了!"

2.4.2 标识符

标识符就是变量名、函数名等。命名规则:

  • 由字母、下划线和数字组成
  • 不能以数字开头
  • 不能使用关键字

命名建议:不要用a、b、c这样简单的名字,试试userAge、totalPrice,这样你的代码会更易读,也更专业。

2.5 基础数据类型

C语言有多种数据类型,主要分为基本类型、枚举类型、void类型和派生类型。

2.5.1 整型

类型 存储大小 值范围
char 1字节 -128 到 127 或 0 到 255
int 4字节 -2,147,483,648 到 2,147,483,647
long 4字节 -2,147,483,648 到 2,147,483,647
unsigned int 4字节 0 到 4,294,967,295

小提示:在64位系统上,int通常是4字节,但long可能是8字节。要确定大小,用sizeof函数。

#include <stdio.h>
int main() {
    printf("int 的大小: %zu 字节\n", sizeof(int));
    printf("long 的大小: %zu 字节\n", sizeof(long));
    return 0;
}

2.5.2 浮点型

类型 存储大小 值范围 精度
float 4字节 1.2E-38 到 3.4E+38 6位有效位
double 8字节 2.3E-308 到 1.7E+308 15位有效位
long double 16字节 3.4E-4932 到 1.1E+4932 19位有效位

幽默提示:float就像一个"小气鬼",只存6位有效数字;double则是"大方佬",能存15位;而long double则是"土豪",能存19位。

2.5.3 类型转换

C语言有隐式和显式类型转换。

int a = 10;
float b = 3.14;
double c = a + b;  // 隐式转换:int -> double

double d = 3.14159;
int e = (int)d;    // 显式转换:double -> int

警告:显式转换会丢失小数部分,比如(int)3.9会变成3。

2.6 运算符

2.6.1 算术运算符

运算符 描述 示例
+ 相加 5 + 3 = 8
- 相减 5 - 3 = 2
* 相乘 5 * 3 = 15
/ 相除 5 / 3 = 1(整数除法)
% 取模 5 % 3 = 2
++ 自增 a++ = 6(先用a的值,再加1)
-- 自减 a-- = 6(先用a的值,再减1)

小技巧:++和--有前缀和后缀之分,前缀先变后用,后缀先用后变。

2.6.2 关系运算符

运算符 描述 示例
== 相等 5 == 5 为真
!= 不相等 5 != 3 为真
> 大于 5 > 3 为真
< 小于 5 < 3 为假
>= 大于等于 5 >= 5 为真
<= 小于等于 5 <= 3 为假

幽默提示:关系运算符就像"比较脸",总是想看看谁更大、谁更小。

2.6.3 逻辑运算符

运算符 描述 示例
&& 逻辑与 5 > 3 && 3 > 1 为真
` `
! 逻辑非 !(5 > 3) 为假

小剧场:&&就像"双人组",两个人都要通过才通过;||就像"单人组",只要一个人通过就行;!就像"反派",把结果翻转。

2.6.4 位运算符

位运算符操作二进制位。

运算符 描述 示例
& 按位与 5 & 3 = 1(二进制:0101 & 0011 = 0001)
` ` 按位或
^ 按位异或 5 ^ 3 = 6(二进制:0101 ^ 0011 = 0110)
~ 按位取反 ~5 = -6(二进制:~0101 = 1010,补码表示)
<< 左移 5 << 2 = 20(二进制:0101 << 2 = 10100)
>> 右移 5 >> 2 = 1(二进制:0101 >> 2 = 0001)

位运算小技巧:<< 和 >> 可以快速实现乘除2的幂次方,比如 x << 3 相当于 x * 8。

2.6.5 赋值运算符

运算符 描述 示例
= 简单赋值 a = 5
+= 加后赋值 a += 5 等价于 a = a + 5
-= 减后赋值 a -= 5 等价于 a = a - 5
*= 乘后赋值 a *= 5 等价于 a = a * 5
/= 除后赋值 a /= 5 等价于 a = a / 5
%= 取模后赋值 a %= 5 等价于 a = a % 5

小提示:+=、-=等运算符是C语言的"快捷键",让你的代码更简洁。

2.6.6 杂项运算符

运算符 描述 示例
sizeof() 返回变量大小 sizeof(int) = 4
& 返回地址 &a 返回a的地址
* 指针解引用 *p 访问p指向的值
?: 条件表达式 a > b ? a : b

幽默提示:sizeof就像一个"身材测量仪",能告诉你变量占了多少空间。

2.7 变量与常量

2.7.1 变量

变量是存储数据的容器。

int age = 25;      // 定义并初始化
float height;      // 仅定义
height = 1.75;     // 初始化

命名建议:用有意义的名字,比如userAge而不是a。

2.7.2 常量

常量是值不变的变量。

#define PI 3.1415926
const float PI = 3.1415926;

区别:#define是预处理器指令,在编译前替换;const是C语言关键字,编译时检查类型。

2.8 输入和输出

2.8.1 scanf() 和 printf()

#include <stdio.h>
int main() {
    char name[50];
    int age;
    
    printf("请输入你的名字: ");
    scanf("%s", name);
    
    printf("请输入你的年龄: ");
    scanf("%d", &age);
    
    printf("你好, %s! 你今年 %d 岁。\n", name, age);
    return 0;
}

小提示:scanf需要取地址符&,因为它是通过指针修改变量的值。

2.8.2 getchar() 和 putchar()

#include <stdio.h>
int main() {
    char c;
    
    printf("请输入一个字符: ");
    c = getchar();  // 读取一个字符
    
    printf("你输入的是: %c\n", c);
    putchar(c);     // 输出一个字符
    return 0;
}

有趣事实:getchar()和putchar()每次只处理一个字符,所以它们就像"单点突破"的勇士。

2.8.3 gets() 和 puts()

#include <stdio.h>
int main() {
    char str[100];
    
    printf("请输入一句话: ");
    gets(str);  // 读取一行字符串
    
    puts(str);  // 输出字符串并换行
    return 0;
}

警告:gets()不安全,容易导致缓冲区溢出。在新代码中,应该使用fgets()。


第三章 流程控制语句

3.1 顺序结构

顺序结构就是代码从上到下依次执行,就像走路一样,一步一步来。

3.2 选择结构

3.2.1 if 语句

if (条件) {
    // 条件为真时执行
} else {
    // 条件为假时执行
}

幽默提示:if就像"选择题",是A还是B,全看条件。

3.2.2 switch 语句

switch (表达式) {
    case 值1:
        // 执行代码
        break;
    case 值2:
        // 执行代码
        break;
    default:
        // 默认执行代码
}

小技巧:switch适合多条件判断,但要注意break,否则会"fall through"(继续执行下一个case)。

3.2.3 三元运算符

int max = (a > b) ? a : b;  // 如果a > b,max = a,否则max = b

小幽默:三元运算符就像"精简版"的if-else,但要小心用,否则代码会变得难读。

3.3 循环结构

3.3.1 while 循环

while (条件) {
    // 循环体
}

特点:先判断条件,再执行循环体。

3.3.2 do-while 循环

do {
    // 循环体
} while (条件);

特点:先执行循环体,再判断条件,至少执行一次。

3.3.3 for 循环

for (初始化; 条件; 更新) {
    // 循环体
}

小提示:for循环最常用,适合已知循环次数的情况。

3.3.4 循环控制语句

语句 描述
break 终止循环或switch
continue 跳过本次循环,进入下一次
goto 跳转到指定标签(不推荐使用)

警告:goto是"编程界的毒药",尽量避免使用,否则代码会变得像" spaghetti code"(意大利面代码)。


第四章 复杂数据类型

4.1 数组

数组是相同类型元素的集合。

int arr[5] = {1, 2, 3, 4, 5};  // 定义并初始化
int a = arr[0];                 // 访问第一个元素

数组小贴士:数组下标从0开始,所以第一个元素是arr[0]。

4.2 字符数组-字符串

在C中,字符串是用\0结尾的字符数组。

char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
char str2[] = "Hello";  // 等同于上面的定义

字符串小知识:strlen(str)返回字符串长度(不包括\0),strlen("Hello")返回5。

4.3 结构体

结构体是用户自定义的数据类型,可以包含不同类型的数据。

struct Student {
    char name[50];
    int age;
    float gpa;
};

int main() {
    struct Student s1 = {"Tom", 20, 3.5};
    printf("Name: %s, Age: %d, GPA: %.2f\n", s1.name, s1.age, s1.gpa);
    return 0;
}

结构体小剧场:结构体就像"多功能盒子",可以把不同类型的数据装在一起。

4.4 共用体

共用体允许在同一内存位置存储不同类型的数据。

union Data {
    int i;
    float f;
    char str[20];
};

int main() {
    union Data data;
    data.i = 10;
    printf("data.i: %d\n", data.i);
    
    data.f = 220.5;
    printf("data.f: %f\n", data.f);
    
    strcpy(data.str, "C Programming");
    printf("data.str: %s\n", data.str);
    return 0;
}

共用体小提示:共用体中的成员共享同一块内存,所以一次只能存储一个成员的值。

4.5 枚举

枚举是自定义的整型常量集合。

enum Weekday { Monday, Tuesday, Wednesday, Thursday, Friday, Saturday, Sunday };
int main() {
    enum Weekday today = Wednesday;
    printf("Today is %d\n", today);  // 输出: 2
    return 0;
}

枚举小知识:枚举的值从0开始,依次递增。你可以指定值,如enum Color { Red = 1, Green = 2, Blue = 3 };。


第五章 函数

5.1 函数的使用

5.1.1 定义函数

return_type function_name(parameter_list) {
    // 函数体
}

小提示:return_type可以是void(无返回值)。

5.1.2 函数声明

return_type function_name(parameter_list);

为什么需要声明:告诉编译器函数的参数和返回类型,避免编译错误。

5.1.3 调用函数

int result = max(10, 20);  // 调用函数

5.2 参数传递

5.2.1 传值调用

void swap(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
}

注意:传值调用不会修改实际参数,因为传递的是副本。

5.2.2 引用调用

void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

小技巧:通过指针传递,可以修改实际参数。

5.3 函数的递归

递归是函数调用自身。

int factorial(int n) {
    if (n == 0)
        return 1;
    else
        return n * factorial(n - 1);
}

递归小提示:递归要有终止条件,否则会无限递归导致栈溢出。


第六章 指针

6.1 指针变量

指针是存储内存地址的变量。

int a = 10;
int *p = &a;  // p 指向 a 的地址
printf("a 的值: %d\n", a);
printf("a 的地址: %p\n", &a);
printf("p 的值: %p\n", p);
printf("p 指向的值: %d\n", *p);

指针小剧场:指针就像"快递员",它不直接给你东西,但告诉你东西在哪里。

6.2 NULL 指针

int *p = NULL;  // 空指针

重要提示:不要解引用空指针,否则会导致程序崩溃。

6.3 指针详解

6.3.1 指针的算术运算

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;  // p 指向 arr[0]
printf("%d\n", *p);      // 1
printf("%d\n", *(p + 1)); // 2

指针小知识:指针加1,地址增加的是该类型大小(如int是4字节)。

6.4 通过指针引用数组

int arr[5] = {1, 2, 3, 4, 5};
int *p = arr;
printf("%d\n", *(p + 2));  // 3

数组和指针:数组名是数组首元素的地址,所以arr等同于&arr[0]。


第七章 动态内存和链表

7.1 动态内存

7.1.1 malloc()

int *p = (int *)malloc(5 * sizeof(int));
if (p == NULL) {
    printf("内存分配失败\n");
    exit(1);
}

小提示:malloc返回分配的内存地址,需要强制转换类型。

7.1.2 calloc()

int *p = (int *)calloc(5, sizeof(int));

区别:calloc会初始化为0。

7.1.3 realloc()

p = (int *)realloc(p, 10 * sizeof(int));

用途:重新分配内存大小。

7.1.4 free()

free(p);
p = NULL;  // 避免野指针

重要提示:释放内存后,最好将指针设为NULL。

7.2 链表

7.2.1 静态链表

(代码示例已修复,更清晰)

7.2.2 动态链表

typedef struct Node {
    int data;
    struct Node *next;
} Node;

Node *createList(int n) {
    Node *head = NULL;
    Node *current = NULL;
    for (int i = 0; i < n; i++) {
        Node *newNode = (Node *)malloc(sizeof(Node));
        newNode->data = i;
        newNode->next = NULL;
        if (head == NULL) {
            head = newNode;
            current = newNode;
        } else {
            current->next = newNode;
            current = newNode;
        }
    }
    return head;
}

链表小剧场:链表就像"火车",每个节点(车厢)包含数据和指向下一个节点的指针。


第八章 对文件的输入输出

8.1 C文件的有关知识

8.1.1 什么是文件

  • 程序文件:源代码文件,如.c、.h
  • 数据文件:存储数据的文件,如.txt、.dat

8.1.2 文件类型指针

FILE *fp;

小提示:FILE是C标准库定义的结构体类型。

8.2 打开与关闭文件

8.2.1 fopen()

FILE *fp = fopen("file.txt", "r");  // 以只读方式打开文件

文件模式:

  • r:只读
  • w:写入(覆盖)
  • a:追加
  • r+:读写
  • w+:读写(覆盖)

8.2.2 fclose()

fclose(fp);

重要提示:使用完文件后一定要关闭,否则可能导致数据丢失。

8.3 顺序读写数据文件

8.3.1 读写字符

char c = getc(fp);  // 读取一个字符
putc(c, fp);        // 写入一个字符

8.3.2 读写字符串

char str[100];
fgets(str, 100, fp);  // 读取一行
fputs(str, fp);        // 写入字符串

小提示:fgets比gets安全,因为可以指定缓冲区大小。


🎉 结语

C语言是编程世界的基石,掌握它就像掌握了魔法。虽然它看起来有点严肃,但只要你用心去学,你会发现它其实很有趣!

最后的小幽默:C语言的编译器就像个"严格的老教师",你写错一个分号,它就会给你一个"编译错误"的"大嘴巴"。但别怕,错误是进步的阶梯,每次编译错误都是你成长的机会!

现在,拿起你的键盘,开始你的C语言之旅吧! 💻✨


最后更新: 2018年10月20日 01:46
上一篇
C#基础语法
下一篇
设计模式

💬 评论

评论系统接入中...

☰ 目录0%

  • 第一章 认识 C 语言
    • 1.1 C 语言的历史
    • 1.2 C 语言的作用
    • 1.3 环境设置
  • 第二章 C 语言基础
    • 2.1 C 语言程序结构
    • 2.2 注释
    • 2.3 头文件和宏定义
      • 2.3.1 头文件
      • 2.3.2 宏定义
    • 2.4 关键字和标识符
      • 2.4.1 关键字
      • 2.4.2 标识符
    • 2.5 基础数据类型
      • 2.5.1 整型
      • 2.5.2 浮点型
      • 2.5.3 类型转换
    • 2.6 运算符
      • 2.6.1 算术运算符
      • 2.6.2 关系运算符
      • 2.6.3 逻辑运算符
      • 2.6.4 位运算符
      • 2.6.5 赋值运算符
      • 2.6.6 杂项运算符
    • 2.7 变量与常量
      • 2.7.1 变量
      • 2.7.2 常量
    • 2.8 输入和输出
      • 2.8.1 scanf() 和 printf()
      • 2.8.2 getchar() 和 putchar()
      • 2.8.3 gets() 和 puts()
  • 第三章 流程控制语句
    • 3.1 顺序结构
    • 3.2 选择结构
      • 3.2.1 if 语句
      • 3.2.2 switch 语句
      • 3.2.3 三元运算符
    • 3.3 循环结构
      • 3.3.1 while 循环
      • 3.3.2 do-while 循环
      • 3.3.3 for 循环
      • 3.3.4 循环控制语句
  • 第四章 复杂数据类型
    • 4.1 数组
    • 4.2 字符数组-字符串
    • 4.3 结构体
    • 4.4 共用体
    • 4.5 枚举
  • 第五章 函数
    • 5.1 函数的使用
      • 5.1.1 定义函数
      • 5.1.2 函数声明
      • 5.1.3 调用函数
    • 5.2 参数传递
      • 5.2.1 传值调用
      • 5.2.2 引用调用
    • 5.3 函数的递归
  • 第六章 指针
    • 6.1 指针变量
    • 6.2 NULL 指针
    • 6.3 指针详解
      • 6.3.1 指针的算术运算
    • 6.4 通过指针引用数组
  • 第七章 动态内存和链表
    • 7.1 动态内存
      • 7.1.1 malloc()
      • 7.1.2 calloc()
      • 7.1.3 realloc()
      • 7.1.4 free()
    • 7.2 链表
      • 7.2.1 静态链表
      • 7.2.2 动态链表
  • 第八章 对文件的输入输出
    • 8.1 C文件的有关知识
      • 8.1.1 什么是文件
      • 8.1.2 文件类型指针
    • 8.2 打开与关闭文件
      • 8.2.1 fopen()
      • 8.2.2 fclose()
    • 8.3 顺序读写数据文件
      • 8.3.1 读写字符
      • 8.3.2 读写字符串
  • 🎉 结语