语言的继承和发展

机器语言

二进制的机器语言 计算机只能读懂机器语言0和1

优点:计算机可以直接识别。 缺点:编写调试非常麻烦。

汇编语言-汇编器翻译成机器语言

第一种编程语言,是机器语言的助记符。 优点:效率高,体积小,非常灵活,可以直接操作计算机各个硬件。 缺点:程序员需要掌握大量的细节和基础知识,兼容性差,编写调试麻烦。

C语言

高级汇编语言翻译成汇编语言

优点: 3.1)结构化设计语言,语法清晰、结构简单,模块化使得程序的各个部分除了必要的信息交流外彼此独立,便于开发、调试以及分工协作。 3.2)运算符多,把括号、赋值、强制类型转换等都作为运算符处理,灵活使用各种运算符可以实现在其它高级语言中难以实现的运算。 3.3)数据结构丰富,能实现各种复杂的数据类型的运算,引入指针、结构体概念使程序效率更高。 3.4)为操作系统而生,可以像汇编语言一样对位、字节和地址进行操作,允许直接访问物理地址对硬件进行操作,把高级语言的基本结构和语句与低级语言的实用性结合起来。 3.5)程序执行效率高,一般比汇编程序生成的目标代码效率低10%~20%。 3.6)可移植性好,C语言抽象了针对CPU编程的细节,能广泛应用于针对大型操作系统和系统软件的编写

头文件(C99版本)

#include <assert.h> //设定插入点 
#include <ctype.h> //字符处理 
#include <errno.h> //定义错误码 
#include <float.h> //浮点数处理 
#include <fstream.h> //文件输入/输出 
#include <iomanip.h> //参数化输入/输出 
#include <iostream.h> //数据流输入/输出 
#include <limits.h> //定义各种数据类型最值常量 
#include <locale.h> //定义本地化函数 
#include <math.h> //定义数学函数 
#include <stdio.h> //定义输入/输出函数 
#include <stdlib.h> //定义杂项函数及内存分配函数 
#include <string.h> //字符串处理 
#include <strstrea.h> //基于数组的输入/输出 
#include <time.h> //定义关于时间的函数 
#include <wchar.h> //宽字符处理及输入/输出 
#include <wctype.h> //宽字符分类
---
---
新加库
#include <complex.h> //复数处理
#include <fenv.h> //浮点环境
#include <inttypes.h> //整数格式转换
#include <stdbool.h> //布尔环境
#include <stdint.h> //整型环境
#include <tgmath.h> //通用类型数学宏
#include <conio.h> 说明调用DOS控制台I/O子程序的各个函数。
#include <sio.h> 包含字符串库函数说明的头文件
#include <slib.h> 包含动态存储与释放函数头文件

特殊符号

标点与分隔符

符号名称作用说明
;分号极其重要,表示一条语句的结束。忘写分号是新手最常见的报错原因。
,逗号用于分隔变量声明或函数参数(如 int a, b;)。
{}大括号用于定义代码块,比如函数体、循环体、if判断内部的代码。
()小括号用于改变运算优先级、或者包裹函数的参数。
[]中括号专门用于声明或访问数组的元素(如 arr[5])。

运算符号

符号类别作用说明
+ - * / %算术运算符加、减、乘、除、取模(求余数,%
= == !=赋值与关系= 是赋值(把右边给左边);== 才是判断“是否相等”;!= 是不等于。
&& || !逻辑运算符&& (并且/与), || (或者/或),! (相反/非)
&*指针/地址符C语言的灵魂!& 用来获取变量的内存地址* 用来声明指针或访问指针指向的值
.->结构体成员符用于访问结构体(struct)内部的数据。普通结构体用 .,结构体指针用 ->
条件表达式 ? 值1 : 值2条件运算符如果条件为,返回值1;如果条件为,返回值2

算数运算符

printf("x + y = %d\n", z);       //和
printf("x + y = %d\n", x + y);   //和
printf("x - y = %d\n", x - y);   //差
printf("x * y = %d\n", x * y);   //积
printf("x / y = %d\n", x / y);   //商
printf("x % y = %d\n", x % y);  //余数

条件运算符

x = a > b ? 1 : 0;

等价于

if (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>)
char1-128 ~ 127CHAR_MIN / CHAR_MAX
unsigned char10 ~ 255UCHAR_MAX
short2-32,768 ~ 32,767SHRT_MIN / SHRT_MAX
unsigned short20 ~ 65,535USHRT_MAX
int4-2,147,483,648 ~ 2,147,483,647INT_MIN / INT_MAX
unsigned int40 ~ 4,294,967,295UINT_MAX
long4 或 8取决于平台(见下方说明)LONG_MIN / LONG_MAX
long long8-9.22×10¹⁸ ~ 9.22×10¹⁸LLONG_MIN / LLONG_MAX
unsigned long long80 ~ 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>)
float4~7 位±1.17×10⁻³⁸ ~ ±3.40×10³⁸
double8~15 位±2.22×10⁻³⁰⁸ ~ ±1.79×10³⁰⁸
long double8/12/16~18-19 位视编译器而定

浮点运算的核心规则:

  • double 输出时小数部分默认显示 6 位
  • 浮点数之间运算的结果仍然是浮点数

类型转换

当一个表达式中混合了不同类型的操作数时,C语言会自动处理类型差异:

隐式转换(自动转换)

编译器自动将较小的类型提升为较大的类型,转换方向为:

char → short → int → long → long long → float → double → long double

例如 int + doubleint 会先被提升为 double,结果也是 double

显式转换(强制转换)

使用转换运算符 (类型名) 手动转换:

int a = 5, b = 2;
double result = (double)a / b;  // 结果为 2.5,而非 2

printf 与 scanf 中的格式说明

类型转换的知识直接体现在输入输出函数的格式说明符选择上:

类型printfscanf
int%d%d
float%f%f
double%f%lf
char%c%c
long long%lld%lld

⚠️ 易错点: printf 输出 double%f,但 scanf 读取 double 必须用 %lf

格式说明符的完整结构

一个转换说明的完整形式为:

%[标志][最小宽度][.精度]转换符
组成部分说明示例
标志0 补零、- 左对齐%05d00042
最小宽度输出的最少字符数%10d 42
精度小数位数%.2f3.14
转换符d f c s

特殊情况: 若要在 printf 中输出 % 字符本身,需要写 `\n”); // 输出:利率为 5%


### 多参数格式化

`printf` 和 `scanf` 的格式字符串可以包含**多个转换说明**,依次对应后面的**多个参数**:

```c
int age = 20;
double score = 95.5;
printf("年龄: %d, 成绩: %.1f\n", age, score);
// 输出:年龄: 20, 成绩: 95.5

总结速查

整数 int  ──→ 只存整数,小数截断,运算结果为整数
浮点 double ──→ 存小数,默认显示6位,运算结果为浮点
混合运算   ──→ 小类型自动提升为大类型(隐式转换)
手动转换   ──→ (类型名)变量(显式转换)
输出 double ──→ printf 用 %f
输入 double ──→ scanf 用 %lf
输出 %     ──→ 写 %%

条件语句

分支语句

if语句

if(true){
    语句
}
if (month < 1 || month > 12)
        printf("%d 月不存在!!\a\n", month);
else if (month <= 2 || month == 12)
        printf("%d 月是冬季。\n", month);
else if (month >= 9)
        printf("%d 月是秋季。\n", month);
else if (month >= 6)
        printf("%d 月是夏季。\n", month);
else
        printf("%d 月是春季。\n", month);

switch语句

switch (条件) {
    case 0: puts("该数能被3整除。");      break;
    case 1: puts("该数除以3的余数是1。"); break;
    case 2: puts("该数除以3的余数是2。"); break;
    // case 6:
}

循环结构

while结构

表达式1
while(条件表达式2){
    ...
    表达式3
}
表达式1
do
{
    ...
    表达式3
}while(条件表达式2);
  • 先循环后判断使用do语句实现。循环体至少执行一次。
  • 先判断后循环,使用while语句,循环体可能一次都不会被执行。也可以使用for语句,可以看成是while语句的简化方式。
  • break语句会中断循环语句的执行。循环语句中的continue语句跳过循环体剩余部分的执行。

for循环

for(表达式1;条件表达式2;表达式3){
    ...
    
}

派生类型

数组

同一类型的对象集中在一起,在内存中线性排序,就是数组。数组通过指定元素类型、个数以及数组名来声明

具有相同数据类型的一组连续存放在内存中的元素构成一个数组

int a[5]={1,2,3,4,5}
/**
int:数组元素的数据类型
a 数组名--数组的起始地址;数组的第一个元素a[0]
5:素组的个数
**/

元素类型为type的数组,称为type数组。元素个数为n的type数组,写作type[n]

访问数组的各个元素时使用下标运算符[]

字符串数组

C语言中没有专门的字符串基础数据类型。字符串实际上是以空字符 \0 结尾的字符数组。 声明和初始化字符串通常有以下几种方式:

// 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. 函数声明,明确了函数类型:返回值为 int,接受两个 int 类型的参数
int add(int a, int b);
 
// 2. 函数定义
int add(int a, int b) {
    return a + b;
}
 
// 3. 函数指针的应用
// 声明一个与 add 函数类型匹配的指针
int (*func_ptr)(int, int); 
func_ptr = add; // 将函数 add 的地址赋给指针 func_ptr
 
// 通过指针调用函数
int result = func_ptr(10, 20); // 相当于直接调用 add(10, 20)

指针类型

指针是C语言的灵魂,它是一个变量,其存储的值是另一个变量的内存地址(即直接操作内存)。

声明指针时,需要指明该指针所指向的数据类型:

int var = 20;   // 实际变量的声明
int *p;         // 指针变量的声明,类型为指向 int 类型的指针
p = &var;       // 在指针变量 p 中存储 var 的内存地址

/*
核心运算符:
 & : 取地址运算符,用于获取变量在内存中的真实地址。
 * : 解引用运算符(间接寻址符),用于访问或修改指针所指向地址中存储的值。
     例如:*p = 30;  // 这会直接将 var 的值改为 30
*/

结构体类型

结构体(struct)是一种用户自定义的复合数据类型。与数组不同(数组只能存储同类型数据),结构体允许将不同类型的数据项组合成一个单一的实体,非常适合用来描述具有多个属性的复杂对象。

声明结构体使用 struct 关键字:

// 声明一个名为 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,也称联合体)在声明语法上与结构体非常相似,但它是为了节省内存而设计的。它允许在相同的内存位置存储不同的数据类型。这意味着在一个共用体变量中,任何时候都只能有一个成员包含有效的值。共用体在内存中所占的大小通常等于其占用内存最大的那个成员的大小。

// 声明一个名为 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类型的指针,该对象用于控制与所打开的文件相关联的流;打开操作失败返回空指令

文件操作的良好习惯:判断是否出现错误

#include <stdio.h>

int main() {
    // 1. 声明 FILE 类型的指针
    FILE *fp;

    // 2. 使用 fopen 打开文件(这里以只读模式 "r" 打开 "data.txt")
    fp = fopen("data.txt", "r");

    // 3. 良好习惯:判断是否出现错误(打开失败会返回 NULL 空指针)
    // 注意:你笔记截图中的“空指令”应该是笔误,准确说法是“空指针(NULL)”
    if (fp == NULL) {
        // 利用标准错误流 stderr 输出错误提示
        fprintf(stderr, "错误:无法打开文件 data.txt\n");
        return 1; // 非0返回值通常表示程序异常退出
    }

    // --- 如果程序执行到这里,说明文件打开成功,可以进行读写操作 ---
    printf("文件打开成功!使用的是标准输出流 stdout\n");

    // 4. 良好习惯:操作完成后,务必使用 fclose 关闭文件释放资源
    fclose(fp);

    return 0;
}

文件权限

打开文件时,可以指定以下四种模式

  • 只读模式——只从文件输入
  • 只写模式——只向文件输出
  • 更新模式——即从文件输入,也向文件输出
  • 追加模式——从文件末尾处开始向文件输出

详细的文件打开模式参数如下表所示:

模式详细说明
r以只读模式打开文本文件。
w以只写模式建立文本文件,若文件存在则文件长度清为0。
a以追加模式(从文件末尾处开始的只写模式)打开或建立文本文件。
rb以只读模式打开二进制文件。
wb以只写模式建立二进制文件,若文件存在则文件长度清为0。
ab以追加模式(从文件末尾处开始的只写模式)打开或建立二进制文件。
r+以更新(读写)模式打开文本文件。
w+以更新模式建立文本文件,若文件存在则文件长度清为0。
a+以追加模式(从文件末尾处开始写入的更新模式)打开或建立文本文件。
r+brb+以更新(读写)模式打开二进制文件。
w+bwb+以更新模式建立二进制文件,若文件存在则文件长度清为0。
a+bab+以追加模式(从文件末尾处开始写入的更新模式)打开或建立二进制文件。

文件的写入与读取

在C语言中,打开文件后,我们需要根据文件打开的模式(如 r 只读, w 写入, a 追加)使用不同的函数进行读写操作。按照处理数据的单位不同,主要分为以下几种常用方式:

字符串的读写(最常用处理文本)

  • fputs(字符串, 文件指针):将指定的字符串写入文件。
  • fgets(缓冲区, 最大长度, 文件指针):从文件中读取一整行内容到缓冲区。
    • 安全提示: fgets 是极其推荐的读取函数,因为它允许指定最大读取长度,能有效防止缓冲区溢出(Buffer Overflow)。

格式化读写(处理结构化数据)

如果你需要将整型、浮点型等变量与文本混合写入/读取,这两个函数与控制台的 printf/scanf 用法几乎完全一致,只是多了一个文件指针参数:

  • fprintf(文件指针, 格式字符串, 参数列表):将格式化的数据写入文件。
  • fscanf(文件指针, 格式字符串, 参数列表):从文件中提取格式化的数据。

数据块读写(处理二进制数据)

用于直接将内存中的数据块(如整个数组或结构体变量)原封不动地写入文件或读出,通常配合 "rb""wb"(二进制模式)使用:

  • fwrite(...):写入二进制数据块。
  • fread(...):读取二进制数据块。

下面是一个完整的示例,展示如何先将多行格式化数据写入文件,然后再按行将它们完整地读取出来打印到屏幕上:

#include <stdio.h>
#include <stdlib.h> // 为了使用 exit()
 
int main() {
    FILE *fp;
    char filename[] = "data_log.txt";
 
    // ==============================
    // 第一部分:写入文件 (Write)
    // ==============================
    
    // 使用 "w" 模式打开。注意:"w"模式下,如果文件已存在,会清空原有内容!
    // 如果想在文件末尾继续添加,应使用 "a" (append 追加) 模式。
    fp = fopen(filename, "w");
    if (fp == NULL) {
        fprintf(stderr, "错误:无法打开文件进行写入\n");
        exit(1); 
    }
 
    // 1. 使用 fputs 写入表头 (包含换行符 \n)
    fputs("ID\tName\tScore\n", fp);
    
    // 2. 使用 fprintf 写入格式化的变量数据
    fprintf(fp, "%d\t%s\t%.1f\n", 1, "Alice", 95.5);
    fprintf(fp, "%d\t%s\t%.1f\n", 2, "Bob", 88.0);
 
    // 良好习惯:写完立刻关闭
    fclose(fp); 
    printf("--> 数据写入成功!\n\n");
 
 
    // ==============================
    // 第二部分:读取文件 (Read)
    // ==============================
    
    // 使用 "r" 模式打开进行读取
    fp = fopen(filename, "r");
    if (fp == NULL) {
        fprintf(stderr, "错误:无法打开文件进行读取\n");
        exit(1);
    }
 
    printf("--- 以下是文件 %s 的内容 ---\n", filename);
    
    char buffer[100]; // 定义一个字符数组作为读取缓冲区
    
    // 使用 fgets 循环读取文件内容。
    // fgets 每次读取一行,遇到文件结束符 (EOF) 时会返回 NULL,借此作为循环结束的条件。
    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
        // buffer 中已经包含了读取到的换行符,所以这里 printf 不需要再加 \n
        printf("%s", buffer); 
    }
 
    // 良好习惯:读完立刻关闭
    fclose(fp); 
    
    return 0;
}