Cómo leer c efectiva
Repost Recuerdo que cuando retomé "Effective C" hace algún tiempo, sentí una sensación de iluminación, así que busqué las notas que tomé cuando lo leí por primera vez. Sólo para fines de referencia y referencia. Consulte el libro "Effective C" si es necesario. por shenzi/2010.5.17
1. Acostúmbrese a C
Artículo 01: Trate a C como una federación de idiomas
Para comprender mejor C, Dividimos C en cuatro sublenguajes principales:
C. Después de todo, C todavía se basa en C. Los bloques, declaraciones, preprocesadores, tipos de datos integrados, matrices y punteros provienen de C.
C reintegrada a objeto. Esta parte es la implementación más directa de los principios clásicos del diseño orientado a objetos en C. Clases, encapsulación, herencia, polimorfismo, funciones virtuales, etc...
Plantilla C. Esta es la parte de programación genérica de C.
STL. STL es una biblioteca de plantillas. Contenedores, iteradores, algoritmos y objetos de función...
Recuerde:
Estos cuatro sublenguajes, cuando cambia de un sublenguaje a otro, hacen que las reglas de programación eficiente requieren que usted cambie su estrategia. Las reglas para una programación eficiente en C varían según la parte de C que utilice.
Elemento 02: Intente reemplazar #define con const, enum e inline
Este elemento se puede cambiar a "Preferir que el compilador reemplace el preprocesador". Es decir, utilice el menor preprocesamiento posible.
Proceso de compilación: archivo .c--preprocesamiento--gt; archivo .i--compilar--gt; archivo .o--enlace--gt; El proceso de preprocesamiento escanea el código fuente, realiza una conversión preliminar y genera un nuevo código fuente para proporcionarlo al compilador. Verifique las declaraciones y definiciones de macros que contienen directivas de preprocesamiento y transforme el código fuente en consecuencia. El proceso de preprocesamiento también elimina comentarios y espacios en blanco adicionales en el programa. Se puede ver que el proceso de preprocesamiento procesa el código fuente antes que el compilador. Las directivas de preprocesamiento son líneas de código que comienzan con el signo #.
Ejemplo: #define ASPECT_RATIO 1.653
Es posible que el compilador nunca vea el nombre del token ASPECT_RATIO o que el preprocesador lo elimine antes de que el compilador comience a procesar el código fuente. Es decir, ASPECT_RATIO ha sido reemplazado por 1.653 al compilar el código fuente. ASPECT_RATIO no se puede ingresar en la tabla de símbolos.
Reemplazo: const double AspectRatio = 1.653;
Los beneficios deberían ser: más verificación de tipos, porque #define es solo un reemplazo simple, y este reemplazo puede aparecer muchas veces en el destino. código 1.653; usar una constante en su lugar nunca producirá el mismo comportamiento.
Dos puntos a tener en cuenta sobre el reemplazo constante #define:
Definir puntero constante:
const char *authorName = "Shenzi";
cosnt std::string AuthorName("Shenzi");
Constantes específicas de clase:
static const int NumTurns = 5 //las constantes estáticas estáticas tienen solo una copia de todas; objetos.
En caso de que su compilador no permita que la "constante de clase entera estática" complete la "configuración del valor inicial en clase" (es decir, establecer el valor inicial del entero estático en la declaración de la clase), podemos use la enumeración Para compensar aumentando el tipo:
enum { NumTurns = 5 }
* Tomar una dirección constante es legal, pero tomar una dirección de enumeración es ilegal y tomar una # La dirección de define también suele ser ilegal. Si no desea que otros obtengan un puntero o referencia que apunte a una constante entera suya, enum puede ayudarlo a implementar esta restricción.
Ejemplo: #define CALL_WITH_MAX(a, b) f((a) gt; (b)) ? (a): (b))
La macro parece una función , Pero no genera la sobrecarga adicional de las llamadas a funciones, sino que es un reemplazo simple.
Reemplazo:
templatelt; typename Tgt
inline void callWithMax(cosnt T amp; a, cosnt T amp; b)
{
f(a gt; b ? a : b);
}
callWithMax es una función real, que sigue las reglas de acción y acceso. .
Recuerde:
Para constantes simples, es mejor reemplazar #defines con objetos constantes o enumeraciones.
Para macros que parecen funciones, es; En su lugar, es mejor utilizar funciones en línea que reemplacen #defines.
Ítem 03: Utilice const siempre que sea posible
Const le permite decirle al compilador y a otros programadores que un valor debe permanecer sin cambios, siempre y cuando "un valor" realmente no deba cambiarse . , entonces realmente deberías decirlo.
La palabra clave const es versátil:
Ejemplo:
char Greeting[] = "Hola"
char *p = Greeting; ; //Tanto el puntero p como la cadena apuntada se pueden cambiar;
const char *p = saludo; //El puntero p en sí se puede cambiar, como p = amp; señalado por p no puede cambiar
char * costnt p = saludo; // El puntero p no se puede cambiar, pero el objeto señalado se puede cambiar
const char * const; p = saludo; //El puntero p y el objeto al que hace referencia se pueden cambiar; no se pueden cambiar todos los objetos
Nota:
Si la palabra clave const aparece a la izquierda de; el asterisco, significa que el objeto puntiagudo es una constante. Las dos formas de escribir const char *p y char const *p tienen el mismo significado, ambas indican que el objeto es una constante;
Si la palabra clave const aparece a la derecha del asterisco, significa que el puntero en sí es una constante.
Ejemplo de STL:
const std:: vectorlt; intgt;:: interator iter = vec.begin() // Actúa como T *const, iter Error: iter es const
std::vectorlt; intgt;::const_iterator cIter = vec.begin(); // Actúa como const T*, *cIter = 10 Error: *cIter es const
Tenga en cuenta lo siguiente:
Hacer que la función devuelva un valor constante a menudo puede reducir los accidentes causados por errores del cliente sin renunciar a la seguridad y la eficiencia.
Ejemplo: const Rational operator* (const Rational amp; lhs, cosnt Rational amp; rhs
Las funciones miembro de Const hacen que la interfaz de clase sea más fácil de entender y facilitan la "operación". const Object" se considera posible;
Nota: Las funciones miembro declaradas como const no pueden cambiar las variables miembro no estáticas. Agregar mutable antes de la declaración de la variable miembro permite cambiarla en la función miembro const.
const_castlt; char amp; (static_castlt; const TextBlock amp; gt; (*this))
//static_cast convierte TextBlock amp; ;;
//const_cast eliminará la restricción constante del valor de retorno;
Recuerde:
Declarar algo como constante puede ayudar al compilador a detectar Detectar errores uso. const se puede aplicar a objetos, parámetros de funciones, tipos de retorno de funciones y cuerpos de funciones miembro en cualquier ámbito;
El compilador impone la constancia bit a bit, pero se deben usar "vehículos conceptuales" al escribir programas (constness conceptual) ;
Cuando las funciones miembro constantes y no constantes tienen implementaciones sustancialmente equivalentes, hacer que la versión no constante llame a la versión constante puede evitar la duplicación de código.
Ítem 04: Asegúrese de que el objeto esté inicializado antes de usarlo
Siempre inicialice un objeto antes de usarlo. Para los tipos integrados sin ningún miembro, debe hacerlo manualmente. En cuanto a cualquier cosa que no sean tipos integrados, la responsabilidad de la inicialización recae en el constructor, asegurando que cada constructor inicialice cada miembro del objeto.
Asignación e inicialización:
C estipula que la inicialización de las variables miembro de un objeto ocurre antes de ingresar al cuerpo del constructor. Por lo tanto, la inicialización de las variables miembro debe colocarse en la lista de inicialización del constructor.
ABEntry::ABEntry(const std::stringamp; nombre, const std::stringamp; dirección,
const std::listlt; PhoneNumbergt; amp; teléfonos)
{
theName = name; // Estas son asignaciones, no inicializaciones
theAddress = dirección; // Estas variables miembro se han llamado de forma predeterminada antes de ingresar al cuerpo de la función. El constructor y luego llama a la función de asignación,
thePhones = teléfonos; // Es decir, se requieren dos llamadas a la función.
numTimesConsulted = 0;
}
ABEntry::ABEntry(const std::stringamp; nombre, const std::stringamp; dirección,
const std:: listlt; PhoneNumbergt; amp; teléfonos)
: theName(nombre), //estas son las inicializaciones
theAddress(dirección), //estas Las variables miembro solo usan el valor correspondiente para el constructor de copia, por lo que suele ser más eficiente.
thePhones(phones),
numTimesConsulted(0)
{ }
Por lo tanto, la inicialización del tipo no integrado Las variables deben estar en Completado en la lista de inicialización para mejorar la eficiencia. Para objetos de tipo integrado, como numTimesConsulted (int), el costo de inicialización y asignación es el mismo, pero para mantener la coherencia, es mejor inicializar a través de la tabla de inicialización de miembros. Si las variables miembro son constantes o de referencia, deben requerir un valor inicial y no se pueden asignar.
C tiene un "orden de inicialización de miembros" muy fijo. Las clases base siempre se inicializan antes que las clases derivadas y las variables miembro de la clase siempre se inicializan en el orden en que se declaran. Entonces: al enumerar miembros en una lista de inicialización de miembros, es mejor ordenarlos siempre en el orden en que se declaran.
Recuerde:
Realice la inicialización manual de los objetos integrados porque C no garantiza la inicialización de los mismos.
Es mejor que los constructores utilicen listas de inicialización de miembros en lugar de hacerlo; Utilice operaciones de asignación dentro del cuerpo del constructor. Las variables miembro enumeradas en la lista de inicialización deben organizarse en el mismo orden que su declaración en la clase;
Para evitar el problema del "orden de inicialización entre unidades de compilación", reemplace non-local con local static objetos objeto estático.
2. Operaciones de construcción/destrucción/asignación
Casi todas las clases que escribas tendrán uno o más constructores, un destructor y un operador de asignación de copia.
Si estas funciones cometen errores, las consecuencias pueden ser profundas y desagradables en toda la clase. Así que asegurarse de que se comporten correctamente es una cuestión de vida o muerte.
Ítem 05: Comprenda qué funciones C escribe y llama silenciosamente
Si lo declara usted mismo, el compilador declarará (la versión del compilador) un constructor de copia para la clase, una copia operador de asignación y un destructor. Además, si no declara ningún constructor, el compilador declarará un constructor predeterminado por usted. Todas estas funciones son públicas y en línea.
Solo cuando estas funciones sean necesarias (llamadas) serán creadas por el compilador. Es decir, el compilador los creará sólo si es necesario.
El constructor y destructor predeterminados le dan principalmente al compilador un lugar para colocar código "oculto detrás de escena", como llamar al constructor y destructor de clases base y variables miembro no estáticas (obligatorio De lo contrario, ¿dónde debería? ¿Se llamarán?).
Nota: El destructor generado por el compilador no es virtual, a menos que la clase base de esta clase declare un destructor virtual.
En cuanto al constructor de copia y al operador de asignación de copia, la versión creada por el compilador simplemente copia cada variable miembro no estática del objeto de origen en el objeto de destino.
Si una clase declara un constructor (con o sin parámetros), el compilador ya no creará un constructor predeterminado para ella.
Operador de asignación de copia generado por el compilador: para variables miembro con punteros, referencias y tipos constantes, todos deberíamos considerar establecer nuestro propio operador de asignación de copia "apropiado". Debido a que los punteros que apuntan al mismo bloque de memoria son potencialmente peligrosos, las referencias no se pueden cambiar y las constantes no se pueden cambiar.
Recuerde:
El compilador puede crear en secreto constructores predeterminados, constructores de copia, operadores de asignación de copia y destructores de clases.
Artículo 06: Si no desea utilizar las funciones generadas automáticamente por el compilador, debe rechazarlas explícitamente
Generalmente, si no desea que una clase admita una clase específica habilidad, simplemente no especifique la función correspondiente. Pero esta estrategia no funciona para constructores de copias ni operadores de asignación de copias. Porque el compilador los declara "conscientemente" y los llama cuando es necesario.
Dado que las funciones generadas por el compilador son de tipo público, el constructor de copia o el operador de asignación de copia se pueden declarar como privados. Este pequeño "truco" evita que las personas lo llamen externamente, pero las funciones miembro y amigas de la clase aún pueden llamar a la función privada. La solución podría estar en una clase base diseñada específicamente para evitar la copia. (La clase proporcionada por Boost se llama no copiable).
Recuerde:
Para denegar la funcionalidad proporcionada automáticamente (e implícitamente) por el compilador, la función miembro correspondiente puede declararse como privada y no implementarse. Usar una clase base como noncopyable también es una buena idea.
Ítem 07: Declarar un destructor virtual para una clase base polimórfica
Cuando el puntero de la clase base apunta a un objeto de la clase derivada, cuando terminemos de usarlo, llamamos eliminar en él A veces, el resultado no estará definido: el componente de la clase base generalmente se destruirá, mientras que el componente de la clase derivada aún puede permanecer en el montón. Esto puede provocar fugas de recursos, estructuras de datos corruptas y consumir mucho tiempo en el depurador.
La solución a los problemas anteriores es simple: darle a la clase base un destructor virtual. Luego, eliminar el objeto de clase derivado funcionará tal como lo desee.
Cualquier clase con una función virtual casi seguramente tendrá un destructor virtual.
Si una clase no contiene una función virtual, generalmente significa que no está destinada a ser utilizada como clase base. Cuando una clase no está destinada a ser utilizada como clase base, se crea su destructor. lo virtual suele ser una mala idea. Porque la implementación de funciones virtuales requiere una sobrecarga adicional (el puntero vptr apunta a la tabla de funciones virtuales).
Los contenedores STL no tienen destructores virtuales, por lo que es mejor no derivarlos.
Recuerde:
Las clases base con propiedades polimórficas deben declarar un destructor virtual. Si una clase tiene funciones virtuales, debería tener un destructor virtual.
Si una clase no está diseñada para usarse como clase base o para tener polimorfismo, no debe declarar un destructor virtual.