Hasta que la muerte nos separe de la estática en C ++





Hola. En una de las revisiones de código, me encontré con la idea de que muchos, y qué esconderme, no es que comprendamos bien cuándo usar la palabra clave estática . En este artículo, me gustaría compartir mi conocimiento e información sobre la palabra clave estática . , , ++. /++ . , static ++, . ++, , , , .



static?



Estático es una palabra clave de C ++ que se utiliza para dar a un elemento una característica especial. Para los elementos estáticos, la memoria se asigna solo una vez y estos elementos existen hasta que el programa termina. Todos estos elementos se almacenan no en el montón o en la pila, sino en segmentos de memoria especiales llamados .data y .bss (dependiendo de si los datos estáticos se inicializan o no). La siguiente imagen muestra un diseño típico para la memoria del programa.





¿Dónde se usa?



A continuación se muestra un diagrama de cómo y dónde se usa la estática en un programa.







Y ahora intentaré describir en detalle todo lo que se muestra en el diagrama. ¡Vamos!



Variables estáticas dentro de una función



Las variables estáticas, cuando se usan dentro de una función, se inicializan solo una vez y luego conservan su valor. Estas variables estáticas se almacenan en un área de memoria estática ( .data o .bss ) en lugar de en la pila, lo que permite que el valor de la variable se almacene y utilice durante toda la vida del programa. Echemos un vistazo a dos programas casi idénticos y su comportamiento. La única diferencia es que uno usa una variable estática y el otro no.



Primer programa:



#include <iostream>

void counter() {
  static int count = 0; //  4
  std::cout << count++;
}

int main() {
  for (int i = 0; i < 10; ++i) {
    counter();
  }
  return 0;
}


Salida del programa:

0123456789


Segundo programa:



#include <iostream>

void counter() {
  int count = 0; //  4
  std::cout << count++;
}

int main() {
  for (int i = 0; i < 10; ++i) {
    counter();
  }
  return 0;
}


Salida del programa:

000000000


Si no usa static en la línea 4 , la asignación de memoria y la inicialización de la variable count ocurre cada vez que se llama a counter () y se destruye cada vez que sale de la función. Pero si hacemos que la variable sea estática, después de la inicialización (la primera vez que se llama a la función counter () ), count tendrá un alcance al final de la función main () , y la variable mantendrá su valor entre las llamadas a counter () .



Objetos de clase estáticos



Un objeto estático de una clase tiene las mismas propiedades que una variable estática regular descrita anteriormente, es decir, almacenado en un segmento de memoria .data o .bss , creado al inicio y destruido al finalizar el programa, y ​​se inicializa solo una vez. El objeto se inicializa como de costumbre, a través del constructor de clases. Consideremos un ejemplo con un objeto de clase estático.



#include <iostream>

class Base { //  3
public:
  Base() { //  5
    std::cout << "Constructor" << std::endl;
  }
  ~Base() { //  8
    std::cout << "Destructor" << std::endl; 
  }
};

void foo() { 
  static Base obj; //  14
} //  15

int main() {
  foo(); //  18
  std::cout << "End of main()" << std::endl;
  return 0;
}


Salida del programa:

Constructor

Fin de principal ()

Destructor


3 Base ( 5) ( 8). . 14 obj Base. foo() 18.



- , , foo() 15, , .. . , , .





#include <iostream>

class Base {
public:
  Base() {
    std::cout << "Constructor" << std::endl;
  }
  ~Base() { 
    std::cout << "Destructor" << std::endl; 
  }
};

void foo() { 
  Base obj; 
} //  15

int main() {
  foo();
  std::cout << "End of main()" << std::endl;
  return 0;
}


Si eliminamos static al crear una variable en la función foo () , entonces la destrucción del objeto ocurrirá en la línea 15 cada vez que se llame a la función. En este caso, la salida del programa será bastante esperada para una variable local con memoria asignada en la pila:

Constructor

Destructor

Fin de principal ()




Miembros de la clase estática



En comparación con los casos de uso anteriores, los miembros estáticos de una clase son un poco más difíciles de entender. Veamos por qué. Supongamos que tenemos el siguiente programa:



#include <iostream>

class A { //  3
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }
};

class B { //  9
public:
  B() { std::cout << "Constructor B" << std::endl; }
  ~B() { std::cout << "Destructor B" << std::endl; }

private:
  static A a; //  15 ()
};

int main() {
  B b; //  19
  return 0;
}


En nuestro ejemplo, creamos la clase A (línea 3) y la clase B (línea 9) con miembros de clase estáticos ( línea 15 ). Suponemos que la creación del objeto b en la línea 19 creará el objeto a en la línea 15 . Este sería el caso si usáramos miembros no estáticos de la clase. Pero la salida del programa será la siguiente:

Constructor B

Destructor B


La razón de este comportamiento es que los miembros estáticos de una clase no se inicializan con un constructor porque no dependen de la inicialización del objeto. Aquellos. en la línea 15, solo estamos declarando el objeto, no definiéndolo, ya que la definición debe ocurrir fuera de la clase usando el operador de resolución de alcance (: :) . Vamos a definir un miembros de la clase B .



#include <iostream>

class A {
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }
};

class B {
public:
  B() { std::cout << "Constructor B" << std::endl; }
  ~B() { std::cout << "Destructor B" << std::endl; }

private:
  static A a; //  15 ()
};

A B::a; //  18 ()

int main() {
  B b;
  return 0;
}


Ahora, después de haber definido nuestro miembro de clase estática en la línea 18, podemos ver el siguiente resultado del programa:

Constructor A

Constructor B

Destructor B

Destructor A


Debe recordarse que el miembro de la clase será el mismo para todas las instancias de la clase B , es decir si creamos tres objetos de la clase B , entonces el constructor del miembro de la clase estática se llamará solo una vez. Aquí hay un ejemplo de lo que estoy hablando:



#include <iostream>

class A {
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }
};

class B {
public:
  B() { std::cout << "Constructor B" << count++ << std::endl; }
  ~B() { std::cout << "Destructor B" << --count << std::endl; }

private:
  static A a; // 
  static int count; // 
};

A B::a; // 
int B::count = 1; // 

int main() {
  B b1, b2, b3;
  return 0;
}


Salida del programa:

Constructor A

Constructor B1

Constructor B2

Constructor B3

Destructor B3

Destructor B2

Destructor B1

Destructor A


Funciones estáticas



Las funciones estáticas llegaron a C ++ desde C. De forma predeterminada, todas las funciones en C son globales, y si desea crear dos funciones con el mismo nombre en dos archivos .c (.cpp) diferentes del mismo proyecto, obtendrá un error que indica que esta función ya definido ( error fatal LNK1169: se encontraron uno o más símbolos de definición múltiple ). A continuación se muestra una lista de tres archivos de un programa.



// extend_math.cpp
int sum(int a, int b) {
  int some_coefficient = 1;
  return a + b + some_coefficient;
}


// math.cpp
int sum(int a, int b) {
  return a + b;
}


// main.cpp
 int sum(int, int); // declaration

int main() {
  int result = sum(1, 2);
  return 0;
}


Para solucionar este problema, declararemos una de las funciones estática. Por ejemplo este:



// extend_math.cpp
static int sum(int a, int b) {
  int some_coefficient = 1;
  return a + b + some_coefficient;
}


, , . sum() math.cpp . , static , , , , , (.h).



, inline static, , . (.cpp), #include , . , , .. include .cpp .





- ()



Puede utilizar una función miembro estática sin crear un objeto de la clase. Se accede a las funciones estáticas utilizando el nombre de la clase y el operador de resolución de alcance (: :) . Cuando se usa una función miembro estática, existen limitaciones como:



  1. Dentro de una función, solo puede acceder a miembros de datos estáticos, otras funciones de miembros estáticos y cualquier otra función fuera de la clase.
  2. Las funciones miembro estáticas tienen el alcance de la clase en la que residen.
  3. No tiene acceso al puntero this de la clase, porque no creamos ningún objeto para llamar a esta función.


Echemos un vistazo al siguiente ejemplo:



#include <iostream>

class A {
public:
  A() { std::cout << "Constructor A" << std::endl; }
  ~A() { std::cout << "Destructor A" << std::endl; }

  static void foo() { //  8
    std::cout << "static foo()" << std::endl;
  }
};

int main() {
  A::foo(); //  14
  return 0;
}


En la clase A, en la línea 8 , tenemos una función miembro estática foo () . En la línea 14 , llamamos a la función usando el nombre de la clase y el operador de resolución de alcance, y obtenemos el siguiente resultado del programa:

foo estático ()


En la salida, puede ver que no hay creación de objetos y no se llama a ningún constructor / destructor.



Si el método foo () no fuera estático, el compilador arrojaría un error en la expresión en la línea 14 , porque necesita crear un objeto para acceder a sus métodos no estáticos.





Conclusión



« static , ». , , .



:



  • , . , , , . , , .
  • , , .. , . , , .. .
  • static Singleton, , . , -. Singleton , , .
  • A veces, para que una función se ejecute solo una vez sin almacenar el estado anterior en algún lugar del objeto, se utilizan variables estáticas. Puede ver un ejemplo en la sección "Variables estáticas dentro de una función". Pero este no es un enfoque muy bueno y puede llevar a largas horas de búsqueda de errores si está utilizando subprocesos múltiples.
  • En la práctica, los programadores de C ++ suelen utilizar funciones miembro estáticas como alternativa a las funciones normales que no requieren la creación de un objeto para ejecutarse.


Espero que haya disfrutado de mi artículo sobre la palabra clave estática en C ++. Estaría encantado de recibir críticas y consejos. ¡Gracias a todos!



All Articles