Comencemos con los tipos de memoria del microcontrolador, hay tres de ellos:
- Flash o ROM: memoria de solo lectura, memoria no volátil del microcontrolador, que almacena el código del programa, firmware, funciones, procedimientos, operaciones, todo esto se queda ahí y no cambia durante la operación del código (puede, por supuesto, ingresar allí, pero no lo haremos, y no lo necesitamos). Durante la descarga del firmware, el cargador de arranque escribe el firmware aquí.
- SRAM o RAM: memoria de acceso aleatorio. Almacena variables cuyos valores pueden cambiar durante la operación, tenemos más que libre acceso a esta memoria y la usaremos activamente. Después de reiniciar el microcontrolador, esta memoria se borra por completo y toma valores erraticos.
- EEPROM: Memoria de solo lectura: ROM programable borrable eléctricamente, memoria no volátil asignada al usuario para almacenar valores que se pueden cambiar, pero que no se restablecerán después de un reinicio. Para datos de configuración etc, hay una lección aparte al respecto.
En un futuro próximo, solo nos interesará la memoria SRAM, en la que se almacenan las variables, de ellas hablaremos más adelante.
Sistema binario.
En el mundo digital, que también incluye al microcontrolador, la información se almacena, convierte y transmite en forma digital, es decir, en forma de ceros y unos. En consecuencia, una celda de memoria elemental solo puede recordar 0 o 1 se llama bit. Por lo tanto, pasamos al sistema binario de cálculo. ¡Vamos, recuerda la informática de la ESO! Sin entrar en detalles sobre «cómo funciona», intentemos considerar el patrón;
Binario | Decimal |
0000 | 0 |
0001 | 1 |
0010 | 2 |
0011 | 3 |
0100 | 4 |
0101 | 5 |
0110 | 6 |
0111 | 7 |
1000 | 8 |
1001 | 9 |
… | … |
10000 | 16 |
Etc. Además del patrón de dígitos y números crecientes, hay uno más: observe más de cerca los números en el sistema binario con todos los ceros a la derecha del uno:
10 | 2 |
100 | 4 |
1000 | 8 |
10000 | 16 |
Es en las potencias de dos donde muchas cosas están ligadas en el mundo digital. Para obtener el total de números decimales que se pueden codificar con un número determinado de bits, eleve 2 a la potencia del número de bits. Miramos la tabla de arriba y continuamos:
- 5 bits – 32
- 6 bits – 64
- 7 bits – 128
- 8 bits – 256
- 9 bits – 512
- 10 bits – 1024
Etc. Inmediatamente debes recordar que en programación, el conteo comienza desde cero, es decir, con 5 bits podemos codificar un número decimal de 0 a 31, con 8 bits – de 0 a 255, 10 bits – de 0 a 1023. Es muy importante entender y recordar esto, será muy útil más adelante.
La siguiente unidad de medida más grande en el mundo digital es un byte, que consta de 8 bits. ¿Por qué 8? Históricamente, los buses de los primeros microprocesadores tenían un ancho de 8 bits, por lo que probablemente este número se tomó como una unidad de memoria más antigua. Además, 8 es 2 elevado a 3, lo cual es muy simbólico y adecuado. Y también, para codificar todas las letras latinas, signos de puntuación, signos matemáticos y solo símbolos (todo en el teclado), antes eran suficientes 7 bits (128 caracteres), pero luego se volvieron pocos, y se introdujo un bit adicional, el octavo. Es decir, 8 bits también es el tamaño de la tabla de caracteres, que se llama ASCII… Volveremos a ello en este capítulo. Entonces, la pregunta de por qué 8 bits en un byte no tiene una respuesta clara, porque hay bytes de 6 bits y bytes de 9 bits… Pero estas son excepciones de los procesadores antiguos, en los dispositivos digitales modernos, un byte siempre contiene 8 bits ( en diferentes arquitecturas AVR pueden ser diferentes), lo que le permite codificar 256 números decimales del 0 al 255, respectivamente. Entonces veamos:
- B = Byte
- b = bit
- K = Kilo = 1.000
- M = Mega = 1.000.000
- 1 KB = 1024 B
- 1 MB = 1024 kB
- 1 GB = 1024 MB
- Etc.
El sistema binario es nativo del microcontrolador, y hay una serie de comandos y funciones para trabajar con bits individuales, hablaremos de ellas en la lección sobre operaciones de bits de la sección de lecciones avanzadas.
Otros sistemas numéricos.
Los datos en la memoria del microcontrolador se almacenan en representación binaria, pero además, existen otros sistemas de cálculo en los que podemos trabajar. Trate de recordar de inmediato y comprender que no necesita traducir números de un sistema numérico a otro, a Arduino no le importa en absoluto en qué formato alimenta el valor de una variable, se interpretarán automáticamente en forma binaria. Se introducen diferentes sistemas de numeración principalmente para la conocimiento del programador.
Ahora, en esencia: arduino admite (y en general no necesita nada más) cuatro sistemas numéricos clásicos: binario, octal, decimal y hexadecimal. Un pequeño recordatorio: el sistema hexadecimal tiene 16 valores por dígito, los primeros 10 son como decimales, el resto son las primeras letras del alfabeto latino: 0, 1, 2, 3, 4, 5, 6, 7, 8 , 9, a, b, c, d, e, f.
Con el sistema decimal, todo es simple, escribimos los números como se ven. 10 es diez, 25 es veinticinco y así sucesivamente. El binario tiene el prefijo 0b (cero b) o B, es decir, el número binario 101 se escribirá en el IDE como 0b101 o B101. Octal tiene el prefijo 0 (cero), por ejemplo 012. El hexadecimal tiene el prefijo 0x (cero x), FF19 se escribirá como 0xFF19.
Base | Prefijo | Ejemplo | Características: |
2 (binario) | B o 0b (cero b) | B1101001 | dígitos 0 y 1 |
8 (octal) | 0 (cero) | 0175 | dígitos 0 – 7 |
10 (decimal) | 160503 | dígitos 0 – 9 | |
16 (hexadecimal) | 0x (cero x) | 0xFF21A | números 0-9, letras A-F |
La característica principal del sistema hexadecimal es que le permite escribir números decimales largos de forma más corta, por ejemplo, un byte (255) se escribirá como 0xFF, dos bytes (65535) como 0xFFFF y los espeluznantes tres bytes (16.777.215) como 0xFFFFFF.
El sistema binario se usa generalmente para la presentación visual de datos y configuraciones de bajo nivel de varios registros hardware. Por ejemplo, la configuración está codificada con un byte, cada bit en ella es responsable de una configuración separada (encendido / apagado), y al transferir un byte de la forma 0b10110100, puede configurar inmediatamente un montón de cosas, volveremos a esto en la lección donde trabajaremos con registros en lecciones posteriores. En la documentación sobre el microcontrolador, se describe al estilo de “el primer bit es responsable de esto, el segundo de aquello” y así sucesivamente.
Variables.
Una variable es una ubicación de memoria SRAM que tiene su propio nombre único y almacena números de acuerdo con su tamaño. Podemos referirnos a una variable por su nombre y obtener su valor o cambiarlo.
El poder del sistema binario nos persigue aún más, porque el volumen de una celda de memoria en el microcontrolador también es un múltiplo de dos:
- 1 byte = 8 bits = 256
- 2 bytes = 16 bits = 65,536
- 4 bytes = 32 bits = 4294967296
Sí, más de cuatro bytes en un arduino (más precisamente, en un Microcontrolador AVR ) ya no encajarán cuando se utilicen tipos de datos ordinarios. Se utilizan diferentes tipos de datos (variables) para trabajar con diferentes rangos de valores. De hecho, puede usar 4 bytes para almacenar cualquier cosa, pero esto no es lo óptimo. Es como saber que necesitarás llevar un máximo de 200 ml de agua (menos de 1 byte), pero igual te llevas una garrafa de 19 litros (2 bytes). O un vagón cisterna de 120 toneladas (4 bytes). Si desea escribir un código atractivo y óptimo, utilice los tipos de datos oportunos. Por cierto, aquí están:
Tipos de datos.
Nombre | alias | Peso | Rango | Característica |
boolean | bool | 1 byte | 0 o 1, verdadero o falso | Variable booleana. bool en Arduino también toma 1 byte. |
char | int8_t | 1 byte | 0 – 255 | Almacena el número de carácter de la tabla de caracteres ASCII. |
– | int8_t | 1 byte | 0 – 255 | Tipo entero. |
byte | uint8_t | 1 byte | -128 – 127 | Tipo entero. |
int | int16_t, short | 2 bytes | -32 768 … 32 767 | Tipo entero. |
unsigned int | uint16_t, word | 2 bytes | 0 … 65 535 | Tipo entero |
long | int32_t | 4 bytes | -2147 483 648 … 2147483647 | Tipo entero |
unsigned long | uint32_t | 4 bytes | 0 … 4 294967 295 | Tipo entero |
float | – | 4 bytes | -3.4028235E + 38 … 3.4028235E + 38 | Almacena números de coma flotante (decimales). Precisión: 6-7 dígitos |
double | – | 4 bytes | Para AVR es lo mismo que float | |
– | int64_t | 8 bytes | – (2 ^ 64) / 2 … (2 ^ 64) / 2-1 | * Números muy grandes. La serie estándar no puede generarlos |
– | uint64_t | 8 bytes | 2 ^ 64-1 | * Números muy grandes. La serie estándar no puede generarlos |
* No he visto una mención de esto en fuentes oficiales, pero Arduino (más precisamente, el compilador) también admite números de 64 bits, respectivamente, el tipo de datos int64_t y uint64_t
El tamaño máximo de todos los tipos de datos se almacena en constantes y puede usarlo en su código según sea necesario:
- UINT8_MAX – devolverá 255
- INT8_MAX – volverá 127
- UINT16_MAX – devolverá 65 535
- INT16_MAX – devolverá 32 767
- UINT32_MAX- devolverá 4294967295
- INT32_MAX – devolverá 2147483647
- UINT64_MAX – volverá 18446744073709551615
- INT64_MAX – devolverá 9 223 372 036 854 775 807
Además de los tipos enteros (byte, int, long) hay más interesantes:
- bool ( booleano )– tipo de datos booleanos, toma valores 0 y 1 o cierto y falso. En realidad se comporta como un bit, pero ocupa 8 bits. ¡Qué injusticia! Hay varias formas de almacenar variables booleanas para que ocupen 1 byte, pero hablaremos de eso más adelante. También una variable como boolean adquiere el significado cierto si le asigna un valor distinto de cero, eso es boolean a = 50; será true y boolean b = -20; también será true! Ejemplo: boolean flag = true;
- char– un tipo de datos de carácter, en equivalente numérico toma valores de -128 a 127. En el caso de char, estos valores son códigos de caracteres en la tabla de caracteres ASCII estándar. Puede aceptar y almacenar datos en formato de caracteres (letra o carácter entre comillas simples), por ejemplo char var = ‘a’ ;
- float – tipo de datos con punto flotante (inglés float – flotante), es decir decimal.var float = 3,1415 ;
Puede encontrar varios tipos más no estándar que a veces se encuentran en el código de otra persona:
- size_t– “sinónimo” uint16_t, destinado a mostrar el tamaño de un objeto en bytes. Por ejemplo, este tipo es devuelto por la función sizeof () y algunos otros.
- Extended char: char16_t, char32_t y wchar_t… Es necesario para almacenar datos de caracteres grandes para alfabetos de diferentes países, no solo en inglés.
Declarar e inicializar variables:
Declaración de variable: reserva un nombre para los datos del tipo especificado. Inicialización: asignación de un valor inicial a una variable mediante el operador =.
- tipo_de_datos nombre;
- tipo_de_datos nombre = valor; // declaración e inicialización
- También puede declarar e inicializar varias variables separadas por comas: int a = 0, b, c = 10;
byte myVal; // declaracion de variable tipo byte int sensorRead = 10; byte val1, val2, val3 = 10;
Puntos importantes:
- La variable debe declararse antes de referirse a sí misma, literalmente debe estar más alta en el código. De lo contrario, recibirá un error «variable no declarada» – No declarada en este ámbito.
- Las variables globales cuando se declaran se inicializan a 0 por defecto (si no se inicializan)
- Las variables locales (creadas dentro de funciones durante la operación del programa) pueden tener un valor aleatorio y nocivo cuando se declaran, porque actúan desde la pila en RAM. Se recomienda encarecidamente inicializarlos si en el código adicional se utilizan como un valor nulo (ejemplo a continuación):
// lee el valor del ADC int averRead () { long sum = 0; // inicializar 0 // si esto no se hace, habrá problemas for ( int i = 0; i < 10; i ++ ) { sum += analogRead(0); // si la suma no se inicializa a cero, // en lugar de la suma de valores, obtenemos basura } sum = (long)sum / 10; return sum; }
Conversión de tipos.
A veces es necesario convertir un tipo de datos en otro: por ejemplo, una función toma un int pero quieres darle un byte… En la mayoría de los casos, el compilador lo resolverá y convertirá byte en int, pero a veces se produce un error en el estilo de » intentar pasar un byte a donde se espera int «. En este caso, puede convertir el tipo de datos, para ello basta con indicar el tipo de datos deseado entre paréntesis antes de la variable que se será convertida. El resultado devolverá una variable con un nuevo tipo de datos, pero el tipo de la variable en sí no cambiará (funciona dentro del marco de la acción). Por ejemplo:
// variable de tipo byte byte val = 10; // pasar a alguna función que espera un int sendVal ( ( int ) val ) ;
¡Y eso es!, dentro de sendVal(), val será manejado como int, no como byte.
A veces puede hacer la conversión de tipos a través del operador de conversión. Describiré brevemente 4 tipos principales:
- reinterpret_cast– Tipo de fundición sin comprobación, indicación directa al compilador. Se usa solo si el programador está completamente seguro de sus propias acciones. No actua en const y volatile, se utiliza para convertir un puntero a cualquier cosa, en cualquier otro tipo y viceversa; Usarlo con mucho cuidado.
- static_cast– convierte expresiones de un tipo estático en objetos y valores de otro tipo estático. Se admite la conversión de tipos numéricos, punteros y referencias a lo largo de la jerarquía de herencia tanto hacia arriba como hacia abajo. La conversión se verifica a nivel de compilación y se mostrará un mensaje en caso de error en la conversión de tipo;
- dynamic_cast– se utiliza para encasillamiento dinámico en tiempo de ejecución. En el caso de un encasillado incorrecto, se lanza la excepción std :: bad_cast para las referencias y se devolverá 0 para los punteros;
- const_cast– la conversión de tipo más simple. Admite const y volatile, es decir, ser constante y no optimizar la variable por parte del compilador. Esta conversión se verifica a nivel de compilación y se emitirá un mensaje en caso de error en la conversión de tipos.
Cómo utilizar: en el ejemplo anterior;
// variable de tipo byte byte val = 10; // pasar a alguna función que espera un int sendVal ( static_cast < int > ( val ) ) ;
Constantes.
Lo que es una constante se desprende claramente de su nombre: algo, cuyo valor solo podemos leer y no podemos cambiar. Hay dos formas de establecer (declarar) una constante: Al igual que una variable, antes del tipo de datos con la palabra const. Si el valor de la variable no cambia durante la ejecución del programa, se recomienda declararlo como constante, esto permitirá al compilador optimizar mejor el código y en la mayoría de los casos será un poco más fácil y rápido.
const byte myConst = 10; // declara una constante de tipo byte
Usando una directiva de preprocesador #define, que hace lo siguiente: en la etapa de compilación del código, el preprocesador reemplaza todas las secuencias de caracteres especificadas en el documento actual (recuerde que las pestañas de Arduino IDE son un documento) con sus valores correspondientes. Constante definida con #define no ocupa espacio en la RAM, pero se almacena como código de programa en la memoria Flash, esta es la mayor ventaja de este método. Sintaxis: #define ‘el valor del nombre’ La coma no se usa. De esta manera, generalmente se indican los pines de conexión, los ajustes, varios valores, etc. Ejemplo:
#define BTN_PIN 10 #define DEFAULT_VALUE 3423
Algunas palabras más sobre constantes y variables: si una variable ordinaria no cambia en ningún lugar durante la ejecución del programa, el compilador puede convertirla en una constante por sí sola y no ocupará espacio en la RAM, es decir, se colocará en Flash.
Área de visibilidad.
Las variables, constantes y otros tipos de datos (estructuras y enumeraciones) tienen un concepto tan importante como alcance. Puede ser:
- Global
- Local
- Formal (parámetro)
Ámbito Global
Una variable global se declara fuera de las funciones y está disponible para leer y escribir en cualquier parte del programa, en cualquiera de sus funciones.
byte var; void setup() { // cambia la variable global var = 50; } void loop() { // cambia la variable global var = 70; }
Ámbito Local
Una variable local vive dentro de una función o dentro de cualquier bloque de código incluido entre { llaves }, disponible para leer y escribir solo en su interior. Al intentar acceder a una variable local desde otra función (fuera del { bloque } ) obtiene un error porque la variable local se vuelve a crear cuando se ejecuta el bloque de código (o función) que lo contiene, y se elimina de la memoria cuando el bloque (o función) termina de ejecutarse:
void setup() { byte var; // variable local para la configuración // cambia silenciosamente la variable local var = 50; } void loop() { // dará como resultado un error porque var no está declarado en este bloque de código var = 70; // haz un bloque de código separado aquí { byte var2 = 10; // ¡var2 solo existe dentro de este bloque! } // aquí var2 ya se eliminará de la memoria }
Un punto importante: si el nombre de una variable local es el mismo que uno global, entonces la prioridad de llamar por nombre en la función se le da a la variable local:
void setup() { // pasa 10 como argumento myFunc ( 10 ) ; } void loop() { } void myFunc ( byte var ) { // var es "local" aquí // sumar 20 var + = 20; // después de} ¡la variable se eliminará de la memoria! }
Estructuras.
struct– Es un tipo de datos muy interesante: es una colección de variables de diferentes tipos, unidas por un nombre. En algunos casos, las estructuras pueden simplificar enormemente la escritura de código, hacerlo más lógico y fácilmente modificable. La estructura del tipo de datos se declara de la siguiente manera:
struct miEstructura { tipo_datos nombre_variable_1; tipo de datos nombre_variable_2; tipo_datos nombre_variable_3; } ;
La etiqueta será un nuevo tipo de datos y, con esta etiqueta, puede declarar la estructura en sí directamente:
miEstructura estructura_1;
También existe una variante de declarar una estructura sin crear un atajo, es decir crear una estructura sin declararla como un tipo de datos con su propio nombre.
struct { tipo_datos nombre_variable_1; tipo_datos nombre_variable_2; tipo_datos nombre_variable_3; } nombre_estructura;
- Un miembro de estructura se direcciona de acuerdo con el siguiente esquema: nombre_estructura.nombre_variable y le permite cambiar o leer el valor.
- Si dos estructuras tienen la misma estructura (declarada por la misma etiqueta), entonces una estructura puede simplemente equipararse a la otra, todas las variables se escribirán en consecuencia en sus lugares.
- Otra opción conveniente es asignar un valor como este: nombre_estructura = ( atajo ) { variable_value_1, variable_value_2, variable_value_3 } ;
Veamos un gran ejemplo que muestra todo lo anterior.
struct myStruct { // crea una estructura myStruct boolean a; byte b; int c; long d; byte e [ 5 ] ; } datosA; // e inmediatamente crea la instancia datosA // crea una matriz de estructuras data_B de tipo myStruct myStruct data_B [ 3 ] ; void setup() { // asigna valores a los miembros de la estructura manualmente datosA. a = true ; datosA. b = 10; datosA. c = 1200; datosA. d = 789456; datosA. e [ 0 ] = 10; // ¡Tenemos una matriz! datosA. e [ 1 ] = 20; datosA. e [ 2 ] = 30; // asigna la estructura datosA a la estructura data_B número 0 data_B [ 0 ] = datosA; // asigna un elemento de matriz de la estructura datosA // estructura data_B número 1 data_B [ 0 ] . e [ 1 ] = datosA. e [ 1 ] ; // completa los datos con la estructura data_B número 2 data_B [ 2 ] = ( myStruct ) { false , 30, 3200, 321654, { 1, 2, 3, 4, 5 } } ; }
¿Para qué sirven las estructuras? La mayoría de los ejemplos en Internet utilizan estructuras para almacenar datos de direcciones, p. Ej. creando una base de datos de direcciones: nombre, apellidos, número de teléfono, etc. En mi práctica, las estructuras resultaron ser muy adecuadas para crear un menú con una gran cantidad de modos y configuraciones (digamos varios canales, cada uno con el mismo conjunto de configuraciones). Es muy conveniente transmitir y recibir estructuras usando módulos RF24, es decir en lugar de matrices, es más conveniente utilizar estructuras y transferir varios tipos de datos en una línea a la vez. Además, toda la estructura se puede escribir en la eeprom en una línea (con el comando put) y leerla en una línea desde allí, sin molestarse con los números de celda, como sucede cuando se escriben datos manualmente.
Tamaño del elemento de estructura
Las estructuras te permiten hacer algo muy interesante para la optimización de la memoria: especificar el peso máximo de un elemento en bits. De esta manera, incluso puede crear indicadores de un solo bit (bool/boolean ocupa 8 bits en memoria). Esto se hace usando el operador de dos puntos. :
// crear y empaquetar la estructura de banderas de un bit struct MyFlags { bool button: 1; bool state: 1; bool position: 1; bool flag_3: 1; bool flag_4: 1; }; // declaramos una estructura de tipo MyFlags MyFlags flags; void setup() { Serial.begin(9600); // por defecto todas las banderas son false // hacer referencia a una estructura // podemos cambiar y trabajar como con banderas ordinarias Serial.println(flags.button); Serial.println(flags.state); flags.position = true; Serial.println(flags.position); } void loop() {}
De la misma forma, puedes empaquetar tipos de datos enteros, por ejemplo, sabemos que el valor de una variable no excederá de 50, podemos declararlo dentro de la estructura como byte val: 6 lo que lo convertirá en 6 bits en la memoria. No tiene sentido para un pequeño conjunto de variables, pero si realmente hay muchas, ¡vale la pena empaquetarlas en una estructura!
Estructuras anidadas
Las estructuras también se pueden anidar entre sí, el acceso al elemento deseado también se realiza mediante el operador «punto», un ejemplo simple:
struct Values { int value1; float value2; }; struct BigStruct { Values values; int otherValue; }; BigStruct myStruct; myStruct.values.value2 = 3.14;
Enumeraciones.
Enumeraciones (enum – enumeración): un tipo de datos, que es un conjunto de constantes con nombre, se necesita principalmente para la conveniencia del programador. Solo un ejemplo de la experiencia: digamos que tenemos un modo variable, que es responsable del número de modo del dispositivo. Recordamos por nosotros mismos a qué valor de la variable corresponderá el modo, y en algún lugar lo escribimos, por ejemplo 0 – modo normal, 1 – modo de espera, 2 – modo de configuración_1, 3 – modo de configuración_2, 4 – calibración, 5 – modo de emergencia, error. Al escribir o leer un programa, a menudo tendrá que consultar esta lista para evitar confusiones. Puede dar el primer paso en la optimización: llame a cada modo con una definición:
#define NORMAL 0 #define WAITING 1 #define SETTINGS_1 2 #define SETTINGS_2 3 #define CALIBRATION 4 #define ERROR_MODE 5
Por lo tanto, en lugar de números, será posible usar palabras comprensibles y será mucho más fácil navegar por el código. Utilizando enum simplifica un poco más esta construcción: la enumeración le permite crear una variable (por defecto de tipo int), que puede aceptar sólo los «nombres» que se le especifican. Esto es conveniente porque un programa puede contener diferentes guardianes de modo con el mismo nombre y a diferencia #define esto no dará lugar a errores.
Declarar una enumeración es algo similar a declarar una estructura:
enum shortcut { nombre1, nombre2, nombre3, nombre4, nombre5 } ;
Así es como declaramos la enumeración en sí:
Al igual que las estructuras, puede declarar una enumeración:
enum { nombre1, nombre2, nombre3, nombre4, nombre5 } nombre de enumeración;
Una enumeración (enum) es un tipo definido con constante de tipo entero. En la declaración de un tipo enum creamos una lista de tipo de datos que se asocian con las constantes enteras 0, 1, 2, 3, 4, 5…
su forma de definirlas es la siguiente:
enum { enumerador1, enumerador2, … enumeradorn }; enum Nombre { enumerador1, enumerador2, … enumeradorn };
En este caso al ser declaradas enumerador1 toma el valor entero de 0, enumerador2 el valor de 1 y asi sucesivamente para cada una de las expresiones siguientes.
Al declarar la enum se puede asociar a los tipos de datos a valores constantes en vez de la asociación que por defecto se realiza (0, 1, 2, …), se utiliza entonces este formato:
enum Nombre { enumerador1 = valor_constante1, enumerador2 = valor_constante2, ... enumeradorn = valor_constanten, };
Un ejemplo de una enumeracion:
{ FALSE, TRUE };
A los enumeradores se pueden asignar valores o expresiones constantes durante la declaración:
enum Hexaedro { VERTICE = 8, LADOS = 12, CARAS = 6 };
Tipos personalizados, typedef.
El idioma tiene una herramienta typedef, que le permite crear su propio tipo de datos basado en otro estándar. ¿Para qué? Por un lado, para la conveniencia del programador, por otro lado, para confundirlo =) Nunca lo he usado, ¡pero necesitas saber esto para analizar los códigos de otras personas de Internet! Entonces, typedef funciona así: typedef nombre del tipo; – crear un nuevo tipo de datos basado en el nombre del tipo… Ejemplo:
ypedef byte color;
Crea un tipo de datos llamado color que será absolutamente idéntico al tipo byte (es decir, tome 0-255). Ahora puedes crear variables con este tipo:
color R, G, B;
Espacio de nombres.
Considere la siguiente situación, cuando tenemos dos personas con el mismo nombre, Sonia, en la misma clase. Siempre que necesitemos diferenciarlos definitivamente tendríamos que utilizar algún tipo de información adicional junto con su nombre, como la zona, si viven en otra zona o el nombre de su madre o padre, etc.
La misma situación puede surgir en sus programas C ++. Por ejemplo, podría estar escribiendo un código que tiene una función llamada xyz () y hay otra biblioteca disponible que también tiene la misma función xyz (). Ahora el compilador no tiene forma de saber a qué versión de la función xyz () se está refiriendo dentro de su código.
Un espacio de nombres está diseñado para superar esta traba y se utiliza como información adicional para diferenciar funciones, clases, variables, etc. similares con el mismo nombre disponible en diferentes bibliotecas. Usando el espacio de nombres, puede definir el contexto en el que se definen los nombres. En esencia, un espacio de nombres define un ámbito.
Definición de un espacio de nombres
Una definición de espacio de nombres comienza con la palabra clave namespace seguida del nombre del espacio de nombres de la siguiente manera:
namespace namespace_name { // code declarations }
Para llamar a la versión habilitada para el espacio de nombres de una función o variable, anteponga (: 🙂 a el nombre del espacio de nombres de la siguiente manera:
name::code; // code could be variable or function.
Veremos cómo el espacio de nombres abarca las entidades, incluidas las variables y las funciones:
#include <iostream> using namespace std; // primer espacio de nombres namespace first_space { void func() { cout << "Inside first_space" << endl; } } // segundo espacio de nombres namespace second_space { void func() { cout << "Inside second_space" << endl; } } int main () { // Calls function from first name space. first_space::func(); // Calls function from second name space. second_space::func(); return 0; }
Si compilamos y ejecutamos el código anterior, esto produciría el siguiente resultado:
Inside first_space Inside second_space
La directiva using
También puede evitar anteponer espacios de nombres con la directiva using namespace. Esta directiva le dice al compilador que el código subsiguiente utiliza nombres en el espacio de nombres especificado. Por lo tanto, el espacio de nombres está implícito para el siguiente código:
#include <iostream> using namespace std; // first name space namespace first_space { void func() { cout << "Inside first_space" << endl; } } // second name space namespace second_space { void func() { cout << "Inside second_space" << endl; } } using namespace first_space; int main () { // This calls function from first name space. func(); return 0; }
Si compilamos y ejecutamos el código anterior, esto produciría el siguiente resultado:
Inside first_space
La directiva ‘using’ también se puede usar para referirse a un elemento en particular dentro de un espacio de nombres. Por ejemplo, si la única parte del espacio de nombres estándar que pretende utilizar es cout, puede hacer referencia a él de la siguiente manera:
using std::cout;
Especificadores.
Además de poder convertir una variable en una constante usando el especificador const tenemos algunas herramientas más interesantes para trabajar con una variable.
Static
estátic- hace que la variable (o constante) sea estática. Qué significa eso?
Local static
Primero, recordemos cómo funciona una variable local ordinaria: cuando se llama a una función, la variable local inicializa y obtiene un valor cero, a menos que se especifique lo contrario. Si la variable local se declara como estátic – almacenará su valor de una llamada a otra llamada de función, es decir, se convertirá, en términos generales, en globalmente local.
Global static
Una variable global estátic pasa a estar disponible solo en este archivo, el especificador estátic le permite ocultarlo de las influencias de otros archivos de programa.
Extern
extern– indica al compilador que la variable está declarada en algún lugar de otro archivo de programa, y durante la compilación la encontrará y la usará. Y si no lo encuentra, no habrá error. Por ejemplo, con este código, puede restablecer el contador millis ().
// indicamos lo que queremos usar // variable timer0_millis, // que se declara en algún lugar lejano // en archivos Arduino extern volatile unsigned long timer0_millis; void setup() { timer0_millis = 0; } void loop() { }
Volaltile
volátile– este especificador le dice al compilador que esta variable no necesita ser optimizada y su valor se puede cambiar desde algún lugar externo. Las variables con este especificador se usan generalmente en manejadores de interrupciones. Los cálculos con tales variables tampoco están optimizados y requieren más tiempo de procesamiento.