#include #include "complexo.h"
usando namespace std;
operador ostream& << (ostream& os, const complex& x) { return os << '(' << real (x) << ',' << imag (x) << ') };
int main() { complexo c1(2, 1); complexo c2(4, 0);
cout << c1 << endl cout << c2 << endl;
cout << c1+c2 << endl; cout << c1-c2 << endl;
cout << conj(c1) << endl; cout << norma(c1) << endl << polar(10,4) << endl;
cout << (c1 += c2) << endl;
cout << (c1 == c2) << endl cout << (c1 != c2) << endl;
cout << (c2 - 2) << endl;
retornar 0;
Conforme mostrado na figura acima, a autoatribuição precisa ser verificada ao sobrecarregar o operador de atribuição "=", pelos seguintes motivos:
Se não houver detecção de autoatribuição, então os m_data do próprio objeto serão liberados, e o conteúdo apontado por m_data não existirá, portanto haverá problemas com a cópia.
Aqui estão duas classes para explicação:
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;
};
Depois de criar esses dois objetos, o compilador (VC) aloca memória para os dois objetos da seguinte forma:
Os dois à esquerda são a alocação de memória do compilador do complexo de classes no modo de depuração e no modo de liberação. No modo de depuração, o compilador insere a parte inicial e final (parte vermelha), parte de informação de tamanho 4*8 + 4 (parte cinza) na memória do objeto complexo. A parte verde é o espaço realmente ocupado pelo objeto complexo. há apenas 52 palavras, mas o VC está alinhado a 16 bytes, então o 16 múltiplo mais próximo de 52 é 64, e a lacuna de 12 bytes deve ser preenchida (parte do bloco ciano). Para o objeto complexo na parte de lançamento, apenas o cabeçalho de informações e a parte do cabeçalho são adicionados. A análise da classe string é basicamente a mesma.
A seguir, vamos ver como o compilador VC aloca objetos de array:
Da mesma forma, o compilador adiciona algumas informações redundantes ao objeto. Para objetos de classe complexos, como a matriz possui três objetos, há 8 duplos. Em seguida, o compilador insere "3" na frente dos três objetos complexos para marcar o número dos objetos individuais. . O método de análise da classe String também é semelhante.
A imagem abaixo ilustra por que a exclusão de um objeto array requer o método delete[]:
String.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;
}
Em C++, um construtor de um parâmetro (ou um construtor multiparâmetro com valores padrão para todos os parâmetros, exceto o primeiro parâmetro) desempenha duas funções. 1 é um construtor, 2 é um operador de conversão de tipo padrão e implícito.
Portanto, às vezes, quando escrevemos código como AAA = XXX, e o tipo de XXX é o tipo de parâmetro do construtor de parâmetro único AAA, o compilador chamará automaticamente esse construtor para criar um objeto AAA.
Isso parece legal e muito conveniente. Mas em alguns casos (veja o exemplo confiável abaixo), isso vai contra nossas intenções originais (dos programadores). Neste momento, você precisa adicionar modificação explícita na frente do construtor para especificar que este construtor só pode ser chamado/usado explicitamente e não pode ser usado implicitamente como um operador de conversão de tipo.
O construtor explícito é usado para evitar conversões implícitas. Por favor, veja o código abaixo:
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;
}
O construtor de Test1 recebe um parâmetro int e a linha 23 do código será convertida implicitamente para chamar o construtor de Test1. O construtor de Test2 é declarado como explícito, o que significa que o construtor não pode ser chamado por meio de conversão implícita, portanto ocorrerá um erro de compilação na linha 24 do código.
Construtores comuns podem ser chamados implicitamente. O construtor explícito só pode ser chamado explicitamente.
Quando qualquer fração precisa ser convertida para o tipo double, a função double() é automaticamente chamada para conversão. Conforme mostrado na figura acima, o compilador determina que 4 é um número inteiro durante a análise de double d = 4 + f, e então continua a determinar f. Observa-se que f fornece a função double() e então executa o double. () operação em f, e calcula 0.6 , depois soma a 4 e finalmente obtém o tipo duplo 4.6.
Uma classe é definida na figura acima, chamada Fraction. O operador "+" é sobrecarregado na classe Durante a operação f+4, "4" é convertido implicitamente (construtor) em um objeto Fraction e então passado. através da fração O operador "+" sobrecarregado participa da operação.
Conforme mostrado na figura acima, a função double() é adicionada ao Fraction, que divide as duas variáveis membro do Fraction e, em seguida, converte-as à força para o tipo double e retorna o resultado. Durante o processo de sobrecarga f+4, o compilador irá. reportar um erro. Você pode fazer a seguinte análise:
1. Primeiro, 4 é convertido implicitamente (construtor) em um objeto Fraction e, em seguida, o operador "+" sobrecarregado é operado com "f" para retornar um objeto Fraction;
2. Primeiro, 4 é convertido implicitamente (construtor) em um objeto Fraction e, em seguida, uma operação dupla é executada no par por meio do operador "+" sobrecarregado e da operação "f" e, finalmente, um objeto Fraction é retornado;
3. . .
Portanto, o compilador tem pelo menos dois caminhos a percorrer, o que cria ambiguidade e reporta um erro. Conforme mostrado na figura acima, se a palavra-chave explícita for adicionada antes do construtor Franction, a conversão implícita será cancelada. Portanto, durante a execução de d2 = f + 4, f chamará a função double para converter para 0,6 e então. adicione-o a 4 para se tornar 4.6 Como o construtor cancela a conversão implícita da fórmula, 4.6 não pode ser convertido em Fraction, portanto, um erro será relatado.
A figura a seguir mostra uma aplicação de sobrecarga de operador e funções de conversão em C++ stl:
A imagem abaixo ilustra muito bem a estrutura interna e o uso dos ponteiros inteligentes: Existem três pontos-chave na sintaxe dos ponteiros inteligentes. O primeiro é o ponteiro externo salvo, que corresponde a T* px na imagem acima. relacionado ao ponteiro de entrada será executado em vez do ponteiro de entrada; a segunda é sobrecarregar o operador "*", desreferenciar e retornar um objeto apontado pelo ponteiro; return Um ponteiro correspondente à imagem acima é px.
Iteradores também são um tipo de ponteiros inteligentes. Existem também os três elementos de ponteiros inteligentes mencionados acima, que correspondem à fonte vermelha e às partes marcadas em amarelo na figura abaixo:
Os caracteres de sobrecarga "*" e "->" da sobrecarga do iterador serão analisados cuidadosamente a seguir:
Crie um objeto iterador de lista, list::iterator ite; a lista aqui é usada para salvar o objeto Foo, que é a classe na definição do modelo de lista; T, operador*() retorna um objeto (*node).data, o nó é do tipo __link_type, mas __link_type é do tipo __list_node<T>* Aqui está Foo, então o nó é do tipo __list_node<Foo >*, então (. *node).data obtém um objeto do tipo Foo, e &(operator*()) finalmente obtém um endereço do objeto Foo, ou seja, retorna Foo* Um ponteiro para o tipo.
Como você pode ver na figura acima, cada functor é uma classe que sobrecarrega o operador "()" e depois se torna um "functor". Na verdade, é uma classe, mas parece ter as propriedades de uma função. Na verdade, cada functor integra uma classe estranha por trás dele, conforme mostrado na figura abaixo. Esta classe não precisa ser explicitamente declarada manualmente pelo programador. O functor na biblioteca padrão também herda uma classe estranha: o conteúdo desta classe é mostrado na figura abaixo. Ela apenas declara algumas coisas e não há variáveis ou funções reais nela. O conteúdo específico será discutido em STL.
Diferente dos modelos de classe, os modelos de função não precisam declarar explicitamente o tipo dos parâmetros recebidos ao usá-los, o compilador deduzirá automaticamente o tipo.
Os modelos de membros são frequentemente usados em programação genérica. Para ter melhor escalabilidade, tome a figura acima como exemplo. T1 é frequentemente a classe base de U1 e T2 é frequentemente a classe base de U2. Desta forma, desde que as classes pai ou ancestral de U1 e U2 passadas sejam T1 e T2, herança e polimorfismo podem ser utilizados de forma inteligente por meio deste método, mas não vice-versa. Este método é muito usado em STL:
Como o nome sugere, a parcialização do modelo refere-se à especificação de tipos de dados específicos no modelo, o que é diferente da generalização: é claro, a parcialização do modelo também tem graus, e tipos parciais podem ser especificados, o que é chamado de especialização parcial:
Muito conteúdo será explicado no curso C++ 11, então vou apenas apresentá-lo aqui por enquanto.
Muito conteúdo será explicado no curso C++ 11. Vou apenas apresentá-lo aqui por enquanto.
Uma referência pode ser vista como um alias para uma variável referenciada.
Conforme mostrado na figura acima, três classes são definidas, A, B e C. B herda de A e C herda de B. Existem duas funções virtuais em A, uma em B e uma em C. O compilador aloca o objeto a de A na memória conforme mostrado na figura acima. Existem apenas duas variáveis de membro m_data1 e m_data2. Ao mesmo tempo, como a classe A possui funções virtuais, o compilador alocará um espaço para o objeto a. para salvar a tabela de funções virtuais, esta tabela mantém o endereço da função virtual (função dinâmica) desta classe. Ligação com estado), como a classe A tem duas funções virtuais, há dois espaços (espaços amarelo e azul) na tabela de funções virtuais de a apontando para A::vfunc1() e A::vfunc2() respectivamente, b It; é um objeto da classe B, porque a classe B substitui a função vfunc1() da classe A. , então a tabela de funções virtuais de B (parte ciano) apontará para B::vfunc1(), e B herda vfunc2() da classe A, então a tabela de funções virtuais de B (parte azul) apontará para A da classe pai A: :vfunc2 () função; da mesma forma, c é um objeto da classe C, representado por A classe C reescreve a função vfunc1() da classe pai, então a tabela de funções virtuais de C (parte amarela) apontará para C::vfunc1(). Ao mesmo tempo, C herda vfunc2() da superclasse A, então a função virtual de B. A tabela (parte azul) apontará para a função A::vfunc2(). Ao mesmo tempo, a figura acima também usa código em linguagem C para ilustrar como a camada inferior do compilador chama essas funções. Esta é a essência do polimorfismo de herança orientado a objetos.
O ponteiro this pode realmente ser considerado como um ponteiro apontando para o endereço de memória do objeto atual. Conforme mostrado na figura acima, como existem funções virtuais na classe base e na subclasse, this->Serialize() será vinculado dinamicamente. que é equivalente a (*(this- >vptr)[n])(this). Isso pode ser entendido combinando o ponteiro virtual e a tabela de funções virtuais na seção anterior. O resumo a seguir explicará por que é correto escrever assim.