C语言的数据类型如下图所示:
结构体
一种新的自定义类型~~
为什么需要结构体类型?
内置类型并不能表示所有的场景,比如学生群体
描述学生:name、gender、age、height
结构体类型创建
定义:
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。
结构:
struct 结构体名
{
成员列表
};
声明与定义:
例如将学生的信息定义为结构体:
struct Stu {
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
};//分号不能丢
int main(){
struct Stu s;
return 0;
}
代码含义:
- struct Stu 是结构体类型,相当于int、float,定义时不申请空间。
- Stu 作为结构体的标签
- 注意:
1.标签尽量不要省略,见名知意;
2.成员列表不能全部省略;
3.变量可以省略。 - struct Stu s通过类型创建变量,申请空间(实例化)
特殊的声明:
在声明结构的时候,可以不完全的声明(匿名结构体类型)
struct{
int a;
char b;
float c;
}x;
现在有一个小实例:
#include <stdio.h>
struct{
int a;
char b;
float c;
}x;
struct {
int a;
char b;
float c;
}*p;
int main(){
p = &x;
return 0;
}
然而代码运行有警告:
这是因为编辑器会把上面两个声明当成完全不同的两个类型。
怎么改正呢? 其实很简单
#include <stdio.h>
struct{
int a;
char b;
float c;
}x, *p;
int main(){
p = &x;
return 0;
}
结构体成员访问
成员:
结构的成员可以是变量、数组、指针,甚至是其他结构体。
点操作符( . )与指向操作符( -> )
#include <stdio.h>
struct Stu {
char name[20];
int age;
};
struct Stu s = { "xiaoming", 19 };
struct Stu *ps = &s;
int main(){
printf("name = %s age = %d\n", s.name, s.age);
printf("name = %s age = %d\n", ps->name, ps->age);
return 0;
}
结构体自引用
struct Node {
int data;
struct Node* next;
};
现在有两个错误实例:
struct Node {
int data;
struct Node next;
};
typedef struct {
int data;
Node* next;
}Node;
//这样写代码,可行否?
//不行,解决方案:
typedef struct Node {
int data;
struct Node* next;
}Node;
上述实例可得:
- 结构体的自引用要带 指针
- 结构体的自引用成员类型不能使用结构体类型的别名
结构体内存对齐
结构体的对齐规则:
- 第一个成员在与结构体变量偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = min(编译器默认的一个对齐数,该成员类型最大者) 。 VS中默认的值为8 Linux中的默认值为4 - 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
- 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处(结构体内成员变量不会和嵌套结构体的成员变量内存对齐),结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
//练习1
struct S1 {
char c1;
int i;
char c2;
};
printf("%d\n", sizeof(struct S1));//12=1+3+4+1+3
//练习2
struct S2 {
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S2));//8=1+1+2+4
//练习3
struct S3 {
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S3));//16=8+1+4+3
struct S5 {
char c;
int i;
double d;
};
cout << sizeof(S5) << endl; // 16 = 1+3+4+8
//练习4-结构体嵌套问题
struct S4
{
char c1;
struct S3 s3;
double d;
};
printf("%d\n", sizeof(struct S4)); //32 = 1+7+16+8
struct S6
{
char c1;
struct S5 s3;
double d;
};
cout << sizeof(S4) << endl; // 32 = 1+7+16+8
struct S7
{
char c1;
char c2;
int i;
double d1;
double d2;
};
cout << sizeof(S5) << endl; // 24 = 1+1+2+4+8+8
pragma pack介绍:
这是给编译器用的参数设置,有关结构体字节对齐方式设置, #pragma pack是指定数据在内存中的对齐方式。
- #pragma pack (n) 作用:C编译器将按照n个字节对齐。 (n得设置成 2 的 i 次幂(i = 0, 1, 2, 3…),如果将n写成3、5之类,编译器会按照默认字节对齐)
- #pragma pack () 作用:取消自定义字节对齐方式。
#pragma pack(1)
struct sample
{
char a;
double b;
};
#pragma pack()
注:若不用#pragma pack(1)和#pragma pack()括起来,则sample按编译器默认方式对齐(成员中size最大的那个)。即按8字节(double)对齐,则sizeof(sample) == 16。成员char a占了8个字节(其中7个是空字节);若用#pragma pack(1),则sample按1字节方式对齐sizeof(sample) == 9(无空字节),比较节省空间啦,有些场和还可使结构体更易于控制。
为什么存在内存对齐?
- 平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
- 性能原因: 数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
总结来说:
- 结构体的内存对齐是拿空间来换取时间的做法。 (没有内存对齐的话,直接将结构体中所有成员类型大小加起来。比较节省空间)
- 如果我们既要满足内存对齐,又要节省空间,如何做到: 让占用空间小的成员尽量集中在一起。
offsetof介绍:
该宏用于求结构体中一个成员在该结构体中的偏移量。
size_t offsetof( structName, memberName );
- 该宏返回结构体structName中成员memberName的偏移量。偏移量是size_t类型的。
- 第一个参数是结构体的名字
- 第二个参数是结构体成员的名字。
offsetof 的定义:
#ifdef __cplusplus
#ifdef _WIN64
#define offsetof(s,m) (size_t)( (ptrdiff_t)&reinterpret_cast<const volatile char&>((((s *)0)->m)) )
#else
#define offsetof(s,m) (size_t)&reinterpret_cast<const volatile char&>((((s *)0)->m))
#endif
#else
#ifdef _WIN64
#define offsetof(s,m) (size_t)( (ptrdiff_t)&(((s *)0)->m) )
#else
#define offsetof(s,m) (size_t)&(((s *)0)->m)
#endif
#endif /* __cplusplus */
模拟实现该宏:
#define offsetof(s,m) (size_t)&(((s *)0)->m)
s *
的含义:让编译器将0号地址单元开始的一块内存空间当成 s 的结构体进行解析
位段
什么是位段?
位段的声明和结构是类似的,但有两个不同:
- 位段的成员必须是 int、unsigned int 或signed int、char(属于整型家族类型) 。
- 位段的成员名后边有一个冒号和一个数字。
实例如下:
#include <stdio.h>
struct S {
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
int main(){
struct S s = { 0 };
s.a = 10;
s.b = 12;
s.c = 3;
s.d = 4;
printf("%d %d %d %d\n", s.a, s.b, s.c, s.d);
printf("%d\n", sizeof(struct S)); // 3
return 0;
}
程序分析图:
程序生成图
注意:
- 如果位段中的类型是有符号的,位段的高位表示符号位
- 前后类型相同,比特位能共用则共用,否则重新开辟对应类型的空间;前后类型不一致,重新开辟空间。
位段的内存分配:
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的。
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
位段的跨平台问题:
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。)
- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。
总结:
跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。
枚举
定义:
枚举顾名思义就是把可能的取值一一列举。
构造:
enum 枚举名{
标识符[=整型常数],
标识符[=整型常数],
...
标识符[=整型常数]
} 枚举变量;
如果枚举没有初始化, 即省掉”=整型常数”时, 则从第一个标识符开始, 顺次赋给标识符0, 1, 2, …。但当枚举中的某个成员赋值后, 其后的成员按 依次加1 的规则确定其值。
实例如下:
enum week{
Mon = 1,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
- enum week 是枚举类型
- { }中的内容是枚举类型的可能取值,也叫枚举常量 。
枚举的优点:
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
联合(共用体)
定义:
联合是一种特殊的自定义类型, 这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
构造:
union 共用体名{
成员1;
成员2;
...
成员n;
}变量表列;
特点:
- 同一个内存段可以用来存放几种不同类型的成员,但是在每一瞬间只能存放其中的一种,而不是同时存放几种。换句话说,每一瞬间只有一个成员起作用,其他的成员不起作用,即不是同时都在存在和起作用。
- 共用体变量中起作用的成员是最后一次存放的成员,在存入一个新成员后,原有成员就失去作用。
- 联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
- 共用体变量的地址和它的各成员的地址都是同一地址。
大小的计算:
- 联合的大小至少是最大成员的大小。
- 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
实例如下:
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
union data{
int a;
char b;
double c;
};
int main(){
printf("%d\n", sizeof(un));//4 = 1 + 3
printf("%d\n", sizeof(union data));//8 = 4 + 1 +3
un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i);//11223355(小端存储)
printf("%x\n", un.c);//55
return 0;
}
- 大段存储:低位存高地址,高位存低地址
- 小段存储:低位存低地址,高位存高地址
程序分析图:
应用实例:
- 面试题:判断当前计算机的大小端存储
int sys_check(){
int a = 1;
return *((char*)&a);
}
int main(){
int n = sys_check();
if (n == 1){
printf("小端\n");//低位存储在低地址
}
else{
printf("大端\n");
}
return 0;
}
- 大小端和操作系统是否有关系?
与操作系统没关系,是与CPU架构有关
知识点习题
- 下面两个结构体
struct One{
double d;
char c;
int i;
}
struct Two{
char c;
double d;
int i;
}
在#pragma pack(4)和#pragma pack(8)的情况下,结构体的大小分别是
A. 16 24,16 24
B. 16 20,16 20
C. 16 16,16 24
D. 16 16,24 24
正确答案:
C
- 在一个64位的操作系统中定义如下结构体:
struct st_task
{
uint16_t id;
uint32_t value;
uint64_t timestamp;
};
同时定义fool函数如下:
void fool()
{
st_task task = {};
uint64_t a = 0x00010001;
memcpy(&task,&a,sizeof(uint64_t));
printf("%11u,%11u,%11u",task.id,task.value,task.timestamp);
}
上述fool函数()程序的执行结果为:
A. 1,0,0
B. 1,1,0
C. 0,1,1
D. 0,0,1
正确答案
A
答案解析
因为字节对齐的原因,所以id占用4个字节,value和timestamp分别是4个字节、8个字节。虽然id占用四个字节的地址,但是只有低两位地址的数值有效(字节对齐的机制,即value存储时的地址对4(自身对齐值)求余应为0)。所以id为 0001 0001,高四位无效,所以为0001,value与timestamp分别为0.
- 下面代码不能正确输出hello的选项为
#include<stdio.h>
struct str_t{
long long len;
char data[32];
};
struct data1_t{
long long len;
int data[2];
};
struct data2_t{
long long len;
char *data[1];
};
struct data3_t{
long long len;
void *data[];
};
int main(void) {
struct str_t str; memset((void*)&str,0,sizeof(struct str_t));
str.len=sizeof(struct str_t)-sizeof(int);
snprintf(str.data,str.len,"hello");//VS下为_snprintf
____________________________________;
____________________________________;
return 0;
}
A. struct data3_t * pData=(struct data3_t*)&str; printf(“data:%s%s\n”,str.data,(char*)(&(pData->data[0])));
B. struct data2_t * pData=(struct data2_t*)&str; printf(“data:%s%s\n”,str.data,(char*)(pData->data[0]));
C. struct data1_t * pData=(struct data1_t*)&str;printf(“data:%s%s\n”,str.data,(char*)(pData->data));
D. struct str_t * pData=(struct str_t*)&str; printf(“data:%s%s\n”,str.data,(char *)(pData->data));
正确答案:
B
答案解析
enum string{
x1,
x2,
x3=10,
x4,
x5,
} x;
函数外部访问x等于什么?
A. 5
B. 12
C. 0
D. 随机值
正确答案: C
答案解析:
全局变量时初始化为0,局部变量时初始化为随机值。
- 在x86_64环境下,请问printf输出的结果是( 48 )
typedef union{
long i;
char j[10];
int k;
} DATE;
struct data{
int m;
DATE n;
double l;
} test;
DATE max;
printf("%d", sizeof(struct data) + sizeof(max));
答案解析:
sizeof (DATE) = 16(当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。)
sizeof(test) = 32
- 在64位系统下,默认对齐方式,针对如下结构体,下列选项正确的有
typedef struct A{
int a;
long b;
char c;
}A;
A. A x; x.a = 1; x.b = 2; *(long *)&x.a == 1; 一定成立
B. sizeof(A) = 13;
C. sizeof(A) = 24;
D. A x = { 1,2,‘a’ };A y = { 1,2,‘a’ }; memcmp(&x, &y, sizeof(A))==0
正确答案: A、C、D
- 请调节如下结构体成员变量的顺序,使之内存对齐
struct process{
uint16_t lis_port;
uint8_t protocol;
char path[4096];
char name[256];
int32_t pid;
};
正确答案:
struct process{
uint8_t protocol;
char path[4096];
char name[256];
uint16_t lis_port;
int32_t pid;
};
答案解析:
int8_t : typedef signed char;
uint8_t : typedef unsigned char;
int16_t : typedef signed short ;
uint16_t : typedef unsigned short ;
int32_t : typedef signed int;
uint32_t : typedef unsigned int;
int64_t : typedef signed long long;
uint64_t : typedef unsigned long long;
如有不同见解,欢迎留言讨论!