11장. 포인터 01_ 포인터의 기본 02_ 포인터와 Const
포인터의 개념 포인터 변수는 다른 변수를 가리키는 변수다. 포인터 변수에는 다른 변수의 주소 값을 저장할 수 있다. 예를 들어서 포인터 변수 A가 다른 변수 B의 주소 값을 보관하고 있다면, 포인터 변수 A가 변수 B를 가리키고 있다고 말한다.
변수의 주소 변수의 주소란 변수가 포함하고 있는 제일 첫번째 바이트의 주소를 말한다. 예를 들어서 int 타입의 변수 a가 있다고 하면, 변수 a는 4바이트의 메모리 공간을 소유하게 된다. 메모리의 각 바이트 마다 주소가 있다. 그 중에서 첫번째 바이트의 주소를 변수 a의 주소라고 말한다.
포인터 변수에 변수의 주소를 보관하기 포인터 변수 p가 변수 a를 가리키도록 만드는 예 실행 결과 // 일반적인 변수를 정의한다. int a = 123; // 포인터 변수를 정의한다. int* p; // p가 a를 가리키도록 만든다. p = &a; // 관련 정보를 출력한다. cout << "&a = " << &a << "\n"; cout << "p = " << p << "\n"; cout << "&p = " << &p << "\n";
포인터 변수의 정의 포인터 변수를 정의할 때는 가리키고자 하는 변수의 타입을 지정해 두어야 한다. 다양한 타입의 포인터 변수 char c = 'C'; char* pc = &c; float f = 700.5f; float* pf = &f; bool b = true; bool* pb = &b; short int s = 456; short int* ps = &s;
가리킬 타입을 지정해야 하는 이유 메모리에는 타입에 대한 정보가 보관되지 않는다. 그러므로, 포인터 변수의 타입을 통해서 데이타의 크기나 종류를 알아낸다.
void 포인터 void* 타입의 포인터 변수는 어떤 타입의 변수라도 가리킬 수 있다. void* p;
주소를 사용해서 정보에 접근하기 포인터 변수가 가리키는 변수에 접근하는 예 실행 결과 // p가 a를 가리키도록 만든다. int a = 123; int* p = &a; // p가 가리키는 변수의 값을 얻는다. cout << "*p = " << *p << "\n"; // p가 가리키는 변수의 값을 변경한다. *p = 789; // 관련 정보를 출력한다. cout << "a = " << a << "\n";
포인터 안전하게 사용하기 잘못된 주소를 가진 포인터를 사용하는 것은 매우 위험하기 때문에, 포인터를 사용할 때는 다음의 가이드 라인을 따른다. 포인터 변수는 항상 0 혹은 NULL 값으로 초기화 한다. 포인터 변수를 사용하기 전에는 0 혹은 NULL 값을 가지고 있는지 확인한다. // 포인터 변수를 정의하고 초기화한다. int* p = NULL; // 이 상태에서 포인터를 사용해보자. if (NULL != p) *p = 30; // p가 변수를 가리키게 만들자 int a = 100; p = &a; if (!p)
Const 속성을 가진 변수 변수의 값이 변경되는 것을 막기위해서 Const 속성을 사용해서 변수를 정의할 수 있다. const int a = 123; // a의 값을 바꾸려고 했으므로 컴파일 에러 발생!! a = 456; // 배열의 크기를 const 변수에 보관한다. const unsigned int arraySize = 100; // 배열을 정의한다. char characters[ arraySize ] = {0}; // 배열을 사용한다. for (int i = 0; i < arraySize; ++i) characters[i] = i + 1;
Const 와 포인터 (1) 포인터 변수에 Const 속성을 부여할 때는 다음의 두 변수를 고려할 수 있다. 포인터 변수 자체 포인터 변수가 가리키는 변수
Const 와 포인터 (2) 포인터 변수에 Const 속성을 적용한 3가지 경우를 비교해보자. int i1 = 10; int i2 = 20; const int* p = &i1; p = &i2; // OK *p = 30; // FAIL int i1 = 10; int i2 = 20; int* const p = &i1; p = &i2; // FAIL *p = 30; // OK int i1 = 10; int i2 = 20; const int* const p = &i1; p = &i2; // FAIL *p = 30; // FAIL
01_ 포인터의 기본 02_ 포인터와 Const 03_ 포인터와 구조체 12장. 배열과 구조체와 포인터 01_ 포인터의 기본 02_ 포인터와 Const 03_ 포인터와 구조체
배열의 원소를 가리키는 포인터 포인터 변수를 사용해서 배열의 원소를 가리키는 예 int arrays[10]; int* p = &arryays[5]; [그림 12-1]
포인터 변수에 정수를 더하기 i 번째 원소를 가리키는 포인터 변수에 정수 n을 더하면 포인터 변수는 i + n 번째 원소를 가리키게 된다. 실행 결과 int array[10]; // 포인터가 array[5]를 가리키도록 만든다. int* p = &array[5]; cout << "p = " << p << "\n"; cout << "&array[5] = " << &array[5] << "\n"; // 포인터가 array[6]을 가리키도록 만든다. p++; cout << "&array[6] = " << &array[6] << "\n"; [그림 12-3] [그림 12-2]
포인터 변수간의 뺄셈 i 번째 원소를 가리키는 포인터 변수에서 j번째 원소를 가리키는 포인터 변수를 빼면 결과는 i-j 가 된다. 실행 결과 short sArrays[10]; short* ps1 = &sArrays[3]; short* ps2 = &sArrays[7]; // 결과를 출력한다. cout << "ps1 = " << ps1 << "\n"; cout << "ps2 = " << ps2 << "\n"; cout << "ps2 - ps1 = " << ps2 - ps1 << "\n"; [그림 12-6] [그림 12-5]
포인터를 사용한 원소의 탐색 일반적인 방법을 사용한 원소의 탐색 포인터를 사용한 원소의 탐색 int nArray[10]; // 배열을 탐색하면서 값을 넣는다. for (int i = 0; i < 10; ++i) nArray[i] = i; int nArray[10]; int* p = &nArray[0]; // 배열을 탐색하면서 값을 넣는다. for (int i = 0; i < 10; ++i) *(p + i) = i;
배열의 이름 배열의 이름은 첫번째 원소의 주소가 된다. 배열의 이름을 사용해서 배열을 탐색하는 예 float f[5]; if ( f == &f[0] ) { // 항상 이곳이 실행된다. } int nArray[10]; // 배열을 탐색하면서 값을 넣는다. for (int i = 0; i < 10; ++i) *(nArray + i) = i;
배열을 가리키는 포인터 배열의 원소가 아닌 배열 전체를 포인터 변수로 가리키는 예 실행 결과 long lArray[20]; // 포인터가 이 배열을 가리키게 만들자 long (*p)[20] = &lArray; // 포인터를 통해서 배열을 사용하자. (*p)[3] = 300; // 결과를 확인하자 cout << "lArray[3] = " << lArray[3] << "\n"; [그림 12-7]
연산자 우선순위 배열을 가리키는 포인터 변수의 정의 배열을 가리키는 포인터 변수의 사용 [그림 12-8] [그림 12-9]
포인터의 배열 포인터 변수의 배열을 정의하는 예 // 참조될 변수들을 정의한다. double a, b, c; // 3개의 원소를 가진 포인터의 배열을 정의한다. double* pArray[3]; // 각 원소가 변수들을 가리키도록 만든다. pArray[0] = &a; pArray[1] = &b; pArray[2] = &c;
배열을 포함하는 구조체 배열을 멤버로 갖는 구조체의 예 실행 결과 struct StudentInfo { char name[20]; // 이름 int stdNumber; // 학번 float grade[2]; // 최근 2학기 평점 }; StudentInfo si = { "Kim Chol-Su", 200121233, {3.2f, 3.5f} }; cout << si.name << "\n"; cout << si.stdNumber << "\n"; cout << si.grade[0] << ", " << si.grade[1] << "\n"; [그림 12-11]
구조체의 배열 구조체 변수의 배열을 정의하는 예 struct StudentInfo { char name[20]; // 이름 int stdNumber; // 학번 float grade[2]; // 최근 2학기 평점 }; StudentInfo stdInfos[5] = { { "Kim Chol-Su", 200121233, {3.2f, 3.5f} }, { "Lee Chol-Su", 200223517, {4.5f, 4.5f} }, { "Park Chol-Su", 200321131, {1.7f, 2.0f} }, { "Yang Chol-Su", 200222289, {0.4f, 4.1f} }, { "Yoon Chol-Su", 199921444, {2.7f, 2.8f} } [그림 12-14]
구조체를 가리키는 포인터 구조체 변수를 가리키는 포인터를 정의하는 예 실행 결과 struct Rectangle { int x, y; int width, height; }; Rectangle rc = { 100, 100, 50, 50}; // 포인터가 이 변수를 가리키게 만든다. Rectangle* p = &rc; // 구조체의 멤버에 접근한다. (*p).x = 200; p->y = 250; cout << "rc = ( " << rc.x << ", " << rc.y << ", "; cout << rc.width << ", " << rc.height << ")\n"; [그림 12-15]
포인터를 포함하는 구조체 포인터 변수를 멤버로 갖는 구조체를 정의하는 예 실행 결과 struct Dizzy { int id; // 구조체 변수마다 갖는 고유한 값 Dizzy* p; // Dizzy 구조체를 가리키는 포인터 }; // Dizzy 객체를 3개 만들고, 서로를 가리키도록 만든다. Dizzy a, b, c; a.id = 1; a.p = &b; b.id = 2; b.p = &c; c.id = 3; c.p = &a; // a 만 사용해서 a, b, c 모두에 접근한다. cout << "a.id = " << a.id << "\n"; cout << "b.id = " << a.p->id << "\n"; cout << "c.id = " << a.p->p->id << "\n"; cout << "a.id = " << a.p->p->p->id << "(again)\n"; [그림 12-17] [그림 12-16] 그림 넣을 공간이 부족하기는 하지만 잘 맞춰서 넣어봐.. ㅎㅎ
01_ 나머지 복합 타입들 02_ 배열, 구조체, 포인터의 나머지 기능 13장. 복합 타입의 모든 것 01_ 나머지 복합 타입들 02_ 배열, 구조체, 포인터의 나머지 기능
공용체(Unions) 공용체의 모든 멤버는 같은 메모리 공간을 공유한다. union ManyMembers { char c; short s; int i; float f; double d; }; [그림 13-3]
공용체의 사용 공용체의 멤버가 같은 공간을 공유한다는 점을 확인하는 예 실행 결과 union MyUnion { int i; void* p; }; MyUnion uni; cout << "&uni.i = " << &uni.i << "\n"; cout << "&uni.p = " << &uni.p << "\n"; uni.i = 0x12345678; cout << hex; cout << "uni.i = " << uni.i << "\n"; cout << "uni.p = " << uni.p << "\n"; uni.p = (void*)0x87654321; [그림 13-2]
열거체(Enumerations) 열거체를 사용해서 상수값에 대한 심볼을 정의하는 예 enum JOB_KINDS { JOB_DWARF, JOB_WARRIOR, JOB_SORCERER }; struct Character { JOB_KINDS jobType; // 다른 멤버들이 더 있다. }; Character c; // c 가 누군가에 의해 초기화 된다. // c가 마법사인 경우 if (JOB_SORCERER == c.jobType) // 필요한 일을 한다. }
열거체와 심볼의 값 열거체의 심볼들은 자동적으로 0 기반의 인덱스 값을 갖는다. = 열겨체의 심볼에 값을 대입한 경우, 뒤쪽의 심볼들은 해당 값을 기반으로 증가한다. enum { JOB_DWARD, JOB_WARRIOR, JOB_SORCERER }; = enum { JOB_DWARD = 0, JOB_WARRIOR = 1, JOB_SORCERER = 2}; enum { JOB_DWARD, JOB_WARRIOR = 3, JOB_SORCERER }; = enum { JOB_DWARD, JOB_WARRIOR = 3, JOB_SORCERER = 4};
열거체와 정수 타입 열거체를 사용해서 산술 연산을 할 수 없다. 정수를 열거체 변수에 대입할 수 없다. 열거체의 심볼들은 암시적으로 정수 타입으로 형변환 된다. 명시적인 형변환을 사용해서 정수 타입의 값을 열거체 변수에 담을 수 있다. enum Color { RED, BLUE, GREEN, SKYBLUE, MAGENTA, YELLOW }; Color color1 = RED; color1 = SKYBLUE + YELLOW; // Error Color1 += 3; // Error Color color2; color2 = 5; // Error int n = MAGENTA; // n 은 4가 된다. int m = BLUE + 2; // m 은 3이 된다. Color color3 = (Color)2 // color3은 GREEN이 된다. Color color4 = (Color)5000 // 결과를 알 수 없다.
레퍼런스(References) 레퍼런스 변수를 사용해서 변수의 별칭을 만드는 예 int target = 20; // 레퍼런스 변수를 정의한다. int& ref = target; cout << "ref = " << ref << "\n"; cout << "target = " << target << "\n"; cout << "&ref = " << &ref << "\n"; cout << "&target = " << &target << "\n"; // ref의 값을 바꿔보자. ref = 100; [그림 13-4]
레퍼런스 변수의 초기화 레퍼런스 변수의 초기화와 대입은 다른 의미를 갖는다. // 변수를 정의한다. float a = 100.0f; float b = 12.34f; // r을 a에 대한 별명으로 만든다. (초기화) float& r = a; // r에 200.0f를 대입한다. (대입) r = 200.0f; // r에 b의 값 12.34f를 대입한다. (대입) r = b; // r에 56.7f를 대입한다. (대입) r = 56.7f
레퍼런스 변수와 Const Const 속성을 가진 레퍼런스 변수만 상수로 초기화할 수 있다. const int& rci = 100; // OK int& ci = 100; // Error [그림 13-7] char c = ‘A’; const int& rci = c; // OK int& ci = c; // Error [그림 13-8]
typedef typedef를 사용해서 타입의 별칭의 만드는 예 typedef unsigned char* uc_ptr; unsigned char uc = ‘A’; uc_ptr p = &uc; [그림 13-9]
비트 필드(Bit Fields) 구조체의 멤버 변수들이 차지하는 비트 수를 지정하는 예 struct Flags { int a : 3; int b : 4; int : 5; // 이 5비트는 쓰지 않는다. bool c : 1; }; [그림 13-11]
구조체를 포함하는 구조체 구조체 변수를 멤버로 갖는 구조체를 정의하는 예 struct A { int i; float f; }; struct B char c; A a; [그림 13-14]
다차원 배열 다차원 배열 (= 배열의 배열)을 정의하는 예 좌측의 그림은 이해하기 편하게 그린 것이고, 우측의 그림은 실제로 다차원 배열이 메모리에 자리잡는 모습을 그린 것이다. int arr[10][5]; // 배열의 모든 원소에 10을 대입한다. for ( int i = 0; i < 10; ++i ) { for ( int j = 0; j < 5; ++j ) arr[i][j] = 10; } [그림 13-15] [그림 13-16]
포인터를 가리키는 포인터 포인터 변수를 가리키는 포인터 변수를 정의하는 예 char c = ‘1’; char* pc = &c; char** ppc = &pc; if ( *ppc == pc ) { // 이곳은 항상 실행된다. } if ( **ppc == c ) [그림 13-17]