-
2023-03-2823:08:27 #3777169009 76.***.207.158 729
#include <bits/stdc++.h> using namespace std; class node { public: int data; node* next; node(int value) { data = value; next = NULL; } }; void insertAtHead(node*& head, int val) { node* n = new node(val); n->next = head; head = n; } void insertAfter(node* head, int key, int val) { node* n = new node(val); if (key == head->data) { n->next = head->next; head->next = n; return; } node* temp = head; while (temp->data != key) { temp = temp->next; // key를 만날때까지 전진 if (temp == NULL) { // key가 없는 경우 return; } } n->next = temp->next; temp->next = n; // head=temp; // 질문1: 이 temp 포인터는 코멘트할경우 어떻게 리턴되는것인가? } void print(node*& head) { node* temp = head; while (temp != NULL) { cout << temp->data << " -> "; temp = temp->next; } cout << "NULL" << endl; } int main() { node* head = NULL; insertAtHead(head, 1); insertAtHead(head, 2); insertAfter(head, 1, 3); insertAfter(head, 3, 6); print(head); cout << endl; return 0; }아웃풋:
2 -> 1 -> 3 -> 6 -> NULL질문 1. 이게 주요 질문인데…
위의 함수 insertAfter( ) 에서 마지막 라인
// head=temp;
이걸 코멘트해도 실행에 문제가 없을까요? 아니면 문제가 생길까요? 아니면 결과가 다를까요? 실행에 문제가 없다면 이유가 뭘까요? (포인터에 이해에 관한것인데 헷갈림)질문 2. 위 프로그램은 메모리 리크가 발생하는데…메모리 리크를 없애려면 어떻게 딜리트를 위에서 잘 이용해야 할까요?
그리고 혹시 재미로 연습삼아 해보고 싶은 분은 insertAfter() 를 응용해서 insertBefore() 를 한번 임플리멘트 해보세요. ㅎㅎ
-
-
insertAfter()를 짜는게 숙제인가요? 현재의 insertAfter()가 특별히 허접(죄송)하게 보여서요.
이 function에서 head의 scope가 뭔지 생각해보세요. head에 뭘 assign하건 이 function에서 return하면 끝입니다. *head에 뭔가를 저장한다면 head에 들어있는 address에다가 저장하는 것이니까 얘기가 달라질 수 있고요. *와 &의 의미를 알면 헷갈리게 없습니다. A pointer of a pointer variable 쯤 되면 조금 헷갈릴 수도 있겠으나…
그밖에 insertAfter의 당장 보이는 문제점을 얘기하자면, null check도 없고 처음에 head에 대한 special case를 넣었는데 그럴 필요가 없습니다. Loop의 initial & terminal condition을 적절히 세트하면 바로 head부터 loop에서 visit하면 됩니다.
insertBefore() 숙제는 스스로 해보세요. 보너스 포인트인가요?
-
허접하게 보인다니…다들 대단들 하세요. 널체크는 사실 중요한거 아니고. 나는 솔직히 이거 링크드 리스트 볼때는 그때는 이해했다고 생각되드라도, 일주일만 안보다가 다시 보면 내가 그전에 이해했던 모든 포인터의 장난 논리가 다 허공 어딘가로 날라가버리고 모든게 다 다시 헷갈리기 시작하는데…ㅋㅋㅋ (**) 와 (*&) 차이도 그렇고 ㅋㅋ ㅋ
원글은 다른거보다,
temp 와 head 포인터들 때문에 헷갈려서 올린거에요. 헤드를 템프에 어싸인해주었으니, 사실 템프가 헤드와 같은 메모리를 포인팅하는 걸로 그냥 생각해버리면 되는데..그래서 결국 템프 포인터는 헤드 포인터와 같은것인데, 어쩐지 두 변수가 자꾸 포인터가 아닐경우처럼, 말하자면 인티저 변수일 경우처럼, 템프는 헤드로 초기화시켜주었을뿐 서로 다른거처럼 그래서 헤드는 계속해서 안변하고 그대로 있고 템프만 계속 변하는걸로 그런식으로 계속 해서 생각되어서 말이죠. 결국은 이 경우는 포인터 타입들이니까 템프가 곧 헤드와 같은것인데… 그래서 굳이 // head=temp; 를 쓰지 않아도 되는데 이걸 써줘야 이해가 분명해지는것처럼 생각되고….ㅋㅋㅋ …우 진짜 …헷갈려. 사실 이런 방식이 계속 트리에서도 쓰는 방식인데….그리고 위의 print() 에서도 써먹은 방법이고요. insertAfter() 에서도 솔직히 temp를 도입할 필요가 처음부터 없었고 temp 대신 head 로 그냥 다 써버려도 아무 문제없이 똑같은 함수이고,
print() 에서도 마찬가지로,void print2(node*& head) { //node* temp = head; // 이거 솔직히 필요없는데 굳이 필요한걸로 계속 혼동되. 아니, 필요한가? 아냐 필요없을거야? 아니, 그래도 필요한 상황이 있는가? 아...몰라 몰라...ㅋㅋㅋ ㅋㅋ ㅋ 뭘 여기까지 엿보고 그래? ㅋㅋㅋ 🤣🤣🤣🤑 while (head != NULL) { cout << head->data << " -> "; head = head->next; } cout << "NULL" << endl; }사실 print2( ) 와 print () 가 이그잭틀리 같은것인데….내 머리한켠에서는 자꾸 ” 아니야 같지 않아…프린트()가 프린트2() 보다 더 안전한 방법이야”…(마찬가지로 head 를 직접써주는것보다 살짝 temp 로 바꾸어서 써주는 insertAfter 함수가 더 안전한 함수야 라고) 이렇게 자꾸 생각하게 된단 말이죠. 물론 이 경우야 두 함수가 동일하지만, 굳이 //node* temp = head; 를 써줘야 안전한 경우가 있긴 있겟죠.
-
위의 내 코멘트는 올바른 이해가 아니고,
혼동으로 인한 잘못된 이해를 기반으로한 코멘트입니다. 여전히 혼동중. 분명히 템프와 헤드는 같은 게 아니라 다르고 헤드 포인터의 노드는 변하지 않고 정해져 있고 템프 노드는 전진하므로 계속 변화함.
-
-
-
insertAfter에서 (1)key값을 발견했을 때만 new를 쓰던지 아니면 (2)발견못했을시 delete로 다시 메모리 해제.
제 경우 무조건 (1)입니다. 쓸데없이 new+delete를 남발할 필요가 없지요.
그리고 malloc후 null check은 필수입니다. 제 경우 안하면 코드리뷰 혹은 Coverity에서 난리가 납니다.
참고로 위 모든 함수가 void로 되어 있던데 상업용코드에서는 디버깅을 위해 가능하면 에러코드를 리턴하게 합니다.
만일 메모리 에러가 나게 되면 결과가 더이상 valid하지 않으니 main에서 모든 실행을 중단시켜야 하지요.-
좋은 생각이네요.
함수가 보이드 함수일경우, 함수 아규먼트로 들어간 포인터가 최종적으로 어떻게 바뀌어 메인에 전달되는지 그게 내 혼동의 핵심이에요. 이경우 헤드 포인터로 들어갔는데 왜 헤드가 아닌 템프의 포인터의 값을 메인에서 전달받는지….아예 처음부터 템프없이 헤드로 쓰면 혼동이 없을지도 모르지만 템프를 쓸수밖에 없는 상황들이 사실 아주 많은거 같아서….솔직히 서큘라링크나 트리에서도 다 사실은 템프/커런트 포인터들을 함수안에서 템포러리로 만든후 헤드/루트 포인터로 초기화한후 대부분 템프 포인터를 전진시키면서 코딩을 시작하는거라서…그런데 그경우들은 사실 함수의 리턴값들이 분명히 헤드나 루트 노드의 포인터를 Node * fun(…) 처럼 리턴해주는경우라 헷갈리지 않는데 이경우는 함수의 리턴이 보이드 void fun( node* head) 인데다가 헤드가 아니라 템프로 변경해준것을 함수안에서 포인터변화를 따라가며 봐야하는데….왜 헤드가 아니라 함수의 아규먼트로 쓰이지도 않은 temp가 메인에 전달되는지…처음엔 이해했다고 생각했는데, 다시생각하면 또 혼동되고…결국 제대로 이해한게 아닌거 같고…이거 뿌리를 뽑아 버려야 할거 같은데…
-
이런 헷갈리는 보이드 함수의 예로 아래와 같은 함수가 또 있네요.
using namespace std; void toUpper(char string[]) { for(char* p=string; *p !='\0'; p++) { if(*p >= 'a' && *p <= 'z') //Only if it's a lower letter { *p -= 32; } } // char* q = string; *q='p'; *(q+1)='q'; //pqCDEFGHIJKLMNOPQRSTUVWX // 포인터의 변경된것은 원래의 포인터 변수를 쓰거나 말거나 상관없이 변경된다. // string=q; // 이걸 써주면 이해에 도움은 되겠지만, 안써줘도 상관없다. }; int main(void) { char message2[5] = {'a','b','c'}; char message[25] = {'a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x'}; toUpper(message); cout << endl << message << endl << endl; // ABCDEFGHIJKLMNOPQRSTUVWX cout << endl << message2 << endl << endl; //abc return 0; };
-
-
-
아래 처럼 while 루프를 for 루프로 고쳐서 다시 쓰면, temp 와 head의 관계에 대해서 쪼끔 이해가 편해지는듯… 😜
void insertAfter(node* head, int key, int val) { node* n = new node(val); if (key == head->data) { n->next = head->next; head->next = n; return; } node* temp ; for( temp = head; temp->data != key; temp = temp->next) // 템프는 헤드로 초기화시켜서 링크드리스트를 아이터레이터처럼 트래버스 시켜줌. 헤드와 같은게 아니라. if (temp == NULL) // key가 없는 경우 return; n->next = temp->next; temp->next = n; } -
이제는 이해했다고 생각했는데….그러나 꼬리를 무는 다음 질문. 왜 아래 함수는 그럼….. 실행이………. 안될까?
void insertAtHead(node** head, int val) { node* temp = *head; // 오리지날 함수에서 이렇게 살짝 바꾸어줌 node* n = new node(val); n->next = temp; temp = n; } int main() { node* head = NULL; insertAtHead(&head, 1); insertAtHead(&head, 2); print(head); cout << endl; return 0; }마찬가지로 아래도 역시 실행 안됨
void insertAtHead(node*& head, int val) { node* temp = head; // 오리지날 함수에서 이렇게 살짝 바꾸어줌 node* n = new node(val); n->next = temp; temp = n; } int main() { node* head = NULL; insertAtHead(head, 1); insertAtHead(head, 2); print(head); cout << endl; return 0; }-
그런데….이 경우엔
함수의 마지막에 다음과 같이 어싸인을 꼭 해주어야만 제대로 실행된다.head=temp;왜? 왜? 왜? 이 함수의 경우는 뭐가 달라서 이 어싸인을 익스플리시트하게 해주어야 하나? 결국 마지막 질문은 함수아규먼트에서 node *& head 를 썼느냐 node* head 를 썻느냐의 차이에 대한 질문으로 귀결되는것일까?
-
-
summay 정리: 결국 보이드 린턴 함수를 만들기 위한 약간의 혼동의 값은 지불해야 하는듯.
그런데 print()와 print3() 이나 insertAfter(head,1,21); 와 insertAfter1(head,1,21); 경우는 언뜻 서로 같아보인다…어떤 경우에 차이가 드러나는 경우가 생길까?#include <bits/stdc++.h> using namespace std; class node { public: int data; node* next; node(int value) { data = value; next = NULL; } }; void insertAfter(node* head, int key, int val) { node* n = new node(val); if (key == head->data) { n->next = head->next; head->next = n; return; } node* temp ; for( temp = head; temp->data != key; temp = temp->next) // 템프는 헤드로 초기화시켜서 링크드리스트를 아이터레이터처럼 트래버스 시켜줌. 헤드와 같은게 아니라. if (temp == NULL) // key가 없는 경우 return; n->next = temp->next; temp->next = n; } void insertAfter1(node*& head, int key, int val) { node* n = new node(val); if (key == head->data) { n->next = head->next; head->next = n; return; } node* temp ; for( temp = head; temp->data != key; temp = temp->next) // 템프는 헤드로 초기화시켜서 링크드리스트를 아이터레이터처럼 트래버스 시켜줌. 헤드와 같은게 아니라. if (temp == NULL) // key가 없는 경우 return; n->next = temp->next; temp->next = n; } node* deleteFirst1(node *head) { if(head !=NULL) { node *temp = head; head = head->next; free(temp); } return head; } void deleteFirst2(node*& head) { if(head !=NULL) { node *temp = head; head = head->next; free(temp); } //return head; } void insertAtHead(node*& head, int val) { node* n = new node(val); n->next = head; head = n; } node* insertAtHead1(node* head, int val) { node* temp =head; // 오리지날 함수에서 이렇게 살짝 바꾸어줌 node* n = new node(val); n->next = temp; temp = n; head=temp; return head; } void insertAtHead2(node** head, int val) { node* temp =*head; // 오리지날 함수에서 이렇게 살짝 바꾸어줌 node* n = new node(val); n->next = temp; temp = n; *head=temp; } void insertAtHead3(node* head, int val) { node* n = new node(val); n->next = head; head = n; } node* insertAtHead4(node* head, int val) { node* n = new node(val); n->next = head; head = n; return head; } void print(node*& head) { node* temp = head; while (temp != NULL) { cout << temp->data << " -> "; temp = temp->next; } cout << "NULL" << endl; } void print2(node*& head) // 이건 분명히 print(node*& head) 와 다르다. { while (head != NULL) { cout << head->data << " -> "; head = head->next; } cout << "NULL" << endl; } void print3(node* head) // 프린트1은 프린트3과 같은거 처럼 보인다. { node* temp = head; while (temp != NULL) { cout << temp->data << " -> "; temp = temp->next; } cout << "NULL" << endl; } void print4(node* head) // 결국 프린트는 이게 가장 간단하며 좋은 방법인듯 { //node* temp = head; while (head != NULL) { cout << head->data << " -> "; head = head->next; } cout << "NULL" << endl; } int main() { node* head = NULL; insertAtHead(head, 1); // 1 -> NULL insertAtHead(head, 2); // 2 -> 1 -> NULL //insertAtHead3(head, 1); // error //insertAtHead3(head, 2); // error //head=insertAtHead4(head, 1); // head=insertAtHead4(head, 2); //insertAtHead1(head, 1); // NULL //insertAtHead1(head, 2); // NULL //head=insertAtHead1(head, 1); // 1 -> NULL //head=insertAtHead1(head, 2); // 2 -> 1 -> NULL //insertAtHead2(&head, 1); // 1 -> NULL //insertAtHead2(&head, 2); // 2 -> 1 -> NULL print(head); cout << endl; //deleteFirst1(head); // 0 -> 1 -> NULL // Wrong! //head=deleteFirst1(head); // 1 -> NULL deleteFirst2(head); // 1 -> NULL // Correct! print(head); cout << endl; insertAfter(head,1,21); //insertAfter1(head,1,21); print(head); cout << endl; return 0; }
-