Herramientas de usuario

Herramientas del sitio


cvsjava

Comparación entre C y Java

Generalidades

  • Los archivos en C llevan las extensión '.c' mientras que en Java '.java'.
  • El compilador de C (comandos gcc o cc) produce archivos con instrucciones de máquina en binario que son directamente ejecutables por la máquina.
  • El compilador de Java (comando javac) produce archivos '.class' que corresponden a instrucciones de una máquina virtual (la JVM: java virtual machine). Para ejecutarlos se invoca el comando java que incluye un compilador JIT (just in time) que traduce en memoria las instrucciones virtuales a las intrucciones de la plataforma usada. Al no generar un archivo con la traducción, la compilación JIT ocurre cada vez que se ejecuta el programa.

Programación orientada a objetos

  • C no es un lenguaje orientado a objetos: no posee clases. Java sí es orientado a objetos (aunque no es puro como Smalltalk).
  • En C se definen los tipos compuestos mediante la declaración struct. Ahí no se pueden incluir métodos.
  • La abstracción que describe como se procesan los datos es la función mientras que en Java es la clase.
  • En Java un archivo contiene 1 clase (opcionalmente más de 1). No hay funciones aisladas. Mientras que en C un archivo es una secuencia de declaraciones que pueden ser tipos de datos (structs), variables globales o funciones.
  • En un archivo en C, las abstracciones declaradas se conocen a partir del punto en donde se declaran. No se pueden referenciar antes. Típicamente se declaran prototipos de las abstracciones (especialmente las funciones) en archivos de encabezado (extensión .h).
  • En Java las abstracciones declaradas en un archivo o clase son globalmente conocidas. No se requiere declarar prototipos ni archivos de encabezados.

Tipos primitivos

  • Usan casi los mismos tipos primitivos: char, short, int, long, float, double.
  • Pero en C, char es en realidad un entero de 1 byte, mientras que en Java ocupa 2 bytes y solo almacena caracteres.
  • En C se puede agregar el atributo unsigned a los enteros.

Strings

  • C no posee el tipo String. Los strings se representan mediante punteros a arreglos de caracteres terminados con el caracter '\0'.
  • En Java existe la clase predefinida String con métodos bien definidos.

Punteros

La siguiente tabla compara la sintaxis usada para las distintas operaciones con punteros:

Java C
declaración
Node p;
Node *p;
asignación de memoria
p= new Node();
p= malloc(sizeof Node);
acceso
Node q= p.next;
Node *q= p->next;
destrucción
free(p);
puntero nulo
p= null;
p= NULL;
  • En C se usa el '*' para trabajar con punteros. En Java no.
  • En C la memoria se pide con malloc, mientras que en Java con new.
  • malloc necesita el tamaño de la memoria a asignar. Esta se obtiene con sizeof.
  • En C se accede a los campos de la esctructura con el operador '->', mientras que en Java se hace con '.'.
  • En Java nunca se libera el área de memoria ocupada por p. Hay un recolector de basuras que recicla esa memoria cuando deja ser referenciada.
  • C no posee recolector de basuras. Se necesita liberar explícitamente la memoria con free.
  • Un error frecuente en C es el puntero loco o colgante (dangling reference). Ocurre cuando equivocadamente se libera la memoria, pero todavía está siendo referenciada.
  • Otro error frecuente en C es la gotera de memoria: cuando un trozo de memoria pedido con malloc nunca se libera.
  • En Java el recolector de basura evita por completo los punteros locos. Usualmente se piensa que también evita las goteras, pero esto no es cierto. Las reduce pero no las evita. Un típico caso es poblar una tabla de hash con objetos que nunca serán consultados. Esos objetos sí son goteras.
  • En C el puntero nulo es NULL en mayúscula. En Java es null en minúsculas.

Variables locales de tipo compuesto

  • En C se pueden declarar variables de tipo compuesto (structs) que son locales a una función. Por ejemplo:
    Node node;
  • No se debe llamar a malloc. El espacio de memoria requerido se asigna automáticamente al ingresar a la función.
  • Se accede a los campos con el operador '.':
    node.next

    No se usa '→'.

  • La variable se destruye automáticamente al retorno de la función.
  • Se puede asignar la dirección de la variable a un puntero:
    Node *p= &node;
  • Un error típico es acceder al contenido de p después del retorno de la función en donde se declaró node.
  • Como Java es un lenguaje robusto, para evitar este error simplemente en Java no se pueden declarar variables de tipos compuestos locales a una función. Por eso en Java se eliminó el uso de '*' pues todas las variables de tipo compuesto son punteros.

Instrucciones

Java heredó las todas las instrucciones de control de C y por lo tanto las siguientes construcciones sintácticas funcionan igual que en Java:

  • flujo condicional: if (cond) inst_1 else inst_2
  • ciclo normal: while (cond) inst
  • variante de ciclo: do { inst … } while (cond);
  • ciclo for: for ( ini ; cond; incr) inst
  • agrupar instrucciones: { declinst … }
  • switch (exp) { const : instbreak; … default: inst … }

Pero se debe tener cuidado si el compilador implementa una versión de C anterior al estándar C99, como por ejemplo ansi-C.

Antes de C99 el ciclo for no admite declarar la variable de control en la misma expresión de inicialización. Por ejemplo el siguiente código es ilegal:

  for (int i=0; i<10; i++)
    ...

La forma correcta es:

  int i;
  ...
  for (i=0; i<10; i++)
    ...

Además las declaraciones de variables antes de C99 siempre debían ir al comienzo de un bloque { … }. Por ejemplo era ilegal:

  int i;        /* declaración */
  i= getchar(); /* no es una declaración */
  int j;        /* error de sintaxis, porque no viene después de una declaración */
  j= ...;

Pero este código sí es correcto:

  int i= getchar(); /* sí es declaración */
  int j;            /* otra declaración */
  j= ...;

A partir de C99 sí se admiten declaraciones en cualquier punto y declaraciones en el ciclo for.

Funciones

La sintaxis para declarar un función es la misma de los métodos de Java, solo que una función no está contenida en la declaración de una clase. Por ejemplo, la función que calcula el factorial recursivamente es:

  /* Archivo fact.c */
  double fact(int n) {
    if (n<=1)
      return 1;
    else
      return n*fact(n-1);
  }

Para usar esta función en otro archivo se debe declarar un prototipo o encabezado de la función previamente:

  /* Archivo main.c */
  double fact(int n);  /* prototipo o encabezado de la función fact */
  
  int main(int argc, char **argv) {
    double res= fact(atoi(argv[1]));  /* invoca la función fact */
    printf("%e\n", res); /* despliega el resultado en la salida esstándar */
    return 0;
  }
  • una función solo es conocida a partir del momento en donde se declara la función o se declara su encabezado.
  • no existen los atributos de visibilidad como private, public o protected.
  • normalmente las funciones son conocidas en todos los archivos, a condición que se declare previamente un prototipo de la función.
  • si se antepone el atributo static, la función es solo conocida en el archivo en donde está declarada.

Archivos de encabezado

Declarar los encabezados de la funciones en cada archivo en donde se usa es peligroso porque si uno se equivoca en el tipo de los parámetros o el valor de retorno, el compilador no avisa y el resultado es imprevisible. La solución es crear archivos que contengan solo los encabezados de un grupo de funciones. Estos archivos de encabezado tienen la extensión “.h” y normalmente se incluyen al inicio de los archivos en donde se usan la funciones incluidas.

Para el ejemplo anterior, el archivo de encabezado sería:

  /* archivo fact.h */
  double fact(int n);

Y el archivo en donde se usa:

  /* Archivo main.c */
  #include "fact.h"
  
  /* no declarar un encabezado para fact */
  
  int main(int argc, char **argv) {
    double res= fact(atoi(argv[1]));  /* invoca la función fact */
    printf("%e\n", res); /* despliega el resultado en la salida esstándar */
    return 0;
  }

Previo a la verdadera compilación de un archivo, el compilador invoca el preprocesador de C (llamado cpp) que se encarga de expandir literalmente los #include reemplazándolos por el contenido del archivo con los encabezados.

De esta forma, uno se debe preocupar solamente de la consistencia de los parámetros de la función declarada con los parámetros que aparecen en el archivo de encabezados “.h”. Esto reduce considerablemente la probabilidad de error.

cvsjava.txt · Última modificación: 2020/03/08 20:47 por lmateu