[Advanced C language] Custom types: structure, enumeration, union

insert image description here
foreword

About the author: Heng Chuan, who loves running , is learning C/C++, Java, Python, etc.
This article is included in the C language advanced series. The main content of this column is data storage, advanced pointers, introduction to string and memory functions, custom type structure, dynamic memory management, file operations, etc., and will be continuously updated!
Related columns Python, Java, etc. are developing, wait and see!


1. Structure

1.1 Basic knowledge of structure

A structure is a collection of values ​​called member variables. Each member of the structure can be a variable of a different type. Note: Each member of the previously learned array is a variable of the same type.

1.2 Declaration of structure

struct tag//tag结构体的名字
{
    
    
     member-list;
}variable-list;

For example to describe a student:

struct Stu
{
    
    
    //成员变量
	char name[20];//名字
	int age;//年龄
	char sex[5];//性别
	char id[20];//学号
}; //分号不能丢
//定义学生类型
struct Stu
{
    
    
	//成员变量
	char name[20];
	int age;
	float weight;
} s4, s5, s6;//s4、5、6都是学生//全局变量

int main()
{
    
    
	//int num = 0;
	//我们用类型创建变量,s1是第一个学生,s2是第二个学生,s3是第三个学生
	struct Stu s1;//局部变量
	struct Stu s2;
	struct Stu s3;

	return 0;
}

1.3 Special notices

When declaring a structure, it can be incomplete.
for example:

//匿名结构体类型
struct 
{
    
    
	char c;
	int a;
	double d;
}s1;

struct
{
    
    
	char c;
	int a;
	double d;
}* ps;

int main()
{
    
    
	//ps = &s1;//err

	return 0;
}

The above two structures omit the structure tag (stu) when declaring.
So here comes the question?

//在上面代码的基础上,下面的代码合法吗?
ps = &s1;

Warning:
The compiler will treat the above two declarations as two completely different types.
So it's illegal.

1.4 Self-referencing of structures

Is it okay to have a member in a struct that is of type the struct itself?
insert image description here
I want to connect such a linked list, so how do I connect it?

struct Node
{
    
    
	int data;
	struct Node n;
};
//可行否?
如果可以,那sizeof(struct Node)是多少?

The answer is of course not feasible

The correct way to self-reference:

struct Node
{
    
    
	int data;//4
	struct Node* next;//4/8
};

int main()
{
    
    
	struct Node n1;
	struct Node n2;
	n1.next =  &n2;

	return 0;
}

This method can be used when a structure can find another structure of the same type as itself. It is absolutely impossible for its own type to contain a variable of its own, but its own type should contain a pointer of its own type. feasible

Expand a new knowledge point

typedef struct
{
    
    
	int data;
	char c;
} S;

typedef can rename an anonymous structure to define a new name, this S is not a variable name but a type name

thinking about a problem

typedef struct 
{
    
    
	int data;
	Node* next;
}Node;

Can this work?

The answer is no, the structure needs to be in a sequence. First read the content inside the curly brackets, and then look at the content outside the curly brackets. It is wrong to use the Node here without renaming it. If you insist on using it like this , it should be written like this

typedef struct Node
{
    
    
	int data;
	struct Node* next;
}Node;

1.5 Definition and initialization of structure variables

With the structure type, how to define variables is actually very simple.
Variable definition:

struct S
{
    
    
	int a;
	char c;
}s1;//全局变量

struct S s3;//全局变量

int main()
{
    
    
	struct S s2;//局部变量
	return 0;
}

Is it okay to write like this?

of course it is possible

Initialization: assign initial values ​​​​while defining variables

struct S
{
    
    
	int a;
	char c;
}s1;

struct B
{
    
    
	float f;
	struct S s;
};

int main()
{
    
    
	//int arr[10] = {1,2,3};
	//int a = 0;
	struct S s2 = {
    
    100, 'q'};
	struct S s3 = {
    
    .c = 'r', .a = 2000};//用.操作可以指定我的顺序,其他都是默认顺序
	struct B sb = {
    
     3.14f, {
    
    200, 'w'}};//结构体里面含有结构体要再用一个大括号
	printf("%f,%d,%c\n", sb.f, sb.s.a, sb.s.c);

	return 0;
}

Struct initialization with pointers

struct S
{
    
    
	char name[100];
	int* ptr;
};

int main()
{
    
    
	int a = 100;
	struct S s = {
    
    "abcdef", NULL};

	return 0;
}

1.6 Structure memory alignment

We have mastered the basic use of structures.
Now we dive into a problem: calculating the size of a structure.
This is also a particularly popular test point: structure memory alignment
directly on the code

struct S1
{
    
    
	int a;
	char c;
};

struct S2
{
    
    
	char c1;
	int a;
	char c2;
};

struct S3
{
    
    
	char c1;
	int a;
	char c2;
	char c3;
}; 

int main()
{
    
    
	//探讨到底是下面三个答案的哪个
	//5 6 7
	//8 8 8
	//8 12 12
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	printf("%d\n", sizeof(struct S3));
	return 0;
}

The answer is 8 12 12. Why?

First, you must master the alignment rules of the structure:

  1. The first member of the structure is always placed at offset 0
  2. Starting from the second member, each subsequent member must be aligned to an integer multiple of an alignment number.
    This alignment number is: the smaller value of the size of the member itself and the default alignment number.
    Remarks:
    There is no default alignment number in the VS environment. When there is no default alignment, the alignment is the size of the member itself
  3. When all the members are stored
    , the total size of the structure must be an integer multiple of the maximum alignment number of the members.
    If it is not enough, space will be wasted for alignment.
  4. If a structure is nested, the nested structure is aligned to an integer multiple of its own maximum alignment, and the overall size of the structure is an integer multiple of all maximum alignments (including the alignment of the nested structure).
    insert image description here
    When repositioning:
    insert image description here

If you don't believe it, you can find their offset.
To find the offset, you need to use an offsetof macro

#include <stddef.h>


struct S
{
    
    
	char c;
	int a;
};

int main()
{
    
    
	struct S s = {
    
    0};
	printf("%d\n", offsetof(struct S, c));//0
	printf("%d\n", offsetof(struct S, a));//4

	return 0;
}

After understanding this rule, let's practice another

struct S2
{
    
    
	char c1;
	char c2;
	int i;
};
int main()
{
    
    
	printf("%d\n", sizeof(struct S2));
	return 0;
}

Picture explanation:
insert image description here
practice one more

struct S3
{
    
    
	double d;
	char c;
	int i;
};

insert image description here
Practice a structure nesting problem

struct S3
{
    
    
	double d;
	char c;
	int i;
};

struct S4
{
    
    
	char c1;
	struct S3 s3;
	double d;
};

int main()
{
    
    
	printf("%d\n", sizeof(struct S4));
	return 0;
}

Picture explanation:
insert image description here
Why is there memory alignment?
What does it look like if there is no memory alignment:
insert image description here
What does it look like when there is memory alignment:
insert image description here
Most of the reference materials say this:

  1. Platform reason (transplant reason):
    Not all hardware platforms can access any data at any address; some hardware platforms can only fetch certain types of data at certain addresses, otherwise a hardware exception is thrown.
  2. Performance reasons:
    data structures (especially stacks) should be aligned on natural boundaries as much as possible.
    The reason is that to access unaligned memory, the processor needs to make two memory accesses; while aligned memory accesses require only one access.

In general:

The memory alignment of the structure is the practice of exchanging space for time.

When designing the structure, we must satisfy the alignment and save space. How to do it:

Let members who occupy a small space gather together as much as possible.

//例如:
struct S1
{
    
    
	char c1;
	int i;
	char c2;
};
struct S2
{
    
    
	char c1;
	char c2;
	int i;
};

insert image description here
The members of the S1 and S2 types are exactly the same, but there are some differences in the size of the space occupied by S1 and S2.

1.7 Modify the default alignment number

We have seen the #pragma preprocessing directive before, and here we use it again to change our default alignment.

#pragma pack(8)//设置默认对齐数为8
struct S1
{
    
    
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
    
    
	char c1;
	int i;
	char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
int main()
{
    
    
	//输出的结果是什么?//结果为6
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

1.8 Structure parameter passing

Go directly to the code:

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(s);  //传结构体
	print2(&s); //传地址
	return 0;
}

Which of the above print1 and print2 functions is better?
The answer is:
the print2 function is preferred.
reason:

When a function passes parameters, the parameters need to be pushed onto the stack, which will cause system overhead in time and space.
If a structure object is passed, the structure is too large, and the system overhead of pushing the parameters on the stack is relatively large, which will lead to a decrease in performance.

Conclusion:
When the structure is passed as a parameter, the address of the structure must be passed.

2. Bit segment

After the structure is finished, we have to talk about the ability of the structure to realize the bit segment.

2.1 What is a bit segment

Bit field declarations and structures are similar, with two differences:

  1. Members of bit fields must be int, unsigned int, or signed int.
  2. A colon and a number follow the member name of a bit field.

for example:

struct A
{
    
    
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};//47个比特位

A is a bit segment type.
What is the size of segment A?

printf("%d\n", sizeof(struct A));

The print result is 8

2.2 Memory allocation for bit segments

  1. The members of the bit field can be int unsigned int signed int or char (belonging to the integer family) type

  2. The space of the bit field is opened up in the form of 4 bytes (int) or 1 byte (char) according to the need.

  3. Bit segments involve many uncertain factors, bit segments are not cross-platform, and programs that focus on portability should avoid using bit segments.

//一个例子

struct S

{
    
    
	char a : 3;
	char b : 4;
	char c : 5;
	char d : 4;
};

struct S s = {
    
     0 };

s.a = 10;

s.b = 12;

s.c = 3;

s.d = 4;

//空间是如何开辟的?

insert image description here

2.3 Cross-platform issues with bit segments

  1. It is undefined whether an int bit field is treated as signed or unsigned.

  2. The maximum number of bits in a bit field cannot be determined. (16-bit machines have a maximum of 16, 32-bit machines have a maximum of 32, written as 27, there
    will be problems on 16-bit machines.

  3. Whether members of a bit field are allocated from left to right in memory or from right to left is undefined.

  4. When a structure contains two bit segments, and the second bit segment is too large to accommodate the remaining bits of the first bit segment,
    it is uncertain whether to discard or utilize the remaining bits.

Summarize:

Compared with the structure, the bit field can achieve the same effect, but it can save space very well, but there are cross-platform problems.

2.4 Application of Bit Segment

insert image description here

3. Enumeration

Enumeration, as the name suggests, is to enumerate one by one.
List all possible values.
For example in our real life:

There are only 7 days in a week from Monday to Sunday, which can be listed one by one.
Gender: male, female, confidential, and can also be listed one by one.
There are 12 months in the month, and you can also list them one by one

Enumerations can be used here.

3.1 Definition of enumerated types

The possible values ​​of the enumeration start from 0 by default and increase by 1

enum Day//星期

{
    
    
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun

};

enum Sex//性别

{
    
    
	MALE,
	FEMALE,
	SECRET

};

enum Color//颜色

{
    
    
	RED,
	GREEN,
	BLUE

};

The enum Day , enum Sex , and enum Color defined above are all enumeration types.

The content in {} is the possible value of the enumeration type, also called enumeration constant.
These possible values ​​are all valid, starting from 0 by default and increasing by 1 at a time. Of course, the initial value can also be assigned when defining.
For example:

enum Color//颜色

{
    
    
	RED = 1,
	GREEN = 2,
	BLUE = 4

};

3.2 Advantages of enumeration

Why use enums?

We can use #define to define constants, why use enums?
Advantages of enums:

  1. Increase code readability and maintainability

  2. Compared with the identifier defined by #define, the enumeration has type checking, which is more rigorous.

  3. prevents naming pollution (encapsulation)

  4. Easy to debug

  5. Easy to use, you can define multiple constants at a time

3.3 Use of enumeration

enum Color//颜色

{
    
    
	RED = 1,
	GREEN = 2,
	BLUE = 4

};

enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。

clr = 5;               //ok??

4. Union (community)

4.1 Definition of union type

The union is also a special custom type.
The variable defined by this type also contains a series of members, and the characteristic is that these members share the same space (so the union is also called a union).
for example:

//联合类型的声明

union Un

{
    
    
	char c;
	int i;
};

//联合变量的定义

union Un un;

//计算连个变量的大小

printf("%d\n", sizeof(un));

The print result is 4

4.2 Characteristics of the union

The members of the union share the same memory space, so the size of such a joint variable is at least the size of the largest member (because the union must at least have the ability to save the largest member).

union Un
{
    
    
	char c;//1
	int i;//4
};

int main()
{
    
    
	union Un u;
	printf("%d\n", sizeof(u));
	printf("%p\n", &u);
	printf("%p\n", &(u.i));
	printf("%p\n", &(u.c));

	return 0;
}

Code result:
insert image description here
picture explanation:
insert image description here

Members of a union share the same space

come to a written test

Determine the size and endian storage of the current computer

insert image description here
This is the diagram of finding the big and small ends before, and now we will use the union to find the big and small ends

union Un
{
    
    
	char c;//1
	int i;//4
};

/int main()
{
    
    
	union Un u;
	u.i = 1;
	if (u.c == 1)
		printf("小端\n");
	else
		printf("大端\n");


	return 0;
}

4.3 Calculation of joint size

  • The size of the union is at least the size of the largest member.
  • When the maximum member size is not an integer multiple of the maximum alignment, it must be aligned to an integer multiple of the maximum alignment.

for example:

union Un1

{
    
    
	char c[5];
	int i;
};

union Un2

{
    
    
	short c[7];
	int i;
};

//下面输出的结果是什么?

printf("%d\n", sizeof(union Un1));//8

printf("%d\n", sizeof(union Un2));//16

If this blog is helpful to everyone, I hope you give Hengchuan a free like as encouragement, and comment and bookmark it, thank you! ! !
It is not easy to make, if you have any questions or give Heng Chuan's opinion, welcome to leave a message in the comment area.

Guess you like

Origin blog.csdn.net/m0_75058342/article/details/129914137