La sintaxis básica para definir
una clase es la que a continuación se muestra:
class <nombreClase>
{
<miembros>
}
De este modo se definiría una
clase de nombre <nombreClase>
cuyos miembros son los definidos en <miembros>
Los miembros de una clase son los
datos y métodos de los que van a disponer todos los objetos de la misma. Un
ejemplo de cómo declarar una clase de nombre A
que no tenga ningún miembro es la siguiente:
class A
{}
Una clase así declarada no
dispondrá de ningún miembro a excepción de los implícitamente definidos de manera
común para todos los objetos que creemos en C#. Estos miembros los veremos
dentro de poco en este mismo tema bajo el epígrafe La clase primegina: System.Object.
Aunque en C# hay muchos tipos de
miembros distintos, por ahora vamos a considerar que estos únicamente pueden
ser campos o métodos y vamos a hablar un poco acerca de ellos y de cómo se
definen:
- Campos: Un
campo es un dato común a todos
los objetos de una determinada clase. Para definir cuáles son los campos
de los que una clase dispone se usa la siguiente sintaxis dentro de la
zona señalada como <miembros>
en la definición de la misma:
<tipoCampo>
<nombreCampo>;
El nombre que
demos al campo puede ser cualquier identificador que queramos siempre y cuando
siga las reglas descritas en el Tema 4:
Aspectos Léxicos para la escritura de identificadores y no coincida con el
nombre de ningún otro miembro previamente definido en la definición de clase.
Los campos de
un objeto son a su vez objetos, y en <tipoCampo>
hemos de indicar cuál es el tipo de dato del objeto que vamos a crear. Éste
tipo puede corresponderse con cualquiera que los predefinidos en la BCL o con
cualquier otro que nosotros hallamos definido siguiendo la sintaxis arriba
mostrada. A continuación se muestra un ejemplo de definición de una clase de
nombre Persona que dispone de tres campos:
class
Persona
{
string
Nombre; // Campo de cada objeto
Persona que almacena su nombre
int
Edad; // Campo de cada objeto Persona que almacena su edad
string
NIF; // Campo de cada objeto Persona que almacena su NIF
}
Según esta
definición, todos los objetos de clase Persona incorporarán
campos que almacenarán cuál es el nombre de la persona que cada objeto
representa, cuál es su edad y cuál es su NIF. El tipo int incluido en la definición
del campo Edad es un tipo
predefinido en la BCL cuyos objetos son capaces de almacenar números enteros
con signo comprendidos entre -2.147.483.648 y 2.147.483.647 (32 bits), mientras
que string es un tipo predefinido que permite almacenar
cadenas de texto que sigan el formato de los literales de cadena visto en el Tema 4: Aspectos Léxicos
Para acceder a un
campo de un determinado objeto se usa la sintaxis:
<objeto>.<campo>
Por
ejemplo, para acceder al campo Edad de
un objeto Persona llamado p y cambiar su
valor por 20 se haría:
p.Edad
= 20;
En realidad
lo marcado como <objeto>
no tiene porqué ser necesariamente el nombre de algún objeto, sino que puede
ser cualquier expresión que produzca como resultado una referencia no nula a un
objeto (si produjese null se
lanzaría una excepción del tipo predefinido System.NullPointerException)
- Métodos:
Un método es un conjunto de
instrucciones a las que se les asocia un nombre de modo que si se desea
ejecutarlas basta referenciarlas a través de dicho nombre en vez de tener
que escribirlas. Dentro de estas instrucciones es posible acceder con
total libertad a la información almacenada en los campos pertenecientes a
la clase dentro de la que el método se ha definido, por lo que como al
principio del tema se indicó, los métodos permiten manipular los datos
almacenados en los objetos.
La
sintaxis que se usa en C# para definir los métodos es la siguiente:
<tipoDevuelto>
<nombreMétodo> (<parametros>)
{
<instrucciones>
}
Todo método
puede devolver un objeto como resultado de la ejecución de las instrucciones
que lo forman, y el tipo de dato al que pertenece este objeto es lo que se
indica en <tipoDevuelto>. Si
no devuelve nada se indica void, y si devuelve algo es obligatorio finalizar
la ejecución de sus instrucciones con alguna instrucción return <objeto>;
que indique qué objeto ha de devolverse.
Opcionalmente
todo método puede recibir en cada llamada una lista de objetos a los que podrá acceder durante la ejecución
de sus instrucciones. En <parametros> se indica es cuáles son los
tipos de dato de estos objetos y cuál es el nombre con el que harán referencia
las instrucciones del método a cada uno de ellos. Aunque los objetos que puede
recibir el método pueden ser diferentes cada vez que se solicite su ejecución,
siempre han de ser de los mismos tipos y han de seguir el orden establecido en <parametros>.
Un
ejemplo de cómo declarar un método de nombre Cumpleaños
es la siguiente modificación de
la definición de la clase Persona
usada antes como ejemplo:
class
Persona
{
string
Nombre; // Campo de cada objeto
Persona que almacena su nombre
int
Edad; // Campo de cada objeto Persona que almacena su edad
string
NIF; // Campo de cada objeto Persona que almacena su NIF
void
Cumpleaños() // Incrementa en uno de la
edad del objeto Persona
{
Edad++;
}
}
La sintaxis
usada para llamar a los métodos de un objeto es la misma que la usada para
llamar a sus campos, sólo que ahora tras el nombre del método al que se desea llamar hay que indicar entre
paréntesis cuáles son los valores que se desea dar a los parámetros del método
al hacer la llamada. O sea, se escribe:
<objeto>.<método>(<parámetros>)
Como
es lógico, si el método no tomase parámetros se dejarían vacíos los parámetros en la llamada al mismo. Por
ejemplo, para llamar al método Cumpleaños() de un objeto Persona llamado
p se haría:
p.Cumpleaños(); // El método no toma parámetros, luego no
le pasamos ninguno
Es importante
señalar que en una misma clase pueden definirse varios métodos con el mismo
nombre siempre y cuando tomen diferente número o tipo de parámetros. A esto se
le conoce como sobrecargar de métodos, y es posible ya que cuando se les llame el
compilador sabrá a cual llamar a partir de <parámetros> pasados en la llamada.
Sin embargo,
lo que no es permite es definir varios métodos que sólo se diferencien en su
valor de retorno, ya que como éste no se tiene porqué indicar al llamarlos no
podría diferenciarse a que método en concreto se hace referencia en cada
llamada. Por ejemplo, a partir de la llamada:
p.Cumpleaños();
Si además de
la versión de Cumpleaños()
que no retorna nada hubiese otra que retornase un int, ¿cómo sabría
entonces el compilador a cuál llamar?
Antes de continuar es preciso
señalar que en C# todo, incluido los literales, son objetos del tipo de cada
literal y por tanto pueden contar con miembros a los que se accedería tal y
como se ha explicado. Para entender esto no hay nada mejor que un ejemplo:
string s = 12.ToString();
Este código almacena el literal
de cadena “12” en la variable s, pues 12 es
un objeto de tipo int (tipo que representa enteros) y cuenta cuenta
con el método común a todos los ints llamado ToString() que lo que hace es devolver una cadena
cuyos caracteres son los dígitos que forman el entero representado por el int sobre
el que se aplica; y como la variable s es de
tipo string
(tipo que representa cadenas) es perfectamente posible almacenar
dicha cadena en ella, que es lo que se hace en el código anterior.