[C++] explicit에 대한 이야기

explicit에 대한 이야기


Explicit란 단어의 사전적 의미는 '분명한, 명쾌한' 입니다.

단어만 봐도 느낌이 옵니다. 개발자의 의도와는 다르게 사용될 여지가 있거나, 그러면 안되는 곳에 쓰일 것 같아요.


간단한 예제입니다.



int형을 클래스 A에 대입하는 이 때 암시적 타입 변환이 이루어 집니다. 만약 생성자를 explicit으로 선언한다면


 

이렇게 명시적으로 변환 하는것만 가능하도록 합니다.


Effective C++ 책에는


필자의 경우, 암시적 타입 변환에 생성자가 사용될 여지를 남겨둘 뚜렷한 이유가 없는 한, 생성자는 explicit 선언을 우선적으로 합니다. 여러분도 주저 마시고 이런 식으로 해 보시기를 적극 추천하는 바입니다.

라고 쓰여있다.


앞으로 생성자를 만들땐 explicit을 붙이는 것을 습관화 해야겠습니다.


[C++] 스택(Stack), 선형큐(Queue) 구현하기

스택(Stack)




선형큐(Queue)



[C++] 선행처리자에 대한 이야기( 매크로 함수 #define, const 상수, 인라인 함수 inline)

컴파일하는 과정


프로그래머가 작성한 소스파일은 가장 먼저 소스파일에 대한 처리를 하는 선행처리기를 거치고, 컴파일 되어 오브젝트 파일이 생성되고, 다시 링커에 의해 실행파일이 된다.



선행처리를 위한 명령에는#define,, #undef, #include, #if, #ifdef, #elif, #else 등

맨 앞에 #기호가 붙는다는 공통점이 있다.


함수 외부에 기술하며, 다른 소스파일들과는 독립적으로 수행된다. 억지로 취소하지 않는 한 그 효과는 지정한 곳부터 끝까지이다. 선행처리에 합당한 적업을 통해 원시코드의 내용을 변경한다.


#include의 경우에는 이 이후에 나오는 파일을 찾아 그 파일에 기술된 모든 내용을 현재 파일의 #include가 기술된 부분에 넣어준다.



매크로 상수 #define 


상수만 기술하며, 프로그램의 명확성을 증대시켜 주는 효과가 있다.


Ex >  #define PI 3.141592 


+ 단점(2018.01.04)


1. 선행 처리자가 상수로 바꾸어 버리기 때문에, 시호 테이블에 들어가지 않음.

따라서 에러메시지엔 3.141592 값인 숫자만 표기되고, 어디서 에러가 발생했는지 모르므로 혼란을 야기함. 


2. 코드 크기가 증가한다.

선행처리자가 상수 값으로 치환하기 때문에 코드 크기가 커진다.


const 상수


#define문 보다 더 쉽게, 안전하게 상수를 만드는 방법!


변수 선언을 초기화와 함께하고 자료형 앞에 const 키워드를 덧붙여서 상수를 정의할 수 있다. 키워드 const를 덧붙인다는 것과 초기값을 반드시 주어야 한다는 것에 주의 해야한다. const 상수는 변수 선언시 준 초기값이 영원히 그 변수의 값이 된다.



인라인 함수 Inline 함수


주로 짧은 명령어로 구성하고! (길면 비효율적) => 실행 속도가 증가합니다! Inline 함수는 매크로 함수와 유사한 기능을 가진 함수이다. 인라인 함수를 호출하면 컴파일할 때 호출하는 부분에 인라인 함수를 대치한다.


사용 방법

inline  자료형 함수이름 (매개변수 리스트)

{
}


함수 머리 부분의 자료형 앞에 inline이라는 예약어만 추가적으로 기술하면 된다.


속도 증가의 이유는?


일반 함수의 경우 함수의 호출이 일어나면 그 함수를 찾아가서 수행을 하고 다시 되돌아와야 한다. 정의된 함수에 프로그램 로직이 들어 있기 때문.

매크로 함수의 경우 치환될 문장이 함수 부분에 대치되므로 코드 양이 많아지기는 하지만 함수를 찾아 가서 수행하고 돌아오는 번거로움이 없다.




[C++] 포인터, 값에 의한 전달, 주소에 의한 전달, 참조에 의한 전달에 대한 이야기


메모리에는 위치를 구분하기 위해 0번지부터 시작해서 일련번호가 붙여져 있는데 이것을 주소(어드레스)라고 한다.

주소는 정수 형태이며 단위는 바이트이다. 컴퓨터가 데이터를 처리하려면 먼저 데이터를 메모리(램)으로 옮겨야한다.


포인터란?


컴퓨터의 메모리 번지(address)로, 데이터가 어디에 저장되어 있는지를 알려준다. C++에서는 이 포인터(변수의 주소)를 직접 사용할 수 있도록 포인터 연산자(&)를 제공한다.


& - 주소

* - 값



포인터 변수?


주소만을 저장한다. 포인터 변수는 선언할 때 반드시 기호를 덧붙여야 한다. 포인터 변수를 단독적으로 p라고 사용하는 것은 포인터값(주소)를 의미하지만, 포인터 변수에 *를 덧붙인 *p는 더 이상 주소가 아니라 해당 주소에 저장된 값을 의미한다.

그래서 p에는 변수 a의 주소가 저장되어 있으므로 *p는 변수 a의 값을 출력한다.


대입 연산자로 값을 대입할 경우 대입 연산자 오른쪽과 왼쪽에 오는 자료형이 같아야 한다.



매개변수의 전달?


함수에 사용될 데이터를 보내는 방법이다.



전달하는 방법 3가지


1. 에 의한 전달 방식( Call by Value)

2. 주소에 의한 전달 방식( Call by Address)

3. 참조에 의한 전달 방식( Call by Reference)



1. 값에 의한 전달 방식( Call by Value)


호출 측의 실 매개변수는 함수 측으로 값만 전달한다.
함수 측에서는 형식 매개변수가 실 매개변수와 별도로 기억공간을 할당받고 그 곳에 실 매개변수에 저장되었던 값만 전달받아 복사
이 방식은 별개의 기억공간이 사용되므로 실 매개변수의 값은 변경되지 않아요.
바꾸고 싶다면 return값을 이용하세요.


2. 주소에 의한 전달 방식( Call by Address)


실 매개변수로 변수의 주소값을 넘겨주고, 형식 매개변수가 이를 포인터 변수에 저장하도록 한다.



* 3. 참조에 의한 전달 방식( Call by Reference)

C++에 추가된 것 중에 하나가 레퍼런스(참조)변수이다. 레퍼런스 변수란 별명(일종의 다른이름)을 의미한다. 레퍼런스 변수는 따로 기억공간이 할당되지 않는다. 메모리 상에 오로지 하나만 존재하고, 여러 이름으로 접근해서 사용할 수 있도록 한다.

자료형 & 별명 = 변수_이름;

<< 이렇게 선언하는 것 말고는 일반 변수처럼 사용하면된다. *,&이런거 생각할 필요가 없어요!  >>

이때 사용된 &기호는 참조 연산자라고 한다.
따라서 변수 선언시 & 기호가 사용된 경우 - 참조 연산자
선언이 끝난 변수에 사용되는 & 기호가 사용된 경우 - 주소 연산자

*레퍼런스 변수를 선언할 때 주의할 점은 반드시 변수 선언시 초기값을 주어야 한다는 것이다.


[C++] 기억클래스에 대한 이야기(자동변수 auto, 외부변수 extern , 정적변수 static, 레지스터 변수 register)

블록내 선언 - 지역변수

블록 외부에 선언 - 전역변수


변수 선언이 의미하는 것은? 메모리에 기억공간, 영역을 확보하기 위해서이다.



변수를 어떻게 선언하느냐에 따라서 유효 범위와 생존기간 등 변수의 성격이 달라진다.

변수의 기억공간을 얼마만큼 확보할 것인지는 자료형에 의해서 결정된다.



유효 범위와 생존 기간을 결정하는것은 기억 클래스이다.


 기억 클래스

유효범위 (scope) 

생존 기간

메모리

초기화 여부 

auto(지역 변수) 

블록 내

일시적 

stack 

쓰레기 값 

extern(외부 변수) 

프로그램 내 

영구적 

메모리

숫자 0 

static(정적 변수)

내부 : 블록 내

외부 : 모듈 내 

영구적 

메모리 

숫자 0 

 register(레지스터)

블록 내 

일시적 

CPU 내의 레지스터 

쓰레기 값 


일반적으로 변수는 기억클래스가 생략된다.

[기억클래스] 자료형 변수명;

변수 선언은 자료형과 기억클래스에 의해 다음 2 가지로 구분가능하다.

 자료형에 의한 구분

int, char, float 등

생략 불가 

저장되는 값의 형태와 

기억공간의 크기를 결정 

기억클래스에 의한 구분 

auto, extern, static 

생략시 auto로 인식

유효범위와 생존 기간을 결정 



자동 변수 auto


 프로그램이 실행되는 동안 생성과 소멸을 반복한다. 선언된 문장을 만나면 메모리 할당이 일어나므로 변수 선언문 이후에서만 사용가능하고, 변수 선언이 된 블록 밖으로 벗어나면 메모리가 해제되어 변수를 사용할 수 없다.



자동변수와 스택


 컴파일러는 자동변수가 선언되면 스택에 메모리 할당을 한다.

 스택은 LIFO구조로 메모리의 일부를 잡아 새로 생성되는 변수들을 차곡차곡 쌓아두었다가 맨 위 (가장 최근에 선언된)에 있는 변수가 사용된다. 제거 할 때에도 맨 위에 있는 것부터 제거한다.


외부 변수 


블록(함수)안에 선언된 변수를 지역변수라 하며 변수를 사용할 수 있는  범위는 그 변수가 선언된 블록 내부로 한정된다. 자동변수는 대표적인 지역변수이다. 변수 선언 함수 외부에서 하는 경우가 있다. 이러한 변수들을 전역변수라고 한다. 외부변수는 대표적인 전역변수로 프로그램 내의 모든 함수에서 사용할 수 있는 변수이다. 외부변수는 프로그램이 실행되는 동안 메모리 상에 항상 배치되므로 값이 유지된다.



지역 변수와 전역 변수


main 함수 내에 선언한 지역변수들은 main 함수 내에서만 사용가능하기 때문에 사용자 정의 함수에서는 사용할 수 없다.

그래서 함수에 매개변수가 있는 것이다. 또한 main 함수에 선언된 변수값을 사용자 정의 함수에서 변경하기 위해서는 단순히 인수만을 사용하는 것만으로는 불가능하다.

포인터나 레퍼런스 변수를 사용하여 Call By Address나 Call By Reference 기법을 사용해야만 가능하다. 이러한 여러 가지 방법을 사용해야 원하는 결과를 얻는 이유는 변수를 지역변수로 선언하였기 때문이다.


전역변수는 인수를 사용하지 않고도 모든 함수에서 사용가능하므로 매우 호감이 간다. 하지만 이러한 전역변수는 쉽고 간단한 접근을 허용하는 대신 프로그램의 신뢰성에 큰 위협을 가한다. 변수값이 어디서나 변경 가능하다는 것은 그 만큼 위험 부담을 안고 있으므로 값비싼 대가를 치러야 할지도 모른다. 좋은 프로그램을 작성하기 위해서는 데이터의 무결성이 잘 보존되도록 데이터를 필요한 경우에만 접근할 수 있도록 격리시켜야 한다. 전역변수도 나름대로 필요하겠지만 편리성을 이유로 부분별하게 사용해서는 안된다.



auto 지역변수 VS extern 전역변수


지역변수가 우선이고, 없으면 전역변수로 !




정적 변수


변수를 선언할 때 변수 이름 앞에 static을 붙이면 정적변수로 선언된다.

지역변수로서의 정적변수와
전역변수로서의 정적변수가 있다.
이에 대한 구분은 변수선언을 어디에 했는지 위치에 의해 결정된다. 블록 내부에 선언되면 지역변수로서의 정적변수이고, 블록 외부에서 선언하면 전역변수로서의 정적변수가 된다.


지역변수로서의 정적 변수는 함수 내부에 선언하고, 그 함수 내에서만 사용가능하다.



자동 변수 vs 정적 변수


자동변수는 생성과 소멸을 반복하므로 스택과 같은 특별한 메모리를 사용하지만 정적변수는 프로그램이 시작하는 시점에서 메모리 영역에 고정적으로 할당된다. 정적변수는 함수의 호출과 상관없이 프로그램이 시작되는 순간 변수가 생성되고, 초기화도 이 때 한번만 한다. 정적변수가 선언된 함수의 실행이 종료되어도 소멸되지 않고 메모리에 그 값이 기억되어 있다.



전역변수로서의 정적변수


전역변수는 함수 외부에서 선언하는 외부변수이다. 프로젝트로 묶여있는 모든 파일 내에서 공유해서 사용할 수 있는 변수가 외부변수이다. 하나의 파일에서는 아무런 조건 없이 외부변수를 가져다 사용할 수 있지만, 다른 파일에서 그 변수를 공유하여 사용하려면 참조 선언을 하여야 한다.



하나의 파일은 static

프로젝트로 여러개의 파일에서 쓸려면 extern



extern 자료형 변수명; 초기값은 줄 수 없다!


어떤 파일에 선언된 외부 변수를 다른 파일에서 접근해서 사용하려면 지정어 extern을 기술하여야한다. extern을 지정하지 않으면 다른 파일에 정의된 외부변수를 사용할 수 없다. 전역정적변수는 그 변수가 선언되어 있는 파일에서만 사용 가능한 전역변수이다. 단 하나의 파일에서만 사용할 수 있는 전역변수가 필요하다면 함수 외부에 변수 선언 시 static을 붙인다.



다른 파일에서의 extern의 의미는 따로 기억공간을 할당 받는 것이 아니라 이미 다른 파일에 선언된 변수를 참조하겠다는 의미이다. 이때 주의할 점은 extern이 붙은 변수 선언에서는 초기값을 줄 수 없다는 점이다.


static으로 선언된 변수를 extern으로 부를 순 없나봅니다.


레지스터 변수


모든 면에서 자동변수와 동일하다.

단, 더빠르다는 것!


 자동변수

스택 (Stack) 

 레지스터 변수

CPU 내의 레지스터 


하지만, CPU 내의 레지스터는 제한되어 있으므로 기억클래스 register를 붙이더라도 레지스터가 여분이 없을 경우 자동변수처럼 스택에 자리를 잡는다.



[C++] 클래스의 설계

 

절차적 프로그래밍

 

C언어가 대표적인 절차적 프로그래밍 언어이다. 절차적 프로그래밍의 언어는 프로그램의 시작을 주도하는 main함수 내에 기술된 내용이 순차적으로 수행된다. 그래서 main 함수는 대부분 다른 함수의 호출로 기술되어 있다. 이처럼 절차적 프로그래밍에서는 함수를 중심으로 프로그램을 설계한 후 거기에 필요한 데이터를 정의한다.

 

 

객체지향 프로그래밍

 

객체를 지향하는 프로그램 방식으로 객체를 생성하기 위한 클래스를 설계한 후에 이를 다룰 메소드(사용자 인터페이스)를 정의하여 메소드로 객체를 다루도록 한다.

 

일정한 순서에 의해 진행되지 않는다.

사건이 일어날 때마다 그에 따른 처리를 하는 식이다.

여기서의 객체는 구조체처럼 멤버변수를 가지고 있으면서 그들만의 함수도 함께 가지고 있는 것을 말한다.

객체 내부에 정의된 함수는 그 객체를 사용하기 위해 필요한 기술적인 측면을 모두 정의하고 있고, 메소드라 한다.

 

 

객체 지향 프로그래밍의 특징

 

1. 캡슐화와 데이터 은닉

 

데이터를 직접 다루면 손상될 수 있으므로 이를 방지하기 위해 제공되는 것이 캡슐화(Encapsulation)와 데이터은닉(Data Hiding)이다. 사용자가 데이터를 직접 사용하지 못하게 하고, 대신 메소드를 통해서 접근 가능하도록 함으로써 데이터의 유효성을 검증한다.

 

 

2. 다형성과 함수의 오버로딩, 연산자 오버로딩


다형성( Polymorphism)은 동일한 함수나 연산자를 자료에 따라 다르게 동작하도록 적용할 수 있음을 의미.

예를 들어, 함수의 이름은 동일한데 매개변수의 자료형이나 개수를 서로 다르게 주어 여러번 정의할 수 있는데 이를 함수의 오버로딩이라고 한다.

특히 C++에서는 연산자를 정의할 수 있다.

이미 사용 중인 연산다를 다른 용도로 다시 정의해 사용할 수 있는데 이를 연산자의 오버로딩(Operator Overloading)이라고 한다.

 

3. 상속성

상속성(Inheritance)은 객체지향의 가장 대표적인 특징으로, 특정 객체의 성격을 다른 객체가 상속받아 사용할 수 있도록 하는 것이다. 클래스를 설계할 때 여러 개의 클래스에 공통적으로 필요한 성격을 가장 기본적인 클래스에 정의해 두는데, 이 기본적인 클래스를 부모 클래스라 한다.

상속은 이미 존재하는 클래스의 상속을 받기 때문에 매번 전체를 다시 만들 필요없어 개발 기간을 단축시키고 기존 코드를 재활용할 수 있다.

 

 

 

class는 자료를 추상화하여 사용자정의 자료형으로 구현할 수 있도록 하는 C++의 도구이다.

class로 클래스를 선언하는 형식은 구조체와 유사하지만 개념은 확연히 다르다.

 

구조체는 단순히 멤버변수들을 정의하여 원하는 형태로 기억공간을 할당받을 수 있도록 정의하는 것.

클래스는 기억공간을 확보하는 것은 물론이고 이 클래스를 다룰 수 있는 방법도 구현해야 한다.

 

클래스는 크게 클래스 선언과 클래스 멤버함수의 정의로 구성되어 있다.

클래스 선언에는 멤버변수와 멤버함수 원형(프로토타입)을 기술한다.

멤버함수의 정의는 클래스 선언 밖에서 따로 이루어진다.

 

<<클래스 선언>>

 

class 클래스명
{

접근 지정자 :

자료형 멤버변수;

접근 지정자:

자료형 멤버함수();

}

 

 

 

<<멤버함수 정의>>

 

자료형 클래스명::멤버함수()

{

}

 

클래스의 선언은 구조체 선언과 그 구조가 비슷하지만 구조체에는 없었던 접근 지정자가 추가된다.

 

private로 선언된 멤버는 해당 멤버가 속한 클래스의 멤버함수에서만 사용할 수 있기 때문에 캡슐화(데이터은닉) 된다.

public 으로 선언된 멤버들은 객체를 사용할 수 있는 범위라면 어디서나 접근이 가능한 공개된 멤버. 주로 private 멤버를 해당 클래스 외부에서 사용하도록 하기 위한 멤버함수를 정의할 때 사용한다.

 

 

클래스는 새로운 자료형으로 설계할 때는 단순히 데이터의 저장 측면뿐만 아니라 데이터를 처리할 메소드(멤버함수) 제공도 고려해야 한다.

그러므로 다음과 같이 클래스는 새로운 자료형이 되고, 데이터는 멤버변수가, 데이터를 처리할 메소드는 멤버함수가 된다.

 

데이터 은닉에 입각해 멤버변수의 접근지정자는 private로 선언하는 것이 일반적이다.

private로 선언된 멤버현수는 직접제어하지 못하므로 이를 다룰 수 있도록 하기 위한 방법으로 제공하는 멤버함수는 클래스 외부에서 접근 가능하도록 하기 위해서 접근 지정자를 public으로 선언하는 것이 일반적.

 

 

 

클래스의 멤버함수 구현


함수의 3요소는 함수의 정의, 선언, 호출이다.

멤버함수도 이 3가지 조건을 만족해야하며, 멤버함수의 호출은 일반함수와 달리 클래스로 객체를 선언하여 인스턴스화되어야 호출할 수 있다.

 

1. 정의할 때 그 함수가 어느 클래스에 소속되는지 나타내기 위해 함수명 앞에 클래스명을 명시한다. 이때 클래스명 다음에 스코프 연산자(::)를 사용한다.

2. 정의하는 목적은 클래스 내에 정의된 private멤버에 접근하여 이를 다루기 위해서이다. 그래서 멤버함수를 메소드라고도 한다.

 

 

만약 함수를 정의할 때 소속된 클래스명이 명시되어 있지 않기 때문에 이 함수는 일반 함수로 인식한다.

클래스명을 명시하는 것은 어느 클래스에 소속되어 있는지를 식별하기 위해서이지만

다른 클래스에서 같은 이름의 멤버함수를 사용할 수 있도록 하기 위해서이기도 하다.

 

private멤버변수는 자신이 속한 클래스의 멤버함수를 제외하고는 다른 어떠한 함수에서도 접근할 수 없다.

그렇다고 멤버변수를 public으로 선언하면 이는 객체지향 개념에서 벗어난 클래스 설계가 된다.

 

 

객체는 클래스를 실체(Instance)화한 것이다. 그래서 객체를 인스턴스(Instance)라고도 한다.

 

 

Ex> int a

 

int : 자료형

a   : 객체

즉 자료형 int는 a변수를 만들어내기 위한 형틀 !

즉, 템플릿(Template)이다.

 

클래스도 객체를 생성하기 위한 하나의 형틀로 이해하면 쉽다.