#incluye #incluye "complejo.h"
usando el espacio de nombres estándar;
ostream& operador << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ')' }
int principal() { complejo c1(2, 1); complejo c2(4, 0);
cout<<c1<<endl;cout<<c2<<endl;
cout << c1+c2 << endl; cout << c1-c2 << endl; cout << c1*c2 << cout << c1 / 2 << endl;
cout << conj(c1) << endl; cout << norma(c1) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << endl;
cout << (c2 - 2) << endl; cout << (5 + c2) << endl;
devolver 0; }
Como se muestra en la figura anterior, es necesario verificar la autoasignación al sobrecargar el operador de asignación "=", por las siguientes razones:
Si no hay detección de autoasignación, se liberará el m_data del propio objeto y el contenido señalado por m_data no existirá, por lo que habrá problemas con la copia.
Aquí hay dos clases para explicación:
class complex
{
public:
complex (double r = 0, double i = 0)
: re (r), im (i)
{ }
complex& operator += (const complex&);
double real () const { return re; }
double imag () const { return im; }
private:
double re, im;
friend complex& __doapl (complex*,
const complex&);
};
class String
{
public:
String(const char* cstr = 0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
Después de crear estos dos objetos, el compilador (VC) asigna memoria a los dos objetos de la siguiente manera:
Los dos de la izquierda son la asignación de memoria del compilador del complejo de clases en modo de depuración y modo de lanzamiento. En el modo de depuración, el compilador inserta la cabeza y la cola (parte roja), la parte de información de tamaño 4 * 8 + 4 (parte gris) en la memoria del objeto complejo. La parte verde es el espacio realmente ocupado por el objeto complejo. solo hay una sección de 52 palabras, pero el VC está alineado con 16 bytes, por lo que el múltiplo de 52 más cercano es 64, y el espacio de 12 bytes debe llenarse (parte del teclado cian). Para el objeto complejo en la parte de lanzamiento, solo se agregan el encabezado de información y la parte del encabezado. El análisis de la clase de cadena es básicamente el mismo.
A continuación, veamos cómo el compilador VC asigna objetos de matriz:
De manera similar, el compilador agrega información redundante al objeto. Para objetos de clase complejos, dado que la matriz tiene tres objetos, hay 8 dobles, luego el compilador inserta "3" delante de los tres objetos complejos para marcar el número de objetos individuales. . El método de análisis de la clase String también es similar.
La siguiente imagen ilustra por qué eliminar un objeto de matriz requiere el método eliminar []:
Cadena.h
#ifndef __MYSTRING__
#define __MYSTRING__
class String
{
public:
String(const char* cstr=0);
String(const String& str);
String& operator=(const String& str);
~String();
char* get_c_str() const { return m_data; }
private:
char* m_data;
};
#include <cstring>
inline
String::String(const char* cstr)
{
if (cstr) {
m_data = new char[strlen(cstr)+1];
strcpy(m_data, cstr);
}
else {
m_data = new char[1];
*m_data = ' ';
}
}
inline
String::~String()
{
delete[] m_data;
}
inline
String& String::operator=(const String& str)
{
if (this == &str)
return *this;
delete[] m_data;
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
return *this;
}
inline
String::String(const String& str)
{
m_data = new char[ strlen(str.m_data) + 1 ];
strcpy(m_data, str.m_data);
}
#include <iostream>
using namespace std;
ostream& operator<<(ostream& os, const String& str)
{
os << str.get_c_str();
return os;
}
#endif
string_test.cpp
#include "string.h"
#include <iostream>
using namespace std;
int main()
{
String s1("hello");
String s2("world");
String s3(s2);
cout << s3 << endl;
s3 = s1;
cout << s3 << endl;
cout << s2 << endl;
cout << s1 << endl;
}
En C++, un constructor de un parámetro (o un constructor de múltiples parámetros con valores predeterminados para todos los parámetros excepto el primer parámetro) desempeña dos funciones. 1 es un constructor, 2 es un operador de conversión de tipo predeterminado e implícito.
Por lo tanto, a veces, cuando escribimos código como AAA = XXX, y el tipo de XXX resulta ser el tipo de parámetro del constructor de un solo parámetro AAA, el compilador llamará automáticamente a este constructor para crear un objeto AAA.
Esto se ve genial y muy conveniente. Pero en algunos casos (vea el ejemplo autorizado a continuación), va en contra de nuestras intenciones originales (de los programadores). En este momento, debe agregar una modificación explícita delante del constructor para especificar que este constructor solo se puede llamar/usar explícitamente y no se puede usar implícitamente como un operador de conversión de tipos.
El constructor explícito se utiliza para evitar conversiones implícitas. Por favor mire el código a continuación:
class Test1
{
public:
Test1(int n)
{
num=n;
}//普通构造函数
private:
int num;
};
class Test2
{
public:
explicit Test2(int n)
{
num=n;
}//explicit(显式)构造函数
private:
int num;
};
int main()
{
Test1 t1=12;//隐式调用其构造函数,成功
Test2 t2=12;//编译错误,不能隐式调用其构造函数
Test2 t2(12);//显式调用成功
return 0;
}
El constructor de Test1 toma un parámetro int y la línea 23 del código se convertirá implícitamente para llamar al constructor de Test1. El constructor de Test2 se declara como explícito, lo que significa que no se puede llamar al constructor mediante conversión implícita, por lo que se producirá un error de compilación en la línea 24 del código.
Los constructores ordinarios se pueden llamar implícitamente. El constructor explícito sólo se puede llamar explícitamente.
Cuando es necesario convertir cualquier fracción a tipo doble, se llama automáticamente a la función double() para la conversión. Como se muestra en la figura anterior, el compilador determina que 4 es un número entero durante el análisis de doble d = 4 + f, y luego continúa determinando f. Se observa que f proporciona la función doble () y luego realiza la función doble. () operación en f, y calcula 0.6, luego lo suma a 4 y finalmente obtiene el tipo doble 4.6.
En la figura anterior se define una clase, llamada Fracción. El operador "+" está sobrecargado en la clase. Durante la operación f+4, el compilador convierte implícitamente "4" en un objeto Fracción y luego lo pasa. a través de Fraction El operador "+" sobrecargado participa en la operación.
Como se muestra en la figura anterior, la función double () se agrega a Fraction, que divide las dos variables miembro de Fraction y luego las convierte a la fuerza a tipo doble y devuelve el resultado. Durante el proceso de sobrecarga f + 4, el compilador lo hará. informar un error. Puede realizar el siguiente análisis:
1. Primero, 4 se convierte implícitamente (constructor) en un objeto Fracción, y luego el operador "+" sobrecargado se opera con "f" para devolver un objeto Fracción;
2. Primero, 4 se convierte implícitamente (constructor) en un objeto Fracción, y luego se realiza una operación doble en el par mediante el operador "+" sobrecargado y la operación "f", y finalmente se devuelve un objeto Fracción;
3. . .
Por lo tanto, el compilador tiene al menos dos caminos por recorrer, lo que crea ambigüedad e informa un error. Como se muestra en la figura anterior, si se agrega la palabra clave explícita antes del constructor Franction, la conversión implícita se cancelará. Por lo tanto, durante la ejecución de d2 = f + 4, f llamará a la función doble para convertir a 0,6 y luego. agréguelo a 4 para convertirse en 4.6. Dado que el constructor cancela la conversión de fórmula implícita, 4.6 no se puede convertir a Fracción, por lo que se informará un error.
La siguiente figura muestra una aplicación de funciones de conversión y sobrecarga de operadores en C++ stl:
La siguiente imagen ilustra bien la estructura interna y el uso de los punteros inteligentes: Hay tres puntos clave en la sintaxis de los punteros inteligentes. El primero es el puntero externo guardado, que corresponde a T * px en la imagen de arriba. el puntero entrante relacionado se realizará en lugar del puntero entrante; el segundo es sobrecargar el operador "*", desreferenciar y devolver un objeto señalado por el puntero; el tercero es sobrecargar el operador "->"; return Un puntero correspondiente a la imagen de arriba es px.
Los iteradores también son un tipo de punteros inteligentes. También existen los tres elementos de los punteros inteligentes mencionados anteriormente, que corresponden a la fuente roja y las partes marcadas en amarillo en la siguiente figura:
Los caracteres de sobrecarga "*" y "->" de la sobrecarga del iterador se analizarán cuidadosamente a continuación:
Cree un objeto iterador de lista, list::iterator ite; la lista aquí se usa para guardar el objeto Foo, que es la clase en la definición de la plantilla de lista. T, operator*() devuelve un objeto (*node).data, el nodo es del tipo __link_type, pero __link_type es del tipo __list_node<T>*. Aquí está Foo, por lo que el nodo es __list_node<Foo >*, entonces ( *node).data obtiene un objeto de tipo Foo, y &(operator*()) finalmente obtiene una dirección del objeto Foo, es decir, devuelve Foo* Un puntero al tipo.
Como puede ver en la figura anterior, cada functor es una clase que sobrecarga el operador "()" y luego se convierte en un "functor". En realidad es una clase, pero parece tener las propiedades de una función. En realidad, cada funtor integra una clase extraña detrás de él, como se muestra en la siguiente figura. No es necesario que el programador declare explícitamente esta clase de forma manual. El functor en la biblioteca estándar también hereda una clase extraña: el contenido de esta clase se muestra en la siguiente figura. Solo declara algunas cosas y no contiene variables o funciones reales. El contenido específico se discutirá en STL.
A diferencia de las plantillas de clases, las plantillas de funciones no necesitan declarar explícitamente el tipo de los parámetros entrantes cuando las usan; el compilador deducirá automáticamente el tipo.
Las plantillas de miembros se utilizan a menudo en la programación genérica para tener una mejor escalabilidad, tome la figura anterior como ejemplo. T1 suele ser la clase base de U1 y T2 suele ser la clase base de U2. De esta manera, siempre que las clases padre o ancestro de U1 y U2 pasadas sean T1 y T2, la herencia y el polimorfismo se pueden utilizar inteligentemente a través de este método, pero no al revés. Este método se usa mucho en STL:
Como sugiere el nombre, la parcialización de plantilla se refiere a especificar tipos de datos específicos en la plantilla, que es diferente de la generalización: por supuesto, la parcialización de plantilla también tiene grados y se pueden especificar tipos parciales, lo que se denomina especialización parcial:
Se explicará demasiado contenido en el curso de C ++ 11, por lo que por ahora solo lo presentaré aquí.
Se explicará demasiado contenido en el curso de C ++ 11. Por ahora, solo lo presentaré aquí.
Una referencia puede verse como un alias de una variable referenciada.
Como se muestra en la figura anterior, se definen tres clases, A, B y C. B hereda de A y C hereda de B. Hay dos funciones virtuales en A, una en B y otra en C. El compilador asigna el objeto a de A en la memoria como se muestra en la figura anterior. Solo hay dos variables miembro m_data1 y m_data2. Al mismo tiempo, dado que la clase A tiene funciones virtuales, el compilador asignará un espacio al objeto a. Para guardar la tabla de funciones virtuales, esta tabla mantiene la dirección de la función virtual (función dinámica) de esta clase. Enlace con estado), dado que la clase A tiene dos funciones virtuales, hay dos espacios (espacios amarillos y azules) en la tabla de funciones virtuales de a que apuntan a A::vfunc1() y A::vfunc2() respectivamente, b It; es un objeto de clase B, porque la clase B reescribe la función vfunc1() de la clase A. , por lo que la tabla de funciones virtuales de B (parte cian) apuntará a B::vfunc1(), y B hereda vfunc2() de la clase A, por lo que la tabla de funciones virtuales de B (parte azul) apuntará a A de la clase principal A: :vfunc2 () función; de manera similar, c es un objeto de clase C, representado por La clase C reescribe la función vfunc1() de la clase principal, por lo que la tabla de funciones virtuales de C (parte amarilla) apuntará a C::vfunc1(). Al mismo tiempo, C hereda vfunc2() de la superclase A, por lo que la función virtual de B. La tabla (parte azul) apuntará a la función A::vfunc2(). Al mismo tiempo, la figura anterior también utiliza código en lenguaje C para ilustrar cómo la capa inferior del compilador llama a estas funciones. Esta es la esencia del polimorfismo de herencia orientado a objetos.
En realidad, este puntero puede considerarse como un puntero que apunta a la dirección de memoria del objeto actual. Como se muestra en la figura anterior, dado que hay funciones virtuales en la clase base y la subclase, this->Serialize() se vinculará dinámicamente. que es equivalente a (*(this- >vptr)[n])(this). Se puede entender combinando el puntero virtual y la tabla de funciones virtuales en la sección anterior. En cuanto a por qué es correcto escribir así al final, se explicará en el siguiente resumen.