结构体和共用体

结构体和共用体

区别:内存分配

结构体的各项成员占用不同的内存,互相不影响,占用内存大于等于所有成员的内存总和

共用体的所有成员占用一段内存,相互影响,占用内存至少为最长的成员所占用的内存,而且要满足是所有成员大小的整数倍

struct

各成员各自拥有自己的内存,各自使用互不干涉,同时存在,遵循内存对齐原则

定义形式

struct 结构体名
{
    结构体所包含的变量或数组
}结构体变量;

注意分号不能少!
若没有结构体名,后面就没法用该结构体定义新变量

其他变量定义形式

struct 结构体名
{
    结构体所包含的变量或数组
};
struct 结构体名 结构体变量;



struct{
    结构体所包含的变量或数组
}结构体变量;    //此时是无名结构

偏移量

偏移量指的是结构体变量中成员的地址和结构体变量地址的差。
结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。

由于存储变量时地址对齐的要求,编译器在编译程序时遵循两条原则

  1. 结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
  2. 结构体大小必须是所有成员大小的整数倍
struct stu1
{
    int i;
    char c;
    int j;
};

第一个成员i的偏移量为0,第二个成员c的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),第三个成员j的偏移量是第二个成员的偏移量加上第二个成员大小(4+1),但第三个成员的偏移量为5,不是自身(int)的整数倍,所以处理时给第二个成员后面补三个空字节,使第三个成员的偏移量变成了8,所以该结构体大小为12。

struct stu2
{
    int k;
    short t;
};

成员k偏移量为0,成员t偏移量为4,无需调整,但6不是k的整数倍,因此编译器会在t后补两个字节,所以该结构体大小为8

顺序会影响大小

struct stu3{
    char c1;
    int i;
    char c2;
};
sizeof(struct stu3)的值为12

struct stu4{
    char c1;
    char c2;
    int i;
};
sizeof(struct stu4)的值为8

结构中成员是另一种结构体类型时,展开即可,展开后的结构体第一个成员的偏移量是被展开的结构体中最大的成员的整数倍

struct stu5
{
    short i;
    struct{
        char c;
        int j;
    }ss;
    int k;
};

ss.c的偏移量为4
sizeof(struct stu5)值为16

结构成员

结构和数组有点像
数组用[]运算符和下表访问其成员

  • a[0] = 10;

结构用.运算符和成员名访问其成员

  • a.b = 10;
  • today.day
  • p1.x

结构体的初始化

获取结构体成员的一般格式: 结构体变量名.成员名;
结构体变量名.成员名 = 成员值;
结构体使用初始化列表进行初始化,可对结构体成员进行逐一赋值和部分赋值
以下两种赋值方式

struct{
    char* name;
    int num;
    int age;
    char group;
    float score;
}stu1;
stu1.name = "haozi";//部分赋值
struct{
    char* name;
    int num;
    int age;
    char group;
    float score;
}stu1,stu2 = {"haozi",12,......};//逐一赋值(类似于对数组赋值)

注意!:不能通过类型名去使用变量

还有指定初始化
可以使用指定初始化通过点运算符和结构体成员名对特定的结构体成员进行指定初始化

#include <stdio.h>
struct test{
  int num ;
  float value;
  int count;
};
int main()
{
    struct test t1 = {.value = 5.5,.count = 10};
    printf("t1.num=%d\n",t1.num);
    printf("t1.value=%f\n",t1.value);
    printf("t1.count=%d\n",t1.count);
    return 0;
}

结构运算

要访问整个结构,直接用结构变量的名字
对于整个结构可以做赋值、取地址,也可以传递给函数参数

  • p1 = (struct point){5,10}; //相当于p1.x=5; p1.y=10;
  • p1 = p2; //相当于p1.x=p2.x; p1.y=p2.y;

结构体和数组

结构体中可以有数组类型的成员,数组的元素也可以是结构体

// 定义一个结构体类型
struct Product {
    char name[50];
    int id;
    float price;
};

// 声明一个结构体数组,包含5个Product类型的元素
struct Product products[5];

// 现在可以通过products数组来访问或初始化其中的每个结构体元素
products[0].name = "Product A";
products[0].id = 1001;
products[0].price = 99.99;

// 对其他数组元素做同样操作...

结构体与指针

当一个指针变量指向结构体时,称为结构体指针

结构体变量名与数组名不一样,数组名在表达式中会被转换成数组指针,而结构体变量名不会,无论在任何表达式中标表示的都是整个结构体本身,结构变量的名字并不是结构变量的地址,必须用&取地址

  • struct date *p Date = &today;

指针可以指向一个结构体,定义形式为struct 结构体名 *变量名;

通过结构体指针获取结构体成员的方式
(ptr).structMember
ptr->structMember
.运算符高于
,所以(*ptr)括号不能少

#include <cstdio>
#include <cstring>
struct Man{
    char name[20];
    int age;
};
int main(){
    struct Man m1 = {"Jack",30};
    struct Man *p = &m1;
    printf("%s,%d\n",m1.name,m1.age);
    printf("%s,%d\n",(*p).name,(*p).age);
    printf("%s,%d\n",p->name,p->age);
    return 0;
}

运行结果:

Jack,30
Jack,30
Jack,30

通过结构体指针可以获取结构体成员,一般形式为:(*pointer).memberNamepointer->memberName

结构体作为函数参数

首先结构体做函数参数有三种传递方式

一是传递结构体变量,这是值传递,二是传递结构体指针,这是地址传递,三是传递结构体成员,当然这也分为值传递和地址传递

结构体变量名代表整个结构体变量本身,当把结构体变量本身作为参数传递时,结构体内部的结构体成员较多时会造成时间和空间开销大,影响程序的运行效率
int numberofdays(struct date d);

  • 整个结构可以作为参数的值传入函数
  • 这时候是在函数内新建了一个结构变量,并复制调用者的结构的值
  • 也可以返回一个结构

使用结构体指针作为参数传递时,因为传递的是地址,会比原本含有较多结构体的结构体变量更加块,但是也会造成结构体变量数据被修改的可能

指向结构的指针

struct date {
    int month;
    int day;
    int year;
}myday;

struct date *p = &myday;

(*p).month = 12;
p->month = 12;

用->表示指针所指的结构变量中的成员

函数指针

函数指针是指向函数的指针变量
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数
函数指针可以向一般函数一样,用于调用函数,调用函数、传递参数

函数指针变量的声明

typedef int ( *pfun)(int, int); // 声明一个指向同样参数、返回值的函数指针类型

那么结构体就可以重新定义,将执行的动作函数定义为函数指针

typedef struct student{
    char * name; //名字
    int age; //年龄
    void (* work)(void);//函数指针
    struct student * classmate; //同桌
}student, * pstudent;

写成完整的代码

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

/*要求张三执行一,李四执行二*/

typedef struct student{
    char *name;
    int age;
    void (*work)(void);
    struct student * classmate;
}student, * pstudent;

void work1(){
    // 执行一
}
void work2(){
    // 执行二
}

int main(){
    int i;
    student ss[2] = {{"zhangsan",18,work1,NULL},{"lisi",20,work2,NULL}};
    for(i=0; i<2; i++){
        ss[i].work();
    }
    return 0;
}

在C语言中,函数名确实可以视为该函数的地址。当你不带任何操作符直接使用函数名时,它会被隐式转换为指向该函数的指针。这意味着你可以像处理其他指针一样传递函数名给其他函数或赋值给函数指针变量,所以上述代码的结构体数组赋值时,可以直接使用函数名。当然也可以在前面加上&,&work1,& work2

结构中的结构

struct dateAndTime{
    struct date sdate;
    struct time stime;
};
嵌套的结构
struct point{
    int x;
    int y;
};
struct rectangle{
    struct point p1;
    struct point p2;
};

如果有变量struct rectangle r;就可以有

r.p1.x
r.p1.y
r.p2.x
r.p2.y

如果有变量定义

struct rectangle r,*rp;
rp = &r;

那么下面四种形式是等价的

r.p1.x
rp->p1.x
(r.p1).x
(rp->p1).x

结构体实现链表

后面有专门说链表的
链表是一种常见的基础数据结构,结构体指针在这里得到了充分的利用。链表可以动态的进行存储分配,也就是说,链表是一个功能极为强大的数组,他可以在节点中定义多种数据类型,还可以根据需要随意增添,删除,插入节点。链表都有一个头指针,一般以head来表示,存放的是一个地址。链表中的节点分为两类,头结点和一般节点,头结点是没有数据域的。链表中每个节点都分为两部分,一个数据域,一个是指针域。head指向第一个元素:第一个元素又指向第二个元素,直到最后一个元素,该元素不再指向其它元素,它称为“表尾”,它的地址部分放一个“NULL”(表示“空地址”),链表到此结束

创建列表

typedef struct student{
    int score;
    struct student *next;
}LinkList;

一般创建链表我们都用typedef struct,因为这样定义结构体变量时,我们就可以直接可以用LinkList *a;定义结构体类型变量了

LinkList *creat(int n){
    LinkList *head, *node, *end;//定义头结点,节点,尾节点
    head = (LinkList *)malloc(sizeof(LinkList));//分配空间
    end = head///若是空链表,则头尾结点一样
    for(int i=0;i<n;i++){
        node = (LinkList *)malloc(sizeof(LinkList));//分配空间
        scanf("%d",&node->score);
        end->next = node;
        end = node;
    }
    end->next = NULL;//最后一个节点指向NULL
    return head;
}

修改链表的节点值

void change(LinkList *list, int n)//n为第n个结点
{
    LinkList *t = list;
    int i = 0;
    while(i < n && t != NULL){
        t = t->next;
        i++;
    }
    if(t != NULL){
        puts("输入修改的值");
        scanf("%d", &t->score);
    }
    else{
        puts("输入的结点不存在");
    }
}

删除链表节点

删除链表的元素也就是把前节点的指针域越过要删除的节点指向下个节点,即p -> next = q -> next;然后释放q的空间

void delet(LinkList *list, int n)//n为第n个结点
{
    LinkList *t = list, *in;
    int i = 0;
    while(i < n && t != NULL){
        in = t;
        t = t->next;
        i++;
    }
    if(t != NULL){
        in->next = t->next;
        free(t);
    }
    else{
        puts("输入的结点不存在");
    }
}

插入链表节点

插入节点就是用插入前节点的指针域链接上插入节点的数据域,再把插入节点的指针域链接上插入后节点的数据域。即e -> next = head -> next; head -> next = e;

void insert(LinkList *list, int n) {
    LinkList *t = list, *in;
    int i = 0;
    while(i < n && t != NULL){
        t = t->next;
        i++;
    }
    if(t != NULL){
        in = (LinkList *)malloc(sizeof(LinkList));
        puts("输入插入的值");
        scanf("%d", &in->score);
        in->next = t->next;//填充in节点的指针域,也就是说把in的指针域指向t的下一个节点
        t->next = in;//填充t的指针域,也就是把t的下一个节点指向in
    }
    else{
        puts("输入的结点不存在");
    }
    return;
}

输出链表,遍历就好

while(h->next != NULL){
    h = h->next;
    printf("%d ", h->score);
}

union

这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间,地址相同

注意!

  1. 共用体使用内存覆盖技术,同一时刻只能保存一个成员的值,再对新成员赋值会覆盖原来成员的值
  2. 不允许对变量名直接复制或其他操作
union 共用体名{
    成员列表
};

创建变量

union data{
    int n;
    char ch;
    double f;
};
union data a,b,c;union data{
    int n;
    char ch;
    double f;
}a,b,c;

共用体data中,成员f占用内存最多,所以data类型变量占8个字节的内存

#include <stdio.h>
union var{
    long j;
    int i;
};
main(){
    union var v;
    v.j = 5;
    printf("v.j is %d\n",v.i);
    v.i = 6;   //最后一次赋值有效
    printf("now v.j is %ld! the address is %p\n", v.j, &v.j);
    printf("now v.i is %d! the address is %p\n", v.i, &v.i);
}

运行结果

v.j is 5
now v.j is 6! the address is 0061FF0C
now v.i is 6! the address is 0061FF0C

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值