I never seem to be able to correctly split and manage my C++ projects that have multiple files. So excuse my ignorance…
I’m receiving these linker related errors from g++ when trying to compile the project:
/usr/bin/ld: main.o: in function "main":
main.cpp:(.text+0x24): undefined reference to "AVLTree<int>::AVLTree()"
/usr/bin/ld: main.cpp:(.text+0x35): undefined reference to "AVLTree<int>::inserir(int)"
/usr/bin/ld: main.cpp:(.text+0x46): undefined reference to "AVLTree<int>::inserir(int)"
/usr/bin/ld: main.cpp:(.text+0x52): undefined reference to "AVLTree<int>::print()"
/usr/bin/ld: main.cpp:(.text+0x5e): undefined reference to "AVLTree<int>::~AVLTree()"
/usr/bin/ld: main.cpp:(.text+0x87): undefined reference to "AVLTree<int>::~AVLTree()"
collect2: error: ld returned 1 exit status
The command I’m using to compile is: g++ -o program src/main.cpp src/arvore.cpp -I include
I’m also trying to run it on VSCode, using the compile&run button. It gives the same error as when I try compiling in the terminal.
My folders are structured like this:
Image of the folder structure
One thing before I show the code for the files: I’m a portuguese speaker, that’s the language the comments and most of the methods are written in. I know there’s a lot of comments and we should keep them brief… there’s so many of them because I’m writing them to help me understand what’s going on as I’m a newbie and studying as I write the code
arvore.hpp
#ifndef ARVORE_H
#define ARVORE_H
template <typename T>
class TreeNode{
T dado;
TreeNode* esquerda;
TreeNode* direita;
int altura;
public:
TreeNode(T dado);
};
template <typename T>
class AVLTree{
private:
TreeNode<T>* raiz;
int getAltura(TreeNode<T>* no);
int getFatorDeBalanceamento(TreeNode<T>*no1, TreeNode<T>*no2);
TreeNode<T>* rotacionarEsquerda(TreeNode<T>* no);
void atualizaAltura(TreeNode<T>* no);
TreeNode<T>* rotacionarDireita(TreeNode<T>* no);
TreeNode<T>* balancearNo(TreeNode<T>* no);
TreeNode<T>* inserirNo(TreeNode<T>* node, T dado);
void delecaoRecursiva(TreeNode<T>* node);
void printRecursivo(TreeNode<T>* node);
public:
AVLTree();
void inserir(T dado);
~AVLTree();
void print();
};
#endif
arvore.cpp
#include <iostream>
#include "../include/arvore.hpp"
template <typename T>
TreeNode<T>::TreeNode(T dado){this->dado = dado; esquerda = nullptr; direita = nullptr; altura = 1;}
template <typename T>
int AVLTree<T>::getAltura(TreeNode<T>* no){
if(no == nullptr)
return 0;
return no->altura;
}
template <typename T>
int AVLTree<T>::getFatorDeBalanceamento(TreeNode<T>*no1, TreeNode<T>*no2){
return getAltura(no1) - getAltura(no2);
}
template <typename T>
TreeNode<T>* AVLTree<T>::rotacionarEsquerda(TreeNode<T>* no){
//deve ser passado como argumento o nó que tem o valor de balanceamento diferente de 1,-1 ou 0.
TreeNode<T>* novaRaiz = no->direita; //a nova raíz (nó mais alto) será o filho à direita do desbalanceado
no->direita = novaRaiz->esquerda; //o nó pai deixa de apontar à direita (para o filho), que agora é a nova raíz. Passa a apontar para o nó à esquerda do filho
novaRaiz->esquerda = no; //o antigo filho passa a apontar para o pai, fazendo assim ele "subir" e se tornar a nova raíz
atualizaAltura(no);
atualizaAltura(novaRaiz);
return novaRaiz;
}
template <typename T>
void AVLTree<T>::atualizaAltura(TreeNode<T>* no){
//vai atualizar a altura atual do nó (os nós folhas tem altura 0, enquanto a raíz tem a maior altura)
//lembrando que a altura da árvore é definida como o número de nós no maior caminho da raíz até uma folha
//utilizar getAltura evita o uso de um operador ternário para tratar o caso das nodes da esquerda ou direita serem nulas
no->altura = 1 + std::max(getAltura(no->esquerda) ,getAltura(no->direita));
}
template <typename T>
TreeNode<T>* AVLTree<T>::rotacionarDireita(TreeNode<T>* no){
//deve ser passado como argumento o nó que tem o valor de balanceamento diferente de 1,-1 ou 0.
TreeNode<T>* novaRaiz = no->esquerda; //a nova raíz (nó mais alto) será o filho à esquerda do desbalanceado
no->esquerda = novaRaiz->direita; //o nó pai deixa de apontar à esquerda (para o filho), que agora é a nova raíz. Passa a apontar para o nó à direita do filho
novaRaiz->direita = no; //o antigo filho passa a apontar para o pai, fazendo assim ele "subir" e se tornar a nova raíz
atualizaAltura(no);
atualizaAltura(novaRaiz);
return novaRaiz;
}
template <typename T>
TreeNode<T>* AVLTree<T>::balancearNo(TreeNode<T>* no){
//o fator de balanceamento é sempre a altura da sub-árvore da esquerda menos a altura da sub-árvore à direita. Isso porque se a direita for maior, vai dar um número
//negativo, que é a norma para indicar um desbalanceamento à direita
//vai retornar o nó balanceado, para ser o novo apontado
int fatorDeBalanceamento = getFatorDeBalanceamento(no->esquerda,no->direita);
if(fatorDeBalanceamento > 1){
//caso de desbalanceamento à esquerda (a árvore da esquerda está mais pesada)
if(getAltura(no->esquerda->esquerda) >= getAltura(no->esquerda->direita)){
//caso de rotação simples à esquerda (não existe filho à direia do filho à esquerda que seja mais pesado que o seu irmão à esquerda)
return rotacionarDireita(no);
}
else{
//caso de rotação dupla à esquerda (rotação esquerda-direita)
no->left = rotacionarEsquerda(no->esquerda); //primeiro vai colocar tudo depois do filho esquerdo à esquerda
return rotacionarDireita(no); //depois rotaciona à direita
}
}
if(fatorDeBalanceamento < 1){
//caso de desbalanceamento à direita (a árvore da direita está mais pesada)
if(getAltura(no->direita->direita) >= getAltura(no->direita->esquerda)){
//caso de rotação simples à direita (não existe filho à esquerda do filho à direita que seja mais pesado que o seu irmão à direita)
return rotacionarEsquerda(no);
}
else{
//caso de rotação dupla à direita (rotação direita-esquerda)
no->direita = rotacionarDireita(no->direita); //primeiro vai colocar a sub-árvore à direita na direita
return rotacionarEsquerda(no); //depois rotaciona à esquerda
}
}
//se não estiver desbalanceado
return no;
}
template <typename T>
TreeNode<T>* AVLTree<T>::inserirNo(TreeNode<T>* node, T dado){
//Vai retornar um nó pois se trata de uma função recursiva
if (node == nullptr){
return new TreeNode<T>(dado);
//se apontar para o nulo, vai retornar um novo nó, inserindo uma nova folha com sucesso
}
if (node->dado < dado){
//se o dado a ser inserido for maior, vai percorrer a sub-árvore à direita. Quando chegar ao nulo, vai retornar tudo.
node->direita = inserirNo(node->direita, dado);
}
if(node->dado > dado){
//se o dado a ser inserido for menor, vai percorrer a sub-árvore à esquerda. Quando chegar ao nulo, vai retornar tudo.
node->esquerda = inserirNo(node->esquerda, dado);
}
//atualiza a altura das folhas até a raíz
atualizaAltura(node);
//como é recursivo, vai balancear todos os nós até chegar na raíz, que vai ser balanceada e retornada como valor de root, quando chamamos inserir()
return balancearNo(node);
}
template <typename T>
void AVLTree<T>::delecaoRecursiva(TreeNode<T>* node){
if (node != nullptr){
delecaoRecursiva(node->esquerda);
delecaoRecursiva(node->direita);
delete node;
}
}
template <typename T>
void AVLTree<T>::printRecursivo(TreeNode<T>* node){
if (node != nullptr){
std::cout<<node->dado<<"t";
printRecursivo(node->esquerda);
printRecursivo(node->direita);
}
}
template <typename T>
AVLTree<T>::AVLTree(){raiz = nullptr;}
template <typename T>
void AVLTree<T>::inserir(T dado){
//Vai começar a inserção pela raiz
raiz = inserirNo(raiz, dado);
}
template <typename T>
AVLTree<T>::~AVLTree(){
//Destrutor, deve deletar todos os nós da árvore utilizando o algoritmo post-order
delecaoRecursiva(raiz);
}
template <typename T>
void AVLTree<T>::print(){
printRecursivo(raiz);
}
main.cpp
#include "../include/arvore.hpp"
int main(void){
AVLTree<int> a;
a.inserir(5);
a.inserir(2);
a.print();
}
Thanks in advance and sorry if it’s something stupid.
Tried compiling it from VSCode. Thought the command I was using to compile from terminal might have been wrong. Was surprised to find out VSCode gave out the same error.
user24336828 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.