Menú

Mostrar Mensajes

Esta sección te permite ver todos los mensajes escritos por este usuario. Ten en cuenta que sólo puedes ver los mensajes escritos en zonas a las que tienes acceso en este momento.

Mostrar Mensajes Menú

Mensajes - eferion

#241
Lo suyo sería realizar el procedimiento en dos pasos:

* calcular el residuo
* reubicar los dígitos

La primera opción se puede resolver con un bucle, la segunda hay que hacerla más a mano:


int CifrarNumero( int numero )
{
  int digitos[4];

  // Paso 1 : transformación
  int i;
  for ( i = 0; i < 4; i++ )
  {
    digitos[ 3 - i ] = ( ( numero % 10 ) + 7 ) % 10;
    numero /= 10;
  }

  // Paso 2 : permutación
  digitos[ 0 ] ^= digitos[ 2 ]
  digitos[ 2 ] ^= digitos[ 0 ];
  digitos[ 0 ] ^= digitos[ 2 ];

  digitos[ 1 ] ^= digitos[ 3 ];
  digitos[ 3 ] ^= digitos[ 1 ];
  digitos[ 1 ] ^= digitos[ 3 ];

  // Paso 3: Recomponemos el número.
  int resultado = 0;
  for ( i=0; i < 4; i++ )
  {
    resultado *= 10;
    resultado += digitos[ i ];
  }

  return resultado;
}


Se puede optimizar, pero yo creo que funciona... no tengo compilador en el teléfono
#242
Programación C/C++ / [APORTE] C++ y bases de datos
9 Septiembre 2014, 16:42 PM
Introducción

En este documento intento plasmar un poco mi experiencia a la hora de tratar con bases de datos. Se que está muy de moda usar herramientas que automatizan la conexión y hacen gran parte del trabajo por nosotros. Sin embargo ésta es una de las cosas que prefiero hacer por mi cuenta, ya que he visto fracasar bastantes proyectos debido a una mala gestión de la base de datos.

Ojo, no me considero un experto sobre el tema, de hecho, mi preparación es ingeniero de telecomunicaciones y en la carrera he visto tanta teoría sobre bases de datos como la que pueden dar unos niños en la guardería.

Se aceptan críticas constructivas, comentarios, anotaciones, mejoras... en fín, lo de siempre.

Como también se pone de moda esto de las licencias, este código se presenta tal cual, sin garantía de ningún tipo. Sois libres de copiar y/o modificar el código a vuestro antojo, sin embargo declino toda responsabilidad que su mal uso pueda conllevar. También sois libres de coger el código y guardarlo en algún rincón oscuro como vuestro tesoro o usarlo para hechizos y brujería, eso si, siempre bajo vuestra responsabilidad.

Nota adicional: No puedo garantizar que el código esté 100% libre de errores, ya que la mayor parte de los ejemplos no son ejecutables y los he tenido que ir retocando (con cuidado, eso sí) para adaptarlos a la guía. Aún así, me parece importante recalcar que el código está escrito bajo el estándar C++11, así que cuidado con usar compiladores no compatibles.

Bueno, dicha la parte aburrida vamos al tema. Antes o después, todo el que se dedica al mundo de la programación, ya sea por hobby o profesionalmente, acaba haciendo uso de bases de datos. Esto suele ser debido a que tenemos la mala costumbre de querer guardar todo tipo de información... con lo divertido que es reescribir los documentos una y otra vez.

Debido a que no suele ser demasiado eficiente eso de resetear la base de datos y rellenarla de nuevo cada vez que queremos almacenar algún cambio, lo lógico es que nos veamos obligados a guardar determinada información sobre los objetos que se encuentran en la base de datos:


  • Necesitamos saber si el objeto es nuevo o no (no es lo mismo CREATE que UPDATE).
  • Necesitamos saber si el objeto ha sido modificado. Si no lo hacemos tendremos que actualizar TODOS, con el tiempo que ello pueda conllevar.
  • En el caso de tablas con campo autoincremental, necesitamos almacenar dicho OID para futuras consultas.

La primera aproximación que yo creo que hemos hecho todos es meter toda esta información a piñón en el propio objeto. Algo del tipo:

Código (cpp) [Seleccionar]

class User
{
 public:

   // Interfaz para la base de datos

   // Devuelve el OID ( en el caso de objetos nuevos vale 0 )
   int OID( ) const;

   // Permite modificar el OID
   void SetOID( int oid );

   // Indica si el objeto es nuevo (no esta aun en la BD )
   bool IsNew( ) const;

   // Indica si el objeto ha sido modificado
   bool IsModified( ) const;

   // Reinicia los flags 'IsNew' e 'IsModified'
   void ObjectSaved( );

   // Interfaz propia del objeto

   std::string Name( ) const;

   std::string Surname( ) const;

   // ...
};


Como queda patente de un primer vistazo, esta solución no parece demasiado elegante. Estamos introduciendo acoplamiento al introducir en la interfaz elementos propios de la gestión de la base de datos. Además, este diseño plantea otros problemas:


  • Si se usan plugins, exponemos información sensible de la base de datos a terceros.
  • Si nuestra aplicación trabaja en red nos obliga a enviar información innecesaria en el lado del cliente.

El diseño, al fin y al cabo, es feo, ya puestos, también podemos poner en 'User' los métodos para leer y escribir en la base de datos, un par de métodos también para poder almacenar la información en un fichero y, si nos quedan más ganas de marcha, también podemos meter en la clase el código que gestione la interfaz gráfica...(espero que se entienda la ironía).

Después de un rato pensando, a alguien podría decir... "bueno, creo una clase base que tenga los datos de la BD y soluciono el problema con herencia:

Código (cpp) [Seleccionar]

class DBItem
{
 public:

   // Interfaz para la base de datos

   // Devuelve el OID ( en el caso de objetos nuevos vale 0 )
   int OID( ) const;

   // Permite modificar el OID
   void SetOID( int oid );

   // Indica si el objeto es nuevo (no esta aun en la BD )
   bool IsNew( ) const;

   // Indica si el objeto ha sido modificado
   bool IsModified( ) const;

   // Reinicia los flags 'IsNew' e 'IsModified'
   void ObjectSaved( );
};

class User : public DBItem
{
 public:

   // Interfaz propia del objeto

   std::string Name( ) const;

   std::string Surname( ) const;

   // ...
};


No voy a negarlo, es una posible solución, aunque este diseño sigue teniendo los mismos problemas de acoplamiento que el diseño anterior. La ventaja, eso sí, es que podremos reutilizar código de 'DBItem', no iban a ser todo malas noticias. Sin embargo, como comentaba, este diseño sigue sin convencer.

La idea entonces parece que pasa por separar físicamente la información relativa a la base de datos de los datos propios del objeto. El problema que aparece entonces es cómo conseguir esto de la forma más limpia posible, intentando además que sea reutilizable. Los requisitos que ha de cumplir este sistema son los siguientes:


  • Debe ser posible separar la lógica de negocio de la capa de acceso a datos.
  • Debe permitir la gestión de elementos eliminados.
  • Debe ser capaz de detectar elementos nuevos y elementos modificados.
  • La solución debe ser reutilizable.




Separar la lógica de negocio de la capa de acceso a datos.

Una posible solución, la que trataré en este tema, pasa por crear un contenedor a modo de caché.

El contenedor va a estar dividido en 2 niveles:


  • Nivel 1: El contenedor. Representa la interfaz accesible por la aplicación. Debe permitir realizar las operaciones habituales sobre los objetos que gestiona: creación, borrado, edición, etc.
  • Nivel 2: Información de base de datos. El contenedor almacenará instancias de un objeto que almacenará la información relevante de la base de datos. Este objeto únicamente será accesible por el contenedor.

De acuerdo con lo expuesto, la implementación de la clase interna del contenedor debería tener un aspecto similar al siguiente:

Código (cpp) [Seleccionar]

/*
* Gestiona información sobre un elemento almacenado en una caché.
* Los objetos se guardan indizados por clave única.
*/
class UserCacheItem final
{
   UserCacheItem( ) = delete;

   UserCacheItem(
             const UserCacheItem& ar_other ) = delete;

   UserCacheItem& operator=(
             const UserCacheItem& ar_other ) = delete;

 public:

   /*
    * Constructor de la clase.
    */
   UserCacheItem(
             User* ap_item,
             int a_key )
     : _item{ ap_item },
       _key{ a_key }
   {
   }

   /*!
    * Destructor de la clase.
    */
   virtual ~UserCacheItem( )
   {
   }

   /*!
    * Método llamado por la caché cuando se han almacando los cambios en la base de datos.
    */
   void Commit( )
   {
     if ( _tempOid )
     {
       _key = *_tempOid;
       _tempOid.reset( );
     }
   }
   
   /*!
    * Indica si el objeto gestionado es nuevo o no.
    */
   inline bool IsNew( ) const
   {
     return 0 == _key;
   }
   
   /*!
    * Indica si el objeto gestionado ha sufrido cambios desde el último commit.
    */
   inline bool IsModified( ) const
   {
     return !IsNew( ) && !_tempOid && _hash != _item->Hash( );
   }

   /*!
    * Expone el puntero al objeto gestionado por la instancia.
    */
   inline User* Item( ) const;
   {
     return _item.get( );
   }
   
   /*!
    * Recupera la clave única utilizada para identificar el objeto gestionado.
    */
   int Key( ) const
   {
     if ( _tempOid )
       return *_tempOid;
     else
       return _key;
   }
   
   /*!
    * Recupera la clave original.
    */
   inline int OriginalKey( ) const
   {
     return _key;
   }

   /*!
    * Permite modificar la clave del objeto.
    */
   void ChangeKey(
             int a_key )
   {
     if ( ar_key == _key )
     {
       _tempOid.reset( );
     }
     else if ( ( !_tempOid ) || ( a_key != *_tempOid ) )
     {
       _tempOid.reset( new int{ a_key } );
     }
   }
   
 private:

   std::unique_ptr< User* > _item;
   int _key;
   std::unique_ptr< int > _tempKey
};


Como se ve, internamente se usan punteros inteligentes. Desde mi punto de vista el uso de punteros inteligentes tiene dos ventajas básicas:


  • Evitar lagunas de memoria.
  • Queda claro que esta clase se encarga de controlar el ciclo de vida de los objetos de tipo 'User', lo cual evita malentendidos con los 'delete'.
  • Facilita el mantenimiento del código.

Además, ya se cómo funciona la memoria dinámica, no necesito practicarlo cada vez que tenga que crear objetos... y más cuando su ciclo de vida está tan delimitado. Prefiero centrar mis esfuerzos en problemas más serios.

En cuanto al control del oid, se puede apreciar que se usan dos claves: _key y _tempKey. Para almacenar el oid se ha optado por un mecanismo en dos pasos:


  • El valor inicial de _key se establece al crear el objeto.
  • Al llamar a ChangeKey se modifica _tempKey.
  • Al llamar a 'Commit' el valor de _key se modifica por _tempKey, suponiendo que tenga valor.

Este mecanismo en dos pasos permite "recordar" la clave que tenía inicialmente el objeto de cara a poder localizarlo en la base de datos en base a dicha clave. Recordemos que las actualizaciones a la base de datos suelen ser del tipo: "UPDATE users SET key=30 WHERE key=20". Si no guardamos ese '20', dificilmente podremos localizar el registro en la base de datos.

Entiendo que en este caso cambiar el oid puede no tener demasiado sentido, pero no siempre es así.

Hablando ahora del contenedor, su interfaz se detalla a continuacion:

Código (cpp) [Seleccionar]

/*!
* Contenedor de BD para objetos de tipo "User"
*/
class UserCache
{
   UserCache(
             const UserCache& ar_other ) = delete;

   UserCache& operator=(
             const UserCache& ar_other ) = delete;

 public:

   /*!
    * Constructor por defecto.
    */
   UserCache( )
   {
   }

   /*!
    * Destructor.
    */
   virtual ~UserCache( )
   {
   }

   /*!
    * Inserta un nuevo elemento en la cache
    */
   bool Add(
             User* ap_element,
             int a_key )
   {
     if ( ap_element )
     {
       auto it = _items.find( a_key );
       if ( it == _items.end( ) )
       {
         UserCacheItem* newItem = new UserCacheItem{ ap_element, a_key };
         _items.insert( std::make_pair( a_key, std::unique_ptr< UserCacheItem >{ newItem } ) );
         return true;
       }
     }

     return false;
   }

   /*!
    * Obtiene la clave para un usuario.
    */
   int Key(
             const User* ap_element ) const
   {
     auto it = FindItem( *ap_element, _items );
     if ( it != _items.end( ) )
       return it->first;

     return 0;
   }

   /*!
    * Recupera la lista de usuarios.
    */
   std::vector< User* > Items( ) const
   {
     std::vector< User* > to_return;

     for ( auto it = _items.begin( ); it != _items.end( ); ++it )
       to_return.push_back( it->second->Item( ) );

     return to_return;
   }

   std::map< int, User* > MappedItems( ) const
   {
     std::map< int, User* > to_return;

     for ( auto it = _items.begin( ); it != _items.end( ); ++it )
       to_return.insert( std::make_pair( it->first, it->second->Item( ) ) );

     return to_return;
   }

   /*!
    * Recupera un usuario en base a su oid.
    */
   User* Item(
             int a_key ) const
   {
     auto it = _items.find( a_key );
     if ( it != _items.end( ) )
       return it->second->Item( );

     return nullptr;
   }

   /*!
    * Permite cambiar el oid de un usuario.
    */
   bool ChangeKey(
             const User* ap_element,
             int a_key );
   {
     if ( ap_element )
     {
       auto it = _items.find( a_key );
       if ( it != _items.end( ) )
         return ( it->second->Item( ) == ap_element );

       it = FindItem( *ap_element, _items );
       if ( it != _items.end( ) )
       {
         // Si cambia la clave tenemos que actualizar el mapa de elementos
         it->second->SetKey( ar_key );
         _items.insert( std::make_pair( ar_key, std::move( it->second ) ) );
         _items.erase( it );
         return true;
       }
     }

     return false;
   }
   
   /*!
    * Marca todos los usuarios como "no modificados", además se encarga de
    * descartar todos los elementos eliminados.
    */
   void Commit( )
   {
     for ( auto& item : _items )
       item.second->Commit( );
   }

   /*!
    * Reinicia la caché. Elimina todo su contenido.
    */
   void Clear( )
   {
     _items.clear( );
   }

   /*!
    * Indica si hay cambios pendientes de llevar a la base de datos.
    */
   bool ChangesPending( ) const;
   {
     for ( auto& item : _items )
     {
       if ( item.second->IsModified( ) )
         return true;
     }

     return false;
   }
   
 private:

   std::map< int, std::unique_ptr< UserCacheItem > > _items;
   
   std::map< int, User* >::iterator FindItem(
             const User& ar_element ) const
   {
     return std::find_if( _items.begin( ),
                          _items.end( ),
                          [&ar_element]( const std::pair< int, User* >& pair )
                          { return pair.second->Item( ) == &ar_element; } );
   }

};


Bueno, con este diseño parece que hemos conseguido desacoplar el uso de la base de datos del objeto 'User'. No está mal para empezar. Sin embargo aún queda camino por recorrer.




Borrar usuarios

Normalmente, cuando en una aplicación se decide eliminar información, éste borrado no suele ser inmediato. En ocasiones hay que esperar a que el usuario confirme la operación dando la orden de "guardar cambios", mientras que en otras ocasiones interesa retrasar las eliminaciones con la intención de intentar agruparlas para optimizar las consultas a la base de datos.

Para satisfacer esta necesidad, nuestro contenedor va a encargarse de la gestión de los elementos borrados de la siguiente forma:


  • Cuando le indicamos que deseamos borrar un elemento que ya se encuentra gestionado por el contenedor lo introduce en un listado de elementos a eliminar. Los elementos no se eliminarán de forma efectiva hasta que no hagamos "commit".
  • Cuando le indicamos que deseamos borrar un elemento que NO se encuentra gestionado por el contenedor lo elimina directamente. Esto es así porque al no estar gestionado por el contenedor no es posible que otras partes del código conozcan este objeto.

Un ejemplo del código que permite cumplir con esta función lo encontramos a continuación:

Código (cpp) [Seleccionar]

class UserCache
{
 public:
 
   bool Delete(
       int a_key )
   {
     auto it = _items.find( a_key );
     if ( it != _items.end( ) )
     {
       _deletedItems.push_back( std::move( it->second ) );
       _items.erase( it );
       return true;
     }

     return false;
   }
   
   bool Delete(
       User* ap_element )
   {
     if ( ap_element )
     {
       auto it = FindItem( *ap_element );

       if ( it != _items.end( ) )
       {
         _deletedItems.push_back( std::move( it->second ) );
         _items.erase( it );
         return true;
       }
     }

     return false;
   }
       
   std::map< int, User* > ToDelete( ) const
   {
     std::map< int, User* > to_return;

     for ( auto& pair : _deletedItems )
       to_return.insert( std::make_pair( pair.first, pair.second->Item( ) ) );

     return to_return;
   }
   
   void Commit( )
   {
     _deletedItems.clear( );

     for ( auto& item : _items )
       item.second->Commit( );
   }

   void Clear( )
   {
     _items.clear( );
     _deletedItems.clear( );
   }

   bool ChangesPending( ) const
   {
     if ( !_deletedItems.empty( ) )
       return true;

     for ( auto& item : _items )
     {
       if ( item.second->IsModified( ) )
         return true;
     }

     return false;
  }
   
 private:
 
   std::vector< std::unique_ptr< User > > _deletedItems;
};


Como se puede apreciar, el contenedor nos va a ofrecer en un cómodo listado todos los objetos que se han marcado como borrados. Si recogemos este listado podemos




Modificar usuarios

Cuando recurrimos al método 'feo' para interaccionar con bases de datos, a menudo caemos en la tentación de registrar las modificaciones con un código similar al siguiente:

Código (cpp) [Seleccionar]

class User
{
 public:
 
   User( )
     : _modified{ false }
   { }
   
   std::string Name( ) const
   { return _name; }
   
   void SetName( const std::string& name )
   {
     if ( name != _name )
     {
       _name = name;
       _modified = true;
     }
   }
   
   bool IsModified( ) const
   { return _modified; }
   
 private:
 
   std::string _name;
   bool _modified;
};


Esta solución ya no nos sirve, ya que intentamos que la clase 'User' sera totalmente independiente de la base de datos.

Para poder marcar los objetos como modificados se puede optar por varias soluciones diferentes, os enumero tres de ellas:


  • Hacer una llamada del tipo 'UserCache::SetModified( usuario )' cada vez que modifiquemos un objeto. Este mecanismo tiene el inconveniente de ser complicado de gestionar, ya que obliga a meter esta instrucción en todas las funciones que modifiquen el objeto. Si el objeto se modifica en zonas muy concretas de la aplicación puede ser una solución bastante factible. PD.: no es recomendado si la clase puede ser modificada por plugins.
Código (cpp) [Seleccionar]

class UserCache
{
 public:
   void SetModified( User* ap_element );
};

class Something
{
 public:
 
   void UpdateUser( User* user );
   {
     user->SetName( anotherName );
     user->SetSurname( anotherSurname );
     
     // El método realmente no es estático, lo pongo asi por legibilidad
     UserCache::SetModified( ap_element );      
   }
};



  • Utilizar un hash para identificar los elementos que han cambiado. Este hash debe estar mapeado en el contenedor para poder detectar cambios. Este mecanismo puede implementarse en el contenedor o en la clase 'User'. Esta solución, aparte de ser más limpia ofrece código reutilizable, ya que el hash puede usarse, por ejemplo, en los operadores de igualdad.
Código (cpp) [Seleccionar]

class User
{
 public:
   std::size_t hash( ) const
   {
     const std::size_t name{ std::hash< std::string >( )( _name ) };
     const std::size_t surname { std::hash< std::string >( )( _surname ) };
     return name ^ ( surname << 1 );
   }
};



  • Utilizar un "contador de versión". El contador de versión puede ser tan sencillo como un "unsigned int" que se va incrementando cada vez que se realiza un cambio en el objeto. Entonces, basta con que el contenedor realice un mapeo de la versión de cada objeto para poder identificar los elementos modificados. La ventaja de este sistema respecto al hash es que es más rápido, la desventaja es que requiere actualizar los setters. Este método se parece bastante al "método feo" que iniciaba este apartado, es cierto.
Código (cpp) [Seleccionar]

class User
{
 public:
 
   User( )
     : _version{ 0 }
   { }
   
   void SetName( const std::string& name )
   {
     if ( name != _name )
     {
       _name = name;
       ++_version;
     }
   }
   
   unsigned int Version( ) const
   { return _version; }
};


Una vez que ya hemos elegido el mecanismo que vamos a implementar tenemos que adaptar el funcionamiento del contenedor para que sea capaz de identificar los elementos que han sufrido cambios. En este caso, el código de ejemplo presupone que se ha tomado como buena la opción de hash. Adaptar el código para que funcione con la opción del "contador de versiones" es bastante sencillo:

Clase UserCacheItem:
Código (cpp) [Seleccionar]

class UserCacheItem final
{
 public:

   /*!
    * \brief Constructor de la clase.
    */
   UserCacheItem(
       User* ap_item,
       int a_key )
     : _item{ ap_item },
       _key{ ar_key },
       _hash{ ap_item->Hash( ) }
   {
   }

   /*
    * Método llamado por la caché cuando se han almacando los cambios en la base de datos.
    */
   void Commit( )
   {
     if ( _tempOid )
     {
       _key = *_tempOid;
       _tempOid.reset( );
     }

     _hash = ap_item->Hash( );
   }
   
   /*!
    * Indica si el objeto gestionado ha sufrido cambios desde el último commit.
    */
   inline bool IsModified( ) const
   { return !IsNew( ) && !_tempOid && _hash != _item->Hash( ); }

 private:

   std::unique_ptr< User > _item;
   int _key;
   std::unique_ptr< int > _tempOid;
   std::size_t _hash;

};


Clase UserCache:
Código (cpp) [Seleccionar]

class UserCache
{
 public:

   std::set< User* > ToCreate( ) const
   {
     std::set< User* > to_return;

     for ( auto& item : _items )
     {
       if ( item.second->IsNew( ) )
         to_return.insert( item.second->Item( ) );
     }

     return to_return;
   }

   std::map< int, User* > ToUpdate( ) const
   {
     std::map< int, User* > to_return;

     for ( auto& item : _items )
     {
       if ( item.second->IsModified( ) )
         to_return.insert( std::make_pair( item.first, item.second->Item( ) ) );
     }

     return to_return;
   }
   
   bool ChangesPending( ) const
   {
     for ( auto& item : _items )
     {
       if ( item.second->IsModified( ) )
         return true;
     }

     return false;
   }
};





Conectar el contenedor con la base de datos

Una de las primeras motivaciones que nos ha llevado a este punto ha sido la necesidad de reducir el nivel de acoplamiento en nuestras clases. Por este motivo vamos a delegar la responsabilidad de la comunicación con la base de datos en una nueva clase.

A mí se me ha ocurrido llamarla 'DBUsers', para gustos, los nombres de las clases. Bueno, al tema, el caso es que esta clase debería proporcionar métodos para leer y escribir en las cachés de datos. Personalmente, en esta clase no me preocupa demasiado que haya acoplamiento ya que, como norma general, cada aplicación tiene su propio diseño de base de datos, por lo que el código de esta clase dificilmente será reutilizable.

Código (cpp) [Seleccionar]

class DBUsers
{
   DBUsers( ) = delete;
   
   DBUsers(
         const DBUsers& ar_other ) = delete;
         
   const DBUsers& operator=(
         const DBUsers& ar_other ) = delete;
         
 public:
 
   DBUsers(
          const UniqueKeyCache< User, int >& ar_cache )
     : _cache{ &ar_cache }
   {
   }
   
   virtual ~DBUsers( )
   {
   }
   
   bool SaveData( )
   {
     bool ok = true;
     
     // Este primer chequeo no sería necesario, pero me gusta la simetría
     // y además así aseguro que se elimina deletedItems
     if ( ok )
     {
       // Primero es recomendable realizar la operación de borrado
       auto deletedItems = _cache.ItemsToDelete( );
       for ( auto& pair : deletedItems )
       {
         // Generación y ejecución de las consultas de eliminación.
         // Si algúna consulta da error, actualizamos ok a false
       }
     }
     
     if ( ok )
     {
       // Después actualizamos los elementos modificados
       auto updatedItems = _cache.ItemsToUpdate( );
       for ( auto& pair : updatedItems )
       {
         // Generación y ejecución de las consultas de actualización
         // Si algúna consulta da error, actualizamos ok a false
       }
     }
     
     // Finalmente damos de alta los nuevos registros
     if ( ok )
     {
       auto newItems = _cache.ItemsToCreate( );
       for ( auto& item : newItems )
       {
         // Generación y ejecución de las consultas de creación
         // Si algúna consulta da error, actualizamos ok a false
         
         // No hay que olvidarse de recuperar el OID de cada elemento
         // y actualizar la cache.
         _cache->ChangeKey( &item, newOid );
       }
     }
     
     if ( ok )
       _cache->Commit( );
     else
     {
       // Si se produce algún error tenemos que decidir entre resetear la cache
       // o permitir al usuario intentarlo más veces.
     }
   }
   
   void LoadUsers( )
   {
     // Consulta de selección sobre la tabla de usuarios
     // ...
     while ( row.next( ) ) // Para cada registro leído...
     {
       User* user = new User{ };
       user->SetName( row.data( "name" ).toString( ) );
       user->SetSurname( row.data( "surname" ).toString( ) );
       
       _cache->Add( user, row.data( "id" ).toInt( ) );
     }
   }
   
   // No hace falta leer toda la tabla... se puede leer bajo demanda para
   // reducir los tiempos de acceso.
   void LoadUsers( const std::string& name );
   
 private:
 
   UniqueKeyCache< User, int >* _cache;
   
};


Como se ve, la caché la almaceno por referencia. Si no tengo necesidad de eliminar un objeto en un contexto dado, y además necesito que ese objeto exista, prefiero usar una referencia que un puntero, así queda claro que no es competencia de ese contexto hacer un delete. Además, la idea de que la caché pueda utilizarse de forma independiente permite aislar la capa de acceso a base de datos, piensa que no es obligatorio que los datos también se pueden guardar en un fichero o enviar por red... este diseño permite tener una clase específica para cada uso y sin necesidad de que se conozcan entre ellas.




Reutilización del código

El código ya nos funciona para la clase 'User', pero la gracia es que todo este esfuerzo pueda ser reutilizado con otras clases. La mejor forma para hacer esto es convertir la caché en un template... el código puede que se ensucie un poco con esta conversión, pero creo que es un sacrificio razonable que se va a compensar con creces:

Template CacheItem (sustituye a UserCacheItem):
Código (cpp) [Seleccionar]

/*!
* Gestiona información sobre un elemento almacenado en una caché.
* Los objetos se guardan indizados por clave única.
*/
template< class _CLASS_, class _KEY_ >
class CacheItem final
{
   CacheItem( ) = delete;

   CacheItem(
       const CacheItem& ar_other ) = delete;

   CacheItem& operator=(
       const CacheItem& ar_other ) = delete;

 public:

   /*!
    * Constructor de la clase.
    */
   CacheItem(
       _CLASS_* ap_item,
       const _KEY_& ar_key );

   /*!
    * Destructor de la clase.
    */
   virtual ~CacheItem( );

   /*!
    * Método llamado por la caché cuando se han almacando los cambios en la base de datos.
    */
   void Commit( );

   /*!
    * Indica si el objeto gestionado es nuevo o no.
    */
   inline bool IsNew( ) const
   { return _KEY_{ } == _key; }

   /*!
    * Indica si el objeto gestionado ha sufrido cambios desde el último commit.
    */
   inline bool IsModified( ) const
   { return !IsNew( ) && !_tempOid && _hash != _item->Hash( ); }

   /*!
    * Expone el puntero al objeto gestionado por la instancia.
    */
   inline _CLASS_* Item( ) const
   { return _item.get( ); }

   /*!
    * Recupera la clave única utilizada para identificar el objeto gestionado.
    */
   _KEY_ Key( ) const;

   /*!
    * Recupera la clave original.
    */
   inline _KEY_ OriginalKey( ) const
   { return _key; }

   /*!
    * Permite modificar la clave del objeto.
    */
   void ChangeKey(
       const _KEY_& ar_key )
   {
     if ( ar_key == _key )
     {
       _tempOid.reset( );
     }
     else if ( ( !_tempOid ) || ( ar_key != *_tempOid ) )
     {
       _tempOid.reset( new _KEY_{ ar_key } );
     }
   }

 private:

   std::unique_ptr< _CLASS_ > _item;
   _KEY_ _key;
   std::unique_ptr< _KEY_ > _tempOid;
   std::size_t _hash;

};

template< class _CLASS_, class _KEY_ >
CacheItem< _CLASS_,_KEY_ >::CacheItem(
         _CLASS_* ap_item,
         const _KEY_& ar_key )
 : _item{ ap_item },
   _key{ ar_key },
   _hash{ ap_item->Hash( ) }
{
}

template< class _CLASS_, class _KEY_ >
CacheItem< _CLASS_,_KEY_ >::~CacheItem( )
{
}

template< class _CLASS_, class _KEY_ >
void
CacheItem< _CLASS_,_KEY_ >::Commit( )
{
 if ( _tempOid )
 {
   _key = *_tempOid;
   _tempOid.reset( );
 }

 _hash = _item->Hash( );
}

template< class _CLASS_, class _KEY_ >
_KEY_
CacheItem< _CLASS_,_KEY_ >::Key( ) const
{
 if ( _tempOid )
   return *_tempOid;
 else
   return _key;
}


Se pueden apreciar algúnos cambios en lo relativo al tratamiento de la clave primaria. Dado que ahora esta clave puede ser de cualquier tipo (un string por ejemplo), los métodos ahora gestionan la clave por referencia.

Clase UniqueKeyCache (sustituye a UserCache):
Código (cpp) [Seleccionar]

template< class _CLASS_, class _KEY_ >
class UniqueKeyCache
{
   UniqueKeyCache(
       const UniqueKeyCache& ar_other ) = delete;

   UniqueKeyCache& operator=(
       const UniqueKeyCache& ar_other ) = delete;

 public:

   /*!
    * Constructor por defecto.
    */
   UniqueKeyCache( )
   { }

   /*!
    * Destructor.
    */
   virtual ~UniqueKeyCache( )
   { }

   /*!
    * Inserta un nuevo elemento en la cache.
    */
   bool Add(
       _CLASS_* ap_element,
       const _KEY_& ar_key );

   /*!
    * Devuelve la clave asociada a un elemento.
    */
   _KEY_ Key(
       const _CLASS_* ap_element ) const;

   /*!
    * Marca un elemento para ser eliminado.
    */
   bool Delete(
       const _KEY_& ar_key );

   /*!
    * Marca un elemento para ser eliminado.
    */
   bool Delete(
       _CLASS_* ap_element );

   /*!
    * Devuelve el listado de elementos.
    */
   std::set< _CLASS_* > Items( ) const;

   /*!
    * Devuelve el listado de elementos mapeados por la clave primaria.
    */
   std::map< _KEY_, _CLASS_* > MappedItems( ) const;

   /*!
    * Devuelve un elemento dada su clave primaria.
    */
   _CLASS_* Item(
       const _KEY_& ar_key ) const;

   /*!
    * Devuelve la lista de elementos que aún no están dados de alta en la base
    * de datos.
    */
   std::set< _CLASS_* > ToCreate( ) const;

   /*!
    * Devuelve la lista de elementos a actualizar en la base de datos.
    */
   std::map< _KEY_, _CLASS_* > ToUpdate( ) const;

   /*!
    * Devuelve la lista de elementos a eliminar de la base de datos.
    */
   std::map< _KEY_, _CLASS_* > ToDelete( ) const;

   /*!
    * Permite modificar la clave primaria de un elemento.
    */
   bool ChangeKey(
       const _CLASS_* ap_element,
       const _KEY_& ar_key );

   /*!
    * Limpia la lista de elementos nuevos y/o modificados y elimina los
    * elementos marcados para borrar.
    */    
   void Commit( );

   /*!
    * Limpia la caché, eliminando todo su contenido.
    */
   void Clear( );

   /*!
    * Indica si hay o no cambios pendientes de llevar a la base de datos.
    */
   bool ChangesPending( ) const;

 private:

   using _Item_              = CacheItem< _CLASS_, _KEY_ >;
   using _ItemMap_           = std::map< _KEY_, std::unique_ptr< _Item_ > >;
   using _ItemPair_          = std::pair< _KEY_, std::unique_ptr< _Item_ > >;
   using _ItemMapIterator_   = typename _ItemMap_::iterator;
   using _ItemList_          = std::vector< std::unique_ptr< _Item_ > >;

   _ItemMap_ _items;
   _ItemList_ _deletedItems;

   /*!
    * Utilidad para buscar en la lista de elementos.
    */
   _ItemMapIterator_ FindItem(
       _CLASS_* ap_element ) const
   {
     return std::find_if( _items.begin( ),
                          _items.end( ),
                          [ap_element]( const _ItemPair_& pair )
                          { return pair.second->Item( ) == ap_element; } );
   }
};

template< class _CLASS_, class _KEY_ >
bool
UniqueKeyCache< _CLASS_, _KEY_ >::Add(
         _CLASS_* ap_element,
         const _KEY_& ar_key )
{
 if ( ap_element )
 {
   auto it = _items.find( ar_key );
   if ( it == _items.end( ) )
   {
     _Item_* newItem = new _Item_{ ap_element, ar_key };
     _items.insert( std::make_pair( ar_key, std::unique_ptr< _Item_ >( newItem ) ) );
     return true;
   }
 }

 return false;
}

template< class _CLASS_, class _KEY_ >
_KEY_
UniqueKeyCache< _CLASS_, _KEY_ >::Key(
         const _CLASS_* ap_element ) const
{
 auto it = FindItem( *ap_element );
 if ( it != _items.end( ) )
   return it->first;

 return _KEY_{ };
}

template< class _CLASS_, class _KEY_ >
bool
UniqueKeyCache< _CLASS_, _KEY_ >::Delete(
         const _KEY_& ar_key )
{
 auto it = _items.find( ar_key );
 if ( it != _items.end( ) )
 {
   _deletedItems.push_back( std::move( it->second ) );
   _items.erase( it );
   return true;
 }

 return false;
}

template< class _CLASS_, class _KEY_ >
bool
UniqueKeyCache< _CLASS_, _KEY_ >::Delete(
         _CLASS_* ap_element )
{
 if ( ap_element )
 {
   auto it = FindItem( *ap_element );
   if ( it != _items.end( ) )
   {
     _deletedItems.push_back( std::move( it->second ) );
     _items.erase( it );
     return true;
   }
 }

 return false;
}

template< class _CLASS_, class _KEY_ >
std::set< _CLASS_* >
UniqueKeyCache< _CLASS_, _KEY_ >::Items( ) const
{
 std::set< _CLASS_* > to_return;

 for ( auto& pair : _items )
   to_return.insert( pair.second->Item( ) );

 return to_return;
}

template< class _CLASS_, class _KEY_ >
std::map< _KEY_, _CLASS_* >
UniqueKeyCache< _CLASS_, _KEY_ >::MappedItems( ) const
{
 std::map< _KEY_, _CLASS_* > to_return;

 for ( auto& pair : _items )
   to_return.insert( std::make_pair( pair.first, pair.second->Item( ) ) );

 return to_return;
}

template< class _CLASS_, class _KEY_ >
_CLASS_*
UniqueKeyCache< _CLASS_, _KEY_ >::Item(
         const _KEY_& ar_key ) const
{
 auto it = _items.find( ar_key );
 if ( it != _items.end( ) )
   return it->second->Item( );

 return nullptr;
}

template< class _CLASS_, class _KEY_ >
std::set< _CLASS_* >
UniqueKeyCache< _CLASS_, _KEY_ >::ToCreate( ) const
{
 std::set< _CLASS_* > to_return;

 for ( auto& pair : _items )
 {
   if ( pair.second->IsNew( ) )
     to_return.insert( pair.second->Item( ) );
 }

 return to_return;
}

template< class _CLASS_, class _KEY_ >
std::map< _KEY_, _CLASS_* >
UniqueKeyCache< _CLASS_, _KEY_ >::ToUpdate( ) const
{
 std::map< _KEY_, _CLASS_* > to_return;

 for ( auto& item : _items )
 {
   if ( item.second->IsModified( ) )
     to_return.insert( std::make_pair( item.first, item.second->Item( ) ) );
 }

 return to_return;
}

template< class _CLASS_, class _KEY_ >
std::map< _KEY_, _CLASS_* >
UniqueKeyCache< _CLASS_, _KEY_ >::ToDelete( ) const
{
 std::map< _KEY_, _CLASS_* > to_return;

 for ( auto& pair : _deletedItems )
   to_return.insert( std::make_pair( pair.first, pair.second->Item( ) ) );

 return to_return;
}

template< class _CLASS_, class _KEY_ >
bool
UniqueKeyCache< _CLASS_, _KEY_ >::ChangeKey(
         const _CLASS_* ap_element,
         const _KEY_& ar_key )
{
 if ( ap_element )
 {
   auto it = _items.find( ar_key );
   if ( it != _items.end( ) )
     return ( it->second->Item( ) == ap_element );

   it = FindItem( *ap_element );
   if ( it != _items.end( ) )
   {
     it->second->SetKey( ar_key );
     _items.insert( std::make_pair( ar_key, std::move( it->second ) ) );
     _items.erase( it );
     return true;
   }
 }

 return false;
}

template< class _CLASS_, class _KEY_ >
void
UniqueKeyCache< _CLASS_, _KEY_ >::Commit( )
{
 _deletedItems.clear( );

 for ( auto& item : _items )
   item.second->Commit( );
}

template< class _CLASS_, class _KEY_ >
void
UniqueKeyCache< _CLASS_, _KEY_ >::Clear( )
{
 _items.clear( );
 _deletedItems.clear( );
}

template< class _CLASS_, class _KEY_ >
bool
UniqueKeyCache< _CLASS_, _KEY_ >::ChangesPending( ) const
{
 if ( !_deletedItems.empty( ) )
   return true;

 for ( auto& item : _items )
 {
   if ( item.second->IsModified( ) )
     return true;
 }

 return false;
}





Posibles mejoras

La cache se puede heredar para añadir funcionalidad específica en función del tipo de objeto que estemos cacheando. Por ejemplo, en el caso de los usuarios podría ser util obtener un listado de los usuarios con la cuenta bloqueada. Básicamente lo único que tendríamos que modificar en la clase 'UniqueKeyCache' es poner el listado de elementos en la parte protegida y heredar:

Código (cpp) [Seleccionar]

class UsersCache : public UniqueKeyCache< User, int >
{
 public:
 
   std::set< User* > LockedAccounts( ) const
   {
     std::set< User* > to_return;
     
     for ( auto& pair : _items )
     {
       if ( pair->second->Item( )->IsLocked( ) )
         to_return.insert( pair->second->Item( ) );
     }
     
     return to_return;
   }
};


Y bueno, dado que un template no es exactamente lo mismo que una clase base, lo mejor es también sustituir 'DBUsers' para que acepte un objeto de tipo 'UserCache':

Código (cpp) [Seleccionar]

class DBUsers
{
   DBUsers( ) = delete;
   
   DBUsers(
         const DBUsers& ar_other ) = delete;
         
   const DBUsers& operator=(
         const DBUsers& ar_other ) = delete;
 public:
 
   DBUsers(
          const UsersCache& ar_cache )
     : _cache{ ar_cache }
   {
   }
   
   virtual ~DBUsers( )
   {
   }
   
   bool SaveData( )
   {
     bool ok = true;
     
     // Este primer chequeo no sería necesario, pero me gusta la simetría
     // y además así aseguro que se elimina deletedItems cuando deja de ser necesario
     if ( ok )
     {
       // Primero es recomendable realizar la operación de borrado
       auto deletedItems = _cache.ItemsToDelete( );
       for ( auto& pair : deletedItems )
       {
         // Generación y ejecución de las consultas de eliminación.
         // Si algúna consulta da error, actualizamos ok a false
       }
     }
     
     if ( ok )
     {
       // Después actualizamos los elementos modificados
       auto updatedItems = _cache.ItemsToUpdate( );
       for ( auto& pair : updatedItems )
       {
         // Generación y ejecución de las consultas de actualización
         // Si algúna consulta da error, actualizamos ok a false
       }
     }
     
     // Finalmente damos de alta los nuevos registros
     if ( ok )
     {
       auto newItems = _cache.ItemsToCreate( );
       for ( auto& item : newItems )
       {
         // Generación y ejecución de las consultas de creación
         // Si algúna consulta da error, actualizamos ok a false
         
         // No hay que olvidarse de recuperar el OID de cada elemento
         // y actualizar la cache.
         _cache.ChangeKey( &item, newOid );
       }
     }
     
     if ( ok )
       _cache.Commit( );
     else
     {
       // Si se produce algún error tenemos que decidir entre resetear la cache
       // o permitir al usuario intentarlo más veces.
     }
   }
   
   void LoadUsers( )
   {
     // Consulta de selección sobre la tabla de usuarios
     // ...
     while ( row.next( ) ) // Para cada registro leído...
     {
       User* user = new User{ };
       user->SetName( row.data( "name" ).toString( ) );
       user->SetSurname( row.data( "surname" ).toString( ) );
       
       _cache.Add( user, row.data( "id" ).toInt( ) );
     }
   }
   
   // No hace falta leer toda la tabla... se puede leer bajo demanda para
   // reducir los tiempos de acceso.
   void LoadUsers( const std::string& name );
   
 private:
 
   UsersCache& _cache;
   
};


Y esto es todo. Creo que el diseño final es elegante, ya que reduce enormemente la dependencia entre clases y permite crear rápidamente enlaces con bases de datos, ficheros XML, ficheros binarios, sockets, etc.

También hay que tener en cuenta que el diseño que he planteado se puede adaptar de forma más o menos sencilla para que funcione bajo diferentes escenarios: Registros sin clave primaria, Registros con clave primaria compuesta, Clave primaria repetida, etc. Yo, personalmente, diseñaría un template de cache específico para cada caso... no soy especialmente partidario de hacer un template que herede de otro.

Y poco más que contar. Espero que os haya gustado, que hayáis aprendido algo o, en el peor de los casos, me conformo con que no penséis que habéis perdido el tiempo al leer este artículo.

Un saludo.
#243
Programación C/C++ / Re: Duda básica sobre C:
9 Septiembre 2014, 09:07 AM
Cita de: superkorlas en  8 Septiembre 2014, 23:57 PM
Yo algunas cosillas que he programado en c++ y lo que he aprendido en conclase, para las frases y demás utilizaba el tipo char, aunque más tarde me entere del tipo string en este foro, y me dijeron que era más correcto, por eso me gustaría saber la diferencia

Un "string" al estilo C puede estar definido de las siguientes maneras:


// Opción 1
// Cadena de sólo lectura. Modificar su contenido puede provocar fallos en la aplicación.
char *cadena = "Una frase cualquiera";

// Opción 2
// Cadena de tamaño dinámico. Es la opción más flexible aunque puede provocar fugas de memoria
// si no se hace con cuidado.
char *cadena = malloc( x );

// Opción 3
// Cadena de longitud fija. Es un diseño demasiado rígido ya que no te permite adaptarte.
// Es esa longitud y punto, no hay más.
char cadena[20];


C++ proporciona la clase "string", esta clase te proporciona la misma funcionalidad que el segundo caso con la ventaja de despreocuparte por el uso de memoria, es decir, te olvidas de malloc, free y realloc:

Un ejemplo en C:


char * cadena = malloc( 10 );
strcpy( cadena, "123456789" );
printf( "%s\n", cadena );

cadena = realloc( cadena, 20 * sizeof(char) );
strcat( cadena, "0123456789" );
printf( "%s\n", cadena );

free( cadena );


Equivalente en C++:
Código (cpp) [Seleccionar]

std::string cadena;
cadena = "123456789";
std::cout << cadena << std::endl;

cadena += "0123456789";
std::cout << cadena << std::endl;


C++ te permite no "reinventar la rueda" cada dos por tres. Esto es a costa de delegar en las clases parte de la funcionalidad. Esto tiene la ventaja de que hay que escribir menos código y éste es menos propenso a errores... la parte negativa es que el código resultante es más lento. Eso sí, dudo mucho que seas capaz de notar la diferencia de velocidad a simple vista entre las dos versiones.
#244
Aparte de lo comentado por kutcher te pongo un par de comentarios adicionales:

* Las variables i, j, Num2 y Res deberían ser variables locales definidas en funciones y no en el cuerpo de la clase, ya que únicamente las usas para calcular resultados parciales.

* Para almacenar la secuencia binaria puedes usar la clase "string" o la clase "vector".

* A diferencia de C, en C++ no es necesario poner "void" en las funciones que no reciben argumentos.

* Si se llama a "Imprimir" sin llamar antes a "Leer" vas a imprimir basura. Para evitar eso necesitas hacer uso del constructor por defecto. El constructor se llama automáticamente al crear el objeto, por lo que es un lugar ideal para inicializar las variables de la clase.

Código (cpp) [Seleccionar]

#include <iostream>
#include <string>

using namespace std;

class Decimal
{
  private:
    int numeroDecimal;
    std::string numeroBinario;

  public:

    // Constructor por defecto
    Decimal( );

    void Leer();
    void Proceso();
    void Imprimir();
};

// Implementación del constructor por defecto.
Decimal::Decimal( )
{
  numeroDecimal = 0;
}

void Decimal::Leer()
{
  cout<<"Ingrese el numero del que desea conocer su equivalencia en binario"<<endl;
  cin>>numeroDecimal;
}

void Decimal::Proceso()
{
  int numero = numeroDecimal;

  if ( numero == 0 )
    numeroBinario = "0";

  while ( numero )
  {
    int digito = numero % 2;
    numero /= 2;

    // Los nuevos valores los vamos añadiendo al inicio de la cadena para que la impresión del
    // resultado sea más sencilla.
    numeroBinario.insert( 0, 1, '0' + digito );
  }
}

void Decimal::Imprimir()
{
  cout<<"El numero "<< numeroDecimal <<" en binario es: " << numeroBinario << endl;
}
#245
Código (cpp) [Seleccionar]

int i, j;
int total_exterior = 0;
int total_interior = 0;

for ( i=0; i< 3; i++ ) // bucle exterior
{
 total_exterior++;

 for ( j=0; j<10; j++ ) // bucle interior
 {
   printf( "i=%d j=%d", i, j );
   total_interior++;
 }
}

printf( "total_exterior = %d\n", total_exterior );
printf( "total_interior = %d\n", total_interior );


Un bucle anidado no tiene mucho misterio... el primer bucle que se ejecuta es el exterior, y por cada iteración del bucle exterior se ejecuta el bucle interior en su totalidad.

En el ejemplo que te he puesto, el bucle exterior se ejecuta 3 veces, en cada una de estas iteraciones el bucle interior se ejecuta 10 veces. al final del proceso 'total_exterior' vale 3 (el bucle exterior se ha ejecutado únicamente 3 veces) y 'total_interior' vale 30 (por cada iteración del bucle exterior, el interior se ha ejecutado 10 veces, 3 * 10 = 30 ).
#246
Tienes varias opciones a la hora de enfocar el problema:

Usar contenedores

Los contenedores de C++ son de los elementos más útiles en programación. Elegir el contenedor adecuado a cada situación puede ahorrarte mucho código.

En tu caso, imagina que tienes X candidatos. Podrías crear un vector de X+1 elementos, de tal forma que el índice cero se use para almacenar los votos nulos, el resto de índices se usa para contar los votos de cada candidatos:

Código (cpp) [Seleccionar]

// Este número lo calculas al leer el fichero
int numCandidatos = 10;

// Creamos un vector que inicialmente va a tener X + 1 elementos
// Al ser un vector de tipo int, el valor inicial de cada elemento es 0.
std::vector< int > votos{ numCandidatos + 1 };

// Fase de votación
std::cout << "Emite tu voto: ";
int voto;
std::cin >> voto;

if ( voto == 0 )
{
  // Fin de la votación
}
else if ( voto < 0 || voto >= votos.size( ) )
  votos[ 0 ]++; // El voto es nulo
else
  votos[ voto ]++; // Incrementamos los votos del candidato


Con este formato, la presentación de los resultados es bastante trivial:

Código (cpp) [Seleccionar]

for ( unsigned int i = 1; i < votos.size( ); ++i )
{
  std:cout << "Candidato " << i << ": " << votos[ i ] << " votos" << std::endl;
}

std::cout << "Votos nulos: " << votos[ 0 ] << std::endl;

// Total de votos versión 1:
int total = 0;
for ( auto it = std::begin( votos ); it != std::end( votos ); ++it )
  total += *it;

// Total de votos version 2:
int total = std::accumulate( std::begin( vector ), std::end( vector ), 0 );

std::cout << "Total de votos: " << total << std::endl;


Otro contenedor que puedes usar es 'map'. Los mapas tienen la ventaja de que pueden usar un string por índice (y quien dice un string dice un float, una clase propia, ... ), lo cual viene bien en bastantes ocasiones.

También puedes usar new y delete para crear un arreglo. A mi no me parece una buena idea salvo que quieras practicar con ello.
#247
Programación C/C++ / Re: Array con numeros primos.
5 Septiembre 2014, 08:37 AM
Bienvenido al foro.

Te pongo las cosillas que he ido viendo al revisar tu código.

1. Estás usando cabeceras de C y de C++

Código (cpp) [Seleccionar]

#include <iostream>
#include <stdlib.h>


La cabecera 'iostream' es propia de C++, sin embargo 'stdlib.h' es de C. Salvo que no haya alternativas es mejor no mezclar. Esto no es un error en sí, es una recomendación.

Además, no veo motivos para tener el include de 'stdlib.h', ya que no estás accediendo a ninguna función declarada en dicha librería. Tener includes innecesarios incrementa el tiempo de compilación, aunque en este ejemplo no se note.

2. Tener conceptos claros

Código (cpp) [Seleccionar]

for(x=0;x<100;x++)  // recorro y lleno el arreglo con los # del 1 al 100
  {
      array_prime[x]=num++;
  }


Sobre las líneas de arriba te puedo decir dos cosas:

* 'num' es una variable que te puedes ahorrar. De hecho 'num=x+1' durante todo el ciclo.
* 'array_prime' también te la puedes ahorrar. Si te das cuenta, en la línea 25 ocurre que 'array_prime[ x ]==x+1', luego puedes hacer la sustitución y ahorrarte tanto el arreglo como el bucle que tienes destacado en este apartado.

3. Tienes que aprender a depurar el código.

Código (cpp) [Seleccionar]

for(x=0;x<100;x++) //Recorro el arreglo
    {
      for(z=1;z<=x;z++) // for para probar cada numero del arreglo es primo
      {
        if(x%z==0)add++;  // si el residuo es 0, contador add cuenta.
      }
      if(add==2)  // si el contador es igual a 2, el #del arreglo es primo.
      {
         cout<<array_prime[x]; //asigno el #primo al array. imprimo
      }
    }


El error en sí se encuentra en estas líneas... para cada número usas 'add' para contar sus divisores... pero no reinicias el valor de 'add' en ningún momento. Luego su valor no hará sino crecer de forma constante:

* x = 0 -> add = 0
* x = 1 - divisores: 1 -> add = 0 + 1 = 1
* x = 2 - divisores: 1, 2 -> add = 1 + 2 = 3
* x = 3 - divisores: 1, 3 -> add = 3 + 2 = 5
* x = 4 - divisores: 1, 2, 4 -> add = 5 + 3 = 8
* x = 5 - divisores: 1, 5 -> add = 8 + 2 = 10
...

Además, fíjate que no estás comparando los mismos números:

Código (cpp) [Seleccionar]

for(x=0;x<100;x++)
    {
      for(z=1;z<=x;z++)
      {
        if(x%z==0)add++;  // AQUI estas mirando si 'x' es primo
      }
      if(add==2)
      {
         cout<<array_prime[x]; // si 'x' es primo, imprimes 'array_prime[x]', que es 'x+1'
      }
    }


Son errores tontos, pero sabiendo usar un depurador los encuentras con cierta facilidad.

A continuación te pongo tu código con algún retoque. He intentado mantener el código original. Como ves te estabas complicando en exceso.

Código (cpp) [Seleccionar]

/* Escribir un programa que almacene en un arreglo los
  números primos comprendidos entre 1 y 100. */

#include <iostream>
using namespace std;

int main()
{
  for( int x=2; x<100; x++ ) //Recorro el arreglo
  {
    int add = 0;

    for( int z=1; z<=x; z++ ) // for para probar cada numero del arreglo es primo
    {
      if(x%z==0)add++;  // si el residuo es 0, contador add cuenta.
    }

    if(add==2)  // si el contador es igual a 2, el #del arreglo es primo.
    {
       cout << x << " "; //asigno el #primo al array. imprimo
    }
  }
   cin.get();cin.get();
   return 0;
}


Aún así has de saber que tu código está trabajando más de lo necesario para encontrar primos. En el mismo momento en el que encuentras un divisor, el número ya no es primo, por lo que no hace falta que sigas comprobando más combinaciones; además, para saber si un número es primo no hace falta comprobar todo el rango [2, x-1], con comprobar el rango [2, x/2] te sobra. Este rango se podría reducir aún más, pero habría que hacer algún cambio más en el código.

Después de estas pequeñas optimizaciones el código podría lucir tal que:

Código (cpp) [Seleccionar]

/* Escribir un programa que almacene en un arreglo los
   números primos comprendidos entre 1 y 100. */

#include <iostream>
using namespace std;

int main()
{
   for( int x=2; x<100; x++ ) //Recorro el arreglo
   {
     bool primo = true;

     int max = x/2;
     for( int z=2; z<=max; z++ )
     {
       if(x%z==0) // Si es divisor, no es primo.
       {
         primo = false;
         break;
       }
     }

     if(primo) // Si es primo, lo saco por pantalla.
     {
        cout << x << " ";
     }
   }
    cin.get();cin.get();
    return 0;
}
#248

if((fichero=fopen(name,"rb"))!=NULL){
  do{
    printf("\nIntroduzca numero de persona a visualizar: ");
    // ...

    for(i=0;i<N;i++){
      if(numero==i){
        printf("\nNombre: %s",persona[i].nombre);
        printf("\nCiudad: %s\n",persona[i].ciudad);
        printf("Edad: %d",persona[i].edad);
        leo=fread(&persona[i],1,sizeof(persona),fichero);
      }
    }
  }while(leo!=0);
  fclose(fichero);
}


No me ha quedado muy clara esta parte del código.

Intentas leer un registro determinado del archivo... pero el mecanismo es extraño. Me explico:

1. Abres el archivo
2. Preguntas al usuario el índice del registro a visualizar??? antes de haber leído el archivo ?????
3. Después, localizas ese registro, lo sacas por pantalla y entonces sobreescribes su contenido con el primer registro que te encuentras en el fichero. Al final lo que acabas de leer te lo guardas para ti y no lo sacas por pantalla.
4. Vuelves al paso 2 hasta que hayas leído todos los registros del archivo.

El código de arriba lo puedes simplificar. Quizás te ayude a ver que no tiene mucho sentido :


if((fichero=fopen(name,"rb"))!=NULL){
  do{
    printf("\nIntroduzca numero de persona a visualizar: ");
    // ...

    printf("\nNombre: %s",persona[numero].nombre);
    printf("\nCiudad: %s\n",persona[numero].ciudad);
    printf("Edad: %d",persona[numero].edad);
    leo=fread(&persona[numero],1,sizeof(persona),fichero);
  }while(leo!=0);
  fclose(fichero);
}


El comportamiento es francamente extraño.

Quizás deberías comentar qué se espera acerca del funcionamiento de este programa.
#249
Para las cadenas, usa la clase string en vez de char*:

Código (cpp) [Seleccionar]

#include <string>

// const& implica una referencia constante
bool palindromo( const std::string& cadena );

// ...

std::string cadena;

// ...

std::cin >> cadena;

// ...


Además, te complicas demasiado la forma de recorrer la cadena:

Código (cpp) [Seleccionar]
int j = sizeof(cadena)/sizeof(cadena[0]);

sizeof(cadena) te va a dar 100... realmente tiene 100 caracteres tu cadena???, no verdad??

Código (cpp) [Seleccionar]
int j = strlen(cadena) - 1;

O si estás haciendo uso de la clase string:

Código (cpp) [Seleccionar]
int j = cadena.length( ) - 1;

Por otro lado, haces un uso un tanto complicado de la variable 'n':

* No la inicializas al principio... eso es peligroso
* Solo va a tener dos posibles estados... mejor declararla como bool
* Poner nombres significativos a las variables facilita la lectura y mantenimiento del código.

Código (cpp) [Seleccionar]

bool esPalindromo = true;

do
{
 if ( cadena[ k ] != cadena[ j ] )
   esPalindromo = false;
 else
 {
   k++;
   j--;
 }
} while ( k < j && esPalindromo ); // k != j - 1 ¿¿??


Aún con todo, insisto, hay formas más sencillas de hacer esto. Por ejemplo copiar la cadena invertida en otro buffer (o en otra instancia de tipo string) y comparar ambas cadenas (con strcmp o con el operador == según el caso )... si son iguales entonces hay palíndromo:

Código (cpp) [Seleccionar]

int main( )
{
 std::string cadena;

 std::cout << "Introduce una cadena: " << std::endl;

 std::cin >> cadena;

 std::string cadenaInvertida{ cadena.rbegin( ), cadena.rend( ) };

 if ( cadena == cadenaInvertida )
   std::cout << "Es palíndromo" << std::endl;
 else
   std::cout << "NO es palíndromo" << std::endl;
}





PD.: Este es mi mensaje nº 1.000 yuhuuuu XDDD
#250
Programación C/C++ / Re: Keyloggers en C o en C++??
3 Septiembre 2014, 09:43 AM
Cita de: patilanz en  3 Septiembre 2014, 09:32 AM
Alguien me puede explicar porque C es mejor para los keyloggers? Si puedes hacer lo mismo en c++ ??

Saludos

No hay diferencias... quizás un poco en rendimiento... pero no creo que eso sea crítico en un keylogger... no hay más que ver que poca gente alcanza las 400 ppm y eso no es ningún reto para un programa normal.