Artigos

22: 11 Atribuição em classe - Espaços vetoriais


22: 11 Atribuição em classe - Espaços vetoriais

01 Tarefa em aula: Bem-vindo à Álgebra Matricial com aplicativos computacionais¶

Todas as tarefas em sala de aula são planejadas de forma que você possa começar assim que chegar à sala de aula. Isso é altamente recomendado. Veja o quão longe você pode chegar sozinho e então você estará pronto quando chegar a hora de fazer perguntas. Aqui estão as instruções básicas.

✅ Passo 1 - Pegue seu laptop¶

Sinta-se à vontade para pegar um dos laptops da sala de aula, se você não tiver o seu.

✅ Passo 2 - Crie uma pasta de tarefas do curso no seu diretório pessoal¶

DICA Armazene todos os blocos de notas na mesma pasta em seu computador. Eles funcionarão melhor assim.

✅ Etapa 3 - Baixe este caderno jupyter¶

Baixe uma cópia deste bloco de notas (arquivo ipynb que serve para bloco de notas ipython).

✅ Etapa 4 - Abra esta tarefa no Jupyter¶

Abra o arquivo baixado dentro do jupyter em seu laptop ou carregue o arquivo em um servidor on-line e abra-o lá.


22: 11 Atribuição em classe - Espaços vetoriais

Biblioteca de modelos em C ++ 17 pequena para cálculos de vetores e matrizes.

Biblioteca fornece sintaxe fácil para declarar, atribuir vetores e matrizes e fazer cálculos. As classes de vetor e matriz são projetadas para ter um layout de memória como arrays C ++ dos respectivos elementos e podem ser passadas para bibliotecas de renderização onde ponteiros para flutuadores (por exemplo) são necessários. A biblioteca usa modelos de expressão preguiçosos para cálculos.

Declaração e atribuição

a classe de vetor fornece acesso a todos os elementos por índices (operador subscrito) e função de modelo em & ltN & gt (). Os primeiros quatro elementos do vetor são acessíveis pelas funções nomeadas x (), y (), z () ew (), respectivamente. Essas funções são definidas apenas onde o tamanho do vetor permite, por exemplo, para um vetor de três elementos, não haverá função w (). Os elementos de vetores podem ser iterados com um loop de intervalo C ++ 11 ou usando iteradores.

a classe de matriz fornece operador subscrito e função de modelo em & ltN & gt () para acessar linhas que são representadas por vetores. Iteradores para linhas são fornecidos por pares de funções row_begin () / row_end (). Os pares begin () / end () fornecerão iteradores sobre todos os elementos da matriz, na ordem em que um array bidimensional C ++ seria disposto.

Buffers de memória como vetores

Um buffer de memória pode ser acessado como um contêiner de vetores com certas propriedades (tamanho, componentes). Um buffer constante pode ser usado para ler dados de uma maneira estruturada, um buffer sem custo pode ser usado para modificar os dados no buffer por meio das classes de utilitários vector_view e memory_vector_view. Um vector_view é para ler um único elemento, memory_vector_view é para usar um buffer como um 'contêiner' de vetores.

A biblioteca fornece quatérnios e operações com eles, como soma, subtração, multiplicação e divisão por escalar, multiplicação de quatérnio, magnitude, normalizar, conjugado e funções inversas. Os componentes de um quatérnio são acessíveis por meio dos acessadores w (), x (), y () e z (), onde w () é a parte real ex (), y () e z () são coeficientes para i, j e k respectivamente. Além disso, a parte escalar é acessível por meio da função de membro scalar_part (), e a parte vetorial é acessível por meio de vector_part ().

Exemplo de uso de quatérnions para rotação

Coordenadas polares, esféricas e cilíndricas

A biblioteca fornece coordenadas polares, esféricas e cilíndricas e conversão entre elas e as coordenadas XYZ.

Componentes de coordenadas polares:

  1. # 0 r () ou rho (), o componente de raio.
  2. # 1 phi () ou azimute (), o componente azimute, o valor está em radianos entre zero e 2π, o valor é normalizado automaticamente.

Componentes de coordenadas esféricas:

  1. # 0 r () ou rho (), o componente de raio.
  2. # 1 phi () ou inclinação (), o ângulo entre a projeção no plano e o vetor. A faixa de valor é [-π / 2, π / 2], o valor é fixado automaticamente.
  3. # 2 theta () ou azimute (), o componente azimute, o valor está em radianos entre zero e 2π, o valor é normalizado automaticamente.

Componentes de coordenadas cilíndricas:

  1. # 0 r () ou rho (), o componente de raio.
  2. # 1 phi () ou azimute (), o componente azimute, o valor está em radianos entre zero e 2π, o valor é normalizado automaticamente.
  3. # 2 z () ou elevação (), a altura acima de zero.

A conversão é definida para:

  • XYZ & lt- & gt polar
  • XYZ & lt- & gt esférico
  • XYZ & lt- & gt cilíndrico
  • polar & lt- & gt esférico
  • polar & lt- & gt cilíndrico
  • esférico & lt- & gt cilíndrico

Com base em classes e expressões vetoriais, a biblioteca fornece classes para cálculos de cores em espaços de cores RGB, HSL e HSV. Para classes de cores, as seguintes operações são definidas:


Exemplo de execução de código Python em Notebooks Jupyter¶

Um dos recursos mais exclusivos e definidores dos notebooks Jupyter é a capacidade de executar código dentro dele. Essa capacidade torna os Jupyter Notebooks especialmente úteis em aulas que ensinam ou usam conceitos de programação.

Os cadernos Jupyter são separados em diferentes tipos de “células”. Os dois principais tipos de células são células Markdown e células de código. As células Markdown (como esta) consistem em texto formatado, imagens e equações muito semelhantes ao seu processador de texto favorito.

A seguir estão duas células de código escritas na linguagem de programação Python. Este código simples é uma ferramenta para facilitar a pesquisa em seus cadernos Jupyter, o que pode ser útil se você estiver procurando por algo de uma aula anterior. O exemplo procura uma string exata nos arquivos de seu notebook no diretório atual e exibe links para os arquivos como saída.

Para executar o código, primeiro selecione a célula de código com o mouse e, em seguida, mantenha pressionada a tecla “Shift” enquanto pressiona a tecla “enter”. Você terá que pressionar a tecla Enter duas vezes para executar as duas células.


Um método seria usar a matriz para inicializar o vetor

Se o seu compilador suporta C ++ 11, você pode simplesmente fazer:

Isso está disponível no GCC a partir da versão 4.4. Infelizmente, o VC ++ 2010 parece estar ficando para trás nesse aspecto.

Como alternativa, a biblioteca Boost.Assign usa magia não macro para permitir o seguinte:

Mas tenha em mente que isso tem alguma sobrecarga (basicamente, list_of constrói um std :: deque por baixo do capô), portanto, para código crítico de desempenho, seria melhor fazer o que diz Yacoby.

Se puder, use o C ++ moderno [11,14,17,20. ] caminho:

A velha maneira de fazer um loop em um array de comprimento variável ou usar sizeof () é realmente terrível para os olhos e completamente desnecessária em termos de sobrecarga mental. Que nojo.

Em C ++ 0x você poderá fazer isso da mesma forma que fez com um array, mas não no padrão atual.

Apenas com suporte a idiomas, você pode usar:

Se você pode adicionar outras bibliotecas, você pode tentar boost :: assign:

Para evitar a codificação do tamanho de uma matriz:

STL convencional com macros genéricas:

STL convencional com uma macro de inicializador de vetor:

Só pensei em jogar meu 0,02. Tenho tendência a declarar o seguinte:

em um cabeçalho de utilitário em algum lugar e tudo o que é necessário é:

Mas eu não posso esperar por C ++ 0x. Estou preso porque meu código também deve ser compilado no Visual Studio. Vaia.

C ++ 11 em diante abaixo também é possível

C ++ 17 em diante, podemos omitir o tipo

Se você não tiver um compilador C ++ 11 e não quiser usar boost:

Se você não tem um compilador C ++ 11 e pode usar boost:

Se você tiver um compilador C ++ 11:

Para inicialização de vetor -

pode ser feito se você tiver o compilador C ++ 11.

Caso contrário, você pode ter uma matriz dos dados e, em seguida, usar um loop for.

Além dessas, existem várias outras maneiras descritas acima usando algum código. Em minha opinião, essas formas são fáceis de lembrar e rápidas de escrever.

A maneira mais fácil de fazer isso é:

Eu construo minha própria solução usando va_arg. Esta solução é compatível com C ++ 98.

Se o seu compilador for compatível com macros Variadic (o que é verdadeiro para a maioria dos compiladores modernos), você pode usar a macro a seguir para transformar a inicialização do vetor em uma linha:

Com esta macro, você pode definir um vetor inicializado com código como este:

Isso criaria um novo vetor de ints denominado my_vector com os elementos 1, 2, 3, 4.

Se você não quiser usar boost, mas quiser desfrutar de sintaxe como

apenas inclua este pedaço de código

você pode fazer isso usando boost :: assign.

Uma pergunta duplicada mais recente tem esta resposta de Viktor Sehr. Para mim, é compacto, visualmente atraente (parece que você está 'empurrando' os valores), não requer c ++ 11 ou um módulo de terceiros e evita o uso de uma variável extra (escrita). Abaixo está como estou usando com algumas alterações. Posso mudar para estender a função de vetor e / ou va_arg no futuro.

Os métodos abaixo podem ser usados ​​para inicializar o vetor em c ++.

int arr [] = <1, 3, 5, 6> vetor & ltint & gt v (arr, arr + sizeof (arr) / sizeof (arr [0]))

vetor & ltint & gtv v.push_back (1) v.push_back (2) v.push_back (3) e assim por diante

O terceiro é permitido apenas em C ++ 11 em diante.

Há muitas respostas boas aqui, mas como cheguei independentemente às minhas antes de ler isto, decidi lançar a minha aqui de qualquer maneira.

Aqui está um método que estou usando para isso, que funcionará universalmente em compiladores e plataformas:

Crie uma estrutura ou classe como um contêiner para sua coleção de objetos. Defina uma função de sobrecarga do operador para & lt & lt.

Você pode criar funções que usam sua estrutura como parâmetro, por exemplo:

Então, você pode chamar essa função, assim:

Dessa forma, você pode construir e passar uma coleção de objetos de tamanho dinâmico para uma função em uma única linha limpa!


Std :: vector :: assign

Atribui novos conteúdos ao vetor, substituindo seu conteúdo atual e modificando seu tamanho de acordo.

No versão do intervalo (1), os novos conteúdos são elementos construídos a partir de cada um dos elementos no intervalo entre primeiro e durar, na mesma ordem.

No preencher versão (2), os novos conteúdos são n elementos, cada um inicializado com uma cópia de val.

Se ocorrer uma realocação, o armazenamento necessário é alocado usando o alocador interno.

No versão do intervalo (1), os novos conteúdos são elementos construídos a partir de cada um dos elementos no intervalo entre primeiro e durar, na mesma ordem.

No preencher versão (2), os novos conteúdos são n elementos, cada um inicializado com uma cópia de val.

No versão da lista de inicializadores (3), os novos conteúdos são cópias dos valores passados ​​como lista de inicializadores, na mesma ordem.

O alocador interno é usado (por meio de suas características) para alocar e desalocar armazenamento se ocorrer uma realocação. Também é usado para destruir todos os elementos existentes e construir os novos.

Quaisquer elementos mantidos no contêiner antes da chamada são destruído e substituídos por elementos recém-construídos (nenhuma atribuição de elementos ocorre).

Isso causa uma realocação automática do espaço de armazenamento alocado se - e somente se - o novo tamanho do vetor ultrapassar a capacidade do vetor atual.


push_back () e insert () chamam grow (), que falha em aumentar a capacidade porque GROWER é um int, então 1,6 trunca para 1, e multiplicar a capacidade * 1 não muda seu valor. Mas mesmo se a capacidade fosse aumentada de maneira adequada, o array data_ptr não está sendo realocado para se ajustar à nova capacidade.

Mas mesmo se grow () estivesse funcionando corretamente, não há necessidade de chamar grow () em cada inserção de um novo elemento, o que anula o propósito de separar n_elems da capacidade para começar. Você deve aumentar o array apenas quando n_elems atingir a capacidade.

Existem muitos outros problemas com sua classe:

operador = não está testando a auto-atribuição e está perdendo a memória alocada do antigo array. Considere usar o idioma copy-swap.

front () não alcança a instrução throw quando o array está vazio.

at () e erase () estão realizando verificação de limites usando capacidade em vez de n_elems.

push_back () está inserindo o novo valor no índice errado, e não incrementando n_elems.

pop_back () não lança um erro quando o array está vazio, fazendo com que n_elems diminua abaixo de 0, o que volta ao valor positivo máximo de size_t porque size_t é não assinado.

erase () e operator == saem dos limites durante a iteração do array.

begin () e end () não devem retornar nullptr para um array vazio. E end () está retornando um ponteiro para o último elemento em uma matriz não vazia em vez de retornar um ponteiro para 1 após o último elemento.

operator == e operator! = não executam nenhuma verificação de limite para garantir que os 2 vetores tenham os mesmos n_elems antes de iterar seus arrays. E eles estão comparando valores de elemento além de n_elems. Além disso, o operador! = Está retornando o resultado errado.

Com isso dito, tente algo mais parecido com isto:

Você está perdendo a declaração do vetor aqui, mas posso supor sua estrutura. Primeiro erro que você está cometendo aqui:

A realocação não acontece, onde está? Você apenas invoca dietis de comportamento indefinido.

Você deve fazer GROWER flutuar ou aumentar o tamanho em um tamanho fixo. Além disso, é muito incomum que um vetor cresça geometricamente. E muito ineficaz para crescer a cada push_back. Em vez disso, você deve ter CAPACIDADE e TAMANHO ALOCADO. O último pode ser maior e aumentaria se push_back aumentasse a capacidade fora de seus limites. Você aparentemente tem vários elementos n_elems lá, mas você os ignora?

Muitos deles estão fazendo muito trabalho extra e não fazem o que deveriam. Como se isso pudesse realmente ser

Use listas de inicialização, não efeitos colaterais. Ou você pode perdê-los. O construtor de cópia também não leva em consideração n_elems de ve pode copiar lixo, convertendo-o em novos elementos.


Oi pessoal. Eu realmente preciso de ajuda. Eu fiz esta tarefa, mas foi enviada bac

Vou colocar o que fiz. e os comentários do avaliador. se alguém pudesse me dizer passo a passo o que fiz de errado e como consertar. Eu estarei para sempre em dívida com você.

Tarefa:
Os espaços vetoriais possuem uma coleção de características e propriedades específicas.

O conjunto de elementos pertencentes a R 2 geralmente é denotado como <(uma, b) | uma, bR >. A combinação de elementos dentro deste conjunto sob as operações de adição e multiplicação escalar deve usar a seguinte notação:

Defina um subespaço não trivial de R 2, mostrando todo o trabalho.

Aqui está o que eu fiz: para definir um subespaço não trivial de, primeiro preciso entender
o que não é trivial realmente é. Não trivial é uma equação linear em que o valor de pelo menos
uma variável da equação não é igual a 0. Assim, para descrever um subespaço não trivial, eu precisaria de uma equação linear em que uma das variáveis ​​não fosse igual a 0. Portanto, seja v = (5, 4) e u = (0, 0) em R ^ 2. . Para mostrar que este é um subespaço de R ^ 2, preciso mostrar três coisas. Primeiro, preciso mostrar que 0 está em R ^ 2). Em segundo lugar, eu precisaria mostrar o fechamento para adição. Por último, preciso mostrar o fechamento para multiplicação escalar. Agora, para mostrar que 0 está em R ^ 2, preciso primeiro chegar a uma equação linear que atravessa a origem e o ponto (5, 4). Portanto, minha equação linear é y = 5 / 4x. Esta é a equação linear que satisfaz ambos os pontos e passa pela origem porque conectarei os pontos e verei se obtenho igualdade. Portanto, 5 = 5/4 (4) = 5 e 0 = 5/4 (0) = 0. Portanto, v e u estão em R ^ 2. Isso mostra que 0 está em R ^ 2. Agora, preciso mostrar que (u + v) está em R ^ 2. Agora vou mostrar que é fechado sob adição: u + v = (4, 5) + (0, 0) = (4 + 0, 5 + 0) = (4, 5) que está em R ^ 2, como mostrado acima de. Portanto, ele está fechado para adição. Agora, precisamos mostrar que o conjunto é fechado sob multiplicação escalar. Vou deixar meu escalar = 5, então 5u = 5 (4, 5) = (20, 25) que está em R ^ 2 porque 25 = 5/4 (20) = 25. Visto que, de cima, eu mostrei que 0 está em R ^ 2, u + v está em R ^ 2 e c (u) está em R ^ 2, este seria um subespaço não trivial de R ^ 2.

Os comentários que recebi quando ele foi enviado de volta para mim são: Muita discussão foi fornecida nesta parte da tarefa. No entanto, não está claro o que é o subespaço não trivial, uma vez que dois vetores foram fornecidos. Dois vetores não constituem um subespaço.

Alguém pode me dizer o que o avaliador quis dizer com este comentário e alguém pode me dizer como posso corrigi-lo. obrigado. Eu estarei para sempre em dívida com você.

Daon2

Membro Sênior

Um subespaço de R ^ 2 que contém um vetor diferente de zero terá infinitos vetores nele, tendo dois vetores é. insuficiente. Por exemplo, se (5,4) pertence ao seu subespaço, então deve (10,8), (-5, -4), etc. Não está claro qual é a sua definição de não trivial (você está falando sobre equações?) , mas normalmente significa que não é <(0,0)> nem R ^ 2.

R ^ 2 é um espaço vetorial bidimensional. Qualquer subespaço adequado será unidimensional. Você tem uma solução no meio do seu parágrafo. Todos os pontos (x, y) satisfazendo y = 5 / 4x.


Recuperação de informação

Na Tarefa de Casa 3, você implementará a recuperação de classificação descrita nas Aulas 7 e 8.

Pontos em comum com o dever de casa nº 2

Os comandos de indexação e consulta usarão um formato de entrada idêntico ao Trabalho de Casa 2, de modo que você não precisa modificar nenhum código para lidar com o processamento da linha de comando. Para recapitular:

Indexando: $ python index.py -i diretório-de-documentos -d arquivo de dicionário -p arquivo de postagens

Procurando: $ python search.py ​​-d arquivo de dicionário -p arquivo de postagens -q arquivo de consultas -o arquivo de saída de resultados

Também usaremos novamente o conjunto de dados de treinamento da Reuters fornecido pela NLTK. Dependendo de onde você especificou o diretório para dados NLTK quando instalou pela primeira vez os dados NLTK (lembre-se de que a instalação é acionada por nltk.download ()), esse conjunto de dados está localizado em um caminho como:. / nltk_data / corpora / reuters / training /.

Como na Tarefa de Casa 2, seu processo de pesquisa não deve manter o arquivo de postagens na memória ao responder às perguntas, mas, em vez disso, usar um cursor de arquivo para acessar aleatoriamente partes do arquivo de postagens. No entanto, essa restrição não existe para o indexador (não é solicitado que você implemente BSBI ou SPIMI). Além disso, você deve continuar a empregar derivação Porter em seu envio, tanto para indexação de documentos quanto para processamento de consultas.

Modelo de Espaço Vetorial

Para implementar o VSM, você maio escolha implementar (você pode fazer de forma diferente) seu dicionário e listas de postagens no formato a seguir. A única diferença entre esse formato e o da Figura 1.4 do livro é que você codifica as frequências dos termos nas postagens com o propósito de calcular tf e timesidf. A tupla em cada postagem representa (doc ID, termo freq).

prazo doc freq (df)
listas de postagens
ambicioso 5 (1, 5)→ (7,2) → (21, 7) → .
. . .

Além do dicionário padrão e do arquivo de postagens, você precisará armazenar informações no momento da indexação sobre o comprimento do documento, a fim de fazer a normalização do documento. Nas notas de aula e no livro-texto, isso é referido como Comprimento [N]. Você pode armazenar essas informações com as postagens, dicionário ou como um arquivo separado.

Na etapa de pesquisa, você precisará classificar os documentos por similaridade de cosseno com base em tf e timesidf. Em termos de notação SMART de ddd.qqq, você precisará implementar o esquema de classificação lnc.ltc (ou seja, log tf e idf com normalização de cosseno para documentos de consultas e log tf, normalização de cosseno, mas não idf para documentos. Calcule similaridade de cosseno entre a consulta e cada documento, com os pesos seguem o cálculo tf & timesidf, onde termo freq = 1 + log (tf) e frequência inversa do documento idf = log (N / df) (para consultas).

É sugerido que você use o log de base 10 para seus cálculos de logaritmo (ou seja, math.log (x, 10), mas tome cuidado com casos como math.log (0, 10)). As consultas que fornecemos agora são consultas de texto livre, ou seja, você não precisa usar operadores de consulta como AND, OR, NOT e parênteses, e não haverá consultas frasais. Essas consultas de texto livre são semelhantes às que você digita na barra de pesquisa de um mecanismo de pesquisa na web.

Seu pesquisador deve gerar uma lista de até 10 docIDs mais relevantes (menos se houver menos de dez documentos que tenham radicais correspondentes à consulta) em resposta à consulta. Esses documentos precisam ser ordenados por relevância, com o primeiro documento sendo o mais relevante. Para aqueles marcados com a mesma relevância, classifique-os ainda mais pela ordem crescente dos docIDs. Produza esses documentos como uma lista delimitada por espaço, como na Tarefa de Casa # 2 Certifique-se de não incluir espaços em branco extras antes e depois de quaisquer IDs de documento. Por exemplo, se os IDs de documento 2, 1024 e 512 forem apenas 3 documentos relevantes, nessa ordem seu pesquisador deve gerar esses IDs de documento uma linha para a consulta correspondente:

O que entregar?

Questões dissertativas

Você também deve responder às seguintes questões dissertativas. Eles são para testar sua compreensão dos materiais de aula. Observe que essas são perguntas abertas e não têm respostas padrão ouro. Normalmente, um ou dois parágrafos são suficientes para cada pergunta. Você pode receber uma pequena quantia de crédito extra se puder apoiar suas respostas com resultados experimentais.

  1. Nesta tarefa, não pedimos a você suporte para consultas frasais, que é um recurso normalmente compatível com mecanismos de pesquisa da web. Descreva como você apoiaria a pesquisa frasal em conjunto com o modelo VSM. Um esboço do algoritmo é suficiente. (Para aqueles que gostam de um desafio, vá em frente e implemente esse recurso em seu envio, mas demarque-o claramente em seu código e permita que esse recurso seja ativado ou desativado usando a opção de linha de comando "-x" (onde "- x "significa ativar o processamento estendido de consultas frasais. Daremos um pequeno bônus aos envios que atingirem essa funcionalidade corretamente).
  2. Descreva como seu mecanismo de pesquisa reage a documentos e consultas longas em comparação com documentos e consultas curtos. A normalização que você usa é suficiente para resolver os problemas (consulte a Seção 6.4.4 para obter uma dica)? Em sua opinião, o esquema ltc.lnc (n.b., não o esquema de classificação que você foi solicitado a implementar) é suficiente para recuperar documentos da coleção Reuters-21578?
  3. Você acha que os índices paramétricos de zona ou campo seriam úteis para pesquisas práticas na coleção da Reuters? Observação: a coleção da Reuters possui metadados para cada artigo, mas a qualidade dos metadados não é uniforme, nem as classificações de metadados aplicadas de maneira uniforme (alguns documentos têm, outros não). Dica: para o próximo dever de casa # 4, nós vontade estar usando metadados de campo, portanto, se você quiser basear a Tarefa de Casa nº 4 em sua Tarefa de Casa nº 3, será bem-vindo para começar o suporte para isso mais cedo (embora nenhum crédito extra seja dado se estiver certo).

Formatação de Submissão

Você tem permissão para fazer esta tarefa individualmente ou como uma equipe de dois. Não haverá diferença nos critérios de avaliação se você fizer a tarefa em equipe ou individualmente. Para as informações de envio abaixo, basta substituir qualquer menção a um número matricial pelos dois números matriciais concatenados com um traço de separação (por exemplo, A000000X-A000001Y).

Para avaliarmos esta tarefa em tempo hábil, precisamos que você siga estritamente as seguintes diretrizes de envio. Eles me ajudarão a avaliar a tarefa de maneira adequada. Você será penalizado se não seguir essas instruções. Seu número matricial em todas as declarações a seguir não deve ter espaços e as letras devem estar em MAIÚSCULAS. Você deve entregar os seguintes arquivos:


3 respostas 3

Você teve muitos problemas com seu código, então é difícil saber por onde começar. :)

Eu concordo com o que John Burger disse.

Levei seu código para o meu PC para economizar tempo de enviá-lo toda vez e também para poder usar o valgrind nele. Certamente valgrind relatou um erro depois que você imprimiu o vetor. A razão para isso é simples. Você passou um cópia de da classe para print_vec, o que significa que o destruidor foi chamado quando print_vec saiu, desalocando assim a memória. Você deveria ter tido um construtor de cópia. Sem ele, o compilador faz uma cópia bit a bit da classe, o que significa que agora você tem dois objetos compartilhando a mesma memória alocada.

Uma solução rápida e suja é chamar print_vec por referência:

No entanto, isso deixa o bug à espreita para o futuro.

Eu implementei o construtor de cópia no meu exemplo abaixo, no entanto, chamar print_vec por referência salva a classe tendo que ser copiada, reduzindo o número de novos / delete s que você está fazendo, possivelmente reduzindo assim a fragmentação da memória.

Como disse John Burger: não chame o destruidor você mesmo! Você não pode fazer isso. Se você quiser fazer a mesma coisa no destruidor e na função clear, apenas faça com que o destruidor chame clear ().

O uso de sublinhados duplos à esquerda em variáveis ​​é contrário ao padrão C ++. Não faça isso. Use um sublinhado à direita se quiser indicar uma variável de membro. Perca todas as referências this- & gt. Eles apenas bagunçam as coisas.

Uma vez que você aloca um array, você deve usar delete [] - você fez isso em um lugar, mas não no outro.

Por que fazer isso? É uma tarefa desnecessária:

Se você for atribuir a classe por algum motivo, também deve implementar um operador = ou terá os mesmos problemas que teve com o construtor de cópia. (Veja meu exemplo).

Aqui você está testando funções (capacidade e tamanho) sem chamá-las:

Se você seguir o caminho "else", a função não retornará nenhum valor (você receberá um aviso do compilador se ativar os avisos).

Meu exemplo retrabalhado, com minhas sugestões nele. Compila sem avisos ou erros e é executado sem travar (em um PC):

Em seu segundo construtor, você começa com o seguinte código:

Isso é mortal! Você não inicializou __data, então ele pode conter algum valor sob o sol. E aqui está de jeito nenhum que poderia possivelmente ser um ponteiro válido para os dados existentes - é um objeto não inicializado totalmente novo. Retornar esse ponteiro de lixo para o heap é simplesmente pedir problemas.

Remova essas linhas - ou melhor ainda, use uma lista de inicializadores como você fez com o primeiro construtor:

Outro problema: em seu membro clear () você escreveu:

Você nunca deveria, Nunca, Nunca chame diretamente um destruidor como este. O compilador pode colocar todos os tipos de outros códigos no código do destruidor, uma vez que "sabe" que é o único que vai chamá-lo. Por exemplo, alguns compiladores pré-enviam um sinalizador para dizer se devem excluir o objeto do heap após fazer a destruição. Você não fez isso, então o que foi descrito acima pode corromper todos os tipos de coisas.

Em vez disso, mova o código que está no destruidor para clear () e simplesmente chame clear () do destruidor.

Outro, este escolhido por @Nick Gammon:

Isso testa se o membro __data é falso e se as funções de capacidade e tamanho foram definidas. Não se preocupe: os dois últimos foram. você perdeu o prefixo __.

[Além disso, pare com this- & gt em todos os lugares. Você usou um __ antes de todas as suas variáveis ​​de membro (o que é contra a convenção), você não precisa insistir no fato de que são variáveis ​​de membro: o compilador já sabe.]

Há algo que gostaria de acrescentar.
Outros já apontaram vários erros no código. E eu poderia apontar muitos mais, mas não é esse o ponto.
IMO, o maior erro é - implementar uma classe Vector para o Arduino!
Mesmo que funcione, é apenas uma má ideia. Lembre-se de que o Arduino tem uma quantidade de memória MUITO limitada. A alocação de memória dinâmica em um espaço tão restrito parece uma má ideia - você está desperdiçando memória, está fragmentando o heap e não ganha quase nada, exceto alguma conveniência de codificação.
Ainda estou para ver um caso em que realmente seja necessária a alocação dinâmica de memória em um projeto Arduino. E mesmo se você fizer isso, existem muitas outras ferramentas que você pode usar (como buffers temporários de pilha alocada, alocadores de arena, alocadores de pool e outros enfeites). Mas, novamente, provavelmente o código e o custo de memória dessas soluções "genéricas" serão inviáveis ​​no Arduino.
Na maioria das vezes não há problema em usar vetores, mapas e o que você quiser em plataformas como o PC (eu trabalho como programador de jogos para PC, então muitas vezes encontro situações em que extras como esses são grandes "não-não", mas para a maioria das aplicações é multar).
Mas, em uma plataforma "compacta" como o Arduino, acho que você deve ficar "perto do metal" e ter total controle do que está acontecendo (memória e ciclos de CPU). Idealmente, você só deve pagar "luxos" como esses quando sabe o que está acontecendo "por baixo" e sabe que pode se safar com isso. O que raramente é o caso em plataformas "apertadas" como o Arduino.


Assista o vídeo: Espaços Vetoriais e Transformações Lineares - Base (Novembro 2021).