C Language - August 5th - Structures and Variables

Table of contents

Structure:

The space occupied by the structure (regardless of memory alignment):

Structure member access:

 The combination of structure and array:

The first:

The second type:

Combination of structure and typedef keywords:

The combination of structure and pointer:

By pointer operator "*" and member accessor ".":

Through the pointer "->": 

The memory size of the structure:

The members in the structure open up space in memory and the complete process of memory alignment:

Nesting of structures:

Combination of structure and dynamic memory:

variable: 

local variable:

Lifecycle of local variables:

Global variables:

The declaration cycle of global variables:

Exercises with structures:


Structure:

Most of the data types we have come into contact with before are basic data types, such as int type and double type, and the structure is a custom data type, which can be designed by ourselves according to our needs. A structure can contain Various data types.

There are two data types in C language called aggregate data types (aggregate data type), they are structures and arrays. As mentioned earlier, an array is a collection of the same elements, and each element of the array can be accessed through a subscript.

But the structure is different. Although the structure is also a collection of some values, these values ​​are called members of the structure. The members in a structure may have different data types. For example, we want to create a structure of student information. The members inside include the student's name, age, height, gender, and test scores. Unlike arrays, each member of a structure has its own name, and members of a structure are also accessed by name.

The form of the design structure is as follows:

struct 类型名
{
成员(变量的声明);
};//分号是必须要有的

Here we use an example to gradually understand the structure:

#include<stdio.h>
struct student
{
    const char *name;
    int age;
    int score;
};
int main()
{
    struct student stu1 = {"stu1",9,99};//花括号里面的内容一一对应结构体里面的成员列表
    return 0;
}

It should be noted here that if our file is pure c code, that is, a file in the form of .c, struct must be added in front of student, but if it is a cpp file, we can directly use student

For example, here I define a structure called student, which consists of three members, namely name, age, and grade. I define a structure variable of stu1 in the main program.

The space occupied by the structure (regardless of memory alignment):

Next, let's take a look at what the structure looks like in memory:

The design of the type does not occupy memory . For example, if I use int type, int does not occupy memory space when I do not use int to define variables, but if I use int to define variables, such as int a = 0; then The int type will occupy 4 bytes of memory space, and we can also map it to a structure. Only when we use the structure type to define variables will it occupy space.

On the premise that we don't consider memory alignment, let's analyze the space occupied by a structure:

I give two structures here:

one:

struct Student
{
    char name[10];
    int age;
    int score;
}

Its structure in memory should look like this:

 

two:

struct Student
{
    const char* name;
    int age;
    int score;
};

The size of the first structure is 10 bytes in the character array plus two int types for a total of 18 bytes.

In the second structure, name is output as the address of the name string in the pointer storage structure. In the 32-bit system, the size of the pointer is 4 bytes, so the total size of the structure is 12 bytes. 

Structure member access:

So how should we access the members of the structure?

We continue to use the above structure, and define a person named stu1 in the main program to correspond to the members of the structure, and access the members through structure variables:

#include<stdio.h>
struct Student
{
    const char* name;
    int age;
    int score;
};
int main()
{
    struct Student stu1 = {"zs",12,100};
    printf("%s ",stu1.name);//通过结构体变量来进行成员的访问
    return 0;
}

operation result: 

Likewise, we now access all members of the struct:

printf("%s,%d,%d\n",stu1.name,stu1.age,stu1.score);

Note: The "." here is called member accessor. 

 The combination of structure and array:

What we wrote above is the case of only one student, stu1. If we need to store the information of multiple students when the Student structure has been defined, it will be troublesome for us to define one by one. We can use the array at this time To store student information:

The first:

Use curly braces to directly input information in the array, access the elements through the subscript of the array, and then use the member accessor to perform directional access to the members in the element:

#include<stdio.h>
struct Student
{
    const char *name;
    int age;
    int score;
};
int main()
{
    struct Student ar[] = {
   
   {"zs",10,100},{"lisi",9,99},{"ww",8,88}};
    int len = sizeof(ar) / sizeof(ar[0]);
    for(int i = 0;i < len;i++){
        printf("第%d个学生,姓名:%s,年龄:%d,成绩:%d\n",i,ar[i].name,ar[i].age,ar[i].score);
    }
    return 0;
}

The result of the operation is:

The second type:

If I have set the students before defining the structure array, it is also possible to directly store the structure variable name in the array:

struct Student stu1 = {"zs",10,100};
    struct Student stu2 = {"lisi",9,99};
    struct Student stu3 = {"ww",8,88};
    struct Student ar[] = {stu1,stu2,stu3};
    int len = sizeof(ar) / sizeof(ar[0]);
    for(int i = 0;i < len;i++){
        printf("第%d个学生,姓名:%s,年龄:%d,成绩:%d\n",i,ar[i].name,ar[i].age,ar[i].score);
    }

The result of the operation is:

Both methods can meet the requirements for accessing structure arrays. The second method has a little more code, but it is relatively neat.

Combination of structure and typedef keywords:

When we define the structure, it is not difficult to find that it is cumbersome to rewrite the struct Student every time we want to use the structure, so we can use the type renaming:

For example, we directly rename struct Student to student here:

struct Student
{
    const char* name;
    int age;
    int score;
};
typedef struct Student student;

Simplify the statement:

typedef struct Student
{
    const char* name;
    int age;
    int score;
}student;//将新类型名称添加到分号的前面

 After renaming the structure type, we can use the new type name to define the variable. When writing the statement, the struct can also be omitted, which is more concise.

The combination of structure and pointer:

By pointer operator "*" and member accessor ".":

We can perform directional access to structure members by using a pointer to the structure that defines the structure type:

    student ar[] = {
   
   {"zs",10,100},{"lisi",9,99},{"ww",8,88}};
    student *ptr = ar;
    printf("%s",(*ptr).name);

Here, the priority of the member access operator "." is higher than that of the pointer operator "*" , so we need to add parentheses when using pointers to access the structure. Similarly, if we want to access the structure array The name of the second member, we can also use the function of adding 1 to the pointer to write:

(*(ptr + 1)).name);//第二个成员的名字
(*(ptr + 2)).name);//第三个成员的名字

operation result:

Through the pointer "->": 

It should be noted here that the pointer is also dereferenced.

For example, here I need to access the name member of the first element in the structure array through a pointer:

    student ar[] = {
   
   {"zs",10,100},{"lisi",9,99},{"ww",8,88}};
    student *ptr = ar;
    printf("%s",ptr->name);

So what do I need to use pointers to access the name members in the second and third elements respectively?

It is still the knowledge of adding 1 to the pointer. Also because of the priority issue, we should add parentheses when performing directional access:

printf("%s",(ptr+1)->name);//第二个元素中name成员的访问
printf("%s",(ptr+2)->name);//第三个元素中name成员的访问

Let's output all three results, and look at the running results:

As shown in the figure, the access is successful.

We then use the typedef keyword to rename the pointer type of the structure:

student ar[] = {
   
   {"zs",10,100},{"lisi",9,99},{"ww",8,88}};
typedef struct Student *Pstu;
Pstu ptr = ar;
printf("%s\n%s\n%s\n",ptr->name,(ptr+1)->name,(ptr+2)->name);

As shown in the figure, it still shows that the compilation is successful:

We can write the renaming of the pointer in the structure of redefining the type name directly before the main program, and simplify the code:

typedef struct Student
{
    const char *name;
    int age;
    int score;
}student,*Pstu;

This method is still possible. 

Note: When we use the pointer method to access the members of the structure, the use of the pointer is preferred over the use of dereference. 

The memory size of the structure:

As mentioned above, regardless of memory alignment, the total size of a structure is the sum of the type sizes of all members in the structure, but if memory alignment is considered, the result is different.

Let's first take a look at what memory alignment is:

struct A
{
    char a;
    int b;
    short c;
    
};
struct B
{
    short c;
    char a;
    int b;
};

In the two structures of A and B, the data types of the members inside are the same, but the order of the members is different. We output the size of the two structures of A and B:

In fact, the memory occupied by the two structures is different. Why is this?

The answer is that memory alignment causes structures that declare variables in different orders to have different sizes. The compiler allocates a suitable memory space for each program in the computer, and all of this can be traced back to the CPU.

We all know that memory plays the role of a bridge in the computer. It is the memory that establishes the connection between the CPU and the external memory. The memory also temporarily stores the computing information in the CPU. Here we focus on the reading of the CPU to the memory. Take method:

The way the CPU reads the memory is in blocks. The size of the block can be 2, 4, 8, 16 bytes, which are generally integer multiples of 2. Therefore, when the CPU reads the memory, it reads one by one. , the block size is called (memory granularity) memory read granularity. Since the read memory is read in blocks, there must be memory waste.

The distribution of structure members in memory has the following three rules:

The first address of the structure variable must be an integer multiple of the number of bytes occupied by members of MIN (the largest basic data type in the structure variable, specifying the memory alignment).

The offset of each member in the structure variable relative to the first address of the structure is MIN (the basic data type of the member occupies an integer multiple of the byte tree, and the memory alignment is specified).

The total size of the structure is an integer multiple of the structure MIN (bytes occupied by the largest basic data type or memory alignment in the variable).

Note: MIN here refers to the minimum value between the two.

The first member of the structure variable is located at address 0. Let's take this structure as an example:

struct A
{
    int a;
    char b;
    short c;
    long long d;
};

In structure A, the largest basic data type is long long, occupying 8 bytes, and the rest are 4 bytes, 1 byte, and 2 bytes, as shown in the figure:

At this time, our default data type alignment is 8 bytes, and the initial address is 0. The structure starts from int a and occupies four bytes, followed by char b. As mentioned earlier, the CPU reads memory one by one. Read, so char b occupies one byte, because of memory alignment, we need to continue to add one byte of space behind, this space does not store data, it is only generated because the computer needs memory alignment Space, so at this time plus the previous int type space is a total of 6 bytes, which is an integer multiple of 2, so we will directly allocate memory space for short, which is now 8 bytes, and we will continue to add long long type later, The total size is 16 bytes. As mentioned above, the total size of the structure must be an integer multiple of the largest basic data type in the structure variable. The largest type of the structure variable is the long long type, which is 8 bytes. Our total size is 16 bytes, which is an integer multiple of 8, so the total size of this structure should be 16 bytes.

Let's take a look at the running results:

The result is correct.

Then if we add a member of integer type to the structure, the space allocated by adding up all types and counting the memory alignment should be 20, but 20 is not an integer multiple of the largest basic data type in the structure, so we should 4 more bytes of space are allocated later, for a total size of 24 bytes. We verify through the program:

The result is correct.

As we mentioned earlier, memory reading is in blocks, and there are 2, 4, 8 and other ways of memory alignment, and the memory alignment can be customized, so how to formulate the memory alignment?

#progma pack(1)//对齐方式开始

Fill in the brackets and need to specify a few bytes as the memory alignment 

For example, we use the structure A above:

struct A
{
    int a;
    char b;
    short c;
    long long d;
}

When we do not use the memory alignment custom statement, the program will determine the total size of the final structure by an integer multiple of the largest basic data type, which is 24.

If we use a custom memory alignment statement, we set it to 4 and 1 here:

At this time, as long as the member plus memory alignment is an integer multiple of 4, that is, 20. 

If the memory alignment is 1, we don’t need to allocate space behind the char type, because the memory type is originally 1, so it is the sum of the size of all member types of the structure, 19.

 When we finish using the custom memory alignment, we naturally need to end it. The end statement is:

#pragma pack()//内存对齐方式结束

#pragma pack(), like #define, is a preprocessing directive.

The members in the structure open up space in memory and the complete process of memory alignment:

Structure:

struct student
{
    int a;
    char b;
    short c;
    int e;
    long long d;
};

  

As shown in the figure: the following is the process of memory development for each member in the structure student:

1: First, open up space for the member a of the int type, with a size of 4.

2: Because the size of the int type is 4, which is an integer multiple of the char type with a size of 1, the space is opened up directly following the int type

3: The size of the short type is 2, and the size of the previous int type plus the char type is 5, which is not an integer multiple of 2, so we once again open up 1 byte of space for memory alignment. At this time, the total size is 6, which is exactly Integer multiple of 2, member c is opened after a newly opened byte

4: At this time, the total size of the front adds up to 8, which is exactly an integer multiple of the size of the member e of the int type, so it is directly developed in the back

5; the previous size adds up to 12, which is not an integer multiple of the member e of the long long type, so we open up 4 bytes of space for memory filling. At this time, the size is 16, which is exactly an integer multiple of 8, so the member d will be developed later.

6: The total size of the members of the structure is 24, we use the program to verify:

The result is correct 

Nesting of structures:

struct address
{
    const char *city;
    const char *street;
};

struct student
{
    const char *name;
    int age;
    struct address;
};

Similarly, let's analyze the process of memory allocation including memory alignment:

1: The two member types in the address structure are both pointer types, and the size of the pointer type is 4, so the total size of the address structure is 8

2: In the student structure, the member char *name is first memory-developed, the size is 4 bytes, the age member is int type, also 4 bytes, 4 is an integer multiple of 4, so it is directly connected to develop, the address structure The total size of the body is 8 bytes, and the sum of the size of the previous members is 8 bytes, which is an integer multiple. The memory is opened directly in the back, and there is no memory alignment during the period, so the total size of the student structure is 16.

We use the program to verify:

As shown in the figure, the result is correct 

Combination of structure and dynamic memory:

typedef struct student
{
    const char *name;
    int age;
};
int main()
{
    student s = {"zs",10};
    student* p = (student*)malloc(sizeof(student));
    assert(p != NULL);
    memset(p,0,sizeof(student));
    printf("%s\n",p -> name);
    free(p);
    p = NULL;
    return 0;
}

Use the malloc function to apply for a student-sized heap space, and initialize this area with 0. We will print out the initialized space belonging to the member name in the form of a string, and the result should be empty. 

operation result:

variable: 

In programming, we must distinguish the connection between each variable and figure out the difference between variables in order to use variables correctly. Variables are divided into two types:

local variable:

Variables defined inside functions:

example:

At this time, a is defined in the main program under the cpp file testarray, so a is a local variable at this time.

And on the premise that the local variable is not assigned a value when it is defined, the system will assign a random value to the local variable.

Lifecycle of local variables:

Defined in the function -> where the definition occurs -> the variable ends when the function ends

                                 Stack -> The development and release of memory in the stack

Global variables:

Variables defined outside the function:

example:

At this time, the variable a is defined outside the main function, and a is oriented to all functions in the testarray file. At this time, a is a global variable.

If the global variable is not assigned a value when it is defined, the system will automatically assign the default value (that is, 0) to the global variable.

The declaration cycle of global variables:

When the program starts, the global variables are generated; when the program ends, the global variables end.

When we define global variables in other files and modify the value of the global variables in the main program, the program will report an error, but we can use a keyword extern in the C language to perform global variables in different files Link.

From this we introduce the extern keyword:

As shown in the figure, I defined the global variable a in the my_struct file, and then tried to assign a value to the variable a in the testarray file, and the program reported an error:

​​​​​​​

But when I add this statement before the main program, the program can be compiled normally:

extern int a;

The role of the extern keyword is to link global variables in external files.

Note: If the global variable at this time is a static global variable, that is, the static keyword is added before the definition of the global variable, then the global variable has no linkable attribute and is only visible in this file. If we If the extern keyword is still used for linking, the program will report an error.

static int a;

Exercises with structures:

Directed access to structure members and combination with sorting:

Write a program that uses the institution body to store student information, and uses sort to sort the students' grades in ascending order. If the grades are the same, they are sorted in descending order by name:

#include<stdio.h>
#include<assert.h>
#include<string.h>
typedef struct Student//重新定义结构体类型名
{
    const char *name;
    int age;
    int score;
}student;
void Swap(student *p,student *q)//交换函数
{
    assert(p != NULL && q != NULL);
    student temp = *p;//因为我们需要交换的是结构体中的成员,所以临时值temp也应是我们重新定义的类型
    *p = *q;
    *q = temp;
}
void Bubble_sort(student *ar,int len)//冒泡排序(针对结构体中的成员,所以形参类型为结构体类型)
{
    assert(ar != NULL && len >= 0);
    while(len--){
        int flag = 0;
        for(int i = 0;i < len;i++){
            if(ar[i].score > ar[i + 1].score){
                flag = 1;
                Swap(&ar[i],&ar[i + 1]);
            }
            else if(ar[i].score == ar[i + 1].score){//如果成绩相同的情况下
                if(strcmp(ar[i].name,ar[i + 1].name) < 0){//使用strcmp函数,如果前一个字符串返回的整数值是小于后一个字符串的则直接进行交换
                    Swap(&ar[i],&ar[i + 1]);
                }
            }
        }
        if(!flag){
            break;
        }
    }
}
int main()
{
    student ar[] = {
   
   {"zs",10,100},{"lisi",9,100},{"ww",8,100}};
    int len = sizeof(ar) / sizeof(ar[0]);
    Bubble_sort(ar,len);
    for(int i = 0;i < len;i++){
        printf("姓名:%s,成绩:%d\n",ar[i].name,ar[i].score);
    }
    return 0;
} 

operation result:

Guess you like

Origin blog.csdn.net/weixin_45571585/article/details/126176226