初学C语言
我们先接触的都是内置的类型
比如说int char short float double long等等
这一期就来聊一聊自定义类型的知识
首先我们要知道什么是结构体
结构体就是各种值集合
这些值被称作结构体成员,这些成员可包括各种不同的类型
struct tag //这里的struct是结构体的关键字,tag是结构体标签,也就是结构体的名称 { number - list; //结构体成员列表 }veriable-list; //结构体的变量
如果结构体的标签是student,我拿student来举例子
结构体的完整声明
struct Student { char name[20];//姓名 char sex;//性别 int age;//年龄 int num;//学号 }; //这里的分号不能丢
结构体的不完全声明(匿名结构体类型)
struct { int a; char b; double c; }s; //这s不能省略
匿名结构体的特点就是没有结构体标签
但这样写用户使用时只能使用一次,也就是说只能在结构体声明时就定义变量
因为你找不到结构体标签,就相当于找不到门牌号一样,无法再对其定义一个变量
结构体的定义大致分为三种情况
<1>结构体声明的同时定义
struct Student { char name[20];//姓名 char sex[20];//性别 int age;//年龄 }s={"zhangsan","nan",20};
<2>结构体先声明,再定义
#include<stdio.h> struct Student { char name[20];//姓名 char sex;//性别 int age;//年龄 int num;//学号 }; int main() { struct Student s = { "zhangsan",'w',20,111 }; return 0; }
<3>匿名结构体定义
struct { char name[20];//姓名 char sex[20];//性别 int age;//年龄 } s = { "zhangsan","nan",20};
注意:结构体初始化与数组相同,都必须整体进行赋值。
struct Node //初始话链表 { int a; struct Node next; };
结构体的自引用就是结构体再套用自己
学过数据结构的朋友应该知道这是初始化链表
不过这一个代码有问题的
问题在于无法求出这个结构体的大小,不清楚这个结构体有多大,因为无法求出自引用的结构体有多大
所有自引用的结构体要用指针来访问
struct Node //初始话链表 { int a; struct Node* next; };
故就可以通过指针来访问每一个结点
当结构体定义且变量初始化完成后,就可以通过操作符来访问变量中的成员
当然,这里给出了两个操作符
分别是 .操作符和 -> 操作符
当使用结构体变量时,就用点操作符,当访问结构体指针变量就用箭头操作符
(1)通过结构体变量进行访问:
printf("%s\n",s.name);
(2)通过结构体指针进行访问:
printf("%s\n",ps->name);
函数的调用有时候需要传一些参数
参数的类型可以是不同的类型,可以是数组,也可以是指针
同样结构体的传参也可通过传结构体或者传指针
#include<stdio.h> struct tag { int a; char b[20]; }s1 = { 100,"abcdef" }; void print() { printf("%d", s1.a); } int main() { print(s1); return 0; }
#include<stdio.h> struct tag { int a; char b[20]; }s2 = { 100,"abcdef" }; void print(struct tag*s2) { printf("%d", s2->a); } int main() { print(&s2); return 0; }
我们要知道函数传参是形参就是实参的临时拷贝
参数是要压栈的(向系统申请空间),既然是临时拷贝,就会再次再栈上开辟空间,当实参足够大时,显然会浪费一定的空间和时间
相比较与传结构体,传指针会更好
在另外一篇文章详细讲过——C语言结构体中内存对齐的问题理解
可能有人没有听过什么是位段
位段的结构类型跟结构体有些类似可以类似结构体去学习
也可以说
位段是结构体特殊的实现
相较于结构体,位段的声明有两点不同
<1>规定位段的成员的必须是int,unsigned int ,signed int (但是写成char类型也没什么大的问题)
<2>位段的成员后面有一个冒号和一个数字
struct A //位段的声明 { int _a : 2; int _b : 5; int _c : 10; int _d : 30; };
#include<stdio.h> struct A { int a : 2; int b : 5; int c : 10; int d : 30; }; int main() { printf("大小是%d字节", sizeof(struct A)); return 0; }
为什么位段的大小是八个字节呢?
成员内包含的数字代表的是这个成员需要利用的内存,单位是bit。
位段成员申请内存时都是以四个字节或者一个字节单位(当成员是char类型时)
int a : 2; //申请4个字节,也就是32个bit位,利用两个还剩30个
int b : 5; //利用5个,还剩25个
int c : 10; //利用10个,还剩15个
int d : 30; //这里的十五不够,所以再申请了4个字节
最终的结果就是八字节
但问题是,变量d利用的空间是留下的15个bit加上重新申请的空间呢
这个结果在不同的环境的结果是不同的,所以位段的跨平台性比较差
位段使用的前提是你知道存储的内存大概有多大
就比如说年龄
十个bit位0111111111,最大值就可以达到1023
就不需要再申请一次利用一个int类型的空间大小
这就达到了节省内存的作用,存在即合理
位段的应用
struct A { char a : 3; char b : 4; char c : 5; }; main() { struct A s = { 0 }; s.a = 10; s.b = 12; s.c = 3; return 0; }
1.int位段被当成有符号数还是无符号数是不确定的,有时候系统会自动转化为无符号整形。
2.位段中最大位的数目不能确定。(因为在早期的16位机器int最大16,而32位机器int最大32)
3.位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
意思当内存一个字节00000000,存入01010,可能会出现00001010或者01010000
4.当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是 舍弃剩余的位还是利用,这是不确定的。
枚举类型适用于可以一一列举的类型
比如说星期、性别
enum Day //星期 { //枚举的可能取值 Mon, Tues, Wed, Thir, Fri, Sta, Sun };
enum Sex //性别 { MALE,//0 FEMALE,//1 SECRET//2 };
枚举类型是有初始值的
如果你没赋予它初始值,就会默认从零开始,依次加一
#include<stdio.h> enum Sex //性别 { MALE = 4, FEMALE=10, SECRET// }; main() { printf("%d %d %d", MALE,FEMALE,SECRET); return 0; }
可以看到,其实默认的值是可以改的
当某个成员被赋予某个值的时候,后续的成员就在此基础上加一
1.相较于数字,枚举增加代码的可读性和可维护性
2.和#define定义的标识符比较枚举有类型检查,更加严谨。
3.防止了命名污染(封装)
4.便于调试
5.使用方便,一次可以定义多个常量
联合体类型也叫共用体
union Un { int a; char b; };
union是联合体关键字,Un是联合体的标签
共用体,顾名思义,这里的共用就是公用内存
内存的也可被折叠
#include<stdio.h> union Un { char c; int i; }; int main() { union Un u = {0}; printf("%d\n", sizeof(u)); printf("%p\n", &u); printf("%p\n", &(u.c)); printf("%p\n", &(u.i)); return 0; }
他们有相同的地址
c和i存放在同一块内存空间上,修改c或者i都会影响到另一个成员。
<1>联合体内存大小是最大成员的大小
<2>最大成员的大小如果不是最大对齐数的整数倍,就会对齐到最大对齐数的整数倍
(联合体也存在内存对齐)
#include<stdio.h> union Un1 { char c[5]; int i; }; //Un1成员最大成员大小5,最大对齐数是4,所以Un1的大小是8; union Un2 { char c[7]; int i; }; //Un2成员最大成员大小7,最大对齐数是4,所以Un2的大小是8; int main() { printf("%d\n", sizeof(union Un1)); printf("%d\n", sizeof(union Un2)); return 0; }