반응형
728x90
반응형

C++의 새로운 형 변환

  • 기존 C언어 스타일의 형 변환 연산자

C언어 스타일의 형 변환 연산자( (자료형) 변수 및 상수 ) 형태는 C언어의 호환성을 위해서 C++에서도 제공을 한다. 그런데 문제는 C언어 스타일의 형 변환 연산자는 변환하지 못하는 대상이 없는 아주 강력한 연산자이다. 다음의 예제에서 이를 나타낸다.

39번 라인에서 Base 클래스 포인터 형을 Derived 클래스 포인터 형으로 형 변환하는 것은 일반적인 연산이 아니다. Derived 클래스 형이 필요했다면 애초에 Base 클래스 포인터 형으로 Derived 객체를 가리키게 생성할 필요가 없었다. 그리고 43번 라인은 확실하게 형 변환의 문제가 된다. 클래스 포인터 변수 pb2는 실재하지 않는 멤버를 호출한다. 왜냐하면 실제 생성된 객체는 A 클래스의 객체인데 B의 멤버 함수를 호출하기 때문이다. 이렇게 문제가 있음에도 불구하고 C 언어의 형 변환 연산자는 오류 하나 내주지 않는다.

C++ 언어에서는 이러한 문제를 보완한 4개의 연산자를 추가로 제공해준다.

static_cast / const_cast / dynamic_cast / reinterpret_cast

 

  • dynamic_cast 연산자 : 상속 관계에서의 안전한 형 변환

<> 사이에 변환하고자 하는 객체의 포인터 또는 참조형으로 구성된 자료형의 이름을 둔다.
( ) 
사이에 변환하고자 하는 대상의 이름을 둔다.

이 연산자를 통한 형 변환은 다음과 같은 형 변환 요구가 따라온다.

상속 관계에 놓여 있는 두 클래스 사이에 Derived 클래스의 포인터 및 참조형 데이터를 Base 클래스의 포인터 및 참조형 데이터로 형 변환하는 경우(Derived 클래스 포인터/참조형 -> base 클래스 포인터/참조형)

위의 요구에 부합하는 형 변환 이외의 변환이(형 변환의 방향이 반대인 경우) 해당 연산자를 통해 이루어지는 경우 컴파일러 차원의 에러가 발생한다. 아래는 그에 해당하는 예제와 실행 결과이다.

그러나 39행의 경우는 크게 문제가 되지 않고, 오히려 필요한 경우도 존재할 수 있는데 이러한 형 변환을 의도적으로 진행하는 것을 의도적으로 명시하기 위한 형 변환 연산자가 제공된다.

 

, Derived 클래스 포인터/참조형 -> base 클래스 포인터/참조형으로 변환이 가능한 경우 또한 존재한다. 다음과 같은 가정 하에.

Base 클래스가 Polymorphic 클래스(하나 이상의 가상함수를 포함하는 클래스) 일 때!

상속 관계에 놓여 있는 두 클래스 사이에서 기초 클래스에 가상함수가 하나 이상 존재하면, dynamic_cast 연산자를 이용하여 Base 클래스 포인터/참조형 -> Derived 클래스 포인터/참조형으로의 변환이 가능하다! 다음은 클래스가 수정된 해당 예이다.

47번 라인은 원래 허용되지 않았던 연산이지만 가상 함수가 존재하는 이유로 허용되는 것을 볼 수 있다. 그러나 50번 라인은 pa2가 가리키는 대상을 pb2가 가리킬 수 없는 상황이다. 변환은 가능하지만 Segmentation fault 에러가 런타임에 나오는 것을 볼 수 있는데, 해당의 경우 반환하게 되면 NULL 값을 반환한다.

이렇게 dynamic_cast 형 변환 연산자는 안정적인 형 변환을 보장해주는데 컴파일 시간이 아닌 실행 시간에 안정성을 검사하도록 컴파일러가 바이너리 코드를 생성한다.

 

  • static_cast 연산자 : A 타입에서 B 타입으로

위의 연산자는 dynamic_cast 연산자와 동일한 형태를 갖는다. static_cast 연산자는 Derived 클래스 포인터/참조형 -> base 클래스 포인터/참조형으로의 변경 뿐만 아니라, base 클래스 포인터/참조형 -> Derived 클래스 포인터/참조형으로의 변경 또한 허용한다. 그러나 그에 따른 책임은 프로그래머가 지도록 되어있다. 다음은 그 예이다.

두 변환 모두 허용은 하지만 특히, 43번과 같은 변환에 대한 결과는 예측할 수 없는 책임질 수 없는 결과가 나온다. static_cast는 프로그래머의 의도적인 변환임을, 그래서 책임 또한 지겠다는 의미가 내포되어 있다.

그리고 static_cast 연산자는 기본 자료형에 대해서도 형 변환이 가능하다.

그렇다면 static_cast 연산자는 그에 따른 결과는 연산 결과를 프로그래머가 책임지면 기존의 C 언어에서의 형 변환 연산자랑 무엇이 다를까?

기존 C 언어에서의 형 변환 연산자는 위와 같은 말도 안되는 경우를 허용한다. 그러나 static_cast로 형 변환을 진행하는 경우에는 다음과 같은 결과를 확인할 수 있다.

 

  • const_cast 연산자 : const 성향을 삭제!

위의 연산자를 이용하여 const로 선언된 참조자 혹은 포인터의 const 성격을 제거하는 형 변환을 이룰 수 있다. 이와 같은 연산은 const의 가치를 떨어뜨릴 수 있지만 다음의 예와 같이 의미를 둘 수 있다.

함수의 인자 전달 시 const 선언으로 형의 불일치가 일어날 경우 요긴하게 쓰일 수 있는 연산자이다. 그러나 const에 대한 의미가 옅어지도록 하는 요소이므로 필요할 때만 사용한다. 그런데 const_cast 연산자로 변환된 형태에 대해서 값의 변경을 시도하면 런타임 에러가 발생하기는 한다.

  • reinterpret_cast 연산자 : 상관없는 자료형으로의 형 변환

위와 같이 이전의 형 변환 연산자들과 동일한 형태를 띤다. reinterpret_cast 연산자는 전혀 상관없는 자료형으로의 형 변환을 행할 때 사용되는 연산자이다. 포인터를 대상으로 하는, 그리고 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다.

위와 같은 형 변환 연산이 허용된다. 클래스 C와 클래스 D는 상속 관계가 아닌 전혀 다른 타입이기 때문이다.

위의 예제와 결과는 포인터를 대상으로 하는 포인터와 관련이 있는 모든 유형의 형 변환을 허용한다는 내용에 대한 예제인데 허용이 되지 않는 것 같다(보류)

  • 형 변환 시 발생하는 예외

위와 같은 경우는 dynamic_cast 연산자의 두 번째 경우와 같은 경우인데(NULL 반환 경우), 참조자를 대상으로는 NULL 값을 반환할 수 없기 때문에 bad_cast 예외가 발생함을 참고한다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

24 예외 처리  (0) 2020.10.15
23 템플릿  (0) 2020.10.13
22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
728x90
반응형

파일의 분할

  • 첫 파일의 분할

위와 같은 예를 파일로 분할할 것이다. 다음과 같이.

< number.c / func.c / main.c >

컴파일러가 소스 코드를 컴파일 할 때는 파일 단위로 컴파일 한다. 그렇기 때문에 다른 파일의 정보를 참조하여 컴파일을 진행하지 않는다. 그렇기 때문에 저렇게 세 파일로 나누게 되면 number 변수가 어디에 있는지, set/getNumber 함수가 어디에 있는지 무엇인지 알 수 없기 때문에 에러가 난다. 그렇기 때문에 컴파일러에게 현재 컴파일하고 있는 소스의 외부 소스에 선언 및 정의가 되어있음을 컴파일러에게 알려야 한다.

다음과 같이 수정한다. 먼저 number 변수에 대해서 외부 변수라고 알린다. 그리고 두 함수에 대해서 외부 함수라는 것을 알린다. 바로 extern 키워드를 통해서 컴파일러에게 이를 알릴 수 있다.

< number.c / func.c / main.c >


< Makefile
과 실행 결과 >

 

각 소스에서 찾을 수 없는 변수나 함수들에 대해서 extern 키워드를 이용하여 외부에 존재한다는 선언을 내려주면 정상적으로 컴파일 되는 것을 확인할 수 있다. 그리고 함수에 대해서는 extern 선언은 생략해도 상관 없다. 이와 반대로 외부에서 접근을 못하게 하고 싶다면 해당 변수나 함수에 static 키워드를 붙여주면 다른 외부 파일에서 extern을 이용한 알림이 불가능하다. static 전역 변수는 접근의 범위를 파일의 내부로 제한하는 경우에 사용한다.

 

  • 헤더 파일의 활용

#include 지시자는 선행 처리기에게 다음과 같은 지시를 전달한다.

해당 #include <…> 문장의 위치에 해당 헤더의 저장된 내용을 가져다 놓기!

그리고 다음 예제를 통해서 #include 지시자를 완전히 이해할 수 있다.


< m.c / h1.h / h2.h >

 

  • 헤더 파일을 include 하는 두 가지 방법

1.     #include <헤더 파일 이름>
-> 표준 헤더 파일이 저장되어 있는 디렉터리에서 해당 헤더 파일을 찾는다.

2.     #include “ 헤더 파일 이름
-> 이 문장을 포함한 소스 파일이 저장된 디렉터리에서 해당 헤더 파일을 찾는다.
->상대 경로(...을 이용한) 및 절대 경로를 이용하여 헤더 파일을 삽입 가능하다.

 

  • 헤더 파일에 담아야 할 요소

1.     외부에 존재하는 변수나 함수에 대해서 extern 하는 선언들을 헤더 파일에 담고, 필요할 때마다 헤더 파일만 삽입하여 외부 정의를 알릴 수 있게끔 구성한다. 그렇지 않으면 모든 소스 파일에서 필요한 extern 선언을 반복해서 해야하기 때문이다.

2.     하나의 구조체 등의 선언 및 정의를 여러 소스 파일에서 사용할 때, 이를 각 소스 마다 명시하여 중복 선언 및 정의를 없앤다. 왜냐하면 중복 정의가 되어 있으면 해당 구조체를 수정하고, 확장할 때 불편함이 따른다. 그렇기 때문에 헤더 파일을 이용하여 구조체의 선언 및 정의가 하나만 존재할 수 있도록 개선한다.
 ->
헤더 파일의 중복 문제가 발생할 수 있다.

 

  • 헤더 파일의 중복 발생 및 조건부 컴파일을 활용한 중복 삽입 문제의 해결

헤더 파일의 중복 삽입 자체는 문제가 되지 않는다.

extern int number;
extern void setNumber(int n);
extern int getNumber(void);

위와 같은 선언은 여러 번 삽입된다고 하더라도 오류가 발생하지 않는다. 그저 컴파일러에게 메시지를 전달하는 것에 지나지 않기 때문이다. 그러나 변수의 선언, 구조체 정의, 구조체 변수 선언, 함수 정의 등은 컴파일러에게 메시지, 알림을 알려주는 차원이 아니라 실행 파일의 내용에 직접적인 연관이 있는, 실행 파일의 내용이 달라질 수 있는 정보이기 때문이다.

조건부 컴파일을 활용한 중복 삽입 문제 해결

위 헤더를 처음 포함하는 소스 파일은 __HEADER_H__ 매크로 상수가 정의되지 않았기 때문에 조건부 if 절을 실행하지만, 중복 포함하는 소스의 경우는 매크로 상수가 정의되어 있기 때문에 헤더를 삽입하더라도 내용이 실행되지 않는다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

19 매크로 선행 처리기  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

매크로 선행 처리기

  • 선행 처리기

선행 처리 과정은 프로그래머가 삽입해 놓은 선행 처리 명령문 대로 소스 코드의 일부를 수정한다. 수정은 단순 치환 형태를 띠는 것이 대부분이다. 다음은 간단한 선행 처리에 대한 예제이다.

위의 예에서 선행 처리 명령문은 2번 라인과 4번 라인, 두 가지가 존재한다. 선행 처리 명령문은 # 문자로 시작하고, 명령의 끝에는 세미콜론을 붙이지 않으며 컴파일 이전에 컴파일러가 아닌 선행 처리기에 의해 처리된다. 먼저, 4번 라인의 경우, PI라는 기호를 만나면 3.1412로 치환해라 라는 의미이다. 이 때 PI 기호를 만난다는 것은 쌍 따옴표로 둘러 쌓인 문자열 내의 PI가 아닌 모든 PI 기호를 이야기한다. 선행 처리기에 의해 선행 처리가 진행되면 오른쪽 그림과 같이 치환이 된다. 그리고 2번 라인에서 헤더 파일을 삽입하는 것 또한 선행 처리기에 의해 진행된다. stdio.h라는 파일의 내용을 include 명령이 쓰인 해당 파일에 가져다 놓으라는 의미를 가지고 있다.

 

  • Object-like macro(오브젝트와 유사한 매크로, 매크로 상수) : #define

    지시자 매크로 매크로_몸체
#define   PI   3.1412

지시자는 #include, #define 등과 같은 것을 가리켜 지시자라고 한다. 그리고 이어서 매크로라고 하는 기호와 대치되어야 할 매크로의 몸체가 등장한다. 선행 처리기는 이 부분을 보고 프로그래머가 지시하는 바를 파악한다. 그 중 #define 지시자는 다음과 같은 내용을 지시한다.

이어서 등장하는 매크로를 마지막에 등장하는 매크로 몸체로 치환!

다음은 이에 대한 예이다.

4번 라인부터 6번 라인까지 매크로 상수를 정의 및 지시가 이루어 졌고, 10번 라인부터 12번 라인에서 컴파일 이전에 치환(대치)이 이루어진다. 특이한 점은 6번 라인처럼 함수의 호출 자체 또한 매크로 몸체로 올 수 있다는 점이다.

 

  • Function-like macro(함수와 유사한 매크로, 매크로 함수) : #define

형태는 매크로 상수와 동일하다. 다음은 매크로 함수의 예이다.

지시자      매크로    매크로_몸체
#define    SQUARE(X)        X*X

기존 매크로 상수에 괄호를 등장시킴으로써 함수의 형태를 보이고 치환을 진행할 수 있다. 여기서 X는 정해 지지않은 임의의 값 또는 문장을 이야기 하는데 어떤 문자로 든 올 수 있으나 매크로와 매크로_몸체에서 사용되는 문자는 같은 문자로 통일 되어야 한다. 위의 예에서 해석을 해보자면 SQUARE(X)와 같은 패턴이 등장하게 되면 X*X 형태로 바꿔라 라는 의미다. 함수 호출과 매우 유사하고, 이렇게 변환되는 과정 자체를 매크로 확장이라고 한다. 다음은 관련 예제이다.

 

  • 매크로 함수의 문제(우선순위)

위의 예제는 매크로 함수의 잘못된 사용의 결과이다. 3+2 값의 Square 연산은 5^2으로 25가 나와야 하지만 전혀 다른 결과가 나왔다. 그 이유는 매크로 함수 치환을 풀어보면 나타난다. 3+2*3+2 형태로 치환이 되었기 때문에 우선 순위 연산에 의해서 곱셈 연산자가 먼저 연산 되는 바람에 11이라는 값이 나왔다. 이를 해결하기 위해서는 매크로 함수 정의 시 괄호를 치고 구분을 해주어야 한다.

위와 같은 정의로 매크로 함수를 구성하면 정확한 결과를 얻을 수 있다.

 

  • 매크로 기타 사항

먼저, 매크로 몸체가 복잡해서 두 줄 이상에 걸쳐서 정의를 할 경우에는 두 줄에 걸쳐서 매크로를 정의해야 하는 경우도 생기는데 이 때 그냥 임의로 줄을 변경하면 에러가 발생한다. 이럴 때에는 \문자를 이용해서 줄이 바뀐 매크로 정의임을 명시한다.

그리고 또한 이미 정의한 매크로를 이용하여 또 다른 매크로 정의가 가능하다. , 매크로 정의 시, 먼저 정의된 매크로도 포함 및 사용이 가능하다.

 

 

  • 매크로 함수의 장점과 단점

 

매크로 함수는 보통 작은 크기의 함수 / 호출의 빈도 수가 높은 함수를 매크로 함수로 보통 정의한다. 함수의 크기가 작아야 에러의 발생 확률 또한 낮고, 호출의 빈도 수가 높아야 매크로 함수가 가져다주는 성능 상의 이점을 최대한 활용할 수 있다.

 

 

  • 조건부 컴파일

#if ~ #endif : 참이라면

조건부 컴파일을 이용한 예이다. 7번의 매크로 상수를 FALSE로 정의하면 12번 라인은 실행하지 않는다.

#ifdef ~ #endif : 정의되었다면

12번 라인에서 ifdef는 해당 매크로 상수가 정의되어 있으면 ifdef 내부를 실행한다. 그리고 13번 라인처럼 비교 연산 또한 가능하다. 그리고 8번 라인처럼 공백으로 매크로 상수를 정의할 수 있다. 이 경우에는 선행 처리 과정에서 공백으로 대체되므로 소멸 의미와 같다.

 

  • #elif의 삽입 : #if만 가능

#elif를 통해서 else if 와 같은 효과를 #else를 통해서 else와 같은 효과를 볼 수 있다.

 

  •  매개 변수의 결합과 문자열화

다음과 같은 문장이 가능한지 보자.

#define CONCATENATE(A,B) “A B”

위와 같은 문장은 일단 불가능하다 왜냐하면 문자열 안에서는 매크로의 매개변수 치환이 발생하지 않기 때문이다. 이러한 경우 # 연산자를 이용한다.

#define STR(A) #A

위 문장의 의미는 A로 전달되는 인자를 문자열 “A의 내용형식으로 치환하라는 의미이다.

따옴표를 넣고 안 넣고 의 차이를 확인한다.            

이런 치환 또한 가능하다. 9번 라인에서 두 문자열을 따로 썼지만 합쳐진 결과가 나타났다. 이를 활용해보면.

#define STRCAT(A,B,C) ABC

위의 경우 STRCAT(YOO, SEUNG, HO);라고 하면 “YOO SEUNG HO”라는 문자열이 조합될 것 같지만, 실제로는 조합되지 않는다. 왜냐하면 ABC ABC의 문자열 결합으로 보지 않고 ABC를 하나의 새로운 매크로 상수로 보기 때문이다. 이 경우 ABC로 그냥 치환이 될 뿐이다.

#define STRCAT(A,B,C) A B C

이 경우에는 STRCAT(as,vf,sd); 라고 호출될 경우 as vf sd 로 치환이 될 뿐이다.

이러한 문제를 해결하기 위해서는 ##연산자를 사용해야 한다.

 

  • 필요한 형태로 단순 결합 : 매크로 ## 연산자

## 연산자를 통해 인자로 전달된 요소들을 단순 결합을 시킨다. 다음은 그에 따른 예제이다.

합쳐진 것을 볼 수 있다.

참고로 소스 코드 위에 단순 치환이기 때문에 AB라는 문자열과 1234라는 문자열을 치 ## 연산할 경우 AB1234라는 문자열이 생기는 것이 아니라 소스 코드 상에 AB1234로 대치가 되는 것이다 그렇기 때문에 AB1234는 소스 코드 상에서 변수나 함수 등의 이름으로 인지될 것이고, 이는 없는 이름이기 때문에 에러가 발생한다. 이 점을 혼동하지 않아야 한다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

20 파일의 분할  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

예외처리

  • 예외

프로그램 실행 도중 발생하는 예외적인 상황을 이야기 한다. 컴파일 시 발생하는 에러는 예외와는 관련이 없다. 0으로 나눗셈 등이 예외이다.

 

기존 방식의 if문을 통한 예외 처리

기존 방식의 if문을 통한 예외 처리이다. 이 경우 먼저 중요한 것은 예외가 발생하는 위치와 예외가 발견되는 위치는 다르다는 것을 알아야한다. 그리고 위와 같은 기존 방식의 if문을 통한 예외 처리의 경우 프로그램의 흐름을 구성하는 코드와 예외 처리를 위한 코드를 한 눈에 쉽게 구분하기 힘듦.

 

C++의 예외 처리 메커니즘

먼저, 위와 같이 try의 구역은 예외 발생이 예상 되는 지역을 이야기 한다. try 지역을 선정하는 것은 예외가 발생할 만한 영역 만을 선정하는 것이 아니라 이 예외와 관련된 영역 모두를 구역으로 묶고 선정해야 한다. 예외가 발생함으로써 실행 되서는 안 될 (예외 지역부터 순차적으로 실행되어 왔어야 할) 영역을 묶지 않게 된다면 문제가 되기 때문이다. , 트랜잭션의 개념으로 try 구역을 묶고 선정해야 한다.

다음은 throw 키워드를 이용하여 발생한 예외 상황에 대해서 알릴 수 있고, 알림으로써 처리를 위한 루틴(catch)으로 예외를 전달할 수 있다. 예외를 던졌다고 표현한다. throw를 통해 예외가 알려지면 해당 catch 영역 내의 해당 throw 밑의 영역은 실행하지 않고 예외가 처리되는 루틴으로 실행 흐름이 넘어간다.

마지막으로 catch 영역은 예외를 처리하는 영역이다. throw에 의해서 알려져 전달된 예외를 이 catch 블록에서 처리를 하는데 이 때, 전달된 예외의 자료형과 catch의 매개 변수의 자료형은 일치해야 전달과 처리까지 진행된다. (자료형 일치를 통해서 던져진 예외는 같은 자료형의 매개변수를 지닌 catch로 전달된다.) 이러한 catch 영역은 try에 연이어 구성되어야 하며, 하나의 try구문 당 여러 catch 구문이 구성될 수 있다. 왜냐하면 처리해야 할 예외의 자료형이 여럿일 수 있기 때문이다.

예외가 발생하지 않을 경우 try문을 실행하고, 이어서 등장하는 catch문은 뛰어 넘고 catch문 직후를 실행한다. 그러나 예외가 발생할 경우 throw에 의해 발생된 예외가 던져지고 던져진 이후의 try 문장은 실행하지 않고, 던져진 예외는 해당 예외를 감싸는 try문에 의해 감지가 되고, 이어서 등장하는 catch 블록에 전달되고 처리된다. 그렇게 처리된 후 다시 try문으로 돌아가 나머지 문장을 실행하는 것이 아니라, catch 블록 이후의 라인이 실행된다.

 

  • Throw를 통한 예외의 전달

예외가 throw에 의해 던져진 지역에서 처리가 되지 않는 경우는 함수 내에서 예외가 발생했지만 그에 따른 처리가 함수 내에서 이루어지지 않는 경우와 일맥상통하다. 위의 예제를 보면 divFunc 함수 내에서 throw에 의해 예외가 던져졌지만, 그에 상응하는 예외 처리가 divFunc 함수 지역 내에서 이루어지지 않았는데 이렇게 해당 지역에서 예외 처리가 이루어지지 않을 경우에는 예외가 발생한 함수를 호출한 영역으로 예외와 예외처리에 대한 책임이 전달된다. 위의 예에서 12번 라인에 의해 예외가 던져지면 13, 14번 라인은 진행되지 않고(함수가 종료됨) 흐름은 27번 라인으로 시작된다.

이러한 현상을 스택 풀기라고 한다. 함수 내의 또 다른 함수 호출이 이루어지는 상황에서 예외가 던져지면 함수가 호출된 순서의 반대 순서로 리턴 하듯이 종료하면서 예외가 전달된다.

 

  • 전달되는 예외의 명시

예외가 전달되는 경우가 존재하는 함수에 대해서 전달될 수 있는 예외의 종류를 명시할 수 있다. 이렇게 명시되는 예외의 종류 또한 함수의 특징이 될 수 있는데 이렇게 전달되는 예외의 종류를 명시해야만 하는 이유는 예외가 함수 내에서 처리되지 않고 전달되어 오게 되면 해당 함수 호출 부분 아래에 catch 문을 구성해야 하는데 이 catch문을 어떤 타입의 예외들로 구성을 해 놓아야 하는지를 알 수 있기 때문이다.

위와 같이 예외를 명시할 수 있다.

 

  • 예외 객체/클래스와 상속 관계에서의 catch 순서

  

위의 예제는 클래스 객체를 통해서 예외 클래스를 만들고 활용하는 예제이다. 여기서 중요한 것은 예외 객체의 상속 관계에 있는 예외들을 전달하게 되는데 받을 때 catch문의 구성이 중요하다. 먼저 왼쪽 main 함수 내의 catch 문 구성은 Base 클래스부터 catch 문을 구성하였고 아래로 내려갈수록 Derived 클래스 순으로 구성하는 것을 볼 수 있다. 오른쪽 main 함수 내의 catch 문 구성은 최하위 Derived 클래스부터 아래로 내려갈수록 Base 클래스 순으로 구성을 하였는데 이에 대한 결과는 다음과 같다.

왼쪽 main 함수에서는 전달되어 온 예외들은 모두 첫 번째 catch ExceptionA 클래스가 모두 받는다. 반면에 오른쪽 main 함수에서 전달되어 온 예외들은 정상적으로 각 예외를 적절한 catch문이 받는 것을 확인할 수 있다. 왜냐하면 Base 클래스가 Derived 클래스 형태로 전달되어 온 예외 객체를 모두 받기 때문이다.

  • new 연산자에 의해 발생되는 예외

bad_alloc 예외 클래스는 new 헤더에 만들어져 있고 메모리 공간의 할당이 실패했음을 알리는 의도로 정의된 예외 클래스이다.

  • 모든 예외를 처리하는 catch 블록

위와 같이 마지막 catch“…” 을 매개변수로 넣은 catch 문을 확인할 수 있는데 이 때, “…”의 의미는 위에서 거르지 못하고 전달되어 온 예외들을 모두 받아드리겠다는 선언이다. , 위와 같은 선언을 할 시에는 발생하는 예외에 대해서 어떤 정보도 받을 수 없고, 예외의 종류 또한 구분할 수 없다. 

  • 전달되어 온 예외, 다시 던지기!

예외를 이중으로 다시 던지는 것이 가능함을 보여주는 예이다. 하지만 간결한 구조를 위해 굳이 예외를 거듭해서 던질 필요 없다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

25 형 변환  (0) 2020.10.17
23 템플릿  (0) 2020.10.13
22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
728x90
반응형

동적 메모리 할당

  • 메모리의 구성

프로그램 실행 시 운영체제에 의해서 마련되는 메모리 구조는 위와 같이 네 개의 영역으로 구분된다. 여러 영역으로 나뉘는 이유는 각기 다른 성향을 가진 여러 공간들을 유사한 성향의 공간끼리 분류해서 해당 성향을 지닌 데이터들을 분류한 공간 별로 저장하면 관리가 용이해지고 메모리의 접근속도가 향상된다.

  • 메모리의 동적 할당

함수가 매번 호출될 때마다 새롭게 할당되고 또 함수를 빠져나가도 유지가 되는 유형의 변수의 생성은 생성과 소멸의 시기가 지역 변수나 전역 변수와는 다른 유형의 변수를 생성하는 것인데 이를 가능케하는 개념이 메모리의 동적 할당이다.

malloc 함수와 free 함수

힙 영역은 프로그래머가 관리하는 메모리 공간이다. 필요한 메모리 공간을 프로그래머에 의해서 동적 할당하기 때문이다. 그렇기 때문에 메모리 할당 및 해체에 대해서 전적으로 프로그래머가 담당하기 때문에 malloc 함수로 할당한 메모리 공간은 free 함수로 꼭 해체해줘야 하며, malloc 함수에 의해 할당된 메모리 공간은 쓰레기 값으로 구성된다. 다음은 예제이다.

9번 라인을 통해 포인터 변수를 선언하고, 11번 라인을 통해 힙 영역 메모리 공간을 동적 할당하는데 이 때 인자로는 바이트 단위의 사이즈가 계산되어 전달되었고, 형 변환이 일어나고 있다. 원래 malloc 함수는 void * 형을 반환하도록 되어 있기 때문에 대입 연산을 위해서는 void * 형에서 원하는 형으로 변환을 거친 후에 참조를 진행해야 한다. malloc 함수가 void * 형을 반환하는 이유는? malloc 함수를 호출할 때 전달되는 것은 바이트 단위의 크기 정보 밖에 전달되지 않기 때문이다. 그렇기 때문에 malloc 함수는 어떤 자료형으로 참조할지 알 수 없는 상황이기 때문에 직접 형 변환 연산을 통해 변환을 진행하는 것이 그 이유이다. 그리고 18, 19번 라인을 통해 동적 할당한 메모리 영역에 값을 대입하고, 출력한다. 그리고 free 함수를 통해 동적 할당 해체를 진행한다.

  • free 함수의 호출이 필요한 이유

힙 메모리 영역은 프로그래머가 관리하는 영역이기 때문에 메모리의 동적 할당만큼 해체 또한 매우 중요하다. 할당한 만큼 해체해주지 않으면 운영체제에 의해서 메모리가 자동 해체되지만 그럼에도 free 함수를 통한 동적 할당 공간 해체가 필요한 이유는 더 큰 규모의 프로그램에서는 실행되는 동안 메모리 부족 등의 문제가 일어날 수 있기 때문에 습관적인 해체는 꼭 필요하다.

 

  • calloc 함수

malloc 함수와 똑같은 기능을 제공하나, 함수의 인자 전달 방식에 차이와 초기화 값의 차이가 있을 뿐이다. elt_size 크기의 블록 elt_count개를 힙 영역에 할당하는데, 해당 메모리 공간을 0으로 초기화 하라 라는 내용의 함수이다.

 

  • realloc 함수

realloc 함수는 ptr이 가리키는 메모리의 크기를 size 크기로 다시 조절하는 함수이다.

반환 값에 따라 두 가지 경우로 나눌 수 있다.

1.     malloc 함수가 반환한 주소와 realloc 함수가 반환한 주소가 같은 경우

-> 확장할 영역이 넉넉해서 기존 영역을 기반으로 공간을 확장한 경우

2.     malloc 함수가 반환한 주소와 realloc 함수가 반환한 주소가 다른 경우

-> 확장할 영역이 넉넉하지 못해서 새로운 영역에 별도로 할당한 경우

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

20 파일의 분할  (0) 2020.10.17
19 매크로 선행 처리기  (0) 2020.10.17
17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
728x90
반응형

템플릿

  • 함수 템플릿

모형자에 비유하여 함수 템플릿을 설명

모형자는 모형을 만들어 낸다. 모형의 틀은 결정되어 있지만, 모형의 색은 결정되어 있지 않아서 결정해야함.
함수 템플릿은 함수를 만들어 낸다. 함수의 기능은 결정되어 있지만, 자료형은 결정되어 있지 않아서 결정해야함.

, 함수 템플릿은 함수를 만드는 도구가 될 수 있다. 함수의 기능은 정해져 있으나 그 기능 수행에 사용되어지는 요소들의 자료형이 결정되어 있지 않기 때문에 다양한 자료형의 함수를 만들어 낼 수 있다.

위의 예는 기본 함수를 나타낸 것이다. 정수형에 대한 곱셈 연산을 진행하는 함수이다. 이를 템플릿으로 정의하게 되면 다음과 같다.

곱셈 연산을 진행하는 함수이지만 연산의 대상에 대한 자료형은 결정되어 있지 않고, 나중에 T를 대신해서 실제 자료형을 결정하겠다는 뜻이다. 여기서 T는 자료형을 결정짓지 않겠다는 의미이고, 함수를 만들어 내는 템플릿을 정의하기 위해 사용된 것이다. 위의 예를 완성시키면 다음과 같다.

14번 라인, 15번 라인에서 <자료형> 방식을 통해 함수 템플릿의 T 를 결정 짓고 호출한다는 뜻이다. 컴파일러는 컴파일 할 때, 각 자료형에 따른 함수를 하나씩 만들고, 호출하게 된다. 컴파일 속도는 떨어지지만 실행 속도가 지연되지는 않으므로 문제가 없다.

다음과 같이 함수를 호출할 때, <자료형> 형식으로 호출하지 않고, 일반 함수 호출 방법도 허용한다. 다만, 컴파일러에 의해서 전달되는 인자로 호출될 자료형의 함수를 판단한다.

17, 18번 라인과 같이 호출 가능.

 

  • 함수 템플릿과 템플릿 함수

 

  • 두 개의 이상의 자료형을 이용하기

8번 라인에서 매개 변수의 타입은 이미 정해졌기 때문에 호출 시 18~ 20번 라인과 같이 템플릿 T1, T2의 자료형을 명시해줘야 한다. 그리고 12번 라인에서 형 변환의 두 가지 방법이 나오는데 모두 같은 효과를 지닌다.

  • 함수 템플릿의 특수화

함수 템플릿의 특수화란 함수 템플릿을 통해 템플릿 함수를 구현할 때, 구성 방법에 예외를 둘 필요가 있을 때 사용하는 방법이다. 위의 예제에서 6번 라인에서의 compare 함수는 두 인자에 대한 비교 후 큰 값을 출력하도록 되어 있는데 18번 라인과 같은 경우는 의미가 없는 비교가 된다. 왜냐하면 사전식 비교나 문자열 길이 비교가 아닌 주소 값의 비교이기 때문이다. 이러한 경우, compare 템플릿 함수의 구성에서 문자열에 대한 템플릿 함수는 의미가 없는 구성이기 때문에 다음과 같이 예외로 두고 새롭게 정의해야 한다.

12, 18번 라인과 같이 적용하면 함수 템플릿의 특수화가 적용이 된다. , 예외가 적용되는 것이다. 12, 18번 라인들의 경우 각각 char*, const char* 형의 템플릿 함수는 예외로 두고 직접 제시할 테니 해당 함수들이 호출될 경우 템플릿 함수로 구성하지 말고 가져다 쓰라는 뜻이다. 위의 예에서는 문자열에 대한 비교에서 길이 비교, 사전식 비교를 진행하는 예외 두 가지를 두었다

  • 클래스 템플릿

클래스 또한 클래스 템플릿을 정의하고 이를 기반으로 컴파일러에 의해 템플릿 클래스를 만들 수 있다.

위의 예에서 볼 수 있듯이 클래스 템플릿을 정의하고 이를 통해 템플릿 클래스의 객체를 생성할 수 있는데 25~27번 라인처럼 T에 대한 자료형을 명시한 채로 객체를 생성해야 한다. 함수처럼 생략은 불가능하다.

클래스 내의 템플릿 함수의 정의와 선언을 분리하는 방법이다.

  • 특정 템플릿 클래스의 객체를 인자로 받는 일반 함수 정의 및 friend 선언

위와 같이 일반 함수를 대상, 매개 변수로 템플릿 클래스를 받을 수 있으며 템플릿 클래스를 상대로 friend 선언 또한 가능하다.

  • 클래스 템플릿 특수화

함수 템플릿의 특수화를 하는 이유 : 특정 자료형에 대해서 구분이 되는 다른 기능을 보이기 위함.
클래스 템플릿의 특수화를 하는 이유 : 특정 자료형을 기반으로 생성된 객체에 대해서 구분이 되는 다른 행동 양식을 적용하기 위함.

19, 20번 라인과 같이 특수화시킬 수 있다.

  • 클래스 템플릿의 부분 특수화와 우선 순위

34번 라인과 같은 경우를 부분 특수화라고 한다. 부분적으로만 자료형을 지정하지 않았기 때문이다. 그렇다면 전체 특수화와 부분 특수화 모두 지정한 경우 50번 라인과 같이 겹치는 자료형에 의한 객체 생성이 발생한다면 어떻게 될까? 그 결과는 전체 특수화된 클래스를 대상으로 객체가 생성된다. , 전체 특수화가 부분 특수화보다 우선시 된다.

  • 템플릿 매개 변수와 템플릿 인자

템플릿 정의할 시에 결정되지 않은 자료형을 의미하는 T, T1 따위의 문자를 템플릿 매개 변수라고 함. 이러한 템플릿 매개 변수에 전달되는 자료형 정보를 템플릿 인자라고 한다.

이러한 템플릿 매개 변수에는 변수의 선언이 올 수 있다.

6번 라인과 같이 템플릿 매개 변수에 일반 변수의 선언이 허용되고, 템플릿 인자로 값을 받을 수 있다. 이 때 받아온 템플릿 인자를 통해서 10번 라인과 같이 멤버 변수에 대한 배열 길이를 정의 한다. 배열 길이의 정의는 생성자로도 충분히 할 수 있지만 생성자를 통해서 동적 할당을 통한 배열 구현의 경우 24번 라인에 대해서 컴파일 에러가 발생하지 않지만, 템플릿 인자를 통한 배열 구현의 경우 24번 라인에 대해서 컴파일 에러가 발생한다. 왜냐하면 <int, 5><int, 3>은 서로 다른 자료형으로 취급하기 때문이다.

  • 템플릿 매개변수의 디폴트 값

템플릿 매개변수 또한 디폴트 값을 가질 수 있다.

  • 템플릿과 static

Static 멤버는 클래스 변수로 객체 별로 생성되는 변수가 아닌 클래스 별로 생성되는 변수로서 객체가 생성되지 않은 상황에서도 이미 생성되어 있는 변수이다. 템플릿을 적용한 템플릿 클래스에서 또한 템플릿 클래스 별로 static 멤버 변수를 유지한다. , 위의 예에서 Data<int> 클래스에 하나, Data<short> 클래스에 하나씩 유지한다. 그렇기 때문에 d1d2(or d3d4)는 서로 다른 객체이지만 static 멤버가 공유된 결과를 볼 수 있다.

 

  • template<typename T …> / template <>

template<typename T …> / template <>

위 키워드의 쓰임은 함수 템플릿 및 클래스 템플릿을 정의 혹은 그 외의 템플릿 관련 정의에 대해서 위와 같은 선을 두고 템플릿의 일부 또는 전부를 정의하고 있다는 사실을 컴파일러에게 알린다. , T가 무엇인지 그리고 정의 및 특수화, 부분화 정의를 시키고 있다 라는 것을 알리기 위함이다. 전자의 경우는 템플릿 매개변수가 사용된 정의에 대해서 관련 템플릿 문자를 맞춰서 선언하고, 후자는 템플릿 문자를 사용하지 않았을 때 선언한다.

  • 템플릿 static 멤버 변수 초기화의 특수화

위 예시의 한 부분인데 위와 같이 static 멤버를 초기화하면 모든 템플릿 클래스에 대해서 0으로 초기화 된다.( Data<char>, Data<int>, Data<long>, Data<short> ) 하나의 자료형에 대해서만 다른 값으로 초기화해야 하는 경우는 다음예의 22, 23번 라인과 같이 특수화 하면 된다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

25 형 변환  (0) 2020.10.17
24 예외 처리  (0) 2020.10.15
22 string 클래스 - 직접 일부 구현  (0) 2020.10.12
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
728x90
반응형

파일 입출력

  • 파일 스트림

파일 입/출력을 위해서는 파일과 프로그램 사이에 입출력 스트림을 형성해야 한다. 파일은 운영체제에 의해서 그 구조가 결정되고 관리되는 대상이기 때문에, 파일 뿐만 아니라 스트림의 형성 또한 운영체제의 몫이다.

 

  • 파일 스트림의 형성 : fopen 함수와 FILE 구조체

fopen 함수는 파일과 프로그램 사이의 스트림을 형성하도록 하는 함수이다. 첫 번째 인자는 스트림을 형성할 파일의 이름(/출력의 대상 파일 경로), 두 번째 인자에는 형성할 스트림의 종류에 대한 정보를 문자열 형태로 전달(읽기/쓰기 모드 등)한다. 그리고 나서 이 함수는 첫 인자에 해당하는 파일과 스트림을 형성하고, 스트림 정보를 FILE 구조체 변수에 담아서 그 변수의 주소 값을 반환한다. FILE 구조체는 어떤 형태로 어떤 내용을 구성하고 있는지 알 필요가 없다. 단지 FILE 구조체에는 fopen함수가 호출 성공 시 해당 파일에 대한 정보가 담기고, 그러한 FILE 구조체 변수의 주소 값을 반환함으로써 FILE 구조체 포인터를 통해 가리킨다. FILE 구조체 포인터 변수는 파일(파일의 정보가 담긴 FILE 구조체 변수)을 가리키고 있는데 이러한 FILE 구조체 포인터를 이용해서 파일에 데이터를 저장하거나 파일에 저장된 데이터를 읽는다.

 

  • 입력 스트림과 출력 스트림 생성의 예

먼저, 스트림 생성 및 소멸에 필요한 함수 소개부터 진행한다.

스트림의 소멸을 요청하는 fclose 함수이다.

파일로 출력하는, 출력 스트림을 생성하는 예제

먼저, 8번 라인에서 파일 포인터를 생성하고, 10번 라인을 통해 fopen 함수를 호출함으로써 파일 출력 스트림을 생성한다. fopen 함수는 파일에 대해서 열지 못할 경우 NULL 포인터를 반환하므로 11번 라인에서 이에 대한 검사를 진행한다. (사실, 파일에 출력을 할 때에는 파일이 존재하지 않으면 파일을 생성하고 출력을 하기 때문에 딱히 검사를 하지 않아도 되긴 하다.) 그리고 16번 라인에서 fputc함수를 이용하여 ‘a’ 문자를 출력한다. 그리고 형성한 출력 스트림을 소멸 요청하는 fclose 함수를 통해 outputFilePointer에 해당하는 출력 스트림을 소멸한다. 그리고 그 결과 파일이 생성되었고, ‘a’ 문자가 잘 출력된 것을 확인할 수 있다.

 

파일로부터 입력하는, 입력 스트림을 생성하는 예제

다음은 위에서 생성한 파일 속 문자를 읽어오는 예제이다.

먼저, 8번 라인에서 파일 포인터를 생성하고, 11번 라인을 통해 fopen 함수를 호출함으로써 파일 입력 스트림을 생성한다. fopen 함수는 파일에 대해서 열지 못할 경우 NULL 포인터를 반환하므로 12번 라인에서 이에 대한 검사를 진행한다. 그리고 17번 라인에서 fgetc함수를 이용하여 문자 하나를 파일로부터 입력 받아온다. 그리고 형성한 입력 스트림을 소멸 요청하는 fclose 함수를 통해 inputFilePointer에 해당하는 입력 스트림을 소멸한다. 그리고 읽어온 ‘a’ 문자가 잘 출력된 것을 확인할 수 있다.

파일의 스트림에도 중간에 입출력 버퍼가 존재한다. 이 버퍼에 값이 저장되고 차례차례 파일로 출력을 하는 상황에서 컴퓨터의 전원이 꺼질 경우 출력되다가 처리가 되지 않은 문자들은 저장되지 않는다. 그러나 fclose 함수를 통해 파일을 닫아주면 출력 버퍼에 저장되어 있던 데이터가 파일로 이동하여 출력 버퍼가 비워지고 저장 또한 일어난다. 즉 파일을 모두 사용할 경우에는 바로 fclose 함수를 호출해줘야만 한다.

그리고 fflush 함수의 특성은 그대로 적용된다.

1.     출력 버퍼를 비움 -> 출력 버퍼에 저장된 데이터를 목적지로 전송

2.     입력 버퍼를 비움 -> 입력 버퍼에 저장된 데이터를 소멸 (버퍼의 내용을 읽으면 소멸)

3.     fflush 함수는 출력 버퍼를 비우는 함수

4.     fflush 함수는 입력 버퍼를 대상으로 호출하더라도 결과가 보장되지 않는다.

출력 버퍼를 비우는 것은 fflush 함수를 호출하면 되지만 입력 버퍼를 비우는 것은 함수가 필요하지 않는다. 왜냐하면 파일에 저장된 데이터는 콘솔 입력과는 다르게 언제든 데이터를 읽을 수 있고, 파일 대상의 입력 버퍼를 비워야만 하는 상황이라는 것은 특별히 존재하지 않기 때문이다.

 

  • 파일의 개방 모드(fopen 함수의 두 번째 인자에 대한 고찰)

스트림을 구분하는 기준

1.     읽기 위한 스트림/쓰기 위한 스트림

+는 읽기와 쓰기가 모두 가능한 스트림의 형성을 이야기하고 aappend로 덧붙임을 의미한다. 읽기와 쓰기가 모두 가능한 모드의 경우 메모리 버퍼를 비워줘야 하고 잘못된 사용으로 이어질 수 있다는 점을 주의.

2.     텍스트 데이터를 위한 스트림/바이너리 데이터를 위한 스트림

사람이 인식할 수 있는 문자를 담고 있는 파일을 텍스트 파일이라고 하고, 컴퓨터가 인식할 수 있는 데이터를 담고 있는 파일을 바이너리 파일이라 한다. 이러한 사실과 관련해서 신경 써야하는 부분은 문장의 끝을 의미하는 개행의 표현 방식이다.

‘\n’, 개행을 나타내는 문자이지만 이것은 C언어에서만 나타낸 약속이다. MS-DOS\r\n, Mac\r, Unix 환경에서는 \n이 개행이다. 그렇다면 위의 시스템들 간에 파일을 공유할 때에는 각 시스템마다 개행을 나타내는 방식으로 변환이 필요한데 파일을 텍스트 모드로 개방하게 되면 자동으로 변환이 진행된다. 반대로 바이너리 모드로 파일을 개방하게 되면 아무런 변환이 이루어지지 않는다. 텍스트 모드로 파일을 여는 경우는 사람이 읽도록 출력이 완벽하게 되어야 하기 때문에 각 시스템에 맞도록 변환이 진행되지만, 바이너리 모드로 파일을 여는 경우는 기계가 이해해야 하기 때문에 하나의 손상도 일어나선 안된다. 변환을 통한 손상 또한 일어나서는 안된다. 그렇기 때문에 바이너리 모드는 변환을 일으키지 않는다.

앞서 두 기준을 취합하여 fopen 함수의 두 번째 인자, 스트림 형성을 완성한다.

  • 텍스트 기반의 입/출력 함수

이미 언급한 함수 모두 사용 방법 및 설명이 같다. stream의 인자 부분에 표준 입/출력 디스크립터 대신 파일에 대한 파일 포인터를 넣으면 된다.

 

  • 파일이 종료되었는지 확인 : feof 함수

 

  • 바이너리 기반의 입/출력 함수

바이너리 데이터 입/출력의 경우 텍스트 데이터 입/출력의 경우보다 단순하기 때문에 다양한 함수를 제공하지 않는다.

바이너리 데이터를 입력하는 함수

fread 함수는 size 크기의 데이터 count 개를 stream으로부터 읽어 들여서 buffer에 저장하라는 의미이다.

바이너리 데이터를 출력하는 함수

fwrite 함수는 buffer에 있는 size 크기의 데이터 count stream에 저장하라는 의미이다.

 

  • 서식에 따른 데이터 입/출력: fprintf, fscanf

사용 방법은 첫 번째 인자에 stream을 넣어 파일을 대상으로 하는 것을 제외하고는 printf/scanf랑 완전히 똑같다.

  • 임의 접근을 위한 파일 위치 지시자의 이동

파일 위치 지시자란? FILE 구조체의 멤버 중에는 파일의 위치 정보를 저장하고 있는 멤버가 존재한다. 이 멤버의 값은 각종 읽기 및 쓰기 함수들이 호출될 때마다 참조 및 갱신이 된다. 이 멤버에 저장된 위치 정보의 갱신을 통해서 데이터를 읽고 쓸 위치 정보가 유지되는 것이다. 이 멤버를 파일 위치 지시자라 부른다.

그런데 파일을 읽을 때 항상 앞부터 읽는 것이 아닌, 중간부터 혹은 뒤부터 읽을 필요가 종종 생기는데 이 때 이 파일 위치 지시자 멤버를 조작하여 읽을 위치를 지정하는 것이다.

위 함수는 stream으로 전달된 파일 위치 지시자를 wherefrom에서부터 offset 바이트만큼 이동을 의미한다. offset은 음수/양수 값 모두 가능하며, 다음은 wherefrom에 전달되는 상수와 그 의미를 정리한 것이다.

다음은 위의 요소들을 종합하여 사용한 예를 나타낸다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

19 매크로 선행 처리기  (0) 2020.10.17
18 동적 메모리 할당  (0) 2020.10.15
16 구조체  (0) 2020.10.12
15 문자와 문자열  (0) 2020.09.29
14 함수 포인터와 void 포인터  (0) 2020.09.23
728x90
반응형

-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#include <iostream>
#include <string>
#include <cstring>
 
namespace mystd
{
    using std::cout;
    using std::cin;
    using std::endl;
    using std::printf;
    using std::ostream;
    using std::istream;
 
    class string
    {
    private:
        char * str;
        int str_len;
    public:
        string();
        string(const string& r_string);
        string(const char * str);
        ~string();
 
        string& operator=(const string& r_string);
        string& operator=(const char * str);
        const string operator+(const char * str);
        const string operator+(const string& r_string);
        string& operator+=(const char * str);
        string& operator+=(const string& r_string);
        bool operator== (const string& r_string);
        bool operator== (const char * str);
 
        friend ostream& operator<<(ostream& os, const string& r_string);
        friend const string operator+(const char * str, const string& r_string);
        friend char * operator+=(char * str, const string& r_string);
        friend bool operator==(const char * str, const string& r_string);
    };
    istream& operator>> (istream& is, string& r_string);
    ostream& operator<< (ostream& os, const string& r_string);
    const string operator+(const char * str, const string& r_string);
    char * operator+=(char * str, const string& r_string);
    bool operator==(const char * str, const string& r_string);
};
 
using namespace mystd;
 
int main(void)
{
    string str1 = "I like "// 복사 생성자
    string str2 = "string class"// 복사 생성자
    string str3 = str1 + str2; // 복사 생성자, + 연산자 오버로딩
 
    cout<<"------ str1 ------"<<endl;
    cout<<str1<<endl// << 연산자 오버로딩 전역
    cout<<"------ str2 ------"<<endl;
    cout<<str2<<endl// << 연산자 오버로딩 전역
    cout<<"------ str3 ------"<<endl;
    cout<<str3<<endl// << 연산자 오버로딩 전역
 
    string str4;
    str4 = "HELLO";
    cout<<"------ str4 ------"<<endl;
    cout<<str4<<endl;
 
    string str5;
    str5 = str3 + str4 ;
    cout<<"------ str5 ------"<<endl;
    cout<<str5<<endl;
 
    string str6;
    str6 = str4 + " AAA" ;
    cout<<"------ str6 ------"<<endl;
    cout<<str6<<endl;
 
    string str7;
    str7 =  "AAA " + str4 ;
    cout<<"------ str7 ------"<<endl;
    cout<<str7<<endl;
 
    string str8 = "asdf";
    string str9 = "fdsa";
 
    str8 += str9;
    str9 += "asdf";
    cout<<"------ str8 ------"<<endl;
    cout<<str8<<endl;
    cout<<"------ str9 ------"<<endl;
    cout<<str9<<endl;
 
    cout<<"------ str11 ------"<<endl;
    string str10="222222222";
    char str11[30= "fdjskalfjklsaf";
    str11+=str10;
    printf("%s\n",str11+=str10);
 
    string str12 = "ZXC1";
    string str13 = "ZXC";
    if(str12 == str13)
        cout<<"동일한 문자열이다1"<<endl;
    else
        cout<<"동일하지 않은 문자열이다1"<<endl;
 
    char str14[10= "ZXC";
    if(str12 == str14)
        cout<<"동일한 문자열이다2"<<endl;
    else
        cout<<"동일하지 않은 문자열이다2"<<endl;
 
    if(str14 == str12)
        cout<<"동일한 문자열이다3"<<endl;
    else
        cout<<"동일하지 않은 문자열이다3"<<endl;
 
    cout<<"문자열 입력 : ";
    cin>>str4; // >> 연산자 오버로딩 전역
    cout<<"입력한 문자열 : "<<str4<<endl// << 연산자 오버로딩 전역
 
    return 0;
}
 
 
mystd::string::string() : str(NULL), str_len(0) { }
mystd::string::string(const string& r_string) : str_len(r_string.str_len)
{
    this->str = new char[r_string.str_len+1];
    strcpy(this->str, r_string.str);
}
mystd::string::string(const char * str) : str_len(strlen(str))
{
    this->str = new char[strlen(str)+1];
    strcpy(this->str, str);
}
mystd::string::~string()
{
    delete []this->str;
}
 
string& mystd::string::operator=(const string& r_string)
{
    this->str_len = r_string.str_len;
 
    if(this->str == NULL)
    {
        this->str = new char[this->str_len+1];
    }
    else
    {
        delete []this->str;
        this->str = new char[this->str_len+1];
    }
    strcpy(this->str, r_string.str);
    return *this;
}
 
string& mystd::string::operator=(const char * str)
{
    this->str_len = strlen(str);
 
    if(this->str == NULL)
    {
        this->str = new char[this->str_len+1];
    }
    else
    {
        delete []this->str;
        this->str = new char[this->str_len+1];
    }
 
    strcpy(this->str, str);
    return *this;
}
const string mystd::string::operator+(const char * str)
{
    char * tmp_str = new char[this->str_len + strlen(str) + 1];
    string tmp;
 
    strcpy(tmp_str, this->str);
    strcpy(tmp_str + this->str_len, str);
 
    tmp = tmp_str; // 대입 연산자 오버로딩
    delete []tmp_str;
    return tmp;
}
const string mystd::string::operator+(const string& r_string)
{
    char * tmp_str = new char[this->str_len + r_string.str_len + 1];
    string tmp;
 
    strcpy(tmp_str, this->str);
    strcpy(tmp_str + this->str_len, r_string.str);
 
    tmp = tmp_str; // 대입 연산자 오버로딩
    delete []tmp_str;
    return tmp;
}
string& mystd::string::operator+=(const char * str)
{
    int tmp_strlen = this->str_len + strlen(str);
    char * tmp_str = new char[tmp_strlen + 1];
 
    strcpy(tmp_str, this->str);
    strcpy(tmp_str+this->str_len, str);
 
    if(this->str == NULL)
        this->str = new char[tmp_strlen + 1];
    else
    {
        delete []this->str;
        this->str = new char[tmp_strlen + 1];
    }
    strcpy(this->str, tmp_str);
    this->str_len = tmp_strlen;
 
    delete []tmp_str;
    return *this;
}
string& mystd::string::operator+=(const string& r_string)
{
    int tmp_strlen = this->str_len + r_string.str_len;
    char * tmp_str = new char[tmp_strlen + 1];
 
    strcpy(tmp_str, this->str);
    strcpy(tmp_str+this->str_len, r_string.str);
 
    if(this->str == NULL)
        this->str = new char[tmp_strlen + 1];
    else
    {
        delete []this->str;
        this->str = new char[tmp_strlen + 1];
    }
    strcpy(this->str, tmp_str);
    this->str_len = tmp_strlen;
 
    delete []tmp_str;
    return *this;
}
bool mystd::string::operator== (const string& r_string)
{
    if(!strcmp(this->str, r_string.str))
        return true;
    else
        return false;
}
bool mystd::string::operator== (const char * str)
{
    if(!strcmp(this->str, str))
        return true;
    else
        return false;
}
 
istream& mystd::operator>> (istream& is, string& r_string)
{
    char str[100];
    is>>str;
    r_string = string(str);
    return is;
}
ostream& mystd::operator<< (ostream& os, const string& r_string)
{
    os<<r_string.str;
    return os;
}
const string mystd::operator+(const char * str, const string& r_string)
{
    char * tmp_str = new char[r_string.str_len + strlen(str) + 1];
    string tmp;
 
    strcpy(tmp_str, str);
    strcpy(tmp_str + strlen(str), r_string.str);
 
    tmp = tmp_str; // 대입 연산자 오버로딩
    delete []tmp_str;
    return tmp;
}
char * mystd::operator+=(char * str, const string& r_string)
{
    int i = 0;
    int tmp_strlen = strlen(str) + r_string.str_len;
    char * tmp_str = new char[tmp_strlen + 1];
 
    strcpy(tmp_str, str);
    strcpy(tmp_str + strlen(str), r_string.str);
 
    while(str[i++]!=0); // str의 사이즈를 구한다.(i-2)
    strncpy(str,tmp_str,i-2); // str의 기존 사이즈만큼 복사한다. 짤리겠지.
    delete []tmp_str;
    return str;
}
bool mystd::operator==(const char * str, const string& r_string)
{
    if(!strcmp(str, r_string.str))
        return true;
    else
        return false;
}
cs

-

 

직접 구현해보았으나 설명 및 풀이를 시간이 나면 진행을 해보겠다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

24 예외 처리  (0) 2020.10.15
23 템플릿  (0) 2020.10.13
21 연산자 오버로딩  (0) 2020.09.23
20 다중 상속  (0) 2020.09.22
19 멤버함수 가상함수의 동작 원리  (0) 2020.09.15
728x90
반응형

구조체

  • 구조체

구조체란 하나 이상의 변수를 묶어서 새로운 자료형을 정의하는 도구이다. 예를 들어, 기존의 자료형과 별개로 3바이트짜리 자료형을 만들어내는 것, 이러한 예처럼 새로운 자료형을 기본 제공 자료형으로 추가하는 것은 불가능 하지만 기존의 자료형들을 이용하여 생성하는 변수들을 묶어서 새로운 자료형을 정의하는 것은 가능하다.

다음은 이름과 학번을 저장하는 새로운 자료형을 정의한 예를 나타낸 것이다.

학생을 관리하는 시스템에서는 학번과 이름은 함께 처리되어야 의미가 있는 자료들이다. 그렇기 때문에 위와 같이 학생의 이름을 나타내는 name 변수와 해당 이름의 학생의 번호를 의미하는 stdNum을 한데 묶어서 관리할 수 있도록 새로운 자료형을 정의한 것이다.

 

  • 구조체의 정의

구조체의 정의는 다음과 같다

왼쪽은 구조체의 정의를 나타낸 것이다. 오른쪽의 예는 구조체를 정의한 예인데, 20 바이트 문자형 배열과, 4바이트 정수를 저장할 수 있는 변수들로 구성되어 있는 PersonalInfo라는 자료형을 하나 새로 만든 것이다. PerosonalInfo라는 자료형은 int, float, double 등처럼 자료형의 이름이 될 수 있다. 이 이름을 통해서 사용자 정의 자료형 변수를 만들게 되는 것이다.

  • 구조체 변수의 선언과 접근

구조체 변수의 선언

 

정의한 사용자 정의 자료형(구조체)을 통해 변수를 선언하는 방법은 왼쪽과 같다. 그리고 오른쪽 13, 14번 라인은 구조체 변수를 선언한 예를 나타내고 있다.

 

구조체 변수를 통한 멤버 변수 직접 접근과 간접 접근

선언된 구조체 변수를 통해 멤버 변수에 접근할 수 있다. 아니, 해야만 한다. 그러기 위해 만든 구조체이기 때문이다.

왼쪽과 같이 구조체 변수 이름과 ‘ . ‘ (직접 접근 연산자)와 멤버 변수의 이름을 명시함으로써 구조체의 멤버 변수에 직접 접근하여 값을 넣거나 값을 출력 혹은 수정까지 할 수 있다.

위의 예에서는 16번 라인과 17번 라인을 통해 멤버 변수에 접근하고, 대입 및 다른 곳으로의 참조를 시키는 등의 작업이 이루어진다. 중요한 것은 구조체_변수_이름 . 구조체의 멤버 변수 형식을 갖추어 직접 접근한다는 것.

구조체(사용자 정의 자료형) 또한 일반 자료형과 같은 취급을 하기 때문에 포인터로 선언하고 주소 값을 받는 모든 행위들은 허용된다. 다음의 예가 가능하다.

위의 예에서 13번 라인에서 일반 구조체 변수를 선언하고, 15번 라인에서는 구조체 포인터 변수를 선언한다. 그리고 나서 16, 17번 라인을 통해서 직접 접근 연산자 ‘.’을 이용하여 구조체 멤버에 직접 접근을 통해서 값을 대입한다. 그리고 19번 라인에서 구조체 변수에 기존에 선언했던 일반 구조체 변수의 주소 값을 대입한다. 중요한 것은 21번 라인의 참조 방식인데, ‘->’(간접 접근 연산자)를 통해서 접근하는 형태를 볼 수 있다. 이렇듯 구조체 포인터 변수를 통해서 가리키고 있는 변수의 멤버에 접근할 때에는 간접 접근 연산자(->)를 통해서 접근한다. 이를 간접 접근 이라고 한다.

참조를 통한 직접 접근 또한 가능하다.

22번 라인과 같이 구조체 포인터가 가리키는 대상에 대해서 접근 연산이므로 직접 접근 연산이 맞다. 포인터를 이용해서 참조한 후 직접 접근 연산자를 통해 멤버에 접근할 수 있는 방법이 제시된 예이다.

 

 

  • 구조체 변수의 초기화

생성과 동시에 초기화

배열의 초기화와 매우 유사하다.

5번 라인에 해당하는 구조체 정의를 두고, 13번 라인에서 구조체 변수를 선언하면서 생성과 동시에 초기화를 진행하고 있는데 그 초기화 방식은 다음과 같다.

중괄호로 감싼 후, 초기화할 값들을 콤마 연산자를 통해 구분하며 나열하는데 구조체 정의 시 멤버 변수의 선언 순서대로 나열을 한다.

 

일반 초기화(값의 대입)

다음의 예와 같이 선언 이후에 값의 대입을 통하여 초기화를 진행할 수 있다.

  • 구조체 배열

구조체 배열 또한 일반 변수의 배열과 마찬가지로 다를 바가 없다. 다음은 이전의 예제를 조금 변형한 구조체 배열에 대한 예제이다.

먼저, 15번 라인에서 구조체 배열을 선언한다. 두 명의 학적 정보를 저장할 수 있도록 배열로 선언한다. 그런데 배열을 선언과 동시에 초기화를 이루고 있다. 이 경우 2차원 배열의 초기화와 크게 다를 바가 없다. 구조체 정의 시 나열한 멤버 변수의 순서대로 값들을 나열하고, 그러한 나열을 행 별로 다시 구분 지어 나열한다. 그리고 20번 라인에서는 선언만 이루어졌고, 22~ 26번 라인에서 대입을 통한 초기화가 이루어지고 있다. 그리고 29번 라인부터는 인덱스를 이용하여 구조체 배열의 각 요소들을 출력하는 것을 볼 수 있다.

 

  • 구조체 포인터 및 구조체 첫 멤버 변수의 주소 / 구조체 변수의 주소 / 구조체 이름

구조체 포인터 또한 일반 변수의 포인터와 다를 바가 없다. 다만 다른 부분은 아래에 제시한다.

위의 결과로 미루어 보았을 때 구조체의 첫 멤버 변수의 주소와 구조체 변수의 주소는 같지만 구조체 이름은 다른 것임을 알 수 있다. (배열 이름과는 다름)

 

l  구조체와 typedef 연산을 통한 자료형 재정의

Typedef 는 자료형을 다른 이름으로 재정의하는 연산자이다. 다음과 같은 형식으로 재정의를 진행한다.

typedef 기존_자료형 새로운_자료형_이름;

다음은 기존 자료형을 통한 자료형 재정의의 예이다.

11, 12번 라인에서 int 자료형과 char 자료형에 대해서 새로운 이름으로 재정의가 일어났다.

다음으로는 사용자 정의 자료형에 대해서 typedef 연산을 통해 재정의가 일어난 몇 가지 경우를 표현했다. 아래에서 제시하는 모든 경우에 대한 결과는 다음과 같다.

14번 라인과 같이, 일반 자료형을 통한 변수 선언 하듯, struct 키워드를 포함하지 않더라도 구조체 변수의 선언이 가능하다. 밑에서 11번 라인에 들어가는 다양한 경우의 typedef 선언을 제시.

 

  • 함수 구조체 인자 전달

 

  • 구조체를 대상으로 가능한 연산

구조체 변수를 대상으로 하는 연산은 제한적으로 대입(=), sizeof, 주소(&) 연산만이 허용된다. 특히 구조체의 변수 간 대입 연산을 진행하게 되면 멤버 대 멤버의 복사가 이루어진다.

15번 라인에서 대입 연산을 진행하고 있고 그 결과, pi1pi2의 멤버들이 지니는 값은 모두 같은 것으로 미루어 보았을 때, 값의 복사가 이루어지고 배열의 경우에도 모조리 값의 복사가 일어나는 것을 확인할 수 있다.

  •  구조체의 정의 이유와 중첩된 구조체의 선언

구조체의 정의가 필요한 이유

구조체를 통해서 연관 있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면, 데이터의 표현 및 관리가 용이해지고, 합리적인 코드를 작성할 수 있다.

구조체를 정의하지 않고 구현한 경우 문제가 되는 점 : 인자 전달 시 매개 변수가 늘어남, 묶어서 하나의 인덱스에 대해서 관리가 불편해짐. 높은 차원의 배열이 계속 생성됨.

 

중첩된 구조체의 정의와 변수 선언

구조체 내에 구조체 변수를 선언할 수 있다. 이때 초기화 할 경우에는 순서에 주의하고, 중첩된 구조체 변수에 대한 초기화 시 중괄호를 통해 구분한다.

  • 공용체(union)

공용체는 구조체와 유사한 개념이긴 하나, 다른 점은 멤버 변수들이 하나의 공간을 공유하는지의 차이가 있다.

위의 예제는 구조체와 공용체의 차이를 나타내는 예제이다. 공용체의 정의나 선언 방법은 구조체와 완전히 똑같다고 보면 된다. 그러나 차이는 24~ 28번 라인에서 볼 수 있다.

24번 라인에서는 구조체의 멤버의 각 주소 값을 출력하는데 결과를 보면 자료형 별 차이만큼 주소 값이 잘 할당되어 있는 것을 확인할 수 있는데 25번 라인에서 공용체의 멤버의 각 주소를 출력하는데 결과를 보니 모두 같은 주소(공용체의 시작 주소) 값을 가지고 있었다. 게다가 이상한 것은 이 뿐만이 아니다. 27번 라인에서 sData의 크기는 멤버 변수들의 메모리 공간 할당 크기들을 합한 결과 값이 나오는데, uData의 크기는 멤버 변수 중 가장 할당 크기가 큰 자료형의 길이 값이 나왔다.

< 구조체 멤버 변수의 할당 구성 >
< 공용체 멤버 변수의 할당 구성 >

위의 그림은 구조체와 공용체의 멤버 변수들의 할당 구성을 나타낸 것이다. 굵은 검정 칸이 실제로 할당된 메모리 공간이 된다. 구조체 할당 구성에서는 각 멤버 변수 별로 크기만큼 할당이 되어 있는 것을 볼 수 있는데 공용체 할당 구성에서는 가장 큰 double 자료형의 크기만큼만 공간을 할당하고 모든 멤버 변수가 이 공간을 공유하는 형태로 할당 구성이 이루어진다.

이러한 공용체는 다음과 같은 예제에서 유용하게 사용될 수 있다.

 

정수 하나를 입력 받고, 입력 받은 정수의 상위 2바이트와 하위 2바이트의 값을 양의 정수로 출력하고, 상위 1바이트와 하위 1바이트에 저장된 아스키 문자 출력하기

 

  • 열거형

구조체나 공용체는 자료형을 명시함으로써 멤버에 저장할 값의 유형(type)을 결정하였다면, 열거형의 경우는 저장이 가능한 값 자체를 정수의 형태로 결정한다.

0, 1, 2, 3, 4, 5, 6 값들을 저장 가능한 color형 자료형

Color 자료형으로 선언되는 모든 변수들은 enum Color 정의에서 정의해 둔 상수들을 값으로 갖을 수 있다. 또한 정의된 상수들은 어디서든 지역 범위를 고려하여 사용할 수 있다. enum 정의 및 변수 선언은 구조체, 공용체와 완전히 똑같다. 그리고 상수 정의 시, 어떤 값도 넣지 않으면 순서대로 0 값부터 값이 1씩 올라가는 형태로 값의 배정이 이루어진다.

다음의 예에서 특이한 점을 발견할 수 있다.

위에서는 정의된 상수들에서 RED에 값을 지정하지 않았으므로 0부터 시작하는데 YELLOW10이라는 값을 지정했다. 그리고 그 결과 YELLOW 이후 부터는 10부터 1씩 증가한 값들로 배정된다.

열거형은 위와 같이 활용될 수 있다.

열거형의 유용함은 이름있는 상수의 정의를 통한 의미의 부여에 있다. 둘 이상의 연관이 있는 이름을 상수로 함께 같이 묶어서 선언함으로써 프로그램의 가독성을 높인다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

18 동적 메모리 할당  (0) 2020.10.15
17 파일 입출력  (0) 2020.10.13
15 문자와 문자열  (0) 2020.09.29
14 함수 포인터와 void 포인터  (0) 2020.09.23
13 다차원배열 및 포인터의 관계  (0) 2020.09.21
728x90
반응형

문자와 문자열

  • 입력과 출력

  • 스트림

프로그램과 입력 및 출력 장치를 연결해주는 소프트웨어적으로 구현되어 있는 도구, 입출력 장치와 프로그램을 연결해주는 가상의 다리이다. 운영체제가 이를 제공한다.

  • 문자 단위 입/출력 함수

EOF End Of File의 약자로 파일의 끝을 표현하기 위해 정의된 상수이다. 위의 함수가 파일이나 콘솔의 출력으로 EOF를 반환하게 되면 파일이나 콘솔로부터 더 이상 입력 받을 것이 없다 라는 의미를 갖는다. 그렇다면 EOF를 반환하는 시기는 다음과 같다.

함수 호출의 실패할 경우
컨트롤 + z / 컨트롤 + d 키가 입력될 경우

문자를 입력 받는 함수인데 반환 형이 정수 int 형인 이유는? 컴파일러에 따라 char unsigned char로 처리하는 경우가 존재한다. 그런데 EOF 라는 값은 -1로 정의되어 있는데 charunsigned char로 처리하는 컴파일러는 EOF에 대한 처리가 불가능하다. 그렇기 때문에 같은 정수 타입인 int로 반환한다.

 

  • 문자열 단위 입/출력 함수

기존의 scanf 함수 등은 공백을 포함한 문자열을 입력 받기에 있어서 크게 제한이 되었다. (공백을 기준으로 입력을 나누기 때문)

 

  • 표준 입/출력 버퍼

표준 입/출력 함수를 통해서 데이터를 입/출력하는 경우, 해당 데이터들은 운영체제가 제공하는 메모리 버퍼를 꼭 통과하게 되어 있다. 메모리 버퍼는 데이터를 임시로 저장해 놓는 메모리 공간인데, 이런 저장하는 작업을 버퍼링 이라고 한다.

키보드로 데이터를 입력하면(문자열 입력 후 엔터를 칠 경우) 해당 데이터들은 입력 버퍼로 전송되어 저장되고(버퍼링), 그 이후에 프로그램으로 데이터들이 전송이 된다.

 

버퍼링을 하는 이유?

프로그램 연산(CPU 연산)에 비했을 때 I/O 장치들 과의 I/O 작업은 상당히 시간이 걸리는 작업이다. , 데이터를 읽어오는데 걸리는 시간이 CPU의 연산 처리 속도보다 훨씬 느리다. 그렇기 때문에 CPU는 그러한 느린 데이터 입출력 처리를 기다리고 있어야 하는데 데이터를 읽을 때마다 조금씩 전송을 하는 구조로 진행되면 데이터 전송의 효율성이 떨어진다. 그렇기 때문에 중간에 메모리 버퍼를 두고 데이터를 한데 묶어서 보내면 빠르고 효율적인 데이터 전송이 될 수 있다.

 

  • 출력 버퍼와 입력 버퍼 비우기!

출력 버퍼를 비운다는 것은 출력 버퍼에 저장된 데이터가 버퍼를 떠나서 목적지로 이동되는 것을 이야기하는데, 이러한 역할을 하는 함수가 존재하는데 다음과 같다.

위 함수는 파일을 대상으로도 버퍼 비우기가 가능하고, 출력 버퍼를 비우는 일은 많이 존재하지는 않는다.

반면에, 입력 버퍼를 비운다는 것은 출력 버퍼와는 다르게 데이터의 소멸을 의미한다. fflush 함수를 이용하여fflush(stdin); 과 같은 형태로 호출하면 입력 버퍼는 비워지거나/비워지지 않거나 보장할 수 없는 결과를 나타낸다. (Windows 계열의 컴파일러의 경우는 입력 버퍼를 지워 준다고 한다.)

입력 버퍼를 비우기 이전에 입력 버퍼를 비워야 하는 경우에 대한 예제이다.

먼저, 6번 라인에서 str1의 크기를 10 바이트로 지정해주었고, 10번 라인에서 str1에 문자열을 대입하는데 9자리의 문자를 대입한다. 9자리라고 함은 문자열 9자리와 NULL 문자 1바이트를 고려한 것이다. 그러나 입력될 때에는 “abcdefghi\0\n” 이 입력되고 입력 버퍼에 들어가게 된다. (fgets함수의 ‘\n’이 입력될 때까지 읽어 들이는 특성 상) 그래서 10번 라인에서는 입력한 “abcdefghi” 문자열에 ‘\0’ 문자가 합쳐져서 입력으로 str1에 저장된다. 그리고 13번 라인에서 fgets 함수가 호출되는데 이 때 입력 버퍼에 남은 ‘\n’ 문자를 읽어버리고 바로 호출이 끝나게 된다. 그렇기 때문에 실행 결과에서는 str2‘\n’만 저장된 결과가 나타난 것이다. 이러한 경우 str1의 저장 이후 입력 버퍼를 비웠더라면 다음 문자열 입력을 받을 수 있었다. 이것이 입력 버퍼 비우기의 필요성이다.

입력 버퍼를 비운다는 것은 문자들을 읽어 들이면 지워진다, 즉 비워진다. 다음과 같은 함수로 입력 버퍼를 지울 수 있다.

23번 라인에서 한 문자씩 읽는데 그 읽은 것이 ‘\n’ 개행 문자일 경우 탈출하게 된다. , 개행 문자까지 버퍼로부터 문자를 읽음으로써 버퍼가 비워진다.

 

  • 문자열의 길이를 반환 : strlen

일단, size_ttypedef unsigned int size_t; 로 자료형 재정의가 되어 있다.

 

  • 문자열의 복사 : strcpy

strcpysrc의 문자열을 dest에 복사한다. strncpy의 경우에는 src의 문자열을 dest에 복사하되, n 크기만큼 복사한다. , strncpy의 경우 마지막 문자가 NULL인지에 대한 검사는 하지 않고 주어진 n만큼만 복사를 한다. , 복사된 문자열의 끝에서 NULL에 대한 처리를 진행하지 않기 때문에 직접 NULL 처리를 해줘야 한다. 이후에 기재되는 모든 n이 들어가는 함수는 NULL처리가 진행되지 않는 공통점을 가지고 있다.

 

  • 문자열 연결 : strcat

Dest 문자열 뒤에 src 문자열을 붙이고 그 값을 dest에 저장한다. 앞 문자열의 NULL 문자를 없애고 그 자리부터 뒷 문자열이 시작된다.

 

  • 문자열 비교 : strcmp

  • 이외의 문자열/수 변환 함수

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

17 파일 입출력  (0) 2020.10.13
16 구조체  (0) 2020.10.12
14 함수 포인터와 void 포인터  (0) 2020.09.23
13 다차원배열 및 포인터의 관계  (0) 2020.09.21
12 포인터의 포인터  (0) 2020.09.15

+ Recent posts