目录
[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 无名位域(填充用))
[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 为函数指针起别名)
[六、输入 & 输出](#六、输入 & 输出)
[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 | ❌ | ✅ | 安全 |