Herramientas de usuario

Herramientas del sitio


highlevel:c

¡Esta es una revisión vieja del documento!


C\C++

Sintaxis

Variables sin signo

Podemos definir variables sin signo, por ejemplo integers, incluyendo unsigned antes del tipo de variable:

unsigned int iNum;

Métodos\funciones

Recuerda que en C, para poder usar un método ha de estar su declaración arriba, si esta no está entonces debería estar toda la función (declaración y cuerpo) antes del código que la llama. La declaración consiste en el tipo devuelto, nombre y parámetros entre paréntesis. Además del ;.
Podemos pasar parámetros de referencia a los métodos, para ello después del tipo de parámetro incluiremos un '&':

void Suma (int& ax, int bx) { 
  ax+=bx; 
}

Con un parámetro de referencia, el dato pasado en la llamada cambia si el método lo cambia. Cuando se llama a esta función, no puede pasarsele números directamente al primer parámetro, es decir algo así Suma(3, 4), sino que hay que pasar una variable (al menos en el primero) Suma(i, 4).
Podemos dar valores por defecto a los parámetros, entonces podemos prescindir de ellos en su llamada:

int Suma (int a, int b=0) {
  return a + b;
}
...
Suma (3);    // Devolvería 3
Suma (3, 2); // Devolvería 5

Cuando crees una función con parámetros por defecto en una librería, estos deberán de ser indicados en el código (archivo .cpp) cuando la compiles y no en la cabecera (archivo .h). Pero cuando utilices la cabecera en el código que utiliza la librería, entonces sí que deberás indicar los parámetros por defecto en esta:

// Archivo .cpp  --------------------------------------------
void Message::Error (char* txt, char* title = "Error!") { ...
 
// Archivo .h  ----------------------------------------------
#ifdef _USRDLL 
   static void Error (char* txt, char* title);
#else
   static void Error (char* txt, char* title = "Error!");
#endif

Parámetros de referencia constantes

Los parámetros de referencia son más eficientes que los convencionales, ya que en los convencionales el parámetro pasado se vuelve a copiar en memoria y se opera con él, con los de referencia no se hace una copia de memoria sino que es la variable la que es pasada al parámetro y el método, si hace un cambio sobre el parámetro, hace que se cambie la variable.
Cuando declaramos un parámetro de referencia constante indicamos que no se hará una copia del dato, sino que se enviará ese dato (referencia), pero este no podrá cambiarse dentro del método (constante):

int Suma (const int& ax, const int& bx).

cout y cin

En c++ existen varios canales de entrada y de salida, los más usados son cout (chanel output) y cin (chanel input), para acceder a ellos debemos incluir iostream (#include <iostream>) y se usan de la siguiente forma:

  • Para mostrar algo por consola mediante cout usaremos dicho comando seguido de « y variable o string:
cout << "Hola mundo";
  • Para recoger algo del canal de entrada, mediante cin seguido de » y la variable donde se guardará lo introducido por dicho canal. Según el tipo de variable así será lo que transforme lo que sea introducido.
  • cin y cout las encontramos en std, podemos acceder a ellos mediante “using namespace std;” o mediante std::cin.
  • Podemos enviar a cout un salto de carro, para ello debemos hacer un cout « endl;
  • Existe un problema con el cin, y es que, en un string, sólo te leerá hasta un espacio, una cadena introducida por el usuario “Manuel García” será sólo “Manuel”. Para leer hasta que se haga un “enter” hemos de llamar al método “getline”, pasándole como parámetros el canal desde donde queremos leer y la variable donde queremos colocar el contenido:
getline(cin, str);

En algunos compiladores, para poder usar el getline, deberemos hacer un include de <string>. Aún así el cin puede fallar, por ejemplo cuando indicamos que el cin volcará el contenido de lo introducido por pantalla en un double y el usuario mete un carácter. Pero podemos controlarlo, tras usar el cin podemos llamar al método cin.fail(), este te devolverá un bool indicandote si ha sido correcto (false, no ha fallado el cin) o no (true) lo introducido por el usuario. También podemos controlarlo si sabemos que la llamada a cin devuelve otro bool, si este es true es que se ha realizado correctamente, por lo que deducimos que podemos hacer esto: if (cin » doubleVar) {…}

Recuerda que el operador « sobre cout lo que hace es enviar la salida de la derecha al operando izquierdo, es decir un código como el siguiente…

int i = 0;
int func () {
	i++;
	return i;
}
...
cout << func() << func() << func() << func();

… devolvería 4321.

Conversiones

  • De string a int: int i = atoi(str.c_str());
  • De string a double: double d = atof(str.c_str());
  • De string a long int: atol
  • De integer a string (debemos incluir: sstream):
ostringstream myStream;
myStream << num << flush;
myStriam.str();

Existe otra conversión de integer a ascii (char), se llama atoi y se le pasan 3 parámetros: el integer a convertir, un array de carácteres y la base de la conversión (generalmente 10).

int i = 5;
char buffer [33];
itoa (i,buffer,10);
printf ("decimal: %s\n",buffer);
itoa (i,buffer,16);
printf ("hexadecimal: %s\n",buffer);
itoa (i,buffer,2);
printf ("binary: %s\n",buffer);

Constantes

Podemos declarar constantes mediante la palabra “const”. Ej.

const double DOLLAR_VALUE = 100;

switch

switch (variable) { 
	valor1: ... break; 
	valor2: ... break; 
	default: 
}

Random

En C el random lo encontramos en la función rand, sin argumentos. Podemos indicar una nueva semilla para que haga un nuevo número aleatorio pasándole un int.

Trucos

  • Coger como número aleatorio 1 o 2:
int a = rand()%2;
  • Coger un número aleatorio entre dos números:
int rand_int(int a, int b) {
   return a + rand() % (b - a + 1);
}
  • Coger un número real aleatorio:
double rand_double(double a, double b) {
   return a + (b - a) * rand() * (1.0 / RAND_MAX);
}
  • Para coger una nueva semilla…
srand( (unsigned)time( NULL ) );

Expresiones matemáticas

  • rint(d) → Redondea al entero más cercano.

#includes

  • Si hacemos un include de una librería genérica haremos: #include <librería>
  • Si hacemos otro include de un .h nuestro: #include “fichero.h”
  • Recuerda que no podremos hacer uso de los métodos o clases definidos si no incluimos ese .h.

Declarar métodos y clases sin .h

  • Clases: class clase;
  • Método: void metodo ();

#defines

#define COLS = "abcdefgh";
#define TOTAL 10
#define getrandom(min, max) \ ((rand()%(int)(((max) + 1)-(min)))+ (min))
#define MSGBOX(body) MessageBox(NULL,body,"",MB_OK);
...
int i = TOTAL;
MSGBOX("holaaa!!");

Arrays estáticos

  • Declarar un array de ints: int array[10];
  • Una vez declarado su tamaño, este no puede ser cambiado.
  • Puedes declarar un array con valores predefinidos:
int array[] = {1,2,3};
  • Los arrays en métodos se pasan por referencia, no se hace una copia del array. Pero sí que podemos indicar mediante la palabra 'const' antes del tipo, que el array no se modificará, cuando pasemos un array a un método, en la definición de este no debemos indicar el tamaño:
void Probando (int array[]) { ... }
  • Lo que no podemos es devolver un array desde una función, para ello devolveremos un puntero a un integer, en punteros veremos que es lo mismo…
  • Podemos declarar arrays de dos dimensiones:
int array[2][2];

La función main

int main(int argc, char* argv[]) {...}
  • Debe retornar un int, este ha de ser 0 si la aplicación ha acabado correctamente sin ningún error.
  • argc es el número de argumentos que ha recibido, en otras palabras el número de elmentos que contiene argv.
  • argv son los argumentos en sí.
  • argc al menos siempre contendrá valor 1 que corresponde a la posición 0 de argv que es el nombre del ejecutable con el que se ha llamado al programa.
int main (int nargs, char* argv[]) {
	printf("%d\n%s\n", nargs, argv[0]);
}

Gestión de memoria

Punteros

int i = 4;
int *a = &i;
cout << a << endl;
cout << *a << endl;

What does it mean?

  1. Definición de variable int, i, que vale 4.
  2. Definición de puntero int, a, que vale la dirección donde se encuentra i.
  3. Mostramos a, la dirección donde se encuentra i.
  4. Mostramos el valor donde a hace referencia.


  • Cuando declares un puntero sin inicializar no olvides igualarlo a 'NULL'. int *i=NULL;, de esta forma 'i' no apunta a ningún sitio y podemos usarlo luego en un if:
if (i != NULL) ...
  • Existe otra forma más elegante de saber si un puntero es NULL:
if (i) // pregunta si i tiene algo, si es NULL no entrará en el if
  • Los punteros se usan para ahorrar memoria, por ejemplo en una clase, para qué enviar tener un objeto que tal vez no se use y esté ocupando memoria. O apuntando a un mismo valor u objeto, de esa forma no hay que hacer una copia en memoria y si se modifica uno… el otro también.
int **b = &a;

Esta línea es un puntero a otro puntero.

  • La definición de un array no es más que un puntero al primer elemento, es más, cuando se pasa un array a un método se hace, automáticamente, por referencia. Por lo tanto podemos igualar un array a un puntero, dicho puntero apuntará al primer valor del array.
int i[] = {1,2,3,4};
int *a = i;			// *a = i[0];
*a = 33;			// i[0] = 33;
  1. Si hacemos cout de *a veremos el valor 33.
  2. Si hacemos a++, a valdrá 2.
  • Entonces, podemos incrementar un puntero x valores, estos x valores serán el elemento del array en el que nos encontremos:
*(a + 1) = 23;		// i[1] = 23;
  • Debemos tener en cuenta que si declaramos una variable local en un método y asignamos su valor a un puntero global, cuando se salga del método dicha variable será destruida y el puntero no apuntará a ese valor. Aún así, si el valor del puntero lo asignamos con un new este sí que se mantendrá, por lo que es importante el uso del delete.
  • Podemos asignar manualmente una posición de memoria a un puntero:
r = (int*)0x0012f144;
r = (int*)55;
  • Si quisiesemos definir un puntero para luego darle un valor deberemos de reservar memoria:
int *c = new int;
*c = 3;

Resumen

int iA = 4;		// Un integer iA con valor 4
int* pA = &iA;		// Un puntero a int llamado pA apunta a la direción de iA
int* pB = pA;		// Un puntero a int llamado pB apunta donde apunta pA
int iB = *pB;		// Un integer iB con valor al que apunta pB
*pB = 5;		// El valor que contiene la dirección donde apunta pB = 5

New y delete

Al hacer puntero = new <tipo> lo que estamos haciendo es reservar un espacio de memoria para una variable\objeto del tipo indicado y apuntar a ese espacio con el puntero. Aunque el valor no esté inicializado ese espacio es para ese puntero.

int *i = new int;
*i = 5;
char *a = new char[10];

Al hacer 'delete puntero' el espacio de memoria al que apuntaba antes el puntero queda libre, puede volver a ser asignado.

delete i;

Por lo que si hacemos el siguiente código el resultado resultante podremos comprobarlo:

int *i = new int;
*i = 6;
delete i;
int *a = new int;
*a = 50;
cout << i << endl;

Lo que se nos mostrará al final por pantalla será 50. Veamos las líneas:

  1. Creamos puntero i y ocupamos el espacio donde apunta con un int.
  2. Damos al lugar donde apunta i el valor 6.
  3. Liberamos el espacio donde apuntaba i.
  4. Creamos otro puntero int llamado a apuntando a la última memoria libre, justo donde apuntaba i.
  5. Asignamos a “donde apunta a” el valor 50.
  6. Mostramos el valor de donde apunta i, que es justamente donde apunta a.

Si quisiesemos eliminar todo un array deberemos usar delete[].

char *a = new char[10];
delete[] a;	

Hemos de tener en cuenta que si antes de hacer el 'delete[] a' hemos hecho un incremento de a: a++, el delete dará error.
Podemos desreservar el espacio que ocupan las matrices:

for (i=0; i<files; i++)
	delete[] x[i];
delete[] x;

Hablando de punteros a arrays:

int* r = new int[10];
r[0] = 3;
r[1] = 4;

Es importante no obviar los 'delete', de esta forma liberamos memoria, porque si no el programa podrá llegar a ocupar mucho. Otra de las cosas aconsejadas es asignar NULL al puntero una vez lo hayamos borrado.

Reserva de memoria (malloc)

Si creasemos un puntero de un integer sin darle ningún valor, este puntero no apuntaría a ningún sitio, por lo que si quisiesemos darle un valor el programa petaría, ya que no podría asignarlo en ningún sitio.
Para que a un puntero podamos dar algún valor debe estar asignado a una posición de memoria, si queremos asignarlo a una nueva posición de memoria debemos reservarla (allocate) antes con malloc, a este se le pasa un tamaño (size_t) y devuelve un puntero void*, por lo que le debemos hacer un cast:

int *num = NULL;
num = (int*)malloc(sizeof(int));
*num = 44;

Existen el realloc (vuelve a reservar memoria pero con otro tamaño) y el calloc (para arrays).

  • Se aconseja comprovar que el malloc no devuelve NULL, esto significaría que no hay más memoria disponible:
if((buffer = (long *)malloc(sizeof(long))) == NULL)
      	exit( 1 );
  • Podemos reservar 10 bytes (como un array de bytes) de la siguiente forma:
char* a;
a = (char*)malloc(10 * sizeof(char));
*(a + 1) = 'b';
cout << *(a+1);

Cuando dejemos de usar un putero no debemos olvidar liberar la memoria reservada mediante la función 'free'. Para el ejemplo anterior sería algo así:

free(a);
  • Los espacios liberados pueden ser reagrupados para un mejor provecho mediante la función: _heapmin()

Tipos de datos

Arrays de carácteres

Antiguamente, cuando no existía la clase string, se usaban los arrays de carácteres para almacenar palabras. La definición de un solo carácter sería:

char chr = 'y';

Puedes declarar un array de chars cual string fuere:

char str[] = "Hola amigos!";

Al acabar un array de carácteres, es importante no olvidar añadir: '\0'. Este indica que se ha llegado al fin de la cadena, y si se va a utilizar el array de chars sólo leerá hasta ahí, sino lo leerá todo.
Una cadena de estas puede ser leida como: const char*.
Existen funciones que te permiten trabajar fácilmente con estos tipos de datos:

  • strcmp → Compara dos cadenas.
  • strcpy → Copia una cadena en otra.
  • strlen → Devuelve el tamaño de la cadena.
  • strtok → Corta la cadena por el carácter especificado.

Otras funciones que pueden sernos de utilidad son las que hacen lo mismo pero con unsigned char:

  • memcmp
  • memcpy
  • memmove

Struct

Una estructura es una agrupación de datos. Podríamos pensar que son muy parecidas a las clases de C++, pero no es así, ya que sólo son una simple agrupación:

struct lista
{
	int clave;
	struct lista *sig;
	string hi;
};

Luego para declarar una variable:

lista element;

Y para acceder:

element.hi = "texto";

Hay otra forma de declarar una variable:

lista lst = {1, NULL, "hola"};

De esta forma estamos dando valores a los elementos de la estructura según el orden que tienen, podemos dejar elementos sin dar valores, pero esos deberán ser los últimos, que quedarán con valor NULL.
Si creasemos un puntero a un elemento del tipo lista, para acceder a sus variables internas usaríamos ->.

lista *elem;
...
elem->clave = 44;

Una estructura puede contener métodos, y hasta un constructor que será llamado cuando se haga un “new” de dicha estructura:

struct Nodo
{
	int clave;
	struct Nodo *Next;
	Nodo(int indx)
	{
		this->Next = NULL;
		this->clave = indx;
	}
};

Enumeraciones

Son una serie de integers dentro de un bloque con un identificador específico.
Podemos declarar las enumeraciones de la forma siguiente:

enum Dias {Lunes, Martes, Miercoles};

Por defecto Lunes tiene valor 0, Martes 1 y Miercoles 2. Aún así podemos cambiar los valores:

enum Dias {Lunes = 1, Martes = 2, Miercoles = 3};

Luego podemos declarar variables del tipo 'Dias':

Dias hoy;

Y asignarlas:

hoy = Lunes;

No podemos asignar integers a hoy, aunque hoy realmente sea un integer:

hoy = 0;	// Error!

Pero sí que podremos hacerlo al revés:

int DiaUno = Lunes;

Y también podemos hacer lo siguiente:

Dias d = Miercoles;
int i = d;

Para asignar a una variable de enumeración un integer, debemos hacer un cast:

Dias d = (Dias)2;

Hay que tener cuidado con los nombres de variables, no podremos repetir los nombres usados dentro de la enumeración ni el identificador de la enumeración.
Para enviar un valor de una enumeración a una función haremos lo siguiente:

void show (Dias d) { cout << d; }

Y también podemos usarlas en if's y switch's:

	if (i == Lunes) ...
	switch (d){ case Lunes: ...

Si hacemos:

enum Dias {Lunes = 2, Martes, Miercoles};

Lunes valdrá 2, Martes 3 y Muercoles 4. Y si hacemos:

enum Dias {Lunes, Martes = 3, Miercoles};

Lunes valdrá 0, Martes 3 y Miercoles 4.

  • La gracia de las enumeraciones es que son constantes autoasignables y que podemos usarlas como valores fijos.

La clase string

  • Podemos definir variables de tipo “string”. Recuerda que este tipo de datos está dentro del namespace std, y que para usarlo debemos hacer un include.
  • Podemos acceder a las posiciones del string como si fuese un array, mediante [pos].
  • Cuando en una función se nos pida un string podremos pasar un char*, peron no podremos pasar un string si se nos pide char*, para ello usar c_str().
  • Métodos:
    • length() → Devuelve el tamaño de la cadena.
    • insert(str, i) → Inserta la cadena str en la posición i.
    • substr(i1, i2) → Devuelve un string de i2 carácteres a partir de i1.
    • replace(i1, i2, str) → Cambia a partir de i1, i2 carácteres por los de str.
    • erase (i1, i2) → Elimina i1 carácteres a partir de i2.
    • find (str, i) → Indice de la primera aparición de str a partir de i.
    • rfind(str, i) → Igual a find pero hacia atrás.
    • c_str() → Pasa el string a constant char.

Podemos concatenar strings mediante el operador '+'.

La clase vector

  • Haciendo un include de vector (#include <vector>) podemos usar la clase vector, que corresponde a un array dinámico, un vector es menos óptimo que un array. Recuerda que esta clase está dentro del namespace std.
  • Para definir un vector debemos hacer: vector<tipo dato> nombreVec;
  • Con esta sentencia declaramos un vector dinámico, pero podemos declararlo también estático: vector<tipo dato> nombreVec[tamaño];
  • Declarándolo estático deberemos tener en cuenta que si hacemos una asignación incorrecta el programa irá mal.
  • Hacer un vector a punteros ints: vector<int*> array;
  • Si tenemos un vector dinámico podemos usarlo como una pila, mediante el método .push_back(<valor>) vamos introduciendole elementos. Podemos acceder a ellos como lo haríamos a un array: vector[posición].
  • De la misma forma también podemos cambiar el valor de un elementos: vector[posición] = valor.
  • Otros métodos:
    • size() → Devuelve el tamaño del vector
    • pop_back() → Elimina el último elemento del vector
  • Respecto a usar un vector en funciones, también podemos pasarlos como parámetros o devolverlos:
vector<int> Sumar (vector<double>) {...}
  • También podemos pasarlos por referencia: vector<double>& v

ANSI C

Las funciones de C clasificadas por librerías. Para utilizar una tendrás que incluir el .h correspondiente, por ejemplo: #include <math>

math

  • tan, atan, sin, asin, cos, acos para cálculos trigonométricos.
  • pow pasándole dos doubles eleva el primero al segundo.
  • sqrt calcula la raíz cuadrada.
  • fabs calcula el valor absoluto.
  • log calcula el logaritmo neperiano y log10 el logaritmo en base 10.
  • fmod calcula el resto de coma flotante de dos doubles.
  • exp calcula la función exponencial.

time

  • clock tiempo de uso del procesador. La frecuencia en la que se mide este tiempo viene dada por la macro CLOCKS_PER_SEC, por lo que para saber cuantos segundos lleva el ordenador encendido haremos: clock()/(double)CLOCKS_PER_SEC.
  • time, esta función devuelve el tiempo de la máquina; el tipo devuelto es un time_t y el que se le pasa como parámetro una referencia a una variable de este tipo, si esta que se le pasa por parámetro no es NULL colocará en ella tambiénen el resultado.
  • localtime convierte un time_t en un tm, otra estructura de tiempo más legible en la que se especifican los segundos, minutos…
time_t t;
time(&t);
tm hora = *localtime(&t);
int hour = hora.tm_hour;
int min = hora.tm_min;
int sec = hora.tm_sec;
  • mktime hace lo contrario a localtime, convierte un tm en time_t.
  • difftime calcula la diferencia entre dos time_t en segundos expresada en double.

ctype

A parte de las funciones…

  • tolower devuelve el char indicado como minúscula.
  • toupper deuvelve el char indicado como mayúscula.

… contiene una serie de macros que nos indican mediante un integer (que si es 0 es false):

isalnum  	(A - Z o a - z) o (0 - 9)
isalpha 	(A - Z o a - z)
isascii 	0 - 127 (0x00-0x7F)
iscntrl 	(0x7F o 0x00-0x1F)
isdigit 	(0 - 9)
isgraph 	Imprimibles menos ' '
islower 	(a - z)
isprint 	Imprimibles incluido ' '
ispunct 	Signos de puntuación
isspace 	espacio, tab, retorno de línea, cambio de línea, tab vertical, salto de página (0x09 a 0x0D, 0x20).
isupper 	(A-Z)
isxdigit 	(0 to 9, A to F, a to f)

locale

Librería con funciones que controlan datos de la 'localidad' de la máquina (si utiliza comas en vez de puntos para los decimales, moneda…).

string

  • memchr encuentra un carácter en una cadena.
  • memset copia un valor todo seguido en memoria tantas veces como sea especificado.
  • strpbrk encuentra una cadena en otra.
  • strcpy copia una cadena en otra.
  • memcmp compara porciones de memoria.
  • strtok separa la cadena en porciones.
  • strcmp compara dos cadenas.
  • memcpy hace una copia de una porción de memoria.
  • strcat concatena dos cadenas.
  • strerror convierte un número de error en una cadena de carácteres.

stdlib

  • div calcula el cociente y el resto de una división devolviendote el resultado en una estructura.
  • abs, labs calculan el valor absoluto en ints.
  • realloc cambia el tamaño asignado a un puntero.
  • atol convierte una cadena en long.
  • atof convierte una cadena en double.
  • atoi convierte una cadena a int.
  • exit finaliza el programa con un estado pasado por parámetro.
  • srand y rand para calcular números aleatorios.
  • system ejecuta un comando.
  • malloc asigna espacio a un puntero.
  • free libera el espacio de un puntero.
  • getenv devuelve un array con los directorios de un path específico.
  • mblen devuelve el tamaño asignado aun puntero.
  • qsort realiza el algoritmo de quicksort sobre un array, has de pasarle un puntero a función que calcule el mayor\menor.
  • strtoul pasa un integer en base x pasado como string a uno en base 10. Los parámetros: el string, NULL, y la base en la que se pasa el valor de entrada. Por ejemplo x en int x = strtoul(“0000110”, NULL, 2); tendría como valor 6.

stdio

Para lectura y escritura de datos por canales de entrada salida. Muchas de las funciones de consola equivalen a las de ficheros sólo que el puntero al destino (el fichero) es asignado como stdout, un puntero al stream de salida por defecto. El canal de entrada es stdin.

Consola

  • printf escribe por consola una cadena con el formato especificado.
  • getchar lee un carácter del canal de entrada por defecto.
  • gets lee una cadena del canal de entrada por defecto.
  • putchar muestra un carácter por consola.
  • puts muestra una cadena por consola.
  • scanf lee por consola una cadena.

Ficheros

  • fflush envia al fichero todos los datos que quedan en el buffer.
  • fopen abre un fichero, retorna un FILE (descriptor de este) y se le pasan dos cadenas, la primera la dirección, la segunda el modo de apertura (r (lectura), w (escribe borrando el contenido), a (añade), rb (lectura binaria), wb (escritura binaria), ab (añadir binaria), r+ (fichero de texto para leer y escribir), w+ (crea si no existe y escribe borrando el contenido), a+ (añade sobre un fichero existente o no), lo mismo para rb+, wb+, ab+).
  • fputs escribe una cadea en un fichero. Para carácteres existe fputc. También existen puts y putc.
  • fseek se coloca en una posición de un fichero. Se le pasan por parámetro el FILE, un int de desplazamiento y una de las siguientes macros que definen el orígen: SSEK_CUR si es la posición actual del puntero, SEEK_SET si es la posición inicial del fichero y SEEK_END el final.
  • remove elimina un fichero, este ha de estar cerrado.
  • tmpfile crea un fichero temporal, que será eliminado automáticamente una vez se cierre, su modo es wb+.
  • tmpnam da un nombre válido para un fichero temporal.
  • rename renombra un fichero.
  • fgetc obtiene un carácter de un fichero. También existe getc y fgets que lee una cadena.
  • fclose cierra el fichero.
  • feof indica si el puntero del fichero está al final de este.
  • fread lee una porción de fichero y la coloca en la memoria indicada.
  • fwrite escribe una porción de memoria en un fichero.
  • fprintf escribe en un fichero unos carácteres con el formato indicado.
  • fscanf lee de un fichero mediante un formato de lectura indicado.
  • rewind coloca el puntero del fichero al principio.

Otros

  • sscanf lee de una porción de memoria con un formato de lectura indicado.
  • sprintf escribe sobre una porción de memoria con el formato indicado.
  • perror convierte un número de error en una secuencia de carácteres que saca por el canal por defecto de errores. Esta cadena es la dada por la función strerror de string. El número de error viene dado por el retorno de la función y puedes controlarlo incluyendo la errno.h, esta trae una serie de defines sobre los tipos de errores.

Preprocesador

A parte de las generalmente conocidas #include o #define, existen directivas para el preprocesador que pueden sernos de utilidad:

  • #ifdef, si está definido tal símbolo, compila el siguiente código.
  • #ifndef, si no está definido tal símbolo, compila el siguiente código.
  • #endif, para indicar que se ha acabado un #ifdef o un #ifndef o un #if.
  • #else, como un else normal pero para el procesador.
  • #elif, el else-if del preprocesador.

Uso del #ifndef

Imaginemos clase Producto, declarada en archivo .h. Debemos asegurarnos que el compilador no compila varias veces este .h, no sólo por eficiencia sino también por la posivilidad de que nos dé un error, para ello usaremos las líneas siguientes en el .h:

#ifndef PRODUCT_H
#define PRODUCT_H
<declaracion de la clase Producto>
#endif

Esto es una condición al compilar, que indica si no está definido PRODUCT_H haz…, y lo primero que hace es definirlo para que así cuando se vuelva a llamar no se recompile.

#if

También existe el #if del preprocesador; podemos utilizarlo para, por ejemplo, controlar si se han hecho una serie de defines y si es así ejecutar un código u otro, esto nos sirve para codificar condicionalmente.

#if defined( WIN32 ) && defined( TUNE )
	#include <crtdbg.h>
	_CrtMemState startMemState;
	_CrtMemState endMemState;
#else
	#include <otros.h>
#endif

#pragma

Otras directivas pero estas no son compatibles con todos los compiladores. Van siempre seguidas de otra directiva que indica la función que hace, por ejemplo once:

#pragma once

Las directivas que pueden seguir a #pragma son:

  • once, indica que el fichero de código que sigue a dicha directiva no se compilará más de una vez, algo parecido a utilizar el #ifndef.
  • comment, coloca un comentario en el resultado de la compilación.
  • warning(disable: xxxx), siendo xxxx el número de warning, desactiva ese warning.

Otras cualidades del lenguaje

system

  • La función system nos sirve para enviar comandos de consola al sistema, por ejemplo “dir”, “pause”…
  • Para usarla debemos incluir “stdlib”.

extern

Si quieres declarar una variable en un archivo de definiciones (.h) no podrás hacerlo, ya que allí no podrás darle ningún valor. Para ello has de darle el valor en el archivo de código (.cpp).
Aún así, para poder inicializar la variable externamente debemos incluir antes de la declaración la clausula 'extern'. En el archivo .h tendremos:

extern int iVal;

y en el .cpp:

int iVal = 33;

Y es que la palabra clave extern indica al compilador que el dato que se está definiendo existe en algún lugar del código (más abajo, en un fichero externo…), pero que es posible que todavía no se haya compilado esa parte.

extern int i; 
extern void func();
int main() {
  i = 0;
  func();
}
int i; 
void func() {
  i++;
  cout << i;
}

Asserts

  • Para usarlas deberás hacer #include<cassert> en unos compiladores, en otros #include <assert.h>
  • La idea se basa en una línea de código que ha de cumplirse para que el programa pueda seguirse ejecutando, si no se cumpliese debería dar error.
  • Ej. assert (i > 3); ← si i no es mayor que 3 el compilador actúa.

Por qué frases tan abstractas como “el compilador actúa” o “debería dar error”? Porque esto va según la config del compilador. Algunos, en código release no hacen caso de los asserts, otros ni en debug. Aún así son muy útiles, ya que generalmente hacen que el programa se pare indicando un error de aserción, donde se encuentra y el por qué.
Si quieres desactivar los assert añade #define NDEBUG.

Ficheros

Para manejar ficheros en C necesitamos un puntero de tipo FILE, este será asignado por la función fopen a la que se le pasa el nombre de fichero y la forma de abertura, esta puede ser:

  • En modo texto:
    • w crea un fichero de escritura. Si ya existe lo crea de nuevo.
    • w+ crea un fichero de lectura y escritura. Si ya existe lo crea de nuevo.
    • a abre o crea un fichero para añadir datos al final del mismo.
    • a+ abre o crea un fichero para leer y añadir datos al final del mismo.
    • r abre un fichero de lectura.
    • r+ abre un fichero de lectura y escritura.
  • En modo binario:
    • wb crea un fichero de escritura. Si ya existe lo crea de nuevo.
    • w+b crea un fichero de lectura y escritura. Si ya existe lo crea de nuevo.
    • ab abre o crea un fichero para añadir datos al final del mismo.
    • a+b abre o crea un fichero para leer y añadir datos al final del mismo.
    • rb abre un fichero de lectura.
    • r+b abre un fichero de lectura y escritura.

Para cerrar un fichero usamos la función fclose.

FILE *pf;
pf=fopen("AGENDA.DAT","rb");
if ( pf == NULL ) printf ("Error al abrir el fichero");
else fclose(pf);
  • Para ver funciones de escritura\lectura en ficheros ves a la sección de stdio.

Ficheros con streams

Para el acceso a ficheros existen una serie de clases que nos facilitan el trabajo. Para poder tener acceso a ellas necesitamos incluir la librería iostream: #include <iostream> (debemos usar también el namespace std) (En algunos compiladores también tendremos que incluir fstream). La clase que usaremos para leer archivos es ifstream, podemos crear un objeto de esta y llamar a su método open pasandole el nombre del archivo a leer. Se usa como el cin:

ifstream f;
f.open("c:\\prueba.txt");
int i;
f >> i;
cout << i << endl;
double d;
f >> d;
cout << d << endl;

Vamos volcando el contenido sobre las variables del tipo que queramos. Pero cuando vamos a leer strings sólo capturamos palabras, hasta el próximo espacio, por lo que debemos hacer uso del método getline. A getline le pasaremos como canal de entrada nuestro objeto ifstream, recuerda que para usar el getline has de incluir “string”.

string s;
getline(f, s);
cout << s << endl;
  • Una vez usado el fichero deberemos cerrarlo con el método close.
  • Métodos útiles de ifstream
    • unget → se mueve en el fichero a un carácter antes
    • get → recoge el siguiente carácter
    • read
  • Para la escritura de ficheros usaremos el ofstream.
  • Podemos usarlos como streams y usar operadores como « o », de esta forma podemos sobreescribir dichos operadores y enviar nuestras clases.
  • :?: Podemos utilizar el ifstream para escribir, todo según los parámetros que le pasemos al método open. Este sigue la siguiente sintaxis: var.open (nombrefichero, ios::modo_apertura);. Y los modos de apertura son:
    • binary: binario.
    • app: appening.
    • ate: abre desde el final del fichero.
    • in: para entrada de datos.
    • out: salida
    • nocreate: si no existe no lo crea.
    • noreplace
    • trunc: lo abre y elimina el contenido.
std::ofstream f2;
f2.open("fichero2.txt", std::ofstream::out);
  • :?: Existen otros métodos a parte de los convencionales que pueden sernos de utilidad:
    • seekg (pos, camino); → El puntero se coloca en un apartado del fichero, pos: long posición inicial, camino: hacia donde (ios::beg (al principio), ios::end (al final), ios::cur (posición actual)).
    • var.tellg(); → Coge la posición actual del puntero. Ej. Si hacemos un var.seekg(0, ios::end); y luego recogemos lo que nos devuelva var.tellg(); podremos saber el tamaño del archivo. También podemos crear una cadena de caracteres de ese tamaño y luego volcar el archivo en ella con var.read(cadena creada, tamaño);
f2.open("fichero2.txt", ofstream::out | ofstream::trunc);

Sobreescritura de operadores

Podemos programar operadores (+, -, *, ++, +=, ==…) para nuestras clases. Podemos hacerlo de dos formas, la primera internamente en nuestra clase, la segunda globalmente.
Para poder programar un operador haremos: tipodevuelto operatoroperador (parámetros) { }

  • Si tenemos una clase A con un dato interno integer llamado i y codificamos dentro de esta clase el operador -:
int operator- (A a) {
	return a.i - i;
}

Ahora podemos hacer int res = objA1 - objA2; y se llamará al restar del objeto objA1 pasándole el 2.

  • También lo podemos hacer globalmente, desde fuera de la clase, pero esta vez necesitaremos indicar dos parámetros:
int operator- (A a, A b) {
	return a.getI() - (b.getI() + 10);
}

Aún así, si declaramos este operador de estas dos formas tendremos un problema, y es que cuando hagos el objA1 - objA2 no sabrá a qué función entrar, para ello debemos especificarlo:

  1. Llamar a la interna de la clase: b.operator - (a)
  2. Llamar a la función global: operator - (a, b)
  • También podemos sobreescribir el operador - que se encarga del cambio de signo: -objA.
  • Otro operador útil de programar es el ==.
  • Piensa que no es lo mismo hacer (2 == objA) que (objA == 2).
  • Para sobreescribir operadores de streams, un ejemplo:
ostream& operator<<( ostream &out, const A &value) {
	out << value.getI();
	return out;
}
  • Podemos también usar el objeto cual función fuese sobreescribiendo el operador (). También pasandole parámetros.
int RandomInt::operator()() { ... }
int RandomInt::operator()(int na, int nb) { ... }
  • Y después podemos hacer: objRandomInt(); o objRandoInt(1,454);
  • También podemos programar los casts, dentro de la clase A:
operator int() const {
	return this->i;
}
  • Ahora podremos hacer int i = (int)objA;
  • Otro que podemos sobreescribir es [] para darle forma de array:
class A
{
	private:
	char *abc;
	public:
	A()
	{
		abc = "abcdefgh";
	}
	char operator[] (int i)
	{
		return abc[i];
	}
};

Luego, al crear un objeto de la clase A, y al pedir, por ejemplo: a[3] recibiríamos 'd'.

  • Otro operador curioso que podemos sobreescribir es delete, esto podríamos usarlo como destructor, aunque recuerda que el destructor es llamado automáticamente al hacer un delete de un objeto. Una sobreescritura de delete sería la siguiente:
void operator delete (void* lista) {	
	cout << ((Lista*)lista)->toString().c_str() << endl;
}
  • Es un método dentro de la clase Lista, como puedes ver, el delete no recibe un puntero a objeto Lista sino un puntero void.

Namespaces

Usados para englobar código. Por ejemplo:

namespace Prueba {
	class F
	{
	}
}

Este ejemplo declara una clase F dentro del namespace Prueba, podemos acceder a ella mediante Prueba::F.

  • También podemos definir alias a los namespaces:
namespace p = Prueba;

Ahora podemos hacer p::F;

  • También podemos declar namespaces para que no sea necesario poner Prueba::, un ejemplo:
using namespace Prueba;
using namespace std;

Ahora ya podemos usar cout o F sin necesidad de poner std:: o Prueba:: delante.

Notas

  • Si defines una función en un .h que está dentro de un namespace, la codificación del cuerpo se hará como sigue:
// Fichero .h
namespace ns {
  void func ();
}
// Fichero .cpp
using namespace ns;
void ns::func () { ... }
  • Puedes anidar namespaces…
namespace nm1 {
  namespace nm2 {
  ...
  }
}
 
using namespace nm1::nm2;
  • Si sólo vas a acceder a un tipo no es necesario que hagas un using de todo el namespace, puedes hacer un using de sólo ese tipo:
using std::vector

static

Cuando declaramos una variable como static, dentro de una función, esa variable conserva el valor con el que ha acabado al salir del método y lo mantendrá cuando vuelva a entrar.
El siguiente ejemplo mostrará 0011:

int func () {
	static int i = 1;
	static int num = -1;
	i++;
	if (i % 2 == 0)
		num++;
	return num;
}
...
cout << func();
cout << func();
cout << func();
cout << func();

Si quisiesemos definir un valor dentro de una declaración de, por ejemplo, una estructura, deberíamos declarar esa variable como “static”:

struct Queue {
	static const int ClavesTotal = 10;
	int anterior, siguiente;
	int claves[ClavesTotal];
};

Funciones inline

Son funciones que insertan su cuerpo en el código donde son llamadas cuando se compila. Pero para ello la función debe cumplir ciertas reglas: No debe contener bucles, switch, o variables estáticas.

  • Es correcto usar este tipo de funciones cuando únicamente asignen, harán que el código sea más rápido.

Try\catch\throw

  • Como bien es sabido, los try y catch sirven para el control de errores. En el bloque try se pone un código propenso a error, en el catch lo que pasa si surge un error.
  • La estructura es: try { codigo } catch (<tipo parametro>) { código }
  • Un error capturable ocurre cuando se llega a una línea donde hay un throw. La sintaxis del throw es throw <objeto>;
  • Podemos poner tantos catch como queramos, según el error lanzado entrará or un catch u otro.
  • Existen errores prediseñados: logic_error, bad_alloc
  • Pero podemos enviar nuestro propio tipo de error, o hasta un string (que lo itercepta como un char*):
...
throw "Error de conversión";
...
try { ... }
catch (char* s)
{
	cout << s << endl;
}

Definición de tipos (typedef)

  • Usando typedef podemos definir tipos: typedef int numero;
  • Ahora podemos hacer: numero i; ← i es un integer

Esto nos irá bien cuando el tipo sea largo de declarar:

typedef vector< pair<string, string> >  StringMap;

StringMap es del tipo: typedef vector< pair<string, string> >.

highlevel/c.1220983350.txt.gz · Última modificación: 2020/05/09 09:24 (editor externo)