Biblioteca de matriz de cadenas

Iniciado por Yoel Alejandro, 3 Marzo 2014, 18:44 PM

0 Miembros y 1 Visitante están viendo este tema.

Yoel Alejandro

Hola a todos. En esta ocasión quiero compartir un trabajo con la comunidad. También se aceptan críticas (de buena fe), sugerencias, etc ...

A veces queremos leer texto organizado en forma de tabla, con filas y columas. Por ejemplo, a partir de un fichero de texto formateado en columnas, o en el resultado de convertir una hoja de cálculo a un .txt
En tales ocasiones es común crear una matriz bidimensional de cadenas para almacenar los datos.

Como es una tarea rutinaria y repetitiva, quise desarrollar una clase "StringMatrix" de matriz de cadenas. Dicha clase posee su respectivo constructor por defecto, un constructor de copia (para obtener un objeto nuevo como copia idéntica del contenido de otro), un operador de asignación (para hacer el contenido de un StringMatrix idéntido al de otro), así como los tradicionales getElement y putElement para obtener y colocar elementos desde y en la matriz. Funcionan así:
Código (cpp) [Seleccionar]

putElement(i, j, str);

escribe el contenido del string str en la posición (i,j) de la matriz (los valores de los índices empiezan en cero). Es de hacer notar que trabaja "por copia", esto es, el elemento (i,j) reserva su espacio de memoria propio donde copia el contenido de str, luego el propio str puede alterado luego sin afectar el contenido de la matriz. Por otra parte, si se pide asignar en una posición que excede el tamaño de la matriz, se intenta dimensionar la misma previamente. Devuelve -1 en caso de error (por ejemplo, si falla la asignación de memoria), y 0 en caso de éxito.

Código (cpp) [Seleccionar]

str = getElement(i, j);

devuelve en str una copia del contenido del elemento (i,j) de la matriz. Es importante que devuelva un valor "por copia", pues así podemos alterar el string str devuelto sin afectar la matriz original.

Los métodos NRows() y NCols(), que no toman argumentos, devuelven la cantidad de filas y columnas, respectivamente, de la matriz (al crear el objeto, estos números valen cero). Por su parte, los métodos addRow() y addColumn() sirven para añadir una fila o una columna a la matriz.

Finalmente el destructor como era de esperarse libera toda la memoria dinámicamente asignada y retorna. Se cuenta con un método peculiar Reinitialize() el cual también libera la memoria asignada y pone las dimensiones de la matriz a cero. Es similar al destructor, excepto que no destruye al objeto (éste sigue vivo) sino que lo pone en las mismas condiciones de cuándo fue creado. Es como "vaciar" la matriz para poder llenarla de nuevo.

CONDICIONES DE PRECAUCIÓN: Es de hacer notar que si el objeto no se crea con éxito (falla la petición de memoria realizada), sus dimensiones se ponen en -1. Los métodos getElement() y putElement() inspeccionan primero si existe esta condición de error y en dicho caso no se ejecutan. De un modo similar los métodos addRow() y addColumn() inspecionan si la petición de memoria fue exitosa y sólo en ese caso incrementan el contador de filas o de columnas. En caso contrario, no se añade ninguna fila o columna, y las dimensiones de la matriz permanecen iguales.

En mi opinión pueden mejorarse algunas cosas, como los nombres de los métodos (quizá addCol() en lugar de addColumn()), y añadir la posibilidad de eliminar una fila o columna solamente. También incorporar un método para redimensionar, que automáticamente añada las filas o columnas que hagan falta si se pasa de una dimensión menor a una mayor, o elimine las que sobren en caso contrario.

Aquí el fichero de cabecera de la clase. Ojo: el que algunos comentarios están en inglés no significa que me lo haya copiado de internet >:(, sino que los puse en ese idioma por si navegando por el inmenso mundo que es la web, este código llega a ser leído fuera de fronteras hispanas (jeje, aunque eso requeriría mucha suerte  :laugh:)

Código (cpp) [Seleccionar]

/* SMATRIX.H
* Declaración de la clase sMatrix
* Las funciones miembro están definidas en smatrix.cpp
*
* Por Yoel Monsalve.
*/

#ifndef SMATRIX_H
#define SMATRIX_H

/*** Definición de la clase StringMatrix ***/
class StringMatrix { // matriz de cadenas de caracteres
public:
StringMatrix(); /* constructor por defecto */
StringMatrix( StringMatrix & ); /* constructor de copia */
~StringMatrix(); /* destructor */
void ReInitialize(void);
char *getElement(int, int);
int putElement(int, int, const char *);
int NRows();
int NCols();
int addRow(); /* añadir una fila: devuelve 0 si tuvo éxito, -1 si no tuvo */
int addColumn(); /* añadir una columna: devuelve 0 si tuvo éxito, -1 si no tuvo */s

StringMatrix &operator = (StringMatrix &);
private:
char ***Matrix; /* para almacenar físicamente los elementos de la matriz */
int _NRows; /* cantidad de filas */
int _NCols; /* cantidad de columnas */
};

#endif


y ahora el código fuente de la clase:

Código (cpp) [Seleccionar]

/* SMATRIX.CPP
* Definiciones de funciones miembro de la clase sMatrix.
*
* Por Yoel Monsalve.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "smatrix.h"

/* Default constructor.
* Constructor por defecto. */
StringMatrix :: StringMatrix( ) {

Matrix = (char ***) malloc( sizeof(char **) );

if (Matrix != NULL)
_NRows = _NCols = 0; /* success */
else
_NRows = _NCols = -1; /* constructor failed !!! */
}

/* Copy constructor. Creates a new object as a copy of other object.
* Constructor de copia. Crea un nuevo objeto como una copia de otro objeto. */
StringMatrix :: StringMatrix ( StringMatrix &S ) {

int i, j;

Matrix = (char ***) malloc( sizeof(char **) );
if (Matrix != NULL)
_NRows = _NCols = 0; /* success */
else {
_NRows = _NCols = -1; /* constructor failed !!! */
return;
}

/* resize at the same size of S */
if ( S.NRows() < 0 || S.NCols() < 0 ) return;
while ( _NRows < S.NRows() )
addRow();
while ( _NCols < S.NCols() )
addColumn();

printf("%d, %d\n", S.NRows(), S.NCols());
/* and copy here the content of S */
for ( i = 0; i < _NRows; i++ )
for ( j = 0; j < _NCols; j++ )
Matrix[i][j] = S.getElement(i, j);
}

/* Assignment operator, from other StringMatrix object.
* Operador de asignacióm, a partir de otro objeto StringMatrix. */
StringMatrix & StringMatrix :: operator = (StringMatrix &S) {

int i, j;

/* avoid self-assignment */
if ( &S == this ) return *this;

/* caution if matrix creation failed */
if ( _NRows < 0 || _NCols < 0 )
return *this;

/* resize at the same size of S */
if ( S.NRows() < 0 || S.NCols() < 0 ) return *this;
while ( _NRows < S.NRows() )
addRow();
while ( _NCols < S.NCols() )
addColumn();

/* and copy here the content of S */
for ( i = 0; i < _NRows; i++ )
for ( j = 0; j < _NCols; j++ )
Matrix[i][j] = S.getElement(i, j);
}

/* "Re-initializes" the object. Frees the dinamic allocated memory, and
* it puts the dimensions to zero. This is, returns he object to the original
* state like it was created.
*
* "Re-inicializa" el objeto. Libera la memoria dinámicamente asignada,
* y pone las dimensiones a cero. Es decir, devuelve el objeto al estado
* original como fue creado */
void StringMatrix :: ReInitialize( ) {

int i, j;

/* if matrix creation failed, try create it again */
if ( _NRows < 0 || _NCols < 0 ) {
Matrix = (char ***) malloc( sizeof(char **) );
if (Matrix != NULL)
_NRows = _NCols = 0; /* success */
else {
_NRows = _NCols = -1; /* constructor failed !!! */
return;
}
}

/* frees the allocated memory space */
if (_NRows > 0) {
for (i = 0; i < _NRows; i++) {
if (_NCols > 0) {
for (j = 0; j < _NCols-1; j++)
free(Matrix[i][j]);
Matrix[i][j] = NULL;
}
free(Matrix[i]);
Matrix[i] = NULL;
}
}
_NRows = _NCols = 0;
}

/* Class destructor /
* Destructor de la clase */
StringMatrix :: ~StringMatrix( ) {
int i, j;

/* frees the allocated memory assigned to the objects /
* libera el espacio de memoria asignado a los objetos */
for (i=0; i<_NRows; i++) {
for (j=0; j<_NCols; j++) {
free( Matrix[i][j] );
Matrix[i][j] = NULL;
}
free( Matrix[i] );
Matrix[i] = NULL;
}
free( Matrix );
Matrix = NULL;

_NRows = -1;
_NCols = -1;
}

/* Get the number of rows of the matriz /
* Obtener el numero de filas de la matriz */
int StringMatrix :: NRows( ) {

return _NRows;
}

/* Get the number of columns of the matriz /
* Obtener el numero de columnas de la matriz */
int StringMatrix :: NCols( ) {

return _NCols;
}

/* Get an specific element in the matrix. Warning: the indexes start in zero.
* On success is returned a copy of the string stored in such element of
* the matrix. On error, returns NULL.

/* Obtener un elemento especifico de la matriz. Cuidado: los índices empiezan
* en cero. En caso exitoso devuelve una copia de la cadena almacenada en
* dicho elemento de la matriz. En caso de error, devuelve NULL. */
char * StringMatrix :: getElement( int i, int j ) {

char *s;

/* caution if matrix creation failed */
if ( _NRows < 0 || _NCols < 0 )
return NULL;

/* must be 0 <= i <= _N_Rows - 1, and 0 <= j <= _NCols - 1 */
if (i < 0 || i >= _NRows || j < 0 || j >= _NCols)
return NULL;

if ( ( s = (char *) malloc ( (strlen(Matrix[i][j]) + 1) * sizeof(char) ) ) == NULL )
return NULL;

strcpy(s, Matrix[i][j]);
return s;
}

/* Puts an element in a specified position of the matrix. If the position
* exceed the size of matrix, the same is automatically resized.
* On success returns 0, and -1 otherwise.
*
* Pone un elemento en una posicion especificada de la matriz. Si la posicion
* excede el tamano de la matriz, la misma es redimensionada automaticamente.
* Devuelve 0 si tuvo exito, -1 si no tuvo. */
int StringMatrix :: putElement( int i, int j, const char * s ) {

char *cPtr;

/* caution if matrix creation failed */
if ( _NRows < 0 || _NCols < 0 )
return -1;

/* fail if i < 0, or j < 0 */
if ( i < 0 | j < 0 )
return -1;

/* if i > _NRows - 1, completed rows that are needed */
while ( i >= _NRows )
addRow();

/* if j > _NCols - 1, completed columns that are needed */
while ( j >= _NCols )
addColumn();

/* now, copies the s string in the (i,j) element of the matrix (allocates
* memory before) */
if ( ( cPtr = (char *) realloc( Matrix[i][j], (strlen(s) + 1) * sizeof(char) ) ) != NULL ) {
strcpy(cPtr, s);
Matrix[i][j] = cPtr;
return 0;
}
else
return -1;

}

/* Add a row to the matrix. On success returs 0, and -1 otherwise.
*
* Anade una fila a la matriz. Devuelve 0 si tuvo éxito, -1 si no tuvo. */
int StringMatrix :: addRow() {

char ***xPtr;
char **yPtr;
char *zPtr;
int j;

/* caution if matrix creation was failed */
if ( _NRows < 0 ) return -1;

/* allocate memory for one more row */
if ( ( xPtr = (char ***) realloc( Matrix, (_NRows + 1) * sizeof(char **) ) ) != NULL ) {
Matrix = xPtr;

/* if matrix already has columns added */
if ( _NCols > 0 ) {
/* complete the new row with empty elements */
if ( ( yPtr = (char **) malloc( _NCols * sizeof(char *) ) ) != NULL ) {
Matrix[_NRows] = yPtr;
for (j = 0; j < _NCols; j++) {
if ( ( zPtr = (char *) malloc( sizeof(char) ) ) != NULL ) {
Matrix[_NRows][j] = zPtr;
*zPtr = '\0';
}
else
return -1;
}
}
else
return -1;
}
/* otherwise */
else {
/* complete the new row with empty elements */
if ( ( yPtr = (char **) malloc( 1 * sizeof(char *) ) ) != NULL ) {
Matrix[_NRows] = yPtr;
if ( ( zPtr = (char *) malloc( sizeof(char) ) ) != NULL ) {
Matrix[_NRows][0] = zPtr;
*zPtr = '\0';
}
else
return -1;
}
else
return -1;
}
}
else
return -1;

/* Success, then increase _NRows and return. */
_NRows++;
return 0;
}

/* Add a column to the matrix. On success returs 0, and -1 otherwise.
*
/* Anade una columna a la matriz. Devuelve 0 si tuvo éxito, -1 si no tuvo. */
int StringMatrix :: addColumn() {

char **yPtr;
char *zPtr;
int i;

/* return if matrix creation was failed */
if (_NRows <= 0 || _NCols < 0) return -1;

for (i = 0; i < _NRows; i++) {
/* allocate memory for one more column */
if ((yPtr = (char **) realloc( Matrix[i], (_NCols + 1) * sizeof(char *) ) ) != NULL ) {
Matrix[i] = yPtr;
if ( ( zPtr = (char *) malloc( sizeof(char) ) ) != NULL ) {
Matrix[i][_NCols] = zPtr;
*zPtr = '\0';
}
else
return -1;
}
else
return -1;
}

/* Success, then increase _NCols and return. */
_NCols++;
return 0;
}


Podemos ver ahora su funcionamiento, en el sencillo archivo de prueba que crea dos matrices, llena una con cuatro cadenas. Luego, en la segunda matriz se copia el contenido de la primera, y éste se imprime por la pantalla ¡Fácil!

Código (cpp) [Seleccionar]

#include <stdio.h>
#include <smatrix.h>       /* fichero de cabecera de la clase */

int main() {

StringMatrix S, M;
int i, j;

S.putElement(0, 0, "hugo");
S.putElement(0, 1, "paco");
S.putElement(1, 0, "maria");
S.putElement(1, 1, "luisa");

/* copia contenido de S en M */
M = S;
/* y lo imprime en pantalla */
for (i = 0; i < 2; i++) {
for (j = 0; j < 2; j++)
printf("%s\t", M.getElement(i, j) );
fputs("\n", stdout);
}

return 0;
}


NOTA: para compilar el fichero de prueba, llamado por ejemplo test.cpp, enlazado con el fichero smatrix.cpp que define la clase StringMatrix, usar la orden:

g++ -o test test.cpp smatrix.cpp

y suponiendo que los tres archivos test.cpp, smatrix.cpp y smatrix.h están en el mismo directorio. Para un proyecto grande valdría la pena crear directorios src, include, lib donde poner los ficheros apropiados y compilar adecuadamente según el caso. Se puede también hacer una biblioteca con esta y otras clases de utilería, ustedes ya saben ...

Sugerencias, comentarios ?????
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

eferion

Yo aportaría algunos comentarios:

* En el destructor no tiene sentido que pongas NRows y NCols a -1... al salir del destructor esas variables pierden todo su sentido.

* Si estás haciendo clases, es decir, C++, por qué no usar al menos la clase string??

* Lo mismo sería aplicable para el tipo booleano. Es más natural usar bool que int.

* Si usas clases de C++ el código queda más limpio y corto:

Código (cpp) [Seleccionar]

#ifndef STRINGMATRIX_H
#define STRINGMATRIX_H

#include <string>
#include <map>

class StringMatrix
{
  public:
    StringMatrix( ); /* constructor por defecto */
    StringMatrix( const StringMatrix& ); /* constructor de copia */
    ~StringMatrix( ); /* destructor */

    void ReInitialize( );
    std::string getElement( unsigned int row, unsigned int col) const;
    void putElement( unsigned int row, unsigned int col, const std::string& string );
    unsigned int NRows( ) const;
    unsigned int NCols( ) const;
    void addRow( ); /* añadir una fila */
    void addColumn( ); /* añadir una columna */

    StringMatrix& operator=( const StringMatrix& );

  private:

    std::map< std::pair< unsigned int, unsigned int >, std::string > _matrix;
    unsigned int _NRows; /* cantidad de filas */
    unsigned int _NCols; /* cantidad de columnas */
};

#endif // STRINGMATRIX_H


Código (cpp) [Seleccionar]

#include "StringMatrix.h"

/* Default constructor.
* Constructor por defecto. */
StringMatrix::StringMatrix( )
  : _NRows( 0 ),
    _NCols( 0 )
{
}

/* Copy constructor. Creates a new object as a copy of other object.
* Constructor de copia. Crea un nuevo objeto como una copia de otro objeto. */
StringMatrix::StringMatrix( const StringMatrix &S )
  : _matrix( S._matrix ),
    _NRows( S._NRows ),
    _NCols( S._NCols )
{
}

/* Assignment operator, from other StringMatrix object.
* Operador de asignacióm, a partir de otro objeto StringMatrix. */
StringMatrix& StringMatrix::operator=( const StringMatrix &S )
{
  _matrix = S._matrix;
  _NRows = S._NRows;
  _NCols = S._NCols;

  return *this;
}

/* "Re-initializes" the object. Frees the dinamic allocated memory, and
* it puts the dimensions to zero. This is, returns he object to the original
* state like it was created.
*
* "Re-inicializa" el objeto. Libera la memoria dinámicamente asignada,
* y pone las dimensiones a cero. Es decir, devuelve el objeto al estado
* original como fue creado */
void StringMatrix::ReInitialize( )
{
  _matrix.clear( );
  _NRows = 0;
  _NCols = 0;
}

/* Class destructor /
* Destructor de la clase */
StringMatrix::~StringMatrix( )
{
}

/* Get the number of rows of the matriz /
* Obtener el numero de filas de la matriz */
unsigned int StringMatrix::NRows( ) const
{
  return _NRows;
}

/* Get the number of columns of the matriz /
* Obtener el numero de columnas de la matriz */
unsigned int StringMatrix::NCols( ) const
{
  return _NCols;
}

/* Get an specific element in the matrix. Warning: the indexes start in zero.
* On success is returned a copy of the string stored in such element of
* the matrix. On error, returns NULL.

* Obtener un elemento especifico de la matriz. Cuidado: los índices empiezan
* en cero. En caso exitoso devuelve una copia de la cadena almacenada en
* dicho elemento de la matriz. En caso de error, devuelve NULL. */
std::string StringMatrix::getElement(
          unsigned int row,
          unsigned int col ) const
{
  std::string to_return;

  if ( row < _NRows && col < _NCols )
    to_return = _matrix.at( std::make_pair( row, col ) );

  return to_return;
}

/* Puts an element in a specified position of the matrix. If the position
* exceed the size of matrix, the same is automatically resized.
* On success returns 0, and -1 otherwise.
*
* Pone un elemento en una posicion especificada de la matriz. Si la posicion
* excede el tamano de la matriz, la misma es redimensionada automaticamente.
* Devuelve 0 si tuvo exito, -1 si no tuvo. */
void StringMatrix::putElement(
          unsigned int row,
          unsigned int col,
          const std::string& string )
{
  if ( _NRows <= row )
    _NRows = row + 1;

  if ( _NCols <= col )
    _NCols = col + 1;

  _matrix[ std::make_pair( row, col ) ] = string;
}

/* Add a row to the matrix. On success returs 0, and -1 otherwise.
*
* Anade una fila a la matriz. Devuelve 0 si tuvo éxito, -1 si no tuvo. */
void StringMatrix::addRow( )
{
  _NRows++;
}

/* Add a column to the matrix. On success returs 0, and -1 otherwise.
*
* Anade una columna a la matriz. Devuelve 0 si tuvo éxito, -1 si no tuvo. */
void StringMatrix::addColumn( )
{
  _NCols++;
}


Código (cpp) [Seleccionar]

#include <iostream>
#include "StringMatrix.h"

int main()
{
  StringMatrix S;
  S.putElement(0, 0, "hugo");
  S.putElement(0, 1, "paco");
  S.putElement(1, 0, "maria");
  S.putElement(1, 1, "luisa");

  /* copia contenido de S en M */
  StringMatrix M = S;
 
  /* y lo imprime en pantalla */
  for (unsigned int i = 0; i < M.NRows( ); i++)
  {
    for (unsigned int j = 0; j < M.NCols( ); j++)
      std::cout << M.getElement(i, j) << "\t";
    std::cout << std::endl;
  }

  return EXIT_SUCCESS;
}


Yoel Alejandro

#2
Bueno eferion, aquí la respuesta es cuestión de gustos. Ambas soluciones son funcionales, por supuesto que usando plantillas STL el código es más breve y fácil para el programador, pero por otra parte hay cierta "sobrecarga", al invocar reiterativamente otras clases y sus métodos.

Claro que las clases STL están optimizadas para aumentar la velocidad de ejecución, pero mi intención aquí fue proponer una clase de matrices que usara sólo los más métodos más básicos, y sin invocar a otras clases. Pero al fin ahora tenemos dos enfoques diferentes para el mismo problema ...
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

eferion

Apuesto a que tu código es más rápido, dado que al no usar clases se optimiza algo mejor... pero tiene la contrapartida de que es bastante más complicado de modificar. Un fallo tonto puede conllevar lagunas de memoria, accesos a memoria no reservada o desbordamientos de buffer.

Además, si estás programando en C++ lo lógico es que uses, al menos, la clase string, ya que la propia STL te lo pide a gritos.

En programación, al menos la parte que afecta a empresas, salvo en casos muy concretos, no se pide ni reinventar la rueda ni recurrir a códigos complicados de mantener... más bien se buscan códigos sencillos y fáciles de mantener (buscando que con ello no se penalice en exceso el rendimiento ). Y teniendo en cuenta que el 99% de los procesos no son críticos en el tiempo... pues como que ganar un poco de velocidad no compensa.

Mi propuesta se basa en aprovechar precisamente esas bazas. Tu propuesta queda genial si la orientas al lenguaje C ( claro que tendrías que sustituir la clase por funciones sueltas )... en C++ sería mucho más recomendable optar por mi opción.

Pero al final es lo que tú has dicho. Son dos puntos de vista diferentes :)

Yoel Alejandro

Totalmente de acuerdo, y con respecto a " lagunas de memoria, accesos a memoria no reservada o desbordamientos de buffer", te cuento que sí que tuve muchos dolores de cabeza con eso, pues me ocurría en las primeras versiones del código.

La verdad, este fue un ejercicio más que todo para aprender, pues como dices en una aplicación real lo más lógico es usar STL pues se vuelve más fácil de comprender y mantener.

Hacía tiempo quise ponerme a "inventar", y emular clases con C pero me encontré algunos problemas. Por ejemplo, tengamos la clase circle (círculo) con sus miembros x, y, radious, cuyos significados son obvios. Ahora queremos definir el método setX para cambiar el valor del miembro x. Claro, no son clases verdaderas, sino la emulación de éstas en C.

En C++ las clases mantienen su espacio privado de nombres de métodos, por lo que debemos usar el operador :: para acceder a ellos. No sucede lo mismo en C, donde hay que usar funciones "sueltas". Una idea sería prefijar los nombres de los métodos con el nombre de la clase, algo como:

CIRCLE_setX( double new_x, circle * this)

aunque aún es un poco burdo. No se si es lo mejor que se puede hacer a costa de mantenerse puro dentro de C, o se puede mejorar un poquito.
¿Acaso podemos ser creativos e inventar algo con #define para volverlo más amigable?

A ver, ¿cómo hacían los programadores antiguos, cuando no había aparecido C++?
Saludos, Yoel.
P.D..-   Para mayores dudas, puedes enviarme un mensaje personal (M.P.)

eferion

Cita de: yoel_alejandro en  9 Marzo 2014, 23:59 PM
En C++ las clases mantienen su espacio privado de nombres de métodos, por lo que debemos usar el operador :: para acceder a ellos.

En C++ sólo es necesario ( como norma general ) usar el operador '::' en los siguientes casos:

* Implementar los métodos de una clase. Esto es así porque la implementación puede estar en cualquier parte del código ( no necesariamente en circle.cpp ) y claro, puede existir otra función setX de, perteneciente, por ejemplo, a la clase Rectangle.

Código (cpp) [Seleccionar]

void Circle::setX( double x )
{
  ...
}


* Llamar a un método del padre que ha sido sobreescrito: Cuando sobreescribes un método virtual, el método del padre deja de estar disponible salvo que el nuevo método así lo requiera. Por tanto, si se requiere ejecutar dicho código hay que llamar a la función indicándole que el ámbito es el del padre.

Código (cpp) [Seleccionar]

class GeometricObj
{
  public:
    virtual void setX( double x );
};

class Circle : public GeometricObj
{
  public:
    virtual void setX( double x ) override;
};

void  Circle::setX( double x )
{
  GeometricObj::setX( double x );
}


* Para acceder a miembros estáticos. Como en C++ el  mismo nombre puede ser utilizado en diferentes clases, para poder acceder a un miembro estático es necesario indicar el ámbito en el que "existe" dicha función.

Código (cpp) [Seleccionar]

class POO
{
  public:
    static int Func( );
}

int value = POO::Func( );


* Acceder a enumerados definidos dentro de una clase. Los enumerados se tratan en este caso igual que los miembros estáticos. Están definidos dentro de una clase y no es posible acceder a ellos a través de una instancia de la clase, luego la forma de recuperar esos valores es usando el operador de ámbito:

Código (cpp) [Seleccionar]


class Animal
{
  public:
    enum Type
    {
      Carnivorous,
      Herbivorous
    };
};

int value = Animal::Carnivorous;


Yo creo que estas son los motivos más comunes en los que es necesario utilizar el operador de ámbito y son casos que están totalmente justificados.

Cita de: yoel_alejandro en  9 Marzo 2014, 23:59 PM
aunque aún es un poco burdo. No se si es lo mejor que se puede hacer a costa de mantenerse puro dentro de C, o se puede mejorar un poquito.

Realmente, si estás programando en C++ no tienes que ser purista C... para eso programas en C directamente... es como intentar ser un purista del latín mientras hablas español... Tendría más sentido intentar ser purista C++ mientras programas C++.

Cita de: yoel_alejandro en  9 Marzo 2014, 23:59 PM
¿Acaso podemos ser creativos e inventar algo con #define para volverlo más amigable?

¿Más amigable?

A mi la forma normal de usar un objeto me parece de lo más amigable:

Código (cpp) [Seleccionar]

class Circle
{
  public:
    Circle( );

    void SetX( double x );
    void SetY ( double y );
    void SetRadius( double radius );
    void Paint( );

  private:
    double _x;
    double _y;
    double _radius;
    InternalPtr* _ptr; // Para representar una reserva de memoria dinamica
};

Circle circle;
circle.SetX( 12.5 );
circle.SetY( 10.4 );
circle.SetRadius( 200.25 );
circle.Paint( );
;

De hecho, me resulta más amigable que el "equivalente en C":


struct circle_t
{
  double x;
  double y;
  double radius;
  InternalPtr* ptr;
};

typedef circle_t Circle;

Circle circle;
Circle_CreateStruct( &circle ); // inicializacion de la estructura
circle.x = 12.5;
circle.y = 10.4;
circle.radius = 200.25;
Circle_Paint( &circle );
Circle_DeleteStruct( &circle ); // Liberar la memoria reservada previamente.


Cita de: yoel_alejandro en  9 Marzo 2014, 23:59 PM
A ver, ¿cómo hacían los programadores antiguos, cuando no había aparecido C++?

¿Y cómo lo hacían cuando no existía C? Pues tenían que apañarse con ensamblador o, más divertido aún, con tarjetas perforadas... C++ es un lenguaje más moderno y, controlándolo un poco, se puede hacer un trabajo más rápido que con C. Se sacrifica algo de rendimiento pero se gana algo de usabilidad y mantenimiento. Si optas por dar otro paso y usar C#... sacrificas bastante más rendimiento pero el mantenimiento es mucho más sencillo y la programación es muchísimo más rápida.

Obviamente si una herramienta no está disponible no puedes usarla... incluso si no sabes que existe ni te planteas ese supuesto.

Sin embargo si vas a usar un lenguaje que te aporta algo diferente respecto a su antecesor ( caso de C++ y C )... quizás deberías plantearte su uso para poder sacarle más partido al lenguaje. Desde mi punto de vista programar en C++ como si fuese C es una pérdida de tiempo. Puede haber partes del código que en un momento dado haya que programarlas usando la sintaxis de C... pero ya te digo yo que esos casos se pueden contar con los dedos de una mano de los simpsons ( que tienen menos dedos que nosotros ).

Excluyo de este comentario todo código que se realice con objeto de aprender, entender y/o dominar determinadas técnicas... Esos son pasos necesarios para entender el por qué de las cosas y para ello hay que mancharse las manos :)