Boletín Pascal #7
INDICE
1. UNAS PALABRAS DEL EDITOR
2. PROGRAMACION DE BASES DE DATOS EN CODIGO
- CREATE STRUCTURE
- USE
- APPEND (y REPLACE)
- GO TOP, GO BOTTOM, SKIP, SKIP n y EOF
- REPLACE
- DELETE
- INSERT
- LOCATE
- LOCATE...CONTINUE y SET FILTER
- SET ORDER TO
- SEEK y (SOFTSEEK o SET NEAR)
- SET FILTER (RANGOS)
3. EL NOVATO Y EL EXPERTO
- Indices y punteros
4. ENLACES
________________________________________________________________________
1. UNAS PALABRAS DEL EDITOR
Quisiéramos aprovechar para agradecer a todos nuestros suscriptores que
respondieron el cuestionario que enviáramos un par de semanas atrás, muy
especialmente a quienes hicieron comentarios, puesto que TODOS han sido
muy útiles. Les pedimos perdón por no responder a todos pero esperamos
que comprendan que recibimos una avalancha de emails.
El resultado es que definitivamente el formato del boletín seguirá
siendo más o menos como es ahora: quincenal, 25K y texto simple. Un poco
más conciso quizás. Acerca de los attachments (archivos adjuntos), en el
futuro se los enviaremos únicamente a quienes elijan recibirlos (son la
gran mayoría, pero parte de la minoría claramente objetó esta idea, así
que sentimos que no hay suficiente consenso para enviarles attachments a
todos). El nivel del newsletter se mantendrá más o menos igual en
promedio, pero algunas veces trataremos de mezclar un poco de temas más
y menos avanzados, o trataremos de mezclar explicaciones "avanzadas" con
explicaciones "triviales". No va a ser tarea fácil...
La escritura calificó entre buena y muy buena, lo que nos conforma
bastante, pero trataremos de mejorar en lo que podamos, aunque los
regionalismos serán difíciles de evitar.
Este boletín no es algo que hacemos para nosotros mismos, sino para
ustedes, así que por favor no esperen al próximo cuestionario dentro de
tres meses para realizar comentarios, críticas, sugerencias, proponer
temas, etc.
Saludos,
Ernesto De Spirito
eds2008 @ latiumsoftware.com
________________________________________________________________________
JfControls Lib. Multilenguaje. Multiapariencia. Skins. Privilegios. Más
de 40 componentes integrados y personalizables. Múltiples problemas de
programación resueltos. Administración centralizada de recursos. Para
Delphi 3-2006 y C++ Builder 3-6. http://www.jfactivesoft.com/spindex.htm
________________________________________________________________________
2. PROGRAMACION DE BASES DE DATOS EN CODIGO
La mayoría de quienes han trabajado en un lenguaje xBase (como dBase,
Fox o Clipper) parecen extrañar la posibilidad de hacer todo con código
sin tener que meterse con componentes en un formulario y un Inspector de
objetos... Bueno, las buenas noticias para todos es que Delphi permite
que todo lo que se puede hacer visualmente también se pueda hacer por
código.
Aquí les proveeremos de algunos ejemplos de programación de bases de
datos usando la BDE y en el futuro trataremos también ADO (la mayoría de
las cosas son iguales). Para probar los ejemplos creen una nueva
aplicación y en la unidad del formulario agreguen DB y DBTables en la
cláusula USES.
CREATE STRUCTURE
================
Para crear una tabla podemos usar un código como el siguiente:
procedure TForm1.Button1Click(Sender: TObject);
var
Productos: TTable;
begin
Productos := TTable.Create(nil);
try
with Productos do begin
// Definición de la tabla
DatabaseName := ExtractFilePath(Application.ExeName);
TableType := ttDBase;
TableName := 'Productos.DBF';
with FieldDefs do begin // Definición de los campos
with AddFieldDef do begin
Name := 'IDProducto';
DataType := ftSmallint;
end;
with AddFieldDef do begin
Name := 'Descripcion';
DataType := ftString;
Size := 25;
end;
with AddFieldDef do begin
Name := 'Precio';
DataType := ftFloat;
end;
end;
with IndexDefs do begin // Definición de los índices
with AddIndexDef do begin
Name := 'PorID';
Fields := 'IDProducto';
end;
with AddIndexDef do begin
Name := 'PorDescr';
Fields := 'Descripcion';
end;
end;
CreateTable; // Crea la tabla
end;
except
Productos.Free;
raise;
end;
Productos.Free;
end;
Primero declaramos el componente TTable y lo creamos sin dueño (pará-
metro nil), significando esto que seremos responsables de liberar el
objeto. Si en su lugar hubiéramos escrito
Productos := TTable.Create(Self);
entonces el componente pertenecería a Form1 y el formulario lo liberará
cuando él sea liberado (por ejemplo cuando la aplicación termine).
Puesto que decidimos liberar "manualmente" el objeto, usamos un bloque
TRY para asegurar que llegaremos a hacerlo incluso si ocurre una
excepción.
Luego realizamos todo el proceso de crear una tabla dentro de un bloque
WITH para ahorrarnos escritura... Sin el bloque WITH tendríamos que
escribir:
Productos.DatabaseName := ExtractFilePath(Application.ExeName);
Productos.TableType := ttDBase;
Productos.TableName := 'Productos.DBF';
FieldDef := Productos.FieldDefs.AddFieldDef;
FieldDef.Name := 'IDProducto';
...
La ventaja de no usar un bloque WITH es que se pueden usar las caracte-
rísticas de autocompletar del editor de código y también a veces el
código es más claro. Usando WITH se escribe menos y su código es más
eficiente cuando la expresión WITH resuelve alguna referencia (por
ejemplo "with a.b do" o "with a[i] do", o en algunos WITHs anidados)
porque la resolución se realiza una sola vez y el resultado se mantiene
en un registro de la UCP (EBX más precisamente) para acceso rápido.
Ahora volvamos a las propiedades de la tabla...
* DatabaseName es el nombre de la base de datos a la que la tabla
pertenece (o pertenecerá). El valor de esta propiedad puede ser un
directorio (por ejemplo usamos el directorio donde se encuentra la
aplicación) o un alias BDE (por ejemplo DBDEMOS).
* TableType define el tipo de la tabla. Normalmente sólo lo establecemos
al crear una tabla. En el ejemplo elegimos una tabla dBase.
* TableName es el nombre de la tabla. Por ejemplo para tablas dBase y
Paradox éste es el nombre del archivo de la tabla (el directorio es
determinado por DatabaseName).
* FieldDefs es una lista de objetos TFieldDef, cada uno de los cuales
representa la definición de un campo. El método AddFieldDef agrega una
definición de campo a la lista y devuelve una referencia a él, la que
usamos en un bloque WITH para establecer las propiedades de este campo
recientemente agregado (por ejemplo: Name, DataType, Size, Precision,
Required, etc.).
* IndexDefs es una lista de objetos TIndexDef, cada uno de los cuales
representa la definición de un índice. El método AddIndexDef agrega
una definición de índice a la lista y devuelve una referencia a él, la
que usamos en un bloque WITH para establecer las propiedades de este
índice recientemente agregado (por ejemplo: Name, Fields, etc.). Si
quiere un índice por más de un campo, todo lo que tiene que hacer es
separar los campos por punto y coma (";"):
Fields := 'Campo1;Campo2;Campo3';
Una vez que hemos establecido todas estas propiedades (siendo IndexDefs
opcional) procedemos a crear la tabla invocando el método CreateTable.
Después de esto deberíamos tener un archivo llamado Productos.DBF en el
directorio de la aplicación.
USE
===
Para abrir y cerrar una tabla podemos usar la propiedad Active o los
métodos Open y Close:
procedure TForm1.Button2Click(Sender: TObject);
var
Productos: TTable;
begin
Productos := TTable.Create(nil);
try
with Productos do begin
DatabaseName := ExtractFilePath(Application.ExeName);
TableName := 'Productos.DBF';
Open; // Active := True;
// Trabajar con la tabla aquí
Close; // Active := False;
end;
except
Productos.Free;
raise;
end;
Productos.Free;
end;
APPEND (y REPLACE)
==================
Para agregar nuevos registros al final de la tabla podemos agregar
algunas líneas entre las llamadas a los métodos Open y Close de arriba:
// APPEND BLANK
// REPLACE IDProducto WITH 137, Descripcion WITH 'Gaseosas', ;
// Precio WITH 0.35
Append;
FieldByName('IDProducto').AsInteger := 137;
FieldByName('Descripcion').AsString := 'Gaseosas';
FieldByName('Precio').AsFloat := 0.35;
Post;
Ahora agreguemos algunos registros más para usarlos después en los
ejemplos:
Append;
FindField('IDProducto').AsInteger := 243;
FindField('Descripcion').AsString := 'Cigarros';
FindField('Precio').AsFloat := 1.15;
Post;
Append;
Productos['IDProducto'] := 395;
Productos['Descripcion'] := 'Galletas';
Productos['Precio'] := 1.35;
Post;
Append;
FieldValues['IDProducto'] := 45;
FieldValues['Descripcion'] := 'Caramelos';
FieldValues['Precio'] := 0.05;
Post;
Append agrega un registro en blanco (no exactamente en blanco porque los
campos pueden tener valores predeterminados) pero la operación no se
completa hasta que llamamos al método Post para guardar los cambios.
Como se puede ver en los ejemplos, hay muchas formas de referenciar
campos y sus valores:
* FieldByName y FindField ambos devuelven una referencia a un objeto
TField. La diferencia es que si el nombre del campo pasado como
parámetro no se encuentra, FieldByName genera un excepción mientras
que FindField devuelve nil.
* FieldValues es la propiedad predeterminada de la clase TTable (esto
significa que por ejemplo "Tabla1.FieldValues['Campo1']" es lo mismo
que sólo "Tabla1['Campo1']") y representa un arreglo de variants con
los valores de los campos.
GO TOP, GO BOTTOM, SKIP, SKIP n y EOF
=====================================
Ahora que tenemos cuatro productos
IDProducto Descripcion Precio
137 Gaseosas 0.35
243 Cigarros 1.15
395 Galletas 1.35
45 Caramelos 0.05
podemos desplazarnos por ellos:
First; // Va al primer registro (GO TOP)
ShowMessage(FieldByName('Descripcion').AsString); // Gaseosas
Last; // Va al último registro (GO BOTTOM)
ShowMessage(FieldValues['Descripcion']); // Caramelos
Next; // Va al siguiente registro (SKIP)
if Eof then ShowMessage('EOF');
ShowMessage(Productos['Descripcion']); // Caramelos
Prior; // Va al registro anterior (SKIP -1)
ShowMessage(FieldValues['Descripcion']); // Galletas
MoveBy(-2); // Va dos registros hacia arriba (SKIP -2)
ShowMessage(FieldByName('Descripcion').AsString); // Gaseosas
En los dialectos xBase, cuando uno está en fin de archivo (EOF), está
posicionado en una especie de registro en blanco que sigue al último
registro de la tabla, y los campos valen cero, falso, espacios o la
fecha nula, dependiendo de los tipos de los campos. Una vez alcanzado el
fin de archivo se puede volver al último registro con SKIP -1.
En Visual Basic, cuando uno está en fin de archivo no hay registro
activo (cualquier intento de acceder a los valores de los campos genera
un error), pero puede volver al último registro con MovePrevious.
En Delphi, EOF es una señal indicando que se intentó un desplazamiento
más allá del fin de archivo, pero aún está posicionado en el último
registro (a menos que la tabla esté vacía, y entonces no habrá registro
activo).
REPLACE
=======
Para reemplazar los valores de uno o más campos de un registro usamos
Edit para preparar el registro para cambios, luego actualizamos los
campos y finalmente guardamos los cambios con Post:
// REPLACE Descripcion WITH 'Gaseosas Diet', Precio WITH 0.40
Edit;
FieldByName('Descripcion').AsString := 'Gaseosas Diet';
FieldByName('Precio').AsFloat := 0.40;
Post;
DELETE
======
ShowMessage(FieldByName('Descripcion').AsString); // Gaseosas Diet
Delete;
ShowMessage(FieldByName('Descripcion').AsString); // Cigarros
En los lenguajes xBase, justo después de borrar un registro, éste aún es
el registro activo y puede ser recuperado (con RECALL). Puede moverse al
siguiente registro con SKIP.
En Visual Basic, después de llamar al método Delete de un Recordset no
hay registro activo (cualquier intento de acceder a los valores de los
campos genera un error), pero puede pasar al siguiente registro con
MoveNext. Los registros borrados no son recuperables (al menos no lo
son usando los Recordsets de VB).
En Delphi, el método Delete borra el registro activo y el siguiente
registro se hace activo. Los registros borrados no son recuperables (al
menos no lo son usando los Datasets de Delphi).
INSERT
======
Last;
Insert; // Puede agregar antes del último registro o al final
FieldByName('IDProducto').AsInteger := 137;
FieldByName('Descripcion').AsString := 'Gaseosas';
FieldByName('Precio').AsFloat := 0.35;
Post;
Insert es casi como Append. Dependiendo del tipo de base de datos el
nuevo registro puede ser agregado al final de la tabla o ser físicamente
insertado antes del registro actual. Para tablas DBFs, Insert es lo
mismo que Append.
LOCATE
======
Locate busca el primer registro con los valores de campo que indique.
Por ejemplo:
// LOCATE FOR Descripcion = 'Caramelos'
Locate('Descripcion', 'Caramelos', [loCaseInsensitive]);
ShowMessage(FieldByName('Descripcion').AsString); // Caramelos
// LOCATE FOR IDProducto = 243 .AND. Descripcion = 'Cigarros'
Locate('IDProducto;Descripcion', VarArrayOf([243,'Cigarros']),
[loCaseInsensitive]);
ShowMessage(FieldByName('Descripcion').AsString); // Cigarros
Puede buscar por claves parciales (sólo afecta a los campos de texto):
Locate('Descripcion', 'GALL', [loCaseInsensitive, loPartialKey]);
ShowMessage(FieldByName('Descripcion').AsString); // Galletas
Y también puede realizar búsquedas sensibles a mayúsculas/minúsculas:
if not Locate('Descripcion', 'CARAMELOS', []) then
ShowMessage('CARAMELOS no encontrado');
ShowMessage(FieldByName('Descripcion').AsString); // Cigarros
Locate devuelve True si encontró un registro y False en caso contrario.
Después de una búsqueda sin éxito, en los lenguajes xBase el registro
activo es EOF, en Visual Basic no hay registro activo, y en Delphi el
registro activo no cambia.
LOCATE...CONTINUE y SET FILTER
==============================
Si necesitáramos encontrar el siguiente o los siguientes registros que
verifican el criterio, en xBase podemos hacer lo siguiente:
LOCATE FOR <condición>
WHILE .NOT. EOF()
* Procesar los registros aquí
CONTINUE
ENDDO
Esto también puede lograrse así:
SET FILTER TO <condición>
GO TOP
WHILE .NOT. EOF()
* Procesar los registros aquí
SKIP
ENDDO
SET FILTER TO
En los Datasets de Delphi no tenemos un equivalente para CONTINUE, así
que tenemos que usar filtros. Hay dos maneras de establecer un filtro:
1) Usando la propiedad Filter (y la propiedad FilterOptions)
FilterOptions := <opciones>;
Filter := <condición>; // Establece el filtro
if FindFirst then
repeat
// Procesar los registros aquí
until not FindNext;
Filter := ''; // Remueve el filtro
2) Usando un evento OnFilterRecord
OnFilterRecord := <manejador de evento>; // Establece el filtro
if FindFirst then
repeat
// Procesar los registros aquí
until not FindNext;
OnFilterRecord := nil; // Remueve el filtro
Veremos ambas maneras en el siguiente ejemplo:
type
TForm1 = class(TForm)
...
private
{ Private declarations }
procedure Filtro(DataSet: TDataSet; var Accept: Boolean);
public
{ Public declarations }
end;
...
procedure TForm1.Filtro(DataSet: TDataSet; var Accept: Boolean);
begin
Accept := Pos('K', DataSet['Company']) <> 0;
end;
procedure TForm1.Button3Click(Sender: TObject);
var
Clientes: TTable;
begin
Clientes := TTable.Create(nil);
try
with Clientes do begin
DatabaseName := 'DBDEMOS';
TableName := 'Customer.DB';
Active := True;
FilterOptions := [foCaseInsensitive];
Filter := 'Country = ' + QuotedStr(InputBox(
'Filtro', 'Ingrese el país a filtrar', 'bAhAmAs'));
if FindFirst then
repeat
ShowMessage(FieldValues['Company'] + ' ('
+ FieldValues['Country'] + ')');
until not FindNext;
Filter := ''; // Remueve el filtro
ShowMessage('Ahora con el evento OnFilterRecord');
OnFilterRecord := Filtro;
if FindFirst then
repeat
ShowMessage(FieldValues['Company']);
until not FindNext;
OnFilterRecord := nil; // Remueve el filtro
Active := False;
end;
except
Clientes.Free;
raise;
end;
Clientes.Free;
end;
FindFirst, FindNext, FindPrior y FindLast son como First, Next, Prior y
Last, pero sólo se mueven por los registros que cumplen la condición del
filtro y devuelven un valor (True si se encontró un registro y False en
caso contrario).
Filtrando vía la propiedad Filter es más simple porque se puede hacer
in-situ y es relativamente más poderoso en el sentido que se le puede
solicitar a usuarios avanzados toda la condición:
Clientes.Filter := InputBox('Filtro', 'Ingrese la condición',
'Country = ''US'' OR Country = ''Bahamas''');
Usando el evento OnFilterRecord implica algunas líneas más de código,
pero puede resultar más rápido puesto que la BDE no tiene que inter-
pretar la condición de filtro, y es relativamente más poderoso en el
sentido que uno no está limitado a una "simple" condición de filtro y en
el manejador de evento (el procedimiento que referencia OnFilterRecord)
uno puede construir su lógica propia para determinar si el registro
debería ser filtrado o no (puede llamar a funciones, usar IFs, buscar
registros en otras tablas, etc.). Finalmente hay que establecer el
parámetro Accept en True (el registro cumple con el criterio) o False
(el registro debe ser excluido).
SET ORDER TO
============
Para determinar el índice activo todo lo que tenemos que hacer es
asignar a la propiedad IndexName el nombre del índice. Por ejemplo:
procedure TForm1.Button4Click(Sender: TObject);
var
Productos: TTable;
begin
Productos := TTable.Create(nil);
try
with Productos do begin
DatabaseName := ExtractFilePath(Application.ExeName);
TableName := 'Productos.DBF';
Open; // Active := True;
// Ningún índice marcado como primario
// ==> Orden natural es el predeterminado
ShowMessage(FieldByName('Descripcion').AsString); // Cigarros
IndexName := 'PorID'; // SET ORDER TO PorID
First;
ShowMessage(FieldValues['Descripcion']); // Caramelos
IndexName := 'PorDescr'; // SET ORDER TO PorDescr
First;
ShowMessage(FieldValues['Descripcion']); // Galletas
IndexName := ''; // SET ORDER TO
First;
ShowMessage(FieldValues['Descripcion']); // Cigarros
Close; // Active := False;
end;
except
Productos.Free;
raise;
end;
Productos.Free;
end;
SEEK y (SOFTSEEK o SET NEAR)
============================
Hay una forma corta y una larga de realizar un "seek" (búsqueda directa
por un índice). Esta es la forma larga:
// SEEK Valor1+Valor2
// IF FOUND()
// ...
SetKey; // Limpia la clave
FieldValues['Campo1'] := Valor1;
FieldValues['Campo2'] := Valor2;
if GotoKey then ...
// SEEK Valor1+Valor3
// IF FOUND()
// ...
EditKey; // Edita la clave
FieldValues['Campo2'] := Valor3;
if GotoKey then ...
Y estas son las versiones cortas de los dos ejemplos anteriores:
if FindKey([Valor1, Valor2]) then ...
if FindKey([Valor1, Valor3]) then ...
La forma larga tiene mejor rendimiento, pero la diferencia es sólo
marginal, por lo que preferimos la forma corta por ser más simple.
GotoKey y FindKey devuelven True si encuentran una coincidencia exacta
y False en caso contrario. Puede buscar por claves parciales con los
procedimientos GotoNearest y FindNearest, que son los equivalentes a
a SOFTSEEK en Clipper o usar SET NEAR ON con SEEK en FoxPro.
Aquí hay algunos ejemplos:
IndexName := 'PorID';
SetKey;
FieldByName('IDProducto').AsInteger := 137;
if GotoKey then
ShowMessage(FieldValues['Descripcion']); // Gaseosas
if FindKey([45]) then
ShowMessage(FieldValues['Descripcion']); // Caramelos
IndexName := 'PorDescr';
SetKey;
FieldByName('Descripcion').AsString := 'Cigarros';
if GotoKey then
ShowMessage(FieldValues['Descripcion']); // Cigarros
SetKey;
FieldByName('Descripcion').AsString := 'Caramelos';
if GotoKey then
ShowMessage(FieldValues['Descripcion'])
else
ShowMessage('Caramelos no encontrado');
SetKey;
FieldByName('Descripcion').AsString := 'Ga';
GotoNearest;
if Copy(FieldValues['Descripcion'],1,2) = 'Ga' then
ShowMessage(FieldValues['Descripcion']); // Galletas
FindNearest(['Car']);
if Copy(FieldValues['Descripcion'],1,3) = 'Car' then
ShowMessage(FieldValues['Descripcion']); // Caramelos
SET FILTER (RANGOS)
===================
Existe una tercera forma de hacer un filtro: estableciendo un rango de
claves en el índice activo. Por ejemplo:
// SET ORDER TO PorID
// SET FILTER TO IDProducto >= 100 .AND. IDProducto <= 300
// GO BOTTOM
// ? Descripcion
// SEEK 45
// IF FOUND()
// ? Descripcion
// ELSE
// ? 'Caramelos no encontrado'
// ENDIF
// SET FILTER TO
IndexName := 'PorID';
SetRange([100],[300]);
ApplyRange;
Last;
ShowMessage(FieldValues['Descripcion']); // Galletas
if FindKey([45]) then
ShowMessage(FieldValues['Descripcion'])
else
ShowMessage('Caramelos no encontrado');
CancelRange;
Esto es todo por ahora. En el próximo número seguiremos explorando las
formas de hacer en Delphi las cosas que se pueden hacer en los lenguajes
xBase. El código fuente completo de los ejemplos de este boletín está
disponible en esta dirección:
http://www.latiumsoftware.com/es/file.php?id=p07
A propósito, los ejemplos del último boletín estuvieron disponibles un
poco tarde. Pedimos disculpas por los inconvenientes.
________________________________________________________________________
3. EL NOVATO Y EL EXPERTO
- Indices y punteros
Aquí presentamos un ejemplo de una función que devuelve la posición de
la primera ocurrencia de un caracter dentro de una cadena.
NOVATO:
function Scan(s: string; char c): integer;
var
i: integer:
begin
Result := 0;
for i := 1 to Length(s) do begin
if s[i] = c then begin
Result := i;
break;
end;
end;
end;
EXPERTO:
function Scan(const s: string; char c): integer;
var
i: integer:
p: pchar;
begin
Result := 0;
p := PChar(s);
for i := 1 to Length(s) do begin
if p^ = c then begin
Result := i;
break;
end;
inc(p);
end;
end;
EXPLICACION:
PChar significa "puntero a caracter". La sentencia "p := PChar(s);" hace
que p apunte al primer caracter en la cadena s, así que decir s[1] es lo
mismo que decir p^ (que significa "el caracter apuntado por p").
La sentencia "inc(p);" incrementa el puntero (o sea, le suma uno),
haciendo que apunte al siguiente caracter, de modo que la primera vez
que se ejecute p apuntará a s[2] (entonces p^ = s[2]). La siguiente vez
que p se incremente, apuntará a s[3] (así que p^ = s[3]), y así sucesi-
vamente, de modo que tenemos el mismo efecto que si usáramos índices. La
diferencia es que con punteros la función es más veloz.
Otro cambio que hemos hecho fue agregar "const" en la declaración del
parámetro de cadena (s). Esto le dice al compilador que la cadena debe
ser pasada por referencia (es más rápido), pero que no intentaremos
modificarla.
ARTIMETICA DE PUNTEROS:
function Scan(const s: string; char c): integer;
var
p, q: pchar;
begin
p := PChar(s);
q := StrScan(p, c);
if q := nil then
Result := 0
else
Result := q - p + 1;
end;
Delphi provee una función llamada StrScan que toma un pchar en vez de
una cadena como parámetro y devuelve un puntero a la primera ocurrencia
del caracter, en vez de su posición, así que usamos aritmética de
punteros para obtener esta posición.
Por ejemplo, supongamos que llamamos "Scan('OOOXOOO', 'X');"
1) Después de "p := PChar(s);", p es la dirección de memoria del primer
caractrer de la cadena s. Una dirección de memoria es un número (como
un índice en el arreglo de la memoria), por ejemplo $1000.
2) Después de "q := StrScan(p, c);" q apunta a la primera ocurrencia del
caracter c ('X') en la cadena, así que en el ejemplo sería $1003.
3) Para obtener la posición usamos la fórmula "q - p + 1". En nuestro
ejemplo sería : $1003 - $1000 + 1 = 3 + 1 = 4 (el caracter
encontrado es el cuarto de la cadena).
+---+---+---+---+---+-
| O | O | O | X | O |
+---+---+---+---+---+-
1 2 3 4 5
| | | | |
p = $1000 ----+ | | | |
$1001 --------+ | | |
$1002 ------------+ | |
q = $1003 ----------------+ |
$1004 --------------------+
________________________________________________________________________
4. ENLACES
EN INGLES
=========
* Aplicaciones Delphi con código fuente
http://sunsite.informatik.rwth-aachen.de/delphi/
* Enlaces Delphi en el Proyecto del Directorio Abierto (Inglés)
http://dmoz.org/Computers/Programming/Languages/Delphi/
* Delphi Links at Cetus-Links.org
http://www.cetus-links.org/oo_delphi.html
* Oberon Microsystems - Aquí encontrarán BlackBox, una herramienta de
desarrollo de software basada en componentes que usa Oberon, un
sucesor del lenguaje Pascal inventado por el mismísimo Prof. Niklaus
Wirth. Hay una versión educativa disponible para descargar (~6 Mb).
Héchele un vistazo y después cuéntenos que le pareció.
http://www.oberon.ch
EN ESPAÑOL
==========
+======================================================================+
| PSP S.A. |
| Este Boletín Pascal hoy no existiría de no ser por la inspiración |
| que he obtenido del Dr. Marcelo Perazolo, a través de su boletín |
| "Novedades Empresarias y Profesionales" que LLEVA YA 50 EJEMPLARES |
| Y CON SUS 110.000 SUSCRIPTORES ES EL BOLETIN DE MAYOR TIRADA EN EL |
| MUNDO. El Dr. Perazolo ha tenido la gentileza de mencionarnos en su |
| newsletter, así que cumplimos mencionándolo en el nuestro, y apro- |
| vechamos para felicitarlo desde aquí por su merecido logro. Quienes |
| estén interesados en recibir esta publicación (se las recomiendo) |
| deben solicitarla escribiendo a <empresa@psp-sa.com> |
| http://www.psp-sa.com |
+======================================================================+
* Numerosas aplicaciones con código fuente y comentarios en español
http://www.galeon.com/roberdi/PagDelphi-Download.htm
* Enlaces Delphi en el Proyecto del Directorio Abierto (Español)
Podrán ver que soy el editor de esta categoría. Si saben de más sitios
que deberían estar aquí por favor sugiéranlos. No pueden ser sitios en
construcción (a menos que ya tengan bastante material), sitios con una
sola página (que explican qué es Delphi, donde conseguirlo y luego los
mandan con enlaces a sitios en inglés), sitios que básicamente constan
de enlaces y no tienen material propio, sitios que el poco material
que tienen ya está en sitios más grandes, cursos y tutoriales (hay
otra categoría para eso), ni empresas que venden Delphi o servicios
(de nuevo hay otra categoría para eso). Verán que sí hay sitios de una
sola página, porque sí aportan algo a la comunidad Delphi. No obstante
ello, les agradeceré que me envíen todos los links que encuentren para
que yo evalúe si cumplen con las reglas del Directorio Abierto o no (y
también pueden opinar al respecto de sitios que piensan que no
deberían estar allí). Desde ya muchas gracias por su colaboración.
http://dmoz.org/World/Espa%f1ol/Computadoras/Programaci%f3n/Lenguajes/
Delphi/
* Enlaces Delphi de Latium Software
Para aquellos que hace mucho no visitan nuestro sito web les comento
que hemos actualizado la página de enlaces en español y hoy por hoy es
una de las más completas que hay.
http://www.latiumsoftware.com/es/links.html
________________________________________________________________________
Si no has recibido el archivo con el código fuente completo de los
ejemplos que se presentan en este boletín, puedes descargarlo de la
siguiente dirección: http://www.latiumsoftware.com/es/file.php?id=p07
________________________________________________________________________
Página principal: http://www.latiumsoftware.com/es/pascal/index.php
Página del grupo: http://espanol.groups.yahoo.com/group/boletin-pascal/
Para suscribirse / apuntarse: boletin-pascal-subscribe@gruposyahoo.com
Para cancelar / removerse: boletin-pascal-unsubscribe@gruposyahoo.com
Para reportar problemas con la suscripción: eds2008 @ latiumsoftware.com
________________________________________________________________________
Este boletín se provee "TAL Y COMO ESTA", sin garantía de ninguna clase.
Su uso implica la aceptación de nuestros términos de licencia y de la
ausencia de garantía que puedes leer en nuestro sitio web. Allí también
encontrarás una nota sobre marcas registradas. Te animamos a que redis-
tribuyas este boletín, siempre y cuando lo hagas en forma completa
(incluyendo la información de copyright), sin modificaciones y de manera
gratuita. Los artículos son copyright de sus respectivos autores y se
reproducen aquí con el permiso de los mismos.
________________________________________________________________________
Latium Software http://www.latiumsoftware.com/es/index.php
Copyright (c) 2000 por Ernesto De Spirito. Todos los derechos reservados
________________________________________________________________________
|