比特鹏哥网课笔记(结构体,枚举,联合体,通讯录项目)

本文探讨了结构体内存对齐的技巧,如何通过调整对齐和节省空间设计高效结构;介绍了枚举用于定义常量的便利性,以及联合体在共用数据领域的使用。还涉及了大小端字节序判断和通讯录项目实战,展示了如何在实际编程中运用这些概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.结构体内存对齐

(1)在设计结构体时,既要满足对齐,又要节省空间(让占用空间小的成员尽量集中在一起) 

struct s1
{    char c1;
     char c2;
     int i;//占用8个字节
struct s2
{    char c1;
     int i;
     char c2;//占用12个字节

(2)修改默认对齐数

#pragma pack(4)  //设置为4 

#pragma pack()   //取消设置的默认对齐数

(3)写一个宏,计算结构体中某变量相对于首地址的偏移(百度笔试题)//offsetof宏

头文件#include<stddef.h>

printf("%d",offsetof(struct name,struct member name));

(4)位段,为了节省空间。但是不能跨平台

struct S
{
    int a:2;//是占据int型(4个字节*8=32bits中的2个比特位)
    int b:5;
    int c:15;
    int d:30;   //sizeof(S)=8个字节,a,b,c占据4个字节中的22个bit
int main()
{
    s.a=10;
    s.b=20;//是把数字先转化为二进制再存储
}

2.枚举(当需要define多个变量时,用枚举来代替)

#include<stdio.h>
enum Color
{
    RED,//0
    GREEN,//1
    BLUE  //2  枚举的可能取值——常量,main里面不能修改,
};
int main()
{
    enum Color c=BLUE;
    printf("%d %d %d",RED,GREEN,BLUE);//输出默认的0 1 2
    return 0;
}

3.联合体(共用体)

union Un
{
    char c;
    int i;
};
int main()
{
    union Un u;
    printf("%d\n",sizeof(u));//i和c不能同时使用

 面试题(判断当前计算机的大小端数据)

(1)大小端字节序问题

int check_sys
{
    union Un
    {
        int i;
        char c;
    }u;
    u.i=1;
    return u.c;//c本是char,以int型返回
}
int main()
{
    int a=1;  //寄存器里用16进制0x00000001 ,若小端,即01 00 00 00(<-------方向读取),
    int ret=check_sys();                     大端,即00 00 00 01(------->方向读取)
    if(1==ret) //转化成char*,是为了取一个字节看是00/01
    {
        printf(“小端”);
    }
    else printf("大端");
    //int a=0x11223344;//11是高字节,44是低字节
    //低地址------------------------>高地址
    //....[][][][][11][22][33][44][][][][][][]...大端字节序存储模式
    //....[][][][][44][33][22][11][][][][][][]...小端字节序存储模式(低对低)

(2)大小的计算

最大成员大小不是最大对齐数的整数倍时,就要对齐到最大对齐数的整数倍

union Un
{
    int a;//4
    char arr[5];//字符数组的对齐数按照单个char来算~1,vs编译器中默认对齐数为8,
}   大小为8

实战项目(通讯录)

功能

1.存放1000个好友的信息

名字,电话,性别,住址,年龄

2.增加好友的信息

3.删除指定名字的好友信息

4.查找好友信息

5.修改好友信息

6.打印好友信息

7.排序

#define _CRT_SECURE_NO_WARNINGS 1//避免因为使用scanf,strcpy等不安全函数而报错
#define MAX 1000
#define MAX_NAME 20
#define MAX_SEX  5
#define MAX_TELE 12
#define MAX_ADDR 30
#include<stdio.h>
#include<string.h>
struct PeoInfo
{
    char name[MAX_NAME];
    int age;
    char sex[MAX_SEX];
    char tele[MAX_TELE];
    char addr[MAX_ADDR];
};
//创建通讯录类型
struct Contact
{
    struct PeoInfo data[MAX];
    int size;//记录当前已经有的元素个数
};
enum Option
{
    EXIT,//0
    ADD, //1
    DEL, //2
    SEARCH,
    MODIFY,
    SHOW,
    SORT
};//用枚举来把case中的0 1 2 3等替换,增加代码的可读性
void InitContact(struct Contact *ps)
{
    memset(ps->data,0,sizeof(ps->data));//将数组内元素设为0,用memset
    ps->size=0;
}
void menu()
{
    printf("****************************************\n");
    printf("*****1.add        2.del        *********\n");
    printf("*****3.search     4.modify     *********\n");
    printf("*****5.show       6.sort       *********\n");
    printf("*****0.exit                    *********\n");
    printf("****************************************\n");
}
void Addcontact(struct Contact *ps)
{
    if(ps->size==MAX)
    {
        printf("通讯录已满,无法增加");
    }
    else
    {
        printf("请输入名字:>");
        scanf("%s",ps->data[ps->size].name);//以%s的形式输入字符数组,引用时只写数组的名字
        printf("请输入年龄:>");
        scanf("%d",&(ps->data[ps->size].age));//注意不是数组形式时,要用&
        printf("请输入性别:>");
        scanf("%s",ps->data[ps->size].sex);
        printf("请输入电话:>");
        scanf("%s",ps->data[ps->size].tele);
        printf("请输入住址:>");
        scanf("%s",ps->data[ps->size].addr);
        ps->size++;
        printf("添加成功\n");
    }
}
int FindByName(const struct Contact *ps,char *name)
{
    int i=0;
    for(i=0;i<ps->size;i++)
    {
        if(0==strcmp(ps->data[i].name,name));//字符串比较不用==,0表示==
        {
            return i;
        }
    }
    return -1;//找不到
}
void Delcontact(struct Contact *ps)
{
    char name[MAX_NAME];
    printf("请输入要删除人的名字:");
    scanf("%s",name);
    //1.查找要删除的人的位置
    int pos=FindByName(ps,name);
    /*int i=0;
    for(i=0;i<ps->size;i++)
    {
        if(0==strcmp(ps->data[i].name,name));//字符串比较不用==,0表示==
        {
            break;
        }
    }删查改都要用到这一段,打包到一个FindBy函数里*/
    //2.删除
    if(-1==pos)
        printf("要删除的人不存在");
    else
    {
        int j=0;
        for(j=pos;j<(ps->size)-1;j++)
        {
            ps->data[j]=ps->data[j+1];
        }
        ps->size--;
        printf("删除成功");
    }
}
void SearchContact(const struct Contact *ps)
{
    char name[MAX_NAME];
    printf("请输入要查找人的名字:");
    scanf("%s",name);
    int pos=FindByName(ps,name);
    if(pos==-1) printf("要查找的人不存在");
    else
    {
        printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n","名字","年龄","性别","电话","住址");
        printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n",
                   ps->data[pos].name,
                   ps->data[pos].age,
                   ps->data[pos].sex,
                   ps->data[pos].tele,
                   ps->data[pos].addr);
    }
}
void ModifyContact(struct Contact *ps)
{
    char name[MAX_NAME];
    printf("请输入要修改人的名字");
    scanf("%s",name);
    int pos=FindByName(ps,name);
    if(pos==-1) printf("修改人的信息不存在");
    else
    {
        printf("请输入名字:>");
        scanf("%s",ps->data[pos].name);//以%s的形式输入字符数组,引用时只写数组的名字
        printf("请输入年龄:>");
        scanf("%d",&(ps->data[pos].age));//注意不是数组形式时,要用&
        printf("请输入性别:>");
        scanf("%s",ps->data[pos].sex);
        printf("请输入电话:>");
        scanf("%s",ps->data[pos].tele);
        printf("请输入住址:>");
        scanf("%s",ps->data[pos].addr);
        ps->size++;
        printf("修改完成\n");
    }
}
void Showcontact(const struct Contact *ps)//因为只是显示,为了避免误操作改变信息,加上const
{
    if(ps->size==0)
    {
        printf("通讯录为空");
    }
    else
    {
        int i=0;
        printf("%-20s\t%-4s\t%-5s\t%-12s\t%-20s\n","名字","年龄","性别","电话","住址");//加上负号,左对齐
        for(i=0;i<ps->size;i++)
        {
            printf("%-20s\t%-4d\t%-5s\t%-12s\t%-20s\n",
                   ps->data[i].name,
                   ps->data[i].age,
                   ps->data[i].sex,
                   ps->data[i].tele,
                   ps->data[i].addr);
        }
    }
}
int main()
{
    int input=0;
    struct Contact con;/*int size=0;
                       //创建通讯录
                       struct PeoInfo con[MAX];*/
    //初始化通讯录
    InitContact(&con);
    do
    {
        menu();
        printf("请选择:>");
        scanf("%d",&input);
        switch(input)
        {
        case ADD:
            Addcontact(&con);
            break;
        case DEL:
            Delcontact(&con);
            break;
        case SEARCH:
            SearchContact(&con);
            break;
        case MODIFY:
            ModifyContact(&con);
            break;
        case SHOW:
            Showcontact(&con);//结构体传参传地址,效率更高,这个函数为了显示信息是否添加成功
            break;
        case SORT:
            break;
        case EXIT:
            printf("退出通讯录");
            break;
        default:
            printf("选择错误");
            break;
        }
    }while(input);
    return 0;
}

<think>好的,用户想了解函数栈帧的概念以及比特的相关教程。首先,我需要回忆一下函数栈帧的基本知识。函数栈帧是在程序运行时为每个函数调用分配的内存区域,用于存储局部变量、参数和返回地址等信息。ESP和EBP寄存器在这个过程中起到维护栈帧的作用。 接下来,用户提到了比特的教程,可能需要结合他的教学内容来解释。根据引用[1],栈区的高地址和低地址之间会为main函数开辟栈帧,ESP和EBP会随着函数调用维护不同的栈帧。例如,调用Add函数时,寄存器会转而维护Add的栈帧。这部分内容可以作为基础,说明栈帧的动态变化。 然后,我需要考虑如何结构清晰地回答。可能需要分步骤讲解函数栈帧的创建、使用和销毁过程,并结合具体的例子,比如函数调用时参数传递、局部变量的存储。引用[2]中的Python函数参数示例虽然与C/C++不同,但可以对比说明栈帧中的参数传递机制。不过主要还是要以C/C++为例,因为函数栈帧通常是在这类语言中详细讨论的。 此外,要注意用户可能对寄存器ESP和EBP的作用不太清楚,需要解释它们在栈帧中的角色。比如,EBP指向栈帧的底部,ESP指向顶部,随着数据入栈出栈而变化。同时,栈帧的空间分配、局部变量的布局以及函数返回时的清理过程也是重点。 关于比特的教程,可能需要提到他的教学内容是否覆盖了栈帧的详细结构、调试方法或者实际应用案例。例如,通过反汇编或调试工具观察栈帧的变化,这样可以帮助理解理论概念。 用户可能还有更深层的需求,比如理解栈溢出原理、递归调用的栈帧使用情况,或者如何优化栈空间。这些问题可以作为后续的相关问题提出,引导进一步学习。 最后,确保回答符合系统级指令的要求,正确使用LaTeX格式,生成相关问题,并引用提供的资料。需要检查是否有遗漏的要点,比如栈帧与静态方法的关系,但引用[3]提到的是静态方法中this不存在,这可能与栈帧中的局部变量表有关,但主要适用于面向对象语言,如Java,而函数栈帧更多在过程式语言如C中讨论。因此可能需要区分不同语言的环境。 总结回答结构:定义函数栈帧,寄存器作用,创建与销毁过程,比特教程内容,相关引用,最后生成问题。</think>### 函数栈帧详解 #### 1. 函数栈帧的定义与作用 **函数栈帧**是程序运行时为每个函数调用分配的独立内存区域,用于存储函数的局部变量、参数、返回地址和临时计算结果等数据[^1]。 - **栈区结构**: 在内存的栈区中,高地址存放主函数(如 `main``)的栈帧,低地址随着嵌套函数调用逐步分配新栈帧。 - **寄存器维护**: `esp`(栈顶指针)和 `ebp`(栈底指针)寄存器动态维护当前函数的栈帧边界。例如,调用 `Add` 函数时,`ebp` 会指向 `Add` 栈帧的底部,`esp` 指向顶部。 #### 2. 函数栈帧的创建与销毁过程 以C语言为例,函数栈帧的操作分为以下步骤: 1. **参数压栈**: 调用函数前,参数按从右到左顺序压入栈中。 2. **保存返回地址**: 将下一条指令的地址存入栈,供函数返回时使用。 3. **调整寄存器**: - 将当前 `ebp` 的值压栈保存。 - 将 `ebp` 更新为当前 `esp` 的值,标记新栈帧的底部。 - 调整 `esp` 为新栈帧分配空间(如预留局部变量空间)。 ```c void Add(int a, int b) { int c = a + b; // 局部变量c存储在Add函数的栈帧中 } ``` 4. **局部变量存储**: 函数内部定义的变量(如 `c`)占用栈帧空间。 5. **栈帧销毁**: 函数返回时,`esp` 恢复为 `ebp` 的值,释放栈帧空间;`ebp` 恢复为调用前的值,返回到上一级函数[^1]。 #### 3. 比特教程重点 比特的教程中可能涵盖以下核心内容: - **调试观察**: 通过调试工具(如GDB)查看 `esp` 和 `ebp` 的变化,直观理解栈帧分配。 - **汇编分析**: 结合反汇编代码,解析函数调用对应的机器指令(如 `call`、`ret`、`push`、`pop`)。 - **实际案例**: 分析递归调用或嵌套函数时的栈帧叠加,解释栈溢出的原理。 #### 4. 关键知识点对比 | 场景 | 栈帧变化 | |--------------------|--------------------------------------------------------------------------| | 函数调用时 | `ebp` 指向新栈帧底部,`esp` 指向顶部 | | 函数返回时 | `esp` 回退到调用前的栈顶,`ebp` 恢复为原栈帧底部 | | 多级嵌套调用 | 栈帧逐层叠加,形成“栈增长”(向低地址延伸) |
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值