C结构体

结构体定义

C结构体定义方式一

在C中定义一个结构体类型用typedef:

1
2
3
4
typedef struct Student
{
int a;
}Stu;

上面的Student是标识符,Stu是变量类型(相当于(int,char等))。    
于是在声明变量的时候就可:

1
2
Stu stu1; // 方法一
struct Student stu1; // 方法二

这里的Stu实际上就是struct Student的别名。Stu==struct Student

这语句实际上完成两个操作:

1) 定义一个新的结构类型

1
2
3
4
struct Student
{  
int a;
};

分析:Student称为“tag”,即“标签”,实际上是一个临时名字,不论是否有typedef struct关键字和Student一起,构成了这个结构类型,这个结构都存在。

我们可以用struct Student varName来定义变量,但要注意,使用Student varName来定义变量是不对的,因为structStudent合在一起才能表示一个结构类型。

2) typedef为这个新的结构起了一个名字,叫Stu

1
typedef struct Student Stu;

因此,Stu实际上相当于struct Student,我们可以使用Stu varName来定义变量。

C结构体定义方式二

若采用如下方式定义

1
2
3
4
struct Student
{
int a;
}Stu;

声明定义的时候需要用到

1
struct Student stu1;

C结构体定义方式三

另外这里也可以不写Student

1
2
3
4
typedef struct
{
int a;
}Stu;

声明定义的时候,需要

1
Stu stu1;

但是不能用struct Student stu1;

C++定义结构体

但在c++里很简单,直接

1
2
3
4
struct Student
{
int a;
};

于是就定义了结构体类型Student,声明变量时直接Student stu2;

在c++中如果用typedef的话,又会造成区别:

1
2
3
4
struct Student   
{
int a;
}stu1;//stu1是一个变量

1
2
3
4
typedef struct Student2   
{
int a;
}stu2;//stu2是一个结构体类型=struct Student

使用时可以直接访问stu1.a
但是stu2则必须先stu2 s2;
然后s2.a=10;

结构体添加函数和初始化

在C语言的结构体中是不能直接定义成员函数的,这点和C++不同,但是我们可以通过定义一个函数指针的方式来指向一个方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include<stdio.h>
#include<stdlib.h>
typedef struct node
{
int a;
void(*p)(int b);
}no;

void fun(int b)
{
printf("hello,%d\n",b);
}

int main()
{
// 初始化方式一,定义的时候完成初始化
no b = { 10,fun };
b.p(b.a);

// 初始化方式二,先初始化为0
no a = {0};
// 初始化方式三,先定义后初始化,不推荐
// no a;
printf("%d\t%d\n", a.a, a.p);
a.a = 1;
a.p = fun;
a.p(a.a);
return 0;
}

运行结果:

1
2
3
hello,10
0 0
hello,1

结构体和malloc结合

例如下面代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include  <stdio.h>
#include <stdlib.h>
#include <string.h>

struct student
{
char *name;
int score;
} stu, *pstu;

int main()
{
/*为name分配指向的一段内存空间*/
stu.name = (char *)malloc(20 *sizeof(char));
memset(stu.name, 0, 20 * sizeof(char));

strcpy(stu.name, "Jimy");
stu.score = 99;

/*为pstu分配指向的一段内存空间*/
pstu= (struct student *)malloc(sizeof(struct student));
memset(pstu, 0, sizeof(struct student));

/*为name分配指向的一段内存空间*/
pstu->name = (char *)malloc(20 * sizeof(char));
memset(pstu->name, 0, 20 * sizeof(char));

strcpy(pstu->name, "Jimy");
pstu->score = 99;

/*采用另外的指针访问分配的存储空间,测试内存中内容是否改变*/
char *p = stu.name;
char *p1 = (char *)0x804a008; //具体的地址值
char *ppstu = pstu->name;
char *pp = (char *)0x804a030; //具体的地址值

/*释放的顺序要注意,pstu->name必须在pstu释放之前释放,
如果pstu先释放,那么pstu->name就不能正确的访问。
*/
free(pstu->name);
free(stu.name);
free(pstu);

/*为了防止野指针产生*/
pstu->name = NULL;
stu.name = NULL;
pstu = NULL;
p = NULL;
ppstu = NULL;

// 结构体 stu 不是指针,所以下面的会报错
// free(stu);
// stu = NULL;
return 0;
}

其中,stu不是指针,因此不能free,而stu.name是在堆上的,使用了malloc函数。而pstu是指针,先使用了malloc分配结构体类型所占用的大小,接着对pstu->name开辟了大小。因此需要两次free。

sizeof结构体大小

C/C++中不同数据类型所占用的内存大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
                     32位                 64位 

char 1 1

int 4 大多数4,少数8

short 2 2

long 4 8

float 4 4

double 8 8

指针 4 8

(单位都为字节)

结构体(struct):比较复杂,对齐问题。

联合(union):所有成员中最长的。

枚举(enum):根据数据类型。

sizeof计算单层结构体大小

运算符sizeof可以计算出给定类型的大小,对于32位系统来说,sizeof(char) = 1; sizeof(int) = 4。基本数据类型的大小很好计算,我们来看一下如何计算构造数据类型的大小。

C语言中的构造数据类型有三种:数组、结构体和共用体。

数组是相同类型的元素的集合,只要会计算单个元素的大小,整个数组所占空间等于基础元素大小乘上元素的个数

结构体中的成员可以是不同的数据类型,成员按照定义时的顺序依次存储在连续的内存空间。和数组不一样的是,结构体的大小不是所有成员大小简单的相加,需要考虑到系统在存储结构体变量时的地址对齐问题。看下面这样的一个结构体

1
2
3
4
5
6
struct stu1  
{
int i;
char c;
int j;
};

用sizeof求该结构体的大小,发现值为12。int占4个字节,char占1个字节,结果应该是9个字节才对啊,为什么呢?

先介绍一个相关的概念——偏移量。偏移量指的是结构体变量中成员的地址和结构体变量地址的差。结构体大小等于最后一个成员的偏移量加上最后一个成员的大小。显然,结构体变量中第一个成员的地址就是结构体变量的首地址。因此,第一个成员i的偏移量为0。第二个成员c的偏移量是第一个成员的偏移量加上第一个成员的大小(0+4),其值为4;第三个成员j的偏移量是第二个成员的偏移量加上第二个成员的大小(4+1),其值为5。

然而,在实际中,存储变量时地址要求对齐,编译器在编译程序时会遵循两条原则

(1)结构体变量中成员的偏移量必须是成员大小的整数倍(0被认为是任何数的整数倍)
(2)结构体大小必须是所有成员大小的整数倍,也即所有成员大小的公倍数。

上面的例子中前两个成员的偏移量都满足要求,但第三个成员的偏移量为5,并不是自身(int)大小的整数倍。编译器在处理时会在第二个成员后面补上3个空字节,使得第三个成员的偏移量变成8。结构体大小等于最后一个成员的偏移量加上其大小,上面的例子中计算出来的大小为12,满足要求。

再来看另外一个例子:

1
2
3
4
5
struct stu2
{
int k;
short t;
};

成员k的偏移量为0;成员t的偏移量为4,都不需要调整。但计算出来的大小为6,显然不是成员k大小的整数倍。因此,编译器会在成员t后面补上2个字节,使得结构体的大小变成8从而满足第二个要求。

由此可见,结构体类型需要考虑到字节对齐的情况,不同的顺序会影响结构体的大小。

对比下面两种定义顺序:

1
2
3
4
5
6
7
8
9
10
11
12
struct stu3  
{
char c1;
int i;
char c2;
}
struct stu4
{
char c1;
char c2;
int i;
}

虽然结构体stu3和stu4中成员都一样,但sizeof(struct stu3)的值为12而sizeof(struct stu4)的值为8。

sizeof计算嵌套的结构体大小

对于嵌套的结构体需要将其展开。对结构体求sizeof时,上述两种原则变为:

(1)展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
(2)结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体。

看下面的例子:

1
2
3
4
5
6
7
8
9
10
struct stu5  
{
short i;
struct
{
char c;
int j;
} ss;
int k;
}

结构体stu5的成员ss.c的偏移量应该是4,而不是2。整个结构体大小应该是16。

下述代码测试原则2:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct stu5  
{
char i;
struct
{
char c;
int j;
} ss;
char a;
char b;
char d;
char e;
char f;
}

结构体ss单独计算占用空间为8,而stu5的sizeof则是20,不是8的整数倍,这说明在计算sizeof(stu5)时,将嵌套的结构体ss展开了,这样stu5中最大的成员为ss.j,占用4个字节,20为4的整数倍。如果将ss当做一个整体,结果应该是24了。

另一个特殊的例子是结构体中包含数组,其sizeof应当和处理嵌套结构体一样,将其展开,如下例子:

1
2
3
4
5
6
struct ss  
{
float f;
char p;
int adf[3];
};

其值为20。float占4个字节,到char p时偏移量为4,p占一个字节,到int adf[3]时偏移量为5,扩展为int的整数倍,而非int adf[3]的整数倍,这样偏移量变为8,而不是12。结果是8+12=20,是最大成员float或int的大小的整数倍。

如何给结构体变量分配空间由编译器决定,以上情况针对的是Linux下的GCC。在Windows下的VC平台也是这样,至于其他平台,可能会有不同的处理。

结构体变量或结构体指针作为返回值

如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include <stdio.h>
#include<stdlib.h>
#define _CRTDBG_MAP_ALLOC
#include <crtdbg.h>
#include <vld.h>

typedef struct {
int w;
float *data;
} image;


// 以变量形式返回,返回的时候,会复制在栈上的值到主函数。
image test1()
{
int i,num = 5;
image img;
//image img = calloc(1, sizeof(image));
img.w = 3; // img 是变量,所以用 点
img.data = (float*)calloc(num, sizeof(float)); // 地址0x00139b50
for(i=0; i<num; i++)
img.data[i] = i;
return img;
}

// 以指针形式返回,要求指针必须已经在堆上分配了内存
image *test2()
{
int i,num = 5;
// 为该指针在堆上分配内存,注意必须加上这个!sizeof(image)会分配一个int的大小和一个float *指针的大小
image *img = (image*)calloc(1, sizeof(image)); // img 地址0x00139ba0
img->w = 3; // img 是指针,所以用 ->
img->data = (float*)calloc(num, sizeof(float)); // 地址0x00139be8
for(i=0; i<num; i++)
img->data[i] = i;
return img;
}

// 下面返回方法是危险的,虽然在VS中并没有报错,但是在Linux下运行会出错
image* error1()
{
int i,num = 5;
image img;
//image img = calloc(1, sizeof(image));
img.w = 3; // img 是变量,所以用 点
img.data = (float*)calloc(num, sizeof(float)); // 地址0x000c9d00
for(i=0; i<num; i++)
img.data[i] = i;
return &img; // &img 0x006ff824
}

int main(int argc, char const *argv[])
{
image img1 = test1(); // img1.data地址0x00139b50,与上面相同
image *img2 = test2(); // img2 地址 0x00139ba0,img2.data地址 0x00139be8
image *img3 = error1(); // img3地址0x006ff824,img3.data 地址 0x000c9d00
free(img1.data);
free(img2->data);
free(img2);
free(img3->data);
_CrtDumpMemoryLeaks();
return 0;
}

参考链接

C/C++ sizeof函数解析——解决sizeof求结构体大小的问题
struct和typedef struct

------ 本文结束------
坚持原创技术分享,您的支持将鼓励我继续创作!

欢迎关注我的其它发布渠道