Custom types - structures, enumerations, unions (detailed, really too detailed)

1. Structure

1.1 What is a structure, how does the structure declare and define variables?

A structure is a collection of values ​​called member variables. Each member of the structure can be a variable of a different type.
struct variable name
{
     Different types of member variables;
}; Be sure to add a semicolon after the curly braces

Declare the structure: 

First of all, the keyword used to declare the structure is struct . When we want to describe an object, we need multiple variables of different types. For example, to declare a structure variable of a student, we need many different types of member variables. Define a name Strings are needed, single-precision floating-point numbers are needed to define age and height, and arrays are needed to define student ID numbers. These are all member variables about students, and these are the structures we declare.

#include<stdio.h>

struct student    //定义结构体时是在主函数外部定义的
{
	char name[10];//学生姓名;
	int id[15];   //学生学号
	int age;      //年龄
	float hight;  //身高
};


 
int main()
{
	return 0;
}

Define the structure variable: 

After the structure definition of the above code is completed, the above is struct student which is equivalent to a type, and we can directly use this type when creating variables in the main function . When assigning an initial value to a structure object, the method we use is (object name + . ). For example, we define a variable to represent class A, define variable B to represent class B, etc. There are two ways to create objects at the same time.


The first:

Define and initialize variables in the main function

#include<stdio.h>

struct student
{
	char name[10];
	int id[8];
	int age;
	float hight;
};


 
int main()
{
	int i = 0;
	struct student A = { "LiHua",{2,0,2,3,0,7,1,3},18,175.5 };
	printf("名字是%s 年龄是%d 身高是%.2f ", A.name, A.age, A.hight);
	printf("学号是");
	for (i = 0; i < 8; i++)
	{
		printf("%d", A.id[i]);//因为学生学号是整型数组,所以在打印时要用for循环遍历输出
	}

	return 0;
}

The running code is as follows:

The second type:

Declare variables while declaring the structure

#include<stdio.h>

struct student
{
	char name[10];
	int id[8];
	int age;
	float hight;
}B = { "XiaoMing",{2,0,2,3,0,7,1,3},20,180.7};


 
int main()
{
	int i = 0;
	printf("名字是%s 年龄是%d 身高是%.2f ", B.name, B.age, B.hight);
	printf("学号是");
	for (i = 0; i < 8; i++)
	{
		printf("%d", B.id[i]);//同理,因为学号是整型数组,打印整型数组要用for循环
	}
	
	return 0;
}

The running code is as follows:

1.2 Special declaration of structure (anonymous structure type)

When declaring the structure, you can declare it incompletely, which is equivalent to not having a tag name . The following code:

Anonymous structs have two limitations.


First, because there is no tag name, even if the member variables of two anonymous structures are the same, the compiler will think that they are two different types. So in general, we can only use an anonymous structure once.

As the following code:


#include<stdio.h>

struct
{
	char name[10];
	int id[8];
	int age;
	float hight;
}A;                  //声明匿名结构体
struct
{
	char name[10];
	int id[8];
	int age;
	float hight;
}*B;                 //声明匿名结构体指针
  
int  main()
{
	B = &A;          //成员变量相同,试图讲A的地址赋给指针B
	return 0;
}

It can run normally, but there is a warning. Even if the members of the two structures are the same, the compiler will still treat the above two declarations as two completely different types , so it is illegal.

Second, because the anonymous structure has no label name, variables cannot be defined in the main function;

#include<stdio.h>

struct
{
	char name[10];
	int id[8];
	int age;
	float hight;
};


int  main()
{
	struct  A = { "LHY",{2,0,2,3,0,7,1,3},19,180.6 };
         //注意是没有标签名的
	return 0;
}

 An error is reported during operation. Generally speaking, the anonymous structure has no label name, so variables cannot be defined in the main function.

1.3 Structural self-reference

The member variables in the structure store their own structure type variables 


When we directly put member variables of our own structure type in the structure, is it okay?

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

int main()
{
	int sz = sizeof(struct Node);
	printf("%d", sz);
	return 0;
}

When we try to run the code, we get an error. Adjust the code to put in your own structure type pointer

#include<stdio.h>

struct Node
{
	int data;
	struct Node* next;
};
int main()
{
	int sz = sizeof(struct Node);
	printf("%d", sz);
	return 0;
}

The running code is as follows:

 So the self-reference of the structure needs to be put into the pointer of its own structure type. Why is this?

This involves the data structure. The data structure describes the organizational structure of the data in the memory. There are related linear data structures and tree data structures. Here we will talk about the linear data structure.


If there is an order, then there is naturally an out-of-order. When the positions of the data in the memory are out of order with each other, we have to find a way to associate the data, which is equivalent to knowing one, and then knowing all.

The structure of storing a data is equivalent to a node . For example, we have five nodes storing data. The node not only stores data, but also stores the address of the next node (since it is an address, of course it is stored with a pointer), so that Through a node, you can access the next node. Such a structure is called a linked list . The data stored in the structure is called the data field, and the pointer stored in the structure is called the pointer field .

 1.3 Structure nesting

Members of a structure are nested into another structure.

For example, I put the structure variable of "Student" in the structure that declares "Teacher"; note that when referring to the structure members of Student, it is necessary to use .



struct Student
{
	char name[20];
	int age;
};
struct Teacher
{
	char name[20];
	int age;
	struct Student stu;
};

int main()
{
	struct Teacher C = { "zhangsan",29,{"Lihua",18} };
	printf("老师名字是%s年龄是%d\n学生名字是%s年龄是%d", C.name,C.age ,C.stu.name, C.stu.age);
	return 0;
	
}

The running code is as follows:

1.4 Structure parameter passing

A structure is a type. In a function call, parameters can be passed directly to the structure or the address of the structure, but there are differences:

When the structure is passed in , any operation in the function is a modification of the copied structure and will not affect the changes of the original structure.

When the structure address is passed in, the function should use the structure pointer to receive it. The pointer points to the start address of the structure. The operation in the function will change the content in the structure. If you want to keep it unchanged, you can add the const modifier .

Such as the following code:

#include <stdio.h>
struct S
{
	int data[1000];
	int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(const 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;
}

 In general, when a function is called, is it better to pass the structure or the address of the structure?

Answer: It is good to pass the address of the structure.

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.

 *1.5 structure memory alignment

We have mastered the basic use of structures. Now we dive into a problem: calculating the size of a structure.
Take a look at the code below and try to calculate the size of the structure.
//代码1;
struct S1
{
	char c1;
	int i;
	char c2;
};

//代码2;
struct S2
{
	char c1;
	char c2;
	int i;
};

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

Both code 1 and code 2 are structures, and the contents of the members inside are the same, so are they the same size?

The running code is as follows:

 It turns out that the structure sizes of code 1 and code 2 are different, why? Because the structure has alignment rules (four points)

The alignment rules of the structure: (also take the first code as an example)
1. The first member is at the address whose offset is 0 from the structure variable.
In layman's terms, the first member of the structure occupies the starting position of the memory (starting from 0)
for example:
struct S1
{
	char c1;
	int i;
	char c2;
};

char c1 is the first member of the structure, character type, size is one byte.


2. Other member variables should be aligned to an address that is an integer multiple of a certain number (alignment number).
Alignment = Compiler's default alignment and the smaller value of the member size. (The default alignment number of VS is 8, and gcc has no default alignment number) (I use VS)
The position of the first member char c1 has been placed, as shown in the figure above.
The second member int i has a byte size of 4 bytes, which is smaller than the default alignment number and requires an integer multiple, so the corresponding position is 4 (1*4, the integer multiple is 1);

The third member is char c2, the byte is a size, which is smaller than the default alignment number, and the position corresponds to an integer multiple, which is actually the next position.


3. The total size of the structure is an integer multiple of the maximum alignment number (each member variable has an alignment number).
According to the memory size occupied by the above members, it should be 9 bytes (0~8), but the size of the structure is an integer multiple of the maximum alignment number. The maximum alignment number of the above code is 4 (because it is an int), So it should be 12 bytes.


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 the integer of all maximum alignments (including the alignment of the nested structure) times.
The maximum alignment number of S3 is 8, and the maximum alignment number of S4 is also 8. In the process of alignment, 32 is an integer multiple of 8.
So the structure size is 32 bytes.
 


#include<stdio.h>

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

struct S4
{
	char c1;
	struct S3 s3;
	double d;
};
int main()
{

    printf("S3的结构体大小为%d\n", sizeof(struct S3));
	printf("S4嵌套S3的结构体大小为%d\n", sizeof(struct S4));
	return 0;
}

 

The memory alignment of the above structure sometimes wastes space, so why is there memory alignment ?
Most of the references say something like this:
1. Platform reason ( transplant reason ) :
Not all hardware platforms can access arbitrary 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.
Take a simple example:
Sample code:
struct S1
{
 char c1;
 int i;
 char c2;
};
struct S2
{
 char c1;
 char c2;
 int i;
};

Run the code: 

 1.6 Modify the default alignment number

Using  the #pragma preprocessing directive, we can change our default alignment.

#include <stdio.h>
#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()
{
	//输出的结果是什么?
	printf("%d\n", sizeof(struct S1));
	printf("%d\n", sizeof(struct S2));
	return 0;
}

Run the code:

Conclusion: When the alignment of the structure is inappropriate, we can change the default alignment by ourselves.

2. Bit segment

2.1 What is a bit segment

Bit field declarations and structures are similar, with two differences:
1. The members of the bit field must be int , unsigned int or signed int .
2. There is a colon and a number after the member name of the bit segment.
In the following code, A is a bit field type.
#include <stdio.h>
struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\n", sizeof(struct A));
	return 0;

}
What is the size of segment A ?
How did this come about? It depends on the memory allocation of the bit segment

2.1 Memory allocation for bit segments

1. The members of the bit segment 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. The bit segment involves many uncertain factors, the bit segment is not cross-platform , and programs that focus on portability should avoid using the bit segment.

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;

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

The space on the bit field is opened up according to four bytes (int) or one byte (char). One byte is 8 bits. The number on the bit field is the unit of bit. The storage order I follow is Little-endian storage such as char a:3 means that 8 bits in a byte occupy 3 bits

 After occupying 3 bits, the remaining 5 bits, if the space occupied by the next member does not exceed 5 bits, it will be placed in the same space, otherwise, a new space will be opened up by itself.

When assigning the initial value to the structure, the initial value is converted from decimal to binary, and then the digits opened from the bit segment corresponding to the lower bit, such as char a:3, take the last 3 digits and put them into the memory. As shown below.

Observe the changes of data in memory: as shown below

 2.2 Cross-platform issues with bit segments 

1. It is uncertain whether the int bit field is regarded as a signed number or an unsigned number.
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 , on a 16- bit machine
device will have problems.
3. Whether the members in the bit segment are allocated from left to right in memory or from right to left has not yet been defined.
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.3  Application of Bit Segment

1. The data unit can save storage space. When the program needs thousands of data units, this method is particularly important.

2. The bit segment can easily access part of an integer value, which can simplify the program source code.

Commonly used for data packet transmission 

3. Enumeration

 3.1 What is an enumeration

An enumeration, as the name implies, is an enumeration, listing the possible values ​​one by one. The keyword of the enumeration is enum
For example the following code:

List the days of the week:
enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};

List colors:

enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

The enum Day 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 valuable, starting from 0 by default and incrementing by 1 at a time
#include <stdio.h>
enum Day
{
		Mon,
		Tues,
		Wed,
		Thur,
		Fri,
		Sat,
		Sun
};
int main()
{
	printf("%d\n",Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fri);
	printf("%d\n", Sat);
	printf("%d\n", Sun);



	return 0;

}

The running code is as follows:


Of course, we can also assign an initial value. After the initial value is assigned, the subsequent value will also increase by 1.

#include <stdio.h>
enum Day
{
		Mon = 1,
		Tues,
		Wed,
		Thur,
		Fri,
		Sat,
		Sun
};
int main()
{
	printf("%d\n",Mon);
	printf("%d\n", Tues);
	printf("%d\n", Wed);
	printf("%d\n", Thur);
	printf("%d\n", Fri);
	printf("%d\n", Sat);
	printf("%d\n", Sun);



	return 0;

}


#include <stdio.h>
enum Color//颜色
{
	RED = 1,
	GREEN,
	BLUE = 4,
	Black= 256
};
int main()
{
	printf("%d\n", RED);
	printf("%d\n", GREEN);
	printf("%d\n",BLUE);
	printf("%d\n", Black);


	
	return 0;

}

The running code is as follows: 


3.2 Use of enumeration 

Only enumeration constants can be assigned to enumeration variables

#include <stdio.h>
enum Color//颜色
{
	RED = 1,
	GREEN = 2,
	BLUE = 4
};

int main()
{
	enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
	printf("%d\n", clr);
	clr = 15;               
	printf("%d\n", clr);
	return 0;
}


The running code is as follows:


3.3 Advantages of enumeration

We can use #define to define constants, why use enums? Advantages of enums:
1. Increase the readability and maintainability of the code
2. Compared with the identifier defined by #define , the enumeration has type checking, which is more rigorous.
3. Prevent naming pollution (encapsulation)
4. Easy to debug
5. Easy to use, multiple constants can be defined at a time

Four.  Union (community)

4.1 What is a consortium 

Unions are also a special custom type.
Variables defined by this type also contain a series of members, characterized by the fact that these members share the same space (so unions are also called unions).
For example the following code:
#include<stdio.h>

union Un
{
	char c;
	int i;
};
int main()
{
	printf("%d\n", sizeof(union Un));//打印联合体大小
	union Un un = { 0 };
	un.i = 0x11223344;
	un.c = 0x55;

	printf("%p\n", &un);            //打印联合体的地址
	printf("%p\n", &(un.i));        //打印成员 i 的地址
	printf("%p\n", &(un.c));        //打印成员 c 的地址

	return 0;
}

 The space of the union is opened according to the size of the largest member, and then all members share this space. For example, in the above code, int i has the largest size, and 4 bytes of space are opened, and then char c members share this space space.

 Run the code:


Because they share the same space, "one move moves the whole body", changing the content of any member will change the content of other members .

#include<stdio.h>

union Un
{
	char c;
	int i;
};
int main()
{
	union Un un = { 0 };
	un.i = 0x11223344;
	un.c = 0x55;

	return 0;
}

We can view the memory changes according to the above code execution order

union One one = { 0 };

1. Assign the initial value of the structure to 0;


 2. Assign a hexadecimal value to the structure member i


3. Assign a hexadecimal number to the member c of the structure, changing the content of member i;

 

 4.2 Calculation of the union

1. The size of the union is at least the size of the largest member.
To give a simple example:
union Un
{
	char c;
	int i;
    double x;
};
Then the size of the union is x, 8 bytes of space;

2. When the maximum member size is not an integer multiple of the maximum alignment number, it must be aligned to an integer multiple of the maximum alignment number.
To give a simple example:
#include<stdio.h>

union Un2
{
	char c[7];
	int i;
};
//下面输出的结果是什么?

int main()
{
	printf("%d\n", sizeof(union Un2));

	return 0;
}

The size of the joint should be aligned to an integer multiple of the maximum number of alignments. In the above code, the space opened up by the joint should be 7 bytes, and the maximum number of alignments to be aligned should be an integer multiple of 4 (int i). So the joint space should be 8 bytes.

 4.3 Use union to judge the big and small endian storage of the current computer

We take an integer, the initial value is 1, and take out the starting address according to the location of the big and small endian storage of the machine. If it is 1, it is little-endian storage, and if it is 0, it is big-endian storage.

 To give a simple example:

union Un1
{
	int i;      //设置一个整型
};


int main()
{
	union Un1 un = {0};
	un.i = 1; 赋初始值为1

	printf("%d\n", (*(char *)&un.i)); 强制转换char*,并取出第一个字节的内容。

	return 0;
}

Well, that’s all for today’s content, it’s not easy to make, if it’s helpful to you, please give a thumbs up to support it! If there are mistakes, please criticize and point out in the comment area, thank you.

Guess you like

Origin blog.csdn.net/LHY537200/article/details/131703324