O construtor padrão é C#. Estruturas e construtores padrão. Alterando os parâmetros passados ​​para o construtor

O construtor padrão é C#. Estruturas e construtores padrão. Alterando os parâmetros passados ​​para o construtor

classe A (público: A():a(0),b(0) () explícito A(int x): a(x), b(0) () A(int x, int y): a(x ), b(y) () privado: int a,b ;

Classe A ( público: A explícito (int x=0, int y=0): a(x), b(y) () privado: int a,b; );

Existem diferenças? Qual é melhor usar?

2 respostas

Vlad de Moscou

Estas duas declarações de classe não são equivalentes.

Na segunda declaração de classe, o construtor é declarado com o especificador de função explícito, o que limita o uso deste construtor em diversas situações.

Na primeira declaração de classe, apenas o construtor de conversão é declarado com o especificador de função explícito. Isso significa que você pode chamar outros construtores implicitamente.

Ou seja, a primeira declaração oferece mais opções de uso da classe.

Considere o seguinte programa de demonstração

#incluir estrutura A (explícita A(int x = 0, int y = 0) : x(x), y(y) () int x; int y; ); estrutura B ( B() : x(0), y(0) () explícita B(int x): x(x), y(0) () B(int x, int y): x(x), y(y)()int x; void f(const A &a) ( std::cout<< "a.x = " << a.x << ", a.y = " << a.y << std::endl; } void g(const B &b) { std::cout << "b.x = " << b.x << ", b.y = " << b.y << std::endl; } int main() { // f({}); // f({ 1, 2 }); g({}); g({ 1, 2 }); }

Sua saída para o console:

Bx = 0, by = 0 bx = 1, by = 2

Neste programa, duas chamadas à função f são comentadas porque se forem descomentadas, o compilador irá gerar uma mensagem de erro.

Outra diferença importante é que uma classe possui apenas um construtor com uma determinada assinatura, enquanto outra classe possui três construtores com assinaturas diferentes.

Considere outra demonstração

Estrutura A (explícita A(int x = 0, int y = 0) : x(x), y(y) () int x; int y; ); estrutura B ( B() : x(0), y(0) () explícita B(int x): x(x), y(0) () B(int x, int y): x(x), y(y)()int x; struct C ( //amigo A::A(); amigo B::B(); ); int principal() ( )

Aqui na classe C, você pode declarar o construtor padrão da classe B como amigo da classe C. Porém, você não pode fazer o mesmo com o construtor padrão da classe A para declará-lo como amigo da classe C, já que o construtor padrão em classe A tem uma assinatura diferente

Você já tem que escrever

Estrutura C ( amigo A::A(int, int); );

e isso pode não ser o que você gostaria de receber. Isto é, se, por exemplo, você quiser que o amigo seja um construtor chamado exclusivamente sem argumentos.

Isto é, novamente, quando existem construtores separados, suas possibilidades são mais amplas.

Se considerarmos funções em vez de construtores, a diferença é ainda mais significativa.

Os argumentos padrão não afetam o tipo de função. Então, por exemplo, se você declarou uma função como

Vazio f(int, int = 0);

então, apesar do argumento padrão e do fato de você poder chamá-lo como

F(valor);

no entanto, seu tipo é void(int, int) . Isto, por sua vez, significa que você não pode, por exemplo, escrever

Void h(void f(int)) ( f(10); ) void f(int x, int y = 0) ( std::cout<< "x = " << x << ", y = " << y << std::endl; } // h(f);

já que o parâmetro de função h é do tipo void(int) ., e a função usada como argumento é do tipo void(int, int)

Se você declarar duas funções em vez de uma

Void h(void f(int)) ( f(10); ) void f(int x) ( std::cout<< "x = " << x << std::endl; } void f(int x, int y) { std::cout << "x = " << x << ", y = " << y << std::endl; }

então esse desafio

estará correto, pois existe uma função com um parâmetro.

IxSci

As diferenças já foram explicadas por @Vlad de Moscou, vou sugerir apenas, das duas opções da pergunta, a terceira opção:

Classe A (público: A():A(0, 0) () explícito A(int x): A(x, 0) () A(int x, int y): a(x), b(y) () privado: int a,b);

Na minha opinião esta opção é a melhor, porque... possui um construtor explícito de um argumento, o que é uma boa prática e evita alguns erros. Por outro lado, explícito para construtores que possuem mais ou menos de um argumento, na minha opinião, é redundante. Porque criar acidentalmente um objeto a partir de mais de um argumento é problemático, e é exatamente por isso que atribuímos explícito ao construtor de um argumento - proteção contra erros acidentais.

Bem, e o mais importante, temos apenas um construtor que inicializa os campos. Todos os demais atuam através dele, o que permite minimizar erros de inicialização.

O conceito de “construtor” é inseparável do conceito de classe. Construtores são funções especiais chamadas automaticamente quando os objetos são inicializados. Seus nomes são iguais aos nomes das classes às quais pertencem e não possuem tipo de retorno. Uma classe pode ter mais de um construtor, diferindo em assinaturas. Os construtores são úteis para inicializar os campos de uma classe. Curiosamente, o compilador cria um construtor padrão sem parâmetros e define os campos como 0, falso ou nulo (para objetos).

Vejamos um exemplo. Vamos criar uma classe "Triângulo" com três campos e um método que calcula seu perímetro.

Usando Sistema; Construtor de namespace ( class Triangle ( int a; int b; int c; public int perímetro() ( return a + b + c; ) ) class Programa ( static void Main(string args) ( Triangle tr = new Triangle(); / /criar um objeto Console.WriteLine("perímetro = " + tr.perímetro());

perímetro = 0

ANÁLISE

Pela primeira vez, três avisos (mas não erros!) aparecem na janela de erro:
O campo "Constructor.Triangle.c" não recebe um valor em nenhum lugar, portanto sempre terá um valor padrão de 0(também para campos um E b).

Já que o construtor sem parâmetros funcionou Triângulo(), então todos os lados do triângulo receberam 0. Vamos adicionar a linha à definição da classe: Triângulo(); e execute o programa. Receberemos uma mensagem de erro:
O tipo "Constructor.Triangle" já define um membro "Triangle" com os mesmos tipos de parâmetros.
Isto confirma o fato de que um construtor com zero parâmetros já foi criado. Agora vamos adicionar um construtor com três parâmetros à descrição da classe:

Triângulo Público (int a, int b, int c) ( this.a = a; this.b = b; this.c = c; )

E no método Main() substituímos a primeira linha por:

Triângulo tr = novo Triângulo(3,4,5);

Resultado da execução do programa: perímetro = 12

Ou seja, inicializamos os campos ( a, b, c) objeto tr, o que a palavra-chave indica esse nas instruções do construtor. Caso contrário teríamos que especificar nomes diferentes: a = d; onde d seria então o primeiro elemento na lista de parâmetros. Um operador como a = a deixará o valor do campo a=0 e o compilador emitirá um aviso:
É realizada a atribuição à mesma variável; realmente cumpre esse propósito e não outro?

Conselhos práticos: Sem eliminar os erros de sintaxe, você não obterá um programa funcional. Mas às vezes os avisos também sinalizam possíveis inconsistências importantes no seu código.

Lembremos que como os campos da classe são privados por padrão, eles não podem ser alterados diretamente no programa, por exemplo:
tr.c = 7;
Neste caso receberemos uma mensagem de erro:
'Constructor.Triangle.c' não está disponível devido ao seu nível de segurança.

O erro será eliminado se adicionarmos um modificador à descrição do campo com público:
público interno c;
No entanto, isso não é uma boa forma em OOP; o campo não está mais protegido.

Ao contrário das estruturas, as classes são tipos de dados de referência e suas instâncias (objetos) “vivem” no heap. Portanto, quando um objeto é criado, os campos são reservados no heap e, dependendo do construtor, os campos são definidos como 0, falso ou nulo (para um construtor padrão) ou os valores correspondentes (para um construtor com parâmetros).

Verifique na prática a exatidão das seguintes afirmações:

1) É possível criar um construtor padrão? - Sim.
2) Se você criar seu próprio construtor, o compilador gerará um construtor padrão? - Não.
3) Se alguns campos não forem inicializados no seu construtor, eles serão inicializados automaticamente pelo compilador? - Sim.
4) É permitido inicializar variáveis ​​onde elas são declaradas? - Sim.

Exemplo de construtor ao herdar uma classe

Vamos usar a classe Random pronta da biblioteca System. Vamos criar uma classe filha RwName (“Random with Name”), adicionar field se dois métodos a ela e também modificar o construtor da classe:

Usando Sistema; Construtor de namespace (class RwName: Random ( public string s; public RwName(string s) ( this.s = s; ) public double NextD() ( return this.NextDouble(); ) public void Rezult() ( Console.WriteLine( this.s + this.NextDouble()); ) classe Programa ( static void Main(string args) ( RwName r = new RwName("Número real: "); Console.WriteLine(r.NextD()); r. Resultado();

Agora o objeto de classe receberá um nome através do construtor, e os dois métodos de classe se tornaram mais específicos. Mesmo que você não especifique um nome "pai" na declaração da classe, ele ainda herdará o construtor da classe Object.

Construtor estático

O construtor também pode ser declarado estático. Um construtor estático normalmente é usado para inicializar componentes que se aplicam a uma classe inteira, em vez de a uma única instância de um objeto dessa classe. Portanto, os membros da classe são inicializados por um construtor estático antes de qualquer objeto dessa classe ser criado:

Usando Sistema; namespace Construtor ( class StatClass ( public static int q; public int p; // Construtor estático static StatClass() ( q = 33; ) // Construtor regular public StatClass() ( p = 44; ) ) class Program ( static void Main (string args) ( StatClass ob = new StatClass(); Console.WriteLine("Acessar campo p da instância: " + ob.p); Console.WriteLine("Acessar campo q: " + StatClass.q); Console. ReadLine ();

É importante que o construtor do tipo estático seja chamado automaticamente e antes do construtor da instância. Isto implica que um construtor estático deve ser executado antes de qualquer construtor de instância. Construtores estáticos não possuem modificadores de acesso - eles possuem acesso padrão e, portanto, não podem ser chamados a partir de um programa.

CONCLUSÃO

Ao criar classes por herança, seja mais preciso na escolha da classe pai. Pode haver mais de um construtor em sua classe, com base na conveniência de usá-los.

O construtor padrão é uma construção bastante simples que equivale à criação de um construtor sem parâmetros para o tipo. Assim, por exemplo, se, ao declarar uma classe não estática, você não declarar um construtor personalizado (não importa, com ou sem parâmetros), o compilador gerará independentemente um construtor sem parâmetros. No entanto, quando se trata de construtores padrão para estruturas (para tipos de valor), as coisas ficam mais complicadas.

Aqui está um exemplo simples de como você responderia à seguinte pergunta: de quantos tipos de valor existem.LÍQUIDOEstruturacontém construtores padrão? A resposta intuitiva parece ser " Todos", e você estará errado, porque na verdade, nenhum dos tipos significativos.LÍQUIDOEstruturanão contém um construtor padrão.

Trabalhar com arrays não é o único lugar onde um construtor de tipo de valor existente não será chamado. A tabela a seguir deixa claro quando tal construtor será chamado e quando não será.
// Chamado var cvt = new CustomValueType(); // Chamado var cvt = Activator.CreateInstance(typeof(CustomValueType)); // Não chamado var cvt = default(CustomValueType); estático T CreateAsDefault () ( return default(T); ) // Não chamado CustomValueType cvt = CreateAsDefault (); estático T CreateWithNew () where T: new() ( return new T(); ) // Não chamado!! CustomValueType cvt = CreateWithNew (); // Não chamado var array = new CustomValueType;

Gostaria de chamar a atenção para a inconsistência de comportamento no seguinte caso: sabe-se que uma instância de um parâmetro genérico é criada usando Activator.CreateInstance , por isso quando ocorre uma exceção no construtor, o usuário do genérico método irá recebê-lo não em sua forma pura, mas na forma TargetInvocationException. No entanto, ao criar uma instância do tipo Tipo de valor personalizado usando Ativador.CreateInstance nosso construtor padrão será chamado, e quando o método for chamado CriarComNovo e criando uma instância do tipo de valor usando novoT() - Não.

Conclusão

Então, descobrimos o seguinte:
  1. O construtor padrão em C# é uma instrução para redefinir o valor de um objeto.
  2. Do ponto de vista do CLR, existem construtores padrão e a linguagem C# sabe até como chamá-los.
  3. C# não permite criar construtores padrão personalizados para estruturas, pois isso levaria a um desempenho ruim ao trabalhar com matrizes e a uma confusão significativa.
  4. Em linguagens que suportam a criação de construtores padrão, você ainda não deve declará-los pelos mesmos motivos que são proibidos na maioria das linguagens da plataforma .NET.
  5. Os tipos de valor não são tão simples quanto parecem: além dos problemas de mutabilidade (mutabilidade), mesmo com construtores padrão, os tipos de valor não são tão simples.

A questão é:

1. Se você criar uma classe e definir um construtor com argumentos (a classe AClass, que possui apenas um construtor que aceita int i), o compilador não criará mais um construtor padrão. Porque violaria o contrato de classe do AClass, que não pode ser inicializado sem argumentos. Se você também deseja ter um construtor padrão, agora defina-o explicitamente.

Caso contrário não haveria como impedir a criação de um construtor padrão, o que seria ruim.

2. Ao criar construtores para uma BClass que herda de outra classe, o compilador exige que a primeira linha do construtor seja uma chamada para outro construtor (herdado ou naquela classe).

Por que? Porque depois de herdar de uma classe, você deseja reutilizar sua lógica. O construtor leva a instância da classe a algum estado integral inicial. No seu caso, AClass requer um argumento para inicializar, sem o qual a JVM não sabe como inicializar uma instância da classe.

Se uma classe não tiver nenhum construtor definido, ela tentará criar um construtor padrão, ou seja, sem argumentos:

Classe pública AClass1 ( )

Como não há construtores explícitos definidos e a classe não herda de outras classes, o compilador cria um construtor padrão.

Isso é equivalente a esta definição:

Classe pública AClass1 ( public AClass1() ( ) )

Agora vamos dar uma olhada em BClass1:

Classe pública BClass1 estende AClass1 ( )

Aqui também nenhum construtor é definido explicitamente e o compilador tenta criar um construtor padrão. Como AClass1 possui um construtor padrão, ele criará um construtor padrão que chamará o construtor de AClass1. Este código é equivalente a este:

A classe pública BClass1 estende AClass1 ( public BClass1() ( super(); ) )

No seu caso, uma classe é criada SEM um construtor padrão:

AClass pública ( public AClass(int i) ( ) )

Como (pelo menos um) construtor é descrito aqui, o construtor padrão não é mais criado. Ou seja, este código não irá mais compilar:

AClass a = new AClass(); //não funciona

preciso de algo como

ClasseA a = new ClasseA(1);

Conseqüentemente, qualquer construtor BClass exigirá uma chamada para algum construtor AClass ou BClass. Com esta descrição, o compilador irá reclamar:

BClass pública estende AClass ( )

Porque haverá uma tentativa de chamar o construtor padrão de AClass, que não está definido:

Public BClass estende AClass ( public BClass() ( super(); // erro; a classe AClass não possui tal construtor ) )

Entretanto, é possível criar uma BClass com um construtor padrão definindo o construtor AClass com algum valor:

Classe pública BClass estende AClass ( public BClass() ( super(1); ) )

Isso irá compilar.

Um construtor é uma função projetada para inicializar objetos de classe. Considere a classe de data:

data da aula
{
int dia, mês, ano;
público:
definir(int,int,int);
};

Em nenhum lugar afirma que um objeto deve ser inicializado, e o programador pode esquecer de inicializá-lo ou fazê-lo duas vezes.
OOP permite ao programador descrever uma função que se destina explicitamente a inicializar objetos. Como tal função constrói valores de um determinado tipo, ela é chamada de construtor. O construtor sempre tem o mesmo nome da própria classe e nunca tem um valor de retorno. Quando uma classe possui um construtor, todos os objetos dessa classe serão inicializados.

data da aula (
int dia, mês, ano;
público:
data(int,int,int); // construtor
};

Se o construtor exigir argumentos, eles deverão ser especificados:

data hoje = data(6,4,2014); //formulário completo
data natal(25,12,0); //forma abreviada
// data meu_burthday; // inválido, inicialização omitida

Se for necessário fornecer diversas maneiras de inicializar objetos de uma classe, vários construtores são especificados:

data da aula (
int mês, dia, ano;
público:
data(int,int,int); // dia mês ano
data(char*); //data na representação de string
data(); //data padrão: hoje
};

Os construtores estão sujeitos às mesmas regras em relação aos tipos de parâmetros que as funções sobrecarregadas. Se os construtores diferirem significativamente nos tipos de seus parâmetros, o compilador poderá escolher o correto cada vez que for usado:

data de julho4("27 de fevereiro de 2014");
namorado(27, 2, 2014);
data agora; // inicializado por padrão

Uma maneira de reduzir o número de funções sobrecarregadas (incluindo construtores) é usar valores padrão.

Construtor padrão

Um construtor que não requer parâmetros é chamado construtor padrão. Pode ser um construtor com uma lista de parâmetros vazia ou um construtor no qual todos os argumentos possuem valores padrão.
Os construtores podem estar sobrecarregados, mas só pode haver um construtor padrão.

data da aula
{
int mês, dia, ano;
público:
data(int,int,int);
data(char*);
data(); // construtor padrão
};

Quando um objeto é criado, o construtor é chamado, exceto quando o objeto é criado como cópia de outro objeto da mesma classe, por exemplo:

data data2 = data1;

Porém, há casos em que a criação de um objeto sem chamar um construtor é feita implicitamente:

  • parâmetro formal – objeto passado por valor, criado na pilha no momento em que a função é chamada e inicializado com uma cópia do parâmetro real;
  • O resultado de uma função é um objeto passado por valor; no momento em que a instrução return é executada, ele é copiado para um objeto temporário que armazena o resultado da função.

Em todos estes casos, o tradutor não chama um construtor para o objeto recém-criado:

  • data2 na definição acima;
  • para um parâmetro formal criado na pilha;
  • para um objeto temporário que armazena o valor retornado pela função.

Em vez disso, eles copiam o conteúdo do objeto de origem:

  • data1 no exemplo dado;
  • parâmetro real;
  • o objeto de resultado na instrução return.

Copiar construtor

Via de regra, ao criar um novo objeto a partir de um existente, ocorre uma cópia superficial, ou seja, são copiados os dados que o objeto de origem contém. Além disso, se o objeto de origem contiver ponteiros para variáveis ​​​​dinâmicas e matrizes ou referências, a criação de uma cópia do objeto exigirá a duplicação obrigatória desses objetos no objeto recém-criado. Para tanto, é introduzido um construtor de cópia, que é chamado automaticamente em todos os casos acima. Possui um único parâmetro - um link para o objeto de origem:

classe String
{
char *str;
tamanho interno;
público:
String(String&); //Copia o construtor
};
String::String(String& direita) ( // Cria cópias de dinâmica
//variáveis ​​e recursos
str = novo caractere;
strcpy(str, direita->str);
tamanho = direita->tamanho;
}

Destruidores

Uma classe definida pelo usuário possui um construtor que garante a inicialização adequada. Muitos tipos também requerem ação reversa. O destruidor garante que os objetos do tipo especificado sejam limpos adequadamente. O nome do destruidor é o nome da classe precedido por um til ~ . Portanto, para a classe X, o destruidor será nomeado ~X() . Muitas classes usam memória dinâmica, que é alocada pelo construtor e liberada pelo destruidor.

1
2
3
4
5
6
7
8
9
10
11
12
13
14

data da aula
{
dia interno, ano;
char *mês;
público:
data(int d, char * m, int y)
{
dia = d;
mês = novo caractere;
strcpy_s(mês, strlen(m)+1,m);
ano = y;
}
~date() (delete mês; ) // destruidor
};


Deixe que haja uma classe vect que implemente um array protegido, e você precise armazenar vários valores para cada array: idade, peso e altura de um grupo de pessoas. Agrupamos 3 arrays dentro de uma nova classe.

O novo construtor de classe tem um corpo vazio e uma lista de construtores de classe vect que podem ser chamados, listados após dois pontos (:) separados por vírgulas (,). Eles são executados com um argumento inteiro i, criando 3 objetos da classe vect: a, b, c.

Os construtores dos membros da classe são sempre executados antes do construtor da classe na qual esses membros são declarados. A ordem em que os construtores dos membros da classe são executados é determinada pela ordem em que os membros da classe são declarados. Se o construtor de um membro da classe exigir argumentos, esse membro com os argumentos necessários será especificado na lista de inicialização. Os destruidores são chamados na ordem inversa.
{
sistema("chcp 1251");
sistema("cls");
multi_vf(3);
para (int eu = 0; eu<= f.getSize(); i++)
{
elemento f.a.(i) = 10 + i;
f.b.element(i) = 20 + 5 * i;
elemento f.c.(i) = 120 + 5 * i;
}
para (int eu = 0; eu<= f.getSize(); i++)
{
corte<< f.a.element(i) << "лет \t" ;
corte<< f.b.element(i) << "кг \t" ;
corte<< f.c.element(i) << "см" << endl;
}
cin.get();
retornar 0;
}


Quando o programa for executado, um destruidor individual será chamado para cada membro do vect antes de sair do bloco principal. Resultado do programa
visualizações