admin 管理员组文章数量: 888526
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(结构体最大基本数据类型整数倍,指定对齐方式的整数倍}
读者自己可以在编码过程中对修改后的规则进行验证。
本文标签: C语言结构体(用户自定义数据类型)
版权声明:本文标题:C语言结构体(用户自定义数据类型) 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.freenas.com.cn/free/1699078808h326819.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论