printf是C语言中最常用的格式化输出函数,用于将格式化的数据输出到标准输出(stdout)。
格式化输出说明符
| 说明符 | 类型 | 示例 |
|---|---|---|
%d |
有符号十进制整数 | printf("%d", 42); → 42 |
%u |
无符号十进制整数 | printf("%u", 42u); → 42 |
%f |
浮点数(默认6位小数) | printf("%f", 3.14); → 3.140000 |
%c |
字符 | printf("%c", 'A'); → A |
%s |
字符串 | printf("%s", "Hello"); → Hello |
%p |
指针地址 | printf("%p", ptr); → 0x7fff5fbff8d0 |
%x |
十六进制(小写) | printf("%x", 255); → ff |
%X |
十六进制(大写) | printf("%X", 255); → FF |
%o |
八进制 | printf("%o", 8); → 10 |
%ld |
长整型 | printf("%ld", 1234567890L); |
%lu |
无符号长整型 | printf("%lu", 1234567890UL); |
%zu |
size_t类型 | printf("%zu", sizeof(int)); |
%e |
科学计数法 | printf("%e", 3140000.0); → 3.140000e+06 |
%g |
自动选择%f或%e | printf("%g", 0.00001); → 1e-05 |
转义字符
| 转义字符 | 含义 |
|---|---|
\n |
换行 |
\t |
制表符 |
\\ |
反斜杠 |
\" |
双引号 |
\0 |
空字符 |
\r |
回车 |
宽度和精度控制
printf("%10d", 42); // 右对齐,宽度10:" 42"
printf("%-10d", 42); // 左对齐,宽度10:"42 "
printf("%010d", 42); // 零填充:"0000000042"
printf("%.2f", 3.14159); // 2位小数:"3.14"
printf("%10.2f", 3.14); // 宽度10,2位小数:" 3.14"
scanf用于从标准输入(stdin)读取格式化数据。
基本用法
int num;
scanf("%d", &num); // 注意:必须使用&取地址
char str[100];
scanf("%s", str); // 字符串不需要&
float f;
scanf("%f", &f);
scanf的返回值
int result = scanf("%d %d", &a, &b);
// result: 成功匹配的项数
// 返回值为EOF时表示输入结束或错误
缓冲区残留问题
int num;
char str[100];
// 问题示例
scanf("%d", &num); // 输入"123\n"
scanf("%s", str); // 可能读取到换行符
// 解决方案
scanf("%d", &num);
while (getchar() != '\n'); // 清除缓冲区
scanf("%99s", str); // 限制读取长度
sprintf:格式化到字符串缓冲区
char buffer[100];
sprintf(buffer, "Value: %d, Name: %s", 42, "test");
// buffer = "Value: 42, Name: test"
snprintf:安全版本,防止缓冲区溢出
char buffer[10];
int result = snprintf(buffer, sizeof(buffer), "Value: %d", 123456789);
// buffer = "Value: 123"
// result = 需要的字符数(不含'\0')
// 如果result >= sizeof(buffer),表示输出被截断
fprintf:格式化输出到文件
FILE *fp = fopen("output.txt", "w");
if (fp) {
fprintf(fp, "Integer: %d\n", 42);
fprintf(fp, "String: %s\n", "Hello");
fclose(fp);
}
fscanf:从文件格式化输入
FILE *fp = fopen("input.txt", "r");
if (fp) {
int num;
char str[100];
fscanf(fp, "%d %s", &num, str);
fclose(fp);
}
// gets:已废弃,不安全
// char str[100];
// gets(str); // 可能导致缓冲区溢出
// puts:输出字符串并自动换行
puts("Hello, World!"); // 等同于 printf("Hello, World!\n");
串口输出格式化
printf("[UART] Received: 0x%02X\n", data);
printf("[ERROR] Code: %d, Message: %s\n", err_code, err_msg);
日志构造
char log_buffer[256];
snprintf(log_buffer, sizeof(log_buffer),
"[%s] %s: %d", timestamp, function, line);
配置文件解析
FILE *config = fopen("config.txt", "r");
if (config) {
fscanf(config, "baudrate=%d", &baudrate);
fscanf(config, "timeout=%d", &timeout);
fclose(config);
}
| 说明符 | 类型 | 大小修饰符 | 示例 |
|---|---|---|---|
%d |
int | %hd(short), %ld(long), %lld(long long) |
%ld |
%u |
unsigned int | %hu, %lu, %llu |
%lu |
%f |
double | %Lf(long double) |
%Lf |
%c |
char | - | %c |
%s |
char* | - | %s |
%p |
void* | - | %p |
%x |
unsigned int | %hx, %lx, %llx |
%lx |
%n |
int* | - | 存储已输出字符数 |
| 说明符 | 用途 | 示例输入 |
|---|---|---|
%d |
读取int | 123 |
%u |
读取unsigned int | 123 |
%f |
读取float | 3.14 |
%lf |
读取double | 3.14 |
%c |
读取char(包括空格) | a |
%s |
读取字符串(遇空格停止) | hello |
%[^\n] |
读取整行 | hello world |
%n |
存储已读取字符数 | - |
%* |
跳过输入(赋值抑制) | - |
// 宽度和精度
printf("%10d", 42); // 右对齐,宽度10
printf("%-10d", 42); // 左对齐
printf("%010d", 42); // 零填充
printf("%.2f", 3.14159); // 2位小数
printf("%10.2f", 3.14); // 宽度10,2位小数
// 动态精度
printf("%.*f", precision, value); // precision是变量
// 特殊格式
printf("%#x", 255); // 输出前缀:0xff
printf("%#o", 8); // 输出前缀:010
printf("%+d", 42); // 显示正号:+42
printf(" %d", 42); // 空格填充正数: 42
// printf_example.c
#include <stdio.h>
#include <stdint.h>
int main() {
int int_val = 42;
unsigned int uint_val = 255;
float float_val = 3.14159;
double double_val = 2.718281828;
char char_val = 'A';
char str_val[] = "Hello, World!";
void *ptr_val = &int_val;
printf("=== Basic Format Specifiers ===\n");
printf("%%d: %d\n", int_val);
printf("%%u: %u\n", uint_val);
printf("%%f: %f\n", float_val);
printf("%%e: %e\n", float_val);
printf("%%g: %g\n", float_val);
printf("%%c: %c\n", char_val);
printf("%%s: %s\n", str_val);
printf("%%p: %p\n", ptr_val);
printf("%%x: %x\n", uint_val);
printf("%%X: %X\n", uint_val);
printf("%%o: %o\n", uint_val);
printf("\n=== Size Modifiers ===\n");
printf("%%hd: %hd\n", (short)12345);
printf("%%ld: %ld\n", 1234567890L);
printf("%%lld: %lld\n", 123456789012345LL);
printf("%%hu: %hu\n", (unsigned short)12345);
printf("%%lu: %lu\n", 1234567890UL);
printf("%%llu: %llu\n", 123456789012345ULL);
printf("%%zu: %zu\n", sizeof(int));
printf("\n=== Width and Precision ===\n");
printf("%%10d: [%10d]\n", 42);
printf("%%-10d: [%-10d]\n", 42);
printf("%%010d: [%010d]\n", 42);
printf("%%.2f: [%.2f]\n", float_val);
printf("%%10.2f: [%10.2f]\n", float_val);
printf("%%-10.2f: [%-10.2f]\n", float_val);
printf("%%10.5f: [%10.5f]\n", float_val);
printf("\n=== Escape Characters ===\n");
printf("Newline: \"Line1\\nLine2\"\n");
printf("Tab: \"Col1\\tCol2\"\n");
printf("Backslash: \"C:\\\\path\"\n");
printf("Quote: \"Say \\\"Hello\\\"\"\n");
printf("\n=== Special Formats ===\n");
printf("%%#x: %#x\n", uint_val);
printf("%%#X: %#X\n", uint_val);
printf("%%#o: %#o\n", uint_val);
printf("%%+d: %+d\n", 42);
printf("%%+d: %+d\n", -42);
printf(" %%d: % d\n", 42);
return 0;
}
编译命令:
gcc -o printf_example printf_example.c
./printf_example
// scanf_example.c
#include <stdio.h>
#include <string.h>
int main() {
int int_val;
unsigned int uint_val;
float float_val;
double double_val;
char char_val;
char str_val[100];
char line_val[100];
printf("=== Basic scanf Usage ===\n");
printf("Enter an integer: ");
scanf("%d", &int_val);
printf("You entered: %d\n", int_val);
printf("Enter an unsigned integer: ");
scanf("%u", &uint_val);
printf("You entered: %u\n", uint_val);
printf("Enter a float: ");
scanf("%f", &float_val);
printf("You entered: %f\n", float_val);
printf("Enter a double: ");
scanf("%lf", &double_val);
printf("You entered: %f\n", double_val);
// 清除缓冲区中的换行符
while (getchar() != '\n');
printf("\n=== Character and String Input ===\n");
printf("Enter a character: ");
scanf("%c", &char_val);
printf("You entered: %c\n", char_val);
// 清除缓冲区
while (getchar() != '\n');
printf("Enter a word (no spaces): ");
scanf("%99s", str_val); // 限制长度防止溢出
printf("You entered: %s\n", str_val);
// 清除缓冲区
while (getchar() != '\n');
printf("Enter a line: ");
scanf("%99[^\n]", line_val); // 读取整行
printf("You entered: %s\n", line_val);
printf("\n=== Return Value ===\n");
int a, b, result;
printf("Enter two integers: ");
result = scanf("%d %d", &a, &b);
printf("Successfully matched %d items\n", result);
printf("Values: %d and %d\n", a, b);
printf("\n=== Multiple Values ===\n");
int x, y, z;
printf("Enter three integers separated by spaces: ");
if (scanf("%d %d %d", &x, &y, &z) == 3) {
printf("Sum: %d\n", x + y + z);
} else {
printf("Error reading input\n");
}
return 0;
}
编译命令:
gcc -o scanf_example scanf_example.c
./scanf_example
// sprintf_example.c
#include <stdio.h>
#include <string.h>
int main() {
char buffer[200];
char small_buffer[10];
int num = 42;
float pi = 3.14159;
char name[] = "World";
printf("=== sprintf Examples ===\n");
// 基本sprintf
sprintf(buffer, "Number: %d, Float: %.2f, String: %s", num, pi, name);
printf("Result: %s\n", buffer);
// 格式化到字符串
sprintf(buffer, "Hex: 0x%08X", 0xDEADBEEF);
printf("Result: %s\n", buffer);
printf("\n=== snprintf Examples ===\n");
// 安全格式化,防止溢出
int result = snprintf(small_buffer, sizeof(small_buffer),
"Value: %d", 123456789);
printf("Small buffer: '%s'\n", small_buffer);
printf("Required length: %d\n", result);
if (result >= sizeof(small_buffer)) {
printf("WARNING: Output was truncated!\n");
}
// 正常情况
result = snprintf(small_buffer, sizeof(small_buffer),
"Value: %d", 42);
printf("Small buffer: '%s'\n", small_buffer);
printf("Required length: %d\n", result);
printf("\n=== Practical Examples ===\n");
// 构造日志消息
char log_msg[256];
int line_num = 42;
const char *func_name = "main";
snprintf(log_msg, sizeof(log_msg),
"[%s:%d] Processing data...", func_name, line_num);
printf("Log: %s\n", log_msg);
// 构造文件路径
char filepath[512];
int file_id = 1001;
snprintf(filepath, sizeof(filepath),
"/data/logs/log_%04d.txt", file_id);
printf("Path: %s\n", filepath);
// 构造URL
char url[1024];
const char *host = "api.example.com";
const char *endpoint = "/users";
int user_id = 123;
snprintf(url, sizeof(url),
"https://%s%s?id=%d", host, endpoint, user_id);
printf("URL: %s\n", url);
return 0;
}
编译命令:
gcc -o sprintf_example sprintf_example.c
./sprintf_example
// file_io_example.c
#include <stdio.h>
#include <stdlib.h>
// 学生结构体
typedef struct {
int id;
char name[50];
float score;
} Student;
int main() {
const char *filename = "students.txt";
FILE *fp;
printf("=== Writing to File with fprintf ===\n");
fp = fopen(filename, "w");
if (fp == NULL) {
perror("Error opening file for writing");
return 1;
}
// 写入标题
fprintf(fp, "# Student Records\n");
fprintf(fp, "# ID\tName\tScore\n");
// 写入数据
Student students[] = {
{1, "Alice", 95.5},
{2, "Bob", 87.3},
{3, "Charlie", 92.1}
};
for (int i = 0; i < 3; i++) {
fprintf(fp, "%d\t%s\t%.1f\n",
students[i].id, students[i].name, students[i].score);
}
fclose(fp);
printf("Data written to %s\n", filename);
// 显示文件内容
printf("\n=== File Contents ===\n");
fp = fopen(filename, "r");
if (fp) {
char line[256];
while (fgets(line, sizeof(line), fp)) {
printf("%s", line);
}
fclose(fp);
}
printf("\n=== Reading from File with fscanf ===\n");
fp = fopen(filename, "r");
if (fp == NULL) {
perror("Error opening file for reading");
return 1;
}
// 跳过注释行
char line[256];
while (fgets(line, sizeof(line), fp)) {
if (line[0] != '#') {
break;
}
}
// 读取数据
Student read_student;
while (fscanf(fp, "%d %49s %f",
&read_student.id, read_student.name, &read_student.score) == 3) {
printf("Read: ID=%d, Name=%s, Score=%.1f\n",
read_student.id, read_student.name, read_student.score);
}
fclose(fp);
printf("\n=== Writing Formatted Data ===\n");
const char *data_file = "data.txt";
fp = fopen(data_file, "w");
if (fp) {
// 格式化写入多种数据类型
fprintf(fp, "Integer: %d\n", 42);
fprintf(fp, "Float: %.3f\n", 3.14159);
fprintf(fp, "String: %s\n", "Hello");
fprintf(fp, "Char: %c\n", 'A');
fprintf(fp, "Pointer: %p\n", (void*)fp);
fprintf(fp, "Hex: 0x%08X\n", 0xDEADBEEF);
fclose(fp);
printf("Formatted data written to %s\n", data_file);
}
return 0;
}
编译命令:
gcc -o file_io_example file_io_example.c
./file_io_example
// embedded_io_example.c
#include <stdio.h>
#include <stdint.h>
#include <string.h>
// 模拟串口寄存器
#define UART_DR (*(volatile uint32_t*)0x40011004)
#define UART_SR (*(volatile uint32_t*)0x40011000)
// 日志级别
typedef enum {
LOG_DEBUG,
LOG_INFO,
LOG_WARN,
LOG_ERROR
} LogLevel;
// 日志函数
void log_message(LogLevel level, const char *function, int line,
const char *format, ...) {
char buffer[256];
char *level_str;
switch (level) {
case LOG_DEBUG: level_str = "DEBUG"; break;
case LOG_INFO: level_str = "INFO"; break;
case LOG_WARN: level_str = "WARN"; break;
case LOG_ERROR: level_str = "ERROR"; break;
default: level_str = "UNKNOWN"; break;
}
// 构造日志前缀
int prefix_len = snprintf(buffer, sizeof(buffer), "[%s] %s:%d: ",
level_str, function, line);
// 格式化实际消息
va_list args;
va_start(args, format);
vsnprintf(buffer + prefix_len, sizeof(buffer) - prefix_len, format, args);
va_end(args);
// 输出到串口(模拟)
printf("%s\n", buffer);
}
// 简化版日志宏
#define LOG(level, ...) log_message(level, __func__, __LINE__, __VA_ARGS__)
// 传感器数据结构
typedef struct {
uint32_t timestamp;
int16_t temperature;
uint16_t humidity;
uint32_t pressure;
} SensorData;
// 格式化传感器数据为CSV格式
int format_sensor_csv(const SensorData *data, char *buffer, size_t size) {
return snprintf(buffer, size, "%u,%d,%u,%u",
data->timestamp, data->temperature,
data->humidity, data->pressure);
}
// 格式化传感器数据为JSON格式
int format_sensor_json(const SensorData *data, char *buffer, size_t size) {
return snprintf(buffer, size,
"{\"timestamp\":%u,\"temperature\":%d,\"humidity\":%u,\"pressure\":%u}",
data->timestamp, data->temperature,
data->humidity, data->pressure);
}
// 解析配置文件
int parse_config(const char *filename, int *baudrate, int *timeout) {
FILE *fp = fopen(filename, "r");
if (!fp) {
return -1;
}
char line[128];
while (fgets(line, sizeof(line), fp)) {
// 跳过注释和空行
if (line[0] == '#' || line[0] == '\n') {
continue;
}
// 解析key=value格式
if (sscanf(line, "baudrate=%d", baudrate) == 1) {
continue;
}
if (sscanf(line, "timeout=%d", timeout) == 1) {
continue;
}
}
fclose(fp);
return 0;
}
// 模拟硬件寄存器格式化输出
void format_register_dump(uint32_t *registers, int count) {
printf("=== Register Dump ===\n");
for (int i = 0; i < count; i++) {
printf("REG[0x%02X]: 0x%08X (bin: ", i * 4, registers[i]);
// 输出二进制表示
for (int bit = 31; bit >= 0; bit--) {
printf("%d", (registers[i] >> bit) & 1);
if (bit % 4 == 0 && bit > 0) {
printf(" ");
}
}
printf(")\n");
}
}
int main() {
printf("=== Embedded IO Examples ===\n\n");
// 1. 日志格式化
printf("--- Log Messages ---\n");
LOG(LOG_INFO, "System initialized");
LOG(LOG_DEBUG, "Sensor value: %d", 25);
LOG(LOG_WARN, "Low battery: %d%%", 15);
LOG(LOG_ERROR, "Failed to read sensor at address 0x%02X", 0x48);
// 2. 传感器数据格式化
printf("\n--- Sensor Data Formatting ---\n");
SensorData sensor = {
.timestamp = 1234567890,
.temperature = 256, // 25.6°C (放大10倍)
.humidity = 650, // 65.0% (放大10倍)
.pressure = 101325 // 1013.25 hPa (放大100倍)
};
char csv_buffer[128];
format_sensor_csv(&sensor, csv_buffer, sizeof(csv_buffer));
printf("CSV: %s\n", csv_buffer);
char json_buffer[256];
format_sensor_json(&sensor, json_buffer, sizeof(json_buffer));
printf("JSON: %s\n", json_buffer);
// 3. 配置文件解析(模拟)
printf("\n--- Configuration Parsing ---\n");
int baudrate = 0, timeout = 0;
// 注意:实际使用时需要真实的配置文件
// parse_config("config.txt", &baudrate, &timeout);
printf("Default baudrate: %d, timeout: %d\n", 9600, 30);
// 4. 寄存器转储格式化
printf("\n--- Register Dump ---\n");
uint32_t regs[] = {0x00000001, 0xDEADBEEF, 0x12345678, 0xFFFFFFFF};
format_register_dump(regs, sizeof(regs) / sizeof(regs[0]));
// 5. 串口输出格式化(模拟)
printf("\n--- UART Output Simulation ---\n");
char uart_msg[64];
snprintf(uart_msg, sizeof(uart_msg), "ACK:CMD%04X:LEN%02X", 0x1234, 16);
printf("UART TX: %s\n", uart_msg);
return 0;
}
编译命令:
gcc -o embedded_io_example embedded_io_example.c -std=c99
./embedded_io_example
格式说明符与参数类型不匹配
int x = 42;
printf("%f", x); // 错误!undefined behavior
printf("%d", (double)x); // 错误!
缓冲区刷新
printf("Hello"); // 可能不会立即显示
fflush(stdout); // 强制刷新
// 或使用换行符
printf("Hello\n");
线程安全
必须使用取地址符
int x;
scanf("%d", x); // 错误!
scanf("%d", &x); // 正确
缓冲区溢出风险
char str[10];
scanf("%s", str); // 危险!输入可能超过9个字符
scanf("%9s", str); // 安全:限制读取长度
scanf返回值
int result = scanf("%d", &x);
if (result == EOF) {
// 输入结束或错误
} else if (result == 0) {
// 输入不匹配
} else {
// 成功读取
}
sprintf缓冲区溢出
char buffer[10];
sprintf(buffer, "Value: %d", 123456789); // 溢出!
snprintf返回值
int result = snprintf(buffer, size, "...");
if (result >= size) {
// 输出被截断
}
字符数组必须以null结尾
char buffer[5];
snprintf(buffer, 5, "Hello"); // 结果:Hell\0
// buffer[4]是null字符
检查文件指针
FILE *fp = fopen("file.txt", "r");
if (fp == NULL) {
perror("Error opening file");
return 1;
}
及时关闭文件
// 使用完文件后立即关闭
fclose(fp);
二进制模式 vs 文本模式
fopen("file.bin", "rb"); // 二进制读取
fopen("file.txt", "w"); // 文本写入
// 错误1:printf格式说明符不匹配
printf("%d", 3.14); // undefined behavior
printf("%f", 42); // undefined behavior
// 错误2:scanf缺少取地址符
int x;
scanf("%d", x); // undefined behavior
// 错误3:sprintf缓冲区溢出
char buffer[10];
sprintf(buffer, "Very long string that exceeds buffer"); // 溢出
// 错误4:未检查返回值
FILE *fp = fopen("nonexistent.txt", "r");
fscanf(fp, "%d", &x); // fp可能为NULL
// 错误5:未关闭文件
void func() {
FILE *fp = fopen("temp.txt", "w");
fprintf(fp, "data");
// 忘记fclose(fp) - 内存泄漏
}
答:
printf:输出到标准输出(stdout)sprintf:输出到字符串缓冲区snprintf:带长度限制的sprintf,防止缓冲区溢出使用snprintf的场景:
答:
正确处理:
int result = scanf("%d", &x);
if (result == EOF) {
// 处理输入结束或错误
} else if (result == 0) {
// 输入不匹配,清除无效输入
while (getchar() != '\n');
} else {
// 成功读取
}
答:
答: 缓冲区残留问题:当使用scanf读取数字后,换行符会留在缓冲区中,影响后续的字符读取。
解决方案:
// 方案1:使用getchar()清除
scanf("%d", &num);
while (getchar() != '\n');
// 方案2:使用scanf跳过空白字符
scanf("%d ", &num); // 注意格式字符串中的空格
// 方案3:使用fgets读取整行
fgets(buffer, sizeof(buffer), stdin);
sscanf(buffer, "%d", &num);
答:
printf:输出到标准输出(通常是串口终端)fprintf:输出到指定文件流嵌入式应用:
答:
int value = 255;
// 十六进制
printf("Hex: %x\n", value); // ff
printf("Hex: %#x\n", value); // 0xff
printf("Hex: %#X\n", value); // 0XFF
printf("Hex: %08x\n", value); // 000000ff
// 八进制
printf("Octal: %o\n", value); // 377
printf("Octal: %#o\n", value); // 0377
// 二进制(需要自定义函数)
for (int i = 31; i >= 0; i--) {
printf("%d", (value >> i) & 1);
}
答: printf不是线程安全的,多个线程同时调用printf可能导致输出混乱。
解决方案:
使用互斥锁:
pthread_mutex_t print_mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&print_mutex);
printf("Thread-safe message\n");
pthread_mutex_unlock(&print_mutex);
使用线程安全函数:
// POSIX线程安全版本
flockfile(stdout);
printf("Thread-safe message\n");
funlockfile(stdout);
使用日志库:许多日志库提供线程安全的功能