Tabla de Contenidos

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:

cout << "Hola mundo";
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

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

int a = rand()%2;
int rand_int(int a, int b) {
   return a + rand() % (b - a + 1);
}
double rand_double(double a, double b) {
   return a + (b - a) * rand() * (1.0 / RAND_MAX);
}
srand( (unsigned)time( NULL ) );

Expresiones matemáticas

#includes

Declarar métodos y clases sin .h

#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

int array[] = {1,2,3};
void Probando (int array[]) { ... }
int array[2][2];

La función main

int main(int argc, char* argv[]) {...}
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.


if (i != NULL) ...
if (i) // pregunta si i tiene algo, si es NULL no entrará en el if
int **b = &a;

Esta línea es un puntero a otro puntero.

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.
*(a + 1) = 23;		// i[1] = 23;
r = (int*)0x0012f144;
r = (int*)55;
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).

if((buffer = (long *)malloc(sizeof(long))) == NULL)
      	exit( 1 );
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);

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:

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

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 clase string

Podemos concatenar strings mediante el operador '+'.

La clase vector

vector<int> Sumar (vector<double>) {...}
for (vector<point>::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>

math

time

time_t t;
time(&t);
tm hora = *localtime(&t);
int hour = hora.tm_hour;
int min = hora.tm_min;
int sec = hora.tm_sec;

ctype

A parte de las funciones…

… 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

stdlib

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

Ficheros

Otros

Preprocesador

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

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:

Otras cualidades del lenguaje

system

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

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:

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

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;
std::ofstream f2;
f2.open("fichero2.txt", std::ofstream::out);
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) { }

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.

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)
ostream& operator<<( ostream &out, const A &value) {
	out << value.getI();
	return out;
}
int RandomInt::operator()() { ... }
int RandomInt::operator()(int na, int nb) { ... }
operator int() const {
	return this->i;
}
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'.

void operator delete (void* lista) {	
	cout << ((Lista*)lista)->toString().c_str() << endl;
}

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.

namespace p = Prueba;

Ahora podemos hacer p::F;

using namespace Prueba;
using namespace std;

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

Notas

// Fichero .h
namespace ns {
  void func ();
}
// Fichero .cpp
using namespace ns;
void ns::func () { ... }
namespace nm1 {
  namespace nm2 {
  ...
  }
}
 
using namespace nm1::nm2;
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.

Try\catch\throw

...
throw "Error de conversión";
...
try { ... }
catch (char* s)
{
	cout << s << endl;
}

Definición de tipos (typedef)

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> >.