C语言基础笔记3【个人用】

目录

一、字符串

[1.1 定义和初始化](#1.1 定义和初始化)

[可以修改 vs 不可修改](#可以修改 vs 不可修改)

[1.2 常用字符串函数(需引入 )](#1.2 常用字符串函数(需引入 ))

二、结构体

[2.1 结构体变量的声明和初始化](#2.1 结构体变量的声明和初始化)

[2.1.1 方式1:先定义类型,后声明变量](#2.1.1 方式1:先定义类型,后声明变量)

[2.1.2 方式2:定义类型的同时声明变量](#2.1.2 方式2:定义类型的同时声明变量)

[2.1.3 方式3:省略类型名(匿名结构体)](#2.1.3 方式3:省略类型名(匿名结构体))

[2.1.4 初始化](#2.1.4 初始化)

[2.2 访问结构体成员](#2.2 访问结构体成员)

[2.3 结构体指针](#2.3 结构体指针)

[2.3.1 定义和使用](#2.3.1 定义和使用)

[2.3.2 完整示例](#2.3.2 完整示例)

[2.4 结构体数组](#2.4 结构体数组)

[2.5 结构体嵌套](#2.5 结构体嵌套)

[2.6 结构体作为函数参数](#2.6 结构体作为函数参数)

[2.6.1 值传递(复制整个结构体)](#2.6.1 值传递(复制整个结构体))

[2.6.2 指针传递(推荐,效率高)](#2.6.2 指针传递(推荐,效率高))

[2.6.3 结构体作为返回值](#2.6.3 结构体作为返回值)

[2.7 typedef 简化结构体使用](#2.7 typedef 简化结构体使用)

[2.8 结构体大小与内存对齐](#2.8 结构体大小与内存对齐)

三、共同体

[3.1 跟结构体的区别](#3.1 跟结构体的区别)

四、位域

[4.1 位域的类型](#4.1 位域的类型)

[4.2 位域的存储和大小](#4.2 位域的存储和大小)

[4.2.1 内存分配规则](#4.2.1 内存分配规则)

[4.2.2 零宽度位域(强制对齐)](#4.2.2 零宽度位域(强制对齐))

[4.2.3 无名位域(填充用)](#4.2.3 无名位域(填充用))

五、typedef

[5.1 typedef 的常见用法](#5.1 typedef 的常见用法)

[5.1.1 为基本类型起别名](#5.1.1 为基本类型起别名)

[5.1.2 为结构体起别名(最常用)](#5.1.2 为结构体起别名(最常用))

[5.1.3 为指针起别名](#5.1.3 为指针起别名)

[5.1.4 为数组起别名](#5.1.4 为数组起别名)

[5.1.5 为函数指针起别名](#5.1.5 为函数指针起别名)

5.2 typedef vs #define

更复杂的差异

[六、输入 & 输出](#六、输入 & 输出)

[6.1 标准输入输出流](#6.1 标准输入输出流)

[6.2 格式化输入输出 - printf() & scanf()](#6.2 格式化输入输出 - printf() & scanf())

[6.2.1 printf()](#6.2.1 printf())

[6.2.2 scanf()](#6.2.2 scanf())

[scanf() 常见陷阱](#scanf() 常见陷阱)

[6.3 字符输入输出](#6.3 字符输入输出)

[6.3.1 getchar() 和 putchar()](#6.3.1 getchar() 和 putchar())

[6.3.2 getc() 和 putc()](#6.3.2 getc() 和 putc())

[6.4 字符串输入输出](#6.4 字符串输入输出)

[6.4.1 gets() 和 puts()(不推荐,已废弃)](#6.4.1 gets() 和 puts()(不推荐,已废弃))

[6.4.2 fgets() 和 fputs()](#6.4.2 fgets() 和 fputs())

[6.5 文件输入输出](#6.5 文件输入输出)

[6.5.1 文件操作基础](#6.5.1 文件操作基础)

[6.5.2 文件打开模式](#6.5.2 文件打开模式)

[6.6 对比](#6.6 对比)

[6.6.1 输出函数](#6.6.1 输出函数)

[6.6.2 输入函数](#6.6.2 输入函数)


一、字符串

在 C 语言中,字符串实际上是使用空字符 \0 结尾的一维字符数组。因此,\0 是用于标记字符串的结束。

cpp 复制代码
// 字符串 "Hello" 在内存中的存储
char str[] = "Hello";
// 实际存储: ['H','e','l','l','o','\0']
// 索引:      0   1   2   3   4   5

关键点:字符串长度是5,但数组长度是6(因为结尾的 \0 也占用一个字节)。

1.1 定义和初始化

cpp 复制代码
// 方式1:字符数组初始化(自动添加\0)
char str1[] = "Hello";      // 大小自动为6

// 方式2:指定大小
char str2[10] = "Hello";    // 剩余位置自动填充\0

// 方式3:字符数组逐个赋值(必须手动添加\0)
char str3[] = {'H','e','l','l','o','\0'};

// 方式4:字符指针(指向字符串常量)
char *str4 = "Hello";       // 存储在只读数据段,不可修改

可以修改 vs 不可修改

cpp 复制代码
// 可修改 vs 不可修改
char arr[] = "Hello";   // 可修改(栈上)
arr[0] = 'h';           // ✅ 变成 "hello"

char *ptr = "Hello";    // 不可修改(只读段)
ptr[0] = 'h';           // ❌ 未定义行为(可能崩溃)

// 但可以修改指针指向
ptr = "World";          // ✅ 重新指向另一个字符串

1.2 常用字符串函数(需引入 <string.h>)

cpp 复制代码
#include <string.h>

char s1[20] = "Hello";
char s2[] = "World";

// 获取长度(不包括\0)
int len = strlen(s1);        // len = 5

// 复制字符串
strcpy(s1, s2);              // s1变为"World"
strncpy(s1, s2, 3);          // 复制前3个字符

// 拼接字符串
strcat(s1, s2);              // s1需要足够大
strncat(s1, s2, 3);          // 拼接前3个

// 比较字符串(字典序)
int cmp = strcmp(s1, s2);    // 返回负数/0/正数
// strncmp() 只比较前n个字符

// 查找字符
char *pos = strchr(s1, 'o'); // 返回第一次出现的位置

// 查找子串
char *sub = strstr(s1, "ell"); // 返回子串首次出现的位置
序号 函数 & 目的
1 strcpy(s1, s2); 复制字符串 s2 到字符串 s1。
2 strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。
3 strlen(s1); 返回字符串 s1 的长度。
4 strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
5 strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。
6 strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。

二、结构体

结构体是C语言中一种用户自定义的数据类型允许将不同类型的数据组合在一起,形成一个新的数据类型

cpp 复制代码
// 定义学生结构体
struct Student {
    char name[50];      // 姓名
    int age;            // 年龄
    float score;        // 分数
    int id;             // 学号
};

2.1 结构体变量的声明和初始化

2.1.1 方式1:先定义类型,后声明变量

cpp 复制代码
struct Student {
    char name[50];
    int age;
    float score;
};

// 声明变量
struct Student stu1;
struct Student stu2;

2.1.2 方式2:定义类型的同时声明变量

cpp 复制代码
struct Student {
    char name[50];
    int age;
    float score;
} stu1, stu2;

2.1.3 方式3:省略类型名(匿名结构体)

cpp 复制代码
struct {
    char name[50];
    int age;
    float score;
} stu1, stu2;  // 只能在这里声明变量,后面无法再定义

2.1.4 初始化

cpp 复制代码
// 方法1:按顺序初始化
struct Student stu1 = {"张三", 20, 95.5, 10001};

// 方法2:指定成员初始化(C99)
struct Student stu2 = {.name = "李四", .age = 21, .score = 88.5, .id = 10002};

// 方法3:先声明后赋值
struct Student stu3;
// ❌ 错误!不能这样写
//stu.name = "小明";  // 编译错误:数组类型不可赋值
strcpy(stu3.name, "王五");
stu3.age = 19;
stu3.score = 92.0;
stu3.id = 10003;

2.2 访问结构体成员

使用 . 运算符(点运算符)

cpp 复制代码
#include <stdio.h>
#include <string.h>

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

int main() {
    struct Student stu;
    
    // 赋值
    strcpy(stu.name, "小明");
    stu.age = 18;
    stu.score = 89.5;
    
    // 访问
    printf("姓名: %s\n", stu.name);
    printf("年龄: %d\n", stu.age);
    printf("分数: %.1f\n", stu.score);
    
    // 修改
    stu.score = 95.0;
    printf("修改后分数: %.1f\n", stu.score);
    
    return 0;
}

2.3 结构体指针

2.3.1 定义和使用

cpp 复制代码
struct Student stu = {"张三", 20, 95.5};
struct Student *ptr = &stu;

// 通过指针访问成员的两种方式
// 方式1:(*ptr).成员 (括号不能省)
printf("%s\n", (*ptr).name);

// 方式2:ptr->成员 (箭头运算符,推荐)
printf("%d\n", ptr->age);
printf("%.1f\n", ptr->score);

// 修改成员
ptr->score = 98.0;

2.3.2 完整示例

cpp 复制代码
#include <stdio.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point p1 = {10, 20};
    struct Point *pPtr = &p1;
    
    // 通过指针修改
    pPtr->x = 30;
    (*pPtr).y = 40;
    
    printf("x = %d, y = %d\n", p1.x, p1.y);  // x = 30, y = 40
    
    return 0;
}

2.4 结构体数组

cpp 复制代码
#include <stdio.h>

struct Student {
    char name[50];
    int score;
};

int main() {
    // 定义并初始化结构体数组
    struct Student class[3] = {
        {"张三", 85},
        {"李四", 92},
        {"王五", 78}
    };
    
    // 遍历数组
    for(int i = 0; i < 3; i++) {
        printf("姓名: %s, 分数: %d\n", class[i].name, class[i].score);
    }
    
    // 修改某个元素
    strcpy(class[1].name, "李四_new");
    class[1].score = 95;
    
    return 0;
}

2.5 结构体嵌套

一个结构体包含另一个结构体

cpp 复制代码
// 日期结构体
struct Date {
    int year;
    int month;
    int day;
};

// 学生结构体包含日期
struct Student {
    char name[50];
    int age;
    struct Date birthday;  // 嵌套结构体
    struct Date enrollDate;
};

int main() {
    struct Student stu = {
        .name = "张三",
        .age = 20,
        .birthday = {2004, 5, 15},
        .enrollDate = {2022, 9, 1}
    };
    
    // 访问嵌套成员
    printf("生日: %d-%d-%d\n", 
           stu.birthday.year, 
           stu.birthday.month, 
           stu.birthday.day);
    
    return 0;
}

2.6 结构体作为函数参数

2.6.1 值传递(复制整个结构体)

cpp 复制代码
#include <stdio.h>

struct Point {
    int x;
    int y;
};

// 值传递:函数内修改不影响原变量
void movePoint(struct Point p, int dx, int dy) {
    p.x += dx;
    p.y += dy;
    printf("函数内部: (%d, %d)\n", p.x, p.y);
}

int main() {
    struct Point p1 = {10, 20};
    movePoint(p1, 5, 5);      // 输出: (15, 25)
    printf("外部: (%d, %d)\n", p1.x, p1.y);  // 输出: (10, 20) - 未改变
    
    return 0;
}

2.6.2 指针传递(推荐,效率高)

cpp 复制代码
// 指针传递:可以修改原变量,且效率高(只传指针)
void movePoint(struct Point *p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
}

int main() {
    struct Point p1 = {10, 20};
    movePoint(&p1, 5, 5);
    printf("(%d, %d)\n", p1.x, p1.y);  // 输出: (15, 25)
    
    return 0;
}

2.6.3 结构体作为返回值

cpp 复制代码
struct Point createPoint(int x, int y) {
    struct Point p;
    p.x = x;
    p.y = y;
    return p;  // 返回结构体副本
}

int main() {
    struct Point p = createPoint(10, 20);
    printf("(%d, %d)\n", p.x, p.y);
    return 0;
}

2.7 typedef 简化结构体使用

cpp 复制代码
// 不使用typedef
struct Student {
    char name[50];
    int age;
};
struct Student stu1;  // 每次都要写struct

// 使用typedef
typedef struct {
    char name[50];
    int age;
} Student;  // Student现在是类型名,不是变量

Student stu1;  // 不需要写struct
Student stu2;

// 更常见的写法
typedef struct Student {
    char name[50];
    int age;
} Student;

2.8 结构体大小与内存对齐

cpp 复制代码
#include <stdio.h>

struct A {
    char c;      // 1字节
    int i;       // 4字节
    char d;      // 1字节
};  // 实际大小可能不是6,而是12(取决于对齐)

struct B {
    int i;       // 4字节
    char c;      // 1字节
    char d;      // 1字节
};  // 大小可能是8

int main() {
    printf("struct A大小: %zu\n", sizeof(struct A));
    printf("struct B大小: %zu\n", sizeof(struct B));
    
    // 查看成员偏移量
    struct A a;
    printf("c偏移: %zu\n", (char*)&a.c - (char*)&a);
    printf("i偏移: %zu\n", (char*)&a.i - (char*)&a);
    printf("d偏移: %zu\n", (char*)&a.d - (char*)&a);
    
    return 0;
}

内存对齐规则

  • 结构体的大小是最大成员大小的整数倍

  • 成员的起始偏移量必须是其自身大小的整数倍

  • 可以通过 #pragma pack(1) 取消对齐(牺牲性能节省空间)

三、共同体

共用体是一种让多个成员共享同一块内存 的数据类型。与结构体不同,共用体的所有成员都使用同一个内存位置,同一时刻只能存储其中一个成员的值

cpp 复制代码
#include <stdio.h>

union Data {
    int i;      // 4字节
    float f;    // 4字节
    char c;     // 1字节
};

int main() {
    union Data d;
    
    printf("大小: %zu\n", sizeof(d));  // 输出4(最大成员的大小)
    
    d.i = 65;
    printf("d.i = %d\n", d.i);    // 65
    printf("d.c = %c\n", d.c);    // 65对应的字符 'A'
    printf("d.f = %f\n", d.f);    // 垃圾值(内存被解读为float)
    
    return 0;
}

3.1 跟结构体的区别

特性 结构体(struct) 共用体(union)
内存分配 各成员独立分配 所有成员共享内存
总大小 各成员大小之和(+对齐) 最大成员的大小
使用方式 可同时存储所有成员 一次只能用一个成员
成员互相影响 互不影响 互相覆盖

四、位域

位域允许我们在结构体或共用体中以位(bit)为单位指定成员所占用的内存大小,主要用于节省内存操作硬件寄存器

cpp 复制代码
#include <stdio.h>

struct Flag {
    unsigned int flag1 : 1;  // 占1位
    unsigned int flag2 : 1;  // 占1位
    unsigned int flag3 : 2;  // 占2位
};

int main() {
    struct Flag f;
    printf("大小: %zu 字节\n", sizeof(f));  // 通常输出4字节
    
    f.flag1 = 1;
    f.flag2 = 0;
    f.flag3 = 3;  // 二进制 11
    
    printf("flag1: %u\n", f.flag1);
    printf("flag2: %u\n", f.flag2);
    printf("flag3: %u\n", f.flag3);
    
    return 0;
}

4.1 位域的类型

支持的数据类型:

cpp 复制代码
#include <stdio.h>

struct BitFields {
    unsigned int a : 1;   // 无符号(推荐)
    signed int b : 2;     // 有符号(取值范围:-2 到 1)
    unsigned char c : 3;  // 字符型(取值范围:0-7)
    // int d : 33;        // ❌ 错误!不能超过类型本身位数
};

int main() {
    struct BitFields bf;
    
    bf.a = 1;
    bf.b = -1;  // 有符号位域
    bf.c = 7;
    
    printf("a: %u\n", bf.a);
    printf("b: %d\n", bf.b);
    printf("c: %u\n", bf.c);
    
    return 0;
}

常用类型:

  • unsigned int - 无符号整数(推荐)
  • int - 有符号整数
  • unsigned char - 无符号字符
  • _Bool - 布尔类型(C99)

4.2 位域的存储和大小

4.2.1 内存分配规则

cpp 复制代码
#include <stdio.h>

// 示例1:打包在同一字节
struct Packed {
    unsigned int a : 2;  // 位0-1
    unsigned int b : 3;  // 位2-4
    unsigned int c : 3;  // 位5-7
};  // 大小:1字节(如果unsigned int是1字节)

// 示例2:跨字节
struct CrossByte {
    unsigned int a : 4;  // 位0-3
    unsigned int b : 6;  // 位4-9(跨字节)
};  // 大小:2字节

// 示例3:不同存储单元
struct DifferentUnit {
    unsigned int a : 16;  // 占2字节
    unsigned int b : 16;  // 占2字节
};  // 大小:4字节

int main() {
    printf("Packed大小: %zu\n", sizeof(struct Packed));
    printf("CrossByte大小: %zu\n", sizeof(struct CrossByte));
    printf("DifferentUnit大小: %zu\n", sizeof(struct DifferentUnit));
    
    return 0;
}

4.2.2 零宽度位域(强制对齐)

cpp 复制代码
struct AlignExample {
    unsigned int a : 4;
    unsigned int b : 4;
    unsigned int : 0;      // 零宽度位域,强制对齐到下一个存储单元边界
    unsigned int c : 8;
};
// 可能占用8字节(4 + 4),而不是4字节

4.2.3 无名位域(填充用)

cpp 复制代码
struct Padding {
    unsigned int a : 4;
    unsigned int : 2;      // 无名位域,占2位但不使用
    unsigned int b : 2;
};
// a占4位,跳过2位,b占2位

五、typedef

typedef 是 C 语言中用于为已有类型创建别名的关键字。它不创建新类型,只是给现有类型起一个更方便、更直观的名字。

cpp 复制代码
#include <stdio.h>

typedef int Integer;        // Integer 是 int 的别名
typedef float Real;         // Real 是 float 的别名

int main() {
    Integer a = 10;         // 等价于 int a = 10;
    Real b = 3.14;          // 等价于 float b = 3.14;
    
    printf("a = %d, b = %.2f\n", a, b);
    
    return 0;
}

5.1 typedef 的常见用法

5.1.1 为基本类型起别名

cpp 复制代码
typedef unsigned int uint;      // 无符号整数
typedef unsigned char uchar;    // 无符号字符
typedef long long ll;           // 长长整数
typedef const char* cstring;    // 常量字符串指针

int main() {
    uint count = 100;
    uchar ch = 'A';
    ll big = 123456789012345;
    cstring msg = "Hello";
    
    return 0;
}

5.1.2 为结构体起别名(最常用)

cpp 复制代码
// 不使用 typedef
struct Student {
    char name[50];
    int age;
};
struct Student stu1;  // 每次都要写 struct

// 使用 typedef
typedef struct {
    char name[50];
    int age;
} Student;  // Student 现在是类型名

Student stu1;         // 不需要写 struct
Student stu2;

// 更完整的写法(推荐)
typedef struct Student {
    char name[50];
    int age;
} Student;  // 可以同时使用 struct Student 和 Student

5.1.3 为指针起别名

cpp 复制代码
#include <stdio.h>

typedef int* IntPtr;        // IntPtr 是 int* 的别名
typedef char* String;       // 把 char* 叫做 String

int main() {
    int x = 10;
    IntPtr p = &x;          // 等价于 int *p = &x;
    *p = 20;
    printf("x = %d\n", x);  // 20
    
    String str = "Hello";   // 等价于 char *str = "Hello";
    printf("%s\n", str);
    
    return 0;
}

5.1.4 为数组起别名

cpp 复制代码
#include <stdio.h>

typedef int IntArray[10];   // IntArray 是长度为10的int数组类型
typedef char Name[50];      // Name 是长度为50的char数组类型

int main() {
    IntArray arr = {1,2,3,4,5};  // 等价于 int arr[10] = {...}
    Name username = "张三";       // 等价于 char username[50] = "张三"
    
    for(int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n%s\n", username);
    
    return 0;
}

5.1.5 为函数指针起别名

cpp 复制代码
#include <stdio.h>

// 定义函数指针类型
typedef int (*CompareFunc)(int, int);

// 比较函数
int max(int a, int b) {
    return a > b ? a : b;
}

int min(int a, int b) {
    return a < b ? a : b;
}

// 使用函数指针作为参数
void printCompare(int x, int y, CompareFunc cmp, const char* name) {
    printf("%s: %d\n", name, cmp(x, y));
}

int main() {
    printCompare(10, 20, max, "最大值");
    printCompare(10, 20, min, "最小值");
    
    return 0;
}

5.2 typedef vs #define

特性 typedef #define
处理时间 编译时 预处理时
作用域 遵循C语言作用域规则 从定义到文件结束
语法 以分号结尾 不需要分号
智能程度 理解类型 纯文本替换
cpp 复制代码
#include <stdio.h>

#define PINT int*
typedef int* TINT;

int main() {
    PINT a, b;   // 展开为:int *a, b;  → a是指针,b是int
    TINT c, d;   // c和d都是int*指针
    
    // 验证
    a = (int*)malloc(sizeof(int));
    b = 10;      // ✅ b是int,可以赋值整数
    
    c = (int*)malloc(sizeof(int));
    d = (int*)malloc(sizeof(int));  // d也是指针
    
    printf("sizeof(b)=%zu\n", sizeof(b));  // 4(int)
    printf("sizeof(d)=%zu\n", sizeof(d));  // 8(指针)
    
    return 0;
}

更复杂的差异

cpp 复制代码
#define CONST_INT const int
typedef const int TConstInt;

int main() {
    // #define 是简单替换
    CONST_INT x = 10;
    // CONST_INT y, z;  // 展开为 const int y, z; ✅
    
    // typedef 是类型定义
    TConstInt a = 10;
    TConstInt b = 20;  // 正确,都是 const int
    
    // 指针差异示例
    #define PCHAR char*
    typedef char* TCHAR;
    
    const PCHAR p1;     // 展开为 const char* p1(指针本身可变?)
    const TCHAR p2;     // const 修饰指针 → char* const p2(指针常量)
    
    return 0;
}

六、输入 & 输出

C语言的输入输出通过标准库函数实现,主要包含在 <stdio.h> 头文件中。C本身不提供输入输出关键字,所有IO操作都通过函数完成。

6.1 标准输入输出流

C程序运行时自动打开三个标准流:

设备 函数 文件指针
标准输入 键盘 scanf()getchar() stdin
标准输出 屏幕 printf()putchar() stdout
标准错误 屏幕 perror()fprintf(stderr) stderr

6.2 格式化输入输出 - printf() & scanf()

6.2.1 printf()

cpp 复制代码
#include <stdio.h>

int main() {
    int a = 100;
    float b = 3.14159;
    char c = 'A';
    char str[] = "Hello";
    
    // 基本类型
    printf("整数: %d\n", a);           // 十进制
    printf("整数(八进制): %o\n", a);   // 144
    printf("整数(十六进制): %x\n", a); // 64
    printf("整数(十六进制大写): %X\n", a); // 64
    printf("无符号整数: %u\n", a);
    
    // 浮点数
    printf("浮点数: %f\n", b);         // 3.141590
    printf("指数形式: %e\n", b);       // 3.141590e+00
    printf("自动选择: %g\n", b);       // 3.14159
    
    // 字符和字符串
    printf("字符: %c\n", c);           // A
    printf("字符串: %s\n", str);       // Hello
    
    // 指针
    printf("地址: %p\n", &a);          // 0x7ffee...
    
    return 0;
}

格式修饰符

cpp 复制代码
#include <stdio.h>

int main() {
    int num = 123;
    float pi = 3.14159;
    
    // 宽度控制
    printf("%5d\n", num);      // "  123" (右对齐,宽度5)
    printf("%-5d\n", num);     // "123  " (左对齐)
    printf("%05d\n", num);     // "00123" (补零)
    
    // 精度控制
    printf("%.2f\n", pi);      // "3.14" (保留2位小数)
    printf("%8.2f\n", pi);     // "    3.14" (宽度8,2位小数)
    printf("%.0f\n", pi);      // "3" (取整)
    
    // 字符串精度
    printf("%.5s\n", "Hello World"); // "Hello" (只输出前5个字符)
    
    // 宽度和精度组合
    printf("%*.*f\n", 8, 2, pi);     // 动态指定宽度和精度
    
    // 标志位组合
    printf("%+d\n", num);      // "+123" (显示正号)
    printf("% d\n", num);      // " 123" (空格代替正号)
    printf("%#x\n", num);      // "0x7b" (显示0x前缀)
    printf("%#o\n", num);      // "0173" (显示前导0)
    
    return 0;
}
格式符 含义 示例
%d 十进制整数 printf("%d", 10);10
%i %d printf("%i", 10);10
%u 无符号十进制 printf("%u", 10);10
%o 八进制 printf("%o", 10);12
%x 十六进制(小写) printf("%x", 10);a
%X 十六进制(大写) printf("%X", 10);A
%f 浮点数 printf("%f", 3.14);3.140000
%e 科学计数法 printf("%e", 3.14);3.140000e+00
%g 自动选择%f%e -
%c 单个字符 printf("%c", 'A');A
%s 字符串 printf("%s", "Hi");Hi
%p 指针地址 printf("%p", &a);0x7ff...
%% 百分号 printf("%%");%

6.2.2 scanf()

cpp 复制代码
#include <stdio.h>

int main() {
    int age;
    float height;
    char name[50];
    char grade;
    
    printf("请输入年龄: ");
    scanf("%d", &age);           // 注意取地址符 &
    
    printf("请输入身高: ");
    scanf("%f", &height);
    
    printf("请输入姓名: ");
    scanf("%s", name);           // 数组名本身就是地址,不需要 &
    
    printf("请输入等级: ");
    scanf(" %c", &grade);        // 前面的空格用于跳过换行符
    
    printf("年龄: %d, 身高: %.2f, 姓名: %s, 等级: %c\n", 
           age, height, name, grade);
    
    return 0;
}

高级用法

cpp 复制代码
#include <stdio.h>

int main() {
    int a, b, c;
    char str[100];
    
    // 多个输入
    scanf("%d %d %d", &a, &b, &c);
    
    // 指定宽度(防止溢出)
    char name[10];
    scanf("%9s", name);  // 最多读取9个字符
    
    // 读取特定字符集
    char hex[20];
    scanf("%x", hex);    // 读取十六进制字符串
    
    // 跳过特定字符
    int day, month, year;
    scanf("%d/%d/%d", &day, &month, &year);  // 输入: 15/08/2024
    
    // 读取直到遇到特定字符
    char line[100];
    scanf("%[^\n]", line);  // 读取一行(直到换行符)
    
    // 读取特定字符集
    char letters[50];
    scanf("%[a-zA-Z]", letters);  // 只读取字母
    
    // 返回值(成功读取的项目数)
    int count = scanf("%d %d", &a, &b);
    printf("成功读取了 %d 个项目\n", count);
    
    return 0;
}

scanf() 常见陷阱

cpp 复制代码
#include <stdio.h>

int main() {
    // 陷阱1:输入缓冲区残留换行符
    int num;
    char ch;
    
    printf("输入数字: ");
    scanf("%d", &num);
    
    printf("输入字符: ");
    // scanf("%c", &ch);  // ❌ 会读取残留的换行符
    
    // 解决方法1:使用空格
    scanf(" %c", &ch);     // ✅ 空格跳过空白字符
    
    // 解决方法2:清空缓冲区
    while(getchar() != '\n');  // 清空缓冲区
    scanf("%c", &ch);
    
    // 陷阱2:字符串输入包含空格
    char text[100];
    // scanf("%s", text);   // ❌ 遇到空格就停止
    
    // 解决方法:使用 %[^\n]
    scanf(" %[^\n]", text);  // ✅ 读取整行
    
    return 0;
}

6.3 字符输入输出

6.3.1 getchar() 和 putchar()

cpp 复制代码
#include <stdio.h>

int main() {
    // 单个字符输入输出
    char ch;
    
    printf("请输入一个字符: ");
    ch = getchar();           // 读取一个字符
    printf("您输入的字符是: ");
    putchar(ch);              // 输出一个字符
    putchar('\n');
    
    // 循环读取直到换行
    printf("请输入一行文字: ");
    while((ch = getchar()) != '\n') {
        putchar(ch);
    }
    putchar('\n');
    
    // 检测文件结束符 Ctrl+D (Linux) 或 Ctrl+Z (Windows)
    printf("输入字符(Ctrl+D退出):\n");
    while((ch = getchar()) != EOF) {
        putchar(ch);
    }
    
    return 0;
}

6.3.2 getc() 和 putc()

cpp 复制代码
#include <stdio.h>

int main() {
    // getc/putc 可以指定流
    char ch;
    
    printf("请输入: ");
    ch = getc(stdin);      // 从标准输入读取
    putc(ch, stdout);      // 输出到标准输出
    putc('\n', stdout);
    
    return 0;
}

6.4 字符串输入输出

6.4.1 gets() 和 puts()(不推荐,已废弃)

cpp 复制代码
#include <stdio.h>

int main() {
    char str[10];
    
    // ❌ 危险的 gets(),不检查边界(C11已移除)
    // gets(str);  // 危险!可能导致缓冲区溢出
    
    // ✅ 安全的 fgets()
    printf("输入字符串: ");
    fgets(str, sizeof(str), stdin);  // 限制读取大小
    
    // puts 自动添加换行符
    puts(str);  // 等价于 printf("%s\n", str);
    
    return 0;
}

6.4.2 fgets() 和 fputs()

cpp 复制代码
#include <stdio.h>

int main() {
    char buffer[100];
    
    // 从标准输入读取一行(包括换行符)
    printf("请输入一行文字: ");
    fgets(buffer, sizeof(buffer), stdin);
    
    // 移除可能存在的换行符
    size_t len = strlen(buffer);
    if (len > 0 && buffer[len-1] == '\n') {
        buffer[len-1] = '\0';
    }
    
    // 输出到标准输出(不自动添加换行符)
    fputs(buffer, stdout);
    fputs("\n", stdout);
    
    return 0;
}

6.5 文件输入输出

6.5.1 文件操作基础

cpp 复制代码
#include <stdio.h>

int main() {
    FILE *fp;
    char buffer[100];
    
    // 写入文件
    fp = fopen("test.txt", "w");
    if (fp == NULL) {
        perror("打开文件失败");
        return 1;
    }
    fprintf(fp, "Hello, File!\n");
    fclose(fp);
    
    // 读取文件
    fp = fopen("test.txt", "r");
    if (fp == NULL) {
        perror("打开文件失败");
        return 1;
    }
    fgets(buffer, sizeof(buffer), fp);
    printf("读取内容: %s", buffer);
    fclose(fp);
    
    return 0;
}

6.5.2 文件打开模式

cpp 复制代码
模式	含义
"r"	只读(文件必须存在)
"w"	只写(创建或覆盖)
"a"	追加(文件末尾添加)
"r+"	读写(文件必须存在)
"w+"	读写(创建或覆盖)
"a+"	读写(追加模式)
"rb"	二进制只读
"wb"	二进制只写

6.6 对比

6.6.1 输出函数

函数 目标 格式化 自动换行 安全性
printf() stdout 中等
fprintf() 指定文件 中等
sprintf() 字符串 ❌危险
snprintf() 字符串 ✅安全
puts() stdout 中等
putchar() stdout 安全
fputs() 指定文件 安全

6.6.2 输入函数

函数 来源 格式化 边界检查 安全性
scanf() stdin 中等
fscanf() 指定文件 中等
sscanf() 字符串 中等
gets() stdin ❌危险
fgets() 指定文件 ✅安全
getchar() stdin 安全
相关推荐
清平乐的技术专栏1 小时前
【FlinkSQL笔记】(三)Flink SQL 核心重难点(窗口函数、水印)
笔记·sql·flink
存在的五月雨1 小时前
JVM 入门学习笔记(内存模型 / GC / 类加载机制)
jvm·笔记·学习
薇茗1 小时前
【初阶数据结构】 升沉有序的平仄 排序 3
c语言·开发语言·数据结构·算法·排序算法·文件归并排序
字节高级特工1 小时前
C++11(一) 革新:右值引用与移动语义
java·开发语言·c++·人工智能·后端
孬甭_1 小时前
双向链表详解
c语言·数据结构·链表
薇茗1 小时前
【初阶数据结构】 升沉有序的平仄 排序 2
c语言·数据结构·算法·排序算法·快排精讲
安生生申1 小时前
uni-app 连接 JDY-31 蓝牙串口模块实践
c语言·前端·javascript·stm32·单片机·嵌入式硬件·uni-app
小离a_a1 小时前
uniapp小程序封装圆环显示比例数据
android·小程序·uni-app
三少爷的鞋1 小时前
Android 面试系列:runBlocking 到底该在哪用?
android