🙇‍♀️포인터

🪐포인터 기초

  • 지금까지 사용한 방법
    • number라는 이름의 4바이트 정수 타입의 바구니를 만든다
    • number라는 변수 스택 메모리에 할당
    • number = 1라 함은, number 바구니에 1이라는 숫자를 넣으라는 의미.
    • 따라서 스택 메모리에 있는 특정 주소(number 바구니)에 우리가 원하는 값을 넣은 셈.
    • number는 비유하자면 메모리에 이름을 붙인 것 (찰떡같이 알아들어서)
    • 나쁘지 않고 편리한데, 단점은 TextRPG 원본 수정

int number = 1;

int ptr;

  • int형 변수를 스택 메모리에 만들어주세요. 그 이름은 ptr이고 ptr에 접근하거나 ptr을 고치면 해당 메모리에 넣으면 됩니다

  • TYPE* 변수이름;
    • 일단 2가지 요소
      • TYPE
      • *
  • 바구니는 바구니인데… [주소를 저장하는 바구니다!]
    • 변수 선언할 떄 * 등장했다 -> 포인터 = 주소
    • fc) 포인터라는 바구니는 4바이트(32비트) or 8바이트(64비트) 고정 크기

int* ptr = &number;

  • 근데 남의 주소를 갖고 뭘 하라는거지?
    • 추가 문법 : [주소를 저장하는 바구니]가 가리키는 주소로 가서 무엇인가를 해라!
    • *변수이름 = 값;
  • 포탈을 타고 순간이동 한다고 생각해보자.
    • *이 여러번 등장하니 헷갈리는데, 사용 시점에 따라서 구분해서 기억하자
      • 변수 선언(주소를 저장하는 바구니다!)
      • 사용할 떄(포탈 타고 순간이동)

int value1 = *ptr;

*ptr = 2;

  • TYPE은 왜 붙여줄까?
    • ‘*’ = 포인터의 의미 = 주소를 저장하는 바구니 = 4or8바이트 고정크기
  • 주소에 가면 뭐가 있는데?
    • ex) 결혼식 청접장에 있는 주소 = 예식장 주소
    • ex) 명함에 있는 주소 = 회사 주소
    • ‘*’ = 포인터 (주소 담는 바구니)
  • 타입의 불일치

__int64* ptr2 = (__int64*)&number;

  • 포인터 활용
void SetHp(int* hp)
{
	*hp = 100;
}

// [매개변수][RET][지역변수(hp 100)][매개변수(주소)][RET][지역변수]
int main()
{
	int hp = 1;
	SetHp(&hp);
}

🪐포인터 연산

  1. 주소 연산자 (&)
  2. 산술 연산자 (+ -)
  3. 간접 연산자 (*)
  4. 간접 멤버 연산자 (->)
  • 주소 연산자 (&)
    • 해당 변수의 주소를 알려주세요
    • 더 정확히 말하면 해당 변수 타입(TYPE)에 따라서 TYPE* 반환 int* pointer = &number;
  • 산술 연산자 (+ -) number += 1; // 1증가했다 (!)

  • int*
    • ‘*’ : 포인터 타입이네! (8바이트) 주소를 담는 바구니!
    • int : 주소를 따라가면 int(4바이트 정수형 바구니)가 있다고 가정해라!

    • [!] 포이터에서 +나 -등 산술 연산으로 1을 더하거나 빼면,
    • 정말 ‘그 숫자’를 더하고 뺴라는 의미가 아니다.
    • 한번에 TYPE의 크기만큼을 이동하라!
    • 다음/이전 바구니로 이동하고 싶다 « [바구니 단위]의 이동으로
    • 즉, 1을 더하면 = 바구니 1개 이동시켜라
    • 3을 더하면 = 바구니 3개 이동시켜라

pointer += 1; // 4증가했다 (?)

  • 간접 연산자 (*)
    • 포탈을 타고 해당 주소로 슝~ 이동

number = 3;*pointer = 3;는 동일한 뜻!

  • 간접 멤버 연산자 (->)
    • ‘*’ 간접 연산자 (포탈 타고 해당 주소로 GOGO)
    • . 구조체의 특정 멤버를 다룰 떄 사용 (어셈블리 언어로 까보면 . 사실상 덧셈)
    • -> 는 *와 . 한방에 !
struct Player
{
	int hp; // +0
	int damage; // +4
};
Player player;
player.hp = 100;
player.damage = 10;

Player* playerPtr = &player;
(*playerPtr).hp = 200;
(*playerPtr).damage = 200;
playerPtr->hp = 200;
playerPtr->damage = 200;

사실상 (*playerPtr).hp = 200;playerPtr->hp = 200;는 완전히 동일하다

🪐참조 기초

  • 값을 수정하지 않는다면, 양쪽 다 일단 문제 없음
  1. 값 전달 방식
    • [매개변수][RET][지역변수(info)][매개변수(info)][RET][지역변수]
    • 복사의 방식
      void PrintInfoByCopy(StatInfo info)
      {
       cout << "________________________" << endl;
       cout << "HP : " << info.hp << endl;
       cout << "ATT : " << info.attack << endl;
       cout << "DEF : " << info.defence << endl;
      }
      
  2. 주소 전달 방식
    • [매개변수][RET][지역변수(info)][매개변수(&info)][RET][지역변수]
    • 참조의 방식
      void PrintInfoByPtr(StatInfo* info)
      {
       cout << "________________________" << endl;
       cout << "HP : " << info->hp << endl;
       cout << "ATT : " << info->attack << endl;
       cout << "DEF : " << info->defence << endl;
      }
      
  • StatInfo 구조체가 10000바이트짜리 대형 구조체라면?
    • (값 전달) StatInfo로 넘기면 1000바이트가 복사되는 것
    • (주소 전달) StatInfo*는 8바이트
    • (참조 전달) StatInfo&는 8바이트
    • 성능차이가 어마어마하게 날 수가 있다
  1. 참조 전달 방식
    • 값 전달처럼 편리하게 사용하고!
    • 주소 전달처럼 주소값을 이용해 진퉁을 건드리는!
    • 일석이조의 방식!
      void PrintInfoByRef(StatInfo& info)
      {
       cout << "________________________" << endl;
       cout << "HP : " << info.hp << endl;
       cout << "ATT : " << info.attack << endl;
       cout << "DEF : " << info.defence << endl;
      }
      
  • 참조 전달
    • 로우레벨(어셈블리) 관점에서 실제 작동 방식은 int*와 동일하다
    • 실제로 실행해보면 포인터랑 100% 똑같다
  • C++ 관점에서는 number라는 바구니에 또 다른 이름을 부여한 것.
  • number라는 바구니에 reference라는 다른 이름을 지어준 것.
  • 앞으로 reference 바구니에다가 뭘 꺼내거나 넣으면,
  • 실제 number 바구니(진퉁에다가) 그 값을 꺼내거나 넣으면 됨

  • 그런데 귀찮게 또 다른 이름을 짓는 이유는?
    • 그냥 number = 3;이라고 해도 똑같은데…
    • 참조 전달 때문!

int number = 1;일 떄,

int* pointer = &number;
*pointer = 2;

int& reference = number;
reference = 3;

는 어셈블리로 까보면 완전히 동일하다!

🪐포인터 vs 참조

  • 포인터 vs 참조 세기의 대결
    • 성능 : 똑같음
    • 편의성 : 참조 승!
  1. 편의성 관련
    • 편의성이 좋다는게 꼭 장점만은 아니다.
    • 포인터는 주소를 넘기니 확실하게 원본을 넘긴다는 힌트를 줄 수 있는데,
    • 참조는 자연스럽게 모르고 지나칠 수도 있음!
      • ex) 마음대로 고친다면?
    • const를 사용해서 이런 마음대로 고치는 부분 개선 가능
  • 참고로 포인터도 const를 사용 가능.
  • ‘*’ 기준으로 앞에 붙이느냐, 뒤에 붙이느냐에 따라 의미가 달라진다.
  1. 초기화 여부
    • 참조 타입은 바구니의 2번쨰 이름 (별칭?)
    • -> 참조하는 대상이 없으면 안됨 * 반면 포인터는 그냥 어떤~ 주소라는 의미
    • -> 대상이 실존하지 않을 수도 있음 * 포인터에서 ‘없다’는 의미로 ?
    • nullptr!
void PrintInfo(StatInfo* info)
{
	cout << "________________________" << endl;
	cout << "HP : " << info->hp << endl;
	cout << "ATT : " << info->attack << endl;
	cout << "DEF : " << info->defence << endl;

	// 별 뒤에 붙인다면?
	// StatInfo* const info
	// info라는 바구니의 내용물(주소)을 바꿀 수 없음
	// info는 주소값을 갖는 바구니 -> 이 주소값이 고정이다!

	// 별 앞에 붙인다면?
	// const StatInfo* info
	// info가 '가리키고 있는' 바구니의 내용물을 바꿀 수 없음
	// '원격' 바구니의 내용물을 바꿀 수 없음

	// info[ 주소값 ] 주소값[ 데이터 ]
	//info = &globalInfo;
	//info->hp = 10000;

}
void PrintInfo(const StatInfo& info)
{
	cout << "________________________" << endl;
	cout << "HP : " << info.hp << endl;
	cout << "ATT : " << info.attack << endl;
	cout << "DEF : " << info.defence << endl;
}
StatInfo* pointer = nullptr;
pointer = &info;
PrintInfo(&info);

StatInfo& reference = info;
PrintInfo(reference);

참조는 무조건 초기값이 있어야 함

🪐배열기초

  • TYPE 이름[개수];

  • 배열의 크기는 상수여야 함 (VC 컴파일러 기준)
    const int monsterCount = 10;
    StatInfo monsters[monsterCount];
    
  • 여태껏 변수들의 [이름]은 바구니의 이름이었음
    int a = 10;
    int b = a;
    
  • 그런데 배열은 [이름]은 조금 다르게 동작한다
    • StatInfo players[10];
    • players = monster; 에러 발생
  • 그럼 배열의 이름은 뭐지?
    • 배열의 이름은 곧 배열의 시작 주소
    • 정확히는 시작 위치를 가리키는 TYPE* 포인터

auto WhoAmI = monsters;

  • 주소[ (100, 10, 1) ] StatInfo[ ] StatInfo[ ] StatInfo[ ] StatInfo[ ] …
  • monster_0 [ 주소 ]
    StatInfo* monster_0 = monsters;
    monster_0->hp = 100;
    monster_0->attack = 10;
    monster_0->defence = 1;
    
  • 포인터의 덧셈! 진짜 1을 더하라는게 아니라, StatInfo 타입 바구니 한개씩 이동하라는 의미
  • StatInfo[ ] 주소[ (200, 20, 2) ] StatInfo[ ] StatInfo[ ] StatInfo[ ] …
  • monster_1[ 주소 ]
    StatInfo* monster_1 = monsters + 1;
    monster_1->hp = 200;
    monster_1->attack = 20;
    monster_1->defence = 2;
    
  • 포인터와 참조는 자유자재로 변환 가능하다
  • StatInfo[ ] StatInfo[ ] monster_2.주소[ ] StatInfo[ ] StatInfo[ ] …
  • monster_2[ 주소 ]
    StatInfo& monster_2 = *(monsters + 2);
    monster_2.hp = 300;
    monster_2.attack = 30;
    monster_2.defence = 3;
    
  • [주의] 이거는 완전 다른 의미다
  • StatInfo[ ] StatInfo[ ] 주소[ 내용물 ] StatInfo[ ] StatInfo[ ] …
  • temp[ 내용물 ]
    StatInfo temp;
    temp = *(monsters + 2);
    temp.hp = 300;
    temp.attack = 30;
    temp.defence = 3;
    
  • 이를 조금 더 자동화한다!
    for (int i = 0; i < 10; i++)
    {
      StatInfo& monster = *(monsters + i);
      monster.hp = 100 * (i + 1);
      monster.attack = 10 * (i + 1);
      monster.defence = 1 * (i + 1);
    }
    
  • 근데 *(monsters + i) 너무 불편하고 가독성이 떨어진다… 더 편한 방법?
  • 인덱스!
  • 배열은 0번부터 시작. N번째 인덱스에 해당하는 데이터에 접근하려면 배열이름[N]
  • *(monsters + i) = mosnters[i]
monsters[0].hp = 100;
monsters[0].attack = 10;
monsters[0].defence = 1;

for (int i = 0; i < 10; i++)
{
	monsters[i].hp = 100 * (i + 1);
	monsters[i].attack = 10 * (i + 1);
	monsters[i].defence = 1 * (i + 1);
}
  • 배열 초기화 문법 몇가지
    • int numbers[5] = {}; // 다 0으로 밀어버린다
    • int numbers1[10] = { 1,2,3,4,5 }; // 설정한 애들은 설정한 값들로, 나머지 값들은 0으로 초기화
    • int numbers2[] = { 1,2,3,4,11,1514,211,3 }; // 데이터 개수만큼의 크기에 해당하는 배열로 만들어준다
    • char helloStr[] = { 'h','e','l','l','o','\0' };
    • cout << helloStr << endl;
  • 배열 요약:
    1. 선언한다 int arr[10] = {};
  1. 인덱스로 접근해서 사용
    arr[1] = 1;
    cout << arr[1] << endl;
    

🪐포인터 vs 배열

void Test(int a)
{
	a++;
}
  • 배열은 함수 인자로 넘기면, 컴파일러가 알아서 포인터로 치환한다 (char[] -> char*)
  • 즉 배열의 내용 전체를 넘긴게 아니라, 시작 주소(포인터)만 넘긴 것
    void Test(char a[])
    {
      a[0] = 'x';
    }
    
  • .data 주소[H][e][l][l][o][][W][o][r][l][d][\0]
  • test1[ 주소 ] « 8바이트 const char* test1 = "Hello World";

  • .data 주소[H][e][l][l][o]
  • [][W][o][r][l][d][\0]
  • [H][e][l][l][o][][W][o][r][l][d][\0]
  • (test2 = 주소)
  • char test2[] = "Hello World";
  • // test2[0] = 'R';
  • // cout << test2 << endl;

  • 포인터는 [주소를 담는 바구니]
  • 배열은 [닭장] 즉, 그 자체로 같은 데이터끼리 붙어있는 ‘바구니 모음’
    • 다만 [배열 이름]은 ‘바구니 모음’의 [시작 주소]
  • 배열을 함수의 인자로 넘기게 되면?

int a = 0;

  • [매개변수][RET][지역변수(a=0)]
    Test(a);
    cout << a << endl;
    
  • Test2가 바뀔까? 안바뀔까? -> 바뀐다!
    Test(test2);
    cout << test2 << endl;
    

a

태그:

카테고리:

업데이트: