C & C++/C++

[C++] 포인터, 참조

거북이 코딩 2024. 7. 1. 16:11

Ch 07. 포인터

포인터의 기본

포인터 : 메모리의 주소를 저장하는 변수

int num = 10;
int* numptr = #
//&num 은 num의 주소값을 의미한다.
cout<<*numptr;
//*numptr 은 numptr이 가르키고 있는 주소의 값을 의미한다.
int* numptr2 = numptr;
//다른 포인터의 값 복사하기
//여러 포인터가 하나의 주소를 가르킬 수 있음

const 포인터

int num = 10;
const int* pNum = &num;
int const* pNum = &num;
//포인터를 이용하여 참조값을 변경할 수 없음
int* const pNum = &num;
//포인터가 가르키는 대상을 바꿀 수 없음
const int* const pNum = &num;
//두가지 다 바꿀 수 없음

배열과의 관계

배열은 포인터로 변환이 된다. 하지만 동일하지는 않다. decay 특성.

배열의 이름은 배열의 0번째 주소를 리턴한다.

int nums[] = { 1, 2, 3 };
int pNums = nums;
cout << nums << endl;
cout << &nums[0] << endl;
//위 둘은 같은 의미를 리턴한다. int 포인터 타입을 리턴한다.
cout << &nums << endl;
//이것은 길이가 3인 배열의 포인터를 의미한다.

typeid 로 확인하기

typeid(nums).name(); //인자의 타입을 반환한다.
typeid(&nums[0]).name();
typeid(&nums).name();
// 순서대로 int[3], int * __ptr64, int (* __ptr64)[3] 을 의미한다.

포인터로 배열 제어하기

pNums[0] = 0;
pNums[1] = 0;
pNums[2] = 0;
//배열처럼
*pNums = 0;
*(pNums + 1) = 0;
*(pNums + 2) = 0;
//포인터로 동작
//배열도 위와 같은 방식으로 제어 가능하다.
*&nums[0] = 0;
*(&nums[1] + 1) = 0;
*(&nums[2] + 2) = 0;
//주소값을 역참조하는 형태

배열과 포인터의 차이점

//sizeof
sizeof(배열); //배열의 크기 (자료형 * 크기)
sizeof(포인터); //포인터의 크기 (4 또는 8)

//단항연산자
포인터++; //포인터가 다음 인덱스의 주소를 가르키게 된다.
배열++; //허용되지 않음

문자열과의 관계

char str1[] = "abcd";
char* str2 = str1;
cout << str1 << endl;
cout << str2 << endl; //같은 출력

리터럴 문자열을 가르키는 경우

const char* str3 = "hello";
//리터럴은 const이기 때문에 const char* 로 선언해야 한다.\\
//str3를 활용해서 cstring의 함수를 사용할 수 있다.

동적 할당

어떤 스코프 내에서 선언된 변수를 지역변수라고 한다. 이는 자동 할당이 된다.

변수 앞에 static을 붙이면 정적 할당이 되어서 전역변수화 된다.

static int num = 0;

동적 할당은 직접 할당하고 해제하는것을 말한다.

int* pNum = new int; //메모리 할당
//스택에 pNum이 할당되고 힙에 int의 크기만큼 할당된다.
//스택은 자동적으로 할당되는 곳이고, 힙은 직접 관리하는 영역이다.
delete pNum; //메모리 해제

초기화

int* pNum = new int(123);

유효하지 않은 메모리를 삭제하거나 접근하면 오류가 발생한다.

int* pNum1 = new int(123);
int* pNum2 = pNum2;
delete pNum1;
cout << *pNum2; //삭제된 메모리 접근

delete pNum2; //삭제된 메모리 삭제

{
	int n = 10;
	pNum1 = &n
}
cout << *pNum1; //해제된 메모리 접근

배열의 동적할당

int s;
cin >> s;
int* arr = new int[s]; //쓰레기 값이 들어갈 수 있음

int* arr = new int[s]{ 1, 2, 3 }; //초기화

delete[] arr; //메모리 해제

구조체 동적할당

int main() {
	struct Person
	{
		float height;
		float weight;
	};
	Person* person = new Person{}; // 동적할당
	cout << (*person).height << " " << person->weight << endl;
	delete[] person; // 동적할당 해제
	return 0;
}

// "(*포인터)." 은 "포인터 ->" 와 의미가 같다.

포인터 배열에 각 각 동적할당

Person* person[2] = {
	new Person{ 170.0f, 60.0f },
	new Person{ 180.0f, 80.0f }
};

for (Person* p : person)
{
	p->weight = 0;
}
for (Person* p : person)
{
	cout << p->weight << endl;
}

포인터 하나에 구조체 배열을 동적할당

Person* persons = new Person[5]{
	{180.0f, 80.0f},
	{170.0f, 70.0f},
	{160.0f, 60.0f},
	{150.0f, 50.0f},
	{140.0f, 40.0f}
};
for (int i = 0; i < 5; i++)
{
	cout << "Person " << i + 1 << " height: " << persons[i].height;
	cout << " weight: " << persons[i].weight << endl;
}

nullptr

null은 비어있다는것을 의미한다. 그래서 아무것도 가르키지 않을 때는 널포인터를 넣어준다.

int* p = nullptr; 
p = new int(10);

if (p) { //p가 널포인터라면 false를 반환한다.
	cout << "p is not null" << endl;
}
else {
	cout << "p is null" << endl;
}
return 0;

nullptr은 nullptr_t 타입의 상수이다.

nullptr_t ptr; //nullptr_t 타입은 nullptr이라는 한가지 값만 갖는 특수한 타입이다.
int* p = ptr; //널포인터는 모든 포인터로 형변환이 가능하다.

void pointer

void는 어떠한 형이 없다는것을 표현한다. 그래서 일반적인 변수 선언은 불가능하다.

하지만 void 포인터는 모든 타입의 포인터를 담을 수 있고 변환이 가능하다..

int num = 10;
int* p = &num;
void* vp = p;
cout << *(int*)vp << endl;
//직접 역참조는 안되지만 형변환을 거치면 가능하다.

C style 동적할당

int* p = (int*)malloc(sizeof(int) * 3); //메모리 동적할당
//malloc의 반환형이 void 포인터이다. 때문에 사용할 때 형변환을 사용한다.
free(p); //메모리 해제

vector

#include<vector>

int main() {
	int s;
  cin >> s;
	std::vector<int> v(s); //벡터의 용량은 프로그램 실행중에 정해질 수 있다.
	for (int i = 0; i < vec.size(); i++) {
		cout << v[i] << endl; //0 출력 벡터는 초기값으로 0이 삽입된다.
	}
	return 0;
}

벡터의 초기화

vector<int> v(10, 1); //10크기의 벡터를 1로 채운다. 1 1 1 1 ...
v.resize(3); //벡터의 크기를 3으로 줄인다. 1 1 1
v.resize(5, 2); //벡터의 크기를 5로 늘리고 2로 채운다. 1 1 1 2 2
v.resize(7); //벡터의 크기를 7로 늘린다. 1 1 1 2 2 0 0
for (int i = 0; i < v.size(); i++) {
	cout << v[i] << " "; //1 1 1 2 2 0 0
}

벡터 활용하기

vector<int> v;
cout << v.size() << endl; // 0
v.push_back(10);
cout << v[0] << ' ' << v.size() << endl; // 10 1
v.pop_back();
cout << v.size() << endl; // 0

벡터는 내부적으로 동적배열이 쓰이고 있고 재할당이 일어날 수 있다.

vector<int> v;
for (int i = 0; i < 10; i++)
{
	v.push_back(i);
	cout << v[i] << endl;
	cout << v.data() << endl;
} //재할당이 일어나는 것을 확인할 수 있다.

벡터의 주요 함수

//vector의 주요 함수
vector<int> vec = { 1,2,3,4,5 };

//push_back() : 벡터의 끝에 요소를 추가
vec.push_back(6); //1, 2, 3, 4, 5, 6

//pop_back() : 벡터의 끝에 있는 요소를 제거
vec.pop_back(); //1, 2, 3, 4, 5

//swap() : 두 벡터의 내용을 교환
vector<int> vec2 = { 7,8,9 };
vec.swap(vec2);
for (int num : vec)
	cout << num << " "; // 7, 8, 9
cout << endl;
for (int num : vec2)
	cout << num << " "; // 1, 2, 3, 4, 5
cout << endl;

//할당
vector<int> vec3 = vec;
for (int num : vec3)
	cout << num << " "; //7, 8, 9
cout << endl;

//비교연산
if (vec == vec3) // true
	cout << "vec == vec3" << endl;
if (vec != vec2) //true
	cout << "vec != vec2" << endl;
if (vec > vec2) //true
	cout << "vec > vec2" << endl;

//front() : 첫 번째 요소를 반환
cout << vec.front() << endl;

//back() : 마지막 요소를 반환
cout << vec.back() << endl;

비교 연산은 앞 인덱스부터 비교하고 길이가 달라도 비교가 가능하다.

{ 1, 2, 3 } > { 1, 2 } 처럼 존재하지 않으면 작은걸로 인정된다.

Ch 08. 참조

참조

레퍼런스, alias, 별칭을 의미한다.

int num0 = 10;
int& num1 = num0; //num0을 참조

cout << num0 << num1; //1010 출력

참조와 포인터의 차이

int& num1; //허용되지 않음, 최초 초기화할 때 만 할당이 가능
int* num2; //가능

const 참조

const int& num1 = num0; // num1을 이용해서 값을 바꿀 수 없음
const int& num2 = 10; //상수를 참조할 수 있음

내부적으로 포인터이다. 기계어 코드로 변환하면 똑같음. 편의를 위해 만든 것.