C++은 C언어에 여러 가지 기능을 추가하거나 개선하여 만들어진 C의 상위 버전
C/C++ 차이점
C | C++ | |
개발 방법 | 구조적 프로그래밍 | 객체 지향 프로그래밍 |
함수에서 변수 선언 | 함수의 선두에 선언 되어야 함 |
언제든지 중간에 변수 추가 가능 |
l C++에서 추가된 기능
내용 | 설명 |
범위 연산자 | 지역변수에 의해 가려진 전역변수 참조 |
명시적 캐스팅 | (int)var가 아닌 int(var) 형식으로 캐스팅 |
인라인 함수 | 본체가 호출부에 삽입되는 함수 |
디폴트 인수 | 실인수가 생략될 때 형식 인수에 적용되는 기본값 |
함수 오버로딩 | 같은 이름의 함수를 여러 개 정의 |
태그가 타입으로 승격됨 | 구조체 태그로부터 변수를 바로 선언 가능 |
이름없는 공용체 | 공용체 이름없이 멤버들이 기억 장소를 공유 |
한줄 주석 | // 로 줄 끝까지 주석 |
레퍼런스 | 변수에 대한 별명을 붙인다. |
bool 타입 | 1바이트의 진위형 타입 |
네임 스페이스 | 명칭을 저장하는 기억 영역 |
IOStream |
입출력을 담당하는 객체 cin은 입력 객체, cout은 출력 객체 |
메모리 할당 연산자 new |
new, delte는 malloc과 free에 대응되는 할당 연산자임 |
바뀐 기능
내용 | 설명 |
헤더 파일 | 확장자 .h를 붙이지 않아도 됨 |
C 언어
malloc 에서 void*형은 뭘까?
일반 변수에 사용될 수 없는 이유 |
void형은 타입이 없기 때문에 얼마만큼의 메모리를 잡아야 하는지 (크기를) 알 수 없기 때문 그렇기 때문에 포인터 연산이 불가능 |
왜 포인터로 사용 가능한가 |
주소 값은 타입에 관계없이 4byte로 일정하다. 그렇기 때문에 타입 정보가 필요 없고, 주소를 받을 수 있는 void*를 사용 |
기능 |
타입이 없기 때문에 어떤 값이든 받을 수 있다. 모든 타입에 대해 일괄적으로 사용 가능 |
활용 |
void * malloc(size_t size) malloc은 주어진 크기 만큼 메모리를 할당하고 주소 값을 반환한다. 그렇다면 반환되는 주소 값을 어디에 담아야 하는가? void로 해버리면 주소 값을 받을 수 없고, 어떤 타입에 담을 수도 없다. 그렇기 때문에 타입 정보가 필요 없고, 주소를 받을 수 있는 void*를 사용 주소를 받은 이후에 캐스팅을 해서 원하는 타입에 넣어줄 수 있다. |
C++
제네릭 포인터 ( Generic Pointer, void poitner )
모든 데이터 자료형을 가리킬 수 있는 특별한 타입의 포인터
직접 역참조는 할 수 없기 때문에 명시적 형 변환을 하여 특정 유형의 포인터에 담을 수 있다.
void* ptr;
double *d_ptr;
int a;
float b;
double c;
ptr = &a;
ptr = &b;
ptr = &c;
d_ptr = static_cast<double*>(ptr);
구조체
멤버함수
내부 정의 | 외부 정의 | |
코드 |
struct Position { void OutPosition() { ... } }; |
struct Position { void OutPosition(); }; void Position::OutPosition() { ... } |
차이 |
인라인 속성을 가진다. inline 키워드 붙이든 안 붙이든 무조건 자동으로 인라인 함수 |
일반적인 함수 호출을 한다. (스텍을 통한 제어 분기)
inline 키워드 붙이면 인라인 함수로 만들 수 있긴 하다. |
언제 사용하는가 |
인라인 함수 호출 부담이 없어 속도가 빠르다. 여러 번 호출할 경우 실행 파일의 크기를 증가 시킨다.
따라서 코드가 아주 짧을 때 사용 (값 읽기, 단순 연산)
보통 3줄 이하의 경우 |
그렇지 않은 경우 |
보기 |
전체 모양을 한눈에 파악 가능 코드 보기에 좋다. |
함수가 외부에 있어서 가독성 떨어진다. |
비트 필드 구조체
비트필드의 값을 한꺼번에 묶어서 사용
#include <stdio.h>
struct Flags {
union { // 익명 공용체
struct { // 익명 구조체
unsigned short a : 3; // a는 3비트 크기
unsigned short b : 2; // b는 2비트 크기
unsigned short c : 7; // c는 7비트 크기
unsigned short d : 4; // d는 4비트 크기
}; // 합계 16비트
unsigned short e; // 2바이트(16비트)
};
};
int main()
{
struct Flags f1 = { 0, }; // 모든 멤버를 0으로 초기화
f1.a = 4; // 4: 0000 0100
f1.b = 2; // 2: 0000 0010
f1.c = 80; // 80: 0101 0000
f1.d = 15; // 15: 0000 1111
printf("%u\n", f1.e); // 64020: 1111 1010000 10 100
return 0;
}
결과 : 64020
printf("%u\n", f1.e);
input : 64020
output : 1111 1010000 10 100
이는 1111 1010000 10 100 을 10진수로 표현한 것
구조체 vs 클래스
구조체 | 클래스 | |
기본 접근 제한자(지정자) | public | private |
= 연산자로 생성할 때 | 값 복사 | 참조 복사 |
멤버 변수 접근 | -> | 점(dot) |
차이점 |
- 기본 접근 제한자(지정자) = 보안 + Java 는 구조체가 없다.
+ C# 에서는 구조체는 값 형식이라서 인스턴스가 스텍에 할당되고 클래스는 참조 형식이라서 인스턴스가 힙에 할당된다. 그 외에 상속 미지원, 필드 초기화 미지원, 소멸자 미지원 등
+ C++ 에서는 클래스/구조체로 결정되는 것이 아니라 일반 선언 시 스텍에 할당, new로 동적 할당 시 힙에 할당 됨 |
|
공통점 |
- 접근 제한자, 상속, 다중상속, 함수, 가상함수, 오버라이딩, this 포인터 등...
Q. 멤버함수가 크기에 영향을 주나요? 크기에 영향을 미치지 않는다. 하지만 가상 함수가 들어가면 다름
- 생성자, 소멸자
|
|
언제 어떤 것을 사용해야 하는가 |
객체를 다룰 때 |
생성자와 소멸자가 없는 데이터 타입 메소드가 아닌 멤버 변수 위주로 사용
C언어 쓸 때 |
일반 선언 |
new로 동적 할당 |
|
코드 |
int main() { struct ST s; s.a = 1; CL c; c.a = 1; } |
int main() { struct st* s = new ST(); CL* c = new CL(); } |
차이 | 메모리를 스텍 영역에 잡는다. | 메모리를 힙 영역에 잡는다. |
메모리 할당 연산자 new
malloc/free | new/delete | |
라이브러리가 제공하는 함수 |
언어가 제공하는 연산자 별도의 헤더 파일 필요 없음 연산자 오버로딩 가능 |
|
메모리 지정 |
필요 메모리를 바이트 단위로 지정하고 void* 를 리턴하므로 sizeof 연산자, 캐스트 연산자 도움 필요 |
할당할 타입을 지정하고 해당 타입의 포인터를 리턴 sizeof 연산자, 캐스트 연산자 필요 없음 |
할당/초기화 |
메모리 할당만 가능 |
메모리 할당과 동시에 초기화 가능 |
생성자 | 없음 |
객체 할당할 때 생성자가 자동 호출 객체 삭제할 때 소멸자 자동 호출 |
할당 타입 |
기본 타입, 구조체, 배열, 객체 할당 가능 |
|
재할당 | 할당한 메모리를 realloc으로 크기를 바꿔서 재할당 가능 |
재할당 하려면 새로 할당하여 복사하고 원래 메모리 해제 |
구조체/클래스 복사
memcpy | opertator = | |
방식 | 구조체 크기, 메모리 복사 | 멤버 변수를 =연산을 통해 복사 |
속도 | 빠름 | 느림 |
주의 |
자료형 크기를 정확하게 대입해야 함 MFC 클래스 있는 경우 오작동 가능 |
복사 연산자 = 이 정의되지 않은 멤버 변수가 있는 경우 |
대입연산자
정의하지 않으면 디폴드 대입 연산자가 삽입된다. 얕은 복사를 진행
내용 | |
클래스 |
단순 대입 연산이 아니라 대입 연산자를 오버로딩 한 함수의 호출임 얕은 복사를 진행 |
구조체 |
값 복사를 한다. |
상속 구조 |
자식 클래스의 대입 연산자에 아무런 명시를 하지 않으면, 부모 클래스의 대입 연산자 호출되지 않는다. |
배열 |
복사 할 수 없다. |
복사 생성자
객체의 복사본을 생성할 때 호출됨, 컴파일러가 알아서 만들어 준다.
클래스 내부에서 포인트 변수를 가지면서 동적 할당, 해제를하는 경우에는 복사 생성자를 정의해주어야 함
클래스이름(const 클래스이름 &rhs);
객체의 복사 | 설명 |
얕은 복사 (shallow copy) |
기본 생성자를 통해 멤버 변수들의 값을 복사, 참조형 멤버는( 포인터 )는 단순히 참조
T2 = T1 후에 T1이 동적할당한 메모리를 해제하고 T2가 해제하려고 하면 문제 생긴다. 이를 위해 깊은 복사가 존재 |
깊은 복사 (deep copy) |
복사 생성자를 통해 참조된 객체 까지도 복사 |
#define / enum / const / inline
용도 | 내용 |
옵션이나 flag 지정 |
#define (가능하다면 쓰지 않는 것이 좋다.) |
간단한 함수를 만들 때 |
#define macro 보다는 extern inline 함수를 사용하면 오버해드 줄일 수 있고 간결해짐 |
상수 용도로 사용할 때 | const / enum 사용 |
enum |
enum class |
|
언어 | C언어에서 사용하는 방식 | C++에서 사용하는 방식 |
이름 |
이름을 명시하지 않아도 접근 가능(전역) 그래서 C++에서 namespace로 묶어줘야 한다.
파라미터로 받으려면 이름 명시해줘야 한다. |
반드시 이름을 명시해야 한다. |
배열의 크기를 지정하는 상수로 쓸 때 |
캐스팅 필요 없이 사용가능 |
static_cast<int>(~) 가 필요함. |
가상 함수( Virtual Function )
상속 관계에서 오버라이딩할 것으로 기대하는 함수 ( 다형성을 위한 것 )
class A
{
public:
void print()
{
printf("A");
}
};
class B : public A
{
public:
void print()
{
printf("B");
}
};
int main()
{
A* b = new B;
b->print();
return 0;
}
virtual void print() : 자식 클래스 B의 print를 B
void print() : 부모 클래스 A의 print 호출 A
가상 함수( Virtual Function ) |
상속 관계에서 오버라이딩할 것으로 기대하는 함수 다형성을 위한 것
virtual void print() { ... } |
순수 가상 함수( Pure Virtual Function ) |
구현이 없는 가상 함수 순수 가상 함수가 하나라도 포함하는 클래스는 추상 클래스로 지정된다.
virtual void print() = 0 |
추상 클래스( Abstract Class ) |
순수 가상 함수를 포함하고 있는 클래스 추상적인 형태만 보여주고 구현은 자식 클래스로 미룬다. 모든 멤버 함수가 순수 가상 함수면 인터페이스라고 부른다.
인스턴스 생성 불가 추상 클래스를 상속하는 자식 클래스에서 모든 순수 멤버 가상 함수를 오버라이드 해야 인스턴스를 생성 가능 |
인터페이스( Interface ) |
파생 클래스에 공통적인 기능을 추가하기 위해 구현해야하는 순수 가상 함수로만 구성된 클래스 추상 클래스로 구현 |
다형성
오버 라이딩( Overriding ) |
부모 클래스의 함수를 자식 클래스에서 재정의하는 것 |
오버 로딩( Overloading ) |
같은 이름의 함수를 여러 개 정의하는 것 매개 변수의 유형, 개수를 다르게 하여 다양한 호출에 응답
|
오버 로딩 안되는 경우 |
반환 값만 다른 경우 int print() { return 1; }; char print() { return 'a'; };
매개변수 형식이 배열이나 포인터 int print(int *p); int print((int p[]); 동일 취급
int print(int p); int print(int *p); 동일 취급
int print(int p); int print(const int p) 동일 취급
모호한 일치 void print(unsigned int value); void print(float value); print(0); 를 호출한 경우에 둘 다 호출 가능하므로 모호한 일치가 발생한다. 이는 컴파일 타임 오류이기 때문에 타입을 명시적으로 밝혀주는 방식으로 해결 |
바인딩 ( Binding )
각종 내부 요소, 이름, 식별자들에 대해 값 또는 속성을 확정하는 과정
바인딩이 일어나는 시간에 따라 정적 / 동적 바인딩으로 분류할 수 있다.
개념 | 설명 |
정적 바인딩 ( Static Binding ) |
컴파일 시점에 바인딩이 일어나는 것 장점 속도가 빠르고 타입 에러를 조기에 발견할 수 있다. 단점 컴파일 이후 변경 불가
int main() { int a = 5; // a는 int형이라고 결정됨 } |
동적 바인딩 ( Dynamic Binding )
|
런타임 시점에 바인딩이 일어나는 것 장점 런타임에 변경할 수 있어 유연하다. 단점 타입 체킹으로 속도 저하나 안정성 문제 발생 가능
class A { virtual void print() { } } class B { void print() { } } int main() { A* a = new A(); B* b = new B(); a->print(); // A의 print() 호출 a = b; a->print(); // B의 print() 호출 } |
C++11의 auto는 정적 바인딩( 타이핑 )인가 동적 타이핑인가 ?
( auto는 static binding or dynamic binding? )
auto로 변수를 선언하면 우변의 타입을 자동으로 추론해준다.
변수의 타입은 처음 선언할 때 컴파일러에 의해 고정되어 다른 타입으로 사용할 수 없다.
그렇기 때문에 정적 바인딩( 타이핑 ) 이다.
auto x = 5;
x = 1.5;
cout << x;
결과는 1.5가 아닌 1이다. 이유는 int로 고정되었기 때문이다.
Static
l 정적 멤버 변수 ( Static Member Variable )
◾ 전역이든 지역이든 초기값을 지정하지 않으면, 자동으로 0으로 초기화 된다.
#include <stdio.h>
static int num1; // 0 으로 초기화 된다.
int main()
{
static int num2; // 0 으로 초기화 된다.
return 0;
}
◾ 클래스 내부에서 초기화 불가능, 클래스 외부에서 초기화시켜야 한다.
void abc1
{
static int cnt1 = 0; // ⭕ 가능
}
class abc2
{
public:
static int cnt2 = 0; // ❌ 올바르지 않은 경우
}
class abc3
{
public:
static int cnt3;
}
int abc3::cnt3 = 0; // ⭕ 올바른 경우
◾ 파일 범위를 벗어난 외부에서 접근할 수 없다.
extern static int n;
이러한 문법은 잘못된 문법이다.
l 정적 멤버 함수
일반 멤버 함수 안에서는 정적, 비정적 변수 모두 사용 가능
정적 멤버 함수 안에서는 정적 변수만 사용 가능
class abc
{
public:
int n;
static int cnt;
void test()
{
n = n + cnt;
}
static void get_cnt()
{
return cnt;
}
}
생성된 객체가 하나도 없더라도 클래스의 이름만으로 호출할 수 있다.
test::print()
class test
{
private:
static int n;
public:
test() { n++ };
~test() { n-- };
static void init() { n = 0 }
static void print() { printf("%d", n); }
}
void main()
{
abc a;
abc *b;
a.print(); // 0 출력
b = new b;
b->print(); // 1 출력
delete b;
b->print(); // 2 출력
}
객체를 통해서 접근을 해도 결국 test::print()로 컴파일 된다.
그래서 마지막의 이 부분이 동작하는 것
delete b;
b->print(); // 2 출력
'C++' 카테고리의 다른 글
객체지향 프로그래밍 (OOP, Object-Oriented Programming) (0) | 2019.10.14 |
---|