从数组到复杂数据:C语言是怎么一步步变复杂的

写代码这事,说白了就是把数据组织起来,然后告诉计算机怎么处理。可数据这东西,有时候简单,有时候复杂得让人头疼。

刚开始学编程的时候,应该觉得数组挺好用。一排一样的东西,挨个排好,用下标访问,多简单。后来才发现,现实世界哪有这么规整。

数组:整齐划一的队伍

数组是C语言里最简单的数据结构。你声明一个数组,里面的元素必须都是同一个类型。

#include <stdio.h>  

int main() {  
    int scores[5] = {85, 90, 78, 92, 88};  
    float prices[3] = {12.5, 8.9, 15.0};  
    char name[20] = "Zhang San";  
      
    printf("第一个成绩: %d\n", scores[0]);  
    printf("第二个价格: %.1f\n", prices[1]);  
      
    return 0;  
}

数组的好处是简单直接。你告诉编译器你要几个元素,什么类型,它就给你分配一块连续的内存。访问的时候,用下标就行,快得很。

可问题也在这。数组里的元素必须都一样。你想在一个数组里既存整数又存浮点数?不行。你想存一个学生的信息,有名字、有成绩、有年龄?数组干不了这事。

现实世界的数据哪有这么整齐。一个人的信息,名字是字符串,成绩是整数,身高是浮点数。这些东西类型不一样,可它们都属于同一个人。

枚举:给数字起个名字

在说结构体之前,咱们先看看枚举。这东西其实挺简单,就是给整数起个名字,让代码好读一点。

#include <stdio.h>  

// 定义枚举类型  
enum Weekday {  
    MONDAY,    // 0  
    TUESDAY,   // 1  
    WEDNESDAY, // 2  
    THURSDAY,  // 3  
    FRIDAY,    // 4  
    SATURDAY,  // 5  
    SUNDAY     // 6  
};  

enum Status {  
    SUCCESS = 0,  
    FAILURE = -1,  
    TIMEOUT = -2  
};  

int main() {  
    enum Weekday today = WEDNESDAY;  
    enum Status result = SUCCESS;  
      
    printf("今天是星期几: %d\n", today);  
    printf("操作结果: %d\n", result);  
      
    if (today == WEDNESDAY) {  
        printf("周三了,快周末了\n");  
    }  
      
    return 0;  
}

枚举的本质就是整数。编译器在编译的时候,会把 MONDAY 换成 0TUESDAY 换成 1,依此类推。它不占额外的内存,也不增加运行时的开销。

那为啥要用枚举?因为人记名字比记数字容易。你写 if (status == SUCCESS) 比写 if (status == 0) 好懂多了。代码是写给人看的,顺便让机器执行。

枚举还有个好处,编译器能帮你检查类型。如果你把 Weekday 类型的变量赋值给 Status 类型,编译器会警告你。这能避免一些低级的错误。

结构体:把不同类型的数据绑在一起

结构体才是真正解决复杂数据的东西。它允许你把不同类型的数据组合在一起,形成一个新的类型。

#include <stdio.h>  
#include <string.h>  

struct Student {  
    char name[20];  
    int age;  
    float score;  
    char gender;  
};  

int main() {  
    struct Student stu1;  
      
    // 给结构体成员赋值  
    strcpy(stu1.name, "Zhang San");  
    stu1.age = 20;  
    stu1.score = 85.5;  
    stu1.gender = 'M';  
      
    // 访问结构体成员  
    printf("姓名: %s\n", stu1.name);  
    printf("年龄: %d\n", stu1.age);  
    printf("成绩: %.1f\n", stu1.score);  
      
    // 也可以直接初始化  
    struct Student stu2 = {"Li Si", 21, 90.0, 'F'};  
    printf("\n第二个学生: %s, %d岁, %.1f分\n",   
           stu2.name, stu2.age, stu2.score);  
      
    return 0;  
}

结构体在内存里是怎么存的?就是把各个成员挨个排好。name 占20字节,age 占4字节,score 占4字节,gender 占1字节。加起来是29字节,但编译器可能会做内存对齐,实际占32字节。

内存对齐这事,说白了就是为了让CPU访问更快。CPU读内存的时候,一次读4字节或8字节最方便。如果数据没对齐,CPU可能得读两次,慢得很。

结构体指针也挺常用。

struct Student *ptr = &stu1;  

// 用指针访问成员  
printf("姓名: %s\n", ptr->name);  
printf("年龄: %d\n", ptr->age);  

// 等价于 (*ptr).name -> 更简洁

-> 这个操作符就是用来通过指针访问结构体成员的。它等价于 (*ptr).name,但写起来方便多了。

共用体:省内存的妙招

共用体跟结构体长得挺像,但用法完全不一样。结构体的所有成员都有自己的内存,共用体的所有成员共享同一块内存。

#include <stdio.h>  

union Data {  
    int i;  
    float f;  
    char str[20];  
};  

int main() {  
    union Data data;  
      
    printf("共用体大小: %zu 字节\n", sizeof(data));  
      
    data.i = 10;  
    printf("data.i = %d\n", data.i);  
      
    data.f = 220.5;  
    printf("data.f = %.1f\n", data.f);  
    // 这时候 data.i 的值已经变了,因为共享内存  
      
    strcpy(data.str, "Hello");  
    printf("data.str = %s\n", data.str);  
    // 这时候 data.i  data.f 都变了  
      
    return 0;  
}

共用体的大小等于它最大的成员的大小。上面的例子里,str 占20字节,所以整个共用体就是20字节。不管你存的是整数还是浮点数,都在这20字节里。

共用体有什么用?省内存。当你同一时间只需要存一种类型的数据时,用共用体可以节省空间。比如网络协议里,不同类型的消息可能用同一个结构,但每次只发送一种。

不过共用体也挺危险的。你存了整数,又当浮点数读,结果完全不可预料。这东西得小心用,知道自己在干什么才行。

预览时标签不可点

分类: C