学习笔记---结构体

41 篇文章 0 订阅
16 篇文章 0 订阅
结构体

结构体类型


定义:用于描述一组不同数据类型的数据类型

形态:

struct  结构体类型名{成员表列};

struct  结构体类型名

{

    类型名  成员名;

    类型名  成员名;

    ....

};


示例:

struct Student//结构体类型名-----声明Student是一种结构体类型
{
    /*以下是数据项/成员/域(三种叫法皆可)-----通知编译器:结构体包括以下数据项*/
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
};

解析:

1.以上即声明一个结构体类型的示例,这个结构体类型的名称是Student其成员则是大括号内的num、name、sex、age、score、addr。

2.结构体和数组的区别在于:数组用于描述一组同质(同数据类型)数据而结构体用于描述一组异质(不同数据类型),但自成整体的数据


结构体变量


定义:用结构体类型声明的变量


示例:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
};
struct Student student1,student2;//定义两个数据类型为结构体Student的变量student1和student2

解析:

1.必须先声明结构体类型,再定义变量名

2.声明结构体类型时,不分配空间定义结构体类型变量时,为其分配内存空间


结构体内存占用

代码示例:

#include <stdio.h>
#include <stdlib.h>
//这个程序用来测试C语言的内存分配机制

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
};
int main()
{
    printf("结构体占用的字节数为:%d\n",sizeof(struct Student));
    printf("int占用的字节数为:%d\nchar占用的字节数为:%d\ndouble占用的字节数为:%d\n",sizeof(int),sizeof(char),sizeof(double));
    printf("将结构体内的所有数据类型的大小想加后占用的字节数应当为:%d",4+20+1+4+8+30);
    return 0;
}
结果:


解析:

1.可以看到,定义的结构体Student占用的字节数大于他本应当占用的字节数

2.计算机技术发展到现代,内存空间已然不是一种特别紧缺的资源。而计算机的运算速率成为了首要的考虑,因为计算机在操作内存空间时,一次性操作4个字节的速率远大于一次性操作1个字节。因此,为了提高计算机的运算速率,规定所有内存的操作都以四个字节为基准。所以即使对于一个字节的char类型数据,也会为其分配4个字节的内存空间(实际使用的只有其中一个字节,剩下三个字节的空间可以说是浪费了)

3.因此,结构体Student的内存占用大小应为:4(int)+20(char[20])+4(char)+4(int)+8(double)+32(char[30](因为30不是4的倍数,所以多为其分配2字节成为4的倍数,方便计算机操作)=72字节。这种所有对内存的操作都以4的倍数为基准的方式,称为“内存对齐”)


这里直接将结构体作为一种和int、double相同性质的数据类型来定义变量,这是可行的。


结构体类型变量定义的几种方法:

一:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
};//先声明结构体,后定义变量
struct Student student1;
二:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
}student1;//在声明结构体的同时定义变量
三:
struct//结构体的名称可以省略
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
}student1;

四:

typedef struct//因为用不到结构体的名称,所以不命名
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
} Student;//自定义类型的名字
Student student1;//因为此处的Student不是结构体,而是一种内部是结构体的自定义类型,所以不需要加struct前缀

解析:

1.第一、二种方法都是可行的,但第二种相对第一种来说耦合性更高,因此最好使用第一种。而第三种定义的结构体没有命名,因此是一次性的,并不推荐。

2.第四种方法为自定义类型法,是最常用的方法。typedef(语法:typedef  类型  类型名)自定义某种东西为一种数据类型,此处将结构体定义为一种名为Student的类型的新的数据类型


结构体嵌套(结构体的成员是结构体)

例:

struct Date
{
    int month;
    int day;
    int year;
};
struct Student
{
    int num;
    char sex;
    int age;
    struct Date birthday;
    char addr[30];
}student1,student2;
解析: 类似数组嵌套 (二维数组/存储数组指针的指针数组) 十分便捷有用的方式。


结构体初始化

定义:对结构体变量进行初始化

示例1:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
}stu1={10001,"Zhang Xin",'M',19,90.5,"Shanghai"};
struct Student stu2={10002,"Wang Li",'F',20,98,"Beijing"};

解析:和数组初始化十分相似,很容易理解。

示例2:

struct Date
{
    int month;
    int day;
    int year;
};
struct Student
{
    int num;
    char name[20];
    char sex;
    struct Date birthday;
    float score;
};
struct Student student1={10001,"Wang Li",'f',{5,26,1995},100};
struct Student student2={10002,"Li Bai",'m',10,10,1800,500};
解析: 对于嵌套型结构体的初始化,有以上两种形式。和多维数组的初始化进行类比,十分易懂。


结构体变量引用

定义:对结构体变量的操作

一:整体赋值

示例:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
}stu1={10001,"Zhang Xin",'M',19,90.5,"Shanghai"};
struct Student stu2;
stu2=stu1;

解析:

1.和数组不同,结构体变量的名字代表的不是结构体的地址,而是整个结构体

2.stu2=stu1这个操作,将会把stu1中相对位置所有的数据复制给stu2(这种用名称进行赋值运算实现整体赋值的方式结构体可以做到,但数组是无法实现的!


二:成员引用

代码示例1:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试对结构体成员值的引用*/
//声明结构体
struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
};

int main()
{
    //定义结构体变量
    struct Student student1,student2;
    int *i;
    student1.num=10001;//使用'.'运算符引用结构体内部成员。
    student2.age=120;
    student1.score=student2.age++;
    i=&student2.age;
    printf("s1.num:%d\ns2.age:%d\ns1.score:%f\n*i:%d\n",student1.num,student2.age,student1.score,*i);
    return 0;
}

结果1:


解析1:

1.对于结构体成员的引用需要使用'.'(成员运算符)


代码示例2:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试嵌套型结构体的成员引用*/
struct Date
{
    int month;
    int day;
    int year;
};
struct Student
{
    int num;
    char name[20];
    char sex;
    struct Date birthday;
    float score;
};
struct Student student1={10001,"Wang Li",'f',{5,26,1995},100};
struct Student student2={10002,"Li Bai",'m',10,10,1800,500};
int main()
{
    printf("%d\n",student1.num);
    printf("%s\n",student1.name);
    printf("%c\n",student1.sex);
    /*对嵌套结构体的成员的引用*/
    printf("%d/%d/%d\n",student1.birthday.month,student1.birthday.day,student1.birthday.year);
    printf("%.1f\n",student1.score);
    return 0;
}
结果2:


解析2:

1.如上,要引用结构体内部的结构体的成员,只要连续两次使用'.'成员运算符即可。

2.通过“结构体变量名.成员名”的方式引用到的成员,和直接定义的同数据类型变量的使用完全相同。因此,如果结构体的成员是数组,则只需要使用结构体变量名.数组名[下标]即可引用数组中的数据。


结构体作函数参数


定义:结构体作为一种数据类型,同样可以作为函数从参数使用

代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试结构体作为函数参数*/

struct Student//声明结构体
{
    int num;
    char name[20];
    char sex;
    int age;
    double score;
    char addr[30];
};
void print(struct Student);//声明函数

int main()
{
    struct Student student1,student2;
    struct Student *p_stu;
    student1.num=10001;
    student1.age=15;
    student2.num=10003;
    student2.age=120;
    p_stu=&student2;
    print(student1);
    print(*p_stu);
    return 0;
}

void print(struct Student s)//输出结构体内成员的值
{
    printf("%d ",s.num);
    //printf("%s ",s.name);
    //printf("%c ",s.sex);
    printf("%d \n",s.age);
    //printf("%f ",s.score);
    //printf("%s \n",s.addr);
    return;
}
结果:


解析:

1.因为,诸如struct Student stu1,stu2;  ......   stu2=stu1;是将stu1复制给stu2(类比int a,b=2;  a=b;),所以以上代码中student1作为实际参数,s作为形式参数时。是将student1的值复制给s而不是像数组一样传递地址。因此,在函数内对s做的任何操作,将不会影响到实际参数student1

2.因为使用结构体作为函数的实参和形参时,每次调用函数都需要重新定义结构体变量、给结构体变量内的成员一一赋值。在大型程序内,当这样的操作次数增大时,消耗资源也会十分巨大。所以,用结构体变量作实参和形参,程序虽然直观易懂,但效率不高浪费空间(定义新的结构体变量)、浪费时间(给新的结构体变量一一赋值)


结构体数组


定义:每个元素都是一个结构体的数组

示例:

struct Student
{
    int num;
    ....
    char addr[30];
}
struct Student stu[3];

解析:只要认识到:结构体本身就是一种特殊的数据类型,使用上和基本数据类型没什么区别。那么一切的扩展应用的理解也便十分容易了。


结构体数组在内存中存储图示:



定义结构体数组的几种方式:

一:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
struct Student stu[3];
二:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}stu[3];
三:
struct
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
}stu[3];

解析:类比基本数据类型数组的定义以及结构体变量的定义。


结构体数组的初始化


基本形式:

struct Student stu2[ ]={{...},{...},{...}};


struct Student stu3[ ]={...,...,...};


例:

struct Student
{
    int num;
    char name[20];
    char sex;
    int age;
    float score;
    char addr[30];
};
struct Student stu1[3]=
{
    {10101,"Li Lin",'M',18,87.5,"103 Beijing Road"},
    {10102,"Zhang Fun",'M',19,99,"130 Shanghai Road"},
    {10104,"Wang Min",'F',20,78.5,"1010 Zhongshan Road"}
};

解析:对结构体数组进行初始化的方法可以类比结构体变量的初始化和数组的初始化。


代码示例:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来测试结构体数组的应用*/
typedef struct
{
    char name[20];
    int count;
} Person;

int main()
{
    Person person[3]={{"Li",0},{"Zhang",0},{"Fun",0}};
    int i,j;
    char name[20];
    for(i=0;i<10;i++)
    {
        scanf("%s",name);
        for(j=0;j<3;j++)
            if(strcmp(name,person[j].name)==0)
                person[j].count++;
    }
    printf("\nResult:\n");
    //输出计票结果
    for(i=0;i<3;i++)
        printf("%s:%d\n",person[i].name,person[i].count);
    return 0;
}
结果:


解析:对结构体数组的简单应用。


指向结构体变量的指针


示例:

struct Student
{
    int num;
    string name;
    char sex;
    float score;
}stu={......};
struct Student *p=&stu;

解析:类比指向基本数据类型的指针,指针变量中存储的是结构体变量的起始地址。


代码示例1:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*这个程序用来测试指向结构体变量的指针*/
struct Student
{
    int num;
    char name[12];
    char sex;
    float score;
};

int main()
{
    struct Student stu;
    stu.num=10301;
    strcpy(stu.name,"Wang Fun");
    stu.sex='f';
    stu.score=89.5;
    struct Student *p=&stu;//定义指针变量指向结构体变量的起始地址
    printf("%d %s %c %.1f\n",stu.num,stu.name,stu.sex,stu.score);
    printf("%d %s %c %.1f\n",(*p).num,(*p).name,(*p).sex,(*p).score);//通过指针法调用结构体变量的各成员值
    printf("%d %s %c %.1f\n",p->num,p->name,p->sex,p->score);//此处仍是用指针法访问,但使用了一种新的运算符。
    return 0;
}
结果:


解析:

1.以上是用指针操作结构体的示例,类比用指针操作其他数据类型的方法。十分易懂。

2.这个示例中引入了一种新的运算符:“->(指向运算符)其初步使用方式如上所示,可直接引用结构体的成员。


代码示例2:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/*这个程序用来测试指针在结构体变量做函数参数中的应用*/

struct Student
{
    int num;
    char name[12];
    float score[3];
};
void print(struct Student *p);//函数形参为指向结构体变量的指针

int main()
{
    struct Student stu,*pt;
    stu.num=12345;
    strcpy(stu.name,"Li Fung");
    stu.score[0]=67.5;
    stu.score[1]=89;
    stu.score[2]=78.5;
    pt=&stu;
    print(pt);//实参也用指向结构体变量的指针
    return 0;
}

void print(struct Student *p)
{
    printf("%d %s\n",p->num,p->name);
    printf("%.1f  %.1f  %.1f\n",p->score[0],p->score[1],p->score[2]);
}
结果:


解析:

1.如上,使用指向结构体变量的指针做函数参数,即可在函数中调用该结构体变量的值。

2.相对于直接使用结构体变量做函数参数,这种方式同样能实现对结构体变量值的调用。而且免去了重复定义结构体、逐个复制结构体变量值的繁杂操作,大大提高的代码的效率(结构体变量做形参,每次调用函数可能要分配巨大的内存空间。而指针变量做形参,每次都只需要分配4字节的空间32位系统中。但要注意,使用指针变量作为函数参数时,传递的是结构体变量的地址!即,在函数内部对改形参结构体的操作,将会影响到实参!


代码示例3:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来验证结构体操作过程中的易错点1*/
struct Test
{
    int x;
    char *str;//注意此处定义的是字符型指针,而非字符型数组!
};
int main()
{
    struct Test a;
    a.x=100;
    char s[]="Hello";
    strcpy(a.str,s);//试图将s中的值复制给结构体中的字符串
    printf("%d %s\n",a.x,a.str);
    return 0;
}
结果:


解析:此处a.str是一个字符型指针。并且是一个未初始化过的字符型指针!因此直接使用strcpy为其赋值,就犯了野指针的错误。正确的做法是先用:

a.str=(char*)malloc(strlen(s)+1);(此处+1是因为字符串末尾需要一个\0作为字符串终止标志)为指针分配空间,再进行赋值。


代码示例4:

#include <stdio.h>
#include <stdlib.h>
/*这个程序用来验证结构体操作过程中的易错点2*/
struct Test
{
    int x;
    char *str;//注意此处定义的是字符型指针,而非字符型数组!
};
int main()
{
    struct Test a;
    a.x=100;
    char *s=(char*)malloc(10);//为了方面演示,使用动态分配内存空间。
    strcpy(s,"Hello");
    a.str=s;//此处直接将s的地址给予a.str而非先通过malloc给a.str分配内存,再通过strcpy给a.str赋值。
    free(s);//此处释放s的内存空间
    printf("%d %s\n",a.x,a.str);
    return 0;
}
结果:


解析:

1.如上,以上代码中直接将s的地址赋给a.str,试图避免给a.str分配空间的麻烦。

2.这种做法看似可行,实际上十分危险。以上代码便是一种情况的模拟,当s的空间被释放。( 此处使用动态分配空间来模拟这一点,实际上还有很多可能会造成同样的效果,比如当程序执行离开了变量的作用范围)a.str的调用就出现了野指针异常

3.结论是:当结构体的成员出现指针时,在使用前务必为其初始化,且为指针分配空间时务必注意此空间的有效范围




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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值