C++ 프로그래밍

김동현·2022년 5월 30일
0

기본개념

선언 : 식별자를 컴파일러에게 알려주는것
정의 : 선언의 일종으로 식별자와 관련된 모든 정보를 제공하는 구문
할당 : 메모리에 데이터를 저장하는 것
초기화 : 정의와 할당을 동시에 하는 것

Reference

C언어에서 간접참조를 위해 사용한 포인터가 사용하기 불편하여 C++에선 레퍼런스가 나왔다.

포인터의 불편함

  • NULL체크를 해야함
  • 역참조 연산자를 사용해야 한다

레퍼런스의 사용은 간접참조를 위해서 쓰는 것.
레퍼런스는 꼭 초기화를 해 주어야 한다.

OverLoading 오버로딩

함수를 작성할 때 동일한 이름을 적는 것
함수의 식별자를 같게 적어도 되지만 매개변수 목록이 달라야 합니다.

반환형이 다를경우 오버로딩 되지 않는다.

오버로딩과 오버라이딩의 차이

오버로딩
은 같은 이름의 함수에 매개변수를 다르게 하여 매개변수에 따라 같은 이름의 다른 함수가 실행 되는 것

오버라이딩
상속받았을 때 부모클래스의 함수를 사용하지 않고 다른 기능을 실행할 때 같은 이름, 매개변수로 재정의해 사용하는 것

Class

클래스는 객체의 청사진 이다.
이 객체가 어떤 식으로 생겨야 하는지 알려주는 얘
사용자 정의 타입이다.

데이터 맴버

  • 데이터를 저장 해 준다. 다시말해 객체이다
  • 필드 라고 불리기도한다.

맴버 함수

  • 타입의 기능부분
  • 메소드 라고 불리기도한다.

내부 타입

  • 클래스 안에 다른 클래스를 만들거나 열거형, 혹은 타입별칭을 붙일 수 있다.

클래스 타입은 Class, Struct, Union이 있다.
C언어에서 구조체 만드는 것과 비슷하다

객체와 인스턴스의 차이 객체가 조금 더 포괄적인 개념 데이터 영역 메모리의 특정 부분을 객체라 함
인스턴스도 메모리를 의미하는데 Class를 바탕으로 메모리에 표현한 것을 인스턴스 라고 한다.

  • 모든 인스턴스틑 객체다 하지만 모든 객체는 인스턴스가 아니다.

인스턴스 멤버는 크게 비정적멤버 정적 멤버로 나뉜다.
비정적 멤버는 인스턴스가 사용하는 멤버이며 스테틱 키워드가 붙지 않으면 다 비정적 멤버이다.

스테틱 키워드가 붙으면 정적멤버가 된다.
정적 멤버는 프로세스 주소 공간 중 스택이나 힙이 아닌 데이터 영역을 사용

비정적 멤버 함수에는 const 수식이 가능하면 const로 수식된 비정적 멤버 함수에서는 데이터 수정이 불가능하다.

public 지정자를 쓰면 기본접근 지정자가 public 임
struct 지정자를 쓰면 기본접근 지정자가 private 임

접근 지정자

public : 클래스 외부에서 접근 가능

private : 클래스 외부에서 접근 불가능

protected : 클래스 내부 및 자식 클래스에게만 접근 권한 부여

기본 합성 메소드

비정적 멤버 함수 중 컴파일러가 자동으로 만들어주는 메소드가 있다.
기본 메소드 : 기본 생성자, 복사 생성자, 이동 생성자, 소멸자, 복사 할당 연산자, 이동 할당 연산자 가 있다.

생성자 : 초기화를 담당하는 특수한 메소드

  • 기본 생성자는 아무런 생성자도 정의하지 않았을 때 컴파일러가 자동으로 합성한다.
  • 기본 생성자 : 매개변수가 없는 메소드
  • 초기화 이외에도 인스터스를 만들면서 해야할 일들을 적는다.

복사 생성자

  • 첫 번째 매개변수로 동일한 타입의 레퍼런스를 가지는 생성자
  • 복사 생성자도 정의하지 않으면 기본으로 합성된다.

소멸자 : 자원 정리를 담당하는 특수한 메소드

  • 소멸자를 정의하지 않았을 때 컴파일러가 자동으로 합성한다.
  • 타입 이름앞에 ~(틸드)를 붙인다.

explicit 한정자

번역하면 명시적 이라는 의미가 있다
암시적 변환을 막아준다.
매개변수가 하나인 생성자를 만들 때 주의해야 한다.
암시적 변환이 일어나기 때문이다.
암시적 변환이 일어나 사용하기 편할 순 있으나 때에따라 실수로 이어질 수 있다.

복사 할당 연산자

하나의 매개변수만 가지며 그 매개변수의 타입이 클래스 타입과 동일한 할당 연산자를 복사 할당 연산자 라고 한다.

기본 메소드에 대해선 default와 delete 키워드를 이용해 컴파일러가 제공하는 기본 함수를 사용하거나 삭제할 수 있다.

동적할당을 할때엔 기본메소드를 전부 다 써줘야한다.

상속

C++ 에서 상속을 하려면 클래스를 정의할 때 뒤에 콜론을 붙이고 상속할 클래스를 접으면 된다.
이 때 접근 지정자를 적어줄 수 있다.
public 을 붙여야 한다 안붙이면 private이 생략되 붙는것 이다.
public 상속 : 부모 클래스의 public 멤버를 자식 클래스의 public 멤버로 가져오고 부모 클래스의 protected 멤버를 자식클래스의 protected 멤버로 가져옴

struct은 기본 접근 지정자가 public이기 때문에 생략 해 주어도 된다.

상속 관계가 있는 객체는 생성은 부모부터 소멸은 자식부터 이뤄진다.

다중 상속을 받을 땐 , 로 구분지어 받는다.

다중 상속이 가능하지만 죽음의 다이아몬드를 겪지 않도록 조심해서 다뤄야 함

가상함수

젤중요

다형성을 지원하기 위한 기능
가상함수를 작성하려면 메소드 앞에 virtual 한정자를 적음댐

가상함수의 내용을 재정의하는 것을 오버라이딩 이라 한다.
매개변수 목록이 달라지면 안댐 부모 클래스에 선언 한 대로 적어줘야 함

상속관계를 가지고 있으면 변환이 가능함

가상함수를 이용해 다형성을 사용하려면 업캐스팅 해서 부모 타입의 포인터 혹은 레퍼런스로 하위 타입의 객체를 다뤄야 함.

업캐스팅 때문에 실제로 내가 어떤 타입의 객체를 가리키는지 모름. 그래서 다형성이 적용되는 것.

일반적으로 호출하는 함수 : 정적 바인딩

실행시간중에 어떤 함수를 호출할지 결정하는 가상함수 : 동적 바인딩
가상함수는 동적 바인딩이며, 가상함수 포인터를 이용해 동작 가상함수 포인터는 가상함수 테이블을 가지고 있다.

가상함수 포인터

가상함수 테이블을 가르키는 가상함수 포인터
가상함수를 정의하면 자동추가
가상함수가 붙어야만 가상함수 포인터가 생긴다

가상함수 테이블

가상 함수의 주소가 저장
각 타입마다 존재

가상함수는 어떻게 동작하는건가

일반적으로 쓰는 함수는 컴파일 시간에 어떤 함수를 호출할 것인지 결정하는 '정적 바인딩' 인 반면에 가상 함수는 실행 시간에 어떤 함수를 호출할 것인지 결정하는 '동적 바인딩' 으로 동작한다.
동적 바인딩을 위해 가상 함수의 주소가 저장되어 있는 '가상 함수 테이블'이 각 타입마다 존재하게 되며 각 인스턴스마다 이를 자기 타입의 가상 함수 테이블을 가리키는 '가상 함수 포인터'를 갖고 있게 된다.

내가 이해한 것은 함수가 가상 함수로 선언되면 포인터 변수가 실제로 가리키는 객체에 따라 호출의 대상이 달라지는 것 이 '동적 바인딩' 으로 이해했다.


위 사진은 가상함수를 정의하고 자동추가된 가상함수 포인터에서 가상함수 테이블이 어떻게 바뀌는지 알아보는 예제이다.

virtual 이 붙은 것들이 가상함수 이다

부모클래스에서 Foo( ) 와 Boo( ) 가 가상함수이고 Coo( ) 는 가상함수가 아니다. 그래서 가상함수 테이블에는 Foo( ) 와 Boo( ) 가 있다.

Base 타입의 가상 함수 테이블
vftable[0] = Base::Foo
vftable[1] = Base::Boo

Base 클래스를 상속받은 Derived 클래스에서 가상함수는 Aoo( )밖에 없지만 중복된 함수이름이 있다. Foo( ) 함수이다.
함수 이름이 중

그리고 새로운 함수도 보이는데 Aoo( ) 가상함수이다.

Derived 타입의 가상 함수 테이블
vftable[0] = Derived::Foo
vftable[1] = Base::Boo
vftable[2] = Derived::Aoo

Boo( )함수는 Derived 클래스에 없기에 부모클래스의 가상 함수 테이블을 이어받고 Aoo( )가상함수를 다음 테이블위치에 들어간다.


Base 클래스에 이름을 붙여주고 Base* p 에 주소값b 를 넣고 출력을 해 보면

p->Foo( ); // Base Foo
p->Boo( ); // Base Boo
p->Coo( ); // Base Coo

가 나온다.
위에서 말했다 싶이 Base 타입의 가상 함수 테이블에는 Base::Foo, Base::Boo 만이 있다.

Base클래스를 Derived클래스에 상속시키고 Foo( ), Boo( ), Coo( )를 출력하면

p->Foo( ); // Derived Foo
p->Boo( ); // Base Boo
p->Coo( ); // Base Coo

가 나온다.

Foo( )는 가상 함수라 Derived타입 가상 함수 테이블에 덮어씌워지고 가상함수가 아닌 Coo( ) 는 그대로 부모클래스의 출력값이 나온다.

p = &d 를 해줬지만 결국 p가 가리키고 있는건 Base* 에 할당된 &b 이므로 Base Coo 가 출력되는 것 이다.

Boo( )는 Derived 클래스에 있지 않기 때문에 Base 의 값을 가져오게 된다.

업캐스팅 : 자식클래스 타입에서 부모 클래스 타입으로 변환하는 것 암시적으로 일어남
다운캐스팅 : 명시적으로 해야됨

다운캐스팅

위 코드에선 Derived클래스 에 있는 Coo( )를 불러오지 못한다.
불러올 방법이 있는데 그것이 다운캐스팅 이다.

((Derived*)p)->Coo( ); 를 하면 부모 클래스 타입 Base 에서 자식 클래스 타입 Derived 으로 다운캐스팅이 된다.
Base 클래스의 Coo( ) 에서 Derived 클래스 Coo( )로 바뀐다.

추상 클래스

인스턴스를 만들 수 없는 클래스로 상위타입(부모 클래스)를 정의하는데 사용한다.


추상 클래스를 만드는 방법
아래와 같이 A a; 를 해주면 컴파일 오류가 뜬다.


순수 가상 함수를 가지고 있는 클래스는 추상 클래스가 된다.


순수 가상 함수는 하위타입으로 하여금 오버라이딩을 강제한다.

가상 소멸자

객체지향에선 업캐스팅 하여 객체를 다루게되는데 이 경우 객체의 소멸이 제대로 안되는 것을 확인할 수 있다.
이때 올바르게 소멸자를 호출하고 싶다면 가상소멸자를 정의해야 한다

C++ 추가내용

함수

함수를 선언할 때 기본인자를 전달할 수 있다.

기본인자는 가장 오른쪽부터 전달할 수 있다.

일반화 프로그래밍

타입에 관계없이 알고리즘을 기술하는 프로그래밍 패러다임이다.
C++에서는 템플릿 이라는 기능으로 하고 C#에서는 제네릭 이라는 타입이 있다.

바뀌는건 타입밖에 없다.

템플릿 문법

템플릿은 기본적으로 클래스 템플릿과 함수 템플릿으로 구분된다.

템플릿은 하나 이상의 템플릿 파라미터와 함께 매개변수화 된다.
템플릿 파라미터에는 비 타입 파라미터, 타입 파라미터, 템플릿 파라미터가 있다.
타입도 마치 인자처럼 전달할 수 있다는 것이다.

특수화

특수화는 컴파일 타임에 일어나며, 컴파일러가 템플릿을 기반으로 특정 타입을 생성한다.

일반적으로 템플릿은 헤더 파일에 모든 내용을 적으며, 정의 소스 파일에 나눠서 적으면 링크 오류가 발생한다.


위와 같을 때 a는 T의 자리에 int 를 넣어 사용하고 b는 T의 자리에 float를 넣어 사용한다. 복사하여 사용한다고 보면 된다.

모든 템플릿 파라미터에 대해 특수화를 하는것을 명시적 특수화
일부만 하는 것을 부분 특수화라고 한다.
아래 사진을 보자

정의와 선언 구분 이유

A.h와
temp.cpp가 있을 때

temp.cpp -> A.h

temp.cpp가 A헤더를 포함하고 있을 때 A헤더의 내용이 변경되면 A헤더를 포함한다는 이유만으로 temp.cpp도 다시 컴파일한다.

profile
해보자요

0개의 댓글