Tipos personalizados de rasgado manual: estructura, enumeración, unión - [lenguaje C]

 Antes de comenzar a estudiar, disfrutemos del paisaje de la montaña Huashan, una de las cinco montañas, para crear un buen estado de ánimo. Solo con un buen estado de ánimo podemos estudiar mejor.

Tabla de contenido

estructura

1 Declaración de estructura

1.1 Conocimientos básicos de estructura

1.2 Declaración de estructura

1.3 Avisos especiales

1.4 Autorreferencia de estructuras

1.5 Definición e inicialización de variables de estructura 

1.6 Alineación de memoria de estructura (énfasis)

1.7 Modificar el número de alineación por defecto 

1.8 Paso de parámetros de estructura

 2. Segmento de bits

2.1 ¿Qué es un segmento de bits?

2.2 Asignación de memoria para segmentos de bits

2.3 Problemas multiplataforma con segmentos de bits

3. Enumeración

3.1 Definición de tipos enumerados

3.2 Ventajas de la enumeración

 3.3 Uso de la enumeración

 4. Unión (comunidad)

4. Unión (comunidad)

4.2 Características de la unión

4.3 Cálculo del tamaño de la junta   


estructura

1 Declaración de estructura

1.1 Conocimientos básicos de estructura

Una estructura es una colección de valores llamados variables miembro. Cada miembro de la estructura puede ser una variable de un tipo diferente.

1.2 Declaración de estructura

struct tag
{
 member-list;
}variable-list;

Por ejemplo para describir a un estudiante:

struct Stu
{
 char name[20];//名字
 int age;//年龄
 char sex[5];//性别
 char id[20];//学号
}; //分号不能丢

1.3 Avisos especiales

Al declarar una estructura, puede estar incompleta. Por ejemplo:

//匿名结构体类型
struct
{
 int a;
 char b;
 float c;
}x;
struct
{
 int a;
 char b;
 float c;
}a[20], *p;

 Las dos estructuras anteriores omiten la etiqueta de estructura (tag) al declararla. Entonces aquí viene la pregunta?

//Según el código anterior, ¿es legal el siguiente código?

p = &x;

Advertencia: el compilador tratará las dos declaraciones anteriores como dos tipos completamente diferentes. Así que es ilegal.

1.4 Autorreferencia de estructuras

¿Está bien tener un miembro en una estructura que sea del tipo de la estructura misma?

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

La respuesta no es factible, porque las autorreferencias en las variables de estructura provocarán infinitos muñecos anidados. Al preguntar por el tamaño de struct Node, se incluye a sí mismo, pero se desconoce su propio tamaño, ¡así que esta forma de escribir es incorrecta!

La forma correcta de escribirlo es:

//代码2
struct Node
{
 int data;
 struct Node* next;
};

Aquí hay otra pregunta:

¿Está bien hacer referencia a los punteros cuando se cambia el nombre con typedefs? el código se muestra a continuación:

typedef struct
{
 int data;
 Node* next;
}Node;
//这样写代码,可行否?

La respuesta es no, porque el orden de ejecución del código es de arriba hacia abajo, y al renombrar typedef se le da un nuevo nombre al final ¡Es incorrecto usarlo en la estructura! La solución específica es la siguiente:

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

1.5 Definición e inicialización de variables de estructura 

Con el tipo de estructura, cómo definir variables es realmente muy simple.

struct Point
{
 int x;
 int y;
}p1; //声明类型的同时定义变量p1(第一种)
struct Point p2; //定义结构体变量p2(第二种)
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};(第三种)
struct Stu        //类型声明
{
 char name[15];//名字
 int age;      //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
 int data;
 struct Point p;
 struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化(第四种)
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化(第五种)

Los cinco métodos anteriores se han mostrado claramente en forma de código. 

1.6 Alineación de memoria de estructura (énfasis)

Hemos dominado el uso básico de las estructuras. Ahora nos sumergimos en un problema: calcular el tamaño de una estructura.

Este es también un punto de prueba particularmente popular: ¡alineación de memoria de estructura! ! !

Empecemos con un programa:

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

¿Cuál debería ser el tamaño de la estructura S1? Generalmente pensamos que son las 6 al principio.

 Entonces, ¿por qué el resultado es 12? Primero pasamos una macro offsetof (calcular el desplazamiento del miembro de la estructura en comparación con la posición inicial de la estructura). Esta macro está en el archivo de encabezado #include<stddef.h>.

#include<stddef.h>
struct S1
{
	char c1;
	int i;
	char c2;
};
int main(void)
{
	//printf("%d\n", sizeof(struct S1));
	printf("%d\n", offsetof(struct S1, c1));
	printf("%d\n", offsetof(struct S1, i)); 
	printf("%d\n", offsetof(struct S1, c2));
	return 0;
}

  El contenido de la estructura está lleno, ¿por qué seguir desperdiciando estos tres bytes?

Aprendamos:

¿Como calcular?

Primero, debe dominar las reglas de alineación de la estructura:

1. El primer miembro está en la dirección cuyo desplazamiento es 0 de la variable de estructura.

2. Otras variables miembro deben estar alineadas con una dirección que sea un múltiplo entero de un cierto número (número de alineación). Alineación = alineación predeterminada del compilador y el valor más pequeño del tamaño del miembro. El valor predeterminado en VS es 8. No hay un número de alineación predeterminado en Linux, y el número de alineación es el tamaño del miembro en sí.

3. El tamaño total de la estructura es un múltiplo entero del número máximo de alineación (cada variable miembro tiene un número de alineación).

4. Si una estructura está anidada, la estructura anidada se alinea con un múltiplo entero de su propia alineación máxima, y ​​el tamaño total de la estructura es el número entero de todas las alineaciones máximas ( incluida la alineación de la estructura anidada ) veces.

Después de aprender las reglas de alineación de memoria anteriores, debemos entender todas las preguntas anteriores, así que ahora estamos practicando una pregunta:


#include<stddef.h>
struct S1
{
	char c1;
	char c2;
	int i;
};
int main(void)
{
	printf("%d\n", sizeof(struct S1));
	return 0;
}

Hagamos un análisis concreto de la estructura anterior:  ¿Es el resultado 8? Verifiquémoslo:  Sí, creo que todos han entendido y dominado básicamente la alineación de memoria de la estructura. Entonces, ¿por qué necesitamos la alineación de la memoria?

razón:

1. Razones de la plataforma (razones para la migración): no todas las plataformas de hardware pueden acceder a ningún dato en ninguna dirección; algunas plataformas de hardware solo pueden obtener ciertos tipos de datos en ciertas direcciones; de lo contrario, se genera una excepción de hardware.

2. Razones de rendimiento: las estructuras de datos (especialmente las pilas) deben alinearse en los límites naturales tanto como sea posible. La razón es que para acceder a la memoria no alineada, el procesador necesita hacer dos accesos a la memoria, mientras que los accesos a la memoria alineada requieren solo un acceso.

En general: La alineación de la memoria de las estructuras es una práctica de intercambio de espacio por tiempo.

 A la hora de diseñar la estructura, debemos cumplir con la alineación y ahorrar espacio.

Permita que los miembros que ocupan un espacio pequeño se reúnan tanto como sea posible.

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

Los miembros de los tipos S1 y S2 son exactamente iguales, pero existen algunas diferencias en el tamaño del espacio ocupado por S1 y S2.

1.7 Modificar el número de alineación por defecto 

Hemos visto la directiva de preprocesamiento #pragma antes, y aquí la usamos nuevamente para cambiar nuestra alineación predeterminada

#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;
}

Cuando establecemos la alineación en 1, el tamaño de la memoria de la misma estructura cambia de 12 a 6.

1.8 Paso de parámetros de estructura

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;
}

 ¿Cuál de las funciones print1 e print2 anteriores es mejor?

La respuesta es: se prefiere la función print2. razón:

Cuando una función pasa parámetros, los parámetros deben colocarse en la pila, lo que provocará una sobrecarga del sistema en tiempo y espacio.

Si se pasa un objeto de estructura, la estructura es demasiado grande y la sobrecarga del sistema de empujar los parámetros en la pila es relativamente grande, lo que conducirá a una disminución en el rendimiento.

 2. Segmento de bits

2.1 ¿Qué es un segmento de bits?

Las declaraciones y estructuras de campos de bits son similares, con dos diferencias:

1. Los miembros del campo bit deben ser int, int sin signo o int con signo.

2. Hay dos puntos y un número después del nombre del miembro del segmento de bits.

struct A
{
 int _a:2;
 int _b:5;
 int _c:10;
 int _d:30;
};

A es un tipo de segmento de bit. ¿Cuál es el tamaño del segmento A? 

struct A
{
	int _a : 2;
	int _b : 5;
	int _c : 10;
	int _d : 30;
};
int main()
{
	printf("%d\n", sizeof(struct A));
		return 0;
}

 Con las preguntas de ahora, miremos hacia abajo.

2.2 Asignación de memoria para segmentos de bits

1. Los miembros del segmento de bit pueden ser de tipo int unsigned int signed int o char (perteneciente a la familia de enteros)

2. El espacio del campo de bits se abre en forma de 4 bytes (int) o 1 byte (char) según la necesidad.

3. Los segmentos de bits implican muchos factores inciertos. Los segmentos de bits no son multiplataforma. Los programas que se centran en la portabilidad deben evitar el uso de segmentos de bits.

Por ejemplo: 

//一个例子
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;
//空间是如何开辟的?

A continuación analicemos:

 Podemos ver de un vistazo la asignación de memoria del segmento de bits en vs.

2.3 Problemas multiplataforma con segmentos de bits

1. No se define si un campo de bit int se trata como un número con signo o como un número sin signo.

2. No se puede determinar el número máximo de bits en un campo de bits. (Las máquinas de 16 bits pueden tener hasta 16, las máquinas de 32 bits pueden tener hasta 32, escrito como 27, habrá problemas en las máquinas de 16 bits.

3. Aún no se ha definido si los miembros del segmento de bits se asignan de izquierda a derecha en la memoria o de derecha a izquierda.

4. Cuando una estructura contiene dos segmentos de bits, y el segundo segmento de bits es demasiado grande para acomodar los bits restantes del primer segmento de bits, no se sabe si descartar o utilizar los bits restantes.

Resumen: en comparación con la estructura, el segmento de bits puede lograr el mismo efecto y puede ahorrar espacio muy bien, pero hay problemas entre plataformas.

3. Enumeración

La enumeración, como su nombre lo indica, es enumerar uno por uno. Enumere todos los valores posibles. Por ejemplo, en nuestra vida real: Género: masculino, femenino, confidencial, también puedes enumerarlos uno por uno. Hay 12 meses en el mes, y también se pueden enumerar uno por uno.

3.1 Definición de tipos enumerados

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Sex//性别
{
 MALE,
 FEMALE,
 SECRET
};
enum Color//颜色
{
 RED,
 GREEN,
 BLUE
};

El enum Day , enum Sex y enum Color definidos anteriormente son todos tipos de enumeración. El contenido en {} es el valor posible del tipo de enumeración, también llamado constante de enumeración.

Estos posibles valores son todos válidos, empezando por 0 por defecto y aumentando a su vez en 1. Por supuesto, el valor inicial también se puede asignar al declarar el tipo de enumeración.

 Por ejemplo:

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

3.2 Ventajas de la enumeración

Podemos usar #define para definir constantes, ¿por qué usar enumeraciones?

Las ventajas de la enumeración: 1. Aumenta la legibilidad y la mantenibilidad del código 2. En comparación con el identificador definido por #define, la enumeración tiene verificación de tipos, que es más rigurosa. 3. Fácil de depurar 4. Fácil de usar, se pueden definir múltiples constantes a la vez 

 3.3 Uso de la enumeración

enum Color//颜色
{
 RED=1,
 GREEN=2,
 BLUE=4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5;               //ok??

 4. Unión (comunidad)

4. Unión (comunidad)

Unión es también un tipo personalizado especial, la variable definida por este tipo también contiene una serie de miembros, la característica es que estos miembros comparten el mismo espacio (por lo que la unión también se llama unión). Por ejemplo:

union Un
{
	char c;
	int i;
};

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

	return 0;
}

No importa a qué parte de la unión se acceda, la dirección es la misma, lo que significa que las variables en la unión comparten el mismo espacio de memoria, y no se asignará nadie para cada variable, y el tamaño de la memoria es 4, que es el más grande de la unión.

Por lo tanto, solo se puede usar un elemento en la unión al mismo tiempo, de lo contrario interferirá.

Podemos usar un fragmento de código para demostrarlo:

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

	return 0;
}

4.2 Características de la unión

Los miembros de la unión comparten el mismo espacio de memoria, por lo que el tamaño de dicha variable conjunta es al menos el tamaño del miembro más grande (porque la unión debe tener al menos la capacidad de guardar el miembro más grande). 

4.3 Cálculo del tamaño de la junta   

El tamaño de la unión es al menos el tamaño del miembro más grande.

Cuando el tamaño máximo del miembro no es un múltiplo entero de la alineación máxima, debe alinearse con un múltiplo entero de la alineación máxima. 

Vamos a practicar: 

union Un1
{
 char c[5];
 int i;
};
//下面输出的结果是什么?
printf("%d\n", sizeof(union Un1));

 ¿Cuál fue el resultado?


Eso es todo por ahora, ¡gracias por mirar! ! !

Supongo que te gusta

Origin blog.csdn.net/m0_74755811/article/details/131757185
Recomendado
Clasificación