Programacion C y C++
Programacion C y C++
Resumen
Este documento presenta una comparacion entre las primitivas proporcionadas
por los lenguajes C y C++, utilizadas como soporte para la docencia de los con-
ceptos fundamentales de la programacion. Nuestra intencion no es hacer una
comparativa general entre ambos lenguajes, sino unicamente repasar aquellos el-
ementos comunes que pueden ser utilizados en un curso basico de programacion.
Como resultado del estudio, pensamos que, en general, cuando el objetivo de un
curso no es el estudio del lenguaje C, es mas adecuado plantear un primer curso
de programacion sobre la base de C++ que sobre C.
El estudio de arrays siempre es parte importante de cualquier primer curso
inicial de programacion. Tradicionalmente este aspecto ha sido tratado (tanto en
C como en C++) mediante arrays predefinidos. Sin embargo, desde el estandard
C++11 (previamente en TR1 ), C++ dispone de un tipo array que pensamos
que aporta numerosas ventajas docentes sobre el uso tradicional de arrays pre-
definidos. Discutimos las caractersticas de cada uno y analizamos ventajas e
inconvenientes de cada enfoque.
Nota: en todo el texto, salvo que se especifique lo contrario, se hace referen-
cia a ANSI-C (C89).
Contenido
1. Definicion de Constantes 2
2. Definicion de Variables 3
3. Control de Ejecucion 4
6. Definicion de Tipos 11
9. Conclusion 35
cc Esta obra se encuentra bajo una licencia Reconocimiento-NoComercial-
CompartirIgual 4.0 Internacional (CC BY-NC-SA 4.0) de Creative Commons. Vease
http://creativecommons.org/licenses/by-nc-sa/4.0/deed.es ES
1
1. Definicion de Constantes
En el ambito de la enzenanza de la programacion, es conveniente transmitir
la importancia del uso de constantes simbolicas en los programas, evitando en la
medida de los posible el uso de constantes literales. A continuacion analizamos
las posibilidades que ofrecen C y C++ para ello.
2
y utilizar dichas constantes con los valores adecuados.
int v = CONSTANTE3 / 2 ;
Sin embargo, este mecanismo solo es adecuado para definir constantes de tipo
entero (int), no pudiendo ser utilizada para definir constantes de otros tipos,
especialmente de coma flotante.
int v = CONSTANTE3 / 2 ;
int v = CONSTANTE3 / 2 ;
2. Definicion de Variables
2.1. Definicion de variables en C
En C, las variables se deben definir en una zona al comienzo de cada bloque,
y no pueden mezclarse con las sentencias ejecutables.1 As mismo, tambien es
posible inicializar las variables en el lugar en que son definidas.
int main()
{
int x, y ;
int z = 5 ;
3
2.2. Definicion de variables en C++
En C++, las variables se pueden definir en cualquier parte del bloque, pu-
diendo mezclarse con las sentencias ejecutables, de hecho, se consideran tambien
sentencias ejecutables (invocacion al constructor). Esto aumenta la localidad del
codigo, permite declarar las variables en el punto mas cercano al lugar de su uti-
lizacion, y permite declarar e inicializar las variables con los valores adecuados,
en vez de realizar una declaracion de la variable sin inicializar.
int main()
{
int z = 5 ;
int y ;
cin >> y ;
int x = y * z ;
}
3. Control de Ejecucion
3.1. Estructuras de control en C y C++
Aunque ambos lenguajes disponen de otras estructuras de control de la eje-
cucion, en el contexto del aprendizaje de programacion en un ambito academi-
co, basado en un enfoque de diseno descendente, abstraccion procedimental y
programacion estructurada, solo consideraremos las siguientes estructuras de
control:
Secuencia: Un bloque determina un secuencia de sentencias ejecutables.
Las sentencias se ejecutan secuencialmente.
int main()
{
int x, y = 4 ;
int z = 5 ;
x = y * z ;
z = z + x ;
}
if : switch:
int main() int main()
{ {
int x = 3, y = 5 ; int x = 4 ;
if (x > y * 2) { switch (x * 3) {
x = y + 2 ; case 0:
y = 0 ; x += 2 ;
} else if (x > y) { ++x ;
x = 0 ; break ;
} else { case 18:
y = 0 ; case 21:
++x ; --x ;
} break ;
} default:
x *= 5 ;
break ;
}
}
4
Iteracion: Repite la ejecucion de un bloque de sentencias, dependiendo
del estado de la computacion en un momento determinado.
Hay que tener presente que la precedencia del operador de casting es la segunda
mas alta (por debajo del operador punto e indexacion), por lo que resulta facil
introducir errores inesperados. Por ejemplo:
int main()
{
double pi = 3.1415 ;
int x = (int)pi+0.5 ;
}
en lugar de:
int main()
{
double pi = 3.1415 ;
int x = (int)(pi+0.5) ;
}
5
4.2. Conversiones de tipos en C++
En C++, la conversion de tipos se realiza mediante el operador prefijo de
casting, donde se especifica el tipo destino al que se quiere convertir una de-
terminada expresion, seguida por la expresion entre parentesis cuyo resultado
quiere ser convertida. De esta forma, se evitan los posibles problemas derivados
de la precedencia de los operadores. Por ejemplo:
int main()
{
int x = int(3.1415) ;
double pi = 3.1415 ;
int z = int(pi+0.5) ;
}
double pi = 3.1415 ;
ulong z = ulong(pi+0.5) ;
}
double pi = 3.1415 ;
unsigned long z = (unsigned long)(pi+0.5) ;
}
6
5.2. Entrada y salida de datos en C++
En C++, la entrada y salida de datos basica se realiza a traves de los flujos
de entrada (cin) y de salida (cout) mediante operadores adecuados para ello:
#include <iostream>
#include <iomanip>
#include <string>
using namespace std;
int main()
{
char c = a ;
short s = 1 ;
int x = 2 ;
unsigned u = 3 ;
long l = 4 ;
float f = 5.0 ;
double d = 6.0 ;
long double dd = 7.0 ;
string nombre_1 = "juan" ;
char nombre_2[30] = "pepe" ;
cout << c << << s << << x << << u << << l <<
<< f << << d << << dd << << nombre_1 <<
<< nombre_2 << endl ;
7
5.4. Salida formateada de datos en C++
En C++, la salida de datos formateada esta orientada al modo de operacion.
A continuacion se muestra un ejemplo de salida formateda de datos en C++,
donde el smbolo # representa el espacio en blanco:
#include <iostream>
#include <iomanip>
int main()
{
char c = a ;
int x = 12 ;
double d = 3.141592 ;
char s[30] = "hola" ;
8
Es mas simple, porque el uso de scanf requiere que los parametros usados
sean punteros (direcciones de memoria) a las variables que almacenaran
los valores ledos. Ello requiere el conocimiento de un concepto complejo
en una fase inicial de aprendizaje donde aun no se esta preparado para
ello. Alternativamente, se podra utilizar la direccion de memoria de una
variable sin conocer su semantica. En este caso se podra plantear el es-
quema indicando que para leer un dato hay que escribir & precediendo a
la variable a leer con scanf. A priori esta segunda opcion no resulta muy
adecuada, y se ve dificultada por el hecho de que dicho operador & no
se utiliza uniformemente para todos los tipos (se debe usar en variables
de tipos simples y no usar en variables de tipo array de char). Ademas,
es frecuente que en un contexto de aprendizaje de la programacion, se
olvide de poner dicho & (o al contrario, se incluya innecesariamente en
el caso de variables de tipo array), en cuyo caso se genera un error que
en las primeras sesiones practicas aparece frecuentemente y complica el
aprendizaje innecesariamente.
int main()
{
int x ;
char nombre[30] ;
9
Favorece la creacion de programas mas extensibles, ya que no es necesario
tener que mantener la correspondencia del tipo de una variable con la
cadena de formato de una sentencia de E/S. Ello facilita la introduccion
de futuras modificaciones y disminuye la posibilidad de introducir nuevos
errores. Por ejemplo, si queremos modificar el siguiente programa:
C: C++:
typedef struct Dato { struct Dato {
int a ; int a ;
float b ; float b ;
} Dato ; } ;
int main() int main()
{ {
Dato d = { 1, 3.14 } ; Dato d = { 1, 3.14 } ;
printf("%d %g\n", d.a, d.b) ; cout << d.a << << d.b << endl ;
} }
C: C++:
typedef struct Dato { struct Dato {
float a ; float a ;
float b ; float b ;
} Dato ; } ;
int main() int main()
{ {
Dato d = { 1, 3.14 } ; Dato d = { 1, 3.14 } ;
printf("%g %g\n", d.a, d.b) ; cout << d.a << << d.b << endl ;
} }
10
#include <iostream>
int main()
{
cout << "sizeof(char): " << sizeof(char) << endl ;
}
6. Definicion de Tipos
6.1. El Tipo Logico (Booleano)
6.1.1. El Tipo Logico (Booleano) en C (Estandar ANSI C89)
El lenguaje de programacion C, segun el estandar ANSI C89, no define
ningun tipo predefinido para representar valores logicos, y utiliza para ello el
tipo int, utilizando un valor igual a cero (0) para representar el valor logico fal-
so, y cualquier valor distinto de cero para representar el valor logico verdadero.
Por ejemplo:
int main()
{
int x = 7 ;
int ok = 1 && (x > 5) || 0 ;
}
Sin embargo, esta definicion podra colisionar con otras definiciones realizadas
dentro de bibliotecas que se estuviesen utilizando, por lo que hay que extremar
las precauciones en este esquema.
11
6.1.3. El Tipo Logico (Booleano) en C++
El lenguaje de programacion C++ dispone del tipo predefinido bool para
representar los valores logicos, as como las constantes predefinidas true y false
para representar los valores logicos verdadero y falso, de tal forma que la eva-
luacion de expresiones logicas produce como resultado un valor de dicho tipo.
int main()
{
int x = 7 ;
bool ok = true && (x > 5) || false ;
}
12
El nuevo estandar de C++ (C++11) define tambien otras clases de tipos
enumerados con mayor control del ambito de los valores definidos, pero no seran
considerados en este documento.
6.3. Registros
6.3.1. Registros en C
El lenguaje de programacion C permite al programador definir tipos estruc-
turados (registros) como composicion de elementos (campos), de tal forma que
el nombre del tipo registro se define dentro de un ambito restringido de tipos
registro, y por lo tanto requiere especificar la palabra reservada struct antes
de utilizar el tipo. Para definir el nombre del tipo registro dentro del ambito
global, es necesario utilizar la palabra reservada typedef. Por ejemplo:
El nombre del tipo definido pertenece al ambito donde haya sido definido
el tipo registro, y puede ser utilizado directamente en dicho ambito.
Por ejemplo:
struct Fecha {
int dia, mes, anyo ;
} ;
int main()
{
Fecha hoy = { 25, 4, 2012 } ;
cout << hoy.dia << / << hoy.mes << / << hoy.anyo << endl;
}
6.4. Arrays
En general, el tipo array se utiliza para definir una secuencia de un numero
determinado (definido en tiempo de compilacion) de elementos de un mismo
tipo de datos (simple o compuesto), de tal forma que se puede acceder a cada
elemento individual de la secuencia de forma parametrizada mediante ndices
(el valor cero indexa el primer elemento de la coleccion).
Tanto en C como en C++ se pueden utilizar arrays predefinidos, pero en
C++, desde la version del estandar C++11 (previamente en TR1 ) es posible,
ademas, usar el tipo array de la biblioteca estandar de C++.
13
6.4.1. Arrays predefinidos en C y C++
En ambos lenguajes se pueden usar arrays predefinidos de igual forma y con
los mismos operadores. En este tipo de arrays:
C: C++:
#define MAX 5 const int MAX = 5 ;
typedef int Vector[MAX] ; typedef int Vector[MAX] ;
int main() int main()
{ {
int i; int i;
Vector v = { 1, 2, 3, 4, 5 } ; Vector v = { 1, 2, 3, 4, 5 } ;
for (i = 0; i < MAX; ++i) { for (i = 0; i < MAX; ++i) {
v[i] = v[i] * 2 ; v[i] = v[i] * 2 ;
} }
} }
14
El tipo array proporciona operadores predefinidos para la copia y asignacion
(=), para las operaciones relacionales (==, !=, >, >=, <, <=)3 y para acceder al
numero de elementos que componen un array (size()). Ademas, se permite la
devolucion de valores de tipo array desde funciones y es posible chequear si el
acceso a elementos del array utiliza ndices dentro del rango definido para el
mismo.
Notese que, en caso de utilizar un compilador que no soporte el nuevo
estandar C++11, la inicializacion de variables de tipo array de TR1 se de-
bera hacer con la enumeracion de los valores constantes entre llaves dobles.
En la seccion 7.2.4 se describe el paso de parametros de arrays de la biblioteca
estandar de C++.
del array.
4 Disponible en versiones de GCC posteriores a Noviembre de 2012 (version 4.8).
15
Los arrays predefinidos tienen una conversion implcita automatica al tipo
puntero que es indeseable en muchos casos.
Finalmente, el tipo array de la biblioteca estandar de C++ proporciona
tanto el constructor por defecto como el constructor de copia y el operador
de asignacion, por lo que podra ser utilizado adecuadamente tanto en entornos
de clases (class) como en entornos de programacion generica (templates),
as tambien como tipo base de los contenedores estandares (vector, deque,
stack, list, etc.). Esto no es posible con los arrays predefinidos.
Clases: Clases Genericas:
class Pixel { template <typename TIPO>
public: class Dato {
Pixel() : rgb() {} public:
Pixel(const Pixel& o) Dato() : dat() {}
: rgb(o.rgb) {} Dato(const Dato& o)
Pixel& operator=(const Pixel& o) : dat(o.dat) {}
{ Dato(const TIPO& o)
rgb = o.rgb ; : dat(o) {}
return *this ; Dato& operator=(const Dato& o)
} {
private: dat = o.dat ;
array<short, 3> rgb ; return *this ;
// short rgb[3] ; // ERROR }
} ; void set(const TIPO& o)
{
Genericidad: dat = o ;
}
template <typename Tipo> TIPO get() const
void intercambio(Tipo& x, Tipo& y) {
{ return dat ;
Tipo aux = x ; }
x = y ; private:
y = aux ; TIPO dat ;
} } ;
En contra del uso del tipo array se puede argumentar que, aunque forma
parte del estandar de C++, es posible que algunos compiladores aun no lo incor-
poren. Sin embargo, los compiladores mas usados si lo incorporan (incorporado
en 2005 a la version 4.0 de GCC en TR1, y en 2007 a la version 4.3 de GCC
como soporte experimental para C++11 en la biblioteca estandar) y es, ademas,
facilmente incorporable a otros compiladores mas antiguos.
16
final de la misma (el caracter \0). Por lo tanto, siempre habra que considerar
que el array tenga espacio suficiente para almacenar dicho caracter terminador.
Las cadenas de caracteres constantes literales se expresan como una secuencia
de caracteres entre comillas dobles (").
Al estar definidas como arrays predefinidos de caracteres, todo lo explicado
anteriormente respecto a los arrays predefinidos (vease 6.4.1) tambien se aplica
a las cadenas de caracteres. En la seccion 7.1.4 se describe el paso de parametros
de cadenas de caracteres en C.
Es mas simple, ya que dispone de operadores predefinidos (=, ==, !=, >, >=,
<, <=) cuyo comportamiento es el esperado y no requiere considerar casos
17
especiales. En el caso de array de char los operadores relacionales no
proporcionan los resultados esperados y la asignacion solo esta permitida
en la inicializacion y si el elemento a asignar es un literal constante o es
un campo de un registro que se asigna a una variable de tipo registro.
Es consistente con los otros tipos de datos, permitiendo la asignacion (=)
de variables de dicho tipo, el uso de los operadores relacionales con el
comportamiento esperado y su devolucion desde una funcion.
18
7. Subprogramas: Paso de Parametros
7.1. Subprogramas y paso de parametros en C
7.1.1. Paso de parametros de tipos simples en C
El lenguaje de programacion C solo define el paso por valor de parametros,
por lo que para soportar parametros de salida (o entrada/salida) el progra-
mador debe utilizar explcitamente otros mecanismos, tales como el uso del tipo
puntero, el operador de direccion de memoria (&) y los operadores de desrefe-
renciacion (* y ->).
int mayor(int x, int y)
{
return (x > y) ? x : y ;
}
void intercambiar(int* x, int* y)
{
int aux = *x ;
*x = *y ;
*y = aux ;
}
void proc(int* z)
{
int i = mayor(5, *z) ;
intercambiar(&i, z) ;
}
int main()
{
int x = 5 ;
int y = mayor(9, x) ;
intercambiar(&x, &y) ;
proc(&x) ;
}
19
img2->nfils = img1.nfils ;
img2->ncols = img1.ncols ;
for (f = 0 ; f < img2->nfils ; ++f) {
for (c = 0 ; c < img2->ncols ; ++c) {
img2->img[f][c] = media(img1, f, c) ;
}
}
}
int main()
{
struct Imagen img1, img2 ;
/* ... */
transformar(img1, &img2) ;
}
Por este motivo, en C los tipos estructurados siempre se pasaran como pun-
teros, que en el caso de parametros de entrada deberan ser punteros a constantes.
Por ejemplo:
struct Imagen {
int nfils, ncols ;
int img[1000][1000] ;
} ;
int media(const struct Imagen* img1, int f, int c)
{
/* ... */
}
void transformar(const struct Imagen* img1, struct Imagen* img2)
{
int f, c ;
img2->nfils = img1->nfils ;
img2->ncols = img1->ncols ;
for (f = 0 ; f < img2->nfils ; ++f) {
for (c = 0 ; c < img2->ncols ; ++c) {
img2->img[f][c] = media(img1, f, c) ;
}
}
}
int main()
{
struct Imagen img1, img2 ;
/* ... */
transformar(&img1, &img2) ;
}
20
}
int main()
{
Datos d ;
inicializar(d) ;
printf("%d\n", suma(d) ) ;
}
21
Si se utiliza el operador de asignacion (=), no se produce ningun error de
compilacion. Este comportamiento es diferente si se utiliza la asignacion
entre arrays que no son parametros. En el primer caso el compilador no
nos avisa de ningun error (aunque en realidad obtendremos resultados
inesperados) mientras que en el segundo si tendremos aviso de error. Por
ejemplo:
#define MAX 5
typedef int Datos[MAX] ;
void copiar(Datos d, Datos o)
{
d = o ; /* No error de compilacion. resultados inesperados */
}
int main()
{
Datos d2, d1 = { 1, 2, 3, 4, 5 } ;
d2 = d1 ; /* error: invalid array assignment */
copiar(d2, d1) ;
}
22
for (i = 0; cad[i] != \0; ++i) {
printf("%c",cad[i]) ;
}
}
int longitud(const char cad[])
{
int i = 0 ;
while (cad[i] != \0) {
++i ;
}
return i ;
}
void copiar(char destino[], const char origen[], int sz)
{
int i ;
for (i = 0; (i < sz-1)&&(origen[i] != \0); ++i) {
destino[i] = origen[i] ;
}
destino[i] = \0 ;
}
int main()
{
Cadena c1 = "Pepe" ;
Cadena c2 ;
int l = longitud(c1) ;
copiar(c2, c1, MAX_CADENA) ;
escribir(c2) ;
copiar(c2, "Luis", MAX_CADENA) ;
escribir(c2) ;
}
23
su facilidad y consistencia de uso (comparese con la complejidad del codigo
equivalente en C 7.1.1).
24
}
return s ;
}
int main()
{
Datos d ;
inicializar(d) ;
printf("%d\n", suma(d) ) ;
}
25
void inicializar(const Datos& d)
{
for (int i = 0 ; i < MAX ; ++i) {
d[i] = i ; /* Error de Compilacion */
}
}
int main()
{
Datos d ;
inicializar(d) ;
}
26
}
cout << endl ;
}
int main()
{
string nombre_1, nombre_2 = "hola" ;
copiar(nombre_1, nombre_2) ;
escribir(nombre_1) ;
}
27
punteros, por lo que cada tipo de datos es susceptible de ser utilizado como
base para ser apuntado por un puntero.
struct Imagen {
int nfils, ncols ;
int img[1000][1000] ;
} ;
void transformar(const struct Imagen* img1, struct Imagen* img2)
{
/* ... */
}
int main()
{
struct Imagen img1, img2 ;
/* ... */
transformar(&img1, &img2) ;
}
28
biblioteca estandar (stdlib.h).
void *malloc(size_t size): reserva un area de size bytes en la zona de memo-
ria dinamica (heap), y devuelve un puntero a dicha area reservada.
void *calloc(size_t nelms, size_t size): reserva (e inicializa a cero) un
area de memoria consecutiva en la zona de memoria dinamica (heap) para alma-
cenar un array de nelms elementos de tamano size bytes cada uno, y devuelve
un puntero a dicha area reservada.
void *realloc(void *ptr, size_t size): cambia el tamano del area de memo-
ria dinamica apuntado por ptr para que pase a ocupar un nuevo tamano de size
bytes, y devuelve un puntero a la nueva area reservada. Si el area de memoria
original puede ser expandida o contrada al nuevo tamano size, entonces se ex-
pande o contrae al nuevo tamano, sin necesidad de copiar los datos. Sin embargo,
si el area de memoria original no puede ser expandida, entonces se reserva un
nuevo area de memoria, se copian los datos originales almacenados en el area
original, y se libera el area original. Finalmente se devuelve un puntero al area
reservada.
void free(void *ptr): libera el area de memoria dinamica apuntada por ptr,
que debe haber sido reservada previamente mediante una invocacion a malloc,
calloc o realloc.
El operador sizeof(Tipo) devuelve el tamano en bytes necesario para al-
macenar una variable del tipo especificado. Si este operador se aplica a una
determinada expresion, entonces devuelve el tamano en bytes necesario para
almacenar el resultado de dicha expresion.
struct Dato {
int valor ;
} ;
int main()
{
struct Dato* ptr = malloc(sizeof(*ptr)) ;
if (ptr != NULL) {
ptr->valor = 5 ;
/* ... */
free(ptr) ;
}
}
29
struct Dato dat ;
struct Dato* ptr = &dat ;
int* pv = &ptr->valor ;
ptr->valor = 5 ;
*pv += 3 ;
incremento(&dat.valor, 7) ; /* dat.valor toma el valor 15 */
}
#define NELMS 5 p
typedef int Datos[NELMS] ;
int main()
{ dat 0 1 2 3 4
Datos dat = { 0, 1, 2, 3, 4 } ;
int* p = dat ;
int* q = &dat[0] ; q
}
30
8.1.5. Punteros genericos en C
Ademas de la definicion de punteros a tipos especficos, el lenguaje de pro-
gramacion C permite definir punteros a tipos desconocidos (genericidad de bajo
nivel, sin control estricto de tipos), que no pueden ser desreferenciados. Para
poder ser desreferenciados antes deben ser convertidos (casting) a punteros a un
determinado tipo concreto. Las conversiones entre void* y otros tipos punteros
son automaticas en C, y no es necesaria la conversion explcita.
struct Imagen {
int nfils, ncols ;
int img[1000][1000] ;
} ;
void cero(void* dato, unsigned sz)
{
int i ;
char* dat = (char*)dato ;
for (i = 0; i < sz; ++i) {
dat[i] = 0 ;
}
}
int main()
{
struct Imagen img ;
cero(&img, sizeof(img)) ;
}
31
int main()
{
Dato dat ;
dat = crear() ;
incrementar(dat, 5) ;
destruir(dat) ;
}
//--------------------------------------
32
As, C++ define dos nuevos operadores (new y delete) que permiten reservar
y liberar memoria dinamica, pero invocando a los contructores y destructores
de los objetos que se crean y destruyen respectivamente.
ptr = new Tipo: reserva un area de memoria dinamica de tamano adecuado
para contener un objeto de tipo Tipo, y lo construye invocando al constructor
especificado. Finalmente devuelve un puntero al objeto alojado y construido en
memoria dinamica.
delete ptr: destruye, invocando a su destructor, el objeto apuntado por ptr, y
libera el area de memoria dinamica que ocupaba. Este objeto debe haber sido
construido utilizando el operador new.
typedef struct Dato* PDato ;
struct Dato {
int valor ;
} ;
int main()
{
PDato ptr = new Dato ;
ptr->valor = 5 ;
/* ... */
delete ptr ;
}
33
solo son utilizadas en contadas ocasiones y siempre dentro de abstracciones que
encapsulen estos mecanismos de bajo nivel. De hecho, el tipo vector de la bi-
blioteca estandar proporciona una abstraccion adecuada de alto nivel.
34
8.2.7. Punteros inteligentes en C++
La potencia de C++, con contructores, destructores y sobrecarga de opera-
dores, permite definir punteros inteligentes que gestionen automaticamente la
liberacion de los recursos (memoria dinamica) asociados a ellos.
La biblioteca estandar (C++11) define los siguientes punteros inteligentes:
shared_ptr, weak_ptr y unique_ptr, as como el obsoleto auto_ptr. Para
compiladores anteriores al estandar de 2011, se debe utilizar la biblioteca TR1.
As mismo, tambien hace posible la definicion de punteros inteligentes que
proporcionen una considerable ayuda en la depuracion de programas y en el
aprendizaje de los conceptos y practica de punteros y gestion de memoria
dinamica.
9. Conclusion
Este documento presenta las primitivas proporcionadas por los lenguajes de
programacion C y C++ como herramientas docentes para el aprendizaje de la
programacion desde un ambito academico.
As, se ha podido mostrar como para cada construccion alternativa que pre-
senta C++ respecto a C, la alternativa presentada por C++ proporciona un
esquema mas simple, seguro y robusto, ante la introduccion de posibles errores
actuales y futuros, respecto a la propuesta original proporcionada por C.
struct Dato_1 {
int x ;
size_t y ;
int z ;
} ;
struct Dato_2 {
int x ;
unsigned int y ;
int z ;
} ;
struct Dato_3 {
int x ;
unsigned long long y ;
int z ;
} ;
/*
* El tipo size_t es el tipo del valor devuelto por el operador
* sizeof(...), as como el tipo de numerosos parametros de la
* biblioteca estandar de C (y C++).
*
35
* Aunque el tipo size_t no es el centro de la argumentacion
* que se describe en este ejemplo, si da idea de los problemas
* asociados al modelo de entrada/salida de C, donde una cadena
* de formato controla la entrada/salida.
*
* El principal problema con size_t es que es un tipo definido
* por la biblioteca estandar sobre el que no se especifica su
* tipo base, y por lo tanto en algunos sistemas de 32 bits se
* define como un tipo unsigned int (de 32 bits), sin embargo,
* en algunos sistemas de 64 bits se define como un tipo
* unsigned long (de 64 bits). Por lo que hacer codigo correcto
* que compile adecuadamente en cualquier maquina, independientemente
* de si es de 32 o 64 bits se vuelve una tarea complicada sin
* utilizar directivas de compilacion condicional (tambien
* dependientes de la maquina).
*
* Este tipo de problema tambien surge cuando se cambia el tipo
* de una variable, parametro o campo de estructura, por ejemplo
* de float a double, de unsigned a int, de short a int, de int
* a long, etc.
*/
void p0_32_bits()
{
/*
* En una maquina de 32 bits, este codigo debe compilar
* adecuadamente, ya que size_t es de tipo unsigned int.
* Sin embargo, en una maquina de 64 bits este codigo
* no compilara adecuadamente (GCC -Wall), y producira
* codigo ejecutable erroneo (en caso de compilar sin flags)
*/
printf("sizeof(char): %u\n", sizeof(char)) ;
}
void p0_64_bits()
{
/*
* En una maquina de 64 bits, este codigo debe compilar
* adecuadamente, ya que size_t es de tipo unsigned long.
* Sin embargo, en una maquina de 32 bits este codigo
* no compilara adecuadamente (GCC -Wall), y producira
* codigo ejecutable erroneo (en caso de compilar sin flags)
*/
printf("sizeof(char): %lu\n", sizeof(char)) ;
}
void p1_32_bits()
{
/*
* Este ejemplo muestra como sera la Entrada/Salida de
* un elemento de tipo size_t en una maquina de 32 bits.
* En una maquina de 64 bits producira errores de compilacion
* (GCC -Wall) o codigo erroneo que afectara a los valores
* de los otros campos.
*/
struct Dato_1 d = { 5, 7, 9 } ;
void p1_64_bits()
{
/*
* Este ejemplo muestra como sera la Entrada/Salida de
* un elemento de tipo size_t en una maquina de 64 bits.
* En una maquina de 32 bits producira errores de compilacion
* (GCC -Wall) o codigo erroneo que afectara a los valores
* de los otros campos.
*/
struct Dato_1 d = { 5, 7, 9 } ;
36
printf(" X: %d Y: %lu Z: %d\n", d.x, d.y, d.z) ;
printf("Introduce un numero: ") ;
scanf(" %lu", &d.y) ;
printf(" X: %d Y: %lu Z: %d\n", d.x, d.y, d.z) ;
}
void p2()
{
/*
* Ejemplo para mostrar los problemas de codigo donde el
* formato de Entrada/Salida especifica 64 bits, y el tipo
* de datos es de 32 bits. Se puede apreciar como la salida
* es incorrecta, y la entrada de datos modifica otras
* variables (Z).
*/
struct Dato_2 d = { 5, 7, 9 } ;
printf("Valores correctos: X: %d Y: %u Z: %d\n", d.x, d.y, d.z) ;
printf(" X: %d Y: %llu Z: %d\n", d.x, d.y, d.z) ;
printf("Introduce un numero: ") ;
scanf(" %llu", &d.y) ;
printf(" X: %d Y: %llu Z: %d\n", d.x, d.y, d.z) ;
printf("Valores correctos: X: %d Y: %u Z: %d\n", d.x, d.y, d.z) ;
}
void p3()
{
/*
* Ejemplo para mostrar los problemas de codigo donde el
* formato de Entrada/Salida especifica 32 bits, y el tipo
* de datos es de 64 bits. Se puede apreciar como la salida
* es incorrecta. El error en la entrada de datos dependera
* de si la maquina es big-endian o little-endian.
*/
struct Dato_3 d = { 5, 7, 9 } ;
printf("Valores correctos: X: %d Y: %llu Z: %d\n", d.x, d.y, d.z) ;
printf(" X: %d Y: %u Z: %d\n", d.x, d.y, d.z) ;
printf("Introduce un numero: ") ;
scanf(" %u", &d.y) ;
printf(" X: %d Y: %u Z: %d\n", d.x, d.y, d.z) ;
printf("Valores correctos: X: %d Y: %llu Z: %d\n", d.x, d.y, d.z) ;
}
int main()
{
printf("-------------------------------\n") ;
p0_32_bits() ;
printf("-------------------------------\n") ;
p0_64_bits() ;
printf("-------------------------------\n") ;
p1_32_bits() ;
printf("-------------------------------\n") ;
p1_64_bits() ;
printf("-------------------------------\n") ;
p2() ;
printf("-------------------------------\n") ;
p3() ;
printf("-------------------------------\n") ;
return 0 ;
}
37