[C++] 포인터

cheon2308
|2023. 11. 30. 09:19
728x90

황기태 저자의 명품 C++ Programming 개정판을 읽고 학습한 내용을 정리한 포스트입니다!

https://search.shopping.naver.com/book/catalog/32436115747

 

C++ Programming : 네이버 도서

네이버 도서 상세정보를 제공합니다.

search.shopping.naver.com

포인터

  • C/C++ 언어에서 포인터(pointer)는 실행 중 메모리의 주소 값이다.
  • 주소(포인터)를 이용하여 메모리에 직접 값을 쓰거나 메모리로부터 값을 읽어올 수 있다.
변수의 메모리 주소
  • 변수란 프로그램 내에서 사용하는 이름
  • 각 변수마다 메모리 공간이 할당 된다.
int n;
n = 3;

 

  • 위 코드에서 n은 정수를 저장할 메모리 공간에 대한 이름
  • 그러나 값 3이 메모리 몇 번지에 기록되는지는 알 수 없는데 => 프로그램이 실행을 시작할 때, 비로소 변수 n의절대 메모리 주소가 정해지기 떄문
포인터 변수 선언
  • c/c++에서 포인터 변수란 포인터 즉 주소를 저장하는 변수
  • c++에서는 실행 중에 변수의 메모리 주소를 알아 낼 수 있고, 이 주소를 포인터 변수에 저장 가능
  • 다음은 *연산자를 사용하여 포인터 변수를 선언하는 사례이다.
int *p; // 정수를 저장하는 메모리에 대한 포인터 변수 p 선언
p  = &n // p에 n의 주소를 저장
  • 변수 n의 주소를 실행 or 컴퓨터마다 다를 수 있기에, & 연산자를 이용하여 실행 중에주소 값을 알아낸다.

변수의 주소 값을 저장하는 포인터 변수 p, q, r

  • 포인터 변수는 다음과 같이 타입이 일치되지 않으면 오류가 발생한다.
int *p = &n; // 옳은 코드
int *t = &d; // 잘못된 코드. 변수 d는 실수(double)을 저장하는 변수이므로 오류

 

포인터 변수 활용

 

*p = 25; // n에 25가 저장됨
*q = 'A'; // c에 문자 'A'가 저장됨
*r = 3.14; // d에 3.14가 저장됨
  • *p는 'p가 가리키는 곳' 혹은 '값'을 뜻하는 표기
  • *p = 25;는 포인터 p가 가리키는 곳에 25를 저장하는 코드


 

배열과 포인터

  • C/C++에서 배열 이름은 배열 메모리의 시작 주소로 다룬다.
int n[10];
  • 다음 코드는 같은 것으로 배열  n의 시작 주소에서 5번째 떨어진 위치에 8을 저장하는 것
n[5] = 8;
*(n+5) = 8;
  • 따라서, 다음과 같이 포인터 변수 p에 배열 n의 시작 주소를 줄 수 있다.
int *p;
p = n; // p = &n[0];과 동일
*p = 100; // n[0] = 100;과 동일
*(p+5) = 8 // n[5] = 8;과 동일
// 아래와 같이 산술연산도 가능

p = p + 7; // p는 n[7]의 주소를 가지게 됨
*p = 99; // n[7] = 99;와 동일

 

  • 그러나 배열 이름은 상수 포인터이므로 다음과 같이 할 수 없다.
n = n+1; // 오류. 배열 이름은 상수이므로 변경 불가

배열과 포인터의 관계

 


포인터를 매개 변수로 가진 함수 만들기

#include <iostream>
using namespace std;

bool equal(int* p, int* q); // 함수의 원형 선언

int main() {
	int a = 6, b = 6;
	if (equal(&a, &b)) cout << "equal" << "\n";
	else cout << "not equal" << "\n";
	return 0;
}

bool equal(int* p, int* q) {
	// 포인터 매개변수
	if (*p == *q) return true;
	else return false;
}
  • 포인터로 정수 2개를 전달받아 같은지 비교하는 equal 함수를 작성하였다.
  • 함수의 매개 변수로 포인터를 선언한 후, 배열을 비교하는 함수를 작성하였다.
#include <iostream>
using namespace std;

bool equalArray(int* p, int* q, int size); // 함수의 원형 선언

int main() {
	int a[] = { 1,2,3,4,5 };
	int b[] = { 1,2,3,4,5 };
	if (equalArray(a, b, 5))
		cout << "arrays equal" << "\n";
	else
		cout << "arrays not equal" << "\n";
}

bool equalArray(int* p, int* q, int size) {
	int i;
	for (i = 0; i < size; i++) {
		if (*p != *q)
			return false;
		p++; // p는 배열 a의 다음 원소를 가리킴
		q++; // q도 배열 b의 다음 원소를 가리킴
	}
	return true;
}
  • 또한, 포인터는 배열로 생각할 수 있으므로 equalArray()는 아래와 같이 작성 가능
bool equalArray(int* p, int* q, int size) {
	int i;
	for (i = 0; i < size; i++) {
		if (p[i] != q[i])
			return false;
	}
	return true;
}

 

&(앰퍼샌드) = 참조(reference) 변수

  • 참조 변수는 한 객체가 다른 객체를 연결하는 수단.
  • 즉, 다른 객체나 값에 대한 별명으로 사용한다고 생각하면 된다. => 크기가 큰 객체를 함수에 인수로 전달할 때 주로 사용
참조 변수 선언
  • 문법 = 자료형& 참조 변수명 = 변수명;
  • &는 포인터에서 사용하던 변수의 주소값을 저장할 때 사용하던 주소 연산자로 쓰인 것이 아니라! 타입을 식별하기 위한 식별자로 사용
  • 즉, 주소가 아닌 참조이기 때문에 저장하는 변수와 같은 메모리를 참조
int num = 10;
int& refer = num;

 

  • 참조 변수를 사용하면 대상 객체와 동일한 곳을 참조하는 별명과도 같은 역할이기 때문에 참조 변수는 참조 대상과 동일한 값을 가진다.
  • 즉, 같은 주소를 가지기에 일반 변수의 값이 변경되면 참조 변수의 값도 변경된다.
주의 사항
  1. 참조 변수는 선언과 동시에 초기화하여야 한다.
  2. null 값을 저장할 수 있는 포인터와 다르게 null 을 참조 x
  3. 참조하려는 변수와 데이터 타입을 일치켜야 한다.또한 const로 선언된 변수는 const로, non-const는 non-const로만 참조할 수 있다.
  4. 한번 참조한 변수는 재참조 불가능

함수의 매개 변수로 활용

  • 참조 변수를 매개 변수로 사용하면 인수의 별명으로 이용
  • 동일한 메모리를 참조하기 때문에 값의 복사가 이루어지지 않아 크기가 큰 객체를 함수의 인수로 전달할 때 사용하면 복사하는데 드는 비용을 줄여 성능을 향상할 수 있어 자주 사용
  • 또한 포인터를 매개변수로 사용하여 역참조를 통해 원본 데이터를 수정할 수 있는 것처럼, 참조 변수를 매개 변수로 사용하면 값의 복사가 아닌 인수로 전달된 원본 데이터와 동일한 메모리를 참조하므로 원본데이터 수정이 가능
#include <iostream>
using namespace std;

void SwapVal(int& a, int& b) {
	int temp = a;
	a = b;
	b = temp;
}

int main() {
	int x = 10;
	int y = 20;

	cout << "x: " << x << "y: " << y << endl;
	SwapVal(x, y);
	cout << "x: " << x << "y : " << y << endl;
	return 0;
}
  • 두 값을 교환하는 함수에 참조 변수를 매개 변수로 사용하여 제작
  • 인수에 x, y가 전달되면 매개변수 a, b가 x, y에 대한 참조로 선언되어 직접 x,y의 값의 교환이 가능해진다.
  • 함수 매개변수에 구조체와 같은 사용자 정의형을 사용한다면 참조를 통해 쉽게 접근 가능하다.
#include <iostream>
#include <string>
using namespace std;

struct Human {
	string name;
	int age;
	double tall;
};

void ChangeStruct(Human& h, string n, int a, double t) {
	h.name = n;
	h.age = a;
	h.tall = t;
}

int main() {
	Human human1 = { "김00", 26, 175.8 };
	cout << human1.name << " " << human1.age << " " << human1.tall << endl;
	ChangeStruct(human1, "박00", 100, 1651.);
	cout << human1.name << " " << human1.age << " " << human1.tall << endl;

	return 0;
}

 

포인터와 참조 변수

  • 참조형 변수는 접근할 때 역참조 포인터와 같은 역할
  • 내부적으로도 참조 변수를 사용할 때 컴파일러는 포인터를 이용하여 접근
  • 따라서 => *ptr과 ref는 동일하게 취급된다. 또한 참조형 변수는 선언과 동시에 초기화되어야 하고 null로 초기화하거나 참조를 변경할 수 없으므로 포인터보다 안전하게 취급받는다.
  • int* ptr = &num
    • 위와 같이 포인터는 포인터 변수 ptr이 stack에 4바이트 공간으로 존재, 변수 num의 주소를 ptr이 가지고 있는 것
    • 둘은 서로 다른 변수
int n = 5;
int *p = &n;

/*
이 때 &n == 주소 == p
	*p == 5
    &p == p의 주소
*/
728x90

'Programming Language > C++' 카테고리의 다른 글

[C++] 동적 메모리 할당 및 반환  (1) 2023.11.30
[C++] 객체 포인터와 객체 배열  (0) 2023.11.30
[C++] 구조체  (0) 2023.11.29
[C++] priority queue with Cpp  (0) 2023.11.29
[C++] 접근 지정자와 인라인 함수  (0) 2023.11.29