<C language> self-defined type

1. Structure

A structure is a user-defined data type that allows data items of different types to be combined to form a larger data structure. A structure can contain multiple member variables, and each member variable can be of different data types, such as integers, characters, floating-point numbers, etc., and can even contain other structures as its members.

Complex object person: name + age + phone + address

Structure struct - describe complex objects

1.1 Declaration of structure

struct tag
{
    
    
 	member-list;
}variable-list;
  1. structIs a keyword used to declare a structure type. tagIt is an identifier for this structure type, which is used to refer to this structure type in subsequent codes.
  2. member-listRepresents a list of members of a structure, each member defined with a data type and an identifier. These members are stored sequentially inside the structure.
  3. variable-listThis part is not a necessary part of the structure, but can be used to create structure variables while defining the structure. If you don't create variables when defining the structure, you can ignore this part.

For example to describe a person:

#include <stdio.h>

// 定义结构体类型
struct Person {
    
    
    char name[50];
    int age;
    float height;
} person1, person2; // 创建两个结构体变量 person1 和 person2,注意分号不可以缺少

int main() {
    
    
    // 初始化结构体变量
    struct Person person3 = {
    
    "John Doe", 30, 1.75};
    
    // 访问结构体成员
    printf("Name: %s\n", person3.name);
    printf("Age: %d\n", person3.age);
    printf("Height: %.2f meters\n", person3.height);
    
    return 0;
}

1.2 Anonymous structure

An anonymous structure means that when defining a structure variable, omit the name of the structure, directly declare the structure variable and define its members. This form is suitable for some temporary or simple data organization needs, and does not require a separate name for the structure type. The scope of an anonymous struct is limited to the current code block.

For example:

#include <stdio.h>

int main() {
    
    
    // 定义匿名结构体变量,并直接初始化
    struct {
    
    
        float length;
        float width;
    } rectangle = {
    
    5.0, 3.0}; // 创建结构体变量并初始化
    
    // 计算矩形的面积
    float area = rectangle.length * rectangle.width;
    
    printf("Rectangle Length: %.2f\n", rectangle.length);
    printf("Rectangle Width: %.2f\n", rectangle.width);
    printf("Rectangle Area: %.2f\n", area);
    
    return 0;
}

Question, is the code below legal?

struct
{
    
    
    char book_name[20];
    char author[20];
    int price;
    char id[15];
} sb1, sb2;

struct
{
    
    
    char book_name[20];
    char author[20];
    int price;
    char id[15];
} *ps;

int main() {
    
    
    ps=&sb1;  //err,编译器会把上面的两个声明当成完全不同的两个类型。所以是非法的。
    return 0;
}

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

Summarize:

Anonymous structures are suitable for some temporary data organizations, especially when they are used within a certain scope and only in a local scope. This can omit the step of giving the structure a name, making the code more concise and clear. However, for complex data structures that need to be used in multiple functions or in multiple places, it is recommended to use a named structure type.

1.3 Self-reference of structure

The self-reference of the structure means that the structure contains pointer members pointing to the same type of structure, thus creating a self-circulating data structure

The linked list structure is realized by using the self-reference of the structure:

typedef struct Node {
    
     //对struct Node重命名为Node
    int data;
    struct Node *next; //指向下个结点的地址,这里的struct不能省略
} Node;

int main() {
    
    
    Node n;
    return 0;
}

1.4 Definition and initialization of structure variables

struct Point{
    
    
    int x;
    int y;
} p1;            //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2

//初始化:定义变量的同时赋初值。
struct Point p3 = {
    
    1, 2};

struct Stu {
    
           //类型声明
    char name[15]; //名字
    int age;       //年龄
};
struct Stu s = {
    
    "zhangsan", 20}; //初始化

Structs can also be initialized nested:

struct Node {
    
    
    int data;
    struct Point p;                 //4,5
    struct Node *next;              //NULL
} n1 = {
    
    10, {
    
    4, 5}, NULL};          //结构体嵌套初始化
struct Node n2 = {
    
    20, {
    
    5, 6}, NULL};//结构体嵌套初始化 

Initialization out of order:

struct S {
    
    
    char c;
    int a;
    float f;
};

int main() {
    
    
    struct S s = {
    
    'w', 20, 3.14f};      //顺序初始化
    printf("%c %d %f\n", s.c, s.a, s.f);
    struct S s2 = {
    
    s2.f = 3.14f, s2.c = 'w', s2.a = 10};   //不按顺序初始化
    printf("%c %d %f\n", s.c, s.a, s.f);
    return 0;
}

Note : not in order to initialize, VS can gcc compiler does not support

1.5 Access to structure members

.Members of a structure can be accessed using the dot operator ( ). The dot operator allows us to access the member variables inside the structure through the structure variable, so that we can read or modify the values ​​of these members. If the structure is a pointer type, we can use the arrow operator ( ->) to access the pointed content.

For example the following structure members:

struct Stu{
    
    
	char name[20];
	int age;
};

struct Stu s;

We can see sthat has members nameand age;

So how do we access smembers?

struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员

A pointer to a structure accesses a member of a pointed-to variable :

Sometimes what we get is not a structure variable, but a pointer to a structure.

how to access members

as follows:

struct Stu {
    
    
    char name[20];
    int age;
};

void print(struct Stu *ps) {
    
    
    printf("name = %s   age = %d\n", (*ps).name, (*ps).age);
    //使用结构体指针访问指向对象的成员
    printf("name = %s   age = %d\n", ps->name, ps->age);
}

int main() {
    
    
    struct Stu s = {
    
    "zhangsan", 20};
    print(&s);//结构体地址传参
    return 0;
}

Use (*ps)post-dereference .operation access, or ->direct access

1.6 Structure parameter passing

Structs can be passed to functions by value or by pointer. When passing by value, the function will receive a copy of the struct, and modifications to the copy will not affect the original struct. When passing by pointer, the function will receive the address of the structure and can directly modify the contents of the original structure.

For example:

struct S {
    
    
    int data[1000];
    int num;
};

void print1(struct S s) {
    
    
    //传值比较浪费空间,可能会栈溢出
    printf("%d %d %d %d\n", s.data[0], s.data[1], s.data[2], s.num);// 1 2 3 100
}

void print2(struct S *ps) {
    
    
    //两种写法  推荐第二个
    printf("%d %d %d %d\n", (*ps).data[0], (*ps).data[1], (*ps).data[2], (*ps).num);// 1 2 3 100
    printf("%d %d %d %d\n", ps->data[0], ps->data[1], ps->data[2], ps->num);        // 1 2 3 100
}

int main() {
    
    
    struct S ss = {
    
    {
    
    1, 2, 3, 4, 5}, 100};
    print1(ss);
    print2(&ss);
    return 0;
}

Which of the above print1and print2functions 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.

1.7 Structure memory alignment

The memory alignment of the structure is a process in which the compiler arranges the members of the structure according to certain byte boundaries according to specific rules. The purpose of alignment is to optimize memory access efficiency and avoid performance loss caused by data misalignment. Alignment guidelines vary by compiler and architecture

How to calculate?

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

1. The first member is at the address whose offset from the structure variable is 0.

2. Other member variables should be aligned to an address that is an integer multiple of a certain number (alignment number).

​Alignment = The smaller value between the compiler's default alignment and the member size .VS中默认的值为8

3. The total size of the structure is an integer multiple of the maximum alignment number (each member variable has an alignment number).

4. If the structure is nested, the nested structure is aligned to an integer multiple of its maximum alignment number, and the overall size of the structure is the integer of all maximum alignment numbers (including the alignment number of the nested structure) times.

Observe the result below:

#include <stddef.h>
#include <stdio.h>
struct S1 {
    
    
    char c1;
    int i;
    char c2;
};

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

int main() {
    
    
    //S1的偏移量
    printf("%d\n", offsetof(struct S1, c1));//0  c1从0开始
    printf("%d\n", offsetof(struct S1, i)); //4	 i从4开始
    printf("%d\n", offsetof(struct S1, c2));//8  c2从8开始 8+1字节=9字节,按4字节对齐=12
    
    printf("%d\n", offsetof(struct S2, c1));//0  c1从0开始
    printf("%d\n", offsetof(struct S2, c2));//1  c2从1开始  
    printf("%d\n", offsetof(struct S2, i)); //4  i从4开始  4+4=8字节,按4字节对齐=8

    printf("%d\n",sizeof(struct S1)); //12
    printf("%d\n",sizeof(struct S2)); //8
    return 0;
}

offsetofMacro - used to calculate the offset of the structure member relative to the starting position, the header file #include

practise:

struct S3 {
    
    
    double d;// 0 - 7
    char c;  // 8
    int i;   // 12-15
};

struct S4 {
    
    
    char c1;     // 0
    struct S3 s3;// 8-20   s3
    double d;    // 24-31
};

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

First, we have two structs struct S3and struct S4.

struct S3Contains three members:

  1. double d: Occupies 8 bytes (usually the size of a 64-bit floating point number).
  2. char c: Occupies 1 byte.
  3. int i: Occupies 4 bytes.

Since the alignment of member variables in a structure is usually the smaller value of the size of the member itself and the default alignment of the compiler (default 8 bytes in Visual Studio), each member needs to be aligned to 8 bytes. Integer multiple address.

So struct S3the total size of is 8 + 1 + 4 = 13bytes. However, due to alignment requirements, the total size of the structure will be adjusted to an integer multiple of the maximum alignment number, so sizeof(struct S3)the result of is 16bytes.

Next, let's see struct S4.

struct S4contains two members:

  1. char c1: Occupies 1 byte.
  2. struct S3 s3: Occupies sizeof(struct S3) = 16bytes.

In struct S4, since struct S3 s3is 16 bytes, the default alignment of the compiler is 8 bytes, so double dneeds to be aligned to 8an address that is an integer multiple of a byte. Therefore, in struct S4, double dthe offset is 24bytes.

The total byte size is 32 bytes, which is an alignment of 8, so the result is 8 bytes

Why does memory alignment exist ?

  • Platform reason (reason for porting):

    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.

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

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

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.8 Modify the default alignment number

The default alignment of structures can be modified by preprocessing directives. In most compilers, #pragma packthis can be achieved using a directive.

#pragma packDirectives allow you to specify an alignment size, thereby changing the alignment of structure members in memory. Normally, the alignment is the compiler default of one value and the smaller value of the member size, but by passing #pragma pack, you can set a different alignment.

For example, assuming the default alignment is set to 4 bytes, you can do this:

#pragma pack(4)

struct YourStruct {
    
    
    // 结构体成员
};

#pragma pack() // 重置为默认对齐数

Note that modifying the alignment may affect the structure's memory layout and performance. Usually, the compiler's default alignment is to achieve the best memory alignment and access efficiency on different platforms. If you are not sure that you need to modify the alignment, it is best to use the compiler's default settings.

If you need to modify the alignment, it is recommended to do it only when necessary, and make sure that all related codes are aware of this modification, so as not to cause unnecessary problems. When modifying the alignment, it is best to understand the specific syntax and features supported by your compiler in order to use the #pragma packinstructions correctly.

#include <stdio.h>
#pragma pack(8)//设置默认对齐数为8
//8和成员大小最小-对齐数4
struct S1 {
    
    
    char c1;   //0
    int i;     //4
    char c2;   //9    对齐4为12
};

#pragma pack() //取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1

//对齐数为1,成员大小为4,对齐数为1
struct S2 {
    
    
    char c1;   //0
    int i;     //1
    char c2;   //5      对齐数为1表示不对齐,大小为0到5=6
};

#pragma pack()//取消设置的默认对齐数,还原为默认

int main() {
    
    
    //输出的结果是什么?
    printf("%d\n", sizeof(struct S1));  //12
    printf("%d\n", sizeof(struct S2));  //6
    return 0;
}

2. Structural position segment

Struct bits are a special type of struct member that allow you to define the length of the member in bits. They are used to use memory efficiently, especially when dealing with hardware registers or binary data. Struct bitfields allow you to specify the bit width of members in the struct declaration, so that these members only occupy a specific number of bits rather than whole bytes.

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.

for example:

struct S {
    
    
    int a;
    int b;
    int c;
    int d;
};

//A是位段-其中的位其实是二进制位
struct A {
    
    
    //先开辟了4byte - 32bit
    int _a : 2; // a成员只需要2个比特位  32-2 = 30
    int _b : 5; // b成员只需要5个比特位  30-5 = 25
    int _c : 10;// c成员只需要10个比特位 25-10 = 15    剩余的15个字节怎么使用由编译器决定
    int _d : 30;// d成员只需要30个比特位  还剩15 不够用 又开辟了四个字节的空间  一共8个字节
};

int main() {
    
    
    printf("%d\n", sizeof(struct S));// 16
    printf("%d\n", sizeof(struct A));// 8
    return 0;
}

2.1 Memory allocation for bit segments

  • Members of a bit field can be int/unsigned int/signed intor char(belonging to the integer family) type
  • The space of the bit field is opened up in the form of 4 bytes (int) or 1 byte (char) according to the need.
  • Bit segments involve many uncertain factors, bit segments are not cross-platform, and programs that focus on portability should avoid using bit segments.

one example:

struct S
{
    
    
    char a : 3;  //一个字节8个比特  8-3 = 5 还有五个比特
    char b : 4;  //5-4  还剩1个比特  再开辟一个字节空间
    char c : 5;  //5  //还剩下3个比特  又开辟一个空间
    char d : 4;  //4
};

int main()
{
    
    
    printf("%d\n", sizeof(struct S)); // 3
    struct S s = {
    
    0};
    s.a = 10;  //10的二进制 1010  但是a位段位3   只能放三位  00000010
    s.b = 12;  //1100  1字节变成01100010 = 十六进制62
    s.c = 3;   //0011    00000011 - 03
    s.d = 4;   //0100    00000100 - 04
    //内存中对应62 03 04 
    return 0;
} 

insert image description here

2.2 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 can be up to 16, 32-bit machines can be up to 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 fields, and the members of the second bit field are too large to fit in the remaining bits of the first bit field, 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.

3. Enumeration

Enumeration (Enum) is a user-defined data type used to represent a set of named integer constants. Enums allow programmers to assign meaningful names to different values, making code clearer and easier to read.

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

3.1 Definition of enumerated types

The syntax is as follows:

enum enumName {
    
    
    value1,
    value2,
    value3,
    // more values...
};

In an enum, enumNameis the name of the enumerated type, value1, value2, value3, … are 枚举常量. Each constant is implicitly assigned an integer value whose value starts at 0 and increments by default. Of course, the initial value can also be assigned at the time of definition.

For example:

enum Color//颜色
{
    
    
    RED = 1,
    GREEN = 2,
    BLUE = 4
};

The size of an enum type?

The size of an enumerated type in C depends on the implementation of the compiler and the architecture of the platform. Although the C standard does not specify a specific enum type size, most compilers implement enums as integer types (usually or int) unsigned intto be able to hold all the constant values ​​defined in the enum.

Typically, the size of an enumerated type is the size of an integer type, which is usually 4 bytes (32-bit platforms) or 8 bytes (64-bit platforms).

3.2 Advantages of enumerated types

Why use enums?

We can use #defineto define constants, why use enums?

#define Red 5
#define Green 7
#define Blue 10
int main(){
    
    
	int num = Red;
	return 0;
}

After define preprocessing, the define command disappears and is replaced by a number, which is inconvenient for debugging

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. Easy to debug

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

5. Prevent naming pollution (encapsulation)

3.3 Use of enumeration

enum Color {
    
    
    //枚举的可能取值
    //每一个可能的取值是常量
    Red,
    Green,
    Blue
    //Red = 5
    //Red = 9
    //Red = 10
};

int main() {
    
     
    enum Color color = Blue;   //只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
    //enum Color color = 5;    //C语言支持, C++会有警告
    //Red = 2;   //常量不可修改
    int num = Red;
    printf("%d\n", num);               //0
    printf("%d\n", Red);               //0
    printf("%d\n", Green);             //1
    printf("%d\n", Blue);              //2
    int sum = Red + Blue;              //可以相加,但是有些编译器不支持
    printf("%d\n", sum);               //2
    printf("%d\n", sizeof(enum Color));//VS中为4
    
    return 0;
}

Note : enumeration constants cannot be modified

4. Consortium (joint body)

Union (Union) is a special data type that allows different types of data to be stored in the same memory space. Unlike a structure, members of a union share the same memory, but only one member is active at a time. The size of a union is determined by the size of the largest of its members.

The syntax for defining a union is as follows:

union unionName {
    
    
    memberType member1;
    memberType member2;
    // more members...
};

In a union, unionNameis the name of the union type, member1, member2, … are the members of the union. Each member can be a different data type, but they share the same memory space.

4.1 Characteristics of the consortium

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).

Examples are as follows:

#include <stdio.h>
union Un {
    
    
    char c;
    int i;
    double d;
};


int main() {
    
    
    union Un un;
    printf("%p\n", &un);    //000000000061FE18
    printf("%p\n", &(un.c));//000000000061FE18
    printf("%p\n", &(un.i));//000000000061FE18
    printf("%p\n", &(un.d));//000000000061FE18
    //地址相同
    printf("%d\n", sizeof(union Un));//8个字节
    return 0;
}

What is the result of the following output?

un.i = 0x11223344;
un.c = 0x55;
printf("%x\n", un.i); 

Result: 11223355

In this code, first 0x11223344assign to un.i, which sets all 4 bytes of the integer to hexadecimal 11 22 33 44. Then, 0x55assign to un.c, which just sets the first byte of the integer to hex 55. Since the first byte of the integer was previously set to 11, it becomes 55. Therefore, when un.ithe value is printed out, it will be equal to the hexadecimal one 11223355.

4.2 Calculation of union size

  • The size of a union is determined by the size of the largest data type among its members.
  • 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 Un {
    
    
    char arr[5];//5 对齐数为1
    int i;      //4
};

union Un1 {
    
    
    short s[7];//14
    int i;     //4
};

int main() {
    
    
    printf("%zu\n", sizeof(union Un)); //联合体大小为5,对齐4的倍数为8
    printf("%zu\n", sizeof(union Un1));//联合体大小为14,对齐4的倍数为16
    return 0;
}

4.3 Find the big and small end of the union

A numerical value, as long as the memory space required for storage exceeds 1 byte, it involves the issue of order

0x 11 22 33 44

Find the big and small end of the union :

int check_sys() {
    
    
    union {
    
    
        char c;
        int i;
    } u;

    u.i = 1;   //联合体公用一块空间 i的空间小端存储为 01 00 00 00
    return u.c;//return返回 c  c为1个字节 就是1  如果是大端返回0
}

int main() {
    
    
    int ret = check_sys;
    if (ret == 1)
        printf("小端\n");
    else
        printf("大端\n");
}

The size of the largest data type in the 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 Un {
    
    
    char arr[5];//5 对齐数为1
    int i;      //4
};

union Un1 {
    
    
    short s[7];//14
    int i;     //4
};

int main() {
    
    
    printf("%zu\n", sizeof(union Un)); //联合体大小为5,对齐4的倍数为8
    printf("%zu\n", sizeof(union Un1));//联合体大小为14,对齐4的倍数为16
    return 0;
}

4.3 Find the big and small end of the union

A numerical value, as long as the memory space required for storage exceeds 1 byte, it involves the issue of order

0x 11 22 33 44

Find the big and small end of the union :

int check_sys() {
    
    
    union {
    
    
        char c;
        int i;
    } u;

    u.i = 1;   //联合体公用一块空间 i的空间小端存储为 01 00 00 00
    return u.c;//return返回 c  c为1个字节 就是1  如果是大端返回0
}

int main() {
    
    
    int ret = check_sys;
    if (ret == 1)
        printf("小端\n");
    else
        printf("大端\n");
}

Guess you like

Origin blog.csdn.net/ikun66666/article/details/131818015