结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
struct tag { member-list; }variable-list;
//************* 定义学生类型
struct Stu
{//成员变量char name[20];int age;float weight;
}s4,s5,s6;//全局变量
int main()
{struct Stu s1;//局部变量struct Stu s2;struct Stu s3 = { "akai",20,50.6 };return 0;
}
在声明结构的时候,可以不完全的声明。没有名字的结构体
正常定义结构体会命名 例如: strcut Stu{...};
但是匿名结构体只能用这一次,相当于一次性用品,用一次,之后就不能用了
struct
{char c;int a;double d;
}s1,s2;
但本身这种写法是错误的
//错误例子:
//创建一个匿名结构体
struct
{char c;int a;double d;
}s1;
//再创建一个匿名结构体
struct
{char c;int a;double d;
}* ps;//创建结构体指针
int main()
{//在上面代码的基础上,下面的代码合法吗?ps = &s1;//error//编译器会把上面的两个声明当成完全不同的两个类型。//所以是非法的。return 0;
}
结构体里的 char xx[20];在初始化之后,是无法直接赋值的!!!
s1.name = "aaaa"; //error - 左值无法改变
name是一个是一个地址,地址是一个常量值,无法被改变
struct S
{char name[20];char ch;int n;
};
int main()
{struct S s1 = { "haha",'z',3 };s1.ch = 'k';s1.n = 6;//s1.name = "aaaa"; //error - 左值无法改变//name是一个是一个地址,地址是一个常量值,无法被改变return 0;
}
原因:自己类型的结构体不能包含自己类型的结构体变量 正在创建结构体Node时(其实还没有创建),在结构体成员里写了Node(不存在)
无限循环下去了,这个结构体节点大小无法求出来
//错误示范:
//struct Node
//{
// int data;
// struct Node n;
// //错误 C2079 “n”使用未定义的 struct“Node”
//
//};
正确使用:自己类型的结构体是可以包含自己类型的结构体指针(结构体自引用)
当有一些数据不是连续存放的时候(数据结构里的链表存储方式),
可以通过指针可以使得一个数据找到下一个数据
struct Node
{int data;//4struct Node* next;//4/8
};
int main()
{struct Node n1;//创建结构体类型struct Node的变量 n1struct Node n2 = { 10 ,NULL};n1.next = &n2;//n1的成员变量next指针指向了下一个数据 n2printf("%d\n", n1.next->data);//打印得到10//通过结构体变量n1里的成员变量next找到所指向的数据n2的地址,//从而通过 指针->成员变量 ,找到结构体变量n2里的成员变量data的值,10return 0;
}
正确使用
//************* 自定义类型名typedef
typedef struct
{int data;char c;
}S; //给匿名结构体类型 重命名为 Stypedef struct Student
{char name[20];int age;
}Stu;//给struct Student结构体类型 重命名为 Stu
错误示范
//错误示范
typedef struct
{int data;Node* next;//error - 报错 C2061 语法错误:标识符"Node"//此时Node还没被重命名,在这里使用会有问题
}Node;
struct Stu
{char name[20];int age;float weight;
}s4,s5;
struct B
{float f;struct Stu s;
};
struct Stu s6;
struct Stu s3 = { "lisi",27,75.8f };//定义且初始化
int main()
{struct Stu s4 = { "hehe",20,34.5f };struct Stu s1;struct Stu s2 = {"zhangsan",20,66.6f};//定义且初始化struct B sb = { 3.14f,{"wifi",33,55.3f} };结构体嵌套初始化 - 结构体里包含结构体类型的时候,用{}包含struct Stu s7 = { .weight=45.6f,.name="haha",.age=30};//不按结构体成员顺序初始化结构体变量printf("%f,%d,%s\n", sb.f, sb.s.age, sb.s.name);//3.140000,33,wifireturn 0;
}
2)
struct S
{char name[10];int* ptr;
};
int main()
{int a = 100;struct S s1 = { "abcdef",NULL };struct S s2 = { "abcdef",&a };printf("%s,%d", s2.name, *(s2.ptr));//abcdef,100return 0;
}
1.结构体的第一个成员永远都放在0偏移处。
2.从第二个成员开始,以后的每个成员都要对齐到某个对齐数的整数倍处。
这个对齐数是: 成员自身大小和默认对齐数的较小值。
*备注:
VS 环境下 默认对齐数是8
gcc 环境下 没有默认对齐数,没有默认对齐数时,对齐数就是成员自身的大小3.当成员全部存放进去后,
结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍。
如果不够,则浪费空间对齐。4.如果嵌套了结构体,嵌套的结构体成员要对齐到自己成员的最大对齐数的整数倍处。
整个结构体的大小,必须是最大对齐数的整数倍,最大对齐数包含中嵌套的结构体成员中的对齐数。
//例1
struct S
{char s[9];//0 1 2 3 4 5 6 7 8 占9字节+补7字节(9 10 11 12 13 14 15 )double d;//16 17 18 19 20 21 22 23 占8字节int n;//24 25 26 27 占4字节//16+8+4=28字节//最大为double 8字节,结构体的总大小是8的倍数,往后面补4个字节,28+4=32字节
};
int main()
{printf("%d\n", sizeof(struct S));return 0;
}//例2
struct S2
{char s[9];//0 1 2 3 4 5 6 7 8 占9字节+补3字节(9 10 11 )=12int n; //12 13 14 15 占4字节double d;//16 17 18 19 20 21 22 23 占8字节//12+4+8=24字节//最大为double 8字节,结构体的总大小是8的倍数,24刚好
};
int main()
{printf("%d\n", sizeof(struct S2));return 0;
}
多练几遍就很容易了
//练习1
struct S1
{char c1;//1 - 从(偏移量:0)开始int i;//4 - 从4的倍数(偏移量:4)开始 4-7 ,到此一共8字节char c2;//1 - 从(偏移量:8 )开始//此时一共9字节,但由于,最大的对其数为4//根据规则:结构体的总大小必须是,所有成员的对齐数中最大对齐数的整数倍。如果不够,则浪费空间对齐。//得到 12 字节
};
int main()
{printf("%d\n", sizeof(struct S1));//12return 0;
}
//练习2
struct S2
{char c1;//0char c2;//1int i;//4 -7//一共8字节,正好为最大对齐数4的倍数
};
int main()
{printf("%d\n", sizeof(struct S2));//8return 0;
}
(稍微难了一点点,多分析一下就出来了)
struct S3
{double d;// 两个结构体中,最大对齐数 8char c;int i;//一共16字节
};
struct S4
{char c1;//0struct S3 s3;//包含了://double d; //8 - 15//char c;//16//int i;//20 - 23double d;//24 - 31//共32个字节,两个结构体最大对齐数 8 的整数倍
};
int main()
{printf("%d\n", sizeof(struct S4));//32//求S4,从S4内的成员开始计算return 0;
}
1. 平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。2. 性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。总体来说: 结构体的内存对齐是拿空间来换取时间的做法
让占用空间小的成员尽量集中在一起(从而节省空间)
//让占用空间小的成员尽量集中在一起(从而节省空间)
//例如:
struct S1
{char c1;//0int i;//4-7char c2;//8//一共9个字节,最大对齐数4 的倍数为 12,所以占12个字节的空间
};
struct S2
{char c1;//0char c2;//1int i;//4-7//一共8个字节,占8个空间//比较 S1 和 S2,同样的成员变量,因为顺序不同,其实所占空间也会不同,//在这里我们创建时可以适当思考成员变量的创建顺序,//使占用空间小的成员尽量集中在一起,从而节省空间,提高效率
};
#include
struct S
{char c;//0int a;//4//一共8字节
};
int main()
{struct S s = {0};printf("%d\n", sizeof(struct S));//8printf("%d\n", offsetof(struct S, c));//0printf("%d\n", offsetof(struct S, a));//4return 0;
}
#pragma 这个预处理指令,可以改变我们的默认对齐数
#pragma pack(1) //将默认对齐数设置修改为 1#pragma pack() //恢复为默认对齐数:vs默认对齐数为8
结论: 结构在对齐方式不合适的时候,我么可以自己更改默认对齐数。
//************ 修改默认对齐数
#pragma //这个预处理指令,可以改变我们的默认对齐数
#pragma pack(1)//将默认对齐数设置修改为 1
struct S
{char c1;int i;char c2;//此时,没有对齐,直接往后创建空间,6
};
#pragma pack()//恢复为默认对齐数:vs默认对齐数为8
int main()
{printf("%d\n", sizeof(struct S));//6return 0;
}
以下print1 和 print2 函数哪个好些? 原因:
函数传参的时候,参数是需要压栈,会有时间和空间上的系统开销。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
而传地址,最多最多就是4/8字节,不会消耗太多空间。如果觉得通过地址可能会改变原来的值,可以在前面加上 const 使得 指针指向的内容无法被改变
结论:
结构体传参的时候,要传结构体的地址。
struct S
{int data[1000];int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{printf("%d\n", ps->num);
}
int main()
{//上面的 print1 和 print2 函数哪个好些?//答案是:首选print2函数。print1(s); //传结构体print2(&s); //传地址return 0;
}
位段的声明和结构是类似的,有两个不同:
1.位段的成员必须是整型家族: int、unsigned int 或signed int 或者char等
2.位段的成员名后边有一个冒号和一个数字
//位段 - 二进制比特位
struct A
{int _a : 2; int _b : 5;int _c : 10;int _d : 30;//首先int _a,一进来开辟4字节空间,也就是32个比特位//其次,_a占 2bit, _b占 5bit, _c占 10bit - 此时32位里占用了17个bit,剩余15个bit//最后,_d占 30个bit,15bit是不够存的,所以继续开辟了一个 int 8字节 ,32位来存30bit//一共用掉了8个字节
};
int main()
{//A就是一个位段类型。那位段A的大小是多少?printf("%d\n", sizeof(struct A));//8字节return 0;
}
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
//一个例子
struct S
{char a : 3;//开辟1个字节 - 8bit 开始存 3bitchar b : 4;//存4bit ,剩余 1bit不够存下一个变量了char c : 5;//又开辟了1字节 - 8bit 存 5bit,剩余3bit,不够了char d : 4;//再开辟1字节 - 8bit 存 4bit//一共开辟3字节
};
int main()
{struct S s = { 0 };//假设:(vs就是这种情况)//1、分配到的内存的比特位是从低位开始存(从右往左)//2、分配的内存剩余的比特位不够使用时,浪费掉s.a = 10;//1010 -3bit 010s.b = 12;//1100 -4bit 1100s.c = 3;//0011 -5bit 00011s.d = 4;//0100 -4bit 0100//01100010 00000011 00000100//十六进制:62 03 04 (调试查看:62 03 04 ,与设想一致)printf("%d\n", sizeof(struct S));//3字节return 0;
}
1. int 位段被当成有符号数还是无符号数是不确定的。
2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。
3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。总结:
跟结构相比,位段可以达到同样的效果,可以很好的节省空间,但是位段有跨平台的问题存在。尽量别用,用就要考虑好移植后平台问题。
vs的位段分配空间的方式:
1、分配到的内存的比特位是从低位开始存(从右往左)
2、分配的内存剩余的比特位不够使用时,浪费掉
枚举顾名思义就是一一列举。 把可能的取值一一列举。 比如我们现实生活中: 一周的星期一到星期日是有限的7天,可以一一列举。 性别有:男、女、保密,也可以一一列举。 月份有12个月,也可以一一列举 这里就可以使用枚举了。
enum Sex
{//枚举的可能取值, 默认从 0开始递增1,当然在定义的时候也可以赋初值。//枚举常量MALE, FEMALE, SECRET//如果赋初值 MALE= 5从修改的位置往后递增1//FEMALE 6//SECRET 7//MALE 0//如果赋初值 FEMALE=10, 从修改的位置往后递增1,之前的不变//SECRET 11};
int main()
{enum Sex s = 1;//在C语言中可以这样修改值,但是在c++里不行//如果语法检测不严格时可以这样的,但是最好按照枚举常量来改//例如:enum Sex s = MALE;printf("%d\n", MALE);//0printf("%d\n", FEMALE);//1printf("%d\n", SECRET);//2return 0;
}