200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > C语言结构体(用户自定义数据类型)

C语言结构体(用户自定义数据类型)

时间:2018-10-10 06:58:49

相关推荐

C语言结构体(用户自定义数据类型)

目录

1.结构体设计

1.1结构体类型的基本形式

1.2结构体设计进阶形式

2.结构体的使用(成员变量访问)

2.1结构体变量

2.1.1结构体变量的初始化

2.1.2结构体变量的成员变量的访问( ”. “ 成员访问符)

2.2结构体指针

2.2.1结构体指针初始化

2.2.2指向结构体成员运算符 “->”

2.2.3结构体指针重命名

3.结构体与数组的结合

3.1结构体数组

3.2指向结构体数组的指针

4.结构体大小

扩展:

在C语言中有多种基本数据类型(内置数据类型)(如char、int、float等)。在一般的程序中,我们总使用这些被设定好的数据类型来定义变量使用。但是这些变量除非我们主观给它们赋予联系,要不然它们都是相互独立、无内在联系的。但是在实际生活中,很多数据是有联系的、成组出现的。例如,一个人有姓名,年龄,性别,手机号等。如果我们将姓名、年龄、性别和手机号这四个数据存储在四个变量中,这四个变量在系统中没有任何联系,无法将这些数据联系起来。所以人们想要把这些数据保存为一个组合的数据,在一个变量中储存多个数据,方便使用。

所以,C语言允许用户自己建立由不同数据类型组成的组合型数据结构,就是结构体。结构体的成员变量在内存中存储地址连续。

1.结构体设计

1.1结构体类型的基本形式

struct 结构体名{成员列表;//属性};

这就是结构体类型的一般形式,”struct 结构体名” 合起来就是自定义的结构体类型,如int型,我们在声明一个int型变量a时使用 int a,如果我们想声明一个结构体变量b就用 struct 结构体名 b 。

成员列表里就是我们想组合存储在一起的各个类型的数据的声明。如我们要设计一个Student结构体类型保存学生的信息,其中包含姓名(字符串型)、年龄(int型)和成绩(int型)。

strcut Student{char name[10];//10字节 数组 存放字符int age;//4字节int score;//4字节};

但是由于每个人的姓名长短不一,尤其可能有少数民族的学生名字特别长,字符数组的长度得随之而变。所以为了避免使用字符数组存储姓名占用内存过大,或数组长度不够的问题,人们常使用指针来存储姓名字符串。( 用指针来存储姓名字符串就是系统先确认内存中有没有这个字符串,如果有的话,就将这个字符串的地址返回给指针,如果没有的话,就将这个字符串存入内存数据区,然后将这个字符串的地址返回给指针。这既不会浪费,也不用担心数组长度不够。)

strcut Student{const char *name;//4字节 指针 指向常量字符串int age;//4字节int score;//4字节};

1.2结构体设计进阶形式

如上面,我们设计好了一个Student结构体类型,如果我们想一个结构体型变量stu需要使用代码struct Student stu,这比较麻烦,struct看起来十分多余,所以我们可以使用关键字typedef,将结构体类型struct Student 重命名为student,这样用student就可以声明一个struct Student型变量,使用起来就很方便。

typedef struct Student student;//类型重命名student stu1;//声明一个struct Student型变量

可以将结构体设计与结构体类型重命名组合起来,就是如以下形式:

typedef struct Student {//合并const char* name;int age;int score;}student; //重命名为student

2.结构体的使用(成员变量访问)

2.1结构体变量

2.1.1结构体变量的初始化

结构体变量的定义可以通过 结构体类型变量名 = { 成员数据,成员数据,......} 进行。如:

student stu1 = { "张三",10,100 };//等价于struct Student stu1 = { "张三",10,100 };

赋值的操作如上,与普通类型变量一样。如 stu1 = { "李四",10,10 }; 。

假设,现在struct Student结构体类型没有被重命名,结构体的设计,变量声明和初始化就能合并在一起。如:

struct Student {const char* name;int age;int score;}stu = { "zs",9,10 };

因为在结构体类型重命名时的student与此时的变量stu处于同一部位,所以不能在重命名时同时完成变量的定义。

2.1.2结构体变量的成员变量的访问( ”. “ 成员访问符)

结构体变量的成员变量通过成员访问符"."来访问,格式:结构体变量名.成员变量名 。例如要查看的变量stu的姓名、年龄和成绩:

typedef struct Student {const char* name;int age;int score;}student;int main() {student stu = { "张三",9,10 };printf("%s,%d,%d\n", stu.name, stu.age, stu.score);}

也可以对变量值进行修改:stu.age = 10; stu.score = 11;

2.2结构体指针

2.2.1结构体指针初始化

结构体数据类型也有指针,其大小也只与操作系统有关(32位系统,4字节大小;64位系统8字节大小)。结构体指针指向结构体变量,先解引用,再通过成员运算符进行访问。如:

struct Student stu = { "张三",9,10 };struct student *p = &stu;//以变量地址对指针进行初始化printf("%s,%d,%d", (*p).name, (*p).age, (*p).score);

因为运算符“*”在执行解引用功能时的优先级,比结构体成员运算符“.”的优先级低,所以使用(*p).name满足其访问操作的顺序。

2.2.2指向结构体成员运算符 “->”

因为通过(*p).name的形式来访问结构体变量的成员变量十分麻烦,所以C语言为结构体指针专门提供了一个指向结构体成员运算符 “->”。这样就可以直接通过p->name的形式访问到变量stu的name。

2.2.3结构体指针重命名

因为使用struct student *p 的形式来声明一个结构体指针p比较麻烦,使用也可以使用typedef关键字,将struct student *重命名为一个新的类型,如将struct student *重命名为PStu。这样就可以用PStu直接声明一个指针变量。如:

student stu = { "张三",9,10 };typedef struct Student* PStu;PStu p = &stu;

然后,结构体类型指针的重命名也可以和结构体类型的重命名一样,与结构体的设计相结合:

typedef struct Student {const char* name;int age;int score;}student,*PStu;int main(){student stu = { "张三",9,10 };PStu p = &stu;}

3.结构体与数组的结合

3.1结构体数组

结构体类型与基本类型一样,也可以定义数组,用来存储结构体类型变量,数组的一个单元格保存一整个结构体变量。并且数组中变量个数也可以通过int len = sizeof(arr) / sizeof(arr[0]);计算。如:

student arr[] = { {"张三",9,10},{"李四",10,10} ,{"王五",10,11} };

3.2指向结构体数组的指针

如同普通数组,我们也可以使用相同类型指针指向数组,通过指针解引用访问数组中的结构体变量。指针变量保存着数组的首地址,指针变量每加一,指针向后偏移一个数组单元格大小(即结构体变量大小)的地址。再解引用,达到遍历整个数组。

student arr[] = { {"张三",9,10},{"李四",10,10} ,{"王五",10,11} };int len = sizeof(arr) / sizeof(arr[0]);PStu p = arr;

其访问形式有两种:(*(p + i)).name<=>(p + i)->name。

4.结构体大小

结构体变量的大小并不是我们想象的,将其成员变量所占字节大小加起来就是结构体的大小。因为CPU在读取内存时,不是按一个字节一个字节地读取的,这样太没有效率了,而是按照以2、4、8的倍数来读取的。(蓝色部分为个人理解,可跳过)

并且我认为CPU在读取时,一次读取一个完整数据,或者多个完整数据,至少是同属于一个数据的一部分。不会将其他数据与一个数据的一部分一起读取,这样很没有规则(在深入了解了CPU与内存间的关系后应该会有更好的理解)。

假设现在有char型a、int型b、long long型c在内存中连续存放着,这样只有CPU是按1字节1字节读取(上面说了不合理),或者读取的字节数大于这三个数据的总大小(如果数据特别多呢,难道每次要根据数据的实际情况来看如何读取数据吗)才能满足上面的条件。

所以,在存储变量时,为了CPU的读写方便,系统会根据一些规则来存储数据。

结构体变量在内存中的存储基本遵循以下三个规则(即内存对齐方式)(百度可知):

1. 结构体变量的首地址,必须是结构体变量中,最大基本数据类型的成员所占字节数的整数倍。

2. 结构体变量中每个成员相对结构体首地址的偏移量,都是该成员基本数据类型所占字节数的整数倍,如不够整数倍会增添空格凑数到离其大小最近的整数倍。

3. 结构体总大小为结构体变量中最大基本数据类型所占字节的整数倍,如不够整数倍会增添空格凑数到离其大小最近的整数倍。。

例如:

typedef struct Test1 {char a;//一字节short b;//两字节int c;//四字节}test1;typedef struct Test2 {short d;int e;char f;}test2;typedef struct Test3 {int g;long long h;//八字节short i;char j;}test3;test1 t1; test2 t2; test3 t3;

如果按我们原本的想法t1和t2都应该是7字节大小,t3是15字节。但并不是这样,我们会发现,t1是8字节大小,t2却是12字节大小,t3是24字节大小。

验证:假设结构体变量的起始地址都是0x000。

对于struct Test1:a在存储进内存时,其相对于其首地址的偏移量为0字节,是a的大小的整数倍,所以将a直接存入;b在存储进内存时,前方只有一字节大小的变量a,b相对于结构体首地址的偏移量才是1字节,不是b本身大小(2字节)的整数倍,为满足规则2,所以用一字节空格填补,此时再将b存入;c在存入内存时,前方有a和b,还有一字节空格,所以其偏移量为4字节,满足c本身大小的整数倍,所以将c直接接着存入。此时,结构体的总大小为8字节,结构体最大成员变量大小为4字节,满足规则3。所以结构体的大小为8字节。

对于struct Test2:d在存入内存时,相对于结构体首地址的偏移量为0字节,所以d直接存入;e在存入内存时,因为此时前方只有变量d,e相对首地址的偏移量为2字节,不是e本身大小的整数倍,所以用2字节空格来填充,再将e存入;f在存入时,相对首地址的偏移量为8字节,所以将f直接存入。此时,结构体大小为9字节,不是最大成员变量e大小的整数倍,所以填充3字节空格。所以结构体大小为12字节,并且结构体中成员变量的顺序也会影响结构体总大小。

对于struct Test3:g在存入内存时,相对于结构体首地址的偏移量为0字节,所以g直接存入;h在存入内存时,因为此时前方只有变量g,h相对首地址的偏移量为4字节,不是h本身大小的整数倍,所以用4字节空格来填充,再将h存入;i在存入时,相对首地址的偏移量为16字节,所以将i直接存入;j在存入时,相对首地址的偏移量为18字节,所以将j直接存入。此时,结构体大小为19字节,不是最大成员变量h大小的整数倍,所以填充5字节空格。所以结构体大小为24字节。

扩展:

其实,上述分析都是建立在Visual Studio平台的默认内存对齐是按8字节大小对齐的。假如我们将对齐方式修改为按4字节。则struct Test3的大小会发生改变。

#pragma pack(4)// #pragma pack(字节) 对齐方式开始 预处理指令typedef struct Test3 {int g;long long h;short i;char j;}test3;#pragma pack()//对齐方式结束 预处理指令

#pragma pack(字节) 为C语言设置内存对齐字节大小的预处理指令。为什么按4字节对齐的话,struct Test3大小变为16字节了呢?

下面我画出唯一可能的对齐方式:

它们成员变量本身的大小就有15字节了,所以只可能填充一个空格。并且,变量的顺序不能改变。

1. 这个空格,没有理由出现在 g 之前、 h 和 i 之间,因为这样肯定会使 g 和 i 相对首地址的偏移量必然不是自身大小的整数倍,也和4字节没任何关系;

2. 因为 j 的大小为1字节,所以存入 j 时肯定没必要在i和j之间填充空格;

3. 如果空格填充在 g 和 h 之间,那 h 相对首地址的偏移量为5字节,这既不是4字节,也不是 h 本身大小8字节,并且还使 i 的偏移量变成13字节,还得在 h 和 i 之间填充空格,所以这个可能性也不存在。

所以,这个空格只能是因为此时结构体大小是15字节,不够16字节,所以填充进去的。此时,16字节也是4字节的倍数。

综上,我们进行分析:在存入 g 时,是按 g 相对首地址的偏移量为 g 本身大小(4字节)的0倍进行判断的;在存入 h 时,是按偏移量为4字节的1倍进行判断的;在存入 i 时,是按偏移量为 i 本身大小6倍或4字节的3倍进行判断的;在存入 j 时,是按偏移量为 j 本身大小的14倍进行判断的。

所以,我们猜测在碰到成员变量比我们设定的4字节大时,按照4字节进行相对首地址的偏移量判断;碰到相等或小于4字节大小的成员变量时,按照成员变量的大小进行偏移量判断的;并且,当结构体成员中有比我们设定的4字节大的,结构体的总大小为我们设定的字节大小的整数倍。

下来,我们就struct Test2对我们的猜想进行验证:

当对齐方式为4字节时test2中没有比4字节大的成员变量,所以test2的大小还是12字节当对齐方式为2字节时test21中有e为4字节,所以在存储 e 时,其相对首地址的偏移量为2字节,满足猜想,所以将e之间存储在 d 之后;直接存储 f ;再填充1字节空格,将结构体总大小补充为8字节,2的四倍。

#pragma pack(4)//将对齐方式修改为4字节typedef struct Test2 {short d;int e;char f;}test2;#pragma pack()printf("%d\n", sizeof(test2));#pragma pack(2)//将对齐方式改为2字节typedef struct Test21 {//Test21与Test2完全相同short d;int e;char f;}test21;#pragma pack()printf("%d\n", sizeof(test21));

运行结果满足猜想,所以我们需要对结构体变量的存储规则进行修改(MIN表示{}中的最小值):

1. 变量首地址,必须是MIN{结构体最大基本数据类型,指定对齐方式}所占字节数的整数倍。

2. 每个成员变量相对于结构体首地址的偏移量,都是MIN{该成员变量整数倍,指定对齐方式}.

3. 结构体总大小为 MIN(结构体最大基本数据类型整数倍,指定对齐方式的整数倍}

读者自己可以在编码过程中对修改后的规则进行验证。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。