1. 복사생성자가 호출되는 경우
=> 말그대로 생성자가 호출되는 시점에 대입이 아닌 복사가 필요한 경우
String s1("abc"); // 이 경우 그냥 생성자가 호출되서 대입됨
String s2(s1); // 이 경우 복사생성자가 호출 됨, s1을 s2로 복사
String s3 = s1; // 이 경우 또한 윗줄과 같은 취급이기때문에 복사생성자가 호출됨
단, String s3; s3=s1;하면 복사생성자 호출 안됨
생성자는 말 그대로 생성할때 호출되는 것이므로 위 경우 생성되고나서 대입임
2. 상속
부모클래스, 자식클래스라고 흔히 말하는데 C++의 경우 여러명의 부모클래스를 가질 수 있기에
부모가 여러명이 되는게 어색할 수 있으니 기반클래스, 파생클래스라고 부르기도 한다
기반클래스에 what() 함수가 있을때 파생클래스에서 기반클래스를 상속받았기 때문에
what함수를 구현하지 않아도 what함수가 사용 된다.
다만 what에서 사용하는 인스턴스 변수들은 기반클래스의 변수를 사용하게 됨
그래서 파생클래스에서 what함수를 또 구현해 주면 그땐 파생클래스의 변수를 사용함
기반클래스 a; a.what(); 하면 당연히 기반클래스의 what이 호출
파생클래스 b; b.what(); 하면 당연히 파생클래스의 what이 호출
하지만 파생클래스에 what함수의 정의가 없으면 기반클래스의 what이 호출될 뿐
반면
파생클래스 b;
기반클래스* ap = &b; 일땐 ap->what() 하면 클래스 따라가기때문에 기반클래스의 what이 호출
이유는 기반클래스에는 파생클래스의 정보가 없음. 즉 파생클래스에 what함수가 있더라도 기반클래스는 모름
그러니 기반클래스의 what이 호출되는 것
protected
private로 선언된 변수는 자식클래스라도 직접적으로 변경 불가능함
private은 무슨일이 있어도 본인 클래스 내에서만 접근 가능
protected로 선언되어 있다면 자식클래스에서까지는 직접 접근 가능
파생클래스의 생성자는 당연히 기반클래스가 있어야 생성 가능하다.
기본적으로 파생클래스의 생성자를 사용하면 기반클래스의 기본생성자가 호출된 이후 파생클래스의 생성자가 호출된다.
만약 기반클래스의 기본생성자가 아닌 다른 생성자를 사용하고 싶다면 초기화리스트에 기반생성자의 해당생성자를 적어줘야 함
is-a
class Manager : public Employee
위처럼 쓰면 의미는
1) Manager클래스는 Employee의 모든 기능을 포함한다.
2) Manager클래스는 Employee의 모든 기능을 수행 할 수 있기 때문에 Manager를 Employee라고 칭할 수 있다
3) 즉 모든 Manager는 Employee다.
Manager is a Employee
그러므로 모든 상속관계는 is-a관계라고 볼 수 있음
has-a
자동차 클래스를 만든다면 엔진클래스, 타이어클래스, 브레이크클래스 등이 필요해지는데
이를 상속받는다고 해서 자동차는 엔진이다. 자동차는 브레이크다. 이렇게 되면 말이 안되고
자동차는 엔진을 가진다. 자동차는 브레이크를 가진다. 이럴때 has-a 관계
class Car
{
Encing e;
Brake b;
}
업캐스팅 = 파생클래스에서 기반클래스로 캐스팅 하는 것
Derived d;
Base *ap = &d; // 파생클래스 객체를 기반클래스로 넣는 것
다운캐스팅 = 반대로 기반클래스 객체를 파생클래스쪽에 넣는것
절대 권장되지 않음
사용 가능한 경우는 아래와 같은 경우
Base p;
Derived c;
Base* p_p = &c; // 업캐스팅이니 문제 없음
// 근데 여기서
Derived* p_c = p_p; // Derived에 Base를 넣으니 다운캐스팅임
즉 이러면 오류가 발생해서 컴파일이 안됨
하지만 사실 p_p가 가리키는 내용은 Derived이기때문에 p_c에 넣으려는것도 Derived
그러니 사실 문제가 되진 않아 보이니 캐스팅 시도 가능
다만 오류가 발생한다면 오류를 알려주게끔 dynamic_cast 사용
이런식으로 오류가 발생할 여지가 있는 포인터간의 캐스팅을 위해 dynamic_cast가 지원 됨
virtual 키워드
Base p;
Derived c;
Base* p_p = &c;
p_p->what();을 하면 Base의 what이 호출되지만, Base의 what을 virtual로 정의하면
Derived의 what이 잇는지 확인하고 있으면 Derived의 what을 호출하게 만듬
이런식으로 런타임중에 정해지는걸 동적바인딩이라고 부름
override 키워드
C++ 11에 나온 것
파생클래스에서 기반클래스의 가상함수를 오버라이드하는 경우 명시적으로 나타낼 수 있음
void what() override { std::cout << s << std::endl; } 처럼 뒤에 override를 붙이면 명시적으로 표현 가능
override한다고 썼는데 기반클래스에 what이 없어서 override할게 없으면 컴파일오류로 알려줌
안적으면 안알려주니까 적으면 실수를 줄여줄듯
다형성
하나의 메소드를 호출했음에도 그때마다 여러가지 다른 작업을 하는 것
오버로딩이나 오버라이딩에 의한 동적바인딩같은 것들
Base클래스의 소멸자는 반드시 virtual로 선언해야 한다.
안그러면
Parent *p = new Child(); 같은 경우 delete p;하면 chile의 소멸자가 아닌 부모의 소멸자만 호출되므로
모든 함수를 virtual로 선언하면 안전할것 같은데 왜 그러지 않는가?
자바는 이미 모든 함수가 default로 virtual로 선언된다.
하지만 c++에선 사용자가 직접 선언해야 하는데
가상함수를 사용하면 약간의 오버헤드가 존재하기 때문
가상함수면 파생클래스에 해당 함수가 있는지 확인하는 절차가 한번 더 추가되기 때문
순수가상함수
virtual void func() = 0;처럼 virtual과 =0을 붙여서 반드시 오버라이딩하게 만드는 함수
그냥 가이드라인만 제공하기 위해 만드는 설계도 개념
추상클래스
순수 가상함수를 하나라도 포함하고 있는, 반드시 상속되어야 하는 클래스
3. 입력(cin)
cin의 4가지 상태
good : 입출력 작업이 가능할 때
bad : 복구 불가능한 오류 발생시
fail : 복구 가능한 오류 발생시
eof : 입력 작업시에 EOF 도달시
if(std::cin.fail()) 오류 발생시
{
std::cin.clear(); // 플래그들을 초기화하고
std::cin.ignore(100, '\0'); // '\0'이 나올때까지 무시
}
입력 형식 바꾸기
예를들어 입력값을 16진수 취급하게 하는 것
ff 입력시 255로 취급, 123 입력시 291로 취급
std::cin.setf(ios_base::hex, ios_base::basefield);
setf함수는 인자 1개만 받는 경우와 2개 받는경우 2개가 있음
한개만 받는건 인자로 준 형식플래그 적용
두개 받는건 두번째 인자의 내용을 초기화하고, 첫번째 인자를 적용하는 것
또는 std::cin >> hex >> t; 처럼 써도 똑같게 됨
여기서 hex같이 스트림을 조작해서 바꿔주는 함수를 조작자라고 부름
setf의 hex(상수값)와 여기서의 hex(함수)는 전혀 다른 것
hex외에도 boolalpha(true, false를 문자열 그대로 처리)
left(왼쪽정렬), right(오른쪽정렬)
endl도 조작자 중 하나(endl는 스트림에 '\n'을 출력함과 동시에 flush도 해줌)
flush(버퍼를 모두 내보냄) => 일반적으로 버퍼에 모아서 내보내지만 정해진 시간에 딱딱 맞춰서 출력해야만 한다면 사용
4. 파일입출력
파일스트림
파일의 위치는 소스파일이 있는 위치와 동일
#include <fstream>
std::ifstream in("test.txt");
std::string s;
if(in.is_open())
{
in >> s;
std::cout >> s << std::endl;
}
else
{
std::cout << "열리지 않음" << std::endl;
}
C에서는 file open하면 close도 해줘야했지만
C++은 소멸자에서 close하기때문에 안해줘도 된다
다만 같은 in변수에 다른 파일 넣으려면
in.close();
in.open("other.txt"); 처럼 쓸 수 있다.
getline함수
getline(cin, buf); // \n까지 받음
cin.getline(buf, bufsize, '+'); // +가 나올때까지 받음
* 파일출력할때 CSV파일 형태로 하면 엑셀에서 열 수 있다.
필요할때 공부해보기
5. 중위표기법/후위표기법
중위표기법
3 + 4 * 5 + 4 * (7 - 2) 이런걸 중위표기법이라고 부름
사람이 보기 쉬운 것
하지만 컴퓨터 입장에선 괄호도 생각해야하고 곱,나누기와 더하기빼기의 우선순위도 생각해야해서
번거로움
후위표기법
3 4 5 * + 4 7 2 - * + 이런걸 후위표기법이라고 부름
컴퓨터에 친숙한 방식
피연산자를 만나면 스택에 저장
연산자를 만나면 스택에서 숫자 2개를 가져와서 계산하고 다시 스택에 넣음
증위표기법 -> 후위표기법 변환
1. 피연산자는 그냥 vec에 넣음
2. 여는괄호 (, [, { 를 만날땐 스택에 push
3. 닫는 괄호를 ), ], } 를 만날땐 여는 괄호가 pop될때까지 pop되는 연산자를 vec에 넣음
4. 연산자일 경우 자기보다 우선순위가 낮은 연산자가 스택의 최상단에 올때까지(혹은 스택이 빌때까지)
스택을 pop하고 (낮은것은 pop하지 않고) pop된 연산자를 vec에 넣음
그리고 마지막에 자신을 스택에 push
6. template
template 함수
기본형
template <typename T>
T add_num(T t)
{
return t;
} // 이런식으로 add_num(변수); 로 쓰면 알아서 변수 자료형에 맞게 처리해줌
template 인자 추가
template <typename T, int num>
T add_num(T t)
{
return t + num;
} // add_num<int, 5>(x); 처럼 사용하면 x+5를 리턴해줌
디폴트 인자 사용도 가능함
template <typename T, int num = 5>
T add_num(T t)
{
return t + num;
} // add_num(x);하면 알아서 5 더해서 리턴해줌
템플릿 파라미터 팩
#include <iostream>
template <typename T>
void print(T arg) {
std::cout << arg << std::endl;
}
template <typename T, typename... Types>
void print(T arg, Types... args) {
std::cout << arg << ", ";
print(args...);
}
int main() {
print(1, 3.1, "abc");
print(1, 2, 3, 4, 5, 6, 7);
}
template <typename T , typename... Types>에서 ...을 템플릿 파라미터 팩이라고 부름
0개이상의 템플릿 인자들을 의미함(템플릿은 type 앞에 ...이 옴)
마찬가지로 함수에 인자로 print(T arg, Types... args)에서 ...을 함수 파라미터 팩이라고 부름
0개 이상의 함수 파라미터를 의미(함수는 type 뒤에 ...이 옴)
파라미터팩은 추론된 인자를 제외한 나머지들을 나타내게 됨
print(1, 3.1, "abc");를 호출하면
첫번째 인자는 1이니 int로 사용되고 arg는 1로 처리됨
그리고 나머지들은 args에 3.1과 "abc"로 들어감
그리고 print(args...);에 3.1과 "abc"가 들어가서 또 동작
재귀적으로 사용
Fold
가변길이 템플릿은 반드시 재귀형태로 구현해야하기때문에 재귀 종료를 위한 함수를 만들어야함
그러니 재귀함수 호출을 종료하기 위한 베이스케이스(예를들면 sum을 계속 하면 마지막꺼에선 return 0을 해준다던가)
이런걸 꼭 만들어야하니 코드가 복잡해 질 수 있음
Fold를 쓰면 더 간단해짐
#include <iostream>
template <typename... Ints>
int sum_all(Ints... nums) {
return (... + nums);
}
int main() {
// 1 + 4 + 2 + 3 + 10
std::cout << sum_all(1, 4, 2, 3, 10) << std::endl;
}
return (... + nums); 부분이 return ((((1 + 4) + 2) + 3) + 10); 처럼 해석됨
fold식은 반드시 (... + nums)처럼 괄호로 감싸야 한다
using
C++ 11부터 typedef 대신 좀 더 직관적인 using을 사용 가능
typedef unsigned int UINT;
using UINT = unsined int; 처럼 사용 가능
함수포인터는
기존에 typedef void (*func)(int, int); 였다면
using func = void (*)(int, int);로 사용 가능