[모던C++입문] 1.8 배열, 포인터, 레퍼런스
- 📕 Book/모던C++입문
- 2021. 7. 4.
1.8 배열, 포인터, 레퍼런스
배열
int x[10]; // 10개의 int 배열
float v[] = {1.0, 2.0, 3.0}, w[] = {7.0, 8.0, 9.0};
int v2[] = {1.0, 2.0, 3.0};; //C++ 11에서 오류
float A[7][9]; // 7x9 행렬
float q[3][2][3]; //3x2x3 배열
unsigned int size = sizeof x / sizeof x[0]; //배열의 크기
ifstream ifs("some_array.dat");
ifs >> size;
float v3[size]; //컴파일 타임에 크기를 알수 없다
포인터
- 포인터는 메모리 주소를 포함하는 변수
- 포인터를 사용하면 sizeof 트릭을 사용할 수 없다
int* y = new int[10]; //크기가 10개인 배열을 할당
ifstream ifs("some_array.dat");
int size;
ifs >> size;
float* v = new float[size]; //런타임에 배열의 크기를 정함
delete[] v; //더 이상 필요하지 않을 때 메모리를 해제
int i = 3;
int* ip2 = &i;
int j = *ip2; //역참조 (Dereferencing)
//포인터는 무작위 값을 할당하기에 초기화를 해야한다
//C++11 이전 방식
int* ip01 = 0;
int* ip02 = NULL;
//C++11 이후 방식
int* ip03 = nullptr;
int* ip04{};
- 포인터의 가장큰 위험은 메모리 누수이다.
- 표준 컨테이너를 사용하자
- 캡슐화 : 메모리는 개체 생성 시 할당하고 파괴시 해제해야함
- RAII(Resource Acquisition Is Initialization)
- 스마트 포인터를 사용하자
스마트 포인터
unique_ptr
- 고유 소유권(Unique Ownership)을 가지는 포인터
- 원시 포인터와 달리 포인터가 만료되면 메모리가 자동으로 해제
- 원시 포인터에 비해 시간과 메모리에 대한 오버헤드가 없다
unique_ptr<double> dp{new double}; //일반적인 할당
double d;
unique_ptr<double> dd{&d}; //오류 할당하지 않은 주소를 할당
double* raw_dp = dp.get(); //포인터 데이터를 얻기 위해서는 get함수 사용
unique_ptr<double> dp2{dp}; //오류 : 다른 unique_ptr에 할당 불가
dp2 = dp; //상동
unique_ptr<double> dp2{move(dp)}, dp3; //unique_ptr는 이동만 가능
dp3 = move(dp2); //상동
shared_ptr
- 여러 포인터에서 공통으로 사용하는 메모리를 관리
- shared_ptr이 더 이상 데이터를 참조하지 않으면 즉시 메모리를 자동 해제
- unique_tr과 달리 원하는 만큼 복사할 수 있다.
- shard_ptr에는 메모리와 실행시간에 약간의 오버헤드가 존재
shard_ptr<double> f()
{
shard_ptr<double> p1{new double};
shard_ptr<double> p2{new double}, p3 = p2;
cout << "p3.use_count() = " << p3.use_count() << endl;
return p3;
//함수를 빠져나갈때 p1은 메모리를 해제한다
}
int main()
{
shard_ptr<double> p = f();
cout << "p.use_count() = " << p.use_count() << endl;
}
//출력 결과
p3.use_count() = 2;
p.use_count() = 1;
//가능하다면 make_shared를 사용해서 shared_ptr를 만들자
shared_ptr<double> p1 = make_shard<double();
weak_ptr
- shared_ptr에서 발생할수 있는 문제인 순환참조(Cycle reference)를 중단할 수 있음
레퍼런스
int i = 5;
int& j = i;
j = 4; //i도 4가 된다
cout << "i = " << i << endl; //i와 j는 항상 같은 값을 가짐
포인터와 레퍼런스 비교
| 특징 | 포인터 | 레퍼런스 |
|---|---|---|
| 정의된 위치 참조 | x | o |
| 초기화 필수 | x | o |
| 메모리 누수 방지 | x | o |
| 개체와 같은 표기법 | x | o |
| 메모리 관리 | o | x |
| 주소 계산 | o | x |
| 컨테이너 만들기 | o | x |
오래된 데이터를 참조하지 말자
//이러지 말자
double& square_ref(double d)
{
double s = d*d;
return s;
// 지역변수를 참조하면 안된다 부실 레퍼런스(stale reference)
}
double* square_ptr(double d)
{
double s = d*d;
return &s;
// 스코프를 벗어난 지역주소를 가지면 댕글링 포인터(dangling pointer)
}
배열용 컨테이너
표준 벡터
#include <vector>
int main()
{
std::vector<float> v(3), w(3);
v[0] = 1; v[1] = 2; v[2] = 3;
w[0] = 7; w[1] = 8; w[2] = 9;
//위 방식보단 아래 방식을 사용하자
std::vector<float> v = {1,2,3}, w = {7,8,9};
int vSize = v.size(); //배열의 사이즈를 구할 수 있음
}
valarray
- 요소별 연산을 사용하는 1차원 배열
#include <valarray>
int main()
{
std::valarray<float> v = {1,2,3}, w = {7, 8, 9} , s = v + 2.0f * w;
v = sin(s);
}반응형