[C++] 포인터, 참조
Ch 07. 포인터
포인터의 기본
포인터 : 메모리의 주소를 저장하는 변수
int num = 10;
int* numptr = #
//&num 은 num의 주소값을 의미한다.
cout<<*numptr;
//*numptr 은 numptr이 가르키고 있는 주소의 값을 의미한다.
int* numptr2 = numptr;
//다른 포인터의 값 복사하기
//여러 포인터가 하나의 주소를 가르킬 수 있음
const 포인터
int num = 10;
const int* pNum = #
int const* pNum = #
//포인터를 이용하여 참조값을 변경할 수 없음
int* const pNum = #
//포인터가 가르키는 대상을 바꿀 수 없음
const int* const pNum = #
//두가지 다 바꿀 수 없음
배열과의 관계
배열은 포인터로 변환이 된다. 하지만 동일하지는 않다. 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 = #
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; //상수를 참조할 수 있음
내부적으로 포인터이다. 기계어 코드로 변환하면 똑같음. 편의를 위해 만든 것.