初识C语言
语言的继承和发展
机器语言
二进制的机器语言
计算机只能读懂机器语言0和1
优点:计算机可以直接识别。
缺点:编写调试非常麻烦。
汇编语言—->汇编器翻译成机器语言
第一种编程语言,是机器语言的助记符。
优点:效率高,体积小,非常灵活,可以直接操作计算机各个硬件。
缺点:程序员需要掌握大量的细节和基础知识,兼容性差,编写调试麻烦。
C语言
高级汇编语言—>翻译成汇编语言
优点:
3.1)结构化设计语言,语法清晰、结构简单,模块化使得程序的各个部分除了必要的信息交流外彼此独立,便于开发、调试以及分工协作。
3.2)运算符多,把括号、赋值、强制类型转换等都作为运算符处理,灵活使用各种运算符可以实现在其它高级语言中难以实现的运算。
3.3)数据结构丰富,能实现各种复杂的数据类型的运算,引入指针、结构体概念使程序效率更高。
3.4)为操作系统而生,可以像汇编语言一样对位、字节和地址进行操作,允许直接访问物理地址对硬件进行操作,把高级语言的基本结构和语句与低级语言的实用性结合起来。
3.5)程序执行效率高,一般比汇编程序生成的目标代码效率低10%~20%。
3.6)可移植性好,C语言抽象了针对CPU编程的细节,能广泛应用于针对大型操作系统和系统软件的编写
头文件(C99版本)
1 | #include <assert.h> //设定插入点 |
特殊符号
标点与分隔符
| 符号 | 名称 | 作用说明 |
|---|---|---|
; |
分号 | 极其重要,表示一条语句的结束。忘写分号是新手最常见的报错原因。 |
, |
逗号 | 用于分隔变量声明或函数参数(如 int a, b;)。 |
{} |
大括号 | 用于定义代码块,比如函数体、循环体、if判断内部的代码。 |
() |
小括号 | 用于改变运算优先级、或者包裹函数的参数。 |
[] |
中括号 | 专门用于声明或访问数组的元素(如 arr[5])。 |
运算符号
| 符号 | 类别 | 作用说明 |
|---|---|---|
+ - * / % |
算术运算符 | 加、减、乘、除、取模(求余数,%)。 |
= == != |
赋值与关系 | = 是赋值(把右边给左边);== 才是判断“是否相等”;!= 是不等于。 |
&& || ! |
逻辑运算符 | && (并且/与), || (或者/或),! (相反/非) |
& 和 * |
指针/地址符 | C语言的灵魂!& 用来获取变量的内存地址;* 用来声明指针或访问指针指向的值。 |
. 和 -> |
结构体成员符 | 用于访问结构体(struct)内部的数据。普通结构体用 .,结构体指针用 ->。 |
| 条件表达式 ? 值1 : 值2 | 条件运算符 | 如果条件为真,返回值1;如果条件为假,返回值2。 |
算数运算符
1 | printf("x + y = %d\n", z); //和 |
条件运算符
1 | x = a > b ? 1 : 0; |
等价于1
2
3
4if (a > b)
x = 1;
else
x = 0;
| 特点 | 三目运算符 ? : |
if-else |
|---|---|---|
| 本质 | 表达式,有返回值 | 语句,无返回值 |
| 适用场景 | 简单的二选一赋值 | 复杂逻辑、多条语句 |
| 可读性 | 简洁但嵌套后难读 | 清晰直观 |
| 能否独立使用 | 可以用在赋值、参数等任何需要值的地方 | 独立语句块 |
预处理符
| 符号 | 作用说明 | 示例 |
|---|---|---|
# |
井号,表示预处理指令的开始。常用于包含头文件或定义宏。 | #include <stdio.h> 或 #define PI 3.14 |
转义字符
| 转义字符 | 含义 | 作用说明 |
|---|---|---|
\n |
换行 (Newline) | 将光标移动到下一行的开头。 |
\a |
警告/响铃 (Alert/Bell) | 触发系统默认的提示音或让终端闪烁。 |
\t |
水平制表符 (Tab) | 相当于按键盘上的 Tab 键,用于对齐文本。 |
\\ |
反斜杠 (Backslash) | 在屏幕上输出一个真正的反斜杠 \。 |
\' |
单引号 (Single quote) | 在屏幕上输出一个单引号(常用于字符常量中)。 |
\" |
双引号 (Double quote) | 在屏幕上输出一个双引号(常用于字符串中)。 |
\0 |
空字符 (Null) | ASCII码值为0的字符,是C语言中字符串的结束标志。 |
数据类型
整数类型
整数分为有符号(signed)和无符号(unsigned)两种,有符号数使用补码表示。
| 类型 | 字节数 | 取值范围 | 宏定义 (<limits.h>) |
|---|---|---|---|
| char | 1 | -128 ~ 127 | CHAR_MIN / CHAR_MAX |
| unsigned char | 1 | 0 ~ 255 | UCHAR_MAX |
| short | 2 | -32,768 ~ 32,767 | SHRT_MIN / SHRT_MAX |
| unsigned short | 2 | 0 ~ 65,535 | USHRT_MAX |
| int | 4 | -2,147,483,648 ~ 2,147,483,647 | INT_MIN / INT_MAX |
| unsigned int | 4 | 0 ~ 4,294,967,295 | UINT_MAX |
| long | 4 或 8 | 取决于平台(见下方说明) | LONG_MIN / LONG_MAX |
| long long | 8 | -9.22×10¹⁸ ~ 9.22×10¹⁸ | LLONG_MIN / LLONG_MAX |
| unsigned long long | 8 | 0 ~ 1.84×10¹⁹ | ULLONG_MAX |
平台差异:
- 16位系统:
int为 2 字节,范围同short- 64位 Windows:
long为 4 字节- 64位 Linux/macOS:
long为 8 字节
整数运算的核心规则:
int只能表示整数,赋值时小数部分直接截断丢弃(不是四舍五入)- 整数之间运算的结果仍然是整数,例如
5 / 2结果为2,而不是2.5
浮点类型
浮点数遵循 IEEE 754 标准,不同类型的区别在于精度(有效数字位数)和范围。
| 类型 | 字节数 | 有效数字 | 取值范围 (<float.h>) |
|---|---|---|---|
| float | 4 | ~7 位 | ±1.17×10⁻³⁸ ~ ±3.40×10³⁸ |
| double | 8 | ~15 位 | ±2.22×10⁻³⁰⁸ ~ ±1.79×10³⁰⁸ |
| long double | 8/12/16 | ~18-19 位 | 视编译器而定 |
浮点运算的核心规则:
double输出时小数部分默认显示 6 位- 浮点数之间运算的结果仍然是浮点数
类型转换
当一个表达式中混合了不同类型的操作数时,C语言会自动处理类型差异:
隐式转换(自动转换)
编译器自动将较小的类型提升为较大的类型,转换方向为:
1 | char → short → int → long → long long → float → double → long double |
例如 int + double,int 会先被提升为 double,结果也是 double。
显式转换(强制转换)
使用转换运算符 (类型名) 手动转换:
1 | int a = 5, b = 2; |
printf 与 scanf 中的格式说明
类型转换的知识直接体现在输入输出函数的格式说明符选择上:
| 类型 | printf | scanf |
|---|---|---|
int |
%d |
%d |
float |
%f |
%f |
double |
%f |
%lf |
char |
%c |
%c |
long long |
%lld |
%lld |
⚠️ 易错点:
printf输出double用%f,但scanf读取double必须用%lf。
格式说明符的完整结构
一个转换说明的完整形式为:
1 | %[标志][最小宽度][.精度]转换符 |
| 组成部分 | 说明 | 示例 |
|---|---|---|
| 标志 | 0 补零、- 左对齐 |
%05d → 00042 |
| 最小宽度 | 输出的最少字符数 | %10d → 42 |
| 精度 | 小数位数 | %.2f → 3.14 |
| 转换符 | d f c s 等 |
— |
特殊情况: 若要在 printf 中输出 % 字符本身,需要写 %%:
1 | printf("利率为 5%%\n"); // 输出:利率为 5% |
多参数格式化
printf 和 scanf 的格式字符串可以包含多个转换说明,依次对应后面的多个参数:
1 | int age = 20; |
总结速查
1 | 整数 int ──→ 只存整数,小数截断,运算结果为整数 |
条件语句
分支语句
if语句
1 | if(true){ |
1 | if (month < 1 || month > 12) |
switch语句
1 | switch (条件) { |
循环结构
while结构
1 | 表达式1 |
1 | 表达式1 |
- 先循环后判断使用do语句实现。循环体至少执行一次。
- 先判断后循环,使用while语句,循环体可能一次都不会被执行。也可以使用for语句,可以看成是while语句的简化方式。
- break语句会中断循环语句的执行。循环语句中的continue语句跳过循环体剩余部分的执行。
for循环
1 | for(表达式1;条件表达式2;表达式3){ |
派生类型
数组
同一类型的对象集中在一起,在内存中线性排序,就是数组。数组通过指定元素类型、个数以及数组名来声明
具有相同数据类型的一组连续存放在内存中的元素构成一个数组
1 | int a[5]={1,2,3,4,5} |
元素类型为type的数组,称为type数组。元素个数为n的type数组,写作type[n]
访问数组的各个元素时使用下标运算符[]
字符串数组
C语言中没有专门的字符串基础数据类型。字符串实际上是以空字符 \0 结尾的字符数组。 声明和初始化字符串通常有以下几种方式:1
2
3
4// 1. 逐个字符初始化,必须手动添加结束符 '\0'
char str1[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
// 2. 使用字符串字面量初始化,编译器会自动在末尾添加 '\0'
char str2[] = "Hello"; // 注意:str2 数组的大小实际上是 6,因为包含了隐藏的 '\0'
操作字符串时,通常需要包含头文件 <string.h>,以使用内置的字符串处理函数(如 strlen 求长度,strcpy 复制,strcmp 比较等)。
函数类型
函数类型由其返回值类型和参数列表决定。在C语言中,函数不仅仅是一段可执行的代码,它在内存中也有自己的首地址。因此,我们可以定义指向函数的指针(即函数指针)。
1 | // 1. 函数声明,明确了函数类型:返回值为 int,接受两个 int 类型的参数 |
指针类型
指针是C语言的灵魂,它是一个变量,其存储的值是另一个变量的内存地址(即直接操作内存)。
声明指针时,需要指明该指针所指向的数据类型:1
2
3
4
5
6
7
8
9
10int var = 20; // 实际变量的声明
int *p; // 指针变量的声明,类型为指向 int 类型的指针
p = &var; // 在指针变量 p 中存储 var 的内存地址
/*
核心运算符:
& : 取地址运算符,用于获取变量在内存中的真实地址。
* : 解引用运算符(间接寻址符),用于访问或修改指针所指向地址中存储的值。
例如:*p = 30; // 这会直接将 var 的值改为 30
*/
结构体类型
结构体(struct)是一种用户自定义的复合数据类型。与数组不同(数组只能存储同类型数据),结构体允许将不同类型的数据项组合成一个单一的实体,非常适合用来描述具有多个属性的复杂对象。
声明结构体使用 struct 关键字:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16// 声明一个名为 Student 的结构体类型
struct Student {
char name[50]; // 姓名 (字符串)
int age; // 年龄 (整型)
float score; // 成绩 (浮点型)
}; // 注意:结构体声明结束后必须有分号 ;
// 声明并初始化结构体变量
struct Student stu1 = {"张三", 20, 95.5};
// 访问结构体成员
// 1. 对于普通结构体变量,使用点号运算符 (.)
// stu1.age = 21;
// 2. 对于指向结构体的指针,使用箭头运算符 (->)
// struct Student *p = &stu1;
// p->score = 98.0;
共用体类型
共用体(union,也称联合体)在声明语法上与结构体非常相似,但它是为了节省内存而设计的。它允许在相同的内存位置存储不同的数据类型。这意味着在一个共用体变量中,任何时候都只能有一个成员包含有效的值。共用体在内存中所占的大小通常等于其占用内存最大的那个成员的大小。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17// 声明一个名为 Data 的共用体类型
union Data {
int i; // 占用 4 字节
float f; // 占用 4 字节
char str[20]; // 占用 20 字节
}; // 这个共用体类型的整体大小将是 20 字节(以最大成员为准)
// 声明共用体变量
union Data data;
// 赋值与访问:请记住,同一时间只能安全地使用一个成员
data.i = 10;
// printf("%d\n", data.i); // 此时访问 data.i 是准确的,值为10
data.f = 220.5;
// 核心注意点:此时这块共用的内存空间已被浮点数 220.5 的二进制数据覆盖。
// 如果现在去访问 data.i,会得到一个无意义的脏数据。
strcpy(data.str, "C Language");
// 现在内存中存储的是字符串 "C Language",data.i 和 data.f 之前的值都已被破坏。
文件处理
C语言程序在启动时准备好了以下3种类型的标准流
- 标准输入流:
stdin - 标准输出流:
stdout - 标准错误流:
stderr
记录控制流所需要的信息的数据类型是FILE型,该数据类型是在<stdio.h>头文件中定义的
打开文件的操作称为打开,函数库中的fopen函数用于打开文件
使用fopen函数打开后,返回指向FILE类型的指针,该对象用于控制与所打开的文件相关联的流;打开操作失败返回空指令
文件操作的良好习惯:判断是否出现错误
1 | #include <stdio.h> |
文件权限
打开文件时,可以指定以下四种模式
- 只读模式——只从文件输入
- 只写模式——只向文件输出
- 更新模式——即从文件输入,也向文件输出
- 追加模式——从文件末尾处开始向文件输出
详细的文件打开模式参数如下表所示:
| 模式 | 详细说明 |
|---|---|
r |
以只读模式打开文本文件。 |
w |
以只写模式建立文本文件,若文件存在则文件长度清为0。 |
a |
以追加模式(从文件末尾处开始的只写模式)打开或建立文本文件。 |
rb |
以只读模式打开二进制文件。 |
wb |
以只写模式建立二进制文件,若文件存在则文件长度清为0。 |
ab |
以追加模式(从文件末尾处开始的只写模式)打开或建立二进制文件。 |
r+ |
以更新(读写)模式打开文本文件。 |
w+ |
以更新模式建立文本文件,若文件存在则文件长度清为0。 |
a+ |
以追加模式(从文件末尾处开始写入的更新模式)打开或建立文本文件。 |
r+b 或 rb+ |
以更新(读写)模式打开二进制文件。 |
w+b 或 wb+ |
以更新模式建立二进制文件,若文件存在则文件长度清为0。 |
a+b 或 ab+ |
以追加模式(从文件末尾处开始写入的更新模式)打开或建立二进制文件。 |
文件的写入与读取
在C语言中,打开文件后,我们需要根据文件打开的模式(如 r 只读, w 写入, a 追加)使用不同的函数进行读写操作。按照处理数据的单位不同,主要分为以下几种常用方式:
字符串的读写(最常用处理文本)
fputs(字符串, 文件指针):将指定的字符串写入文件。fgets(缓冲区, 最大长度, 文件指针):从文件中读取一整行内容到缓冲区。- 安全提示:
fgets是极其推荐的读取函数,因为它允许指定最大读取长度,能有效防止缓冲区溢出(Buffer Overflow)。
- 安全提示:
格式化读写(处理结构化数据)
如果你需要将整型、浮点型等变量与文本混合写入/读取,这两个函数与控制台的 printf/scanf 用法几乎完全一致,只是多了一个文件指针参数:
fprintf(文件指针, 格式字符串, 参数列表):将格式化的数据写入文件。fscanf(文件指针, 格式字符串, 参数列表):从文件中提取格式化的数据。
数据块读写(处理二进制数据)
用于直接将内存中的数据块(如整个数组或结构体变量)原封不动地写入文件或读出,通常配合 "rb" 或 "wb"(二进制模式)使用:
fwrite(...):写入二进制数据块。fread(...):读取二进制数据块。
下面是一个完整的示例,展示如何先将多行格式化数据写入文件,然后再按行将它们完整地读取出来打印到屏幕上:
1 |
|
