Skip to content

C Typedef

C 语言中的 typedef 关键字提供了一种为现有数据类型创建**别名(alias)**或同义词的方法。它不会创建新类型,而是为已定义的类型提供另一个名称。

这对于以下方面特别有用:

  • 提高可读性: 为复杂类型使用有意义的名称。
  • 简化声明: 使复杂类型(如结构体指针或函数指针)的声明更容易编写和理解。
  • 可移植性: 在一个地方定义可以针对不同平台进行调整的自定义类型(如 BYTE 或 INT32)。

基本语法遵循变量声明的模式,但在前面加上 typedef 关键字:

typedef existing_type new_type_name;

existing_type 是您想要创建别名的数据类型(例如 unsigned char、int *、struct ComplexNumber)。new_type_name 是您要定义的别名(同义词)。

#include <stdint.h> // 用于 int32_t
// 为基本类型或标准类型创建别名
typedef unsigned char BYTE;
typedef int32_t INTEGER32;
typedef char* STRING; // char 指针的别名
int main(void) {
BYTE status_flag = 0x01; // status_flag 实际上是 unsigned char
INTEGER32 counter = -50000; // counter 实际上是 int32_t
STRING message = "Hello typedef!"; // message 实际上是 char*
printf("Status: %u\n", status_flag);
printf("Counter: %d\n", counter);
printf("Message: %s\n", message);
return 0;
}

按照惯例,typedef 名称通常使用大写(例如 BYTE),但这并不是严格的规则。

通过避免重复编写 struct TagName 来简化结构体变量的声明。

// 原始结构体定义
struct Point2D {
double x;
double y;
};
// 结构体的 typedef
typedef struct Point2D Coordinate;
// 或者组合定义和 typedef:
typedef struct Vector3D {
double x, y, z;
} Vector; // Vector 现在是 struct Vector3D 的别名
int main(void) {
struct Point2D p1; // 使用原始结构体名
Coordinate p2; // 使用 typedef 别名 - 更简洁!
Vector v1; // 使用组合的 typedef 别名
p2.x = 1.0;
p2.y = 2.0;
v1 = (Vector){3.0, 4.0, 5.0}; // 使用 typedef 的复合字面量
printf("Point p2: (%.1f, %.1f)\n", p2.x, p2.y);
printf("Vector v1: (%.1f, %.1f, %.1f)\n", v1.x, v1.y, v1.z);
return 0;
}

可以使指针声明更简洁。

typedef int* IntPtr; // IntPtr 是 'int *' 的别名
typedef void (*CallbackFunc)(int); // CallbackFunc 是指针的别名
// 指向一个接受 int 参数并返回 void 的函数
void my_callback(int id) {
printf("Callback called with ID: %d\n", id);
}
int main(void) {
int value = 10;
IntPtr ptr1 = &value; // ptr1 是 int*
IntPtr ptr2 = NULL; // ptr2 是 int*
CallbackFunc func_ptr = my_callback; // 赋值函数地址
*ptr1 = 20;
printf("Value via ptr1: %d\n", *ptr1);
if (func_ptr != NULL) {
func_ptr(123); // 通过指针别名调用函数
}
return 0;
}

虽然两者都可以创建别名,但它们的工作方式不同:

  • typedef: 由编译器处理。它理解 C 语法并创建真正的类型同义词。它尊重作用域(scope)。
  • #define: 由预处理器(preprocessor)处理(在编译之前)。它执行简单的文本替换,不了解 C 类型或作用域。可能导致意外的行为,尤其是在处理指针或复杂类型时。

指针的示例区别:

#define StringMacro char*
typedef char* StringTypedef;
int main(void) {
StringMacro p1, p2; // 预处理器展开为:char* p1, p2;
// 这里,p1 是 char*,但 p2 只是 char!
StringTypedef pt1, pt2; // Typedef 同时应用于两者:
// pt1 是 char*,pt2 也是 char*
return 0;
}

最佳实践: 使用 typedef 创建类型别名,因为它更安全,并且与 C 语言的类型系统更好地集成。

输入(Input) 指的是将数据输入到程序中,通常来自键盘或文件。输出(Output) 指的是从程序中显示数据,通常到屏幕或文件。

C 语言提供了标准库函数,主要在 <stdio.h>(标准输入/输出)中声明,用于处理这些操作。

当 C 程序启动时,会自动打开三个标准的通信通道(流):

流名称文件指针默认设备
标准输入stdin键盘(默认)
标准输出stdout屏幕/控制台(默认)
标准错误stderr屏幕/控制台(默认)

stdin、stdout 和 stderr 是预定义的 FILE* 指针(文件指针),允许访问这些流。标准输出通常是缓冲的(buffered)(输出可能不会立即显示),而标准错误通常是非缓冲的(unbuffered)(错误消息会及时显示)。

字符输入/输出:getchar() 与 putchar()

Section titled “字符输入/输出:getchar() 与 putchar()”
  • int getchar(void): 从 stdin 读取下一个可用字符,并将其作为 int 返回。在错误或输入结束时返回 EOF(表示文件结束的特殊整数常量)。
  • int putchar(int c): 将字符 c(作为 int 传递)写入 stdout,并返回写入的字符,错误时返回 EOF。

这些函数一次处理一个字符。

示例:

#include <stdio.h>
int main(void) {
int input_char;
printf("Enter a character: ");
input_char = getchar(); // 读取一个字符
if (input_char == EOF) {
printf("\nError or end of input detected.\n"); // 检测到错误或输入结束。
} else {
printf("\nYou entered: ");
putchar(input_char); // 打印字符
putchar('\n');
}
// 注意:如果您按 Enter 键,getchar() 通常也会读取换行符('\n')。
// 您可能需要额外的 getchar() 调用来消费剩余的输入。
return 0;
}

执行:

$ ./a.out
Enter a character: H
You entered: H
  • char *fgets(char *str, int size, FILE *stream): 从指定的 stream(例如 stdin)读取一行文本到缓冲区 str 中。当读取了 size - 1 个字符、遇到换行符(\n)或发生 EOF 时停止读取。关键是,如果换行符能容纳,fgets 会将其包含在缓冲区中,并且它总是会对缓冲区进行空字符终止(null-terminates)(如果 size > 0)。成功时返回 str,在读取任何字符之前发生错误或 EOF 时返回 NULL。这是读取字符串的推荐安全方法。
  • int puts(const char *str): 将字符串 str 写入 stdout,并在其后添加一个换行符(\n)。成功时返回非负值,错误时返回 EOF。

警告: 旧的 gets(char *s) 函数极其危险,因为它不检查缓冲区大小,很容易导致缓冲区溢出(buffer overflows)。它已从最新的 C 标准中移除。永远不要使用 gets()。总是使用 fgets()。

使用 fgets 的示例:

#include <stdio.h>
#include <string.h> // 用于 strcspn
#define BUFFER_SIZE 100
int main(void) {
char input_line[BUFFER_SIZE];
printf("Enter a line of text: ");
if (fgets(input_line, sizeof(input_line), stdin) != NULL) {
// 可选:如果存在尾部换行符则移除
input_line[strcspn(input_line, "\n")] = '\0';
printf("\nYou entered: ");
puts(input_line); // puts 会添加它自己的换行符
} else {
printf("\nError reading input or EOF reached.\n"); // 读取输入错误或到达文件末尾。
}
return 0;
}

执行:

$ ./a.out
Enter a line of text: This is a test line.
You entered: This is a test line.

格式化输入/输出:scanf() 与 printf()

Section titled “格式化输入/输出:scanf() 与 printf()”
  • int printf(const char *format, ...): 将格式化输出写入 stdout。format 字符串包含要打印的文本和格式说明符(format specifiers)(以 % 开头),用于指示如何转换和打印后续参数(例如,%d 用于 int,%f 用于 float/double,%c 用于 char,%s 用于 string,%p 用于 pointer)。返回打印的字符数,错误时返回负值。
  • int scanf(const char *format, ...): 从 stdin 读取格式化输入。format 字符串使用格式说明符指定预期的输入格式。后续参数必须是指向将存储读取数据的变量的指针(pointers)。返回成功匹配和赋值的输入项数量,在进行任何转换之前发生输入失败时返回 EOF。

常用格式说明符:

  • %d 或 %i:有符号整数
  • %u:无符号整数
  • %f:浮点数(printf 中用于 float/double,scanf 中用于 float*)
  • %lf:长浮点数(scanf 中用于 double*)
  • %Lf:长双精度浮点数(long double)
  • %c:单个字符
  • %s:字符串(scanf 中读取直到遇到空白字符)
  • %p:指针地址
  • %%:字面量 ’%’ 字符

示例:

#include <stdio.h>
int main(void) {
char name[50];
int age;
float height;
printf("Enter your name, age, and height (e.g., Alice 30 1.65): ");
// 在 scanf 的 %s 中使用宽度限制以防止缓冲区溢出!
int items_read = scanf("%49s %d %f", name, &age, &height);
if (items_read == 3) { // 检查是否成功读取了所有 3 个项
printf("\n--- User Info ---\n");
printf("Name : %s\n", name);
printf("Age : %d years\n", age);
printf("Height : %.2f meters\n", height); // 打印浮点数并保留 2 位小数
} else {
printf("\nError: Invalid input format.\n"); // 读取输入错误或到达文件末尾。
// 清除输入缓冲区中剩余的无效输入(简单示例)
while (getchar() != '\n');
}
return 0;
}

执行:

$ ./a.out
Enter your name, age, and height (e.g., Alice 30 1.65): Bob 25 1.8
--- User Info ---
Name : Bob
Age : 25 years
Height : 1.80 meters

scanf 的陷阱:

  • 缓冲区溢出(Buffer Overflows): 使用没有宽度限制的 %s(如 %49s)是危险的。
  • 输入格式匹配: 输入必须与格式字符串完全匹配(包括空白字符)。
  • 空白字符处理: %s 会跳过开头的空白字符,并在下一个空白字符处停止。%c 会读取下一个字符,包括空白字符。
  • 换行符: scanf 通常会将按 Enter 键产生的换行符(\n)留在输入缓冲区中,这可能会干扰后续的 fgets 或 getchar 调用。
  • 错误检查: 始终检查 scanf 的返回值,以确保成功读取了预期的项数。

由于这些问题,fgets 后跟解析函数(如 sscanf 或 strtol)通常是处理用户输入更健壮的方法。