Guía C++ para programadores Java
Guía C++ para programadores Java
Gerson Lázaro
2016
Nota del autor (
)
CC BY-SA 4.0
Esta obra se comparte bajo licencia Creative Commons Atribución - CompartirIgual 4.0 Internacional (CC BY-SA 4.0), lo cual le permite:
● Atribución — Usted debe darle crédito a esta obra de manera adecuada , proporcionando un enlace a la licencia, e i
ndicando si se han
realizado cambios . Puede hacerlo en cualquier forma razonable, pero no de forma tal que sugiera que usted o su uso tienen el apoyo del
licenciante.
● CompartirIgual — Si usted mezcla, transforma o crea nuevo material a partir de esta obra, usted podrá distribuir su contribución siempre que
utilice la
misma licenciaque la obra original.
1
Contenido
1. Para empezar
1.1. ¿Que es esta guia?
1.2. Estructura de la guía
2. Aspectos Básicos
2.1. Consideraciones iniciales
2.2. Estructura y Hola Mundo
2.3. Tipos de datos
2.4. Entrada y salida
3. Estructuras de datos
3.1. ArrayList - Vector
3.2. TreeSet - Set
3.3. TreeMap - Map
3.4. Stack
3.5. Queue
3.6. Priority Queue
3.7. Deque
4. Utilidades de C++ vs. Java
4.1. Matemáticas
4.2. Búsqueda y Ordenamiento
5. Utilidades de C++ ausentes en Java
5.1. Bitset
5.2. Pair
6. Utilidades de Java ausentes en C++
7. Navaja Suiza: Optimizar trabajo en C++
2
7.1. bits/stdc++.h
7.2. Acortar nombre de los tipos de datos (typedef)
7.3. Definir datos globales (define)
7.4. Redefinir instrucciones (define)
7.5. Llenar arreglos o matrices (o inicializarlos)
7.6. Optimizar entradas y salidas
7.7. Un ejemplo completo: Java vs. C++
3
1. Para empezar
Por supuesto, son dos lenguajes diferentes, con una idea diferente, y no todo lo que haga en uno, tendrá una equivalencia exacta en el otro, por lo
cual en algunos aspectos he recurrido a buscar soluciones que cumplan la misma función, aunque se realicen de formas diferentes; aun así, el proceso
de aprendizaje de un lenguaje conlleva prueba y error, por lo que si en algún punto no te sientes conforme con la equivalencia planteada, siempre
puedes revisar en la documentación del lenguaje, en StackOverflow o en el todopoderoso Google. Y si crees que tu solución es mejor que la
equivalencia que yo he planteado, ¡házmelo saber!.
Antes de empezar con el código puro y duro, pido un favor adicional: Si encuentras un error ya sea en la escritura, en el código o las equivalencias,
infórmame para corregirlo (
gersonlazaro@[Link] ).
4
2. Aspectos Básicos
Hola Mundo
importjava
.
io
.*; #include
<
bits
/
stdc
++.
h>
public
class
Main{ using
namespacestd;
public
static
voidmain
(
String
[]args
){ intmain
(){
System
.
out
.
println
(
"Hola Mundo"
); cout
<<
"Hola Mundo :)" << endl;
} return
0;
} }
● El #include de c++ cumple la misma función del import de java. Su sintaxis es:
#include
<nombreLibreria>
5
Pueden incluirse una a una las librerías que van a usarse. Sin embargo, en el ejemplo puede leerse #include <bits/stdc++.h>, un macro que se
encarga de incluir todo lo necesario sin que debas hacerlo manualmente. Usando esta linea, no debes preocuparte por ningún otro include1 .
● Si bien no entraré en detalle en “using namespace std”, es importante definirlo siempre al inicio, pues indica que usaremos el espacio de
nombres std, y esto nos evitará estar escribiendo “std::” en el resto del programa.
● En java, el método main es void, mientras en c++ es int. Por lo tanto, requiere retornar algo. Por esta razón, añadimos al final del main return
0, que indica éxito (cualquier número diferente a 0 indica error).
//Arreglos //Arreglos
intarreglo
[]
=
new
int
[
100
]; intarreglo
[
100
];
1
Mas información sobre como funciona <bits/stdc++.h> en la sección 7.1
6
2.4. Entrada y salida
Entrada y Salida
Scannersc
=
new
Scanner
(
System
.
in
); inta;
inta
=sc
.
nextInt
(); stringc;
Stringc
=sc
.
next
(); //MODO 1
System
.
out
.
println
(
a+
"
"
+c
); cin
>>a
>>c
;
//Lee un entero y un string desde consola
cout
<<a
<<
" "
<<c
<<endl
;
//Imprime en pantalla
//MODO 2
scanf
("%
d
",
&
a
);
//Lee un entero
printf
("%
d\n
",a
);
7
3. Estructuras de datos
8
//Borrar valor //Borrar valor
t
.
remove
(
25
); t
.
erase
(
25
);
//Borrar todo //Borrar todo
t
.
clear
(); t
.
clear
();
//Obtener tamaño //Obtener tamaño
t
.
size
(); t
.
size
();
9
tm
.
containsKey
("
clave
"); tm
.
count
("
clave
");
//Obtener el valor asociado a una clave //Obtener el valor asociado a una clave
tm
.
get
("
clave
") tm
["
clave
"];
//Modificar un elemento (ej: sumar 2) //Modificar un elemento (ej: sumar 2)
tm
.
put
("
clave
",tm
.
get
("
clave
")
+
2
); tm
["
clave
"]
+=
2;
//Verificar si el TreeMap está vacío //Verificar si el map está vacío
tm
.
isEmpty
(); tm
.
empty
();
//Borrar valor //Borrar valor
tm
.
remove
("
clave
); tm
.
erase
("
clave
");
//Borrar todo //Borrar todo
tm
.
clear
(); tm
.
clear
();
//Obtener tamaño //Obtener tamaño
tm
.
size
(); tm
.
size
();
3.4. Stack
10
s
.
push
(
15
); s
.
push
(15);
//Eliminar del tope y obtener el valor //Eliminar del tope (no obtiene, solo elimina)
s
.
pop
(); s
.
pop
();
//Obtener del tope sin eliminar //Obtener del tope sin eliminar
s
.
peek
(); s
.
top
();
//Verificar si el stack está vacío //Verificar si el stack está vacío
s
.
empty
(); s
.
empty
();
3.5. Queue
11
//Verificar si la cola está vacío //Verificar si la cola está vacío
s
.
isEmpty
(); s
.
empty
();
3.7. Deque
12
4. Utilidades de C++ vs. Java
4.1. Matemáticas
Todas las principales funciones matemáticas se encuentran tanto en C++ como en java con el mismo nombre, con la única diferencia que en Java se invoca
mediante “Math.NOMBRE_FUNCION()” y en C++ solo “NOMBRE_FUNCION()”.
Búsqueda y Ordenamiento
ArrayList
<
Integer
>AR
=
new
ArrayList
<>(); vector
<int>AR
;
//Ordenamiento //Ordenamiento
Collections
.
sort
(
AR
); sort
(
AR
.
begin
(),AR
.
end
());
//Verificar si contiene un elemento //Verificar si contiene un elemento
AR
.
contains
(
13
);
//Retorna true o false count
(
AR
.
begin
(),AR
.
end
(),
13
);
//Retorna el número de
veces que se encontró el elemento
//Búsqueda binaria //Búsqueda binaria
Collections
.
binarySearch
(
AR
,
13
);
//Retorna el indice binary_search
(
AR
.
begin
(),AR
.
end
(),
13
);
//Retorna un bool
13
5. Utilidades de C++ ausentes en Java
Algunas utilidades de C++ no tienen un equivalente preconstruido en Java, aunque bien pueden construirse fácilmente. En esta sección se presentan
estas utilidades, como podrían construirse en java, y cómo se utiliza la implementación definida en C++.
5.1. Bitset
El bitset emula un array de elementos booleanos, pero optimizados para la asignación de espacio: generalmente cada elemento ocupa solo un bit
(que en muchos sistemas es ocho veces menor que el tipo de dato elemental mas pequeño: char.2
Bitset
//En java no existe un bitset como tal. Por lo tanto,
debemos implementar el típico array de booleans, aunque como
se explicó en la definición, ocupa mucho mas espacio.
booleanbs
[]
=
new
boolean
[
100
]; bitset
<
100
>bs
;
//Declarar una posición como true //Declarar una posición como true
bs
[
13
]
=
true
; bs
.
set
(
13
);
//Declarar una posición como falsa //Declarar una posición como false
bs
[
13
]
=
false
; bs
.
unset
(
13
);
//Invertir el valor de una posición //Invertir el valor de una posición
bs
[
13
]
=
!
bs
[
13
]; bs
.
flip
(
13
);
//Contar cuantas posiciones están en true //Contar cuantas posiciones están en true
intposiciones
=
0
; bs
.
count
();
for
(
inti
=
0
;i
<bs
.
length
;i
++)
{
if
(
bs
[
i
])posiciones
++;
}
//Verificar si existe al menos una posición en true //Verificar si existe al menos una posición en true
boolexiste
=
false
; bs
.
any
();
for
(
inti
=
0
;i
<bs
.
length
;i
++)
{
if
(
bs
[
i
])
{
existe
=
true
;
break
;
}
}
2
Definición tomada de
[Link]
14
5.2. Pair
El pair es una estructura que permite guardar dos elementos de diferente tipo en una sola unidad.
Pair
//Java no cuenta con una estructura pair (clavevalor)
predefinida. En cierta manera puede utilizarse [Link], o
bien, construir una clase simple para esta labor. A
continuación se presenta una clase simple, pero en la
practica puede ser necesario crear mas métodos para dotarla
de una verdadera funcionalidad
static
class
Pair
<
T
,U
>{
publicT first
;
publicU second
;
public
Pair
(
T t
,U u
)
{
this
.
first
=t
;
this
.
second
=u
;
}
}
15
6. Utilidades de Java ausentes en C++
Existen sin duda alguna muchas utilidades que facilitan el trabajo en uno u otro lenguaje. Ya vimos algunas de las utilidades de C++ que no están
presentes en java (Solo un par, son muchas mas), y ahora es el turno de hablar de aquellas que están en Java pero no en C++. Si bien pueden ser
muchisimas, existen 3 en especifico que son de suma utilidad en competencias de programación:
● BigInteger
● BigDecimal
● Geometry
No existen librerías propias de C++ que suplan estas necesidades. Existen por supuesto, muchas utilidades creadas por terceros que llenan estas
falencias, pero en competencias no es posible importar librerías que no hacen parte del std de C++. Está la opción también de construir durante
competencia nuestras propias librerías que emulen estas características (o mejor aún, llevarlas impresas para copiarlas en competencia en caso de ser
necesario), pero esto siempre conlleva un tiempo considerable, por lo que la mejor recomendación es utilizar Java cuando un ejercicio requiera
estrictamente estas utilidades.
Aún así, es importante verificar que estas librerías sean realmente necesarias. Muchos ejercicios que parecieran manejar BigInteger pueden resolverse
con simples Strings, o aritmética modular. Del mismo modo, muchos ejercicios en esencia geométricos, son en realidad resolubles con matemática
básica. Y en cualquiera de estos casos, sí que podemos trabajar C++ en vez de Java.
16
7. Navaja suiza: Optimizar trabajo en C++
Existen muchas utilidades pequeñas que pueden hacer mas fácil la manera de trabajar en C++, dando versatilidad al lenguaje. Estas utilidades no son
de obligatorio uso, y existen pros y contras en su utilización. Aquí se presentan sin tomar partido a favor o en contra, y es responsabilidad de cada
quien incluirlos o no en su código.
7.1. bits/stdc++.h
Al principio de la guía, se utilizó #include <bits/stdc++.h>. De no haber usado esta línea, sería necesario hacer un include en una línea por cada
librería utilizada: #include <vector>, #include <stack>, #include <iostream>, etc. Sin embargo, al incluir bits/stdc++.h no es necesario hacer ningún
otro include.
¿Cómo funciona? Este es un header que incluye todas las demás librerías necesarias. Esto bajo muchos enfoques puede ser negativo, pues
posiblemente aumente el tiempo de compilación y el tamaño del programa compilado. Sin embargo, en competencia, donde cada segundo es
importante, puede tener una enorme utilidad reduciendo el tiempo de escritura de código. No se debe confundir el tiempo de compilación con el
tiempo de ejecución, motivo por el cual usar bits/stdc++.h de ninguna manera provocará tiempos límites excedidos.
Otro punto a tener en cuenta es que no es un estándar del lenguaje, motivo por el cual solo funciona en el compilador GNU C++ (gcc++). La buena
noticia es que la gran mayoría de jueces de programación competitiva (de hecho, todos los jueces que conozco) usan gcc++.
17
7.2. Acortar nombre de los tipos de datos (typedef)
Crear una instancia de una estructura de datos en C++ es algo corto (de hecho, es mas corto que en Java dado que no es necesaria la inicialización).
Aún así, puede acortarse aún mas si tu lo quieres. Para esto existe el typedef, que permite llamar a los tipos de datos como tu quieras.
Si por ejemplo, tienes un map (TreeMap) que guarda parejas de enteros con vectores de enteros, la declaración sería así:
map
<
int
,vector
<int>
>nombreMap;
Sí esta declaración te parece muy larga, complicada, o simplemente crees que hace ver el código enredado, puedes crear un sobrenombre para esta
declaración, y ubicarlo justo después de using namespace std; Este typedef consiste de solo una línea así:
typedefnombreDeTipoExistente sobrenombre;
typedefmap
<
int
,vector
<int>
>vii;
Después de eso, cada vez que quieras crear un mapa de entero - vector de enteros, simplemente tienes que hacer:
vii nombreDelMapa;
Lo cual simplifica mucho la instrucción. ¿Cuando usar typedef? Esto queda a consideración de cada persona. Muchos competidores tienen una lista de
typedef impresos, y al iniciar la competencia lo escriben en un archivo de texto, copiandolo después en todos los códigos que hagan durante la
competencia. Otros lo usan únicamente cuando durante el programa van a utilizar algún tipo que se torna complejo (Ej: set<map<pair<int, string>,
<vector<int> > > >) o bien, cuando algún tipo se repite muchas ocasiones durante el codigo.
A continuación se presentan algunos typedef que son comunes entre muchos competidores:
18
typedef
long
long ll
;//Paraprogramadoresjavaacostumbradosalong,longlongpuedesermuylargo.
Podemos reducirlo a ll
typedefvector
<int>vi
;
typedefpair
<
int
,
int
>ii
;
typedef vector
<ii> vii
;//Puede usarse un sobrenombre definido en un typedef (ii) enladeclaración
de otro typedef
typedef
set
<int>si
;
typedefmap
<
string
,
int
>msi;
Existen constantes matemáticas que C++ no trae por defecto, pero que podemos definir desde un inicio para su uso en el programa. Por ejemplo,
podríamos incluir justo después de los typedef o del using namespace std las siguientes líneas:
● #defineE
2.718281828459045
● #definePI
3.141592653589793
● #defineINF
1000000000
//Infinito arbitrario
● #defineEPS
1e9
Cada vez que en el código encuentre un llamado a E, PI, INF o EPS inmediatamente lo reemplazará por el valor definido en esta sección.
19
7.4. Redefinir instrucciones (define)
#define sirve no solo para definir constantes numéricas como se vió en la sección anterior, sino para redefinir cualquier tipo de instrucciones. Por
ejemplo, una de las instrucciones mas comunes en la programación es un ciclo que vaya desde cero hasta un número dado, aumentando de uno en
uno. Es decir:
for
(
inti
=
0
;i
<a
;i
++)
Si durante tu código esta instrucción aparece muchas veces, o simplemente quieres hacerla mas sencilla de escribir, puedes crear un define de la
siguiente manera:
#definef
(
a
)
for
(
inti
=
0
;i
<a
;
i
++)
Ahora puedes escribir un for desde cero hasta 100 tan simple como:
f
(
100
){
cout
<<i
<<endl;
}
En este ejemplo, he pasado directamente un valor numérico (100), perfectamente habría podido pasar una variable previamente definida, como
f(nombreVariable) con el mismo resultado.
Si bien esto aporta gran flexibilidad y puede reducir enormemente las líneas de código, también hace que el código final sea algo mas inentendible
que código sin macros; cualquier persona que tenga conceptos en programación entenderá un for. Pero alguien que no conoce el define que creamos
previamente, no sabrá qué significa f(100), por lo tanto debe usarse con precaución. Mi recomendación es que si se van a usar estas instrucciones en
competencia, se estudien y definan con anterioridad y sean un estándar entre los integrantes del equipo en caso de competencias grupales. La idea es
que durante la competencia, los integrantes del grupo conozcan que #define’s usan, y no tengan que revisar la declaración de cada #define para
entender el código, pues el tiempo que se gana en escritura, se pierde en mayor cantidad en revisión.
20
Algunos de los #define’s mas comunes en competencias de programación son:
● #definef
(
a
)
for
(
inti =
0
;i
<a
;
i
++)
● #define
foreach
(
v
,c
) for
(
typeof
((
c
).
begin
())v
=
(
c
).
begin
(); v
!=
(
c
).
end
();
++
v)
● #define all
(
a
) a .
begin
(), a.
end
()
//Métodos como sort, requieren como parámetros un iterador
al inicio y un iterador al [Link]ámetrosconuna
simple palabra "all".
● #define add push_back //Para programadores java mas acostumbrados al add que al push_back,
bien pueden reemplazar la palabra sin problema
● #definefill
(
a
,
v
)memset(
a
,v
,
sizeofa
)
//Se verá a fondo en la siguiente sección
memset
(
nombreArreglo
,valorInicial
,tamañoArreglo
);
Sin embargo el tamaño del arreglo debe manejarse con cuidado, pues no es el número de elementos que contiene sino la cantidad de memoria que
ocupa (es decir, el número de elementos multiplicado por la memoria que ocupa cada elemento). El cálculo puede hacerse a mano, pero C++ resuelve
esto automáticamente con la función sizeof, la cual retorna el tamaño de cualquier tipo de dato. Si tenemos un arreglo de enteros llamado “a”, y
queremos inicializar todas sus casillas a cero, podemos hacer:
memset
(
a
,
0
,
sizeof
(
a
));
Y de esta manera, todo el arreglo se inicializa.
21
Con las matrices varía un poco. El sizeof de una matriz no devuelve exactamente el tamaño de una matriz. La solución en este caso es tomar el
tamaño (sizeof) de una casilla específica de la matriz, y multiplicarlo por el número de filas y de columnas. Por ejemplo:
intb
[
100
][
200
];
memset
(
b
,
0
,
sizeof
(
b
[
0
][
0
])
*
100
*
200
);
Si la matriz es de mas de dos dimensiones, se sigue el mismo principio, multiplicando por cada una de sus dimensiones.
Del mismo modo, reutilizando lo visto en la sección anterior, podemos crear #define’s que nos simplifiquen este trabajo
#definefill
(
a
)memset
(
a
,0
,
sizeofa
) //Llenar arreglos de una dimensión
C++ es un derivado de C, y en C el mecanismo estándar es scanf/printf. C++ trae su propio mecanismo por defecto para operaciones de entrada/salida,
que es cin/cout, pero aún, podemos usar cualquiera de los dos mecanismos.
Contrario a la creencia popular, cin/cout NO es mas lento que scanf/printf. Lo que ocurre es que cin/cout se sincroniza internamente con scanf/printf
para que puedas utilizar ambos mecanismos en el mismo programa, y esta sincronización es la que causa cierta lentitud. Por lo tanto la
recomendación es: NO MEZCLAR AMBOS MECANISMOS. O utilizamos únicamente scanf/printf, o utilizamos cin/cout. Si elegimos scanf/printf, no hay
ningún problema, el I/O está optimizado. Si elegimos cin/cout, debemos desactivar la sincronización añadiendo esta linea al inicio del main:
22
ios_base
::
sync_with_stdio
(
false
);
cin
.
tie
(
NULL
);
Una cosa mas a tener en cuenta: Al realizar una impresión con cout, podemos provocar un salto de línea con “endl”:
cout
<<
"
linea de texto
"
<<endl;
Sin embargo se recomienda utilizar “\n” en lugar de endl, pues endl provoca un flush inmediato innecesario. De esta forma la linea quedaria:
cout
<<
"linea de texto"
<<
"\n"
;
//o bien
cout
<<
"
linea de texto\n
";
¿Entonces, usar cin/cout o scanf/printf? En realidad esto depende de cada uno, el cin/cout correctamente optimizado es tan rapido como scanf/printf
(o incluso ligeramente mas rápido). En cualquier caso, los dos son radicalmente mas rápidos que el Scanner de java (incluso si no hemos optimizado
cin/cout). Mi recomendación es utilizar cin/cout salvo cuando la salida deba tener un formato específico (como por ejemplo doubles con un
determinado número de cifras decimales).
5
3.2 Juan
4.1 Luis
4.1 Pedro
1.2 Lucas
3.2 Mateo
23
A continuación, deben imprimirse los estudiantes agrupados por sus notas en orden ascendente (los estudiantes con la misma nota se muestran en el
orden de llegada) de la siguiente manera:
1.2:
Lucas
3.2:
Juan
Mateo
4.1:
Luis
Pedro
El procesamiento se hará de la siguiente manera: Se creará un mapa que para cada nota (double) guarda un vector (arraylist) con los estudiantes que
obtuvieron dicha nota.
Demostración
importjava
.
util
.*; #include
<
bits
/
stdc++.h>
importjava
.
lang
.*; using
namespacestd;
importjava
.
io
.*; intmain
(){
public
class
Main{ ios_base::
sync_with_stdio (false);
cin.
tie(
NULL
);
public
static
voidmain (
String
[]args){ intn
;
//Número de estudiantes
Scannersc
=
new Scanner(
System.
in
); map
<
double,vector <string> >notas;
TreeMap
<
Double ,
ArrayList <
String>
>notas
=
new cin
>>n;
TreeMap
<>(); while
(
n
>
0){
intn
=sc.
nextInt (); doublenota;
while
(
n
>
0){ stringnombre;
doublenota
=sc.
nextDouble(); cin
>>nota >>nombre;
Stringnombre
=sc.
next
(); notas[
nota].push_back (
nombre);
ArrayList
<
String >aux; }
if
(
notas.containsKey (
nota)){ for
(
map
<double,vector <string> >::
iterator it =
aux =notas .
get
(
nota); notas
.
begin();it !=notas .
end
();it ++){
}
else{ cout <<it>first <<":\n";
aux =newArrayList<
String>(); for
(
inti =0
;i <it>
second .
size
();i
++){
} cout <<it>second[
i
]<<endl;
aux
.
add(
nombre ); }
24
notas
.
put
(
nota
,aux
); }
} return
0;
for
(
Map
.
Entry
<
Double
,
ArrayList
<
String
>
>entry
: }
notas
.
entrySet
()){
System
.
out
.
println
(
entry
.
getKey
()
+
":"
);
for
(
inti
=
0
;
i<entry
.
getValue
().
size
();i
++){
System
.
out
.
println
(
entry
.
getValue
().
get
(
i
));
}
}
}
}
25