Asignaciones e inicializacion de constructores..

Iniciado por digimikeh, 22 Junio 2019, 22:05 PM

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

digimikeh

Buenas..

Me ha causado extrañeza la forma en que se puede inicializar un tipo a traves de un argumento al constructor:

Código (cpp) [Seleccionar]

struct s {
private:
int x = 0;
public:
s(int _x) : x{ _x } {}
void p() const { std::cout << x; }
int getx() const { return x; }
};

int main() {
s _s(5);                                       //forma argumento a constructor 1
s _t = 6;                                     //forma argumento a constructor 2
s _u{ 7 };                                   //forma argumento a constructor 3
s _v = { 8 };                              //forma argumento a constructor 4

std::cout << std::endl;
std::cout << _s.getx() << std::endl; //salida : 5
std::cout << _t.getx() << std::endl; //salida : 6
std::cout << _u.getx() << std::endl; //salida : 7
std::cout << _v.getx() << std::endl; //salida : 8
return 0;
}


Siempre he inicializado un tipo (que reciba un argumento de constructor) con los parentesis, es decir, tal como sale en la salida 5, es decir:
Código (cpp) [Seleccionar]

s _s(5);



Sin embargo, estaba convencido que la siguiente linea solo tenia como fin la asignacion de un valor:
Código (cpp) [Seleccionar]

s _t = 6;


Me ha parecido curioso porque se supone que una asignacion por regla general debe tener el mismo tipo que el asignado... sin embargo, el tipo s no es un entero.


Entonces me pregunto si esa linea realmente es una asignacion o lleva otro nombre....



Dungeons & dragons;
dragons.Attack();

_TTFH_3500

C++ crea de forma implicita un contructor por defecto (sin parametros) y sobrecarga el operando de asignacion en el caso de que la clase sea bastante trivial, es decir implementa lo siguiente a partir de tu codigo:

Código (cpp) [Seleccionar]

class s {
private:
int x;
public:
s() { x = 0}
s(int x1) { x = x1; }
s operator=(int x1) { x = x1; return *this; }
void p() const { std::cout << x; }
int getx() const { return x; }
};


La linea s t = 6; en el main llama al constructor por defecto s() y pone x = 0, luego llama s.operator=(6) y pone x = 6

Otra cosa que no tiene que ver con tu pregunta, NUNCA pongas uno o dos guion bajo al principio del nombre de una variable en C++, esos nombres estan reservados para las librerias del lenguaje y es una mala practica de programacion que deriva de los lenguajes no case-sensitive como Visual Basic donde para diferenciar User y user escribes _user.

Citar"Don't overlegislate naming, but do use a consistent naming convention: There are only two must-dos: a) never use "underhanded names," ones that begin with an underscore or that contain a double underscore;" (p2 , C++ Coding Standards, Herb Sutter and Andrei Alexandrescu)


digimikeh

#2
Hola, tendre en cuenta esa convención de las variables, gracias.
puede ser de la siguiente forma?:

Código (cpp) [Seleccionar]

int un_numero = 6;


o es mejor asi? :

Código (cpp) [Seleccionar]

int unNumero = 6;



Respecto a la autoasignacion del operador =, C++ lo hara para todos los tipos creados por el usuario que no haya recargado el operador = ? o tiene distinciones ?..

Saludos y gracias.

Dungeons & dragons;
dragons.Attack();

@XSStringManolo

#3
Hay formas de nombrar a la variables que suelen prevalecer, y en algunas librerías, en la documentación prácticamente te fuerzan a su manera.

Es mejor la segunda opción.
Por qué?
Porque alguna librerías suelen usar el guion bajo para separar el tipo del dato del nombre de la variable.

El un_numero se podría interpretar como una variable unsigned int. Mucho lo entederan como UnsignedNumberNumero.

En algunas librerías se nombran de esa manera, por ejemplo p_Numero lo entenderan como un puntero.

Librerías como la windows api usan la inicial para describir el tipo de variable. Por ejemplo nVariable se entiende como una variable que contiene un número. hVentana es un handler a una ventana. Etc.

A mi me gusta usar variables que empiecen en minúsculas con nombres largos que describan mejor lo que almacenan. Para las funciones uso la primera mayúscula. Para defines uso todo mayúsculas.

Asi si lees en mi codigo:
funcion variable
Funcion funcion
FUNCION macro

Sabes que siempre uso el mismo método y puedes saber que es.
En caso de sobrecargar funciones para que devuelvan datos distintos con el mismo nombre uso:
n_Funcion numero
c_Funcion caracter
s_Funcion string
...

No es importante que método uses, lo importante es que no lo cambies durante todo el código y siempre respetes esa norma a rajatabla.

Yo suelo poner nombres largos del estilo:
unsigned short int numeroDeCaracteresQueContieneElArrayescenario;
Podrias usar la abreviatura y añadir un comentario.
unsigned short int n_soae; //Size of Array escenario

Con esta forma de nombrar las variables puedes acordarte facilmente palabras largas que comentaste en la declaración de la variable:
noeiaoslistaDeAdmitidos
number of elements inside array of strings "listaDeAdmitidos".

Desde luego es preferible a llamarle a las variables a, b, c, d...
Imaginate que en la linea 4582 del código declaraste:
int a;
char b;

en la línea 4800 del código:
string c;
Mivector d {4, a};

En la linea 5430 del codigo:
string e = "ejemplo";

En la línea 6323:
string f = e;
//operaciones
Código (cpp) [Seleccionar]
if (operacion1 == operacion2)
{
e = f;
}

Y en la línea 7214 tienes:
if (a<=b || e.size() <= a && f.size() != 2 )
{
  for (int i = 3; i < e.size() || i != f.size() +a ; ++i)
  {
  i = a;
  ++a;
   e.append(f, a,( e*e.size())-a));
     while (!e.empty())
     {
      for (auto iter ....
      {
        f.append(*iter);
       e.resize((e.size()-1));
       }
      }
   }
}

Vas a tener que andar a buscar por el codigo 200 veces paea entender una funcion.
Podrias ahorrar todo ese sufrimiento al lector de codigo con un simple comentario de 2 lineas y usar nombres que representen mejor que tiene cada cosa:
/*
Este codigo llena el vectorNombresDeAlumnos del tipo creado MiVector vector<inicialesDeAlumnos>
Si el string sColegio no ha sido modificado previamente. */

_TTFH_3500

Puedes usar guion bajo siempre que no este al comienzo o hallan dos seguidos.
MAL:
int _numero;
int un__numero;
int __ARRAY_
BIEN:
int un_numero;
int esto_es_una_variable;
int estoTambien;

C++ implementa los siguientes metodos... a veces, no estoy seguro de cuando, depende de si una clase hereda de otra, es virtual o virtual pura, usa un template, etc. Igual no importa mucho, si lo usas y te da error al compilar entonces no esta definido y tienes que implementarlo vos, si te da error en la ejecucion quizas lo implemento y no hace lo que quieres y tienes que redefinirlo*.

Código (cpp) [Seleccionar]

class array {
private:
  int inicio, tamanio;
  int* datos;
public:
  // Constructor por defecto (sin parametros):
  array();
  // Constructor por copia
  array(const array&);
  // Constructor por movimiento
  array(array&&);
  // Operador de asignacion
  array operator=(const array&);
  // Operador de asignacion por movimiento
  array& operator=(array&&);
  // Destructor
  ~array();

  // NO implementa otros constructores
  array(int, int, int*);
  // NO implementa la comparacion
  bool operator==(const array&);
  bool operator<=(const array&);
};


*
Código (cpp) [Seleccionar]

// Lo que hace por defecto
array::array(const array& arr) {
  start = arr.start;
  size = arr.size;
  data = arr.data;
}

// Lo que tu quieres que haga
array::array(const array& arr) {
  start = arr.start;
  size = arr.size;
  data = new int[size];
  for (int32_t i = 0; i < size; i++)
    data[i] = arr.data[i];
}



RayR

En el caso del código que pusiste, en esta línea:

Código (cpp) [Seleccionar]
s _t = 6;


en realidad no se invoca al constructor por defecto ni al operator=. De hecho, dando  que tú ya escribiste un constructor, el compilador ya no genera la versión por defecto o sin parámetros (sólo la genera si tú no creas ninguno, o si se lo pides, con  = default). Y por supuesto, en ningún caso generaría una sobrecarga de operator= que tome como parámetro un int. El único operador de asignación que el compilador generará automáticamente toma como parámetro una referencia const (en algunos casos, no const) al mismo tipo de la clase, en este caso s.

Lo que en realidad sucede en esa línea es que se invoca al constructor que tú definiste, y se le pasa como parámetro 6. O sea, es equivalente a esto:

Código (cpp) [Seleccionar]
s _t(6);


En cuanto a tu última pregunta, en general, el compilador genera un operador de asignación para cualquier tipo que crees, salvo unas pocas excepciones (por ejemplo, si contiene alguna variable miembro no static que sea const, o referencia)

Loretz


struct s {
private:
int x = 0;
public:
s(int _x) : x{ _x } {}              //  converting constructor: inicializa un s a partir de un int
void p() const { std::cout << x; }
int getx() const { return x; }
};


Estas cuatro expresiones hacen exactamente lo mismo: invocan al "converting constructor".

s _s(5);      // crea _s a partir del int 5   
s _t = 6;      // crea _t a partir del int 6
s _u{ 7 };     // crea _u a partir del int 7 (direct-list-initialization a partir de C++11)
s _v = { 8 }; // crea _v a partir del int 8 (copy-list-initialization a partir de C++11)


En este caso las cuatro expresiones construyen e inicializan un objeto de tipo 's' a través del "converting constructor", que es el constructor que especifica la conversión desde el tipo int al tipo s.

En C++98 sólo existían las dos primeras y C++11 introduce las dos últimas, que las llama "uniform initialization", en sus dos variantes, "direct list initialization" y "copy list initialization".

Si observas la salida del programa, se mostrarán los ints 5, 6, 7, y 8 porque esos son los valores con que cada objeto fue inicializado.

Y Si usas el debugger para ejecutar paso a paso el programa, verás que cómo el flujo de ejecución pasa por ese constructor.

digimikeh

Entiendo, ya me va quedando todo mas claro... gracias por las respuestas..

Vaya Stroustup, que le gusta complicar las cosas, pero supongo que es para mejor...
Dungeons & dragons;
dragons.Attack();