F.R.I.D.A.Y.

[TIPS 19TH] 08 : 2018.07.19. (목) 본문

외부활동/TIPS 19th

[TIPS 19TH] 08 : 2018.07.19. (목)

F.R.I.D.A.Y. 2018. 7. 21. 14:02
반응형

1. typedef

 이전에 [ #define ]에 대한 글이 한번 적혔다. 이번 [ typedef ]는 전처리기 구문인 [ #define ]과는 비슷하면서도 다르다. 차이는 후술하고 먼저 [ typedef ]에 대해 알아보자.


 [ typedef ]는 사용자 정의 데이터형을 생성하는 문법으로, C언어의 정식 문법이다. 기본적인 사용볍은 다음과 같다.


typedef unsigned char AGE;
// typedef [기존 자료형] [새로 사용할 자료형];

 여기서 ' ; '은 필수적으로 필요하다.

 그렇다면 여기서 왜 [ typedef ]라는 문법이 필요하는가? 다음 코드를 보자.


#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

void PrintAge(unsigned char age) {
	printf("%hhu\n", age);
}

void InputAge(unsigned char *age) {
	printf("나이를 입력하세요 : ");
	scanf("%hhu", age);
}

int main(void) {
	unsigned char age;
	InputAge(&age);

	PrintAge(age);

	return 0;
}

 나이를 입력받아 출력하는 함수를 작성하였다. 일반적으로 나이는 음수를 가지지 않고, 200세를 넘기는 커녕 130세를 넘기는 경우도 드물기 때문에 [ unsigned char ]로 크기를 잡았다. 그런데 만약, 굉장히 굉장하게 장수하는 사람을 발견해서, 혹은 동식물[각주:1]의 나이도 입력받는다면 [ unsigned char ]로는 부족하다. 그렇다면 저 코드를 어떻게 바꿔야할까? [ unsigned char ]를 [ unsigned short ]로 바꿔야 할 것이다. 서식지정자는 논외로 하자구.

 꽤 까다로운 작업이 될텐데, 그럼 [ typedef ]를 이용해 작성한 코드를 보자.


#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

typedef unsigned char AGE; //typedef를 이용해 unsigned char를 통해 새로운 자료형인 AGE를 생성함

void PrintAge(AGE age) {
	printf("%hhu\n", age);
}

void InputAge(AGE *age) {
	printf("나이를 입력하세요 : ");
	scanf("%hhu", age);
}

int main(void) {
	AGE age;
	InputAge(&age);

	PrintAge(age);

	return 0;
}

 이 코드에서도 위와 똑같이 문제가 발생해 자료형의 크기를 키워야할 때, 몇 군데나 바꿔야할까? 한 번. [ typedef unsigned char AGE; ] 이부분 하나만 바꾸면 된다. 여기서도 서식지정자는 논외로 하자구.


 이러한 이점 뿐 아니라, 긴 자료형을 짧게 줄이거나 의미가 모호할 수 있는 변수 선언을 명확하게 해줄 수도 있다.


int (*p)[5];             // 1번 코드

typedef int MY_DATA[5];  // 2번 코드
MY_DATA *p;


위 두 코드는 길이 5인 배열을 가리키는 포인터 변수 [ p ]를 생성하는 코드이다. 똑같은 코드이지만 좀더 명확하게 선언할 수 있다.


 간단히 하면 장점으로는 이런 것들이 있겠다.

>> 긴 자료형을 짧게 줄일 수 있다.

>> 변수 선언시 의미를 명확히 할 수 있다.

>> 코드의 유지보수에 용이하다.

>> 이후에 나올 함수 포인터에서 매개변수가 나열식으로 되는 문제를 해결할 수 있다.


 그러나 좋다고 남발하면 그것으로 문제가 되므로, 추후에 자료형을 변경할 수 있다고 예측되거나 의미를 명확히 해야하는 경우에 사용하는 것이 좋다.


 마지막으로 앞에서 후술하겠다는 [ #define ]과 [ typedef ]의 차이이다.


#define MY_POINTER int *
...
MY_POINTER p1, p2; // int *p1, p2;

//------------------------------------------------------------

typedef int * MY_POINTER
...

MY_POINTER p1, p2; // int *p1, *p2;

 위 아래 코드 모두 [ int * ]타입의 변수 [ p1 ]과 [ p2 ]를 선언한 것 같지만, 위에서는 [ p1 ]은 [ int * ]타입으로, [ p2 ]는 [ int ] 타입으로 선언이 되어있다. 반대로 [ typedef ]로 작성한 코드변수에서는 정상적으로 [ p1 ], [ p2 ] 모두 [ int * ]타입으로 선언되어있다.



2. 구조체

 동류의 데이터를 처리할 때는 일반적으로 배열을 많이 사용한다. 그렇다면 어떤 그룹 안에 하위 데이터가 존재하는 경우에는 어떻게 처리해야할까? C언어는 이런 문제를 구조체라는 문법의 제공으로 해결한다. 일종의 변수 묶음이라고 봐도 된다.


 기본 구조는 다음과 같다.


struct People{
    char name[11];
    char Age;
    
};

struct People Person1;            // People 구조체를 자료형으로 하는 변수 [ People1 ]을 선언한다.
Person1.Age = 20;                 // [ Person1 ]의 하위 데이터인 [ Age ]에 20을 대입한다.
strcpy(Person1.name, "PangPnag"); // [ Person1 ]의 하위 데이터인 [ name ]에 "PangPang"을 복사한다.


 변수 선언을 할 때는 [ struct <구조체 이름> varName ]으로 작성해주면 된다. 해당 구조체의 하위 데이터에 접근할 때는 ' . '기호를 사용해 접근할 수 있다.


 구조체 변수를 선언할 때는 매번 구조체 이름 앞에 [ struct ]라는 단어를 작성해주어야하는 불편함이 있다. 이러한 불편함은 앞에서 배운 [ typedef ]로 해결할 수 있다.


struct People{
    char name[11];
    char Age;
    
};

typedef struct People Person;

Person Person1;                   // People 구조체를 자료형으로 하는 변수 [ People1 ]을 선언한다.
Person1.Age = 20;                 // [ Person1 ]의 하위 데이터인 [ Age ]에 20을 대입한다.
strcpy(Person1.name, "PangPnag"); // [ Person1 ]의 하위 데이터인 [ name ]에 "PangPang"을 복사한다.

 [ struct People ]이란 자료형으로 [ Person ]이라는 이름의 자료형을 새로 만들었다. 위 선언 방식뿐 아니라 다양하게 작성할 수 있다.



 구조체는 선언과 동시에 초기화도 가능한데 이 때, 중괄호를 이

struct People{
    char name[11];
    char Age;
    
};

typedef struct People Person;

Person Person1 = {{'P','a', ...'0'}, 20};
// 구조체 [ Person1 ]의 [ name ]에 "PangPang"을, [ age ]에 20을 대입

 단, 주의할 점은 꼭 구조체에서 정의내린대로 초기화를 해주어야한다.


 구조체 포인터 변수도 사용할 수 있다.


typedef struct People {
	char name[11];
	char Age;

}Person;

int main(void) {

	Person p;
	Person *p1 = &p;
	
	(*p1).Age = 20;  // 구조체 포인터 변수 [ p1 ]이 가리키는 구조체 주소의 [ Age ]영역에 20을 대입
	p1->Age = 20;

	return 0;
}


fawefawef

 구조체 포인터로 간접 접근을 하기 위해서는 위와 같이 [ (*p1).Age ]처럼 괄호를 작성해주어야한다. 늘 그렇듯이 포인터 연산자의 연산순위가 그리 놓지 않기 때문에 발생하는 문제다. 이러한 귀찮음을 보완하기 위해 [ -> ]연산자가 생겼다. [ -> ]연산자를 사용하면 굳이 [ (* ) ]를 사용할 필요가 없다.

 구조체도 배열을 만들 수 있는데, 그냥 일반 배열 변수처럼 인덱스를 붙인 뒤, 하위 데이터에 접근하고자 하면 [ .ChildData ]처럼 작성해주면 된다. 위 [ Person ]구조체에서는 다음과 같이 작성할 수 있다.


Person p[5];

for(int i = 0; i < 5; i++)
        p[i].Age = 15 + 2 * i;



3. 파일 입출력

 파일 입출력은 C언어의 문법[각주:2]이라기보단 함수의 사용법이다. 일반적으로 함수이름 앞에 " f '가 붙는다.


FILE *fp; // 파일 포인터 선언
fp = fopen("C:\\test.txt", "rt");

...

fclose(fp);

 기본적인 작동 구조이다. [ fopen ]함수는 성공하면 해당 파일 포인터를 반환하지만, 실패할 경우 [ NULL ]을 반환한다.  [ fopen ]함수 뒤에 존재하는 [ " rt " ]는 해당 파일을 어떤 특성으로 열 것인지 명령하는 것으로 보면 된다. 일반적으로 두자리, 혹은 세자리로 구성되며 구성은 다음과 같다.


특성 

의미 

 읽기 모드로 열기

 파일이 존재하지 않으면 오류 발생

 쓰기 불가 

 쓰기 모드로 열기

 파일이 존재하지 않으면 새로 생성

 파일 내에 데이터가 존재할 경우 데이터를 지우고 새로 작성 

a[각주:3] 

 추가 모드로 열기

 파일이 존재하지 않으면 새로 생성

 파일 내에 데이터가 존재할 경우 기존 데이터 뒤에 이어 작성 

r+ 

 읽기 모드에 쓰기 모드 추가
 파일이 존재하지 않으면 오류 발생
 임의 부분에 접근해 수정이 가능[각주:4] 

w+ 

 쓰기 모드에 읽기 모드 추가

 파일이 존재하지 않으면 새로 생성

 파일 내에 데이터가 존재할 경우 데이터를 지우고 새로 작성 

a+ 

 추가 모드에 읽기, 쓰기 모드 추가

 파일이 존재하지 않으면 새로 생성

 파일 내에 데이터가 존재할 경우 기존 데이터 뒤에 이어 작성 

참고 : MSDN fopen, wfopen 함수 관련 글


 위 특성 뒤에는 추가로 파일을 어떤 방식으로 열 것인지 제어하는 코드가 추가로 들어간다.


방식 

의미 

 텍스트 모드로 열기, 기본값 

 이진(바이너리) 모드로 열기 


 출력함수(파일에 쓰기)

>> int fputc(int c, FILE *stream)

: 문자 출력


>> int fputs(const char *s, FILE *stream)

: 문자열 출력


>> int fprintf(FILE *stream, const char *format, ...);

: 형식 지정 문자열 출력, 매개변수로 [ FILE *stream ]이 앞에 추가되었다 뿐이지 [ printf ]함수와 동일함.


 입력함수(파일에서 불러오기)

>> int fgetc(FILE *stream)

: 문자 입력


>> char * fputs(const char *s, FILE *stream)

: 문자열 출력


>> int fscanf(FILE *stream, const char *format, ...)

: 형식 지정 문자열 입력, 매개변수로 [ FILE *stream ]이 앞에 추가되었다 뿐이지 [ scanf ]함수와 동일함.


 위 함수는 텍스트 모드로 입출력할 때 사용되는 함수이고 바이너리 모드로 입출력을 할 때는 아래 함수를 사용하자.


출력함수

>> size_t fread( void *buffer, size_t size, size_t count, FILE *stream)

buffer : 불러온 데이터를 저장할 주소

size : 한번에 읽을 데이터 크기, 바이트 단위

count : 위 [ size ]만큼 읽는 행위를 몇 번 행할지의 수

stream : 불러들일 파일 포인터


 입력함수

>> size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream)

buffer : 파일에 쓸 데이터 주소

size : 한번에 쓸 데이터 크기, 바이트 단위

count : 위 [ size ]만큼 쓰는 행위를 몇 번 행할지의 수

stream : 쓸 파일 포인터



4. 함수 포인터

 포인터가 가지는 가장 큰 강점은 명시적인 대상이 없더라도 컴파일에서 오류가 나지 않는다는 점이다. 즉, 포인터를 이용하면 미래 지향적인 코드를 생성할 수 있다. 우리가 여태 배운 것은 [ 전역 변수, static 변수, 힙, 스택 ]영역을 가리키는 데이터 포인터였다. 그러나 이번에 배우는 함수 포인터는 코드 세그먼트를 가리키는 포인터이다.


 함수 포인터의 사용법은 다음과 같다.


#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int Sum(int a, int b) {
	return a + b;
}

int main(void) {

	int(*fp/**/)(int, int);
	fp = &Sum; // 엠퍼센트(&) 기호는 생략 가능하다.

	printf("%d", (*fp)(4, 5)); // 출력 : 9

	return 0;
}

 함수 포인터는 선언시에 함수의 원형과 동일하게 작성해주어야 한다.


int TriSum(int a, int b, int c);

 위와 같은 함수가 존재하면 꼭 매개변수의 자료형과 개수가 일치하는 함수포인터만 사용할 수 있다.


 함수 포인터도 다른 일반 변수나 포인터 변수처럼 배열로 만들어 사용할 수 있는데, 아래와 같이 사용할 수 있다.


int (*p[5])(int, int);


 길이 5인 함수 포인터를 생성했다. 이 함수 포인터도 앞에서 설명한 [ typedef ]문법을 적용할 수 있는데 아래와 같이 사용함으로써 좀 더 읽기 쉬워질 수 있다. 이렇게 배열로 선언한 함수 포인터는 반복문올 사용할 수도 있다.


typedef int (*FuncPointer)(int, int);

FuncPointer p[5];

 함수 포인터를 [ typedef ]와 결합할 때는 주의할 점이 새로 선언할 자료형의 이름과 함수 포인터가 뒤섞여 있다. [ FuncPointer ]가 새로운 자료형의 이름이 되는 것이다.


 참고로 요즘은 컴파일러가 진화해서 사용할 때 굳이 [ (*fp)(4, 5) ]에서 [ (* ) ]를 사용할 필요가 없다. 그러나 괄호를 지우면 우선순위가 바뀌므로 꼭 다 쓰던지, 다 안쓰던지 한쪽으로만 쓰도록 하자.


 참고

>> 콜백이란 암시적 호출, 즉 함수 포인터를 의미한다고 한다. 어디 부분에서 어떤 함수가 호출될지 알 수 있으면 그것은 명시적 호출.




Etc.

 다른 의미를 지니는 서로 다른 변수를 선언할 때는 따로 선언해주는 것이 좋다.

int Age, Year; // 일반적인 선언

int Age;       // 권장되는 선언
int Year;

 데이터에 대한 구조화는 구조체가 지원하고, 코드에 대한 구조화는 함수가 지원한다.


 구조체는 컴파일러로 번역될 때, 포인터 어드레싱 방식으로 바뀐다.



  1. 제일 장수한 나무는 3,000살이 넘고, 상어류 중 그린란드 상어는 400살도 발견되었다. 이게 얼마나 대단한거냐면 3,000살은 기원전 1,000년 쯤, 그러니까 고구려라는 나라가 생기기 대략 900년 전이고, 400살은 병자호란 시절즈음에 태어난거다 [본문으로]
  2. 주의할 것은 FILE이라는 것은 C에서 지원하는 자료형이다.<corecrt_wstdio.h>에 정의되어있다. <stdio.h>에서 인클루드 된다. [본문으로]
  3. append 의 약자 [본문으로]
  4. 이 특성때문에 실제로 가장 많이 사용된다고 한다. [본문으로]
728x90
반응형

'외부활동 > TIPS 19th' 카테고리의 다른 글

[TIPS 19TH] 10 : 2018.07.26. (목)  (2) 2018.07.27
[TIPS 19TH] 09 : 2018.07.24. (월)  (2) 2018.07.24
[TIPS 19TH] 07 : 2018.07.16. (월)  (2) 2018.07.17
[TIPS 19TH] 06 : 2018.07.13. (목)  (2) 2018.07.13
[TIPS 19TH] 05 : 2018.07.09. (월)  (2) 2018.07.10
Comments