Grandes tipos de datos c. Tipos de datos y variables. Otros tipos de datos

Además de dividir los datos en variables y constantes, existe una clasificación de los datos por tipo. La descripción de variables consiste principalmente en declarar su tipo. El tipo de datos caracteriza el rango de sus valores y la forma de representación en la memoria de la computadora. Cada tipo se caracteriza por un conjunto de operaciones realizadas sobre los datos. Tradicionalmente, en los lenguajes de programación universales existen tales tipos estándar, como entero, real, simbólico y lógico 3. Notemos inmediatamente que tipo booleano no en c. Una expresión (en el caso particular, una variable) se considera verdadera si es distinta de cero, en caso contrario se considera falsa.

La existencia de dos tipos numéricos (entero y real) está asociada a dos posibles formas de representar números en la memoria de la computadora.

Datos tipo entero almacenado en forma de presentación punto fijo. Se caracteriza por una precisión absoluta en la representación de números y en la realización de operaciones con ellos, así como por un rango limitado de valores numéricos. tipo entero Se utiliza para datos que, en principio, no pueden tener parte fraccionaria (número de personas, coches, etc., números y contadores).

tipo real corresponde a la forma de representación numérica punto flotante, que se caracteriza por una representación aproximada de un número con un número determinado de dígitos significativos (signos de mantisa) y un gran rango del orden del número, lo que permite representar números muy grandes y muy pequeños en valor absoluto. Debido a la representación aproximada de datos de tipo real, su es incorrecto comparar por igualdad.

En las implementaciones modernas de los lenguajes de programación universales, generalmente hay varios tipos enteros y varios tipos reales, cada uno de los cuales se caracteriza por su propio tamaño de memoria asignada para un valor y, en consecuencia, su rango de valores numéricos, y para los tipos reales, y su precisión (el número de dígitos de la mantisa).

Datos tipo de personaje tomar valores en todo el conjunto de aceptables de esta computadora personajes. Se asigna un byte para almacenar el valor de un carácter; los caracteres se codifican de acuerdo con una tabla de codificación estándar (generalmente ASCII).

Hay 4 tipos básicos en C:

carbonizarse- tipo de personaje;

entero- todo un tipo,

flotar- tipo real de precisión simple,

doble- tipo real de doble precisión.

Para definir tipos derivados, utilice clasificados:corto(corto) - usado con tipo entero,largo(largo) - usado con tipos entero Y doble;firmado(con una señal), no firmado(sin firmar): aplicable a cualquier tipo de número entero. En ausencia de la palabra sin firmar, el valor se considera firmado, es decir es decir, el valor predeterminado está firmado. Debido a la admisibilidad de una combinación arbitraria de calificadores y nombres de tipos básicos, un tipo puede tener varias designaciones. La información sobre los tipos C estándar se presenta en las Tablas 1 y 2. Los descriptores sinónimos se enumeran en las celdas de la primera columna, separados por comas.

Tabla 1. Tipos de datos enteros estándar C

tipo de datos

Rango de valores

carbón, carbón firmado

int sin firmar, sin firmar

int, int con signo, corto int, corto

2147483648...2147483647

Curiosamente, en C, el tipo char se puede utilizar como carácter o como tipo entero, según el contexto.

Tabla 2. Tipos de datos reales estándar C

Comentario. Para escribir programas para la primera parte del manual, necesitaremos principalmente dos tipos:flotarYentero.

Una diferencia importante entre el lenguaje SI y otros lenguajes (PL1, FORTRAN, etc.) es la ausencia de un principio predeterminado, lo que lleva a la necesidad de declarar explícitamente todas las variables utilizadas en el programa junto con una indicación de sus tipos correspondientes. .

Las declaraciones de variables tienen el siguiente formato:

[especificador de clase de memoria] descriptor de especificador de tipo [=iniciador] [,descriptor [=iniciador] ]...

Un descriptor es un identificador de una variable simple o una construcción más compleja con corchetes, paréntesis o un asterisco (un conjunto de asteriscos).

Un especificador de tipo es una o más palabras clave que definen el tipo de variable que se declara. El lenguaje SI tiene un conjunto estándar de tipos de datos, mediante el cual se pueden construir tipos de datos nuevos (únicos).

Iniciador: especifica el valor inicial o la lista de valores iniciales que se asignan a la variable cuando se declara.

Especificador de clase de memoria: determinado por una de las cuatro palabras clave SI: auto, extern, registro, estático e indica cómo se asignará la memoria para la variable declarada, por un lado, y, por el otro, el alcance de esta variable. es decir, desde qué partes del programa se puede acceder.

1.2.1 Categorías de tipos de datos

Palabras clave para definir tipos de datos básicos

Tipos de enteros: Tipos flotantes: char float int doble corto largo doble largo con signo sin signo

Una variable de cualquier tipo puede declararse no modificable. Esto se logra agregando la palabra clave const al especificador de tipo. Los objetos con tipo const representan datos de solo lectura, es decir. A esta variable no se le puede asignar un nuevo valor. Tenga en cuenta que si no hay ningún especificador de tipo después de la palabra const, entonces el especificador de tipo int está implícito. Si la palabra clave const aparece antes de la declaración de tipos compuestos (matriz, estructura, mezcla, enumeración), esto lleva al hecho de que cada elemento tampoco debe ser modificable, es decir. solo se le puede asignar un valor una vez.

Const doble A=2,128E-2;

constante B=286; (const int B=286 está implícito)

A continuación se analizarán ejemplos de declaración de datos compuestos.

1.2.2. tipo de datos entero

Para definir datos de tipo entero, se utilizan varias palabras clave, que determinan el rango de valores y el tamaño del área de memoria asignada para las variables (Tabla 6).

Tabla 6 Tenga en cuenta que las palabras clave firmadas y sin firmar son opcionales. Indican cómo se interpreta el bit cero de la variable declarada, es decir, si se especifica la palabra clave sin signo, entonces el bit cero se interpreta como parte de un número; de lo contrario, el bit cero se interpreta como con signo. Si falta la palabra clave sin firmar, la variable entera se considera firmada. Si el especificador de tipo consta de tipo de clave

firmado o sin firmar seguido del identificador de la variable, se tratará como una variable int. Por ejemplo:

Ent sin firmar n;

int sin signo b; intc; (se da a entender int c firmado);. Entonces, en máquinas de 16 bits el tamaño de la palabra es de 2 bytes, en máquinas de 32 bits es de 4 bytes, es decir El tipo int es equivalente a los tipos short int o long int, dependiendo de la arquitectura de la PC utilizada. Así, un mismo programa puede funcionar correctamente en un ordenador y de forma incorrecta en otro. Para determinar la longitud de la memoria ocupada por una variable, puede utilizar el operador sizeof del lenguaje SI, que devuelve la longitud del modificador de tipo especificado.

Por ejemplo:

A = tamaño de (int);

b = tamaño de (int largo);

Por ejemplo:

c = tamaño de (largo sin firmar);

d = tamaño de (corto);

Tenga en cuenta también que las constantes octales y hexadecimales también pueden tener el modificador sin signo. Esto se logra especificando el prefijo u o U después de la constante; una constante sin este prefijo se considera firmada.

0xA8C (int firmado);

01786l (firmado durante mucho tiempo);

0xF7u (int sin firmar); 1.2.3. Datos flotantes;

Para las variables que representan un número de punto flotante, se utilizan los siguientes modificadores de tipo: flotante, doble, doble largo (en algunas implementaciones del lenguaje doble largo no hay SI).

Un valor con el modificador de tipo flotante ocupa 4 bytes. De ellos, se asigna 1 byte para el signo, 8 bits para el exponente sobrante y 23 bits para la mantisa. Tenga en cuenta que el bit más significativo de la mantisa es siempre 1, por lo que no se llena, por lo que el rango de valores para una variable de coma flotante es aproximadamente 3,14E-38 a 3,14E+38.

Un valor doble ocupa 8 bits de memoria. Su formato es similar al formato flotante. Los bits de memoria se distribuyen de la siguiente manera: 1 bit para el signo, 11 bits para el exponente y 52 bits para la mantisa. Teniendo en cuenta el bit alto omitido de la mantisa, el rango de valores es de 1,7E-308 a 1,7E+308.

El especificador de tipo especifica el tipo de objeto y puede ser cualquier tipo básico, tipo de estructura o mezcla (esto se discutirá más adelante). Al especificar la palabra clave void en lugar del especificador de tipo, puede diferir de forma única la especificación del tipo al que se refiere el puntero. Una variable declarada como puntero al tipo void se puede utilizar para hacer referencia a un objeto de cualquier tipo. Sin embargo, para poder realizar operaciones aritméticas y lógicas sobre punteros o sobre los objetos a los que apuntan, es necesario determinar explícitamente el tipo de objetos al realizar cada operación. Estas definiciones de tipos se pueden realizar mediante la operación de conversión de tipos.

Las palabras clave const, near, far, huge pueden usarse como modificadores al declarar un puntero. La palabra clave const indica que el puntero no se puede modificar en el programa. El tamaño de una variable declarada como puntero depende de la arquitectura de la computadora y del modelo de memoria utilizado para el cual se compilará el programa. Los punteros a diferentes tipos de datos no tienen por qué tener la misma longitud.

Para modificar el tamaño del puntero, puede utilizar las palabras clave cerca, lejos, enorme.

Int sin firmar * a; /* la variable a es un puntero al tipo unsigned int */ double * x; /* variable x indica tipo de datos de coma flotante de doble precisión */ char * fuffer ; /* declara un puntero llamado fuffer que apunta a una variable de tipo char */ double nomer; *direcciones nulas;, pero el valor al que apunta no lo es. */ carácter sin firmar * const w = &obj.

/* La variable w se declara como un puntero constante a datos de caracteres sin firmar.

Esto significa que w apuntará a la misma ubicación de memoria durante todo el programa. El contenido de esta área puede cambiarse. */

1.2.5. Variables enumeradas

Una variable que puede tomar un valor de alguna lista de valores se llama variable enumerada o enumeración.

Una declaración de enumeración comienza con la palabra clave enum y tiene dos formatos de presentación.

Formato 1. enum [nombre-etiqueta-enum] (lista-enum) descriptor[,descriptor...]; Formato 2. enum enum-tag-name descriptor [,descriptor..]; La declaración de enumeración especifica el tipo.

variable de enumeración

y define una lista de constantes con nombre, llamada lista de enumeración. El valor de cada nombre de lista es un número entero. Una variable de tipo enumeración puede tomar el valor de una de las constantes nombradas de la lista. Las constantes de lista con nombre son de tipo int. Por tanto, la memoria correspondiente a una variable de enumeración es la memoria necesaria para acomodar un valor int. Las variables de tipo enum se pueden utilizar en expresiones de índice y como operandos en

operaciones aritméticas

y en operaciones relacionales.

En el primer formato 1, los nombres y valores de enumeración se especifican en una lista de enumeración. El nombre de etiqueta de enumeración opcional es un identificador que nombra la etiqueta de enumeración definida por la lista de enumeración. El descriptor nombra una variable de enumeración. Se puede especificar más de una variable de tipo de enumeración en una declaración.

Una lista de enumeración contiene una o más construcciones de la forma:

identificador [= expresión constante]

1. La variable puede contener valores duplicados.

2. Los identificadores de una lista de enumeración deben ser distintos de todos los demás identificadores del mismo ámbito, incluidos los nombres de variables normales y los identificadores de otras listas de enumeración.

3. Los nombres de los tipos de enumeración deben ser distintos de otros nombres de tipos de enumeración, estructuras y mezclas en el mismo ámbito.

4. El valor puede seguir al último elemento de la lista de enumeración.

Semana de enumeración ( SUB = 0, /* 0 */ VOS = 0, /* 0 */ POND, /* 1 */ VTOR, /* 2 */ SRED, /* 3 */ HETV, /* 4 */ PJAT /* 5 */ ) rab_ned ;

EN en este ejemplo Se declara una etiqueta enumerable semana, con un conjunto de valores correspondiente, y se declara una variable rab_ned de tipo semana.

El segundo formato utiliza el nombre de la etiqueta enum para hacer referencia a un tipo de enumeración definido en otro lugar. El nombre de la etiqueta de enumeración debe hacer referencia a una etiqueta de enumeración ya definida dentro del alcance actual. Debido a que la etiqueta de enumeración se declara en otro lugar, la lista de enumeración no está representada en la declaración.

Al declarar un puntero a un tipo de datos de enumeración y declarar typedefs para tipos de enumeración, puede usar el nombre de una etiqueta de enumeración antes de definir esa etiqueta de enumeración. Sin embargo, la definición de enumeración debe preceder a cualquier uso del puntero de la declaración typedef al tipo. Una declaración sin una lista posterior de descriptores describe una etiqueta o, por así decirlo, un patrón de enumeración.

1.2.6. matrices

Los arrays son un grupo de elementos del mismo tipo (doble, flotante, int, etc.). De la declaración de la matriz, el compilador debe obtener información sobre el tipo de elementos de la matriz y su número. Una declaración de matriz tiene dos formatos:

descriptor de especificador de tipo [const - expresión];

descriptor de especificador de tipo;

El descriptor es el identificador de la matriz.

El especificador de tipo especifica el tipo de elementos de la matriz declarada. Los elementos de la matriz no pueden ser funciones o elementos vacíos.

La expresión constante entre corchetes especifica el número de elementos de la matriz. Al declarar una matriz, se puede omitir una expresión constante en los siguientes casos:

Cuando se declara, la matriz se inicializa,

La matriz se declara como un parámetro formal de la función,

SI define sólo matrices unidimensionales, pero dado que un elemento de una matriz puede ser una matriz, también se pueden definir matrices multidimensionales. Están formalizados mediante una lista de expresiones constantes que siguen al identificador de la matriz, con cada expresión constante entre corchetes.

Cada expresión constante entre corchetes especifica el número de elementos a lo largo de esa dimensión de la matriz, de modo que una declaración de matriz bidimensional contiene dos expresiones constantes, una matriz tridimensional contiene tres, y así sucesivamente. Tenga en cuenta que en SI, el primer elemento de una matriz tiene un índice de 0.

Ent a; /* representado como una matriz a a a a a */ double b; /* vector de 10 elementos de tipo double */ int w = ( ( 2, 3, 4 ), ( 3, 4, 8 ), ( 1, 0, 9 ) );

En el último ejemplo, se declara la matriz w. Las listas entre llaves corresponden a cadenas de matriz; si faltan las llaves, la inicialización no se realizará correctamente.

En SI puedes usar sectores de matriz, al igual que en otros idiomas. alto nivel(PL1, etc.), sin embargo, se imponen una serie de restricciones al uso de las secciones. Las secciones se forman omitiendo uno o más pares de corchetes. Los pares de corchetes solo se pueden eliminar de derecha a izquierda y de forma estrictamente secuencial. Se utilizan secciones de matrices para organizar el proceso computacional en funciones SI desarrolladas por el usuario.

Si escribe s al llamar a una función, se pasará la cadena cero de la matriz s.

Al acceder a una matriz b, puede escribir, por ejemplo, b y se transferirá un vector de cuatro elementos, y al acceder a b se obtendrá una matriz bidimensional de tamaño 3 por 4. No puede escribir b, lo que implica que se creará un vector. ser transferido, porque esto no cumple con la restricción impuesta sobre el uso de secciones de matriz.

Un ejemplo de declaración de matriz de caracteres.

char str = "declaración de matriz de caracteres";

Tenga en cuenta que hay un elemento más en un literal de carácter, ya que el último elemento es la secuencia de escape "\0".

1.2.7. Estructuras

Las estructuras son un objeto compuesto que incluye elementos de cualquier tipo, a excepción de funciones. A diferencia de una matriz, que es un objeto homogéneo, una estructura puede ser heterogénea. El tipo de estructura está determinado por una entrada del formulario:

estructura (lista de definiciones)

Se debe especificar al menos un componente en la estructura. La definición de estructuras es la siguiente:

descriptor de tipo de datos;

donde tipo de datos especifica el tipo de estructura de los objetos definidos en los descriptores. En su forma más simple, los identificadores son identificadores o matrices.

Estructura ( doble x,y; ) s1, s2, sm;

estructura (int año; char polilla, día;) fecha1, fecha2;

Las variables s1, s2 se definen como estructuras, cada una de las cuales consta de dos componentes xey. La variable sm se define como una matriz de nueve estructuras. Cada una de las dos variables fecha1, fecha2 consta de tres componentes año, mes y día. >p>Existe otra forma de asociar un nombre a un tipo de estructura, se basa en el uso de una etiqueta de estructura. Una etiqueta de estructura es similar a una etiqueta de tipo enumeración. La etiqueta de estructura se define de la siguiente manera:

etiqueta de estructura (lista de descripciones;);

donde etiqueta es un identificador.

El siguiente ejemplo describe el identificador de estudiante como una etiqueta de estructura:

Estructura estudiante ( nombre char; int id, edad; char prp; );

La etiqueta de estructura se utiliza para declarar posteriormente estructuras de este tipo en el formulario:

etiqueta de lista de identificación de estructura;

estructura estudiante st1,st2;

El uso de etiquetas de estructura es necesario para describir estructuras recursivas. A continuación se analiza el uso de etiquetas de estructura recursivas.

Nodo de estructura ( int datos; nodo de estructura * siguiente; ) st1_node;

El nodo de etiqueta de estructura es efectivamente recursivo ya que se utiliza en su propia descripción, es decir en la formalización del siguiente puntero. Las estructuras no pueden ser directamente recursivas, es decir una estructura de nodo no puede contener un componente que sea una estructura de nodo, pero cualquier estructura puede tener un componente que sea un puntero a su tipo, como se hace en el ejemplo anterior.

Se accede a los componentes de la estructura especificando el nombre de la estructura y lo siguiente, separado por un punto, el nombre del componente seleccionado, por ejemplo:

St1.name="Ivánov";

st2.id=st1.id;

st1_node.data=st1.age;

1.2.8. Combinaciones (mezclas)

Se accede a los miembros de un sindicato del mismo modo que a las estructuras. La etiqueta de unión se puede formalizar exactamente de la misma manera que la etiqueta de estructura.

La asociación se utiliza para los siguientes fines:

Inicializar un objeto de memoria en uso si en un momento dado sólo uno de muchos objetos está activo;

Interpretar la representación subyacente de un objeto de un tipo como si a ese objeto se le asignara un tipo diferente.

La memoria que corresponde a una variable de tipo unión está determinada por la cantidad requerida para acomodar el elemento más largo de la unión. Cuando se utiliza un elemento más corto, la variable de tipo unión puede contener memoria no utilizada. Todos los elementos de una unión se almacenan en la misma área de memoria, comenzando en la misma dirección.

Unión (char fio; char direcciones; int vozrast; int telefon; ) informar;

unión (int ax; char al;) ua;

Cuando se utiliza un objeto info de tipo unión, puede procesar solo el elemento que recibió el valor, es decir Después de asignar un valor al elemento inform.fio, no tiene sentido acceder a otros elementos. La concatenación ua permite el acceso separado a los bytes ua.al bajo y ua.al alto del número de dos bytes ua.ax.

1.2.9. campos de bits Un elemento de la estructura puede ser un campo de bits que proporciona acceso a bits individuales de memoria. Los campos de bits no se pueden declarar fuera de las estructuras. Tampoco puede organizar matrices de campos de bits y no puede aplicar la operación de determinación de dirección a los campos. EN caso general

el tipo de estructura con un campo de bits se especifica de la siguiente manera:

Estructura (identificador sin firmar 1: longitud del campo 1; identificador sin firmar 2: longitud del campo 2;)

longitud: los campos se especifican mediante una expresión entera o una constante. Esta constante especifica el número de bits asignados al campo correspondiente. Un campo de longitud cero indica alineación con el límite de la siguiente palabra.

Estructura (a1 sin firmar: 1; a2 sin firmar: 2; a3 sin firmar: 5; a4 sin firmar: 2;) prim;

Las estructuras de campos de bits también pueden contener componentes firmados. Dichos componentes se colocan automáticamente en límites de palabras apropiados y es posible que algunos bits de palabras queden sin utilizar.

Muy a menudo, algunos objetos de programa pertenecen a la misma clase y difieren sólo en algunos detalles. Consideremos, por ejemplo, la representación de formas geométricas. La información general sobre formas puede incluir elementos como área y perímetro. Sin embargo, la información de las dimensiones geométricas correspondientes puede diferir según su forma.

Consideremos un ejemplo en el que se representa información sobre formas geométricas basándose en el uso combinado de estructura y unión.

Figura de estructura ( área doble, perímetro; /* componentes comunes */ tipo int; /* atributo de componente */ unión /* enumeración de componentes */ ( radio doble; /* círculo */ doble a; /* rectángulo */ doble b ; /* triángulo */ ) geom_fig ) fig1, fig2;

En general, cada objeto figura constará de tres componentes: área, perímetro y tipo. El componente de tipo se denomina etiqueta de componente activo porque se utiliza para indicar en qué componente de la unión geom_fig está activo. en este momento. Esta estructura se llama estructura variable porque sus componentes cambian dependiendo del valor de la etiqueta. componente activo(escriba valor).

Tenga en cuenta que en lugar de utilizar un componente de tipo int, sería recomendable utilizar un tipo enumerado. Por ejemplo, así

Enum figura_ajedrez (CÍRCULO, CAJA, TRIÁNGULO);

Las constantes CIRCLE, BOX, TRIANGLE recibirán valores iguales a 0, 1, 2, respectivamente. La variable de tipo se puede declarar como de tipo enumerado:

enumeración figura_ajedrez tipo;

En este caso, el compilador SI advertirá al programador sobre asignaciones potencialmente erróneas, como

figura.tipo = 40;

En general, una variable de estructura constará de tres partes: un conjunto de componentes comunes, una etiqueta para el componente activo y una parte con componentes cambiantes. La forma general de una estructura variable es la siguiente:

Estructura (componentes comunes; etiqueta de componente activo; unión (descripción del componente 1; descripción del componente 2; ::: descripción del componente n; ) identificador de unión; ) identificador de estructura;

Ejemplo de definición de una variable de estructura denominada helth_record

Estructura ( /* información general*/ nombre de carácter; /* nombre */ int edad; /* edad */ char sexo; /* género */ /* etiqueta del componente activo */ /* ( Estado civil) */ enum merital_status ins;

/* parte variable */ unión ( /* soltero */ /* sin componente */ struct ( /* casado */ char marripge_date; char cónyuge_nombre; int no_children; ) matrimonio_info; /* divorciado */ char fecha_divorciado; ) marital_info ;

) registro_salud;

enum marital_status ( SOLTERO, /* soltero */ MARRIGO, /* casado */ DIVORIADO /* divorciado */ );

Puede acceder a los componentes de la estructura mediante enlaces:

Helth_record.neme, helth_record.ins, helth_record.marriage_info.marriage_date.

1.2.11. Definición de objetos y tipos

Como se mencionó anteriormente, se deben declarar todas las variables utilizadas en los programas SI. El tipo de variable que se declara depende de qué palabra clave se utiliza como especificador de tipo y de si el especificador es un identificador simple o una combinación de un identificador con un puntero (asterisco), una matriz (corchetes) o un modificador de función (paréntesis).

Al declarar una variable, estructura, mezcla, unión o enumeración simple, el identificador es un identificador simple. Para declarar un puntero, matriz o función, el identificador se modifica en consecuencia: un asterisco a la izquierda, un cuadrado o paréntesis a la derecha.

Observemos una característica importante del lenguaje SI: al declarar, se puede usar más de un modificador simultáneamente, lo que permite crear muchos descriptores de tipos complejos diferentes.

Sin embargo, debemos recordar que algunas combinaciones de modificadores son inaceptables:

Los elementos de una matriz no pueden ser funciones.

Las funciones no pueden devolver matrices o funciones.

Al inicializar descriptores complejos, los corchetes y paréntesis (a la derecha del identificador) tienen prioridad sobre el asterisco (a la izquierda del identificador). Los cuadrados o paréntesis tienen la misma precedencia y se expanden de izquierda a derecha. El especificador de tipo se considera en el último paso, cuando ya se ha interpretado completamente. Puede utilizar paréntesis para cambiar el orden de interpretación según sea necesario.

3. Si en algún momento se encuentra un paréntesis de cierre a la derecha, entonces todas estas reglas deben aplicarse primero dentro del paréntesis y luego la interpretación debe continuar.

4. Interprete el especificador de tipo.

Int * (* comp ) ();

6 5 3 1 2 4

Este ejemplo declara la variable comp (1) como una matriz de diez (2) punteros (3) a funciones (4) que devuelven punteros (5) a valores enteros (6).

Char * (* (*var) ()) ;

7 6 4 2 1 3 5

La variable var (1) se declara como un puntero (2) a una función (3) que devuelve un puntero (4) a una matriz (5) de 10 elementos, que son punteros (6) a valores char. Además de declarar variables de varios tipos, es posible declarar tipos. Esto se puede hacer de dos maneras. La primera forma es especificar un nombre de etiqueta al declarar una estructura, unión o enumeración, y luego usar ese nombre en declaraciones de variables y funciones como referencia a esa etiqueta. El segundo es utilizar la palabra clave typedef para declarar un tipo. Cuando se anunció desde

palabra clave

typedef, el identificador que reemplaza al objeto que se describe, es el nombre del tipo de datos introducido en consideración, y este tipo puede usarse luego para declarar variables.

Tenga en cuenta que se puede declarar cualquier tipo utilizando la palabra clave typedef, incluidos los tipos de puntero, función o matriz. Se puede declarar un nombre con la palabra clave typedef para tipos de puntero, estructura y unión antes de que se definan esos tipos, pero dentro del alcance del declarante.

Typedef doble(*MATEMÁTICAS)();

/* MATH - nuevo nombre de tipo que representa un puntero a una función que devuelve valores dobles */ MATH cos;

Un controlador abstracto es un identificador no identificador que consta de uno o más modificadores de puntero, matriz o función. El modificador de puntero (*) siempre se coloca antes del identificador en el descriptor, y los modificadores de matriz y función () se colocan después. Por tanto, para interpretar correctamente un descriptor abstracto, se debe comenzar la interpretación con el identificador implícito.

Los descriptores abstractos pueden ser complejos. Los paréntesis en descriptores abstractos complejos especifican el orden de interpretación de manera similar a la interpretación de descriptores complejos en declaraciones.

1.2.12. Inicialización de datos

Cuando se declara una variable, se le puede asignar un valor inicial agregando un iniciador a un declarador. El iniciador comienza con el signo "=" y tiene las siguientes formas.

Formato 1: = iniciador;

Formato 2: = (lista - iniciadores);

El formato 1 se utiliza al inicializar variables de tipos básicos y punteros, y el formato 2 se utiliza al inicializar objetos compuestos.

La variable tol se inicializa con el carácter "N".

const long megabute = (1024 * 1024);

La variable megabute no modificable se inicializa con una expresión constante después de la cual no se puede cambiar.

estático int b = (1,2,3,4);

Se inicializa una matriz bidimensional de b valores enteros; a los elementos de la matriz se les asignan valores de la lista. La misma inicialización se puede hacer así:

estático int b = ((1,2), (3,4));

Al inicializar una matriz, puede omitir una o más dimensiones

estático int b == ".")
cadena[ix] = "_";

Eso es todo lo que queríamos decir sobre la clase string por ahora. De hecho, esta clase tiene muchas más propiedades y capacidades interesantes. Digamos que el ejemplo anterior también se implementa llamando a una única función replace():

Reemplazar (str.begin(), str.end(), ".", "_");

reemplazar() es uno de los algoritmos generales que presentamos en la Sección 2.8 y que se discutirá en detalle en el Capítulo 12. Esta función recorre el rango desde comenzar() hasta finalizar(), que devuelve punteros al principio y al final de la cadena, y reemplaza los elementos , iguales a su tercer parámetro, por el cuarto.

Ejercicio 3.12

Encuentre errores en las siguientes declaraciones:

(a) char ch = "El camino largo y sinuoso"; (b) int ival = (c) char *pc = (d) cadena st(&ch); (e) pc = 0; (i) pc = "0";
(f) st = pc; (j) st =
(g)ch = pc; (k)ch = *pc;
(h) pc = st; (l) *pc = ival;

Ejercicio 3.13

Explique la diferencia en el comportamiento de las siguientes declaraciones de bucle:

Mientras (st++) ++cnt;
mientras (*st++)
++cnt;

Ejercicio 3.14

Se dan dos programas semánticamente equivalentes. El primero usa el tipo de cadena incorporado, el segundo usa la clase de cadena:

// ***** Implementación usando cadenas C ***** #include #incluir
int principal()
{
errores enteros = 0;
const char *pc = "una cadena literal muy larga";< 1000000; ++ix)
{
para (int ix = 0; ix
int len ​​= strlen(pc);
char *pc2 = nuevo char[ len + 1 ];
strcpy(pc2, ordenador);
si (strcmp(pc2, pc))
}
++errores;<< "C-строки: "
<< errors << " ошибок.\n";
eliminar pc2;
corte
int principal()
{
errores enteros = 0;
) // ***** Implementación usando la clase de cadena ***** #include< 1000000; ++ix)
{
#incluir
string str("una cadena literal muy larga");
para (int ix = 0; ix
}
++errores;<< "класс string: "
<< errors << " ошибок.\n;
}

int len ​​= str.size();
cadena cadena2 = cadena;

si (cadena! = cadena2)

¿Qué hacen estos programas?

Resulta que la segunda implementación se ejecuta dos veces más rápido que la primera. ¿Esperabas tal resultado? ¿Cómo lo explicas?

Ejercicio 3.15

¿Podrías mejorar o agregar algo al conjunto de operaciones de la clase de cadena proporcionada en la última sección? Explica tus propuestas< 512; ++index) ... ;

3.5. especificador constante
Tomemos el siguiente ejemplo de código:
Para (int índice = 0; índice

Hay dos problemas con el uso del literal 512. El primero es la facilidad de percepción del texto del programa. ¿Por qué el límite superior de la variable del bucle debería ser exactamente 512? ¿Qué se esconde detrás de este valor? Ella parece aleatoria...< bufSize

El segundo problema se refiere a la facilidad de modificación y mantenimiento del código. Digamos que el programa tiene 10.000 líneas y el literal 512 ocurre en el 4% de ellas. Digamos que en el 80% de los casos el número 512 debe cambiarse por 1024. ¿Te imaginas la complejidad de tal trabajo y la cantidad de errores que se pueden cometer al corregir el valor incorrecto? Resolvemos ambos problemas al mismo tiempo: necesitamos crear un objeto con el valor 512. Al darle un nombre significativo, como bufSize, hacemos que el programa sea mucho más comprensible: queda claro cuál es exactamente la variable del bucle. en comparación con..

Índice< bufSize; ++index)
// ...

En este caso, cambiar el tamaño de bufSize no requiere pasar por 400 líneas de código para modificar 320 de ellas. ¡Cuánto se reduce la probabilidad de errores añadiendo un solo objeto! Ahora el valor es 512.

localizado

La ejecución de este código hará que el valor bufSize sea 1, lo que puede provocar un comportamiento del programa completamente impredecible. Los errores de este tipo suelen ser muy difíciles de detectar porque simplemente no son visibles.
Usar el especificador const resuelve este problema. Al declarar el objeto como

Const int tamaño buf = 512; // tamaño del buffer de entrada

convertimos la variable en una constante con el valor 512, cuyo valor no se puede cambiar: el compilador suprime estos intentos: el uso incorrecto del operador de asignación en lugar de comparación, como en el ejemplo anterior, provocará un error de compilación.

// error: intento asignar un valor a una constante if (bufSize = 0)...

Dado que a una constante no se le puede asignar un valor, debe inicializarse en el lugar donde está definida. Definir una constante sin inicializarla también provoca un error de compilación:

Const doble pi; // error: constante no inicializada

Const doble salario mínimo = 9,60; // ¿Bien? ¿error?
doble *ptr =

¿Debería el compilador permitir tal asignación? Dado que minWage es una constante, no se le puede asignar un valor. Por otra parte, nada nos impide escribir:

*ptr += 1,40; // cambiando el objeto minWage!

Como regla general, el compilador no puede protegerse contra el uso de punteros y no podrá señalar un error si se usan de esta manera. Esto requiere un análisis demasiado profundo de la lógica del programa. Por lo tanto, el compilador simplemente prohíbe la asignación de direcciones constantes a punteros ordinarios.
Entonces, ¿estamos privados de la capacidad de utilizar punteros a constantes? No. Para ello, existen punteros declarados con el especificador const:

Const doble *cptr;

donde cptr es un puntero a un objeto de tipo const double. La sutileza es que el puntero en sí no es una constante, lo que significa que podemos cambiar su valor.

Por ejemplo:
Const doble *pc = 0; const doble salario mínimo = 9,60; // correcto: no podemos cambiar el salario mínimo usando la PC
pc = doble dval = 3,14; // correcto: no podemos cambiar el salario mínimo usando la PC
// aunque dval no es una constante
pc = // dval correcto = 3.14159; //Bien

*pc = 3,14159; // error

La dirección de un objeto constante se asigna únicamente a un puntero a una constante. Al mismo tiempo, a dicho puntero también se le puede asignar la dirección de una variable normal:

Un puntero constante no permite modificar el objeto al que se dirige mediante direccionamiento indirecto. Aunque dval en el ejemplo anterior no es una constante, el compilador no permitirá que se cambie dval a través de la computadora. (Nuevamente, porque no puede determinar qué dirección de objeto puede contener un puntero en ningún momento durante la ejecución del programa).
En programas reales, los punteros a constantes se utilizan con mayor frecuencia como parámetros formales de funciones. Su uso garantiza que el objeto pasado a una función como argumento real no será modificado por esa función. Por ejemplo:

// En programas reales, los punteros a constantes se utilizan con mayor frecuencia // como parámetros formales de funciones int strcmp(const char *str1, const char *str2);

(Hablaremos más sobre punteros constantes en el Capítulo 7, cuando hablemos de funciones).
También hay indicaciones constantes. (¡Tenga en cuenta la diferencia entre un puntero constante y un puntero a una constante!). Un puntero constante puede direccionar una constante o una variable. Por ejemplo:

Número de error int = 0; int *const currErr =

Aquí curErr es un puntero constante a un objeto no constante. Esto significa que no podemos asignarle la dirección de otro objeto, aunque sí se puede modificar el objeto en sí.

Así es como se podría utilizar el puntero curErr:
Hacer_algo(); si (*curErr) (
errorHandler();
}

*curErr = 0; // correcto: restablecer el valor errNumb

Intentar asignar un valor a un puntero constante provocará un error de compilación:

CurErr = // error

Un puntero constante a una constante es una unión de los dos casos considerados.

Const doble pi = 3,14159; constante doble *const pi_ptr = π

Ni el valor del objeto señalado por pi_ptr ni el valor del puntero en sí se pueden cambiar en el programa.

Ejercicio 3.16

Explique el significado de las siguientes cinco definiciones. ¿Alguno de ellos está equivocado?

(a) int i; (d) int *ipc constante; (b) constante; (e) constante int *const cpic; (c) const int *imagen;

Ejercicio 3.17

¿Cuáles de las siguientes definiciones son correctas? ¿Por qué?

(a) int i = -1; (b) constante int ic = i; (c) constante int *pic = (d) int *const cpi = (e) constante int *const cpi =

Ejercicio 3.18

Utilizando las definiciones del ejercicio anterior, identifique los operadores de asignación correctos. Explicar.

(a) i = ic; (d) imagen = cpic; (b) pic = (i) ipc = (c) ipc = pic; (f)ic = *cpic;

Un tipo de referencia, a veces llamado alias, se utiliza para darle a un objeto un nombre adicional. Una referencia le permite manipular indirectamente un objeto, tal como puede hacerlo con un puntero. Sin embargo, esta manipulación indirecta no requiere la sintaxis especial necesaria para los punteros. Normalmente, las referencias se utilizan como parámetros formales de funciones. En esta sección, veremos el uso de objetos de tipo de referencia por nuestra cuenta.
Un tipo de referencia se indica especificando el operador de dirección (&) antes del nombre de la variable. El enlace debe estar inicializado. Por ejemplo:

Int ival = 1024; // correcto: refVal - referencia a ival int &refVal = ival; // error: la referencia debe inicializarse en int

Int ival = 1024; // error: refVal es de tipo int, no int* int &refVal = int *pi = // correcto: ptrVal es una referencia a un puntero int *&ptrVal2 = pi;

Una vez definida una referencia, no puede cambiarla para que funcione con otro objeto (razón por la cual la referencia debe inicializarse en el punto donde se define). En el siguiente ejemplo, el operador de asignación no cambia el valor de refVal; el nuevo valor se asigna a la variable ival, la que refVal aborda.

Int valor_mínimo = 0; // ival recibe el valor de min_val, // en lugar de refVal cambia el valor a min_val refVal = min_val;

ValorRef += 2; suma 2 a ival, la variable a la que hace referencia refVal. De manera similar int ii = refVal; asigna a ii el valor actual de ival, int *pi = inicializa pi con la dirección de ival.

// se definen dos objetos de tipo int int ival = 1024, ival2 = 2048; // una referencia y un objeto definidos int &rval = ival, rval2 = ival2; // se definen un objeto, un puntero y una referencia
int inal3 = 1024, *pi = ival3, &ri = ival3; // se definen dos enlaces int &rval3 = ival3, &rval4 = ival2;

Una referencia constante puede inicializarse mediante un objeto de otro tipo (suponiendo, por supuesto, que sea posible convertir un tipo en otro), así como mediante un valor sin dirección, como una constante literal. Por ejemplo:

doble dval = 3,14159; // verdadero sólo para referencias constantes
constante int &ir = 1024;
const int &ir2 = dval;
constante doble &dr = dval + 1,0;

Si no hubiéramos especificado el especificador constante, las tres definiciones de referencia habrían generado un error de compilación. Sin embargo, la razón por la cual el compilador no pasa tales definiciones no está clara. Intentemos resolverlo.
Para los literales, esto está más o menos claro: no deberíamos poder cambiar indirectamente el valor de un literal utilizando punteros o referencias. En cuanto a objetos de otros tipos, el compilador convierte el objeto original en algún objeto auxiliar. Por ejemplo, si escribimos:

doble valor = 1024; constante int &ri = dval;

luego el compilador lo convierte a algo como esto:

temperatura interna = dval; constante int &ri = temp;

Si pudiéramos asignar un nuevo valor a la referencia ri, en realidad cambiaríamos no dval, sino temp. El valor dval seguiría siendo el mismo, lo cual no es nada obvio para el programador. Por lo tanto, el compilador prohíbe tales acciones y la única forma de inicializar una referencia con un objeto de otro tipo es declararla como constante.
Aquí hay otro ejemplo de un enlace que es difícil de entender la primera vez. Queremos definir una referencia a la dirección de un objeto constante, pero nuestra primera opción provoca un error de compilación:

Intervalo constante = 1024; // error: se necesita referencia constante
int*&pi_ref=

También falla un intento de corregir el problema agregando un especificador constante:

Intervalo constante = 1024; // sigue siendo un error const int *&pi_ref =

¿Cuál es la razón? Si leemos atentamente la definición, veremos que pi_ref es una referencia a un puntero constante a un objeto de tipo int. Y necesitamos un puntero no constante a un objeto constante, por lo que la siguiente entrada sería correcta:

Intervalo constante = 1024; // Bien
int *const &piref =

Hay dos diferencias principales entre un enlace y un puntero. Primero, el enlace debe inicializarse en el lugar donde está definido. En segundo lugar, cualquier cambio en un vínculo transforma no el vínculo, sino el objeto al que se refiere.

Veamos ejemplos. Si escribimos:

Ent *pi = 0;

Inicializamos el puntero pi a cero, lo que significa que pi no apunta a ningún objeto. Al mismo tiempo grabando
constante int &ri = 0;
significa algo como esto:
temperatura interna = 0;

constante int &ri = temp;

En cuanto a la operación de asignación, en el siguiente ejemplo:

Int ival = 1024, ival2 = 2048; int *pi = &ival, *pi2 = pi = pi2;
la variable ival apuntada por pi permanece sin cambios y pi recibe el valor de la dirección de la variable ival2. Tanto pi como pi2 ahora apuntan al mismo objeto, ival2.

Si trabajamos con enlaces:

Int &ri = ival, &ri2 = ival2; ri = ri2;
// ejemplo de uso de enlaces // El valor se devuelve en el parámetro next_value

internacional; mientras (get_next_value(ival)) ...

Int &next_value = ival;

(El uso de referencias como parámetros formales de funciones se analiza con más detalle en el Capítulo 7.)

Ejercicio 3.19

¿Hay algún error en estas definiciones? Explicar. ¿Cómo los arreglarías?

(a) intervalo = 1,01; (b) int &rval1 = 1,01; (c) int &rval2 = ival; (d) int &rval3 = (e) int *pi = (f) int &rval4 = pi; (g) int &rval5 = pi*; (h) int &*prval1 = pi; (i) constante int &ival2 = 1; (j) constante int &*prval2 =

Ejercicio 3.20

¿Alguno de los siguientes operadores de asignación es erróneo (usando las definiciones del ejercicio anterior)?

(a) rval1 = 3,14159; (b) prval1 = prval2; (c) prval2 = rval1; (d) *prval2 = ival2;

Ejercicio 3.21

Encuentre errores en las instrucciones dadas:

(a) intervalo = 0;
constante int *pi = 0;
constante int &ri = 0; (b)pi =

ri =

pi =

3.7. Tipo booleano
Un objeto de tipo bool puede tomar uno de dos valores: verdadero y falso. Por ejemplo:
// inicialización de la cadena string search_word = get_word(); // inicialización de la variable encontrada
bool encontrado = falso; cadena siguiente_palabra; mientras (cin >> palabra_siguiente)
si (siguiente_palabra == palabra_buscada)
encontrado = verdadero;
++errores;<< "ok, мы нашли слово\n";
// ... // taquigrafía: si (encontrado == verdadero)<< "нет, наше слово не встретилось.\n";

si (encontrado)

más cout

Aunque bool es uno de los tipos de enteros, no se puede declarar como con signo, sin signo, corto o largo, por lo que la definición anterior es incorrecta:

// error bool corto encontrado = falso;
{
Los objetos de tipo bool se convierten implícitamente al tipo int. verdadero se convierte en 1 y falso se convierte en 0. Por ejemplo:
Bool encontrado = falso; int recuento_ocurrencias = 0; mientras (/*murmullo */)

encontrado = buscar_for(/* algo */);

// el valor encontrado se convierte a 0 o 1
recuento_ocurrencias += encontrado; )

De la misma manera, los valores de tipos enteros y punteros se pueden convertir a valores de tipo bool. En este caso, 0 se interpreta como falso y todo lo demás como verdadero:

// devuelve el número de apariciones extern int find(const string&); bool encontrado = falso; if (found = find("rosebud")) // correcto: found == true // devuelve un puntero al elemento
int externo* buscar(valor int); if (encontrado = encontrar(1024)) // correcto: encontrado == verdadero

3.8. Transferencias

A menudo hay que definir una variable que tome valores de un determinado conjunto. Digamos que un archivo se abre en cualquiera de estos tres modos: para leer, para escribir y para agregar.

Bool open_file(cadena nombre_archivo, int open_mode); //...
open_file("Phoenix_and_the_Grúa", agregar);

Esta solución es posible, pero no del todo aceptable, ya que no podemos garantizar que el argumento pasado a la función open_file() sea solo 1, 2 o 3.
Usar un tipo de enumeración resuelve este problema. Cuando escribimos:

Enum open_modes (entrada = 1, salida, agregar);

definimos un nuevo tipo open_modes. Los valores válidos para un objeto de este tipo están limitados al conjunto de 1, 2 y 3, y cada uno de los valores especificados tiene un nombre mnemotécnico. Podemos usar el nombre de este nuevo tipo para definir tanto el objeto de ese tipo como el tipo de los parámetros formales de la función:

Void open_file(cadena nombre_archivo, open_modes om);

entrada, salida y anexar son elementos de enumeración. El conjunto de elementos de enumeración especifica el conjunto de valores permitido para un objeto de un tipo determinado.

Una variable de tipo open_modes (en nuestro ejemplo) se inicializa con uno de estos valores y también se le puede asignar cualquiera de ellos; Por ejemplo:

Open_file("El Fénix y la Grulla", agregar);

Un intento de asignar un valor distinto de uno de los elementos de enumeración a una variable de este tipo (o pasarlo como parámetro a una función) provocará un error de compilación. Incluso si intentamos pasar un valor entero correspondiente a uno de los elementos de la enumeración, seguiremos recibiendo un error:

// error: 1 no es un elemento de la enumeración open_modes open_file("Jonah", 1);

Existe una manera de definir una variable de tipo open_modes, asignarle el valor de uno de los elementos de enumeración y pasarlo como parámetro a la función:

Modos_abiertos om = entrada;

// ... om = agregar;<< input << " " << om << endl;

open_file("TailTell", om);

Sin embargo, no es posible obtener los nombres de dichos elementos. Si escribimos la declaración de salida:

// ... om = agregar;<< open_modes_table[ input ] << " " << open_modes_table[ om ] << endl Будет выведено: input append

corte

entonces todavía obtenemos:

Para definir una enumeración, utilice la palabra clave enum y los nombres de los elementos se especifican entre llaves, separados por comas. Por defecto, el primero es 0, el siguiente es 1, y así sucesivamente. Puede cambiar esta regla utilizando el operador de asignación. En este caso, cada elemento posterior sin un valor especificado explícitamente será 1 más que el elemento que le precede en la lista. En nuestro ejemplo, especificamos explícitamente el valor 1 para la entrada, y la salida y el agregado serán iguales a 2 y 3. Aquí hay otro ejemplo:

// forma == 0, esfera == 1, cilindro == 2, polígono == 3 enum Formas (compartir, esfera, cilindro, polígono);

Los valores enteros correspondientes a diferentes elementos de una misma enumeración no tienen por qué ser diferentes. Por ejemplo:

// punto2d == 2, punto2w == 3, punto3d == 3, punto3w == 4 enumeración Puntos ( punto2d=2, punto2w, punto3d=3, punto3w=4 );

Un objeto cuyo tipo es una enumeración se puede definir, utilizar en expresiones y pasar a una función como argumento. Un objeto de este tipo se inicializa sólo con el valor de uno de los elementos de enumeración, y sólo se le asigna ese valor, ya sea explícitamente o como el valor de otro objeto del mismo tipo. Incluso no se le pueden asignar valores enteros correspondientes a elementos de enumeración válidos:

Void mumble() ( Puntos pt3d = punto3d; // correcto: pt2d == 3 // error: pt3w se inicializa con el tipo int Puntos pt3w = 3; // error: el polígono no está incluido en la enumeración de Puntos pt3w = polígono; / /correcto: ambos objetos de tipo Puntos pt3w = pt3d )

Sin embargo, en expresiones aritméticas, una enumeración se puede convertir automáticamente al tipo int. Por ejemplo:

Const int tamaño_matriz = 1024; // correcto: pt2w se convierte a int
int tamaño_fragmento = tamaño_matriz * pt2w;

3.9. Escriba "matriz"

Ya tocamos las matrices en la sección 2.1. Una matriz es un conjunto de elementos del mismo tipo, al que se accede mediante índice: el número de serie del elemento en la matriz. Por ejemplo:

internacional;

define ival como una variable int, y la instrucción

Int ia[ 10 ];

especifica una matriz de diez objetos de tipo int. A cada uno de estos objetos, o elementos de matriz, se puede acceder mediante la operación de índice:

Ival = ia[ 2 ];

asigna a la variable ival el valor del elemento de matriz ia con índice 2. De manera similar

Ia[ 7 ] = ival;

asigna al elemento del índice 7 el valor ival.

Una definición de matriz consta de un especificador de tipo, un nombre de matriz y un tamaño.

El tamaño especifica el número de elementos de la matriz (al menos 1) y está entre corchetes. El tamaño de la matriz debe conocerse ya en la etapa de compilación y, por lo tanto, debe ser una expresión constante, aunque no necesariamente se especifica como literal.
A continuación se muestran ejemplos de definiciones de matrices correctas e incorrectas:
Externo int get_size(); // constantes buf_size y max_files

const int buf_size = 512, max_files = 20;
int tamaño_personal = 27; // correcto: carácter constante input_buffer[ buf_size ]; // correcto: expresión constante: 20 - 3 char *fileTable[ max_files-3 ]; // error: no es un salario doble constante[ staff_size ]; // error: no es una expresión constante int test_scores[ get_size() ];
Los objetos buf_size y max_files son constantes, por lo que las definiciones de matriz input_buffer y fileTable son correctas. Pero staff_size es una variable (aunque inicializada con la constante 27), lo que significa que los salarios son inaceptables. (El compilador no puede encontrar el valor de la variable staff_size cuando se define la matriz de salarios).

La expresión max_files-3 se puede evaluar en tiempo de compilación, por lo que la definición de la matriz fileTable es sintácticamente correcta.< array_size; ++ ix)
La numeración de elementos comienza en 0, por lo que para una matriz de 10 elementos el rango de índice correcto no es 1 – 10, sino 0 – 9. A continuación se muestra un ejemplo de iteración a través de todos los elementos de la matriz:
}

Int main() ( const int tamaño_matriz = 10; int ia[ tamaño_matriz ]; for (int ix = 0; ix

ia[ ix ] = ix;

Al definir una matriz, puede inicializarla explícitamente enumerando los valores de sus elementos entre llaves, separados por comas:

Const int tamaño_matriz = 3; int ia[ tamaño_matriz ] = ( 0, 1, 2 );

Si especificamos explícitamente una lista de valores, no tenemos que especificar el tamaño de la matriz: el propio compilador contará el número de elementos:

// matriz de tamaño 3 int ia = ( 0, 1, 2 );

Cuando se especifican explícitamente tanto el tamaño como la lista de valores, son posibles tres opciones. Si el tamaño y el número de valores coinciden, todo es obvio. Si la lista de valores es más corta que el tamaño especificado, los elementos restantes de la matriz se inicializan a cero.

Si hay más valores en la lista, el compilador muestra un mensaje de error:

La dimensión de la matriz ca1 es 3, la matriz ca2 es 4 (en cadenas literales, se tiene en cuenta el carácter nulo final). La siguiente definición provocará un error de compilación:

// error: la cadena "Daniel" consta de 7 elementos const char ch3[ 6 ] = "Daniel";

A una matriz no se le puede asignar el valor de otra matriz y no se permite la inicialización de una matriz por otra. Además, no está permitido utilizar una serie de referencias.

A continuación se muestran ejemplos del uso correcto e incorrecto de matrices:
{
Const int tamaño_matriz = 3; int ix, jx, kx; // correcto: matriz de punteros de tipo int* int *iar = ( &ix, &jx, &kx ); // error: no se permiten matrices de referencia int &iar = (ix, jx, kx); int principal()
int ia3( tamaño_matriz ]; // correcto
// error: las matrices integradas no se pueden copiar
ia3 = ia;
}

devolver 0;

Para copiar una matriz a otra, debes hacer esto para cada elemento por separado:
Const int tamaño_matriz = 7; int ia1 = (0, 1, 2, 3, 4, 5, 6); int principal() (< array_size; ++ix)
int ia3[ tamaño_matriz ];
}

para (int ix = 0; ix

ia2[ ix ] = ia1[ ix ];

devolver 0;

Un índice de matriz puede ser cualquier expresión que produzca un resultado de tipo entero. Por ejemplo:

Int algúnVal, get_index(); ia2[ get_index() ] = algúnVal;

Destacamos que el lenguaje C++ no proporciona control de los índices de la matriz, ni en la etapa de compilación ni en la etapa de ejecución. El propio programador debe asegurarse de que el índice no traspase los límites de la matriz. Los errores al trabajar con un índice son bastante comunes. Desafortunadamente, no es muy difícil encontrar ejemplos de programas que se compilan e incluso funcionan, pero que aún así contienen errores fatales que, tarde o temprano, provocan fallos.

Ejercicio 3.22

¿Cuáles de las siguientes definiciones de matriz son incorrectas? Explicar.

(a) int ia[buf_size]; (d) int ia[ 2 * 7 - 14 ] (b) int ia[ get_size() ]; (e) char st[ 11 ] = "fundamental"; (c) int ia[4 * 7 - 14];<= array_size; ++ix)
Ejercicio 3.23
}

El siguiente fragmento de código debe inicializar cada elemento de la matriz con un valor de índice. Encuentra los errores cometidos:

Int main() ( const int tamaño_matriz = 10; int ia[ tamaño_matriz ]; for (int ix = 1; ix

ia[ ia ] = ix;

El primer valor (4) especifica el número de filas, el segundo (3), el número de columnas.

El objeto ia se define como una matriz de cuatro cadenas de tres elementos cada una. También se pueden inicializar matrices multidimensionales:

Int ia[ 4 ][ 3 ] = ( ( 0, 1, 2 ), ( 3, 4, 5 ), ( 6, 7, 8 ), ( 9, 10, 11 ) );

Las llaves internas, que dividen la lista de valores en líneas, son opcionales y generalmente se usan para hacer que el código sea más fácil de leer. La inicialización a continuación es exactamente la misma que el ejemplo anterior, aunque es menos clara:

Int ia = ( 0,1,2,3,4,5,6,7,8,9,10,11 );

La siguiente definición inicializa solo los primeros elementos de cada línea.

Los elementos restantes serán cero:

Int ia[ 4 ][ 3 ] = ( (0), (3), (6), (9) );

Si omite las llaves internas, el resultado será completamente diferente. Los tres elementos de la primera fila y el primer elemento de la segunda recibirán el valor especificado y el resto se inicializará implícitamente a 0.

Int ia[ 4 ][ 3 ] = ( 0, 3, 6, 9 );< rowSize; ++i)
Al acceder a elementos de una matriz multidimensional, debe utilizar índices para cada dimensión (están entre corchetes). Así es como se ve la inicialización de una matriz bidimensional usando bucles anidados:< colSize; ++j)
Int main() ( const int tamañofila = 4; const int tamañocol = 3; int ia[ tamañofila ][ tamañocol ]; for (int = 0; i
}

para (int j = 0; j

ia[ yo ][ j ] = yo + j j;

Diseño

Ia[ 1, 2 ]

es válido desde el punto de vista de la sintaxis de C++, pero no significa en absoluto lo que espera un programador sin experiencia. Esta no es una declaración de una matriz bidimensional de 1 por 2. Un agregado entre corchetes es una lista de expresiones separadas por comas que dará como resultado el valor final 2 (consulte el operador de coma en la Sección 4.2). Por tanto la declaración ia es equivalente a ia.

Esta es otra oportunidad para cometer un error.

3.9.2. Relación entre matrices y punteros.

Si tenemos una definición de matriz:

Int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 );

Entonces ¿qué significa simplemente indicar su nombre en el programa?

Usar un identificador de matriz en un programa equivale a especificar la dirección de su primer elemento:

De manera similar, puedes acceder al valor del primer elemento de una matriz de dos maneras:

// ambas expresiones devuelven el primer elemento *ia; Iowa;

Para tomar la dirección del segundo elemento del array debemos escribir:

Como mencionamos anteriormente, la expresión

también proporciona la dirección del segundo elemento de la matriz. En consecuencia, su significado se nos da de las dos maneras siguientes:

*(ia+1); Iowa; Tenga en cuenta la diferencia en las expresiones: que la operación de suma (las prioridades del operador se analizan en la Sección 4.13).

Por lo tanto, la primera expresión primero desreferencia la variable ia y obtiene el primer elemento de la matriz, y luego le suma 1. La segunda expresión entrega el valor del segundo elemento.

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: #incluir<< *pbegin <<; ++pbegin; } }

int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); int *pbegin = ia; int *pend = ia + 9; while (pbegin != pend) ( corte

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: El puntero pbegin se inicializa en la dirección del primer elemento de la matriz. Cada paso por el bucle incrementa este puntero en 1, lo que significa que se desplaza al siguiente elemento. ¿Cómo sabes dónde quedarte? En nuestro ejemplo, definimos un segundo puntero pend y lo inicializamos con la dirección que sigue al último elemento de la matriz ia. Tan pronto como el valor de pbegin sea igual a pend, sabremos que la matriz ha terminado. Reescribamos este programa para que el principio y el final de la matriz se pasen como parámetros a una determinada función generalizada que puede imprimir una matriz de cualquier tamaño:
void ia_print(int *pbegin, int *pend) (
++errores;<< *pbegin << " ";
mientras (pbegin != pendiente) (
}
++pcomenzar;
{
) int principal()
int ia = (0, 1, 1, 2, 3, 5, 8, 13, 21);
}

ia_print(ia, ia + 9);

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: Nuestra función se ha vuelto más universal, sin embargo, solo puede funcionar con matrices de tipo int. Hay una manera de eliminar esta limitación: convertir esta función en una plantilla (las plantillas se introdujeron brevemente en la sección 2.5): plantilla<< *pbegin << " "; ++pbegin; } }

void print(elemType *pbegin, elemType *pend) ( while (pbegin != pend) ( cout

Ahora podemos llamar a nuestra función print() para imprimir matrices de cualquier tipo:
Int main() ( int ia = ( 0, 1, 1, 2, 3, 5, 8, 13, 21 ); double da = ( 3.14, 6.28, 12.56, 25.12 ); string sa = ( "lechón", " eeyore", "pooh" ); print(ia, ia+9);
print(da, da+4);
}

imprimir(sa, sa+3); escribimos generalizado

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: función. La biblioteca estándar proporciona un conjunto de algoritmos genéricos (ya lo mencionamos en la Sección 3.4) implementados de manera similar. Los parámetros de dichas funciones son punteros al principio y al final de la matriz con la que realizan determinadas acciones. Así es, por ejemplo, cómo se ven las llamadas a un algoritmo de clasificación generalizado:
int main() ( int ia = ( 107, 28, 3, 47, 104, 76 ); string sa = ( "cerdito", "eeyore", "pooh" ); sort(ia, ia+6);
};

ordenar(sa, sa+3);
La biblioteca estándar de C++ contiene un conjunto de clases que encapsulan el uso de contenedores y punteros. (Esto se analizó en la Sección 2.8.) En la siguiente sección, veremos el vector de tipo contenedor estándar, que es una implementación orientada a objetos de una matriz.

3.10. clase vectorial

Usar la clase vectorial (ver Sección 2.8) es una alternativa al uso de matrices integradas. Esta clase proporciona mucha más funcionalidad, por lo que es preferible su uso. Sin embargo, hay situaciones en las que no puede prescindir de matrices del tipo integrado. Una de estas situaciones es el procesamiento de los parámetros de la línea de comando pasados ​​al programa, que discutiremos en la sección 7.8. La clase de vector, al igual que la clase de cadena, es parte de la biblioteca estándar de C++.
Para utilizar el vector debes incluir el archivo de encabezado:

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo:

Hay dos enfoques completamente diferentes para usar un vector, llamémoslos modismo de matriz y modismo STL. En el primer caso, un objeto vectorial se utiliza exactamente de la misma manera que una matriz de tipo integrado. Se determina un vector de una dimensión dada:

Vector< int >ivec(10);

que es similar a definir una matriz de un tipo incorporado:

Int ia[ 10 ];

Para acceder a elementos individuales de un vector, se utiliza la operación de índice:

Vacío simp1e_examp1e() ( const int e1em_size = 10; vector< int >ivec(e1em_size);< e1em_size; ++ix)
int ia[ e1em_size ];
}

para (int ix = 0; ix

ia[ ix ] = ivec[ ix ]; //...< ivec.size(); ++ix)
++errores;<< ivec[ ix ] << " ";
}

Podemos averiguar la dimensión de un vector usando la función size() y verificar si el vector está vacío usando la función vacía(). Por ejemplo:

Vector< int >Vacío print_vector(vector

ivec) (si (ivec.empty()) regresa; para (int ix=0; ix
Los elementos del vector se inicializan con valores predeterminados. Para tipos numéricos y punteros, este valor es 0. Si los elementos son objetos de clase, entonces el iniciador para ellos lo especifica el constructor predeterminado (ver sección 2.3). Sin embargo, el iniciador también se puede especificar explícitamente mediante el formulario:

ivec(10, -1);

Los diez elementos del vector serán iguales a -1.

Una matriz de un tipo incorporado se puede inicializar explícitamente con una lista:< int >Int ia[ 6 ] = ( -2, -1, O, 1, 2, 1024 );

Una acción similar no es posible para un objeto de la clase vectorial. Sin embargo, dicho objeto se puede inicializar utilizando un tipo de matriz incorporado:

// Se copian 3 elementos: ia, ia, ia vector< int >ivec(&ia[ 2 ], &ia[ 5 ]);

Otra diferencia entre un vector y una matriz integrada es la capacidad de inicializar un objeto vectorial con otro y utilizar el operador de asignación para copiar objetos. Por ejemplo:

Vector< string >svec; void init_and_assign() ( // un vector es inicializado por otro vector< string >nombres_usuario(svec);
// ... // un vector se copia a otro
}

svec = nombres_usuario;

Vector< string >Cuando hablamos del lenguaje STL, nos referimos a un enfoque completamente diferente al uso de un vector. En lugar de especificar inmediatamente el tamaño deseado, definimos un vector vacío:

texto;

Luego le agregamos elementos usando varias funciones. Por ejemplo, la función push_back() inserta un elemento al final de un vector. Aquí hay un fragmento de código que lee una secuencia de líneas de la entrada estándar y las agrega a un vector:

Palabra de cadena;

// ... om = agregar;<< "считаны слова: \n"; for (int ix =0; ix < text.size(); ++ix) cout << text[ ix ] << " "; cout << endl;

mientras (cin >> palabra) ( text.push_back(palabra); // ... )

// ... om = agregar;<< "считаны слова: \n"; for (vectorAunque podemos utilizar la operación de índice para iterar sobre los elementos de un vector:<< *it << " "; cout << endl;

Un uso más típico de iteradores dentro de este modismo sería:
::iterador it = text.begin(); eso != texto.end(); ++it) cout

Un iterador es una clase de biblioteca estándar que es esencialmente un puntero a un elemento de matriz.

Expresión

Vector elimina la referencia al iterador y proporciona el elemento vectorial en sí. Instrucciones

Mueve el puntero al siguiente elemento. No es necesario mezclar estos dos enfoques.

Si sigues el modismo STL al definir un vector vacío:

ivec;

Vector Sería un error escribir:

Todavía no tenemos ni un solo elemento del vector ivec; El número de elementos se determina mediante la función size().

También se puede cometer el error contrario. Si definimos un vector de algún tamaño, por ejemplo:< int >yo(10);< size; ++ix) ivec.push_back(ia[ ix ]);

Luego, al insertar elementos, aumenta su tamaño, agregando nuevos elementos a los existentes.
Aunque esto parezca obvio, un programador novato bien podría escribir:

Tamaño int constante = 7; int ia[ tamaño ] = ( 0, 1, 1, 2, 3, 5, 8 ); vector

ivec(tamaño); para (int ix = 0; ix
Esto significó inicializar el vector ivec con los valores de los elementos ia, lo que resultó en un vector ivec de tamaño 14.

Siguiendo el lenguaje STL, no sólo puedes agregar sino también eliminar elementos de un vector.< vector< int >(Veremos todo esto en detalle y con ejemplos en el Capítulo 6.)
Ejercicio 3.24< int >¿Hay algún error en las siguientes definiciones?
int ia[ 7 ] = ( 0, 1, 1, 2, 3, 5, 8 );< int >(un) vector
>ivec;< string >(b)vector
ivec = (0, 1, 1, 2, 3, 5, 8);< string >(c)vector

ivec(ia, ia+7);

(d)vector
bool es_equal(const int*ia, int ia_size,
vector constante &ivec);
La función is_equal() compara dos contenedores elemento por elemento. En el caso de contenedores de distintos tamaños no se tiene en cuenta la “cola” del más largo. Está claro que si todos los elementos comparados son iguales, la función devuelve verdadero, si al menos uno es diferente, falso. Utilice un iterador para iterar sobre elementos. Escriba una función main() que llame a is_equal().

3.11. complejo de clases

La clase de números complejos es otra clase de la biblioteca estándar.

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo:

Como es habitual, para utilizarlo es necesario incluir el archivo de cabecera:

Un número complejo consta de dos partes: real e imaginaria. La parte imaginaria es la raíz cuadrada de un número negativo. Un número complejo generalmente se escribe en la forma

donde 2 es la parte real y 3i es la parte imaginaria. A continuación se muestran ejemplos de definiciones de objetos de tipo complejo:< double >// número puramente imaginario: 0 + 7-i complejo< float >puroi(0, 7); // la parte imaginaria es 0: 3 + Oi complejo< long double >rea1_num(3); // tanto la parte real como la imaginaria son iguales a 0: 0 + 0-i complejo< double >cero; // inicialización de un número complejo con otro complejo

purei2(purei);

Dado que complejo, como vector, es una plantilla, podemos crear una instancia con los tipos float, double y long double, como en los ejemplos dados. También puedes definir una matriz de elementos de tipo complejo:< double >Complejo< double >conjugado[ 2 ] = (complejo< double >(2, -3) };

Dado que complejo, como vector, es una plantilla, podemos crear una instancia con los tipos float, double y long double, como en los ejemplos dados. También puedes definir una matriz de elementos de tipo complejo:< double >(2, 3), complejo< double >*ptr = complejo

&ref = *ptr;

3.12. directiva typedef

La directiva typedef le permite especificar un sinónimo para un tipo de datos integrado o personalizado. Por ejemplo: Typedef doble salario; vector de definición de tipo

vec_int; typedef vec_int test_scores; typedef bool en_asistencia; typedef int *Pinta;

Los nombres definidos usando la directiva typedef se pueden usar exactamente de la misma manera que los especificadores de tipo: // doble por hora, semanal; salarios por hora, semanalmente; //vector
vecl(10); vec_int vecl(10); //vector< bool >prueba0(c1ass_size); const int clase_tamaño = 34; test_scores test0(c1ass_size); //vector< in_attendance >asistencia; vector

asistencia(c1ass_size); // int *tabla[ 10 ]; Mesa de pinta [10];
¿Para qué se utilizan los nombres definidos mediante la directiva typedef? Al utilizar nombres mnemotécnicos para los tipos de datos, puede hacer que su programa sea más fácil de entender. Además, es común usar dichos nombres para tipos compuestos complejos que de otro modo serían difíciles de entender (ver el ejemplo en la Sección 3.14), para declarar punteros a funciones y funciones miembro de una clase (ver la Sección 13.6).
A continuación se muestra un ejemplo de una pregunta que casi todo el mundo responde incorrectamente. El error se debe a una mala interpretación de la directiva typedef como una simple sustitución de macro de texto.

Definición dada:

Typedef char *cstring;

¿Cuál es el tipo de variable cstr en la siguiente declaración?

Const externa cstring cstr;

La respuesta que parece obvia es:

Carácter constante *cstr

Sin embargo, esto no es cierto. El especificador constante hace referencia a cstr, por lo que la respuesta correcta es un puntero constante a char:

Char *const cstr;

3.13. especificador volátil
Un objeto se declara volátil si su valor se puede cambiar sin que el compilador se dé cuenta, como una variable actualizada por el reloj del sistema. Este especificador le dice al compilador que no necesita optimizar el código para trabajar con este objeto.

El especificador volátil se usa de manera similar al especificador constante:

Volátil int disp1ay_register; Tarea volátil *curr_task; volátil int ixa[ max_size ]; Pantalla volátil bitmap_buf;
display_register es un objeto volátil de tipo int. curr_task: puntero a un objeto volátil de la clase Task. ixa es una matriz inestable de números enteros y cada elemento de dicha matriz se considera inestable. bitmap_buf es un objeto volátil de la clase Screen, cada uno de sus miembros de datos también se considera volátil.

El único propósito de utilizar el especificador volátil es decirle al compilador que no puede determinar quién puede cambiar el valor de un objeto determinado y cómo.

Por lo tanto, el compilador no debe realizar optimizaciones en el código que utiliza este objeto. 3.14. par de clases La clase de par de la biblioteca estándar de C++ nos permite definir un par de valores con un objeto si existe alguna conexión semántica entre ellos.

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo:

Estos valores pueden ser iguales o

diferentes tipos< string, string >. Para utilizar esta clase debes incluir el archivo de encabezado:

Por ejemplo, instrucciones
Par

autor("James", "Joyce");
Joyce.segundo == "Joyce")
primerLibro = "Stephen Hero";

Si necesita definir varios objetos del mismo tipo de esta clase, es conveniente utilizar la directiva typedef:

par de definición de tipo< string, string >Autores; Autores proust("marcel", "proust"); Autores joyce("James", "Joyce"); Autores musil("robert", "musi1");

Aquí hay otro ejemplo del uso de un par. El primer valor contiene el nombre de algún objeto, el segundo es un puntero al elemento de la tabla correspondiente a este objeto.

Ranura de entrada de clase; Ranura de entrada externa* 1ook_up(cadena); par typedef< string, EntrySlot* >Entrada de símbolo; SymbolEntry current_entry("autor", 1ook_up("autor"));
// ... if (EntrySlot *it = 1ook_up("editor")) (
current_entry.first = "editor";
entrada_actual.segundo = eso;
}

(Volveremos a la clase de pares cuando hablemos de tipos de contenedores en el Capítulo 6 y de algoritmos genéricos en el Capítulo 12).

3.15. Tipos de clases

El mecanismo de clases le permite crear nuevos tipos de datos; con su ayuda, se introdujeron los tipos de cadena, vector, complejo y par discutidos anteriormente. En el Capítulo 2, introdujimos los conceptos y mecanismos que respaldan los enfoques orientados a objetos y a objetos, utilizando el ejemplo de la implementación de la clase Array. Aquí, basándonos en el enfoque de objetos, crearemos una clase String simple, cuya implementación nos ayudará a comprender, en particular, la sobrecarga de operadores; hablamos de ello en la sección 2.3. (Las clases se tratan en detalle en los Capítulos 13, 14 y 15). Hemos dado una breve descripción de la clase para dar ejemplos más interesantes. Un lector nuevo en C++ puede saltarse esta sección y esperar una descripción más sistemática de las clases en capítulos posteriores.)
Nuestra clase String debe admitir la inicialización mediante un objeto de la clase String, un literal de cadena y un tipo de cadena incorporado, así como la operación de asignar valores a estos tipos. Usamos constructores de clases y un operador de asignación sobrecargado para esto. El acceso a caracteres de cadena individuales se implementará como una operación de índice sobrecargada. Además, necesitaremos: la función size() para obtener información sobre la longitud de la cadena; la operación de comparar objetos de tipo String y un objeto String con una cadena de un tipo incorporado; así como las operaciones de E/S de nuestro objeto. Finalmente, implementamos la capacidad de acceder a la representación interna de nuestra cadena como un tipo de cadena incorporado.
Una definición de clase comienza con la palabra clave clase, seguida de un identificador: el nombre de la clase o tipo. En general, una clase consta de secciones precedidas por las palabras pública (abierta) y privada (cerrada). Una sección pública normalmente contiene un conjunto de operaciones admitidas por la clase, llamadas métodos o funciones miembro de la clase. Estas funciones miembro definen la interfaz pública de la clase, en otras palabras, el conjunto de acciones que se pueden realizar sobre los objetos de esa clase. Una sección privada normalmente incluye miembros de datos que proporcionan implementación interna. En nuestro caso, los miembros internos incluyen _string, un puntero a un carácter, así como _size de tipo int. _size almacenará información sobre la longitud de la cadena y _string será una matriz de caracteres asignada dinámicamente. Así es como se ve una definición de clase:

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: clase Cadena; istream& operador>>(istream&, String&);
ostream& operador<<(ostream&, const String&); class String {
público:
// conjunto de constructores
// para inicialización automática
// cadena cadena; // Cadena()
// Cadena str2("literal"); // Cadena(const char*);
// Cadena str3(str2); // Cadena (cadena constante&);
Cadena();
Cadena (carácter constante*);
Cadena (cadena constante&);
// destructor
~Cadena();
// operadores de asignación
// cadena = cadena2
// str3 = "una cadena literal" String& operador=(const String&);
Cadena& operador=(const char*);
// operadores de prueba de igualdad
// cadena == cadena2;
// str3 == "una cadena literal";
operador bool==(const String&);
operador bool==(const char*);
}

// sobrecargar el operador de acceso por índice

// cadena[ 0 ] = cadena2[ 0 ]; operador char&(int);// acceso a los miembros de la clase

int tamaño() (retorna _tamaño;)
char* c_str() (retorna _string;) privado:

int_tamaño;

carácter *_cadena;

La clase String tiene tres constructores. Como se discutió en la Sección 2.3, el mecanismo de sobrecarga le permite definir múltiples implementaciones de funciones con el mismo nombre, siempre que todas difieran en el número y/o tipos de sus parámetros.

Primer constructor

Es el constructor predeterminado porque no requiere

indicación explícita

valor inicial. Cuando escribimos:
Para str1, se llama a dicho constructor.

Los dos constructores restantes tienen cada uno un parámetro. Si, por

Esto provocará un error de compilación porque no hay ningún constructor con un parámetro de tipo int.
Una declaración de operador sobrecargada tiene el siguiente formato:

Operador de tipo de retorno op(lista_de_parámetros);

Donde operador es una palabra clave y op es uno de los operadores predefinidos: +, =, ==, etc. (Consulte el Capítulo 15 para obtener una definición precisa de la sintaxis). Aquí está la declaración del operador de índice sobrecargado:

Operador Char&(int);

Este operador tiene un único parámetro de tipo int y devuelve una referencia a un carácter.
Un operador sobrecargado puede estar sobrecargado si las listas de parámetros de las instancias individuales difieren. Para nuestra clase String, crearemos dos operadores de asignación e igualdad diferentes.

Para llamar a una función miembro, utilice los operadores de acceso a miembros punto (.) o flecha (->). Tengamos declaraciones de objetos de tipo String:
Objeto de cadena("Danny");
Cadena *ptr = nueva Cadena("Anna");
Matriz de cadenas;
Así es como se ve una llamada a size() en estos objetos: vector

tamaños(3);
// miembro de acceso para objetos (.); // los objetos tienen un tamaño de 5 tamaños[ 0 ] = object.size(); // acceso de miembros para punteros (->)
// ptr tiene tamaño 4
tamaños[ 1 ] = ptr->tamaño(); // miembro de acceso (.)
// la matriz tiene tamaño 0

tamaños[ 2 ] = matriz.tamaño();
Devuelve 5, 4 y 0 respectivamente.

Los operadores sobrecargados se aplican a un objeto de la misma forma que los normales:
Nombre de cadena ("Yadie"); Nombre de cadena2("Yodie"); // operador bool==(cadena constante&)
si (nombre == nombre2)
devolver;
demás
// Cadena& operador=(const Cadena&)

nombre = nombre2;

Una declaración de función miembro debe estar dentro de una definición de clase, y una definición de función puede estar dentro o fuera de una definición de clase. (Tanto las funciones size() como c_str() se definen dentro de una clase). Si una función se define fuera de una clase, entonces debemos especificar, entre otras cosas, a qué clase pertenece.
En este caso, la definición de la función se coloca en el archivo fuente, digamos String.C, y la definición de la clase en sí se coloca en el archivo de encabezado (String.h en nuestro ejemplo), que debe incluirse en el archivo fuente. :
corte
// contenido del archivo fuente: String.C // habilitando la definición de la clase String
#include "String.h" // incluye la definición de la función strcmp()
bool // tipo de retorno
String:: // clase a la que pertenece la función
{
operador== // nombre de la función: operador de igualdad
(const String &rhs) // lista de parámetros
si (_tamaño! = derecho._tamaño)
devolver falso;
}

Recuerde que strcmp() es una función de biblioteca estándar de C. Compara dos cadenas integradas y devuelve 0 si las cadenas son iguales y distinto de cero si no son iguales. El operador condicional (?:) prueba el valor antes del signo de interrogación. Si es verdadero, se devuelve el valor de la expresión a la izquierda de los dos puntos; de lo contrario, se devuelve el valor a la derecha. En nuestro ejemplo, el valor de la expresión es falso si strcmp() devolvió un valor distinto de cero y verdadero si devolvió un valor cero. (El operador condicional se analiza en la sección 4.7.)
La operación de comparación se usa con bastante frecuencia, la función que la implementa resultó ser pequeña, por lo que es útil declarar esta función integrada (en línea). El compilador sustituye el texto de la función en lugar de llamarla, por lo que no se pierde tiempo en dicha llamada. (Las funciones integradas se analizan en la Sección 7.6.) Una función miembro definida dentro de una clase está integrada de forma predeterminada. Si está definido fuera de la clase, para declararlo integrado, debe usar la palabra clave en línea:

bool en línea String::operator==(const String &rhs) ( // lo mismo )

La definición de una función incorporada debe estar en archivo de encabezado que contiene la definición de clase. Al redefinir el operador == como un operador en línea, debemos mover el texto de la función del archivo String.C al archivo String.h.
La siguiente es la implementación de la operación de comparar un objeto String con un tipo de cadena incorporado:

Cadena bool en línea::operador==(const char *s) ( return strcmp(_string, s) ? false: true; )

El nombre del constructor es el mismo que el nombre de la clase. Se considera que no devuelve un valor, por lo que no es necesario especificar un valor de retorno ni en su definición ni en su cuerpo. Puede haber varios constructores. Como cualquier otra función, se pueden declarar en línea.

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: // constructor predeterminado en línea String::String()
{
_tamaño = 0;
_cadena = 0;
) String en línea::String(const char *str) ( if (! str) ( _size = 0; _string = 0; ) else ( _size = str1en(str); strcpy(_string, str); ) // copiar constructor
Cadena en línea::Cadena(cadena constante & rhs)
{
tamaño = derecho._tamaño;
si (! rhs._string)
_cadena = 0;
demás(
_string = nuevo carácter[ _size + 1 ];
} }

Dado que asignamos memoria dinámicamente usando el nuevo operador, debemos liberarla llamando a eliminar cuando ya no necesitemos el objeto String. Otra función miembro especial sirve para este propósito: el destructor, que se llama automáticamente a un objeto en el momento en que este objeto deja de existir. (Consulte el Capítulo 7 sobre la vida útil de los objetos). El nombre de un destructor se forma a partir del carácter de tilde (~) y el nombre de la clase. Aquí está la definición del destructor de clase String. Aquí es donde llamamos a la operación de eliminación para liberar la memoria asignada en el constructor:

Cadena en línea: :~String() ( eliminar _string; )

Ambos operadores de asignación sobrecargados utilizan la palabra clave especial this.
Cuando escribimos:

Nombre de cadena("orville"), nombre2("wilbur");
nombre = "Orville Wright";
este es un puntero que apunta al objeto nombre1 dentro del cuerpo de la función de la operación de asignación.
esto siempre apunta al objeto de clase a través del cual se llama la función.
Si
ptr->tamaño();

objeto[ 1024 ];

Luego, dentro de size() el valor de esto será la dirección almacenada en ptr. Dentro de la operación de índice, contiene la dirección de obj. Al eliminar la referencia a esto (usando *this), obtenemos el objeto en sí. (Este puntero se describe en detalle en la Sección 13.4.)

Cadena y cadena en línea::operador=(const char *s) ( if (! s) ( _size = 0; eliminar _string; _string = 0; ) else ( _size = str1en(s); eliminar _string; _string = new char[ _size + 1 ]; strcpy(_string, s); devolver *esto;

Al implementar una operación de asignación, un error que se comete a menudo es que se olvidan de comprobar si el objeto que se está copiando es el mismo en el que se está realizando la copia. Realizaremos esta comprobación utilizando el mismo puntero:

Cadena y cadena en línea::operador=(const String &rhs) ( // en la expresión // namel = *pointer_to_string // esto representa nombre1, // rhs - *pointer_to_string. if (this != &rhs) (

Aquí está el texto completo de la operación de asignar un objeto del mismo tipo a un objeto String:
_cadena = 0;
demás(
_string = nuevo carácter[ _size + 1 ];
Cadena y cadena en línea::operador=(const String &rhs) ( if (this != &rhs) ( eliminar _string; _size = rhs._size; if (! rhs._string)
}
}
strcpy(_string, rhs._string);
}

devolver *esto;

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: La operación de tomar un índice es casi idéntica a su implementación para la matriz Array que creamos en la sección 2.3:
caracteres en línea&
{
Cadena::operador (int elem)< _size);
afirmar(elem >= 0 && elemento
}

Los operadores de entrada y salida se implementan como funciones separadas en lugar de miembros de una clase.

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: (Hablaremos de las razones de esto en la Sección 15.2. Las Secciones 20.4 y 20.5 hablan sobre la sobrecarga de los operadores de entrada y salida de la biblioteca iostream). Nuestro operador de entrada puede leer un máximo de 4095 caracteres. setw() es un manipulador predefinido, lee un número determinado de caracteres menos 1 del flujo de entrada, asegurando así que no desbordemos nuestro búfer interno inBuf. (El Capítulo 20 analiza en detalle el manipulador setw().) Para utilizar manipuladores, debe incluir el archivo de encabezado correspondiente:

inline istream& operator>>(istream &io, String &s) ( // límite artificial: 4096 caracteres const int 1imit_string_size = 4096; char inBuf[ limit_string_size ]; // setw() está incluido en la biblioteca iostream // limita el tamaño de el bloque legible a 1imit_string_size -l io >> setw(1imit_string_size) >> inBuf s = mBuf // String::operator=(const char*);<< не является функцией-членом, он не имеет доступа к закрытому члену данных _string. Ситуацию можно разрешить двумя способами: объявить operator<< дружественным классу String, используя ключевое слово friend (дружественные отношения рассматриваются в разделе 15.2), или реализовать встраиваемую (inline) функцию для доступа к этому члену. В нашем случае уже есть такая функция: c_str() обеспечивает доступ к внутреннему представлению строки. Воспользуемся ею при реализации операции вывода:

El operador de salida necesita acceso a la representación interna de String.<<(ostream& os, const String &s) { return os << s.c_str(); }

Desde operador

Puedes recorrer una matriz usando un índice, como hicimos en la sección anterior, o usando punteros. Por ejemplo: Operador y ostream en línea
A continuación se muestra un programa de ejemplo que utiliza la clase String. Este programa toma palabras del flujo de entrada y cuenta su número total, así como el número de palabras "the" y "it", y registra las vocales encontradas.
#include "String.h" int main() ( int aCnt = 0, eCnt = 0, iCnt = 0, oCnt = 0, uCnt = 0, theCnt = 0, itCnt = 0, wdCnt = 0, notVowel = 0; / / Las palabras "El" y "Eso"
// comprobaremos usando operator==(const char*)
Cadena pero, el("el"), it("it");<<(ostream&, const String&)
++errores;<< buf << " "; if (wdCnt % 12 == 0)
++errores;<< endl; // String::operator==(const String&) and
// operador>>(ostream&, String&)
mientras (cin >> buf) (
++wdCnt;
// operador
// Cadena::operador==(const char*);
if (buf == el | | buf == "El")
++elCnt;< buf.sizeO; ++ix)
{
demás
si (buf == eso || buf == "Eso")
{
++itCnt;
// invoca String::s-ize()
para (int ix =0; ix
// invoca Cadena::operador(int)
cambiar(buf[ ix ])
caso "a": caso "A": ++aCnt; romper;
}
}
caso "e": caso "E": ++eCnt; romper;<<(ostream&, const String&)
++errores;<< "\n\n"
<< "Слов: " << wdCnt << "\n\n"
<< "the/The: " << theCnt << "\n"
<< "it/It: " << itCnt << "\n\n"
<< "согласных: " < << "a: " << aCnt << "\n"
<< "e: " << eCnt << "\n"
<< "i: " << ICnt << "\n"
<< "o: " << oCnt << "\n"
<< "u: " << uCnt << endl;
}

caso "i": caso "I": ++iCnt; romper;

Alice Emma tiene el pelo rojo largo y suelto. Su papá dice que cuando el viento sopla entre su cabello, parece casi vivo, como un pájaro de fuego en vuelo. Un hermoso pájaro de fuego, le dice, mágico pero indómito. "Papá, cállate, no existe tal cosa", le dice, al mismo tiempo que quiere que le cuente más. Tímidamente, ella pregunta: "Quiero decir, papá, ¿lo hay?". palabras: 65
el/El: 2
eso/Eso: 1
consonantes: 190
una: 22
mi: 30
yo: 24
o: 10
tú: 7

Ejercicio 3.26

Nuestras implementaciones de constructores y operadores de asignación contienen mucha repetición. Considere mover el código duplicado a una función miembro privada separada, como se hizo en la Sección 2.3. Asegúrese de que la nueva opción funcione.

Ejercicio 3.27

Modifique el programa de prueba para que cuente también las consonantes b, d, f, s, t.

Ejercicio 3.28

Escriba una función miembro que cuente el número de apariciones de un carácter en una Cadena usando la siguiente declaración:

Cadena de clase ( public: // ... int count(char ch) const; // ... );

Ejercicio 3.29

Implemente el operador de concatenación de cadenas (+) para que concatene dos cadenas y devuelva el resultado en un nuevo objeto String. Aquí está la declaración de la función:

Clase Cadena ( public: // ... Operador de cadena+(const String &rhs) const; // ... );

Esta sección discutirá los principales tipos de datos en C++; estos tipos de datos también se denominan integrados. El lenguaje de programación C++ es un lenguaje de programación extensible. El término extensible significa que además de los tipos de datos integrados, puede crear sus propios tipos de datos. Es por eso que hay una gran cantidad de tipos de datos en C++. Estudiaremos sólo los principales.

Tabla 1: tipos de datos de C++
Tipo byte Rango de valores aceptados

tipo de datos entero (booleano)

booleano 1 0 / 255

tipo de datos entero (carácter)

carbonizarse 1 0 / 255

tipos de enteros datos

corto int 2 -32 768 / 32 767
int corto sin firmar 2 0 / 65 535
entero 4
entero sin firmar 4 0 / 4 294 967 295
largo int 4 -2 147 483 648 / 2 147 483 647
int largo sin firmar 4 0 / 4 294 967 295

tipos de datos de punto flotante

flotar 4 -2 147 483 648.0 / 2 147 483 647.0
flotador largo 8
doble 8 -9 223 372 036 854 775 808 .0 / 9 223 372 036 854 775 807.0

La Tabla 1 muestra los principales tipos de datos en C++. Toda la tabla está dividida en tres columnas. La primera columna indica una palabra reservada, que determinará, cada una su propia, tipo de datos. La segunda columna indica el número de bytes asignados para una variable con el tipo de datos correspondiente. La tercera columna muestra el rango de valores aceptables. Tenga en cuenta que en la tabla todos los tipos de datos están ordenados de menor a mayor.

tipo de datos booleano

El primero en la tabla es el tipo de datos bool. un tipo de datos entero, ya que el rango de valores válidos son enteros del 0 al 255. Pero como ya habrás notado, entre paréntesis dice tipo de datos lógico, y esto también es cierto. Porque booleano Se utiliza exclusivamente para almacenar los resultados de expresiones booleanas. Una expresión booleana puede tener uno de dos resultados: verdadero o falso. verdadero: si la expresión lógica es verdadera, falso: si la expresión lógica es falsa.

Pero dado que el rango de valores válidos del tipo de datos bool es de 0 a 255, era necesario hacer coincidir de alguna manera este rango con las constantes lógicas verdadero y falso definidas en el lenguaje de programación. Por lo tanto, la constante verdadera es equivalente a todos los números del 1 al 255 inclusive, mientras que la constante falsa es equivalente a un solo número entero: 0. Considere un programa que utiliza el tipo de datos bool.

// data_type.cpp: define el punto de entrada para la aplicación de consola. #incluye "stdafx.h" #incluye usando el espacio de nombres estándar; int main(int argc, char* argv) ( bool boolean = 25; // variable de tipo bool llamada boolean if (boolean) // condición del operador if cout<< "true = " << boolean << endl; // выполнится в случае истинности условия else cout << "false = " << boolean << endl; // выполнится в случае, если условие ложно system("pause"); return 0; }

EN linea 9tipo de variable declarada booleano , que se inicializa a 25. Teóricamente, despuéslineas 9, en una variable booleana debería contener el número 25, pero en realidad esta variable contiene el número 1. Como dije, el número 0 es un valor falso, el número 1 es un valor verdadero. El punto es que en una variable como booleano puede contener dos valores: 0 (falso) o 1 (verdadero). Mientras que bajo el tipo de datos booleano se asigna un byte completo, lo que significa que una variable de tipo booleano puede contener números del 0 al 255. Para determinar valores falsos y verdaderos, sólo se necesitan dos valores 0 y 1. Surge la pregunta: "¿Para qué sirven los otros 253 valores?".

En base a esta situación, acordamos utilizar los números del 2 al 255 como equivalente al número 1, es decir, la verdad. Precisamente por eso la variable booleana contiene el número 25 y no 1. En líneas 10-13 declarado, que transfiere el control al operador en línea 11, si la condición es verdadera y el operador en línea 13, si la condición es falsa. El resultado del programa se muestra en la Figura 1.

Verdadero = 1 Para continuar, presione cualquier tecla. . .

Figura 1: tipo de datos bool

tipo de datos carácter

El tipo de datos char es un tipo de datos entero que se utiliza para representar caracteres. Es decir, cada carácter corresponde a un número determinado del rango. El tipo de datos char también se denomina tipo de datos carácter, ya que la representación gráfica de caracteres en C++ es posible gracias a char. Para representar caracteres en C++, al tipo de datos char se le asigna un byte, un byte contiene 8 bits, luego elevamos dos a la potencia de 8 y obtenemos el valor 256, el número de caracteres que se pueden codificar. Por lo tanto, utilizando el tipo de datos char, puede mostrar cualquiera de los 256 caracteres. Todos los caracteres codificados se representan en formato .

ASCII (del código estándar inglés para el intercambio de información): código estándar estadounidense para el intercambio de información.

Considere un programa que utiliza el tipo de datos char.

// símbolos.cpp: define el punto de entrada para la aplicación de consola. #incluye "stdafx.h" #incluye usando el espacio de nombres estándar; int main(int argc, char* argv) ( char símbolo = "a"; // declarando una variable de tipo char e inicializándola con el símbolo "a" cout<< "symbol = " << symbol << endl; // печать символа, содержащегося в переменной symbol char string = "сайт"; // объявление символьного массива (строки) cout << "string = " << string << endl; // печать строки system("pause"); return 0; }

Entonces, en linea 9una variable llamada símbolo , se le asigna el valor del símbolo"a" ( código ASCII). EN linea 10 operador de corte imprime el carácter contenido en la variable símbolo EN línea 11declaró una matriz de cadenas con el nombre cadena y el tamaño de la matriz se especifica implícitamente. Una cadena se almacena en una matriz de cadenas."sitio web" . Tenga en cuenta que cuando guardamos el símbolo en una variable como carbonizarse , luego después del signo igual ponemos comillas simples en las que escribimos el símbolo. Al inicializar una matriz de cadenas con una determinada cadena, se colocan comillas dobles después del signo igual, en el que se escribe una determinada cadena. Al igual que un carácter normal, las cadenas se generan mediante el operador corte, línea 12. El resultado del programa se muestra en la Figura 2.

Símbolo = una cadena = sitio Para continuar, presione cualquier tecla. . .

Figura 2: tipo de datos de caracteres

Tipos de datos enteros

Los tipos de datos enteros se utilizan para representar números. Hay seis de ellos en la Tabla 1: short int, unsigned short int, int, unsigned int, long int, unsigned long int . Todos ellos tienen su propio tamaño de memoria y rango de valores aceptados. Dependiendo del compilador, el tamaño de la memoria ocupada y el rango de valores aceptados pueden variar. En la Tabla 1, se toman todos los rangos de valores aceptados y tamaños de memoria ocupada para el compilador MVS2010. Además, todos los tipos de datos en la Tabla 1 están ordenados en orden creciente según el tamaño de la memoria ocupada y el rango de valores aceptados. El rango de valores aceptados, de una forma u otra, depende del tamaño de la memoria ocupada. En consecuencia, cuanto mayor sea el tamaño de la memoria ocupada, mayor será el rango de valores aceptados. Además, el rango de valores aceptados cambia si el tipo de datos se declara con el prefijo sin signo. El prefijo sin signo significa que el tipo de datos no puede almacenar valores con signo, por lo que el rango de valores positivos se duplica, por ejemplo, los tipos de datos short int y unsigned short int.

Prefijos de tipos de datos enteros:

corto el prefijo acorta el tipo de datos al que se aplica reduciendo el tamaño de la memoria que ocupa;

largo el prefijo extiende el tipo de datos al que se aplica aumentando el tamaño de la memoria que ocupa;

sin firmar: el prefijo duplica el rango de valores positivos, mientras que el rango de valores negativos no se puede almacenar en este tipo de datos.

Entonces, esencialmente, tenemos un tipo de número entero para representar números enteros: el tipo de datos int. Gracias a los prefijos short, long, unsigned, aparece una cierta variedad de tipos de datos int, que se diferencian en el tamaño de la memoria ocupada y (o) en el rango de valores aceptados.

Tipos de datos de punto flotante

Hay dos tipos de datos de punto flotante en C++: flotantes y dobles. Los tipos de datos de punto flotante están diseñados para almacenar números de punto flotante. Los tipos de datos flotantes y dobles pueden almacenar números de coma flotante positivos y negativos. El tipo de datos flotante ocupa la mitad de la memoria que el tipo de datos doble, lo que significa que el rango de valores aceptados también es menor. Si el tipo de datos flotante se declara con el prefijo largo, entonces el rango de valores aceptados será igual al rango de valores aceptados del tipo de datos doble. Básicamente, los tipos de datos de punto flotante son necesarios para resolver problemas con cálculos de alta precisión, por ejemplo, transacciones monetarias.

Entonces, hemos analizado los puntos principales relacionados con los principales tipos de datos en C++. Sólo queda mostrar de dónde provienen todos estos rangos de valores aceptados y los tamaños de memoria ocupada. Y para ello desarrollaremos un programa que calculará las principales características de todos los tipos de datos comentados anteriormente.

// data_types.cpp: define el punto de entrada para la aplicación de consola. #incluye "stdafx.h" #incluye // biblioteca de manipulación de E/S #include // archivo de encabezado de funciones matemáticas #include usando el espacio de nombres estándar; int principal(int argc, char* argv) ( cout<< " data type " << "byte" << " " << " max value "<< endl // encabezados de columna <<"bool = " << sizeof(bool) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных bool*/ << (pow(2,sizeof(bool) * 8.0) - 1) << endl << "char = " << sizeof(char) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных char*/ << (pow(2,sizeof(char) * 8.0) - 1) << endl << "short int = " << sizeof(short int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных short int*/ << (pow(2,sizeof(short int) * 8.0 - 1) - 1) << endl << "unsigned short int = " << sizeof(unsigned short int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных unsigned short int*/ << (pow(2,sizeof(unsigned short int) * 8.0) - 1) << endl << "int = " << sizeof(int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных int*/ << (pow(2,sizeof(int) * 8.0 - 1) - 1) << endl << "unsigned int = " << sizeof(unsigned int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных unsigned int*/ << (pow(2,sizeof(unsigned int) * 8.0) - 1) << endl << "long int = " << sizeof(long int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных long int*/ << (pow(2,sizeof(long int) * 8.0 - 1) - 1) << endl << "unsigned long int = " << sizeof(unsigned long int) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных undigned long int*/ << (pow(2,sizeof(unsigned long int) * 8.0) - 1) << endl << "float = " << sizeof(float) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных float*/ << (pow(2,sizeof(float) * 8.0 - 1) - 1) << endl << "double = " << sizeof(double) << " " << fixed << setprecision(2) /*вычисляем максимальное значение для типа данных double*/ << (pow(2,sizeof(double) * 8.0 - 1) - 1) << endl; system("pause"); return 0; }

Este programa está publicado para que pueda ver las características de los tipos de datos en su sistema. No es necesario comprender el código, ya que el programa utiliza declaraciones de control con las que probablemente aún no esté familiarizado. Para un conocimiento superficial del código del programa, explicaré algunos puntos a continuación. Operador tamaño de() Calcula el número de bytes asignados para un tipo de datos o variable. Función potencia(x,y) eleva el significado x elevado a y , esta función está disponible en el archivo de encabezado . manipuladores fijos y setprecision() disponible desde el archivo de encabezado . El primero esta arreglado , pasa valores en forma fija al flujo de salida. Manipulador setprecision(n) muestra n lugares decimales. El valor máximo de un determinado tipo de datos se calcula mediante la siguiente fórmula:

Max_val_type = 2^(b * 8 - 1) - 1; // para tipos de datos con números negativos y positivos // donde b es el número de bytes asignados en la memoria para una variable con este tipo de datos // multiplica por 8, ya que hay 8 bits en un byte // resta 1 entre paréntesis, ya que los números del rango deben dividirse en dos para valores positivos y negativos // restar 1 al final, ya que el rango de números comienza desde cero // tipos de datos con el prefijo unsigned max_val_type = 2^(b * 8) - 1; // para tipos de datos solo con números positivos // las explicaciones de la fórmula son similares, solo que la unidad no se resta del paréntesis

Un ejemplo de cómo funciona el programa se puede ver en la Figura 3. La primera columna muestra los principales tipos de datos en C++, la segunda columna muestra el tamaño de memoria asignada para cada tipo de datos y la tercera columna muestra el valor máximo que el correspondiente tipo de datos puede contener. El valor mínimo se encuentra similar al máximo. Para tipos de datos con prefijo sin signo, el valor mínimo es 0.

Tipo de datos byte valor máximo bool = 1 255,00 char = 1 255,00 int corto = 2 32767,00 int corto sin signo = 2 65535,00 int = 4 2147483647,00 int sin signo = 4 4294967295,00 int largo = 4 2147483647,0 0 int largo sin signo = 4 4294967295.00 float = 4 2147483647.00 doble = 8 9223372036854775808.00 Para continuar, presione cualquier tecla. . .

Figura 3: tipos de datos de C++

Si, por ejemplo, a una variable de tipo short int se le asigna el valor 33000, entonces la cuadrícula de bits se desbordará, ya que el valor máximo en una variable de tipo short int es 32767. Es decir, algún otro valor se almacenará en una variable. de tipo short int, lo más probable es que sea negativo. Ya que hemos tocado el tipo de datos int, vale la pena señalar que puedes omitir la palabra clave int y escribir, por ejemplo, simplemente short . El compilador interpretará dicha entrada como short int. Lo mismo se aplica a los prefijos largos y sin signo. Por ejemplo:

// taquigrafía del tipo de datos int short a1; // igual que short int long a1; // igual que long int unsigned a1; // igual que unsigned int unsigned short a1; // igual que int corto sin firmar




Arriba