게으른 개발자

[6]명품 C++ Programming -4장[객체 포인터와 객체 배열, 객체의 동적 생성](1)-[내용 정리] 본문

C++/명품 C++ Programming

[6]명품 C++ Programming -4장[객체 포인터와 객체 배열, 객체의 동적 생성](1)-[내용 정리]

Tomato_Coffee 2022. 5. 23. 12:02

[1]포인터
   1.함수에서 2개 이상의 인자을 넣어주거나 반환할때
   2.동적할당
   3.클래스를 쓸때
[2]클래스 쓸때 접근지정자(public, private)쓰는거 까먹지 말것.
[3]포인터는 만들고나서 특정값을 가르키는 해주는게 좋다. 
    int *p = nullptr; // 주소를 가르키지 않다는 뜻임--->주소값이 없다 라는 의미 
[4]포인터는 가리키고있는 그 주소가 중요함
   (*p)--> 그 주소에 있는 값을 의미함---> 교수님은 가로를 쳐주는 습관이 좋은거라고 말함
[5]왜 포인터에는 데이터 타입이 따라 붙는가? 그 주소에 가서 데이터 타입에 따라서 값을 표현해야되기 때문이다.

[6]Line 은 클래스임
   아래에는 클래스 포인터 설명예시이다.
   Line a;
   Line *p=nullptr; 
   Line *p=&a;
   *p.print() , (*p).print() 이거 2개는 서로 같은게  아니다 ...-> 우선 순위: [ . > *] 이기 때문에 실제로는

   *p.print()= *    (p.print()) 랑 같은 의미냐고 물어보는거와 똑같음.
   (*p).print(); == p->print();  같은 의미
   모를때는 ( )를 써주자 ---> 가독성을 높여준다

[7]클래스를 포인터로 이용하는 경우가 매우 흔하다. 

[8]************************************객체 배열은 default생성자가 있어야한다.**************************************
   Line arr[10];
   배열을 만들때 default 생성자가 없으면 컴파일 에러가 뜸 (빨간줄)
   ->  Line arr[10](1,2,3,4) : 이렇게 만들수 없음
   단, Line arr[3] ={ Line(3,4,5,6,), Line(4,5,6,7), Line(1,2,3,4)} 이렇게는 가능함 이때는 디폴트 생성자가 없어도 됨, 이렇       게 초기값을 주면서 만들수있음
   --> int arr2[ ] = {1,2,3}; 위 방법은  이 방식을 이용한거임
   2차원 배열: Line arr[2][3] = { { Line(3,4,5,6,), Line(4,5,6,7), Line(5,7,9,11)}, { Line(2,2,2,2,), Line(3,3,3,3), Line(1,1,1,1)}}//      3개씩 2 묶음


   배열의 각 원소 객체마다 생성자가 실행이 됨
   배열의 각 객체마다 소멸자 호출, 생성의 반대순으로 소멸됨 

   마찬가지로 배열은 포인터로 접근할수있다
   Line *q = &(arr[0][0]);// 이렇게 하면 2차원 배열을 1차원 배열처럼 순차적으로 접근할수있다.

[9]스텍 메모리에 main 함수가 만들어진다

[10]포인터 쓰는 이유 40분
   1. 함수에서 인자로 쓸때
   2. 동적메모리
   3.  클래스 ---> copy를 피하기 위해서 포인터를 사용
   덩치가 커서 값으로 카피하기 싫을때 포인터를 쓴다
   ## 서로 다른 함수는 서로간의 메모리 영역이 다르다.--> 그래서 함수의 인자로 주는 것은 카피를 통해 이루어 진         다.   . 클래스도 마찬가지 이다.
   예를 들어서 이미지를 저장하는 클래스를 만든다고 하자
   사진 정보는 가볍지 않다.---> 12000만개의 점으로 이루어져 있다. ( 점하나당 3byte 따라서 36Mb의 용량을 차지함)
   ---> 그래서 메모리 복사가 쉽지 않다.  너무 heavy하다. 따라서 카피를 피하기 위해서 포인터를 이용 (카피를 최소화     하기 위해, 주소만 주면 간단하니까)

[11]

   정적할당은 지금 scope 안에서 만들어진다.
   ex) main에 정적배열을 선언한다면 이떄 큰 배열을 만들지 못한다, 왜냐하면 스텍메모리에 main함수가 만들어지는       대 스텍메모리는 크기가 작기 때문

[12]

동적메모리란?
#include<cstdlib>---->#include<iostream> 에 포함되어있음
int * darr1=(int*) malloc(sizeof(int)*10)// 이방식은 c언어에서 동적할당을 할때 사용하는 방식임.
Line *darr=(Line*)malloc(sizeof(Line)*10)// 요놈은 생성이랑 전혀 상관이 없는 놈임. 이렇게 만들때 생성자를 호출하지 않음
free(darr1);// 메모리 해제
free(darr2);

동적할당을 만들때 c++부터는 반드시 malloc, free 를 안쓰고, new 를 통해서 생성을 한다.
int *darr1=new int[10];// new 라고 쓴다.
Line *darr2 = new Line[10];
Circle *pCircle = new Circle();// 1개 만들때
int *pint = new int; //1개 만들때
char *pChar = new char; //1개 만들때
클래스의 동적할당도 당연히 생성자를 호출한다. 이때 생성자는 디폴트가 호출된다. 따라서 디폴트(default) 생성자가 이때 필요하다. 디폴트 생성자가 없으면 빨간줄이 생김.
힙 메모리가 부족하면 new는 NULL을 리턴한다. 따라서 new의 리턴값이 NULL인지 검사하는것이 좋다.
delete [] darr1;// 꺽쇠만 써주고 메모리를 해제,꺽쇠안에 배열의 크기를 써줄 필요없다. 꺽쇠는 배열을 의미한다. 배열을 지운다는 뜻이다. *****free 대신 이걸 씀*****
하나를 만들고 싶을때 ::   => Line *dp =new Line(); or new Line[1] or 하나를 만들때만 생성자를 써줄수 있다.(인자가 있는걸로)-> new Line(2,3,4,5);
delete dp;//  원소하나를 만들때 이렇게 지운다.

이미 반환한 메모리를 중복 반환할수없다.---> 중복반환 하게되면 오류가 발생함.

 

[13]

동적할당은 갯수가 정해지지 않은 양에서 사용
main메모리에서 동적할당을 하게 되면, 메모리 자체는 heap 메모리에 동적할당이 된다. -> main함수가 사라져도 데이터가 사라지지 않는다.
heap 메모리는 겁나 크다. --> 큰 배열을 만들수있다.
배열은 동적할당시 초기화가 불가능하다.
그래서delete를 해줘야한다.
 delete를 2번 하면 안된다

[14]

배열은 동적 할당 시 ****초기화 불가능******하다. 생성자를 쓸수 없다.
int *parr = new int [10](234);// 이렇게 못씀
int *parr = new int (234)[10];// 이렇게 못씀
----> int *parr = new int[10]; 이렇게 쓰고 default 생성자가 호출됨

delete 할때 [ ]를 생략하면 비정상적인 결과가 일어남. --->
int*p = new int [10];
delete p; // 이렇게 해주면 맨 앞 1개만 지워짐
int *q = new int;
delete [ ] q; // 이때 에러발생

 

[15]

안전한 방법-> delete 해주고 nullptr을 해준다. 
delete dp;****************** 이렇게 해주고 다음에 nullptr해주자.!!
dp = nullptr;/////^&*^&*^(^ 꼭 이렇게 해주자)
이렇게 되면 delete는 nullptr을 무시하기 때문에 2번 지워도 아무런 문제가 되지 않는다. 실수로 2번 이상 지워도 됨
***delete 주의 사항*****

 

[16]

int n;
int*p = &n;
delete p; // 원래 주인이 n이여서 못지움 -> 오류발생

 

[17] new를 할때 클래스에서 default 생성자가 없으면 에러발생**************************내가 겪은 실수임, 주의할것

 

[18] 더블 포인터를 이용해서 default 생성자가 없어도 만들수 있다-----> 너무 이해가 안감---------> 여기부터 진행 1시부터 진행
하나를 만들때 생성자를 쓸수 있다 를 응용한다.

 

[19] 

default 생성자가 없을때 , 또는 배열을 각각 다른 생성자를 통해서 초기화를 하고 싶을때
1. 각각 하나의 원소는 초기화가 가능하다 ex) Line *darr2= new Line(2,3,4,5); 하나를 만들때 생성자를 쓸수있다.
Line *darr2;
darr2= new Line(2,2,3,4);
2. 그래서 darr2를 10개를 만들 생각을 하게됨
-> Line *darr2[10];// 이것의 의미는 (Line *) darr2[10]; 이거임--> 포인터 데이터 타입 10개를 만든다.
darr2[0]= new Line(2,2,3,4);
darr2[1]= new Line(2,2,3,4);
darr2[2]= new Line(2,2,3,4);
즉. for(int i=0;i<10;i++)
darr2[i] = new Line(i,i,3*i,4*i); // 이렇게 쓸수있다. // 각각의 포인터에  new를 통해서 각각의 원소 하나씩을 만들게되면 우리가 원하는 생성자를 호출하는 방법임.

for(int i=0, i<10;i++)// 출력
darr2[i] ->print();
for(int i=0;i<10;i++)// 지울때
delete darr2[i]; // 각각의 포인터에 접근해서 지워준다. , 포인터 자체는 생성자가 생성되지 않는다.(메모리 주소일뿐이다)
delete[ ] darr2[i];//위에 코드와 이게 뭔 차이지?&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
위 방식의 장점은 내가 원하는 생성자를 각각의 생성자를 통해서 각가의 원소를 만들수있다.
단점은 정적배열이다.---> 정적할당은 스텍메모리에서 만들어지기 때문에 큰 배열을 못만든다.
이걸 해결하려면  아래방식을 이용하면 된다

[20]

int a[10]; // 이걸 아래방식으로 동적으로 할당함
int*a= new int [10];

int size =100;
Line**darr2 = new Line* [size];// 포인터의 배열의 생겨나는것이다. , 더블포인터이다.----> 배열이 만들어 지는데 각각이 포인터이다
for(int i=0;i<size;i++)
darr2[i]= new Line(i,i,3*i,4*i);// 각각은 포인터 이다.
각각의 포인터는 new로 어딘가의 Line을 가리킨다 , 어딘가의 동적 메모리를 가리킨다.

일반적인 배열과 다른점은 그냥  정적으로 Line의 배열을 만들면 1열로 메모리가 들어선다.
이경우는 포인터의 배열을 만들었기 때문에 각각이 서로다른위치에 있는 메모리를 참조한다.//@!#!$@# 이 방법 엄청 많이 쓰인다.

이경우 지울떄 조심해야함 $%$%$#%
for(int i=0;i<10;i++)// 지울때
delete darr2[i];
delete [ ] darr2;// 이걸해줘야함

(위에 내용은 책에 없는 내용이다)---[20]은 책에 없는 내용임

 

[21]

delete를 하는 이유--> 동적메모리를 했을때, 메모리 누수를 방지해야한다.
실제로는 왠만큼 누수를 해서 체감하기는 어렵지만, 문제는 특정함수속에서 메모리를 만들고 까먹고 지우지 않고 그 함수를 계속 쓴다면 문제가됨
천번, 만번 부르게 되면 힙메모리가 부족해짐

 

[22]

this 포인터(나의 주소)
클래스속에서 자기 자신을 가리키는 포인터

[23]

#include<cmath>---> sqrt() : 루트 라는 의미

[24]

클래스로 인자로 받을때는 포인터를 인자로 쓴다.****
ex)
bool compare (Line *a, Line *b)
{
if(a->getLength()> b->getLength())
return true;
return false;
}
bool Line::isLong(Line *in)
{ if(compare (this, in) ==true) return true;
return false;}

[25]

책에서 this 포인터 설명이 좀 이상해서 교수님이 설명해주심
this라는 포인터는 클래스의 멤버변수 속에서만 쓸수있다.************중요 멤버함수속에서 자기 자신을 지칭하고 싶을때 this포인터를 씀
멤버변수를 만들었는데, 함수의 인자로 똑같은 이름을 쓰는건 엄청 안좋은 습관이다.
멤버 함수속에서 자기자신을 넘겨줄 필요가 있을때 this포인터를 사용함
main에서는 this가 없다.-> 빨간줄이 뜬다.

[26]

비주얼 스튜디오에서 ctrl+ 함수를 클릭하면 api 가나옴(함수의 속성이 나옴)

[27]

1차원 배열
int*arr = new int[10];
delete [ ] arr;
//포인터하나가 배열을 만들수있음
// 2차원 배열은 배열이 여러개--> 포인터의 배열을 만들자
int *arr[10];// 포인터의 배열 -> 10개의 포인터
for(int i=0;i<10;i++)
arr[i] =new int[5];/// 이건  메모리에 5개씩 파편화 되어있다.

for(int i=0;i<10;i++)// 이렇게 지움
delete[ ] arr[i];&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&위에 이런줄이랑 비교 

[28]

int arr[10][5]와 다른점---> 이건 일렬로 쭉 메모리에 만들어짐

int n=10;
int m =5;
int arr[n][m] // 아래랑 비교
int** arr= new int*[n];// 포인터의 배열

for(int i=0;i<n;i++)
arr[i] = new int [m];

for(int i=0;i<n;i++)
delete[ ] arr[i];
delete [ ] arr; *****************************************************이제 좀 이해감

[29]

 

void func1(int arr[ ] [5])// 이렇게 하면 동적으로 만들면 이렇게 만들면 안됨
{   
   arr[1][3]---> 2번째 묶음의 4번째놈/// 이게 가능한 이유는 정적배열이기 때문이다// 메모리에 일렬로 되어있기 때문
}
void func2(int **arr){// 동적으로 만들민 이렇게 함수를 만들어야함

arr[1][3];//2번째 포인터가 의미하는 주소로 가서 4번째 값을 읽으라는 뜻

}
int main( ) {

int brr[10][5];

func1(brr);

int ** arr = new int *[n];
for(int i=0;i
arr[i]=new int [m];
func1(arr); // 이렇게 하면 에러가 남, 메모리의 구조가 정적배열과 다르기 때문임.
func2(arr);

for(int i=0;i
delete[] arr[i];

delete[] arr;
}

[29]

string a("sejong university");
cout<< a<< endl;
// 함수로 문자열을 넘겨주는 법을 배워보자

char text[ ]= "software";
func3(text);// 이건 문제 없이 됨
void func3(char str[ ])  or void func3(char *str) // 이거 문제 없음
{    }

그러나!! func3("software") 이건 안됨? 왜 일까?

문자열 " " , int n=5, ----> 이런걸 literal(리터럴) 이라고함, 하드코딩으로 박아넣는걸 리터럴이라고함

char text [ ] = " software";// 우리가 쓰고 있는 컴파일러는 우리가 쓰고 있으면 , 코드에 박혀있는 리터럴은 미리 따로 저장한다.// 컴파일러는 이걸 컴파일하면서 어딘가에 "software"을 하드코딩으로 가지고 있음 
/ / 값을 줄때 저장한 값을 던져준다. 
text[0]='S'; // 여기서 "software"를 변화시키는게 아님... 여기 text 포인터는 내용과 똑같은 메모리를 가지는 어딘가에 배열을 만들고 따로 저장한다.(복사본을 저장한다)// 즉 복사본의 값을 변화시켜주는것이다.
func3("software")에서 컴파일러는 똑같은 문자열을 보면 아까 그놈(리터럴)이라고 생각한다. 그래서 아까 그거(리터럴)라고 가리키게된다. 
// func3이 넘겨주는 software 이거는  어딘가에 메모리를 만드는것이 아니다. 컴파일러가 가지고있는 (변경시킬수없는) 메모리는 주는 것이다. 그래서 func3("software")에 빨간줄이 뜸

 func3("software") 이렇게 호출할때는 약속을 해야함//  아래처럼 바꾸면 에러안남
void func3(const char *str or str[ ] )// 상수로 지정해주면됨---> 포인터의 값을 변경시키지 않을거라는 약속임. str값을 바꿀수없음
{
str[0];// 접근하는건 상관없음

str[0]='S'// 근데 값을 바꾸는건 안됨--> 상수이기 때문에(컴파일 에러발생)

}