====== C\C++ ====== * [[highlevel:c:poo|C\C++ orientado a objetos]] * [[highlevel:c:xtra|Documentación extra]] * [[otros:otros#compiladores_de_c|Configuración de compiladores de 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 '') 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 . 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 '' * 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?** - Definición de variable int, i, que vale 4. - Definición de puntero int, a, que vale la dirección donde se encuentra i. - Mostramos a, la dirección donde se encuentra i. - 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; - Si hacemos cout de *a veremos el valor 33. - 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 '' 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: - Creamos puntero i y ocupamos el espacio donde apunta con un int. - Damos al lugar donde apunta i el valor 6. - Liberamos el espacio donde apuntaba i. - Creamos otro puntero int llamado a apuntando a la última memoria libre, justo donde apuntaba i. - Asignamos a "donde apunta a" el valor 50. - 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 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 '') 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 nombreVec;'' * Con esta sentencia declaramos un vector dinámico, pero podemos declararlo también estático: ''vector 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 array;'' * Si tenemos un vector dinámico podemos usarlo como una pila, mediante el método .push_back() 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 Sumar (vector) {...} * También podemos pasarlos por referencia: ''vector& v'' * Podemos recorrer un vector con un iterador: for (vector::iterator it = points.begin(); it!=points.end(); ++it) { cout << it->x << endl; } ===== ANSI C ===== Las funciones de C clasificadas por librerías. Para utilizar una tendrás que incluir el .h correspondiente, por ejemplo: ''#include '' ==== 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 #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 _CrtMemState startMemState; _CrtMemState endMemState; #else #include #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'' en compiladores de C++, en los de C ''#include '' * 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 es menor que 3 el compilador actúa. (Comprueba si la expresión pasada por parámetro no es cero, si lo es envia lanza un error) \\ //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|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 '' (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 **operator**//operador// (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: - Llamar a la interna de la clase: ''b.operator - (a)'' - 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 () { código }'' * Un error capturable ocurre cuando se llega a una línea donde hay un throw. La sintaxis del throw es ''throw ;'' * 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 > StringMap; StringMap es del tipo: typedef vector< pair >.