선행처리는 컴파일이전의 처리를 의미한다


#define PI 3.14 // 컴파일시 PI 를 만나면 3.14로 치환 

선행처리기에 의해서 처리되는 문장은 명령문의끝에 세미콜론을 붙이지 않는다

이때 , #difine을 지시자 , PI 를 매크로 , 3.14를 매크로 몸체라고한다. // 매크로를 매크로몸체로 치환! , 매크로는 대문자로 정의하는것이 일반적.


매크로 함수


#difine SQUARE(X) X*X 

매크로이름에 등장하는 괄호안의존재 X는 정해지지않은 임의의 값을 의미한다. 

선행처리기는 연산을 순서대로 처리하는것이 아니라 , 그 값을 바꾸는것이기 때문에 , SQUARE (3+2)는 3+2*3+2로 치환되어 11값이나타나게된다 따라서 , SQUARE(X) (X)*(X)로 치환해주는게 좋다. 이와같이 120/SQUARE(2) 또한 120/4 가아닌120/2*2로 치환되기때문에 , SQUARE((X)*(X))로 선언해줘야한다.

매크로 함수를 선언할때는 괄호를 자주 사용하는것이좋다.

매크로를 두줄에선언할때는 \ 문자를 이용해서 선언한다


#define SQUARE (X)   \

     ((X)*(X))


매크로 정의시 먼저 정의된 매크로도 사용 가능하다.

#define CIRCLE_AREA(R) ((SQUARE(R))*(PI))


매크로 함수의 장점


매크로함수는 일반 함수에 비해 실행속도가 빠르다

자료형에 따라서 별도로 함수를 정의하지않아도된다


매크로 함수의 단점


정의하기가 까다롭다

디버깅하기 쉽지않다.


매크로로 정의하면 좋은함수


작은크기의 함수

호출의 빈도수가 높은 함수


조건부 컴파일을 위한 매크로


#if... #endif : 참이라면


#define ADD 1

#if ADD // ADD가 참이라면 밑의코드 컴파일( ADD가 1로 참이므로 컴파일 )

 #endif // ADD가 거짓이라면 여기까지 코드를 컴파일에서 배제함.


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


#define ADD 0

#if ADD // ADD가 정의되었다면 밑의코드 컴파일 (ADD가 0으로 거짓이어도 컴파일)

#endif // ADD가 정의되지않았다면 여기까지 코드를 컴파일에서 배제함


#ifndef ... #endif : 정의되지않았다면


//define ADD 0

#if ADD // ADD가 정의되지않았다면 밑의코드 컴파일 (ADD가 0으로 거짓이어도 컴파일)

#endif // ADD가 정의되지않았다면 여기까지 코드를 컴파일에서 배제함


#else문의 삽입 , #elif문의 삽입


#define HIT_NUM 7


#if HIT_NUM == 5 //조건식 사용가능!

puts ("매크로 상수는 5입니다!");

#elif HIT_NUM == 6 

puts ("매크로 상수는 6입니다!");

#else 

puts("매크로상수는 5,6이 아닙니다!");

#endif


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


문자열 내에서는 매크로의 매개변수 치환이 발생하지않기때문에 , 문자열내의 매개변수를 치환하고싶다면 #연산자를 사용해야한다.

#연산자는 치환의 결과를 문자열로 구성하는 연산자이다.

#define STR(A,B) "#A의 직업은 #B입니다" // 11,12 입력시 "11의 직업은 12입니다"(11, 12도 문자열로변환)


문자열을 나란히 선언하면 하나의 문자열이 된다


char * str = "abc""def"; // str엔 "abcdef" 저장

char * str = STR(ABC) STR(DEF); //str에 "ABCEDF" 저장


필요한 형태대로 단순하게 결합하기 : ##연산자 ( 문자열로 치환하지않고 )


이연산자는 매크로함수의 전달인자를 다른대상 (전달인자 , 숫자 , 문자 , 문자열등 ) 과 이어줄때 사용한다,


#define CON(UPP , LOW) UPP ## 00 ## LOW

int num = CON(22.33) // num에는 220033이 저장된다




메모리 영역별로 저장되는 데이터 유형


코드영역 : 코드가 저장되는 공간. CPU는 코드영역에 저장된 명령문들을 하나씩 가져가서 실행한다.


데이터영역 : 전역변수와 static 으로 선언된 변수가 할당된다. 이 영역에 할당되는 변수들은 프로그램 시작과 동시에 메모리공간에 할당되어 프로그램 종료시까지 남아있게된다.


스택영역 : 지역변수와 매개변수가 할당된다. 이 영역에 할당되는 변수들은 선언된함수를 빠져나가면 소멸된다는 특징이있다.


힙 영역 : 프로그래머가 원하는 시점에 변수를 할당하고  , 원하는 시점에 변수를 소멸시킬수 있도록 지원하는 영역이다.


메모리의 동적할당


함수가 매번 호출될 때 마다 새롭게 할당되고 또 함수를 빠져나가도 유지가 되는 유형의 변수가 필요할때 , malloc과 free라는 이름의 함수를 통해서 힙 영역에 할당하고 소멸할 수 있다.


#include <stdlib.h>

void * malloc (size_t size); // 힙영역으로의 메모리공간 할당

void free (void * ptr) // 힙영역에 할당된 메모리 공간해제


malloc 함수는 성공시 할당된 메모리의 주소값을 반환하고, 실패시 NULL을 반환한다. // malloc 함수를 이용해 힙에 할당된 메모리공간은 포인터변수를 이용해서 접근하는 방법밖에 없다.

free함수를 사용해 메모리를 해제해주지않아도 프로그램 종료시 할당된 메모리는 해제되지만, 메모리값을 사용하지 않아도될때는 반드시 free함수를 이용하는 습관을 들이는것이 좋다.

malloc에는 크기정보만이 전달되기때문에 void * 자료형으로 반환하고 , void *자료형으로는 아무런 연산이 불가능하므로 할당할 메모리의 자료형을 따로 정해줘야한다.


ex)

int * ptr = (int*)malloc(sizeof(int));

double * ptr  = (double*)malloc(sizeof(double));

int * ptr = (int *)malloc(sizeof(int)*7); // 이때 sizeof(ptr)의 크기는 sizeof(int)*7이 아니다


malloc 함수는 메모리공간의 할당에 실패할 경우 NULL 을 반환하므로, 메모리 할당의 여부를 확인할때는


if(ptr == NULL)

{

// 메모리할당실패시 오류처리

}

이런식으로 확인 가능하다


calloc함수


#include <stdlib.h>

void * calloc(size_t elt_count,size_t elt_size); //성공시 할당된 메모리의 주소값, 실패시 NULL반환


malloc 함수가 size 만큼의 바이트를 메모리에 할당하는 함수라면 , calloc 함수는 elt_size 만큼의 블록 elt_count 개를 힙영역에 할당하는 함수이다. 둘은 결과적으로 완전히 동일하지만, malloc 함수는 할당된 메모리 공간을 별도의 값으로 초기화하지않아 쓰래기값으로 채워지지만, calloc 함수는 모든 비트를 0으로 초기화한다. calloc 함수를 해제할때도 free 함수를 사용하면 된다.


힙에 할당된 메모리공간 확장시 : realloc 함수


#include <stdlib.h>

void * realloc( void *ptr , size_t size ); // 성공시 새로 할당된 메모리의 주소값 , 실패시 NULL 반환

ptr이 가리키는 메모리의 크기를 size 크기로 조절한다

할당된 메모리를 확장하는건 힙영역에서만 가능하다.


ex)


int * arr = (int *)malloc(sizeof(int)*3); // 길이가 3인 int형 배열 선언

arr = realloc(arr,sizeof(int)*5); // arr을 길이가 5인 배열로 확장


이때 , arr배열뒤에 넉넉한 메모리가 남아있다면 arr의 주소값은 변하지않지만, 그렇지 않은경우 다른 주소값이 할당된다.


파일과 스트림


구현한 프로그램에서 데이터를 불러올때, 프로그램과 참조할 데이터 사이의 데이터가 이동할 수 있는 다리를 스트림이라한다.


fopen 함수호출을 통한 스트림형성과 FILE 구조체


#include<stdio.h>

FILE * fopen (const char * filename, const char * mode); // 성공시 해당파일의 FILE 구조체 변수의 주소값 , 실패시 NULL포인터 반환


FILE 구조체의 포인터는 파일을 가리키기 위한 용도로 사용된다. 즉, 이 포인터를 이용해서 파일에 데이터를 저장하거나 파일에 저장된 데이터를 읽게된다.


입력스트림과 출력스트림의 형석


FILE * fp1 = fopen ("data.txt","wt"); // 출력스트림, 파일 data.txt와 스트림을 형성하되 , wt 모드로 스트림을 형성

FILE * fp2 = fopen ("data.txt","rt"); // 입력스트림, 파일 data.txt와 스트림을 형성하되 , rt 모드로 스트림을 형성

FILE * fp3 = fopen ("C:\\Project\\data.txt","wt"); // 출력파일이 생성될 위치를 지정할 수 있다.


if(fp1=NULL) // 파일오픈이 정상적으로 됐는지 검사

{

printf("파일오픈실패!\n"); 

return -1; // 비정상적인종료일때 -1값반환

fputc('A',fp1); // 출력스트림 fp1을통해 data.txt에 A 문자 출력

int ch = fgetc(fp2); // 입력스트림 fp2를통해 data.txt로부터 A값을 불러옴


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


#include <stdio.h>

int fclose(FILE * stream); // 성공시 0 , 실패시 EOF 반환


fclose함수의 호출을통해 개방되었던 파일을 닫아줘야하는 이유


운영체제가 할당한 자원의 반환

버퍼링 되었던 데이터의 출력 // 운영체제가 정해놓은 버퍼링방식에따라 파일저장하는때가 다르기때문에 , fclose함수로 데이터를 출력해주는게좋다.


스트림을 종료하지않고 버퍼를 비울때는 fflush 함수


#include<stdio.h>

int fflush( FILE * stream); // 함수호출 성공시 0 , 실패시 EOF 반환


파일의 개방모드


스트림을 구분하는 기준1 : 읽기위한스트림, 쓰기위한스트림


모드 r : 읽기가능 , 파일없을시 에러출력

모드 w : 쓰기가능 , 파일없을시 파일생성

모드 a : 파일의끝에 덧붙여쓰기가능 , 파일없을시 파일생성

모드 r+ : 읽기/쓰기가능 , 파일없을시 에러출력

모드 w+ : 읽기/쓰기가능 , 파일없을시 파일생성

모드 a+ : 읽기/덧붙여쓰기가능 , 파일없을시 파일생성

//웬만하면 r , w , a 를 사용하여 입력스트림과 출력스트림을 따로 생성해 사용하는것이 좋다.


스트림을 구분하는 기준2 : 텍스트모드와 바이너리모드


사람이 인식할수 있는 문자를 담고있는 파일을 가리켜 텍스트파일이라고 하며 , 그 이외에 컴퓨터가 인식할 수 있는 데이터를 파일을가리켜 바이너리파일이라고 한다. 이때 , 개행( \n)이 C언어에서만의 개행표시인데, 이것을 텍스트모드로 개방할때는 , 개행을 각 환경에 맞는 개행표시로 변환해준다. 바이너리모드는 개행을 변환하지않는다.


텍스트 모드


rt , wt , at , r+t , w+t , a+t 


바이너리모드


rb , wb , ab , r+b , w+b , a+b


파일 입출력함수


int fputc(int c,FILE * stream); // 문자 출력

int fgetc(FILE * stream); // 문자입력

int fputs(const char * s , FILE * stream ); // 문자열 출력

char * fgets(char * s , int n , FILE * steam); // 문자열 입력


ex)

char str[30];

FILE * fp = fopen("simple.txt","wt");

if(fp == NULL) // fopen 함수는 파일오픈실패시 NULL반환

{

puts("파일오픈실패!\n");

return -1;

}

fputs("MY NAME IS HONG\n",fp);  //문자열을 simple.txt 파일에 출력할때 , 개행키워드(\n)가 포함되었기때문에 txt파일로 파일을 개방해야한다. 또한, 파일에 문자열을 출력할때는 널문자가 포함되지않기때문에 , 개행키워드가(\n) 문자열을 구분하는 기준이된다.

fgets(str,sizeof(str),fp);

printf("%s",str); // MY NAME IS HONG\n 출력


파일의 끝을 확인하는 함수 : feof


#include <stdio.h>

int feof(FILE *stream); // 파일의 끝에 도달한경우 0이 아닌값 반환


ex)


FILE * src  = fopen("src.txt","rt");

FILE * des = fopen("des.txt","wt");


while ( fgets(str,sizeof(str),src)!=NULL)//fgets 함수는 읽을 데이터가 없을때 NULL을 반환한다

//or while(fgetc(n,src) != EOF) // fgetc함수는 읽을데이터가없을때 EOF반환

{

fputs(str,des);

}


if(feof != 0)

printf("파일복사성공!\n");

else

puts("파일복사실패!\n");

fclose(src);

fclose(des);


바이너리 데이터의 입출력 : fread , fwrite


#include<stdio.h>

size_t fread(void * buffer,size_t size , size_count,FILE* stream);// 성공시 전달인자 count, 실패 또는 끝 도달시 count보다 작은값 반환


ex)

int buf[12];

fread((void *)buf,sizeof(int),12,fp);//sizeof(int) 크기의 데이터 12개를 fp로부터 읽어들여서 buf 에 저장하라


#include<stdio.h>

size_t fwrite(const void * buffer,size_t size ,size_t count , FILE * stream);//성공시 전달인자 count  ,실패시 count 보다 작은 값 반환


ex)

int buf[7]={1,2,3,4,5,6,7};

fwrite((void*)buf,sizeof(int),sizeof(buf)/sizeof(int),fp); // sizeof(int)크기의 데이터 sizeof(buf)/sizeof(int)개를 fp로 출력하라


텍스트 데이터와 바이너리 데이터 동시에 입출력하기


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


fprintf의 사용법

ex)

char name[10]="홍길동";

char sex = 'M';

int age =24;

fprintf(fp,"%s %c %d",name,sex,age); // fp가 첫번째 전달인자 , fprintf 함수를 통해 "홍길동 M 24" 라는 문자열이 만들어지고 , 이렇게 만들어진 문자열이 첫번째 전달인자가 가리키는 파일에 저장된다. 즉 , 텍스트데이터와 바이너리데이터를 하나의 문자열로 묶어서 저장하는셈이다.

scanf("%s %c %d",name,&sex,&age);

getchar(); // scanf함수는 \n을 읽어들이지않고 입력버퍼에 남겨두기때문에 getchar();함수를 이용해 \n을 소멸시켜줘야한다

fprintf(fp,"%s %c %d",name,sex,age);

fclose(fp);// fp스트림 소멸


fscanf의 사용법 

fscanf 함수는 파일의 끝에 도달하거나 오류가 발생하면 EOF 를 반환한다


ex)

int ret;

char name[10];

char sex;

int age;

FILE * fp =fopen("friend.txt","rt");

while(1)

{

ret = fscanf(fp,"%s %c %d",name,&sex,&age); 

if(ret ==EOF) // ret 값이 EOF라면 파일의 끝 도달 혹은 오류발생

break;

printf("%s %c %d",name,sex,age);

}

fclose (fp);


텍스트와 바이너리 데이터의 집합체인 구조체변수의 출력


구조체변수를 하나의 바이너리데이터로 인식하고 처리한다 // fread 함수 혹은 fwrite 함수를 이용한다

ex)

Friend myfriend1;

Friend myfriend2;

FILE * fp;

fp=fopen("friend.bin","wb");

...

fwrite((void*)&friend1,sizeof(myfriend1),1,fp); // myfriend1에서 sizeof(myfriend1)만큼의 데이터를 1번 fp 스트림을통해 출력

fclose(fp)

fp=fopen("friend.bin","rb");

fread((void *)&myfriend2,sizeof(myfriend2),1,fp); // myfriend2 에 sizeof(myfriend2 ) 만큼의 데이터를 1번 fp 스트림을 통해 입력


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


FILE 구조체의 멤버중 파일의 위치정보를 저장하고있는 멤버가있는데, 이 멤버의 값은 fgets,fputs,fread,fwrite와 같은 함수가 호출 될 때마다 참조 및 갱신되며, 이 멤버가 가리키는 위치를 시작으로 문자열을 읽어들이게된다. 이 멤버를 가리켜 '파일위치 지시자'라고 부른다. 파일위치 지시자는 처음 파일이 개방되면 무조건 파일의 맨 앞부분을 가르킨다.


파일위치 지시자의 이동 : fseek


#include<stdio.h>

int fseek(FILE * stream, long offset, int wherefrom); // 성공시 0 , 실패시 0 이아닌값을 반환

// stream 으로 전달된 파일위치지시자를 wehrefrom에서부터 offset 바이트만큼 이동시켜라

매개변수 wherefrome이 

SEEK_SET 이라면 파일위치지시자는 파일 맨앞에서부터 이동을시작

SEEK_CUR이라면 파일위치지시자는 현재위치에서부터 이동을 시작

SEEK_END이라면 파일위치지시자는 파일 맨뒤에서부터 이동을시작 // 이때 , 파일의 끝 (EOF)에서부터 이동을시작

//offset에는 양의 정수뿐만아니라 음의정수도 전달될 수 있다. (음의정수 전달시 앞으로 , 양의정수전달시 뒤로 이동)


현재 파일위치지시자의 위치 : ftell


#include< stdio.h>

long ftell(FILE * stream); //파일위치지시자의 위치정보 반환



+ Recent posts