C++ Rookiss Part1 C++ 프로그래밍 입문 : 포인터
🙇♀️포인터
🪐포인터 기초
- 지금까지 사용한 방법
- number라는 이름의 4바이트 정수 타입의 바구니를 만든다
- number라는 변수 스택 메모리에 할당
- number = 1라 함은, number 바구니에 1이라는 숫자를 넣으라는 의미.
- 따라서 스택 메모리에 있는 특정 주소(number 바구니)에 우리가 원하는 값을 넣은 셈.
- number는 비유하자면 메모리에 이름을 붙인 것 (찰떡같이 알아들어서)
- 나쁘지 않고 편리한데, 단점은 TextRPG 원본 수정
int number = 1;
int ptr;
-
int형 변수를 스택 메모리에 만들어주세요. 그 이름은 ptr이고 ptr에 접근하거나 ptr을 고치면 해당 메모리에 넣으면 됩니다
- TYPE* 변수이름;
- 일단 2가지 요소
- TYPE
- *
- 일단 2가지 요소
- 바구니는 바구니인데… [주소를 저장하는 바구니다!]
- 변수 선언할 떄 * 등장했다 -> 포인터 = 주소
- 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);
}
🪐포인터 연산
- 주소 연산자 (&)
- 산술 연산자 (+ -)
- 간접 연산자 (*)
- 간접 멤버 연산자 (->)
- 주소 연산자 (&)
- 해당 변수의 주소를 알려주세요
- 더 정확히 말하면 해당 변수 타입(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;
는 완전히 동일하다
🪐참조 기초
- 값을 수정하지 않는다면, 양쪽 다 일단 문제 없음
- 값 전달 방식
- [매개변수][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; }
- 주소 전달 방식
- [매개변수][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바이트
- 성능차이가 어마어마하게 날 수가 있다
- 참조 전달 방식
- 값 전달처럼 편리하게 사용하고!
- 주소 전달처럼 주소값을 이용해 진퉁을 건드리는!
- 일석이조의 방식!
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 참조 세기의 대결
- 성능 : 똑같음
- 편의성 : 참조 승!
- 편의성 관련
- 편의성이 좋다는게 꼭 장점만은 아니다.
- 포인터는 주소를 넘기니 확실하게 원본을 넘긴다는 힌트를 줄 수 있는데,
- 참조는 자연스럽게 모르고 지나칠 수도 있음!
- ex) 마음대로 고친다면?
- const를 사용해서 이런 마음대로 고치는 부분 개선 가능
- 참고로 포인터도 const를 사용 가능.
- ‘*’ 기준으로 앞에 붙이느냐, 뒤에 붙이느냐에 따라 의미가 달라진다.
- 초기화 여부
- 참조 타입은 바구니의 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;
- 배열 요약:
- 선언한다
int arr[10] = {};
- 선언한다
- 인덱스로 접근해서 사용
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