data_type *variable_name; 또는 data_type* variable_name;
최근에 푸른 색을 더 선호하는 편이다.
저렇게 선언을 하면 컴퓨터는 변수의 주소를 받을 준비가 되어 있다고 생각한다.
Pointer 연산
포인터 연산
변수의 주소를 계산하는 연산자[주소 연산자] : &
변수의 메모리 주소 값 : &variable
시작하는 위치의 주소를 계산한다!
아래 사진에서 number의 주소를 1008이라고 하면 포인터 변수 p에 number 변수의 주소 1008이 들어가게 되고 이에 따라 *p가 가리키는 값은 10이 된다.
int number = 10; // 정수형 변수 number 선언
int *p; // 포인터 변수 p 선언
p = &number; // 변수 number의 주소가 포인터 p로 대입
주소 연산자 &
간접 참조 연산자 * : 포인터가 가리키는 값을 가져오는 연산자
Pointer 변수가 가리키는 메모리 공간의 내용을 가져오려면 *pointer_variable 를 사용하면 된다.
#include <iostream>
using namespace std;
int main() {
int number = 10; // 변수 number의 주소를 계산하여 p에 저장한다.
int *p = &number;
cout << *p << endl; // p가 가리키는 공간에 저장된 값을 출력한다.
return 0;
}
int i = 10; // 정수형 변수 i 선언
int *p; // 포인터 변수 p 선언
p = &i; // 변수 i의 주소가 포인터 p로 대입
printf("%d", *p); // 출력값 : 10
2. 포인터 변수의 선언과 사용 예
(1)
#include <iostream>
using namespace std;
int main() {
char c1;
char c2 = 'B';
char *signp;
signp = &c2; // signp: c2의 주소를 가리킵니다.
c1 = *signp; // c1에 *signp의 값, 즉 c2의 값이 "복사"됩니다. 이 시점에서 c1은
// c2와 같은 값이 됩니다.
*signp = 'A'; // *signp가 c2를 가리키는데, 이를 통해 c2의 값이 'A'로 변경됩니다.
cout << "signp 값(=c2 주소) : " << signp << endl; // signp가 가리키는 메모리에 저장된 값, 즉 'A'를 출력합니다.
cout << "c1 주소 : " << &c1 << endl;
cout << "c2 주소(=signp 값) : " << &c2 << endl;
/* 정확한 메모리 주소를 출력하려면 주소값을 문자열로 변환하지 않도록 조치해야 합니다.
여러 가지 방법이 있지만 가장 간단한 방법은 reinterpret_cast를 사용하여 주소값을 정수로
캐스팅한 후 출력하는 것입니다. 다음은 코드를 수정하여 주소값을 정수로 변환하여 출력하는 방법이다.*/
cout << "c1 주소 : " << reinterpret_cast<void*>(&c1) << " /" << " c2 주소 : " << reinterpret_cast<void*>(&c2) << " /" << " signp의 주소 : " << &signp << endl;
cout << "c1 값 : " << c1 << endl; // c1의 값, 즉 c2의 초기 값이 출력됩니다.
cout << "c2 값 : " << c2 << endl; // c2의 현재 값, 즉 'A'가 출력됩니다.
}
(1)의 결과
(2)
#include <iostream>
using namespace std;
int main() {
int i = 33;
int j = 44;
int k = 0;
int *p, *q; // 선언에서 *p, *q는 정수를 가리키는 포인터입니다.
p = &j; // p는 1004로 할당됨 – 이는 j의 주소입니다
i = j; // i는 44로 지정되어 있으며, 이는 j의 값입니다
k = *p; /*식에서 "dereference p"로 읽음 – */
/* p의 값은 1004 입니다. "deliverse 1004"는 1004번지에서 44라는 값을 가져온다는 것을 의미합니다." */
*p = 88; /*"dereference p" - p(주소값 1012)에 저장하는 것이 아니라, 1004번지에 있는 곳에 저장한다.
(p의 값을 '주소' & '그 주소에 저장하는용'으로 사용된다.*/
cout << i << endl;
cout << j << endl;
cout << k << endl;
cout << p << endl;
cout << q << endl;
}
(2)의 결과(2)의 결과를 그림으로 나타낸 것
3. 포인터 증감 연산
포인터의 증가 연산의 경우, 이란 변수와는 약간 다르다. 증가되는 값은 포인터가 가리키는 객체의 크기이다. 따라서 가리키는 객체의 크기만큼 증가하게 된다.
포인터 타입
++ 연산 후 증가되는 값
char
1
short
2
int
4
float
4
double
8
예시 : int형 포인터 변수 i가 있을 때 i++ 하게 되면 아래와 같이 바뀌게 된다.
(처음 i의 값)
===>
===>
===>
(p++ 후 i의 값)
4. 포인터는 어디에서 사용되는가?
포인터 변수를 이용해서 주소 값을 통해 결국 변수에 접근하게 된다. 그러면 여기서 '굳이 포인터 변수를 사용하지 않고, 원래 있던 변수를 바로 참조하면 되는 것이 아닌가?'하고 의문을 가질 수 있다.
여기서 우리가 알아야 할 점은 일반 변수는 메모리를 컴퓨터가 관리하고, 포인터로 관리하는 변수는 개발자가 직접 메모리를 관리할 수 있다는 점이다. 즉, 장치 드라이버나 동적 메모리 할당에서 메모리를 주소로 참조해야 하는 경우가 발생하는데 이 때 포인터가 사용되는 것이다.
또한, 함수의 포인터 형식 인자를 통한 실제 인자 접근(access)할 때 사용한다.
함수의 인수 전달 방법으로 '값에 의한 호출(call by value)'와 '참조에 의한 호출(call by reference)'가 있다. '값에 의한 호출'은 C에서 기본적인 방법이며, '참조에 의한 호출'은 C에서 포인터를 이용하여 흉내낼 수 있다. 따라서 포인터 형식 인자를 통한 실제 인자 접근은 '참조에 의한 호출'이다.
call by value(값에 의한 전달): 실매개변수의 값을 형식매개변수로복사하여 전달하는 기법
실매개변수의 값이 호출되는(called) 함수의 형식매개변수에 복사가 된다.
호출되는(called) 함수에서 형식매개변수의 값을 변경해도 호출하는 함수(caller) 쪽에서 실매개변수 값에 영향을 미치지가 않는다.(애초에 복사를 한 것이라 실체가 다르기 때문!)
call by reference(참조에 의한 전달): 실매개변수의 주소를 형식매개변수로 전달하는 기법
즉, 실체가 똑같게 된다.
Call by value의 예시 - 변수 2개의 값을 바꾸는 작업을 함수로 이용하기
#include <iostream>
void swap(int x, int y);
using namespace std;
int main() {
int a = 100, b = 200;
swap(a, b); cout << "a : " << a << endl;
cout << "b : " << b << endl;
return 0;
}
void swap(int x, int y) {
int tmp;
tmp = x;
x = y;
y = tmp;
}
위 코드의 결과
이 코드는 a와 b의 값을 바꿀려고 만든 코드인데, 실제로는 바뀌지가 않는다. 왜냐하면 함수 swap(100, 200)를 호출하면 실매개변수 a, b 값이 함수 swap의 형식매개변수 x, y로 각각 전달이 되는데 원본을 복사해서 전달하기 때문이다. 함수 swap에서 x와 y의 값을 바꾸지만, 수행이 끝난 후, 호출한 곳으로 돌아오면 a와 b의 값은 변하지 않는 것이다.
Call by reference의 예시
#include <iostream>
void swap(int *px, int *py);
using namespace std;
int main() {
int a = 100, b = 200;
swap(&a, &b);
cout << "a : " << a << endl;
cout << "b : " << b << endl;
return 0;
}
void swap(int *px, int *py) {
int tmp;
tmp = *px;
*px = *py;
*py = tmp;
}
위 코드의 결과
함수 호출시에 원본 주소가 복사되며, 함수 내에서 값이 변경됨에 따라 동시에 원본값도 바뀌는 것을 알 수 있다.
동적할당 ( vs 정적할당)
정적할당은 배열 int a[10];같은 경우이며 항상 10개로 고정이다.
동적할당은 배열의 크기를 조절할 수 있으며, 포인터를 이용하지 않고는 동적할당 이용할 수가 없다.
Array 변수는 pointer 변수로 취급되기 때문에 함수 정의에서 array 변수를 인자로 사용하는 것과 pointer 변수를 인자로 사용하는 것은 같다.
/* 아래의 두 prototype은 동등함. */
double sum(double a[], int n);
double sum(double *a, int n);
코드 예시 - 배열의 원소들의 합을 구하는 함수
#include <iostream>
using namespace std;
double sum(double a[], int n){
double sum = 0.0;
int i;
for (i = 0; i < n; i++) {
sum += a[i];
}
return sum;
}
double sum_by_pointer(double* a, int n) {
double sum = 0.0;
int i;
for (i = 0; i < n; i++) {
sum += a[i]; // sum += *(a+i);
}
return sum;
}
int main() {
double v[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
cout << sum(v, 10) << endl; // sum(&v[0], 10); 1 ~ 10 까지의 합
cout << sum_by_pointer(v, 10) << endl; // 1 ~ 10 까지의 합
cout << sum_by_pointer(&v[7],3) << endl; // 8 + 9 + 10
}
결과
7. 심화 : 포인터 구조 확인
#include <stdio.h>
#include <iostream>
using namespace std;
int main() {
int var = 10;
int* p_var = NULL;
cout << sizeof(var) << endl;
cout << sizeof(p_var) << endl;
int a[10];
int length = sizeof(a) / sizeof(int);
for (int i = 0; i < length; i++) {
a[i] = i * i;
}
int* point = &a[0];
for (int i = 0; i < length; i++) {
int* now = point + i;
wcout << "i [" << i << "] > 메모리 주소 " << now << "배열의 값 : " << *now << endl;
}
return 0;
}