C Typedef
C - typedef
Section titled “C - typedef”C 语言中的 typedef 关键字提供了一种为现有数据类型创建**别名(alias)**或同义词的方法。它不会创建新类型,而是为已定义的类型提供另一个名称。
这对于以下方面特别有用:
- 提高可读性: 为复杂类型使用有意义的名称。
- 简化声明: 使复杂类型(如结构体指针或函数指针)的声明更容易编写和理解。
- 可移植性: 在一个地方定义可以针对不同平台进行调整的自定义类型(如
BYTE或INT32)。
基本语法遵循变量声明的模式,但在前面加上 typedef 关键字:
typedef existing_type new_type_name;existing_type 是您想要创建别名的数据类型(例如 unsigned char、int *、struct ComplexNumber)。new_type_name 是您要定义的别名(同义词)。
1. 基本类型的别名:
Section titled “1. 基本类型的别名:”#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),但这并不是严格的规则。
2. 结构体类型的别名:
Section titled “2. 结构体类型的别名:”通过避免重复编写 struct TagName 来简化结构体变量的声明。
// 原始结构体定义struct Point2D { double x; double y;};
// 结构体的 typedeftypedef 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;}3. 指针类型的别名:
Section titled “3. 指针类型的别名:”可以使指针声明更简洁。
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 vs. #define
Section titled “typedef vs. #define”虽然两者都可以创建别名,但它们的工作方式不同:
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 语言的类型系统更好地集成。
C - 输入与输出
Section titled “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.outEnter a character: HYou entered: H行输入/输出:fgets() 与 puts()
Section titled “行输入/输出:fgets() 与 puts()”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.outEnter 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.outEnter your name, age, and height (e.g., Alice 30 1.65): Bob 25 1.8
--- User Info ---Name : BobAge : 25 yearsHeight : 1.80 metersscanf 的陷阱:
- 缓冲区溢出(Buffer Overflows): 使用没有宽度限制的
%s(如%49s)是危险的。 - 输入格式匹配: 输入必须与格式字符串完全匹配(包括空白字符)。
- 空白字符处理:
%s会跳过开头的空白字符,并在下一个空白字符处停止。%c会读取下一个字符,包括空白字符。 - 换行符:
scanf通常会将按 Enter 键产生的换行符(\n)留在输入缓冲区中,这可能会干扰后续的fgets或getchar调用。 - 错误检查: 始终检查
scanf的返回值,以确保成功读取了预期的项数。
由于这些问题,fgets 后跟解析函数(如 sscanf 或 strtol)通常是处理用户输入更健壮的方法。