보통 if문 대신 사용하며 case를 나눌 필요가 있을 때, if-else를 여러번 쓰기 힘들 때 사용한다.
switch (n) // n은 정수형 변수로 전달되는 인자정보, n에 지정된 값에 따라 case 영역 실행
{
case'M':
// 조건 입력break;
case2:
// 조건입력break;
default:
// else문과 비슷한 역할
}
이때, break를 하지 않으면 다음 break가 있는 case문까지 같이 실행되기에 적는 것이 좋다.
§ break와 continue
break: 가장 가까운 반복문을 탈출한다.
continue: 실행위치와 상관없이 반복문의 조건검사위치로 이동한다.
이때, 이후부터는 continue는 생략하고 재실행한다.
※ while 문 vs for 문
보통 while문은 특정 종료조건이 나타나기 전까지의 지속적인 실행을 위해,
보통 for문은 반복횟수가 정해져 있는 경우에 많이 사용된다.
※ 비트 연산자
메모리에 할당된 정수값을 bit단위로 논리연산을 실행하기 위해 사용한다.
※ 함수 선언
[함수 return 타입] [함수 이름] (인자) {
/*
*
*/
}
인자 (argument): 함수 호출 시 전달되는 "값"
매개변수(parameter): 그런 인자를 받는 "변수"
※ 지역변수와 전역변수, static과 block scope
지역변수(local variable): 중괄호에 의해 형성되는 영역안에 존재, "스택"이라는 메모리 영역에 할당
전역변수(global variable): 초기화를 하지 않으면 0으로 초기화되며 많이 사용하면 효율이 떨어진다.
§ 지역변수와 Block Scope
특정함수, 명령문의 블록안에 선언된 변수는 블록범위(Block Scope)밖에서는 사용이 불가능하다!
int a = 1;
{
a = 2; // a는 2로 초기화 됨
}
{
int a = 5; // 앞선 a와는 전혀 다른 a// Block scope가 끝나면 사라짐
}
{
a++; // 여기까지 a값은 2, 아래줄 실행 시 a=3으로 증가
}
printf("%d", a); // 3 출력
§ static: "한 함수 내"에서 "지역변수의 전역변수화"를 해주는 tool
선언된 함수내에서만 접근 가능 (지역변수의 특징)
1회만 초기화, 종료전까지 메모리 공간에 저장 (전역변수 특징)
★ 전역변수와 달리 Block Scope 내부에서만 접근 가능
- 이미 지워진 문자열을 대상으로 p1의 delete 연산을 진행해야 한다는 문제점이 발생한다.
★ 따라서 깊은 복사를 위한 복사생성자를 만들어 각각의 문자열을 참조해 문제가 발생하지 않게 해줘야 한다.
※깊은복사
- 멤버 뿐만 아니라 포인터로 참조하는 대상까지 복사하는 방법
- default 생성자가 불충분할 때 사용자 정의 복사생성자를 선언한다.
위의 코드에서 아래 코드를 추가해주면 된다.
- 멤버변수 age의 멤버 대 멤버 복사
- 메모리공간 할당 후 문자열 복사, 할당된 메모리 주소값을 멤버 name에 저장
즉, 두 객체를 각각 동적으로 할당된 배열을 갖게 하는데 이를 깊은 복사(deep copy)라 한다.
§ 또 다른 예제
DynamicArray (const DynamicArray& other) : mSize(other.mSize), mArray(nullptr){
// 자신만의 동적데이터공간 할당
mArray = newint[mSize];
// 다른 객체로부터의 데이터 복사for (int i = 0; i < mSize; i++) {
mArray[i] = other.mArray[i];
}
}
※ 복사생성자 호출 시점:객체를 새로 생성하되 생성과 동시에 동일한 자료형의 객체로 초기화 할 때.
1. 기존에 생성된 객체를 이용해 새로운 객체를 초기화 할 때. [Person p2 = p1;]
2. Call-by-Value 방식의 함수호출과정에서 객체를 인자로 전달할 때
3. 객체를 반환하지만 참조형으로 반환하지 않는 경우.
- 함수의 값을 반환하면 별도의 메모리공간이 할당되고 이 공간에 반환값이 저장된다. (반환값으로 초기화)
2. CBV 함수호출에서 객체를 인자로 전달하는 경우 예시
출처) 윤성우의 열혈C++프로그래밍
3. 객체를 반환하지만 참조형으로 반환하지 않는경우
출처) 윤성우의 열혈C++ 프로그래밍
cf. 생성자, 소멸자, 복사생성자 호출시점 명확히 알고가자!
Q. 어 그러면 생성자가 모두 출력된 후 소멸자가 출력되는 거면 되게 쉽게 생각해도 되겠네요?
A: 그렇게 생각하면 안된다. 객체 소멸은 여러 가지의 경우가 있는데 그 중 하나로는 객체는 함수가 종료되면 사라진 다는 점이다. 정확히는 함수 내부의 nested block이 끝나면 사라지게 되는 것이다.
예를 들어 아래와 같이 p1객체에 block을 씌웠다 가정하자.
{
Person p1(5);
cout << "부모 객체 p1 생성" << endl;
}
Person p2(p1); //오류 발생
cout << "부모 객체 p2 생성 및 객체 p1 복사" << endl;
만약 실행되었다면 다음과 같이 출력이될 것이다.
부모의 생성자
부모 객체 p1 생성
부모의 소멸자
부모의 생성자
부모 객체 p2 생성 및 객체 p1 복사
부모의 생성자
하지만 부모의 소멸자가 호출되어 객체 p1이 없어지니 객체 p2는 객체 p1을 복사하지 못하게 되는 것이다.
이해를 위해 또 다른 예시를 들어 보겠다.
{
Person p1(5);
}
cout << "부모 객체 p1 생성" << endl;
이렇게 된다면 출력은 어떻게 될까?
이는 아래와 같다.
부모의 생성자
부모의 소멸자
부모 객체 p1 생성
즉, 블럭이 종료되면 소멸자가 호출된다는 것을 알 수 있다.
cf. 만약 private멤버로 char* name과 같은 배열관련 변수가 들어갔다면 delete []name로 memory leak을 막아야 한다.
※ 임시객체를 이용한 obj.func().func(); 연산
※ 반환시 만들어진 (임시)객체의 소멸
1. 임시객체는 다음행으로 넘어가면 바로 소멸된다.
2. 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.
또 다른 예시)
※ r-value 참조
r-value 참조는 상수나 임의 저장소를 참조하는 개념으로 데이터 타입에 Double Ampersand(&&)를 붙여 표기한다.
이런 더블 엡퍼센드를 r-value 참조자라 부른다.
사실 이런 r-value 참조는 l-value랑 달리 OOP의 원칙을 준수하는 것보다
프로그램의 성능향상 및 프로그램 개발 생산성에 초점을 맞춰 생겨난 개념이다.
/*---------------일반 변수--------------- */voidf(){
int val = 10; // val 변수 생성 후 10 입력
}
// 함수 반환 시 변수를 소멸/*---------------r value 참조 변수--------------- */voidf(){
int&& val = 10; // 저장소를 만들어 10을 입력, 저장소에 대해 r value 참조
}
// 함수 반환 시 임시저장소와 함께 r value 참조를 소멸
위의 경우, 표현은 다르지만 작업결과는 동일한데 이말은 즉, r value 참조자는 위와 같은 목적으로 만들어진 개념이 아니다
- 이동생성자가 호출되면얕은복사(shallow copy) => 원본의 소유권을 대상으로 이전하는(move)방식으로 객체를 생성.
- 그 이후 원본 객체를 NULL로 초기화 하여 접근할 수 없게 한다.
Q. 복사생성자가 있는데 굳이 이동생성자를 써야하는 이유는 뭘까?
A. 아래 코드를 통해 설명하겠다.
#include<iostream>#include<cstring>#include<vector>usingnamespace std;
classAnimal {private:
char *name;
int age;
public:
Animal(int age_, constchar* name_) {
age = age_;
name = newchar[strlen(name_) + 1];
strcpy(name, name_);
}
Animal(const Animal & a) { //복사생성자에 의한 깊은복사
age = a.age;
name = newchar[strlen(a.name) + 1];
strcpy(name, a.name);
cout << "복사생성자" << endl;
}
Animal(Animal && a) noexcept { //이동생성자에 의한 얕은복사
age = a.age;
name = a.name;
cout << "이동생성자" << endl;
a.name = nullptr;
}
~Animal(){
cout <<"Destructor!!" << endl;
if(name)
delete [] name;
}
voidchangeName(constchar *newName){
strcpy(name, newName);
}
voidprintAnimal(){
cout << "Name: " << name << " Age: " << age << endl;
}
};
intmain(){
Animal A(10, "Jenny");
A.printAnimal();
vector<Animal> vec; // Animal 벡터 타입// Animal 벡터 객체 삽입
vec.push_back(A);
vec.push_back(A);
vec.push_back(A);
vec.push_back(A);
vec.push_back(A);
A.printAnimal();
for (int i = 0; i < 5; i++) {
vec[i].printAnimal();
}
return0;
}
복사생성자만 있는 경우, 새 vector 생성마다 기존 vector에 저장된 객체들이
새로운 vector로 복사될 때, 복사생성자가 호출되고 성능저하가 일어남
이는 복사생성자의 new로 인한 메모리 할당의 반복으로 일어나는 현상임.
이런 문제를 해결하기 위해 shallow copy를 수행하는 이동생성자를 정의해주는데,
새로운 vector로 이동시, 이동생성자가 호출되며 불필요한 메모리할당을 줄여준다.
[이동생성자 사용 시 주의할 점]
1. 이동생성자에는 noexcept 키워드가 지정되어야 한다. (이동생성자 수행중, 예외가 없다는 것을 컴파일러가 인지해야함)
2. shallow copy가 일어나는 변수(포인터, 주소관련 변수)에 nullptr를 넣어줘야 함