Tipo personalizado de lenguaje C

En lenguaje C, además de varios tipos de datos básicos que usamos comúnmente, también hay un tipo llamado tipo personalizado. Por ejemplo: Queremos describir a un estudiante. Este estudiante tiene nombre, sexo, edad, altura, etc. Los tipos de datos básicos por sí solos no se pueden describir completamente. En este momento, usaremos nuestro tipo personalizado para describirlo. Los tipos personalizados son estructuras, enumeraciones y uniones.

1. Estructura

Una estructura es una colección de valores, estos valores se denominan variables miembro, y cada variable miembro puede tener un tipo diferente.

1. Declaración

struct etiqueta//etiqueta aquí representa una etiqueta, no un nombre de variable

{

        lista de miembros; // variable miembro

}variable-list ; //Lista de variables, usada para definir variables. Tenga en cuenta el punto y coma aquí

Por ejemplo, para describir a una persona:

struct person
{
	char name[20];
	char sex[5];
	int age;
	char nation[20];
};

La lista de variables aquí es opcional. Las variables definidas en la lista de variables son variables globales.

2, definir e inicializar

Para definir, se puede definir directamente en la lista de variables o en la función principal.

struct person
{
	char name[20];
	char sex[5];
	int age;
	char nation[20];
}person1;//结构体变量person1

struct person person2;//结构体变量person2

int main(void)
{
	struct person person3;//结构体变量person3
    struct person person4[2];//结构体数组变量person4,有两个元素,都是结构体类型
    return 0;
}

También es muy simple de inicializar.

#include<stdio.h>

struct person
{
	char name[20];
	int age;
}person1 = {"zhangsan", 18};//结构体变量person1

struct person person2 = {"lisi", 18};//结构体变量person2

int main(void)
{
	struct person person3 = {"wangwu", 18};//结构体变量person3
	struct person person4[2] = 
	{"abc", 18, {"def", 18}};//加不加{}都可以

	return 0;
}

A veces, la estructura también estará anidada:

struct person
{
	char name[20];
	int age;
}person1 = {"zhangsan", 18};//结构体变量person1

struct people
{
	struct person person;
	char nation[20];
};

 Inicializarlo:

struct people people = { {"zhangsan",18}, "XXX" };//Inicialización de estructuras anidadas

Al declarar la estructura, tampoco podemos declararla completamente y omitir la etiqueta en este momento.

struct
{
 int a;
 char b;
 float c;
}a;

Hay un caso en el que no se puede omitir la etiqueta, es decir, cuando se usa la palabra clave typedef. La palabra clave typedef puede definir un nuevo nombre para un tipo de datos. Si se omite, se informará de un error.

typedef struct S
{
	int data[1000];
	int num;
}s;
	s s1 = { {1,2,3,4}, 1000 };

3, acceso a los miembros de la estructura

El acceso a los miembros de la estructura se debe realizar a través del operador punto (.). Ahora imprimamos estas variables inicializadas.

#include<stdio.h>

struct person
{
	char name[20];
	int age;
}person1 = {"zhangsan", 18};//结构体变量person1

struct people
{
	struct person person;
	char nation[20];
};

struct person person2 = {"lisi", 18};//结构体变量person2

int main(void)
{
	struct person person3 = { "wangwu", 18 };//结构体变量person3
	struct person person4[2] = 
	{"abc", 18, {"def", 18}};//加不加{}都可以

	struct people people = { {"zhangsan",18}, "XXX" };//嵌套结构体的初始化

	printf("%s\n", person1.name);
	printf("%d\n", person1.age);
	printf("--------------\n");
	printf("%s\n", person2.name);
	printf("%d\n", person2.age);
	printf("--------------\n");
	printf("%s\n", person3.name);
	printf("%d\n", person3.age);
	printf("--------------\n");
	printf("%s\n", person3.name);
	printf("%d\n", person3.age);
	printf("--------------\n");
	printf("%s\n", person4[1].name);
	printf("%d\n", person4[1].age);
	printf("--------------\n");
	printf("%s\n", people.person.name);
	printf("%s\n", people.nation);

	return 0;
}

 Ahora, echemos un vistazo a los miembros de la variable a los que apunta el acceso de puntero de estructura. Existe el siguiente código:

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

#include<stdio.h>

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

struct people
{
	struct person* person;
	char nation[20];
};

int main(void)
{
	struct person person = { "zhangsan", 18 };
	struct people people = { &person ,"XXX"};

	printf("%s\n", people.person->name);
	printf("%d\n", people.person->age);
	printf("%s\n", people.nation);

	return 0;
}

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

Al pasar parámetros, los parámetros deben colocarse en la pila. Al pasar un objeto de estructura, debido a que la estructura es demasiado grande, la sobrecarga del sistema cuando los parámetros se envían a la pila es relativamente grande, lo que resulta en una degradación del rendimiento. Además, al calcular el tamaño de la estructura, si es el valor de la estructura pasada, hará que el tamaño de la estructura aumente infinitamente. Por lo tanto, la estructura necesita pasar la dirección al pasar parámetros.

5, alineación de memoria de estructura

Ahora, analicemos el tamaño de la memoria de las estructuras.

5.1, mira el siguiente código, calcula su tamaño (*):

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

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

 Encontramos que el tamaño de esta estructura no es de 6 bytes como pensábamos, sino de 12 bytes.

La estructura de memoria se calcula de la siguiente manera:

1, el primer miembro está en la dirección en el desplazamiento 0 de la variable de estructura.

2. Otras variables miembro deben alinearse con direcciones que sean múltiplos enteros del árbol de alineación. Alineación = el menor de la alineación predeterminada del compilador y el tamaño del miembro. El valor predeterminado en VS es 8, y algunos predeterminados en 4.

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 propio número máximo de alineación, y el tamaño total de la estructura es un número entero de todos los números máximos de alineación (incluido el número de alineación de estructuras anidadas). .

En pocas palabras, es: en la memoria, la primera variable se coloca en la dirección inicial de 0, y el lugar donde se colocarán las otras direcciones depende del número de alineación. La alineación es el mínimo de la alineación predeterminada y el tamaño del miembro. Después de colocarse de acuerdo con el número de alineación, el tamaño de esta estructura es un múltiplo entero del número de alineación máximo.

Calcule el tamaño de la siguiente estructura anidada: 

struct S1
{
	char c1;//1
	int i;//4
};//大小为8

struct S2
{
	char c1;//1
	struct S1 s1;//8
	int d;//4
};


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

 Al calcular, no es posible sumar directamente el tamaño de las variables miembro, en el múltiplo del valor máximo del número de alineación. Por ejemplo, en el primer cálculo (*): la suma es 6, y al tomar un múltiplo entero del número de alineación (4), el resultado es 8, lo que obviamente es inconsistente con el resultado del cálculo.

Veamos el último:

struct S
{
	short s1;//2
	char c1;//1
	int s2;//4
};


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

 Si lo hace de manera competente, no necesita hacer un dibujo, puede hacer el cálculo directamente. A veces, short y char se encuentran juntos. En este momento, se puede considerar directamente como 4 tamaños.

5.2, las razones de la existencia de alineación de memoria:

1. Motivos de plataforma (motivos de trasplante): no todas las plataformas de hardware pueden acceder a ningún dato en cualquier dirección; algunas plataformas de hardware solo pueden obtener ciertos tipos de datos en ciertas direcciones, de lo contrario, se generará 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; los accesos a la memoria alineada requieren solo un acceso.

En general: La alineación de la memoria de las estructuras es la práctica de intercambiar espacio por tiempo. Esta práctica es muy común.

Cuando diseñamos la estructura, no solo debemos cumplir con la alineación, sino también ahorrar espacio, para que los miembros que ocupan el espacio pequeño se concentren lo más posible. Si cambiamos el orden en (*), por ejemplo, juntamos dos tipos de caracteres, el tamaño de esta estructura cambiará.

5.3, modifique el número de alineación predeterminado

#pragma pack(1)//设置默认对齐数为1
struct S2
{
	char c3;
	int i2;
	char c4;
};
int main()
{
	printf("%d\n", sizeof(struct S2));//6
	return 0;
}

Volver al valor predeterminado:

#pragma pack()//Desestablece el número de alineación predeterminado, restaura el valor predeterminado 

En segundo lugar, el segmento

1. Definiciones y declaraciones

Las estructuras pueden implementar campos de bits. Los campos de bits se declaran de manera similar a las estructuras. El campo de bits define el espacio ocupado por las variables miembro en la estructura (o unión) en unidades de bits. El uso de la estructura de segmentos de bits no solo ahorra espacio, sino que también facilita la operación. Por ejemplo, segmento de bits A:

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

El nombre del miembro del campo de bits va seguido de dos puntos y un número. Los números subsiguientes están en bits. Los miembros del campo de bits deben ser de la familia de enteros.

2, la asignación de memoria del segmento de bits

El espacio del campo de bits se abre en 4 bytes (int) o 1 byte (char), según sea necesario. El segmento de bits anterior A, _a es de tipo int, que se abre con 4 bytes. Entonces, ¿cuál es el tamaño de A (32 bits)?

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

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

 ¿Cómo se calcula esto?

Aquí, _a abre un tamaño de 4 bytes, pero _a solo necesita dos bits. Debajo de 32 bits, hay 30 bits más. _b necesita 5 bits, quedan 25 bits, _c necesita 10 bits, entonces quedan 15 bits, _d necesita 30 bits, los 15 bits restantes no son suficientes, solo abra 4 bytes de espacio para almacenar _b. Se supone que la figura aquí se coloca desde la izquierda.

Veamos esta pregunta (comenzando desde la derecha en VS):

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

int main(void)
{
	struct S s = { 0 };
	s.a = 10;
	s.b = 12;
	s.c = 3;
	s.d = 4;//在内存中是什么样的
    return 0;
}

3. El problema multiplataforma del segmento de bits

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

2. No se puede determinar el número máximo de bits en el segmento de bits. (Las máquinas de 16 bits tienen un máximo de 16, las máquinas de 32 bits tienen un máximo de 32 y escriben 27, lo que causará problemas en las máquinas de 16 bits.

3. No está definido si los miembros del campo de bits se asignan en la memoria de izquierda a derecha o de derecha a izquierda.

4. Cuando una estructura contiene dos segmentos de bits, y los miembros del segundo segmento de bits son demasiado grandes para acomodar los bits restantes del primer segmento de bits, no está claro si descartar los bits restantes o usarlos.

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

3. Enumeración

Enumerar es enumerar. Haz una lista de los posibles valores.

1. Definición:

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur,
 Fri,
 Sat,
 Sun
};
enum Day//星期
{
	Mon,
	Tues,
	Wed,
	Thur,
	Fri,
	Sat,
	Sun
};

int main(void)
{
	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;
}

 La enumeración Day definida anteriormente es un tipo de enumeración. Los contenidos de {} son los valores posibles del tipo de enumeración, también llamados constantes de enumeración. Todos estos valores posibles tienen valores. El valor predeterminado comienza desde 0 y se incrementa en 1. Por supuesto, también se puede asignar un valor inicial al definir.

enum Day//星期
{
 Mon,
 Tues,
 Wed,
 Thur = 6,
 Fri,
 Sat,
 Sun
};

2. Ventajas

1. Aumentar la legibilidad y 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. Evita la contaminación de nombres (encapsulación)

4. Fácil de depurar

5. Fácil de usar, puede definir múltiples constantes a la vez

3. Uso

En la declaración de cambio, si tenemos demasiadas ramas y la etiqueta después del caso es un número, en este momento, para saber qué representa el número, necesitamos encontrarlo. Pero si usamos enum, podemos reemplazar los números con nombres constantes que hacen que las personas lo sepan de un vistazo, lo que mejora enormemente la legibilidad del código.

enum Game
{
	EXIT,
	GAME_BEGIN,
	GAME_DISCONTINUE
};

int main(void)
{
	switch (1)
	{
	case GAME_BEGIN:
		printf("你开始了游戏!\n");
	case GAME_DISCONTINUE:
		printf("你中止游戏!\n");
	case EXIT:
		printf("你退出了游戏!\n");
	}

	return 0;
}

 4. Consorcio (Estado Libre Asociado)

1. Definición:

Las uniones también son un tipo personalizado especial que define variables que contienen una serie de miembros. Estos miembros comparten un espacio común, por lo que también se le llama cuerpo común. La definición de unión es similar a la de estructura.

//联合类型的声明
union Un
{
 char c;
 int i;
};
//联合变量的定义
union Un un;

2. Características:

La unión comparte un espacio, de modo que el tamaño de la variable unión, al menos el tamaño del miembro más grande, puede almacenar el miembro más grande.

#include<stdio.h>

union Un
{
	int i;
	char c;
};
union Un un;

int main(void)
{
	// 下面输出的结果是一样的吗?
	printf("%p\n", &(un.i));
	printf("%p\n", &(un.c));

    //下面输出的结果是什么?
    un.i = 0x44;
    un.c = 0x55;
    printf("%x\n", un.i);

	return 0;
}

 

De ahí podemos ver las características del consorcio: compartir un espacio. Debido a esto, al imprimir un.i, encontrará que el resultado es 0x55. Aquí se sobrescribe 0x44. Según las características de la unión, se puede utilizar para juzgar el tamaño de almacenamiento de la computadora actual.

union Un
{
	int i;
	char ch;
};

int main(void)
{
	union Un un;
	un.i = 1;
	printf("%d\n", un.ch);//1,是小端
	
	return 0;
}

3. Cálculo del tamaño de la unión

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 del número de alineación máximo, debe alinearse con un múltiplo entero del número de alineación máximo.

union Un1
{
	char c[5];//共5个元素,每个占1个字节,总的大小为5
	int i;//4
};

int main(void)
{
	printf("%d\n", sizeof(union Un1));//8,是最大对齐数4的倍数。注意不是5,这里的5是数组总的大小
	return 0;
}
union Un2
{
	short c[7];//2*7
	int i;//4
};

int main(void)
{
	printf("%d\n", sizeof(union Un2));//16
	return 0;
}

Supongo que te gusta

Origin blog.csdn.net/Naion/article/details/122686571
Recomendado
Clasificación