目录
[1. 结构体与共用体](#1. 结构体与共用体)
[1.1 结构体的定义](#1.1 结构体的定义)
[1.1.1 结构体的基本概念](#1.1.1 结构体的基本概念)
[1.1.2 结构体变量的声明](#1.1.2 结构体变量的声明)
[1.2 结构体成员的访问](#1.2 结构体成员的访问)
[1.2.1 结构体变量成员的表示方法](#1.2.1 结构体变量成员的表示方法)
[1.2.2 结构体变量的赋值和初始化](#1.2.2 结构体变量的赋值和初始化)
[1.3 结构体数组](#1.3 结构体数组)
[1.3.1 结构体数组的定义](#1.3.1 结构体数组的定义)
[1.4 结构体指针](#1.4 结构体指针)
[1.4.1 指向结构体变量的指针](#1.4.1 指向结构体变量的指针)
[1.4.2 指向结构体数组的指针](#1.4.2 指向结构体数组的指针)
[1.4.3 结构体指针作为函数参数](#1.4.3 结构体指针作为函数参数)
[1.4.4 动态存储分配与链表](#1.4.4 动态存储分配与链表)
[1.5 共用体(联合体)](#1.5 共用体(联合体))
[1.5.1 共用体的基本概念](#1.5.1 共用体的基本概念)
[1.5.2 共用体的应用场景](#1.5.2 共用体的应用场景)
[1.6 枚举类型](#1.6 枚举类型)
[1.6.1 枚举类型的定义和使用](#1.6.1 枚举类型的定义和使用)
[1.6.2 枚举与结构体结合使用](#1.6.2 枚举与结构体结合使用)
[1.7 类型定义符 typedef](#1.7 类型定义符 typedef)
[2. 位运算](#2. 位运算)
[2.1 位运算符](#2.1 位运算符)
[2.1.1 按位与运算(&)](#2.1.1 按位与运算(&))
[2.1.2 按位或运算(|)](#2.1.2 按位或运算(|))
[2.1.3 按位异或运算(^)](#2.1.3 按位异或运算(^))
[2.1.4 按位取反运算(~)](#2.1.4 按位取反运算(~))
[2.1.5 左移运算(<<)](#2.1.5 左移运算(<<))
[2.1.6 右移运算(>>)](#2.1.6 右移运算(>>))
[2.2 位域(位段)](#2.2 位域(位段))
[2.2.1 位域的基本概念](#2.2.1 位域的基本概念)
[2.2.2 位域的注意事项](#2.2.2 位域的注意事项)
[2.3 本章小结](#2.3 本章小结)
摘要:
本文系统介绍了C语言中结构体、共用体和位运算的核心知识点。结构体部分详细讲解了定义方式、成员访问、数组与指针操作、动态内存分配及链表实现;共用体部分阐述了其共享内存特性及应用场景;位运算部分全面解析了6种位运算符的功能与典型应用,并介绍了位域的概念与注意事项。此外,还涵盖了枚举类型和typedef的用法。这些内容为C语言中复杂数据结构的构建、内存优化和底层操作提供了关键技术手段,是系统编程和嵌入式开发的重要基础。
1. 结构体与共用体
1.1 结构体的定义
1.1.1 结构体的基本概念
结构体(struct)是一种用户自定义的数据类型,用于将不同类型的数据组合成一个整体。
定义结构体的一般形式:
cpp
struct 结构体标签 {
数据类型 成员1;
数据类型 成员2;
// ... 更多成员
};
示例:
cpp
// 定义一个学生结构体
struct Student {
int id; // 学号
char name[50]; // 姓名
int age; // 年龄
float score; // 成绩
};
1.1.2 结构体变量的声明
方式1:先定义结构体类型,再声明变量
cpp
struct Student {
int id;
char name[50];
float score;
};
// 声明变量
struct Student stu1, stu2;
方式2:定义结构体类型的同时声明变量
cpp
struct Student {
int id;
char name[50];
float score;
} stu1, stu2; // 直接声明变量
方式3:使用匿名结构体
cpp
// 匿名结构体(没有标签)
struct {
int id;
char name[50];
float score;
} stu1, stu2; // 只能在这里声明变量
方式4:使用typedef创建别名
cpp
typedef struct Student {
int id;
char name[50];
float score;
} Student; // Student 是 struct Student 的别名
// 现在可以直接使用 Student 声明变量
Student stu1, stu2;
1.2 结构体成员的访问
1.2.1 结构体变量成员的表示方法
使用.运算符访问结构体成员。
示例:
cpp
#include <stdio.h>
#include <string.h>
struct Person {
char name[50];
int age;
float height;
};
int main() {
struct Person p1;
// 访问结构体成员并赋值
strcpy(p1.name, "张三");
p1.age = 25;
p1.height = 175.5;
// 读取结构体成员
printf("姓名: %s\n", p1.name);
printf("年龄: %d\n", p1.age);
printf("身高: %.1f cm\n", p1.height);
// 可以直接赋值
struct Person p2 = p1; // 结构体可以直接赋值(浅拷贝)
printf("\n复制后的姓名: %s\n", p2.name);
return 0;
}
1.2.2 结构体变量的赋值和初始化
初始化方式:
cpp
#include <stdio.h>
struct Date {
int year;
int month;
int day;
};
struct Book {
char title[100];
char author[50];
float price;
struct Date publish_date; // 结构体嵌套
};
int main() {
// 方式1:按顺序初始化所有成员
struct Date d1 = {2023, 10, 15};
// 方式2:按顺序初始化部分成员(剩余成员自动初始化为0)
struct Date d2 = {2023}; // month=0, day=0
// 方式3:指定成员初始化(C99标准)
struct Date d3 = {
.year = 2023,
.month = 10,
.day = 15
};
// 方式4:结构体嵌套初始化
struct Book b1 = {
"C语言程序设计",
"李四",
45.5,
{2022, 8, 20} // 嵌套结构体初始化
};
// 方式5:指定成员的嵌套初始化
struct Book b2 = {
.title = "数据结构",
.author = "王五",
.price = 38.0,
.publish_date = {
.year = 2021,
.month = 5,
.day = 10
}
};
// 输出验证
printf("书籍信息:\n");
printf("书名: %s\n", b1.title);
printf("作者: %s\n", b1.author);
printf("价格: %.2f\n", b1.price);
printf("出版日期: %d年%d月%d日\n",
b1.publish_date.year,
b1.publish_date.month,
b1.publish_date.day);
return 0;
}
1.3 结构体数组
1.3.1 结构体数组的定义
结构体数组是每个元素都是结构体类型的数组。
定义方式:
cpp
struct Student {
int id;
char name[50];
float score;
};
// 定义结构体数组
struct Student class[3]; // 包含3个学生的数组
示例:
cpp
#include <stdio.h>
#include <string.h>
struct Employee {
int id;
char name[50];
char department[30];
float salary;
};
int main() {
// 定义并初始化结构体数组
struct Employee employees[5] = {
{1001, "张三", "技术部", 8000.0},
{1002, "李四", "销售部", 7000.0},
{1003, "王五", "市场部", 7500.0},
{1004, "赵六", "技术部", 8500.0},
{1005, "孙七", "人事部", 6500.0}
};
int count = 5;
// 遍历结构体数组
printf("员工信息表:\n");
printf("ID\t姓名\t部门\t\t薪资\n");
printf("--------------------------------------\n");
for (int i = 0; i < count; i++) {
printf("%d\t%s\t%s\t%.2f\n",
employees[i].id,
employees[i].name,
employees[i].department,
employees[i].salary);
}
// 计算平均工资
float total_salary = 0;
for (int i = 0; i < count; i++) {
total_salary += employees[i].salary;
}
printf("\n平均工资: %.2f\n", total_salary / count);
// 查找最高工资的员工
int max_index = 0;
for (int i = 1; i < count; i++) {
if (employees[i].salary > employees[max_index].salary) {
max_index = i;
}
}
printf("\n最高工资员工:\n");
printf("ID: %d, 姓名: %s, 部门: %s, 薪资: %.2f\n",
employees[max_index].id,
employees[max_index].name,
employees[max_index].department,
employees[max_index].salary);
return 0;
}
1.4 结构体指针
1.4.1 指向结构体变量的指针
使用指针访问结构体成员有两种方式:
-
使用
->运算符 -
使用
*解引用然后使用.运算符
示例:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
char title[100];
char author[50];
int pages;
float price;
} Book;
int main() {
// 创建结构体变量
Book book1;
strcpy(book1.title, "C Programming");
strcpy(book1.author, "K&R");
book1.pages = 274;
book1.price = 35.5;
// 创建指向结构体的指针
Book *ptr = &book1;
printf("访问结构体成员的不同方式:\n\n");
// 方式1:直接访问
printf("直接访问:\n");
printf("书名: %s\n", book1.title);
printf("作者: %s\n", book1.author);
printf("页数: %d\n", book1.pages);
printf("价格: %.2f\n\n", book1.price);
// 方式2:使用指针和 -> 运算符
printf("使用指针和 -> 运算符:\n");
printf("书名: %s\n", ptr->title);
printf("作者: %s\n", ptr->author);
printf("页数: %d\n", ptr->pages);
printf("价格: %.2f\n\n", ptr->price);
// 方式3:使用指针和 * 运算符
printf("使用指针和 * 运算符:\n");
printf("书名: %s\n", (*ptr).title);
printf("作者: %s\n", (*ptr).author);
printf("页数: %d\n", (*ptr).pages);
printf("价格: %.2f\n\n", (*ptr).price);
// 通过指针修改结构体成员
ptr->price = 38.0; // 修改价格
strcpy(ptr->title, "C Programming Language");
printf("修改后的信息:\n");
printf("书名: %s\n", book1.title);
printf("价格: %.2f\n", book1.price);
return 0;
}
1.4.2 指向结构体数组的指针
示例:
cpp
#include <stdio.h>
typedef struct {
int id;
char name[20];
int score;
} Student;
int main() {
// 定义并初始化结构体数组
Student students[] = {
{1001, "Alice", 85},
{1002, "Bob", 92},
{1003, "Charlie", 78},
{1004, "David", 95},
{1005, "Eve", 88}
};
int count = sizeof(students) / sizeof(students[0]);
// 创建指向结构体数组的指针
Student *ptr = students; // 指向第一个元素
printf("使用指针遍历结构体数组:\n");
printf("ID\t姓名\t成绩\n");
printf("--------------------\n");
// 方法1:使用指针遍历
for (int i = 0; i < count; i++) {
printf("%d\t%s\t%d\n",
ptr->id, // 等价于 (*ptr).id
ptr->name, // 等价于 (*ptr).name
ptr->score); // 等价于 (*ptr).score
ptr++; // 移动到下一个结构体
}
printf("\n计算平均成绩:\n");
// 重置指针
ptr = students;
// 方法2:使用指针运算
float total = 0;
Student *end_ptr = students + count; // 指向数组末尾
while (ptr < end_ptr) {
total += ptr->score;
ptr++;
}
printf("平均成绩: %.2f\n", total / count);
return 0;
}
1.4.3 结构体指针作为函数参数
传递结构体指针比传递整个结构体更高效。
示例:
cpp
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
typedef struct {
char name[50];
int age;
float salary;
} Employee;
// 函数1:通过值传递结构体(会复制整个结构体)
void print_employee_by_value(Employee emp) {
printf("值传递方式:\n");
printf("姓名: %s\n", emp.name);
printf("年龄: %d\n", emp.age);
printf("薪资: %.2f\n\n", emp.salary);
// 修改不会影响原结构体
emp.salary = 9999.99; // 不影响原数据
}
// 函数2:通过指针传递结构体(只传递地址)
void print_employee_by_pointer(const Employee *emp) {
printf("指针传递方式:\n");
printf("姓名: %s\n", emp->name);
printf("年龄: %d\n", emp->age);
printf("薪资: %.2f\n\n", emp->salary);
}
// 函数3:通过指针修改结构体
void increase_salary(Employee *emp, float percentage) {
emp->salary += emp->salary * percentage / 100;
printf("给 %s 涨薪 %.1f%% 后,新薪资: %.2f\n",
emp->name, percentage, emp->salary);
}
// 函数4:动态创建结构体并返回指针
Employee* create_employee(const char *name, int age, float salary) {
Employee *new_emp = (Employee*)malloc(sizeof(Employee));
if (new_emp == NULL) {
printf("内存分配失败\n");
return NULL;
}
strcpy(new_emp->name, name);
new_emp->age = age;
new_emp->salary = salary;
return new_emp;
}
// 函数5:比较两个员工薪资
int compare_salary(const Employee *emp1, const Employee *emp2) {
if (emp1->salary > emp2->salary) return 1;
if (emp1->salary < emp2->salary) return -1;
return 0;
}
int main() {
// 创建员工结构体
Employee emp1 = {"张三", 30, 8000.0};
printf("=== 结构体指针作为函数参数示例 ===\n\n");
// 测试值传递
print_employee_by_value(emp1);
printf("原薪资未改变: %.2f\n\n", emp1.salary);
// 测试指针传递
print_employee_by_pointer(&emp1);
// 测试修改函数
increase_salary(&emp1, 10.0);
printf("修改后原薪资: %.2f\n\n", emp1.salary);
// 测试动态创建
Employee *emp2 = create_employee("李四", 28, 7500.0);
if (emp2 != NULL) {
printf("动态创建的员工:\n");
print_employee_by_pointer(emp2);
// 测试比较函数
int result = compare_salary(&emp1, emp2);
if (result > 0) {
printf("%s 的薪资比 %s 高\n", emp1.name, emp2->name);
} else if (result < 0) {
printf("%s 的薪资比 %s 低\n", emp1.name, emp2->name);
} else {
printf("%s 和 %s 薪资相同\n", emp1.name, emp2->name);
}
// 释放动态分配的内存
free(emp2);
}
// 结构体指针数组示例
printf("\n=== 结构体指针数组示例 ===\n");
Employee employees[] = {
{"王五", 35, 9000.0},
{"赵六", 32, 8500.0},
{"孙七", 29, 7800.0}
};
int count = sizeof(employees) / sizeof(employees[0]);
// 创建结构体指针数组
Employee *emp_ptrs[3];
for (int i = 0; i < count; i++) {
emp_ptrs[i] = &employees[i];
}
// 使用指针数组排序(按薪资降序)
for (int i = 0; i < count - 1; i++) {
for (int j = 0; j < count - i - 1; j++) {
if (emp_ptrs[j]->salary < emp_ptrs[j+1]->salary) {
// 交换指针
Employee *temp = emp_ptrs[j];
emp_ptrs[j] = emp_ptrs[j+1];
emp_ptrs[j+1] = temp;
}
}
}
printf("按薪资降序排列:\n");
printf("姓名\t年龄\t薪资\n");
printf("----------------------\n");
for (int i = 0; i < count; i++) {
printf("%s\t%d\t%.2f\n",
emp_ptrs[i]->name,
emp_ptrs[i]->age,
emp_ptrs[i]->salary);
}
return 0;
}
1.4.4 动态存储分配与链表
示例:单向链表的实现:
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义链表节点结构体
typedef struct Node {
int id;
char data[50];
struct Node *next; // 指向下一个节点的指针
} ListNode;
// 函数声明
ListNode* create_node(int id, const char *data);
void insert_at_begin(ListNode **head, int id, const char *data);
void insert_at_end(ListNode **head, int id, const char *data);
void delete_node(ListNode **head, int id);
void print_list(ListNode *head);
ListNode* search_node(ListNode *head, int id);
void free_list(ListNode **head);
int main() {
ListNode *head = NULL; // 链表头指针
printf("=== 单向链表操作示例 ===\n\n");
// 插入节点
insert_at_end(&head, 1, "第一项");
insert_at_end(&head, 2, "第二项");
insert_at_begin(&head, 3, "第三项(在开头)");
insert_at_end(&head, 4, "第四项");
// 打印链表
printf("当前链表内容:\n");
print_list(head);
// 搜索节点
printf("\n搜索节点:\n");
ListNode *found = search_node(head, 2);
if (found) {
printf("找到节点: ID=%d, Data=%s\n", found->id, found->data);
}
// 删除节点
printf("\n删除ID=2的节点后:\n");
delete_node(&head, 2);
print_list(head);
// 释放链表内存
free_list(&head);
return 0;
}
// 创建新节点
ListNode* create_node(int id, const char *data) {
ListNode *new_node = (ListNode*)malloc(sizeof(ListNode));
if (new_node == NULL) {
printf("内存分配失败\n");
return NULL;
}
new_node->id = id;
strcpy(new_node->data, data);
new_node->next = NULL;
return new_node;
}
// 在链表开头插入节点
void insert_at_begin(ListNode **head, int id, const char *data) {
ListNode *new_node = create_node(id, data);
if (new_node == NULL) return;
new_node->next = *head;
*head = new_node;
}
// 在链表末尾插入节点
void insert_at_end(ListNode **head, int id, const char *data) {
ListNode *new_node = create_node(id, data);
if (new_node == NULL) return;
// 如果链表为空
if (*head == NULL) {
*head = new_node;
return;
}
// 找到最后一个节点
ListNode *current = *head;
while (current->next != NULL) {
current = current->next;
}
current->next = new_node;
}
// 删除指定ID的节点
void delete_node(ListNode **head, int id) {
if (*head == NULL) {
printf("链表为空\n");
return;
}
// 如果要删除的是头节点
if ((*head)->id == id) {
ListNode *temp = *head;
*head = (*head)->next;
free(temp);
return;
}
// 查找要删除的节点
ListNode *current = *head;
ListNode *prev = NULL;
while (current != NULL && current->id != id) {
prev = current;
current = current->next;
}
if (current == NULL) {
printf("未找到ID=%d的节点\n", id);
return;
}
// 删除节点
prev->next = current->next;
free(current);
}
// 打印链表
void print_list(ListNode *head) {
if (head == NULL) {
printf("链表为空\n");
return;
}
ListNode *current = head;
while (current != NULL) {
printf("ID: %d, Data: %s\n", current->id, current->data);
current = current->next;
}
}
// 搜索节点
ListNode* search_node(ListNode *head, int id) {
ListNode *current = head;
while (current != NULL) {
if (current->id == id) {
return current;
}
current = current->next;
}
return NULL; // 未找到
}
// 释放链表内存
void free_list(ListNode **head) {
ListNode *current = *head;
ListNode *next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
*head = NULL; // 避免野指针
printf("\n链表内存已释放\n");
}
1.5 共用体(联合体)
1.5.1 共用体的基本概念
共用体(union)允许在相同的内存位置存储不同的数据类型,但任何时候只能存储其中一种类型。
定义共用体:
cpp
union Data {
int i;
float f;
char str[20];
};
共用体与结构体的区别:
-
结构体:每个成员有自己的内存空间
-
共用体:所有成员共享同一块内存空间
示例:
cpp
#include <stdio.h>
#include <string.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
printf("共用体大小: %zu 字节\n", sizeof(data));
printf("int 大小: %zu 字节\n", sizeof(int));
printf("float 大小: %zu 字节\n", sizeof(float));
printf("char[20] 大小: %zu 字节\n\n", sizeof(char[20]));
// 使用 int 成员
data.i = 10;
printf("data.i = %d\n", data.i);
// 使用 float 成员(会覆盖 int)
data.f = 220.5;
printf("data.f = %.2f\n", data.f);
printf("data.i 现在 = %d(无意义)\n\n", data.i); // 被覆盖了
// 使用 char 数组成员
strcpy(data.str, "Hello");
printf("data.str = %s\n", data.str);
printf("data.f 现在 = %.2f(无意义)\n", data.f); // 被覆盖了
// 同时只能正确访问最后赋值的成员
printf("\n正确使用方式:\n");
union Data d1, d2, d3;
d1.i = 100;
printf("d1.i = %d\n", d1.i);
d2.f = 3.14;
printf("d2.f = %.2f\n", d2.f);
strcpy(d3.str, "Test");
printf("d3.str = %s\n", d3.str);
return 0;
}
1.5.2 共用体的应用场景
示例1:节省内存空间
cpp
#include <stdio.h>
// 产品类型枚举
typedef enum {
BOOK,
ELECTRONIC,
CLOTHING
} ProductType;
// 产品信息共用体
union ProductInfo {
struct {
int pages;
char author[50];
} book;
struct {
float weight;
char model[50];
} electronic;
struct {
char size[10];
char material[30];
} clothing;
};
// 完整产品结构体
typedef struct {
int id;
char name[100];
float price;
ProductType type;
union ProductInfo info; // 共用体,根据类型存储不同信息
} Product;
void print_product(const Product *p) {
printf("产品ID: %d\n", p->id);
printf("产品名称: %s\n", p->name);
printf("价格: %.2f\n", p->price);
printf("类型: ");
switch (p->type) {
case BOOK:
printf("书籍\n");
printf("页数: %d\n", p->info.book.pages);
printf("作者: %s\n", p->info.book.author);
break;
case ELECTRONIC:
printf("电子产品\n");
printf("重量: %.2f kg\n", p->info.electronic.weight);
printf("型号: %s\n", p->info.electronic.model);
break;
case CLOTHING:
printf("服装\n");
printf("尺码: %s\n", p->info.clothing.size);
printf("材质: %s\n", p->info.clothing.material);
break;
}
printf("---\n");
}
int main() {
Product p1 = {
.id = 1,
.name = "C语言程序设计",
.price = 45.5,
.type = BOOK,
.info.book = {
.pages = 320,
.author = "谭浩强"
}
};
Product p2 = {
.id = 2,
.name = "智能手机",
.price = 2999.0,
.type = ELECTRONIC,
.info.electronic = {
.weight = 0.2,
.model = "XYZ-2023"
}
};
Product p3 = {
.id = 3,
.name = "T恤衫",
.price = 89.9,
.type = CLOTHING,
.info.clothing = {
.size = "L",
.material = "纯棉"
}
};
printf("产品信息:\n\n");
print_product(&p1);
print_product(&p2);
print_product(&p3);
// 显示内存节省效果
printf("\n内存占用分析:\n");
printf("Product 结构体大小: %zu 字节\n", sizeof(Product));
printf("共用体 ProductInfo 大小: %zu 字节\n", sizeof(union ProductInfo));
// 如果不使用共用体,结构体会更大
struct WithoutUnion {
int id;
char name[100];
float price;
ProductType type;
// 所有信息都存储
int book_pages;
char book_author[50];
float electronic_weight;
char electronic_model[50];
char clothing_size[10];
char clothing_material[30];
};
printf("不使用共用体的大小: %zu 字节\n", sizeof(struct WithoutUnion));
printf("节省了 %zu 字节\n",
sizeof(struct WithoutUnion) - sizeof(Product));
return 0;
}
示例2:类型转换和位操作
cpp
#include <stdio.h>
// 将浮点数按位解释为整数
union FloatInt {
float f;
int i;
};
// 将32位整数分解为4个字节
union IntBytes {
int value;
unsigned char bytes[4];
};
// 检查系统字节序(大端/小端)
void check_endianness() {
union {
int i;
char c[4];
} test = {0x12345678};
printf("测试字节序:\n");
printf("整数值: 0x%x\n", test.i);
printf("字节内容: ");
for (int i = 0; i < 4; i++) {
printf("[%d]: 0x%02x ", i, (unsigned char)test.c[i]);
}
printf("\n");
if (test.c[0] == 0x78) {
printf("这是小端字节序(低位在前)\n");
} else {
printf("这是大端字节序(高位在前)\n");
}
}
int main() {
// 示例1:浮点数到整数的按位转换
union FloatInt fi;
fi.f = 3.14159;
printf("浮点数: %f\n", fi.f);
printf("按整数解释: 0x%x\n", fi.i);
printf("二进制: ");
// 打印二进制表示
for (int i = 31; i >= 0; i--) {
printf("%d", (fi.i >> i) & 1);
if (i % 8 == 0) printf(" ");
}
printf("\n\n");
// 示例2:分解整数为字节
union IntBytes ib;
ib.value = 0x12345678;
printf("整数: 0x%x\n", ib.value);
printf("分解为字节: ");
for (int i = 0; i < 4; i++) {
printf("[%d]: 0x%02x ", i, ib.bytes[i]);
}
printf("\n\n");
// 示例3:字节序检查
check_endianness();
return 0;
}
1.6 枚举类型
1.6.1 枚举类型的定义和使用
定义枚举:
cpp
enum 枚举标签 {
标识符1,
标识符2,
// ...
};
示例:
cpp
#include <stdio.h>
// 定义枚举类型
enum Weekday {
MONDAY, // 0
TUESDAY, // 1
WEDNESDAY, // 2
THURSDAY, // 3
FRIDAY, // 4
SATURDAY, // 5
SUNDAY // 6
};
// 可以指定枚举值
enum Color {
RED = 1, // 1
GREEN, // 2
BLUE = 5, // 5
YELLOW, // 6
WHITE = 10 // 10
};
// 使用typedef创建别名
typedef enum {
JANUARY = 1,
FEBRUARY,
MARCH,
APRIL,
MAY,
JUNE,
JULY,
AUGUST,
SEPTEMBER,
OCTOBER,
NOVEMBER,
DECEMBER
} Month;
int main() {
// 声明枚举变量
enum Weekday today = WEDNESDAY;
enum Color my_color = BLUE;
Month current_month = OCTOBER;
printf("枚举类型示例:\n\n");
// 使用枚举
printf("今天是星期几?\n");
switch (today) {
case MONDAY:
printf("星期一\n");
break;
case TUESDAY:
printf("星期二\n");
break;
case WEDNESDAY:
printf("星期三\n");
break;
case THURSDAY:
printf("星期四\n");
break;
case FRIDAY:
printf("星期五\n");
break;
case SATURDAY:
printf("星期六\n");
break;
case SUNDAY:
printf("星期日\n");
break;
}
printf("\n颜色值: %d\n", my_color);
printf("当前月份: %d月\n", current_month);
// 枚举值实际上是整数
printf("\n枚举值的整数值:\n");
printf("MONDAY = %d\n", MONDAY);
printf("TUESDAY = %d\n", TUESDAY);
printf("WEDNESDAY = %d\n", WEDNESDAY);
printf("\n颜色枚举值:\n");
printf("RED = %d\n", RED);
printf("GREEN = %d\n", GREEN);
printf("BLUE = %d\n", BLUE);
printf("YELLOW = %d\n", YELLOW);
printf("WHITE = %d\n", WHITE);
// 枚举可以进行整数运算
enum Weekday tomorrow = (enum Weekday)(today + 1);
printf("\n明天是星期: %d\n", tomorrow);
// 遍历枚举
printf("\n所有月份:\n");
for (Month m = JANUARY; m <= DECEMBER; m++) {
printf("月份 %d\n", m);
}
return 0;
}
1.6.2 枚举与结构体结合使用
示例:
cpp
#include <stdio.h>
#include <string.h>
// 枚举类型
typedef enum {
MALE,
FEMALE,
OTHER
} Gender;
typedef enum {
FRESHMAN,
SOPHOMORE,
JUNIOR,
SENIOR,
GRADUATE
} GradeLevel;
// 结构体类型
typedef struct {
int id;
char name[50];
int age;
Gender gender;
GradeLevel grade;
float gpa;
} Student;
// 打印性别字符串
const char* gender_to_string(Gender g) {
switch (g) {
case MALE: return "男";
case FEMALE: return "女";
case OTHER: return "其他";
default: return "未知";
}
}
// 打印年级字符串
const char* grade_to_string(GradeLevel g) {
switch (g) {
case FRESHMAN: return "大一";
case SOPHOMORE: return "大二";
case JUNIOR: return "大三";
case SENIOR: return "大四";
case GRADUATE: return "研究生";
default: return "未知";
}
}
// 打印学生信息
void print_student(const Student *s) {
printf("学号: %d\n", s->id);
printf("姓名: %s\n", s->name);
printf("年龄: %d\n", s->age);
printf("性别: %s\n", gender_to_string(s->gender));
printf("年级: %s\n", grade_to_string(s->grade));
printf("GPA: %.2f\n", s->gpa);
printf("---\n");
}
int main() {
// 创建学生数组
Student students[] = {
{1001, "张三", 20, MALE, SOPHOMORE, 3.5},
{1002, "李四", 21, FEMALE, JUNIOR, 3.8},
{1003, "王五", 22, MALE, SENIOR, 3.2},
{1004, "赵六", 19, FEMALE, FRESHMAN, 3.9},
{1005, "孙七", 24, MALE, GRADUATE, 3.7}
};
int count = sizeof(students) / sizeof(students[0]);
printf("学生信息列表:\n\n");
// 打印所有学生
for (int i = 0; i < count; i++) {
print_student(&students[i]);
}
// 统计各年级人数
printf("各年级人数统计:\n");
int grade_count[5] = {0}; // 对应5个年级
for (int i = 0; i < count; i++) {
grade_count[students[i].grade]++;
}
for (GradeLevel g = FRESHMAN; g <= GRADUATE; g++) {
printf("%s: %d人\n", grade_to_string(g), grade_count[g]);
}
// 查找GPA最高的学生
int max_index = 0;
for (int i = 1; i < count; i++) {
if (students[i].gpa > students[max_index].gpa) {
max_index = i;
}
}
printf("\nGPA最高的学生:\n");
print_student(&students[max_index]);
return 0;
}
1.7 类型定义符 typedef
typedef 用于为已有的数据类型创建别名,提高代码的可读性和可移植性。
基本用法:
cpp
typedef 已有类型 新类型名;
示例:
cpp
#include <stdio.h>
#include <string.h>
// 1. 为基本类型创建别名
typedef int INTEGER;
typedef float REAL;
typedef char CHAR;
// 2. 为数组类型创建别名
typedef int INT_ARRAY_10[10];
typedef char STRING[100];
// 3. 为指针类型创建别名
typedef int* INT_PTR;
typedef char* STR_PTR;
// 4. 为结构体创建别名
typedef struct {
int x;
int y;
} Point;
// 5. 为函数指针创建别名
typedef int (*COMPARE_FUNC)(int, int);
// 使用别名
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
int main() {
printf("typedef 示例:\n\n");
// 使用基本类型别名
INTEGER num1 = 10;
REAL num2 = 3.14;
CHAR ch = 'A';
printf("INTEGER: %d\n", num1);
printf("REAL: %.2f\n", num2);
printf("CHAR: %c\n\n", ch);
// 使用数组别名
INT_ARRAY_10 arr;
for (int i = 0; i < 10; i++) {
arr[i] = i * 2;
}
STRING name;
strcpy(name, "张三");
printf("姓名: %s\n\n", name);
// 使用指针别名
INT_PTR p1 = &num1;
STR_PTR p2 = name;
printf("p1 指向的值: %d\n", *p1);
printf("p2 指向的字符串: %s\n\n", p2);
// 使用结构体别名
Point p = {10, 20};
printf("点坐标: (%d, %d)\n\n", p.x, p.y);
// 使用函数指针别名
COMPARE_FUNC func_ptr;
func_ptr = add;
printf("10 + 5 = %d\n", func_ptr(10, 5));
func_ptr = subtract;
printf("10 - 5 = %d\n", func_ptr(10, 5));
// 复杂的typedef示例
typedef struct Node* NodePtr;
typedef NodePtr List;
struct Node {
int data;
NodePtr next;
};
// 创建链表
struct Node node1 = {10, NULL};
struct Node node2 = {20, NULL};
struct Node node3 = {30, NULL};
node1.next = &node2;
node2.next = &node3;
List head = &node1;
printf("\n链表遍历:\n");
NodePtr current = head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
return 0;
}
typedef 与 #define 的区别
cpp
#include <stdio.h>
// 使用 #define
#define INT_PTR_DEF int*
#define INT_PAIR_DEF struct {int a; int b;}
// 使用 typedef
typedef int* INT_PTR_TYP;
typedef struct {int a; int b;} INT_PAIR_TYP;
int main() {
printf("#define 与 typedef 的区别:\n\n");
// 示例1:指针声明的区别
INT_PTR_DEF p1, p2; // 展开为: int* p1, p2;
// p1 是指针,p2 是普通int
INT_PTR_TYP p3, p4; // 两个都是指针
// p3 和 p4 都是 int*
int a = 10, b = 20;
p1 = &a;
p2 = b; // p2 是 int,不是指针
p3 = &a;
p4 = &b; // p4 是指针
printf("p1=%p, *p1=%d\n", (void*)p1, *p1);
printf("p2=%d (不是指针)\n", p2);
printf("p3=%p, *p3=%d\n", (void*)p3, *p3);
printf("p4=%p, *p4=%d\n\n", (void*)p4, *p4);
// 示例2:结构体的区别
INT_PAIR_DEF pair1 = {1, 2}; // 每次使用都要写完整的 struct
INT_PAIR_DEF pair2 = {3, 4}; // 这是不同的类型!
INT_PAIR_TYP pair3 = {5, 6};
INT_PAIR_TYP pair4 = {7, 8}; // 这是相同的类型
// 可以赋值
pair4 = pair3; // 正确
// 但这对 pair1 和 pair2 不适用,因为它们是不同的类型
// pair2 = pair1; // 错误:类型不匹配
printf("pair1: a=%d, b=%d\n", pair1.a, pair1.b);
printf("pair3: a=%d, b=%d\n", pair3.a, pair3.b);
printf("pair4: a=%d, b=%d\n", pair4.a, pair4.b);
return 0;
}
2. 位运算
2.1 位运算符
C语言提供了6种位运算符,用于对整数的二进制位进行操作。
2.1.1 按位与运算(&)
规则:两个位都为1时,结果为1,否则为0。
真值表:
cpp
0 & 0 = 0
0 & 1 = 0
1 & 0 = 0
1 & 1 = 1
示例:
cpp
#include <stdio.h>
void print_binary(int num) {
printf("二进制: ");
for (int i = 31; i >= 0; i--) {
printf("%d", (num >> i) & 1);
if (i % 8 == 0) printf(" ");
}
printf("\n");
}
int main() {
int a = 12; // 二进制: 0000 1100
int b = 25; // 二进制: 0001 1001
int result;
printf("=== 按位与运算(&) ===\n\n");
printf("a = %d\n", a);
print_binary(a);
printf("b = %d\n", b);
print_binary(b);
result = a & b;
printf("\na & b = %d\n", result);
print_binary(result);
// 应用示例1:检查奇偶性
printf("\n应用1:检查奇偶性\n");
for (int i = 1; i <= 5; i++) {
if (i & 1) {
printf("%d 是奇数\n", i);
} else {
printf("%d 是偶数\n", i);
}
}
// 应用示例2:清零特定位
printf("\n应用2:清零特定位\n");
int flags = 0b11111111; // 8位全为1
int mask = 0b11110111; // 要清零第4位(从0开始)
int cleared = flags & mask;
printf("原始标志位: ");
for (int i = 7; i >= 0; i--) printf("%d", (flags >> i) & 1);
printf("\n");
printf("掩码: ");
for (int i = 7; i >= 0; i--) printf("%d", (mask >> i) & 1);
printf("\n");
printf("结果: ");
for (int i = 7; i >= 0; i--) printf("%d", (cleared >> i) & 1);
printf("\n");
// 应用示例3:提取特定位
printf("\n应用3:提取特定位\n");
int data = 0b11011010;
int bit_mask = 0b00000100; // 提取第2位
int extracted = (data & bit_mask) >> 2;
printf("数据: %d (二进制: ", data);
for (int i = 7; i >= 0; i--) printf("%d", (data >> i) & 1);
printf(")\n");
printf("第2位的值: %d\n", extracted);
return 0;
}
2.1.2 按位或运算(|)
规则:两个位中只要有一个为1,结果为1,否则为0。
真值表:
cpp
0 | 0 = 0
0 | 1 = 1
1 | 0 = 1
1 | 1 = 1
示例:
cpp
#include <stdio.h>
int main() {
int a = 12; // 二进制: 0000 1100
int b = 25; // 二进制: 0001 1001
printf("=== 按位或运算(|) ===\n\n");
printf("a = %d (二进制: ", a);
for (int i = 7; i >= 0; i--) printf("%d", (a >> i) & 1);
printf(")\n");
printf("b = %d (二进制: ", b);
for (int i = 7; i >= 0; i--) printf("%d", (b >> i) & 1);
printf(")\n");
int result = a | b;
printf("\na | b = %d (二进制: ", result);
for (int i = 7; i >= 0; i--) printf("%d", (result >> i) & 1);
printf(")\n");
// 应用示例1:设置特定位
printf("\n应用1:设置特定位\n");
int permissions = 0b00000100; // 只有读取权限
int write_permission = 0b00000010; // 写入权限位
int execute_permission = 0b00000001; // 执行权限位
// 添加写入权限
permissions = permissions | write_permission;
printf("添加写入权限后: ");
for (int i = 7; i >= 0; i--) printf("%d", (permissions >> i) & 1);
printf("\n");
// 添加执行权限
permissions = permissions | execute_permission;
printf("添加执行权限后: ");
for (int i = 7; i >= 0; i--) printf("%d", (permissions >> i) & 1);
printf("\n");
// 应用示例2:合并标志位
printf("\n应用2:合并标志位\n");
int flag1 = 0b00100000; // 标志1
int flag2 = 0b00001000; // 标志2
int flag3 = 0b00000010; // 标志3
int all_flags = flag1 | flag2 | flag3;
printf("合并所有标志: ");
for (int i = 7; i >= 0; i--) printf("%d", (all_flags >> i) & 1);
printf("\n");
return 0;
}
2.1.3 按位异或运算(^)
规则:两个位不同时,结果为1,相同时为0。
真值表:
cpp
0 ^ 0 = 0
0 ^ 1 = 1
1 ^ 0 = 1
1 ^ 1 = 0
示例:
cpp
#include <stdio.h>
int main() {
int a = 12; // 二进制: 0000 1100
int b = 25; // 二进制: 0001 1001
printf("=== 按位异或运算(^) ===\n\n");
printf("a = %d (二进制: ", a);
for (int i = 7; i >= 0; i--) printf("%d", (a >> i) & 1);
printf(")\n");
printf("b = %d (二进制: ", b);
for (int i = 7; i >= 0; i--) printf("%d", (b >> i) & 1);
printf(")\n");
int result = a ^ b;
printf("\na ^ b = %d (二进制: ", result);
for (int i = 7; i >= 0; i--) printf("%d", (result >> i) & 1);
printf(")\n");
// 异或的性质
printf("\n异或运算性质:\n");
printf("1. a ^ a = 0\n");
printf(" %d ^ %d = %d\n", a, a, a ^ a);
printf("2. a ^ 0 = a\n");
printf(" %d ^ 0 = %d\n", a, a ^ 0);
printf("3. 交换律: a ^ b = b ^ a\n");
printf(" %d ^ %d = %d, %d ^ %d = %d\n",
a, b, a ^ b, b, a, b ^ a);
printf("4. 结合律: (a ^ b) ^ c = a ^ (b ^ c)\n");
int c = 7;
printf(" (%d ^ %d) ^ %d = %d ^ %d = %d\n",
a, b, c, a ^ b, c, (a ^ b) ^ c);
printf(" %d ^ (%d ^ %d) = %d ^ %d = %d\n",
a, b, c, a, b ^ c, a ^ (b ^ c));
// 应用示例1:交换两个变量的值(不使用临时变量)
printf("\n应用1:交换两个变量的值\n");
int x = 10, y = 20;
printf("交换前: x = %d, y = %d\n", x, y);
x = x ^ y;
y = x ^ y; // y = (x ^ y) ^ y = x ^ (y ^ y) = x ^ 0 = x
x = x ^ y; // x = (x ^ y) ^ x = y ^ (x ^ x) = y ^ 0 = y
printf("交换后: x = %d, y = %d\n", x, y);
// 应用示例2:数据加密/解密
printf("\n应用2:简单的数据加密/解密\n");
char plain_text[] = "HELLO";
char key = 0b10101010; // 加密密钥
printf("明文: %s\n", plain_text);
printf("密钥: ");
for (int i = 7; i >= 0; i--) printf("%d", (key >> i) & 1);
printf("\n");
// 加密
printf("加密后的密文: ");
for (int i = 0; plain_text[i] != '\0'; i++) {
char encrypted = plain_text[i] ^ key;
printf("%02X ", (unsigned char)encrypted);
}
printf("\n");
// 解密
char encrypted[] = {0xCA, 0xCF, 0xCC, 0xCC, 0xC9}; // "HELLO"加密后的值
printf("解密后的明文: ");
for (int i = 0; i < 5; i++) {
char decrypted = encrypted[i] ^ key;
printf("%c", decrypted);
}
printf("\n");
// 应用示例3:找出唯一不重复的数字
printf("\n应用3:找出唯一不重复的数字\n");
int nums[] = {1, 2, 3, 4, 5, 4, 3, 2, 1};
int size = sizeof(nums) / sizeof(nums[0]);
int unique = 0;
for (int i = 0; i < size; i++) {
unique ^= nums[i];
}
printf("数组: ");
for (int i = 0; i < size; i++) {
printf("%d ", nums[i]);
}
printf("\n唯一不重复的数字: %d\n", unique);
return 0;
}
2.1.4 按位取反运算(~)
规则:对操作数的每一位取反,0变1,1变0。
示例:
cpp
#include <stdio.h>
int main() {
unsigned char a = 12; // 二进制: 0000 1100
printf("=== 按位取反运算(~) ===\n\n");
printf("a = %d (二进制: ", a);
for (int i = 7; i >= 0; i--) printf("%d", (a >> i) & 1);
printf(")\n");
unsigned char result = ~a;
printf("\n~a = %u (二进制: ", result);
for (int i = 7; i >= 0; i--) printf("%d", (result >> i) & 1);
printf(")\n");
// 注意:有符号整数取反的陷阱
printf("\n有符号整数取反的注意事项:\n");
char signed_a = 12;
char signed_result = ~signed_a;
printf("有符号 char: %d\n", signed_a);
printf("~%d = %d (注意:这不是 -13)\n", signed_a, signed_result);
// 应用示例1:创建掩码
printf("\n应用1:创建掩码\n");
unsigned int mask = ~((1 << 4) - 1); // 清除低4位的掩码
printf("清除低4位的掩码: %08X\n", mask);
printf("二进制: ");
for (int i = 31; i >= 0; i--) printf("%d", (mask >> i) & 1);
printf("\n");
// 应用示例2:求补码(取反加1)
printf("\n应用2:求补码\n");
int num = 42;
int complement = ~num + 1; // 补码
printf("%d 的补码是: %d\n", num, complement);
printf("验证: %d + %d = %d\n", num, complement, num + complement);
// 应用示例3:切换特定位
printf("\n应用3:切换特定位\n");
unsigned char flags = 0b10101010;
unsigned char toggle_mask = 0b00001111; // 切换低4位
printf("原始标志位: ");
for (int i = 7; i >= 0; i--) printf("%d", (flags >> i) & 1);
printf("\n");
unsigned char toggled = flags ^ toggle_mask; // 使用异或切换
printf("切换后标志位: ");
for (int i = 7; i >= 0; i--) printf("%d", (toggled >> i) & 1);
printf("\n");
return 0;
}
2.1.5 左移运算(<<)
规则:将操作数的所有位向左移动指定的位数,右边空出的位用0填充。
示例:
cpp
#include <stdio.h>
int main() {
int a = 5; // 二进制: 0000 0101
printf("=== 左移运算(<<) ===\n\n");
printf("a = %d (二进制: ", a);
for (int i = 7; i >= 0; i--) printf("%d", (a >> i) & 1);
printf(")\n\n");
// 左移1位
for (int shift = 1; shift <= 4; shift++) {
int result = a << shift;
printf("a << %d = %d (二进制: ", shift, result);
for (int i = 7; i >= 0; i--) printf("%d", (result >> i) & 1);
printf(")\n");
}
// 左移的性质:相当于乘以2的n次方
printf("\n左移运算的性质:\n");
printf("a << n 相当于 a × 2^n\n");
for (int i = 0; i <= 4; i++) {
printf("%d << %d = %d, %d × 2^%d = %d\n",
a, i, a << i, a, i, a * (1 << i));
}
// 应用示例1:快速计算2的幂
printf("\n应用1:快速计算2的幂\n");
for (int i = 0; i <= 10; i++) {
printf("2^%d = %d\n", i, 1 << i);
}
// 应用示例2:设置特定位
printf("\n应用2:设置特定位\n");
unsigned int flag = 0;
// 设置第3位(从0开始)
flag = flag | (1 << 3);
printf("设置第3位后: %08X\n", flag);
// 设置第5位
flag = flag | (1 << 5);
printf("设置第5位后: %08X\n", flag);
// 应用示例3:RGB颜色组合
printf("\n应用3:RGB颜色组合\n");
unsigned char red = 255; // 0-255
unsigned char green = 128; // 0-255
unsigned char blue = 64; // 0-255
// 将RGB组合成一个32位整数(ARGB格式,A=255表示不透明)
unsigned int color = (255 << 24) | (red << 16) | (green << 8) | blue;
printf("红色分量: %d (0x%02X)\n", red, red);
printf("绿色分量: %d (0x%02X)\n", green, green);
printf("蓝色分量: %d (0x%02X)\n", blue, blue);
printf("组合颜色: 0x%08X\n", color);
// 从组合颜色中提取分量
unsigned char extracted_red = (color >> 16) & 0xFF;
unsigned char extracted_green = (color >> 8) & 0xFF;
unsigned char extracted_blue = color & 0xFF;
printf("提取的红色: %d\n", extracted_red);
printf("提取的绿色: %d\n", extracted_green);
printf("提取的蓝色: %d\n", extracted_blue);
return 0;
}
2.1.6 右移运算(>>)
规则:将操作数的所有位向右移动指定的位数。
注意:对于有符号数,右移时左边空出的位用符号位填充(算术右移);对于无符号数,用0填充(逻辑右移)。
示例:
cpp
#include <stdio.h>
int main() {
printf("=== 右移运算(>>) ===\n\n");
// 无符号数的右移
printf("无符号数的右移(逻辑右移):\n");
unsigned int u = 0b10101100; // 172
printf("u = %u (二进制: ", u);
for (int i = 7; i >= 0; i--) printf("%d", (u >> i) & 1);
printf(")\n\n");
for (int shift = 1; shift <= 4; shift++) {
unsigned int result = u >> shift;
printf("u >> %d = %u (二进制: ", shift, result);
for (int i = 7; i >= 0; i--) printf("%d", (result >> i) & 1);
printf(")\n");
}
// 有符号数的右移
printf("\n有符号数的右移(算术右移):\n");
int s = -8; // 二进制补码: 1111 1000
printf("s = %d (二进制: ", s);
for (int i = 7; i >= 0; i--) printf("%d", (s >> i) & 1);
printf(")\n\n");
for (int shift = 1; shift <= 4; shift++) {
int result = s >> shift;
printf("s >> %d = %d (二进制: ", shift, result);
for (int i = 7; i >= 0; i--) printf("%d", (result >> i) & 1);
printf(")\n");
}
// 右移的性质:相当于除以2的n次方(向下取整)
printf("\n右移运算的性质:\n");
printf("对于正整数:a >> n 相当于 a ÷ 2^n\n");
printf("对于负整数:a >> n 相当于 a ÷ 2^n(向下取整)\n\n");
int positives[] = {16, 32, 64, 128};
int negatives[] = {-16, -32, -64, -128};
printf("正整数右移:\n");
for (int i = 0; i < 4; i++) {
printf("%d >> 1 = %d, %d / 2 = %d\n",
positives[i], positives[i] >> 1,
positives[i], positives[i] / 2);
}
printf("\n负整数右移:\n");
for (int i = 0; i < 4; i++) {
printf("%d >> 1 = %d, %d / 2 = %d\n",
negatives[i], negatives[i] >> 1,
negatives[i], negatives[i] / 2);
}
// 应用示例1:提取特定位段
printf("\n应用1:提取特定位段\n");
unsigned int data = 0x12345678;
printf("原始数据: 0x%08X\n", data);
// 提取高16位
unsigned int high = data >> 16;
printf("高16位: 0x%04X\n", high);
// 提取低16位
unsigned int low = data & 0xFFFF;
printf("低16位: 0x%04X\n", low);
// 提取中间的8位(从第8位开始)
unsigned int middle = (data >> 8) & 0xFF;
printf("中间8位(8-15位): 0x%02X\n", middle);
// 应用示例2:快速除以2的幂
printf("\n应用2:快速除以2的幂\n");
int numbers[] = {100, 1000, 10000, 100000};
for (int i = 0; i < 4; i++) {
printf("%d / 8 = %d, %d >> 3 = %d\n",
numbers[i], numbers[i] / 8,
numbers[i], numbers[i] >> 3);
}
// 应用示例3:分离RGB颜色分量
printf("\n应用3:分离RGB颜色分量\n");
unsigned int rgb_color = 0xFF336699; // ARGB格式
printf("颜色值: 0x%08X\n", rgb_color);
unsigned char alpha = (rgb_color >> 24) & 0xFF;
unsigned char red = (rgb_color >> 16) & 0xFF;
unsigned char green = (rgb_color >> 8) & 0xFF;
unsigned char blue = rgb_color & 0xFF;
printf("Alpha通道: %d (0x%02X)\n", alpha, alpha);
printf("红色分量: %d (0x%02X)\n", red, red);
printf("绿色分量: %d (0x%02X)\n", green, green);
printf("蓝色分量: %d (0x%02X)\n", blue, blue);
return 0;
}
2.2 位域(位段)
2.2.1 位域的基本概念
位域允许在结构体中定义占用特定位数的成员,用于节省内存空间。
定义语法:
cpp
struct {
类型 成员名 : 位数;
类型 成员名 : 位数;
// ...
};
示例:
cpp
#include <stdio.h>
#include <stdbool.h>
int main() {
printf("=== 位域(位段)示例 ===\n\n");
// 示例1:简单的位域结构
struct {
unsigned int flag1 : 1; // 1位
unsigned int flag2 : 1; // 1位
unsigned int flag3 : 1; // 1位
unsigned int flag4 : 1; // 1位
unsigned int value : 4; // 4位(0-15)
} bitfield1;
printf("示例1:简单的位域结构\n");
printf("结构体大小: %zu 字节\n", sizeof(bitfield1));
bitfield1.flag1 = 1;
bitfield1.flag2 = 0;
bitfield1.flag3 = 1;
bitfield1.flag4 = 0;
bitfield1.value = 12;
printf("flag1: %u\n", bitfield1.flag1);
printf("flag2: %u\n", bitfield1.flag2);
printf("flag3: %u\n", bitfield1.flag3);
printf("flag4: %u\n", bitfield1.flag4);
printf("value: %u\n", bitfield1.value);
// 示例2:混合位域和普通成员
struct Mixed {
char name[20]; // 20字节
unsigned int age : 7; // 7位(0-127岁)
unsigned int gender : 1; // 1位(0=男,1=女)
unsigned int grade : 4; // 4位(0-15年级)
unsigned int : 4; // 4位未命名填充
float salary; // 4字节
};
printf("\n示例2:混合位域和普通成员\n");
printf("结构体大小: %zu 字节\n", sizeof(struct Mixed));
struct Mixed person = {"张三", 30, 0, 3, 8000.0};
printf("姓名: %s\n", person.name);
printf("年龄: %u\n", person.age);
printf("性别: %s\n", person.gender ? "女" : "男");
printf("年级: %u\n", person.grade);
printf("薪资: %.2f\n", person.salary);
// 示例3:位域的实际应用(网络协议头)
printf("\n示例3:IP数据包头结构(简化)\n");
// 简化的IP头结构(仅用于演示)
struct IPHeader {
unsigned int version : 4; // 版本号(4位)
unsigned int ihl : 4; // 头部长度(4位)
unsigned int dscp : 6; // 差分服务代码点(6位)
unsigned int ecn : 2; // 显式拥塞通知(2位)
unsigned int total_length : 16; // 总长度(16位)
unsigned int identification : 16; // 标识(16位)
unsigned int flags : 3; // 标志(3位)
unsigned int fragment_offset : 13; // 分片偏移(13位)
unsigned int ttl : 8; // 生存时间(8位)
unsigned int protocol : 8; // 协议(8位)
unsigned int checksum : 16; // 头部校验和(16位)
unsigned int source_ip; // 源IP地址(32位)
unsigned int dest_ip; // 目的IP地址(32位)
};
struct IPHeader ip_packet = {
.version = 4,
.ihl = 5,
.dscp = 0,
.ecn = 0,
.total_length = 1500,
.identification = 12345,
.flags = 2,
.fragment_offset = 0,
.ttl = 64,
.protocol = 6, // TCP
.checksum = 0xABCD,
.source_ip = 0xC0A80101, // 192.168.1.1
.dest_ip = 0xC0A80164 // 192.168.1.100
};
printf("IP头大小: %zu 字节\n", sizeof(struct IPHeader));
printf("版本: %u\n", ip_packet.version);
printf("头部长度: %u 字(%u 字节)\n", ip_packet.ihl, ip_packet.ihl * 4);
printf("总长度: %u 字节\n", ip_packet.total_length);
printf("TTL: %u\n", ip_packet.ttl);
printf("协议: %u\n", ip_packet.protocol);
printf("源IP: %08X\n", ip_packet.source_ip);
printf("目的IP: %08X\n", ip_packet.dest_ip);
// 示例4:位域的边界对齐
printf("\n示例4:位域的边界对齐\n");
struct BitFieldTest1 {
unsigned int a : 5;
unsigned int b : 8;
unsigned int c : 19;
} test1;
struct BitFieldTest2 {
unsigned int a : 5;
unsigned int : 0; // 强制对齐到下一个边界
unsigned int b : 8;
unsigned int c : 19;
} test2;
struct BitFieldTest3 {
unsigned int a : 5;
unsigned int b : 8;
unsigned int : 0; // 无名位域,填充剩余位
unsigned int c : 19;
} test3;
printf("test1 大小: %zu 字节\n", sizeof(test1));
printf("test2 大小: %zu 字节\n", sizeof(test2));
printf("test3 大小: %zu 字节\n", sizeof(test3));
// 示例5:位域与联合体结合
printf("\n示例5:位域与联合体结合\n");
union StatusRegister {
unsigned int raw_value;
struct {
unsigned int ready : 1; // 设备就绪
unsigned int busy : 1; // 设备忙
unsigned int error : 1; // 错误标志
unsigned int timeout : 1; // 超时标志
unsigned int data_ready : 1; // 数据就绪
unsigned int : 3; // 保留位
unsigned int error_code : 8; // 错误代码
unsigned int data_size : 16; // 数据大小
} bits;
};
union StatusRegister status;
status.raw_value = 0xABCD1234;
printf("原始值: 0x%08X\n", status.raw_value);
printf("ready: %u\n", status.bits.ready);
printf("busy: %u\n", status.bits.busy);
printf("error: %u\n", status.bits.error);
printf("timeout: %u\n", status.bits.timeout);
printf("data_ready: %u\n", status.bits.data_ready);
printf("error_code: %u (0x%02X)\n", status.bits.error_code, status.bits.error_code);
printf("data_size: %u\n", status.bits.data_size);
// 修改位域
status.bits.error = 1;
status.bits.error_code = 0x42;
printf("修改后的原始值: 0x%08X\n", status.raw_value);
return 0;
}
2.2.2 位域的注意事项
cpp
#include <stdio.h>
int main() {
printf("=== 位域的注意事项 ===\n\n");
// 1. 位域不能取地址
struct Test {
unsigned int a : 4;
unsigned int b : 4;
int normal; // 普通成员
} t;
// &t.a; // 错误:不能对位域成员取地址
&t.normal; // 正确:可以对普通成员取地址
// 2. 位域不能是数组
struct BadExample {
// unsigned int arr[10] : 4; // 错误:位域不能是数组
};
// 3. 位域的类型限制
struct TypeExample {
unsigned int u1 : 4; // 正确
int s1 : 4; // 正确(但有符号)
// float f1 : 4; // 错误:不能是浮点型
// double d1 : 4; // 错误:不能是双精度型
};
// 4. 位域的跨字节边界
struct CrossBoundary {
unsigned int a : 10; // 10位,可能跨字节
unsigned int b : 10; // 10位,可能跨字节
unsigned int c : 12; // 12位
} cb;
printf("跨字节边界的位域大小: %zu 字节\n", sizeof(cb));
// 5. 位域的符号问题
struct SignedBitField {
int a : 3; // 3位有符号,范围:-4到3
int b : 4; // 4位有符号,范围:-8到7
int c : 5; // 5位有符号,范围:-16到15
} sbf;
sbf.a = 3; // 正确
sbf.b = -5; // 正确
// sbf.c = 20; // 错误:超出范围(可能被截断)
sbf.c = 15; // 正确
printf("\n有符号位域示例:\n");
printf("a = %d\n", sbf.a);
printf("b = %d\n", sbf.b);
printf("c = %d\n", sbf.c);
// 6. 零长度位域的特殊含义
struct ZeroWidth {
unsigned int a : 5;
unsigned int : 0; // 强制对齐到下一个int边界
unsigned int b : 7;
} zw;
printf("\n零长度位域示例:\n");
printf("结构体大小: %zu 字节\n", sizeof(zw));
// 7. 位域的存储顺序(依赖于系统)
struct BitOrder {
unsigned int a : 1;
unsigned int b : 1;
unsigned int c : 1;
unsigned int d : 1;
} bo;
bo.a = 1;
bo.b = 0;
bo.c = 1;
bo.d = 0;
printf("\n位域存储顺序示例:\n");
printf("位域值: a=%d, b=%d, c=%d, d=%d\n", bo.a, bo.b, bo.c, bo.d);
// 8. 位域的实际应用:设备寄存器
printf("\n实际应用:设备控制寄存器\n");
struct DeviceControl {
unsigned int enable : 1; // 设备使能
unsigned int mode : 2; // 工作模式(00,01,10,11)
unsigned int speed : 3; // 速度(0-7)
unsigned int reserved : 2; // 保留位
unsigned int status : 4; // 状态位
unsigned int error_code : 8; // 错误代码
unsigned int data : 12; // 数据
};
struct DeviceControl device = {
.enable = 1,
.mode = 2,
.speed = 5,
.status = 3,
.error_code = 0,
.data = 2047
};
printf("控制寄存器内容:\n");
printf("使能: %s\n", device.enable ? "是" : "否");
printf("模式: %u\n", device.mode);
printf("速度: %u\n", device.speed);
printf("状态: %u\n", device.status);
printf("错误代码: %u\n", device.error_code);
printf("数据: %u\n", device.data);
return 0;
}
2.3 本章小结
结构体与共用体总结
-
结构体(struct)
-
用于组合不同类型的数据
-
支持嵌套、数组、指针
-
可以使用
.运算符访问成员
-
-
共用体(union)
-
所有成员共享同一块内存
-
用于节省内存或实现类型转换
-
同时只能有效存储一个成员
-
-
枚举(enum)
-
定义命名的整数常量集合
-
提高代码可读性
-
可进行整数运算
-
-
typedef
-
为已有类型创建别名
-
提高代码可读性和可移植性
-
-
结构体指针
-
使用
->运算符访问成员 -
动态内存分配创建结构体
-
链表等数据结构的实现基础
-
位运算总结
-
基本位运算符
-
&:按位与,用于屏蔽位 -
|:按位或,用于设置位 -
^:按位异或,用于切换位 -
~:按位取反 -
<<:左移,相当于乘以2的幂 -
>>:右移,相当于除以2的幂
-
-
位域(位段)
-
在结构体中定义特定位数的成员
-
节省内存空间
-
用于硬件寄存器映射
-
-
常见应用
-
权限管理系统
-
图像处理
-
数据加密
-
网络协议解析
-
硬件控制
-
-
注意事项
-
注意有符号数的右移行为
-
位域不能取地址
-
位运算的优先级较低,建议使用括号
-
注意整数溢出问题
-
综合应用建议
-
结构体与指针结合
-
使用指针传递大结构体提高效率
-
动态创建结构体数组
-
实现链表、树等数据结构
-
-
位运算优化技巧
-
使用位运算代替乘除法
-
使用掩码操作特定位
-
使用异或交换变量值
-
-
内存对齐考虑
-
结构体成员顺序影响内存占用
-
位域的存储顺序依赖系统
-
使用
#pragma pack控制对齐
-
-
可移植性
-
避免依赖特定字节序
-
使用固定宽度整数类型(如
uint32_t) -
为位域使用无符号类型
-