1. 结构体与共用体基础概念解析
在C/C++程序设计中,结构体(struct)和共用体(union)是两种非常重要的复合数据类型。它们都能将不同类型的数据组合成一个整体,但在内存使用方式和应用场景上有显著区别。
结构体是一种将不同类型的数据项组织在一起的构造数据类型。它允许我们将多个变量(可以是不同类型)组合成一个单一实体。结构体的每个成员都有自己独立的内存空间,结构体的大小等于所有成员大小之和(考虑内存对齐)。
共用体则是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。共用体的所有成员共享同一块内存空间,共用体的大小等于最大成员的大小。任何时候共用体只能存储其中一个成员的值。
2. 结构体应用实例详解
2.1 宿舍卫生检查系统实现
让我们通过一个宿舍卫生检查系统的例子来理解结构体的实际应用。这个系统需要统计多个宿舍的卫生得分情况,并输出不合格宿舍数和最高分。
c复制#include<stdio.h>
// 定义宿舍得分结构体
struct DormScore {
int trash; // 垃圾桶得分
int hook; // 挂钩得分
int desk; // 桌子得分
int bed; // 床铺得分
int windowsill; // 窗台得分
};
int main() {
int n, count = 0, max = -1;
scanf("%d", &n);
struct DormScore dorms[100]; // 最多100个宿舍
for(int i = 0; i < n; i++) {
// 输入五项得分
scanf("%d%d%d%d%d", &dorms[i].trash, &dorms[i].hook,
&dorms[i].desk, &dorms[i].bed, &dorms[i].windowsill);
// 计算总分
int sum = dorms[i].trash + dorms[i].hook + dorms[i].desk
+ dorms[i].bed + dorms[i].windowsill;
if(sum < 85) count++; // 统计不合格宿舍
if(sum > max) max = sum; // 更新最高分
}
if(max < 85)
printf("%d No", count);
else
printf("%d %d", count, max);
return 0;
}
这个例子展示了如何使用结构体来组织相关数据。我们将五项卫生得分组合成一个DormScore结构体,使代码更加清晰和易于维护。
2.2 候选人票数统计系统
另一个典型应用是选举票数统计系统。我们需要统计多个候选人的得票情况,并找出得票最高者。
c复制#include <stdio.h>
struct Candidate {
int id;
int votes;
};
int main() {
int m, n, x;
scanf("%d %d", &m, &n);
struct Candidate candidates[1000] = {0}; // 初始化所有票数为0
for(int i = 0; i < n; i++) {
scanf("%d", &x);
candidates[x].id = x;
candidates[x].votes++;
}
int max_index = 1;
for(int i = 1; i <= m; i++) {
if(candidates[i].votes > candidates[max_index].votes) {
max_index = i;
}
}
printf("%d\n%d\n", candidates[max_index].id, candidates[max_index].votes);
return 0;
}
在这个例子中,我们使用结构体来关联候选人ID和得票数,使得数据管理更加直观和高效。
3. 共用体高级应用技巧
3.1 多类型数据存储系统
共用体特别适合需要存储不同类型数据但又不会同时使用的场景。下面是一个支持INT、DOUBLE、STRING三种类型的数据存储系统实现:
c复制#include<stdio.h>
#include<string.h>
union Data {
int i;
double d;
char str[20];
};
struct DataType {
char type[10]; // 类型标识
union Data data; // 实际数据
};
int main() {
int n, m, x;
scanf("%d%d", &n, &m);
struct DataType arr[100000];
for(int i = 0; i < n; i++) {
scanf("%s", arr[i].type);
if(strcmp(arr[i].type, "INT") == 0)
scanf("%d", &arr[i].data.i);
else if(strcmp(arr[i].type, "DOUBLE") == 0)
scanf("%lf", &arr[i].data.d);
else if(strcmp(arr[i].type, "STRING") == 0)
scanf("%s", arr[i].data.str);
}
for(int i = 0; i < m; i++) {
scanf("%d", &x);
if(strcmp(arr[x].type, "INT") == 0)
printf("%d\n", arr[x].data.i);
else if(strcmp(arr[x].type, "DOUBLE") == 0)
printf("%.2lf\n", arr[x].data.d);
else if(strcmp(arr[x].type, "STRING") == 0)
printf("%s\n", arr[x].data.str);
}
return 0;
}
这个例子展示了共用体的核心优势 - 节省内存空间。所有数据类型共享同一块内存,根据实际需要存储不同类型的数据。
3.2 枚举类型与共用体结合应用
枚举类型常用于定义一组相关的命名常量。结合共用体,我们可以创建更灵活的数据结构:
c复制#include <stdio.h>
#include <string.h>
enum Color {RED, ORANGE, YELLOW, GREEN, BLUE, VIOLET};
union Flower {
char name[20];
enum Color color;
};
int main() {
int n;
scanf("%d", &n);
while(n--) {
char color[20];
scanf("%s", color);
enum Color c;
if(strcmp(color, "red") == 0) c = RED;
else if(strcmp(color, "orange") == 0) c = ORANGE;
else if(strcmp(color, "yellow") == 0) c = YELLOW;
else if(strcmp(color, "green") == 0) c = GREEN;
else if(strcmp(color, "blue") == 0) c = BLUE;
else if(strcmp(color, "violet") == 0) c = VIOLET;
else {
printf("I don't know about the color %s.\n", color);
continue;
}
switch(c) {
case RED: printf("Rose are red.\n"); break;
case ORANGE: printf("Poppies are orange.\n"); break;
case YELLOW: printf("Sunflower are yellow.\n"); break;
case GREEN: printf("Grass are green.\n"); break;
case BLUE: printf("Bluebells are blue.\n"); break;
case VIOLET: printf("Violets are violet.\n"); break;
}
}
return 0;
}
4. 结构体排序算法实践
4.1 多条件排序实现
在实际应用中,我们经常需要对结构体数组进行排序。下面是一个多条件排序的例子,先按质量升序,质量相同则按价格降序:
c复制#include <stdio.h>
struct Item {
int weight;
int price;
};
void bubbleSort(struct Item arr[], int n) {
for(int i = 0; i < n-1; i++) {
for(int j = 0; j < n-i-1; j++) {
// 先按重量排序
if(arr[j].weight > arr[j+1].weight) {
struct Item temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
// 重量相同则按价格降序
else if(arr[j].weight == arr[j+1].weight && arr[j].price < arr[j+1].price) {
struct Item temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int n;
scanf("%d", &n);
struct Item items[100];
for(int i = 0; i < n; i++) scanf("%d", &items[i].weight);
for(int i = 0; i < n; i++) scanf("%d", &items[i].price);
bubbleSort(items, n);
for(int i = 0; i < n; i++)
printf("%d %d\n", items[i].weight, items[i].price);
return 0;
}
4.2 选手排名系统
另一个常见的排序应用是比赛排名系统。下面实现一个按得分降序排序,得分相同保持输入顺序的排名系统:
c复制#include<stdio.h>
struct Player {
int id;
int score;
int order; // 记录原始顺序
};
void stableSort(struct Player arr[], int n) {
for(int i = 0; i < n-1; i++) {
for(int j = 0; j < n-i-1; j++) {
if(arr[j].score < arr[j+1].score) {
struct Player temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int N;
scanf("%d", &N);
struct Player players[10000];
for(int i = 0; i < N; i++) {
scanf("%d%d", &players[i].id, &players[i].score);
players[i].order = i;
}
stableSort(players, N);
for(int i = 0; i < N; i++)
printf("%d %d\n", players[i].id, players[i].score);
return 0;
}
5. 综合应用案例分析
5.1 宾馆选择系统
下面是一个综合应用结构体和多条件排序的宾馆选择系统。优先按价格满意度排序,价格相同则按舒适度排序:
c复制#include <stdio.h>
struct Hotel {
int id;
int priceSatisfaction;
int comfortSatisfaction;
};
int main() {
int n;
scanf("%d", &n);
struct Hotel hotels[5000];
for(int i = 0; i < n; i++) {
scanf("%d%d", &hotels[i].priceSatisfaction, &hotels[i].comfortSatisfaction);
hotels[i].id = i + 1;
}
// 找出最满意的宾馆
int best = 0;
for(int i = 1; i < n; i++) {
if(hotels[i].priceSatisfaction > hotels[best].priceSatisfaction) {
best = i;
}
else if(hotels[i].priceSatisfaction == hotels[best].priceSatisfaction) {
if(hotels[i].comfortSatisfaction > hotels[best].comfortSatisfaction) {
best = i;
}
}
}
printf("%d\n", hotels[best].id);
return 0;
}
5.2 人员筛选系统
下面是一个更复杂的人员筛选系统,结合了结构体、排序和条件筛选:
c复制#include <stdio.h>
#include <string.h>
struct Person {
char name[21];
int height;
int weight;
};
void sortPersons(struct Person arr[], int n) {
for(int i = 0; i < n-1; i++) {
for(int j = 0; j < n-i-1; j++) {
if(arr[j].height > arr[j+1].height) {
struct Person temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
else if(arr[j].height == arr[j+1].height && arr[j].weight > arr[j+1].weight) {
struct Person temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int n, a, b, c, d;
scanf("%d", &n);
struct Person persons[1000];
for(int i = 0; i < n; i++) {
scanf("%s%d%d", persons[i].name, &persons[i].height, &persons[i].weight);
}
scanf("%d%d%d%d", &a, &b, &c, &d);
sortPersons(persons, n);
int count = 0;
for(int i = 0; i < n; i++) {
if(persons[i].height >= a && persons[i].height <= b &&
persons[i].weight >= c && persons[i].weight <= d) {
printf("%s %d %d\n", persons[i].name, persons[i].height, persons[i].weight);
count++;
}
}
if(count == 0) printf("No\n");
return 0;
}
6. 性能优化与最佳实践
6.1 结构体内存对齐优化
理解结构体内存对齐对编写高效程序至关重要。编译器通常会根据成员变量的类型和顺序对结构体进行内存对齐,这会影响结构体的大小和访问速度。
c复制#include <stdio.h>
// 非优化结构体
struct Unoptimized {
char a;
int b;
char c;
};
// 优化后的结构体
struct Optimized {
int b;
char a;
char c;
};
int main() {
printf("Unoptimized size: %lu\n", sizeof(struct Unoptimized));
printf("Optimized size: %lu\n", sizeof(struct Optimized));
return 0;
}
输出可能是:
code复制Unoptimized size: 12
Optimized size: 8
通过合理安排成员变量的顺序,可以减少结构体的内存占用,提高缓存利用率。
6.2 共用体的类型安全使用
共用体虽然灵活,但需要特别注意类型安全。下面是一些最佳实践:
- 总是记录当前存储的数据类型
- 提供类型安全的访问接口
- 避免直接暴露共用体给外部代码
c复制#include <stdio.h>
#include <string.h>
enum DataType {INT, DOUBLE, STRING};
struct SafeData {
enum DataType type;
union {
int i;
double d;
char str[20];
} data;
};
void printData(struct SafeData *sd) {
switch(sd->type) {
case INT: printf("%d\n", sd->data.i); break;
case DOUBLE: printf("%.2lf\n", sd->data.d); break;
case STRING: printf("%s\n", sd->data.str); break;
}
}
int main() {
struct SafeData data1 = {INT, .data.i = 42};
struct SafeData data2 = {DOUBLE, .data.d = 3.14159};
struct SafeData data3 = {STRING};
strcpy(data3.data.str, "Hello");
printData(&data1);
printData(&data2);
printData(&data3);
return 0;
}
7. 常见问题与调试技巧
7.1 结构体初始化问题
结构体初始化不当是常见错误来源。C语言提供了多种初始化方式:
c复制#include <stdio.h>
struct Point {
int x;
int y;
};
int main() {
// 方式1:逐个成员初始化
struct Point p1;
p1.x = 10;
p1.y = 20;
// 方式2:声明时初始化
struct Point p2 = {30, 40};
// 方式3:指定成员初始化(C99)
struct Point p3 = {.y = 50, .x = 60};
// 方式4:复合字面量(C99)
struct Point p4 = (struct Point){70, 80};
printf("p1: %d,%d\n", p1.x, p1.y);
printf("p2: %d,%d\n", p2.x, p2.y);
printf("p3: %d,%d\n", p3.x, p3.y);
printf("p4: %d,%d\n", p4.x, p4.y);
return 0;
}
7.2 共用体数据覆盖问题
共用体成员共享内存空间,不当使用会导致数据覆盖:
c复制#include <stdio.h>
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
data.i = 10;
printf("data.i: %d\n", data.i);
data.f = 3.14;
printf("data.f: %f\n", data.f);
printf("data.i after setting f: %d (unreliable)\n", data.i);
strcpy(data.str, "Hello");
printf("data.str: %s\n", data.str);
printf("data.f after setting str: %f (unreliable)\n", data.f);
return 0;
}
输出结果中,修改一个成员会影响其他成员的值,因为它们共享同一块内存。
7.3 结构体指针与箭头运算符
使用结构体指针时,要注意点运算符(.)和箭头运算符(->)的区别:
c复制#include <stdio.h>
#include <stdlib.h>
struct Student {
char name[20];
int age;
};
int main() {
struct Student s1 = {"Alice", 20};
struct Student *ptr = &s1;
// 通过指针访问成员的两种方式
printf("Name: %s, Age: %d\n", (*ptr).name, (*ptr).age);
printf("Name: %s, Age: %d\n", ptr->name, ptr->age);
// 动态分配结构体
struct Student *s2 = malloc(sizeof(struct Student));
strcpy(s2->name, "Bob");
s2->age = 21;
printf("Name: %s, Age: %d\n", s2->name, s2->age);
free(s2);
return 0;
}
箭头运算符(->)是解引用和成员访问的组合,代码更简洁易读。