반응형
안녕하세요? 수구리입니다.
지난 포스팅에서는 이니셜라이저 리스트에 대해서 알아봤었죠?
2021.08.17 - [C++] - [C++] 1.1.15 이니셜라이저 리스트
이번 포스팅에서부터는 1.2절로 넘어와서 C++의 고급 기능에 대해서 알아보려고 합니다!
확실히 1.1절보다는 내용이 많고 제가 처음보는 개념도 등장하네요..
그러면 바로 시작하겠습니다! 복습 필수
1.2 C++의 고급 기능
- 이번 파트에서는 C++에서 제공하는 다양한 기능들에 대하여 알아보도록 하자.
1.2.1 C++의 스트링
C++에서 스트링을 다루는 방법
1. C와 같이 스트링을 문자배열로 표현한다.
2. 스트링 타입으로 감싸서 표현한다.
3. 비표준 제너릭 클래스를 사용한다.
- 위의 내용들은 나중에 2장에서 자세히..
// C++의 string 타입은 <string> 헤더 파일에 정의.
string myString = "hello!";
cout << "the value of myString is " << myString << endl;
cout << "the 2'th letter of myString is " << myString[1] << endl;
1.2.2 포인터와 동적 메모리
스택과 힙
- 스택
- 주로 현재 실행중인 함수를 가리킨다.
- 현재 실행중인 함수는 최상단 스택 프레임의 메모리 공간에 있다.
- 새로운 함수를 호출한다면 스택 프레임이 올라온다.
- 스택 프레임은 각각의 함수마다 독립적인 메모리 공간을 제공한다는 점에서 유용하다.
- 스택에 할당된 변수는 프로그래머가 직접 할당 해제할 필요가 없이 자동으로 처리 되어진다.
- 힙
- 현재 함수 또는 스택 프레임과는 완전히 독립적인 메모리 공간 이다.
- 함수가 끝난 뒤에도 사용하던 변수를 유지하고 싶을땐, 힙에 저장한다.
- 힙에 할당된 메모리 공간은 직접 할당 해제해야 한다.
포인터 사용법
- 어떤 정숫값을 힙에 저장하려면 힙에는 정수 타입에 맞는 메모리 공간을 할당해주어야 한다. 그럴때 사용하는 것이 포인터이다.
- 만약 선언 후 초기화를 해주지 않았다면 오류가 날 수 있기 때문에 반드시 포인터 변수는 선언과 동시에 초기화를 해주는 것을 까먹지 말자.
// int(*)는 이 변수가 정수 타입에 대한 메모리 공간을 가리킨다
int* myIntPointer;
// 포인터 변수는 초기화하지 않으면 어느 메모리를 가리키는지 알 수 없기 때문에
// 항상 선언과 동시에 초기화를 nullptr로 해준다.
int* myIntPointer = nullptr;
// myIntPointer의 값이 널 포인터는 여기서 false로 취급
if (!myIntPointer) {
// do something
}
// 포인터변수에 메모리를 동적으로 할당할 때에는 new 연산자를 사용함
myIntPointer = new int;
// 이 포인터가 가리키는 값에 접근하기 위해서는 역참조를 통해 접근.
// 역참조 : 포인터가 힙에 있는 실제값을 가리키는 화살표를 따라감.
*myIntPointer = 8; // myIntPointer = 8 와는 전혀 다른것임에 주의!
- 동적으로 할당한 메모리를 사용 후 delete를 통해서 해당 공간을 해제해야 한다.
- 해제 후 곧바로 포인터 변수의 값을 nullptr로 초기화!
// delete 후 nullptr 초기화
delete myIntPointer;
myIntPointer = nullptr;
- 포인터는 힙 뿐만 아니라 스택과 같은 다른 종류의 메모리를 가리킬 수 있다.
- 참조 연산자 &를 통해서 말이다.
// 변수 i의 포인터값을 알아보자.
int i = 8;
int* myPointer = &i; // 8이란 값을 가진 변수 i의 주소값을 가리키는 포인터
C++ 구조체에서의 포인터 사용
- "*" 연산자로 역참조를 하여 구조체 자체 시작지점에 접근
- 접근한 뒤 필드에 접근하려면 "." 연산자로 표기한다.
// getEmployee() 함수가 Employee 구조체를 리턴한다고 가정.
Employee* anEmployee = getEmployee();
cout << (*anEmployee).salary << endl;
// 좀더 간결하게 "->"를 사용하여 표현
Employee* anEmployee = getEmployee();
cout << anEmployee->salary << endl;
// 단락 논리를 적용하면 잘못된 포인터에 접근하지 못하도록 함.
bool isValidSalary = (anEmployee && anEmployee->salary > 0);
// 좀더 구체적으로 표현
bool isValidSalary = (anEmployee != nullptr && anEmployee->salary > 0);
- anEmployee의 포인터 값이 올바를 때에만 역참조하여 급여 정보를 가져온다.
동적으로 배열 할당하기
- 힙을 사용하여 배열을 동적으로 할당이 가능하다.
- 사용하는 연산자는 new[] 연산자이다.
- 사용 후 힙에서 제거하기 위해 delete[] 연산 사용
- new와 delete가 항상 한 쌍으로 이루어지지 않는다면 메모리 누수가 발생한다.
// size 만큼 배열 생성
int size = 8;
int* arr = new int[size];
// 일반 스택 기반 배열처럼 사용 가능.
arr[3] = 2;
// delete[] 연산 사용
delete[] arr;
arr = nullptr;
널 포인터 상수
- NULL은 실제로 상수 0과 같기 때문에 문제가 발생할 수 있다.
void func(char* str) {
cout << "char* version" << endl;
}
void func(int i) {
cout << "int version" << endl;
}
int main() {
func(NULL); // 아래쪽 func 호출
func(nullptr); // 위쪽 func 호출
return 0;
}
- 위의 예제에서는 func()를 호출할 때 NULL 값으로 호출하고 있다. 그러면 어떤 func가 호출 될까?
- NULL은 위에서 말했듯이 상수 0과 같기 때문에 아래의 func의 함수가 호출되어진다!
- 이를 방지하기 위해서 정식 널 포인터 상수인 nullptr을 사용하는 것이 좋다.
스마트 포인터
- 스마트 포인터를 사용하면 메모리와 관련하여 흔히 발생하는 문제를 해결할 수 있다.
- 해당 포인터로 지정한 객체가 스코프를 벗어나면 메모리가 자동으로 해제된다.
스마트 포인터의 2가지 타입
- 헤더 파일에 정의되어 있으며 std 네임스페이스에 속해있다.
- std::unique_ptr
- 가리키는 대상이 스코프를 벗어나거나 삭제될 때 할당된 메모리나 리소스도 자동으로 삭제
- 그러나 unique_ptr가 가리키는 객체를 일반 포인터로는 가리킬 수 없다.
- 함수의 return 문을 여러 개 작성하더라도 각각에 대해 리소스를 해제하는 코드를 작성할 필요가 없기 때문에 함수를 간결하게 작성 가능하다.
// 작성 예시
auto anEmployee = make_unique<Employee>();
주목할 점 : delete가 자동으로 호출되어지므로 따로 적을 필요가 없다. 그래서 smart!
- std::shared_ptr
- 데이터를 공유할 수 있다. shared_ptr에 대한 대입 연산이 발생할 때마다 레퍼런스 카운트(참조 횟수)가 하나씩 증가한다.
- shared_ptr가 스코프를 벗어나면 레퍼런스 카운트가 감소한다. 그러다 0이 되면 해당 데이터를 아무도 가지고 있지 않기 때문에 포인터로 가리키던 객체를 해제한다.
// 작성 예시
auto anEmployee = make_shared<Employee>();
if (anEmployee) {
cout << "Salary: " << anEmployee->salary << endl;
}
* shared_ptr에 배열도 저장할 수 있다. 하지만 C++17에서는 아래와 같이 작성해야 한다.
// C++17에서의 shared_ptr
shared_ptr<Employee[]> employees(new Employee[10]);
cout << "Salary: " << Employees[0].salary << endl;
이상으로 C++의 고급기능인 스트링과 포인터 그리고 동적 메모리에 대해 알아보았습니다.
감사합니다!
반응형
'🌈 프로그래밍 > C++' 카테고리의 다른 글
[C++] 1.2.4 ~ 1.2.5 레퍼런스와 익셉션 (2) | 2021.08.19 |
---|---|
[C++] 1.2.3 const의 이해 (3) | 2021.08.18 |
[C++] 1.1.15 이니셜라이저 리스트 (0) | 2021.08.17 |
[C++] 1.1.14 반복문 (2) | 2021.08.13 |
[C++] 1.1.13 구조적 바인딩 (0) | 2021.08.13 |