학교에서 강의를 통해 공부하면서 조금씩 적어놨던 것을 기록하고자합니다. 내용이 많지는 않습니다.
struct 구조체명
{
...
};
- 구조체와 클래스의 차이점은 단지 디폴트 접근 지정자의 차이 뿐이다.
(구조체는 public, 클래스는 priviate)
- C++에서 구조체 객체 생성 시에 "struct 키워드"를 사용하지 않는다.
e.g.
struct Circle
{
...
};
Circle circle;
- 클래스 선언부에서 정의한 함수의 경우에는 자동으로 인라인화 된다.
- 함수 호출에 대한 오버헤드를 막기 위해서는,
크기가 작고 자주 호출되는 함수에 대해 인라인화 하는 것이 좋다.(inline 키워드 사용)
- 인라인화는 컴파일러에 의해 이루어지며, 컴파일러가 임의로 인라인화 하는 경우도 있다.
- 컴파일러가 컴파일 전에 인라인화 후 컴파일을 진행한다.
- inline 함수는 선언과 정의를 분리하더라도 같은 파일에 작성해야한다.
- 객체 배열 선언 시에는 매개변수가 없는 디폴트 생성자가 호출된다.
(매개변수가 있는 생성자를 호출할 수 없다.)
- 유도 클래스 객체의 생성과정
1) 메모리 공간 할당 진행
2) 유도 클래스 생성자 호출
3) 기초 클래스 생성자 호출 및 실행
3) 유도 클래스 생성자 실행
=> 기초 클래스의 객체 생성 이후, 유도 클래스의 객체 생성
- 유도 클래스 객체의 소멸과정
1) 유도 클래스 소멸자 호출 및 실행
2) 기초 클래스 소멸자 호출 및 실행
3) 할당된 메모리 공간 해제
=> 유도 클래스의 객체 소멸 이후, 기초 클래스의 객체 소멸
- 클래스에 어느 생성자도 정의되어 있지 않으면 디폴트 생성자가 자동 삽입되나,
어느 생성자 하나만 있더라도 생성자는 자동 삽입되지 않는다.
- 객체 배열 초기화 방법
1) 반복문을 통해 객체마다 하나씩 초기화
2) 생성자 선언문 내부({})에서 객체마다 하나씩 초기화
- C++에서의 동적할당/해제는 반드시 new / delete로 하자.
객체 생성, 소멸시
1. 메모리 할당/해제 역할 뿐만 아니라
2. 생성자, 소멸자를 호출
하는 역할도 하기 때문이다.
malloc / free의 경우에는
생성자, 소멸자가 호출되지 않는다.
- 동적할당이 실패하면 nullptr을 반환한다.
* 동적할당의 예
int *p = new int(); // 0으로 초기화
int *p = new int(20); // 20으로 초기화
- 동적할당을 받지 않은 메모리나, 동일한 메모리를 두번 반환(해제)시에는 런타임 에러가 발생한다.
* 동적배열의 초기화
원래 동적배열 할당시 초기화가 불가능했으나,
C++11부터 초기화 리스트(initializer list)를 사용해 동적 배열을 초기화할 수 있다.
int fixedArray[5] = { 9, 7, 5, 3, 1 }; // initialize a fixed array in C++03 (고정배열)
int* array = new int[5] { 9, 7, 5, 3, 1 }; // initialize a dynamic array in C++11 (동적배열)
일관성을 위해 C++ 11에서는 유니온 초기화를 사용하여 고정 배열도 초기화할 수 있다.
int fixedArray[5] { 9, 7, 5, 3, 1 }; // initialize a fixed array in C++11(고정배열)
char fixedArray[14] { "Hello, world!" }; // initialize a fixed array in C++11(고정배열)
주의사항: 동적 배열을 C 스타일 문자열로 초기화할 수 없다.
char* array = new char[14] { "Hello, world!" }; // doesn't work in C++11(동적배열은 이렇게 불가능)
또한, 동적 배열은 명시적으로 길이를 설정해 선언해야 한다.
int fixedArray[] {1, 2, 3}; // okay: implicit array size for fixed arrays
int* dynamicArray1 = new int[] {1, 2, 3}; // not okay: implicit size for dynamic arrays
int* dynamicArray2 = new int[3] {1, 2, 3}; // okay: explicit size for dynamic arrays
- 메모리 누수 발생이 없도록 항상 주의하자!
- 프로그램이 종료되면, 운영체제는 누수 메모리를 모두 힙에 반환한다.
* this 포인터
1) 객체 자신에 대한 포인터
2) 클래스 멤버 함수 내에서만 사용
3) 컴파일러가 선언한 변수
4) 멤버함수가 호출될 때, 컴파일러에 의해 보이지 않게 전달되는 객체에 대한 주소
5) 멤버함수에 컴파일러에 의해 묵시적으로 삽입 선언되는 매개변수
6) 각 객체마다 this는 다름
7) static 멤버 함수에서 this는 사용불가
(객체 생성 전에 static 멤버함수 호출이 가능하기 때문이다.)
8) 멤버 이니셜라이저에서는 this 사용불가
* C++의 문자열
1) C-str(C 스타일의 문자열(널문자 포함))
2) C++ string 클래스 객체(널문자를 포함하지 않음)
stoi(s); // string => 정수 변환
atoi(s.c_str()); // (C)문자열 => 정수 변환
* C++에서의 입력
1) cin : 공백관련(' ', '\n', '\t') 문자 기준으로 입력 받음
2) cin.getline(buf, size, delim='\n') : 공백관련 문자를 포함하여 입력 받음
3) getline(cin, string객체, delim='\n') : 공백관련 문자를 포함하여 입력 받음
- cin.ignore(streamsize n = 1, int delim = EOF) : 입력버퍼에서 읽어들이는 함수
1. call by value 시,
- 함수의 매개변수 객체 생성시
매개변수 객체의 생성자는 호출되지 않으며, 복사 생성자가 호출된다
- 함수 종료시, 매개변수 객체의 소멸자가 호출된다.
=> 매개변수 객체의 생성자, 소멸자의 비대칭 실행구조
2. call by address 시,
- 함수 호출시 매개변수 객체의 생성자, 소멸자 둘다 호출되지 않음
* 동일 클래스 타입의 객체간 대입 연산 가능
(타입이 다른 경우는 형 변환이 일어난 다음, 대입 연산 진행)
=> 객체의 모든 데이터가 비트 단위로 복사됨
* 참조자(reference)
기존 변수에 별칭을 매기는 것에 불과하며,
새로운 공간을 할당하지는 않음
3. call by reference 시,
- 참조 매개변수는 실인자 변수를 참조함
- 참조 매개변수 이름만 생기고 새로운 공간이 생기지는 않음
- 참조 매개 변수는 실인자 변수 공간을 공유함
* 복사 생성자
- 한 클래스에 오직 한개만 선언이 가능함
- 디폴트 생성자와 같이 선언이 가능하며,
따로 선언하지 않으면 디폴트 복사 생성자가 생성됨
* 함수오버로딩
1. 가능 범위
1) 보통 함수들 사이(전역함수)
2) 클래스의 멤버함수들 사이
3) 상속 관계에 있는 기초클래스와 유도클래스의 멤버함수들 사이
2. 오버로딩 조건
1) 이름은 동일
2) 매개변수 타입이 다르거나, 개수가 다름
3) const 선언 유무
* 주의! : 리턴타입은 오버로딩 조건에 해당하지 않음.
- 컴파일러가 오버로딩 함수들을 구분함 => 실행시간 저하는 없음
* 생성자도 오버로딩 가능하나, 소멸자는 불가
* 디폴트 매개변수는 오른쪽부터 채우는 형식으로 되어 있어야만 한다.
컴파일러에 의해 변환되며, 디폴트 매개변수는 함수 선언부에만 작성한다.
* 멤버 이니셜라이저는 함수의 정의부에만 작성한다.
* static
- 생명주기 : 프로그램 시작될 때 생성, 프로그램 종료시 소멸
- 사용범위 : 변수나 함수가 선언된 범위 내에서 사용, 지역 or 전역
* 클래스의 멤버
- static : 프로그램 시작할 때 생성, 클래스당 하나만 생성, 모든 객체가 이 멤버를 공유, 클래스 멤버라고도 함
- non-static : 객체마다 객체 내에 생성, 객체와 생명주기가 동일, 인스턴스 멤버라고도 함
- static 멤버변수 초기화 구문은 반드시 전역공간에 선언하자.
- static 멤버는 클래스명::static멤버변수명 으로 접근이 가능하다.
* static 멤버함수가 접근 가능한 것
1) static 멤버 함수
2) static 멤버 변수
3) 해당 함수 내의 지역 변수
* static 멤버 함수는 non-static 멤버에 접근 불가능하나,
non-static 멤버 함수는 static 멤버에 접근이 가능하다.
* static 멤버함수는 this 포인터 사용이 불가능하다.
* friend 함수
- 클래스 외부에 작성된 함수(전역 or 다른 클래스의 멤버 함수 or
다른 클래스(멤버함수) 전체가 될 수도 있음)로, 멤버함수가 아니다.
- A 클래스 내에서 friend class B;가 선언되면, B 클래스는 A 클래스의 모든 멤버에 대해 접근이 가능해진다.
- freind 함수 개수에 제한은 없다.
* friend 선언은 클래스 내에 private, public 등 어디든지 가능하다.
* 연산자 오버로딩은 반드시 클래스와 관계를 가진다.
- 오버로딩된 연산자는 반드시 피연산자에 객체를 동반
- 연산자 오버로딩 함수는
1) 클래스의 멤버로 구현
2) 전역함수 + friend(또는 get() , set() 역할의 public 함수)로 구현
3) 1), 2)를 섞어서 구현
* 연산자 오버로딩이 불가능한 연산자
. .* :: ? :(삼항연산자)
* 기본 자료형이 아닌 경우에는 꼭 필요한 경우가 아니라면 참조자 활용이 효율적이다.
* 상속에서의 접근지정자의 의미
1. private : private보다 넓은 범위의 접근지정자는 private로, 나머지는 그대로 상속
2. protected : protected보다 넓은 범위의 접근지정자는 protected로, 나머지는 그대로 상속
3. public : public보다 넓은 범위의 접근지정자는 public로, 나머지는 그대로 상속
(public보다 넓은 범위의 접근지정자가 없으므로, 결국 private를 빼고는 그대로 상속한다는 의미)
*** 주의! : private 멤버는 어떻게 상속되어도
유도클래스에서의 "직접"접근은 불가능하다.
=> 상속 받은 기초 클래스의 멤버함수를 통한 간접접근만이 가능하다.
* 유도클래스의 생성자에서 기초클래스의 생성자 호출을 "명시"하지 않으면,
기초클래스의 void 생성자가 호출된다. (이 때, 기초 클래스의 void 생성자가 정의되어 있어야 한다.)
- 유도 클래스 객체의 생성과정
1) 메모리 공간 할당 진행
2) 유도 클래스 생성자 호출
3) 기초 클래스 생성자 호출 및 실행(동적 바인딩에 의함)
3) 유도 클래스 생성자 실행
=> 기초 클래스의 객체 생성 이후, 유도 클래스의 객체 생성
- 유도 클래스 객체의 소멸과정
1) 유도 클래스 소멸자 호출 및 실행(동적 바인딩에 의함)
2) 기초 클래스 소멸자 호출 및 실행
3) 할당된 메모리 공간 해제
=> 유도 클래스의 객체 소멸 이후, 기초 클래스의 객체 소멸
* 다중 상속으로 인한 기초클래스 멤버의 중복 상속 해결 => 가상 상속
- 유도클래스의 선언문에서 (중복되는) 기초클래스 앞에 "virtual" 선언
=> 기초클래스의 멤버가 중복 상속되는 것을 방지
* boolalpah => bool값을 문자열로 출력하는 operator
e.g.
cout << boolalpha << true << endl; // "true" 출력
cout << boolalpha << false << endl; // "false" 출력
* 다형성을 이용하기 위해 부모클래스 포인터로
자식클래스 객체를 가리키는 경우 주의해야 할 점
- 컴파일러는 "포인터형"을 기준으로 연산을 한다.
따라서 포인터형이 아닌 객체 자체의 타입을 기준으로 연산하고 싶다면
"virtual" 선언이 필요하다. 이는 상속관계의 소멸자 호출에서도 중요하다.
* 가상 함수(virtual function)
- virtual 키워드로 선언된 멤버 함수
- virtual 키워드의 의미
1) 동적 바인딩 지시어
2) 컴파일러에게 함수에 대한 호출 바인딩을 실행 시간까지 미루도록 지시
3) 가상함수는 기본클래스나 파생클래스 어디에서나 선언될 수 있다
* 함수 오버라이딩(function overriding)
- 파생 클래스에서 기본 클래스의 가상 함수와 동일한 이름의 함수 선언
- 기본 클래스의 가상 함수의 존재감을 상실시킴
- 가상 함수를 호출하면, 파생 클래스에서 오버라이딩한 함수가 호출되도록 동적 바인딩
- 함수 재정의라고도 부름
- 다형성의 한 종류
* 가상 함수를 재정의하는 오버라이딩의 경우 함수가 호출되는 실행 시간에 동적 바인딩이 일어나지만,
그렇지 않은 경우 컴파일 시간에 결정된 함수가 단순히 호출된다.(정적 바인딩)
* 동적 바인딩
- 가상함수가 호출되면, 실행 중에 객체 내에 오버라이딩된 가상함수를 동적으로 찾아 호출
- 기본클래스의 포인터로 가상함수가 호출될 때 일어난다.
- 실행 중에 이루어짐
- 실행시간 바인딩, 런타임 바인딩, 늦은 바인딩으로 불림
=> 이것 때문에 포인터 기준의 클래스에 있는 가상함수가 실행되는 것이 아니라,
해당 객체 기준의 클래스에 있는 가상함수가 실행된다.
* 오버라이딩의 성공 조건
- 가상함수 이름, 매개 변수 타입과 개수, 리턴 타입이 모두 일치
- 오버라이딩 시 virtual 지시어 생략 가능
- 가상함수의 virtual 지시어는 상속됨, 파생 클래스에서 virtual 생략 가능
- 가상함수의 접근 지정
private, protected, public 중 자유롭게 지정 가능
* 가상(virtual) 소멸자
- 소멸자를 virtual 키워드로 선언
- 소멸자 호출 시 동적 바인딩 발생
* 생성자 및 소멸자의 호출은 일단 기본적으로 "객체 기반"이 아니라 "포인터 기반"으로 호출된다.
virtual의 유무에 따라 동적 바인딩 유무가 결정되고,
이로 인해 호출 및 실행이 런타임 시간에 변동하는 것 뿐이다.
* 생성자는
유도 클래스의 생성자가 자신의 코드를 "실행 전", 기초 클래스의 생성자를 호출하도록 컴파일된다.
* (virtual) 소멸자는
유도 클래스의 소멸자가 자신의 코드 "실행 후", 기초 클래스의 소멸자를 호출하도록 컴파일된다.
* 오버로딩
1) 매개변수 타입이나 개수가 다르나, 이름이 같음
2) 클래스 멤버들 사이, 외부 함수들 사이, 상속관계 사이에 존재 가능
3) 정적 바인딩, 컴파일 시에 중복된 함수들의 호출 구분
3) 컴파일 시간 다형성
* 함수 재정의(가상함수가 아닌 멤버에 대해)
1) 유도 클래스에서 기초클래스 멤버함수에 대해
이름, 매개변수 타입과 개수, 리턴타입까지 동일한 원형으로 재작성
2) 상속관계 사이에 존재 가능
3) 정적 바인딩, 컴파일 시에 함수들의 호출 구분
4) 컴파일 시간 다형성
* 오버라이딩(가상함수가 대상)
1) 유도 클래스에서 기초클래스 멤버함수에 대해
이름, 매개변수 타입과 개수, 리턴타입까지 동일한 원형으로 재작성
2) 상속관계 사이에 존재 가능
3) 동적 바인딩, 실행시간에 본래 호출된 함수가 오버라이딩된 함수를 찾아 실행(가상함수 테이블에 존재함)
4) 실행 시간 다형성
* 순수 가상함수
- 기초 클래스의 가상함수 목적
- 파생 클래스에서 오버라이딩 할 함수를 알려주는 역할
- 실행할 코드를 작성할 목적이 아님
- 실행 코드가 없음
- pure virtual function
- 함수의 코드가 없고 선언만 있는 가상 멤버 함수
- 선언 방법 : 멤버 함수의 원형 = 0; 으로 선언
class Shape
{
... 생략
public:
virtual void draw() = 0; // 순수 가상함수 선언
};
* 추상 클래스 : 최소한 하나의 순수 가상 함수를 가진 클래스
* 추상 클래스의 특징
- 온전한 클래스가 아니므로 객체 생성 불가능
e.g.
Shape shape; // 컴파일 오류
Shape *p = new Shape(); // 컴파일 오류
- 추상 클래스의 포인터는 선언 가능
Shape *p;
* 추상 클래스의 목적
- 추상 클래스의 인스턴스를 생성할 목적 아님
- 상속에서 기초 클래스의 역할을 하기 위함
- 순수 가상함수를 통해 파생 클래스에서 구현할 함수의 형태(원형)을 보여주는 "인터페이스 역할"
- 추상 클래스의 모든 멤버 함수를 순수 가상 함수로 선언할 필요는 없음
* 추상 클래스의 상속
- 추상 클래스를 단순 상속하면 자동 추상 클래스
* 추상 클래스의 구현
- 추상 클래스를 상속받아 순수 가상함수를 오버라이딩하면,
파생 클래스는 추상 클래스가 아님(온전한 클래스가 됨)
* 템플릿
- 함수나 클래스를 일반화(generic)하는 C++ 도구
- template 키워드로 함수나 클래스 선언
- 변수나 매개변수의 타입만 다르고, 코드 부분이 동일한 함수를 일반화시킴
- 제네릭 타입 : 일반화(generic)를 위한 데이터 타입
* 함수 템플릿이나 클래스 템플릿 자체만으로는 코드가 생성되지 않으며,
제네릭 타입이 구체화(specialization)되어 호출되면,
그에 맞는 코드(함수 혹은 클래스)가 생성되어 컴파일 된다.
* 컴파일러는 재정의된 함수를 템플릿 함수보다 우선하여 바인딩한다.
* 템플릿 부분 특수화도 가능하다.
그러나 전체 특수화와 부분 특수화 둘 다 작성되어 있다면,
전체 특수화가 우선시 된다.
* STL(Standard Template Library)
1. 표준 템플릿 라이브러리 : C++ 표준 라이브러리 중 하나
2. 많은 제네릭 클래스와 제네릭 함수 포함 : 개발자는 이들을 이용하여 쉽게 응용 프로그램 작성
* STL의 구성
1. 컨테이너 : 템플릿 클래스
- 데이터를 담아두는 자료구조를 표현한 클래스
- 리스트, 큐, 스택, 맵, 셋, 벡터
2. iterator(반복자) : 컨테이너 원소에 대한 포인터
- 컨테이너의 원소들을 순회하면서 접근하기 위해 만들어진 컨테이너 원소에 대한 포인터
3. 알고리즘 : 템플릿 함수
- 컨테이너 원소에 대한 복사, 검색, 삭제, 정렬 등의 기능을 구현한 템플릿 함수 + 전역함수
- 컨테이너의 멤버 함수 아님
- iterator와 함께 작동
* STL이 선언된 이름 공간은 std
STL은 std 이름 공간에 작성되었기 때문에,
STL을 사용하려면
1) using namespace std; 를 선언 후 사용하던지,
2) 매번 이름공간을 같이 선언 해주어야 한다. e.g. std::(...)
* auto : C++11부터 컴파일러에게 변수 선언문에서 추론하여 타입을 자동 선언하도록 지시
- 복잡한 변수 선언을 간소하게, 긴 타입 선언 시 오타 줄임
- 포인터도 가능하나, 참조자나 const는 불가능하므로 따로 지정해주어야 함(주의)
* 람다(Lambda)
- 람다 대수와 람다식 : 람다 대수에서 람다식은 수학 함수를 단순하게 표현하는 기법
- 수학에서 이름 없는 함수를 람다식이라고 부른다.
- C++ 람다 : 익명의 함수 만드는 기능으로 C++11에서 도입
- [ 캡쳐리스트(람다식 외부에 선언된 변수 목록) ](매개변수리스트) ->리턴타입 { ... } (매개변수값 지정가능);
- 리턴타입이 void인 경우 "->리턴타입" 생략이 가능
- auto 변수에 람다식을 저장후, auto 변수를 통해 호출도 가능
(람다식의 형식은 컴파일러만 알기 때문이다.)
* 실행오류의 종류
1) 컴파일 오류(문법)
2) 런타임 오류(논리)
* 예외처리 방법
try - throw - catch 방법
1) throw로 예외객체(예외데이터)를 던지면 catch에서 받아서 처리
- 기본형 데이터 타입이 아닌 경우는 참조형으로 받는게 효율적
- 예외객체를 클래스객체로 구현하면 조금 더 자세한 예외처리가 가능
2) try 구문은 하나의 기능(work) 단위로 구성하는 것이 좋음
3) catch 구문은 여러개일 수 있음,
그러나 클래스 상속관계에 있는 객체에 대한 catch 구문을 여러개 작성할 경우 유의해야 함
- 반드시 가장 말단의 유도클래스 예외객체와 관련된 catch 구문부터 순서대로 작성해야 함
=> 그렇지 않으면 무조건 기초클래스 catch 구문에 걸리게 된다.
4) 만약 throw 하였는데 해당 함수 안에서 처리되지 않으면,
해당 함수를 호출한 함수로 다시 throw된다.
main에서조차 throw된 예외객체가 처리되지 않으면 프로그램이 종료된다.(terminate() 함수가 호출됨)
'Programming' 카테고리의 다른 글
C 문자열 함수 정리 (0) | 2020.08.24 |
---|---|
컴파일과 링크 (0) | 2020.08.22 |