Dia Egg - Shugo Chara

C

C언어 간단 정리

별ㅇI 2023. 8. 19. 01:18
728x90
반응형

#주의: 이 게시물의 개인적인 시선에서 주목해야할 부분을 정리한 게시물로 빠진 개념이나 부족한 설명이 있을 수 있으니 참고용으로만 읽어주시고 처음 c를 배우시는 분은 동영상강의나 책을 이용하시는 것을 추천드립니다!

#코드블럭에 c언어가 없어 c++로 대신 번역되었으며 글을 쓰고 있는 제가 항상 수정, 첨가 하고 있는 게시물입니다. ㅠ..ㅜ

C언어 간단정리

# 1 – 프로그래밍과 C언어 

운영체제를 다루기위해 C언어가 생겼다. C언어가 다른 언어와 구분되는 특징은 대표적으로 하드웨어제어가 용이하고 호환성, 이식성이 좋다는 것이다.

우리가 배우는 것은 여러 C의 버전에서 C99이다.

 

소스코드란?

소스코드(source code)는 아스키값으로 저장된 text file이다. 간단히 말하자면 0과 1이라는 2진수로 이루어진 text file을 아스키코드(ASCII)라는 컴퓨터 약속에 따라 변환한 것이다. (1byte = 8bit)

# 2.1 – C프로그램의 기본 형태와 데이터 출력 방법

C프로그램의 기본 형태는 아래와 같다.

int main(void) //머리(메인 함수)
{
	int a = 10; //문장의 끝에는 세미콜론(;)
    int b = 20; //변수 선언
    int sum; //함수의 안은 들여쓰기
    sum = a+b; //한 줄 주석
    /* 나는 여러문을 주석처리
    하는 방법입니다 */
    printf("%d\n", &sum);
    return 0; //정상이면 0, 비정상이면 1 반환
}

main함수는하나인데 그 이유는 하드디스크에서 파일을 불러와 실행파일을 시작 시킬때 사용자가 작성한 것과는 별개로 파일의 맨 앞에는  startup code라는 것이 있어 컴파일러가 운영체제에 없는 것을 붙여넣고 다음으로  main함수를 자동으로 먼저 실행 할 수 있게 함으로서 순서를 정리해두었기떄문이다. 고로 프로그램에 혼란이 생기지 않기위해 main은 하나만 지정해둔다.  참고로 이는 cpu의 제어권이동이며 main 함수로 들어가 코드를 수행하고나면 cpu의 제어권은 return으로 startup code로 돌아가게 된다.

 

[자주쓰이는 표현들]

printf : 출력함수, 자동 줄 바꿈이 없으므로 줄을 바꾸길 원한다면 \n으로 줄바꿈을 따로 수행해주어야한다.

\n : 줄바꿈

\r : 현재 커서를 그 줄의 맨 앞으로 (파일 수정에 우용)

\t : 현재 커서를 한 칸 뒤로 (파일 수정에 우용)

\a: 소리를 내줌

%d : 10진수 정수를 받는 형태

%lf : 소수를 받는 형태 (%5.1lf처럼 활용가능)


# 2.2 – 상수와 데이터 표현 방법

상수란?

상수는 프로그램 실행 중에 변하지않는 값을 말하는 데 정수와 실수가 있다.

 

정수

2진수, 8진수, 10진수, 16진수등이 있으며 기본적으로는 int형(4byte, %d), short형(2byte, %d), long형(4byte, %lf), long long형(8byte, %lld)이 있다.

특이한 점은 기본적으로 int a;를 하는 것은 자동으로  signed 로 선언되는데 이는 음수, 양수를 다 넣을 수 있는 변수이다. 그럼 이것을 어떻게 구분할까 바로 MSB라는 가장 앞의 비트를 사용하여 구분한다.(1이면 음수, 양수와 음수의 변환은 절댓값을  모든 비트를 보수화한 값에 비트연산으로 1을 더하면 된다. 양수와 음수를 더하면 0이 되기때문이다. ) 따라서 넣을 수 있는 수의 절댓값이 반으로 줄어들기때문에 (signed는 -128~127, unsigned는 0~255까지 )음수를 쓰지않는다면 따로 unsigned int a; 처럼 지정해주는 것도 방법이다. (unsigned 는 %u로 보통 받는다.)

문자열변수라는 것도 있다.

char a = 'A'; 와 같이 선언한다(1byte,%c) 문자는 아스키코드값 정수또는 문자로 읽을 수 있다.(65또는 A)

 

실수

실수는 float(4byte, %f), double(8byte, %lf),long double((대체적)8byte, %Lf) 등이 있으며 정수처럼 MSB를 부호값으로가지고 나머지공간에 지수정보값(2의 몇 승)과 유효숫자를 저장한다.

 

(만약 6.5를 double형에 저장한다면 6.5는 양수이므로 첫번째 비트에 0이 들어가고(1bit) 6.5를 이진수로 바꾸면 (0.5는 1/2이므로 2의-1이니 .1로 바꿔진다.) 110.1이므로 지수정보에는 1.101*(2**2) 들어간다(8bit) 그리고 남은 23 bit에는 1101이라는 유효숫자가 들어간다. )

 

이때 무한 소수를 넣을경우 이 유효숫자가 메모리를 넘어버리므로 데이터이상이 올 수 있다. 이가 소수를 정수형에 넣었을떄 생기는 문제점이기도 하다. (그래서 부호를 고려했을때 보통 float는 총 7자리정도, double은 총 15자리정도 들어간다고 생각하면 된다.)

 

(참고로 그럼 0.4는 유효숫자 어케구함? 이라고 생각하는 사람도 있을텐데 0.4에 2를 곱해서 다음자리로 올라가면 1을 기록 아니면 0을 기록하는 것을 반복하여 구한다)

/추가로 
float ft = 3.4; /선언을 해버리면
/* 3.4 == 실수형 상수 == 8byte 로 갈 수 있는데 float는 4byte기에*/
ft = 3.4f /로 4byte처리를 해준다고 한다.

//5칸으로 소수 2번째자리까지 출력하고자한다면
printf("%5.2lf",ft); //처럼 사용하면 된다. 나머지 소수점은 반올림해준다.

 

문자열?

char str[20] = "apple"; //초기화가 가능하다. 끝에 NULL문자(\0)는 자동으로 붙는다.
printf("%s\n", str);
str = "banana" //(X)이것은 안된다. 문자열의 이름은 시작주소 그 자체이기 때문이다. 
strcpy(str,"banana"; //(O)주소값을 하나하나 찾아가 붙여넣기 해주는 이 함수를 쓰면 변경이 가능하다.

 

const?
const를 변수호출 앞에 붙이면 초기화 이후에는 변경이 불가능하도록 할 수 있다. (상수)


# 3 – 데이터 입력

scanf("%d %lf",&age, &height)

1. 스페이스가 구분문자역할

2. tab도 가능 (하나의 아스키코드값)

3. enter도 구분가능 (white space 들)

 

char name[20];
scanf("%s",name); /자동으로 문자열로 읽어줌

 

# 4.1 – 산술, 관계, 논리 연산자

(10+20)같이 피연산자가 2개 필요한건 이항 연산자,(-a)같이 하나가 필요한 걸 단항 연산자로 부른다. 단항연산자의 순서가 이항연산자의 순서보다 높다.

 산술 연산자 : +,-,*,/(몫, 실수형태일 경우는 제대로 구함),%(나머지연산자),++,--

컴파일형변환: (int) int형 변환, (double) double형 변환, (int *) int 포인터형 변환, (char) char형 변환

int a = 10; /*일 경우 
a/3을 수행하는 경우 3이 계산된다
그러나 a/3.0을 수행하는 경우 a를 꺼내와서 
컴파일러가 연산 직전에 double형으로 바꿔줌(자동 형변환)*/

계산시 컴파일형 변환, 형변환시 수 변

int grade = 100, empl = 90;
double res1,res2;
res1 = (empl/grade)*100.0 //이때 empl/grade가 0이므로
res2 = ((double)empl/grade)*100.0 //이렇게 하나만 해줘도 정성적으로 기능한다.
res2 = (int)res1 //이건 더 작은 형에 억지로 넣는거라 자동형변환 오류가 발생할 수 있다.

 

또 ++,--의 경우, 전위표기, 후위표시에 따라 순서가 달라질 수 있으니 조심하여 사용하여야 한다.

예를 들어

b = a++*3 //일때와
b = ++a*3 // 은 a의 값은 같으나 b이 값이 다를 수 있다.

 

논리연산자 : >, >=, <, <=, ==, != (이떄 0은 거짓, 나머지 모든 수는 참을 의미하는데 보통은 1을 대표적으로 쓴다.)

파이썬처럼 연속된 구조 (10>a>20)을 못쓰기떄문에 &&이나 ||로 따로 연결해주어야한다. 만약 그렇지 않으면 왼쪽에서 부터 읽어들이기떄문에, 10>a,10>20만 보고 참을 반환할 수도 있다. 연결해 줄때도 주의점이 필요한데, 예를 들자면

if (a<0)&&(++b>0)
/*일때 앞부분이(a<0)거짓일 경우 뒷문장은 아예 수행되지않는다.
이러한 배열의 차이가 코드를 바꿀 수 있기떄문에 조심해야한다.(||도 마찬가지)
이것을 short circit rule일고 한다.*/

 

변수는 논리연산자의 왼쪽에 있는가 오른쪽에 있는가에 따라 l-value(공간), r-value(값)으로 쓰인다. 

예를 들어

int main(void)
{
	int a = 10;
    int b;
    b = a*2 //일때 a와 b는 모두 int형 변수이지만 이 문장에서 b는 공간으로쓰이고 a는 10이라는 값으로 쓰인다.이는 컴파일러가 처리해주는 영역이다.
}

 

 

# 4.2 – 비트 연산자와 그 외의 멋진 연산자

sizeof 연산자 

int a = 10;
double b = 3.4;
print("%d",sizeof a) //4
//sizeof 3.4 도 가능하고 sizeof(3.5+10)도 가능

 

그 외에 복합대입연산자(+=), 그리고 ,연산자, 조건 연산자, 비트 연산자 등이 있다.

조건연산자사용예시

int main(void){
	int a = 10, b=20;
    int max;
    max = (a>b) ? a:b; //max == b
    (a>b) ? (max = a):(max = b);
    (a>b) ? (printf("%d",a):printf("%d",b)) //함수도 사용가능한데, 단 반환값이 있는 경우만 가능
}

비트 연산자

비트연산자는 논리연산자(&(둘다 참일 경우 참), |(하나이상 참일경우 참), ~(XOR연산으로 서로 다른값일경우 참), ^(부정))와 이동연산자(<<,>>)로 나뉜다.  <<=,&= 처럼 복합대입으로도 사용가능하다. 이동 연산이 복잡하게 생각될 수 있는데 <<은 왼쪽으로 비트를 하나씩 옮기고 가장 오른쪽에 0을 넣는것(결과적으로 2를 곱한 값이됨), >>은 오른쪽으로 비트를 하나씩 옮기고 가장 왼쪽에 0을 (꼭 0은 아니고 signed bit를 집어넣는다. unsigned인 경우 무조건 0으로 채운다)을 넣는다(결과적으로 2로 나눈 결과 됨) 


# 5. if문 활용과 switch문 ~ case문, while, for, do~while

제어문은 선택문, 반복문, 분기문으로 나뉜다. 

선택문 : if elseif else, switch~case //if애서 주의해야 할 점은 else가 다른 중첩 if와 연결될 수 있으므로 중괄호를 잘치자.

반복문 : while, for, do while

분기문 : break, continue, goto, return

 

switch ~case예시 /제어문 break/continue

int main(void){
	int rank = 3;
    int m;
    switch(rank)//rank의 상수같과 같은 case를 수행한다.
    ...
    case 1: m =300; break;//break를 안하면 해당위치부터 아래를 모두 수행 하기 떄문에 이 코드에서는 m==50이 됨
    case 2: m =200; break;//brek는 가장 가까운 반복문을 하나만 빠져나온다.
    case 3: m =100; break;
    defalt: m =50; break;//어디있든 상관은 없다. 있어도 없어도 가능
    //continue는 stop이 아니라 반복문제어의 끝으로 돌아간다. 
}

for 문 예시

int main(void){
	int i; //
    for(i = 0; i<3; i++){ //1.초기식, 2.5.조건식, 4.증감식
    	printf("%d",i); //3.함수 앞의 순서대로 수행하므로 3번 수행된다.
    }
}

while문 예시

int main(void){
	int i =0;
    while(i<5){
    	printf("%d",i);
        i++
    }
}

do while문

int main(void){
	int i =0;
    do//첫번째는 조건에 상관없이 무조건 들어간다.
    {
    	pritnf("%d",i)
    	i++
    }while(i<5);
    /* 아래처럼도 사용가능  
    do{printf("%d",i),i++}while(i<5)
    */
}

무한반복처리

for(;;)
while(1)

위 예시에서 나는 i값을 변동시켰는데 왠만하면 예측이 어려우니 건들지말고 다른 변수를 건들도록 하자

# 7.1 – 함수의 작성과 사용

함수정의방법

int sum(int a, int b); //함수를 아예 메인함수 위에서 선언하거나 이렇게 헤드르 선언해줘야 찾아쓸 수가 있다.

int main(void){
	int a = 10;
    int b = 20;
    int result = sum(a,b);
    return 0;
}

int sum(int a, int b){//반환형, 매게변수의 형들을 선언해준다. 
	int res;
    res = a+b;
    return res; //함수는 전체적으로 호출된 임시공간에서 수행되므로 그 같을 반환하고 따로 변수에 저장해야 main함수에서 사용할 수 있다.
}


# 7.2 – 여러가지 함수 유형

재귀호출 함수

자기 스스로를 호출하느 함수로, 함수에 할당된 공간은 유한하므로 꼭 종료조건을 설정해주어야한다.

새로운 함수가 호출되는 형태라는 면에서 반복문과는 다르다.

void fruit(int cnt)
{
	printf("apple");
    if(cnt == 3) return 0; //return을 하면 그 전 함수로 간다. 
    fruit(cnt+1)
}

# 8.1 – 배열의 선언과 사용

배열은 연속적인 크기의 변수를 선언하는 것이다.

int grade[5]; //int변수를 5개 가지는 1차원배열이 선언
grade[0] =10; // 으로 접근해서 집어넣을 수 있다.
int grade2[5]={1,2,3,4,5}//또는 
int array[] ={1,2,3} //처럼 초기화 가능하다 후자는 중괄호 안을보고 할당.
int grade3[5] ={1,2,3}//만 하면 나머지는 자동으로 0으로 채워진다.

배열의 크기

int size = sizeof(array)/sizeof(array[0]) //으로 계산가능하다.


# 8.2 – 문자를 저장하는 배열

문자열은 문자를 저장하는 배열이다.

char ch[20] = {'s','t','a','r'}; //(나머지는 자동으로 0으로 채워져서 NULL역할 가능)또는
char ch2[20] = {"star"}; //또는 
char ch3[20];

scanf("%s",ch3); 
/*로 선언가능하며 마지막에는 무조건 NULL문자가 붙고 대신 공백문자를 구분하지 못한다.
공백문자를 쓰고 싶다면 gets함수를 통해 아래처럼 가능하다, 엔터도 해준다*/
gets(ch3)

/*while(ch[i]!='\0')이라는 조건으로 printf함수로 출력도 가능하다.
NULL값은 아스키코드 0값으로 \n으로 표기한다. 또는 한줄로 puts함수를 사용해 아래처럼 나타낼 수 있다
이를 사용하면 한번에 다 출력하고 한줄을 바꿔줄 수 있다.*/
puts(ch3);
//다만
char str[20];
str = "star";
//의 형태는 불가능하다 str은 주소값에 가깝기 때문이다. 따라서 하나씩 찾아가서 NULL이 아닐떄까지 복붙해주는

strcpy(str,"star"); //형태로 해주어야한다.복사하는 쪽에는 str같은 배열명도 가능하다.
//#include <string.h>가 필요하다.


# 9.1 – 포인터의 기본 개념무료

포인터는 주소를 담는 변수로 어떤 형의 주소를 담냐에 따라 다르게 선언을 해준다. (%p를 하면 16진수 대문자로 찍는다.)

(int* , double* ...) 연산자참조는 가능하지만 계속 그 결과를 알아야한다면 포인터를 선언해주는 것이 메모리 낭비가 적다.

 

배열의 이름에는 두가지 기능이 있는데, 첫번째기능은 배열의 시작주소상수라는 것인데 이 또한 주소연산자(&)를 변수의 주소를 알아내는 방법을 통해 알수 있다. 포인트선언을 하면 4byte를 메모리에 할당해준다. 주소는 상수(배열이름도), 포인터는 변수이다.

int *fp = &a

printf("%d",p) //100
printf("%d",*p) //10

*는 간접참조연산자인데, 배열명이 직접참조하는 경우이다. *는 간단하게 말하면 주소라는 상자를 열어 변수를 뽑아내는 열쇠라고 볼 수 있다.

*p = 20; //이렇게 직접적으로 값을 바꿀 수 있다. 
/*
&a == p == 100
a == *&a == *p == 10
*p는 변수이므로 대입연산자 왼쪽에 쓰면 l-value, 오른쪽에 쓰면 r-value로 취급된다.
scanf("%d", &a); //a에 저장
scanf("%d", p); //p가 가리키는 주소에 넘어감(==a)
scanf("%d", &p); //p라는 박스에 저장
scanf("%d", *p);//이건 틀린 표현인데, *p라는 상수값을 주소로 넘겨주기때문에 쓰레기값 덮어쓰기발생

const를 포인트에 사용시 

const int *p = &a
p = &b; //로 p가 가리키는 값을 바꾸는 것은 가능하지만(화살표를 바꾼것)
*p = 10; //가 불가능하도록 한다. (간접참조)(상자안의 값을 바꾼것)
b = 10; //b는 condt변수가 아니니까 바꿀 수 있음
//1번도 불가능하게 하고 싶다면 
const int *const p = &a //로 포인터가 상수화하여 가능하다

포인터에서 포인터와 포인터 사이의 대입은 가능하지만 가리키는 형의 크가 다르면 해석방법에 의해 선언된 공간이외의 공간이 침범될 수 있다.(정수해석:2진수,실수해석:IEEE표준에 따름)

또한 포인터를 초기화하지않는 경우 어떤 주소를 가져온지 모르기떄문에 굉장히 위험하다. 반드시 초기화하거나 적어도 NULL로 지정해주어야 한다.

 

포인터사용예시

return은 한가지 변수만 할 수 있는데 두가지 값을 교환시키는 경우 복사값이기떄문에 값을 외부함수에서 모두 얻기 힘들다. (a라는 이름은 호출된 블록 안에서만 사용할 수 있다. 마치 한집에는 중복되는 이름을 가진 사람이 없는 것처럼) 따라서 포인터로 주소값을 매게변수로 보내고 교환해 그 값을 바구면 return 을 신경쓰지않고 교환이 가능하다

swap(&a,&b);

void swap(int *pa, int *pb){ //포인터의 형
	int tmp; //포인터가 가리키는 값의 변경이므로 int형 사용
    tmp = *pa; //포인터가 가리키는 값
    *pa = *pb; 
    *pb = tmp;
}

# 10.1 – 배열과 포인터의 관계

배열의 이름은 첫번째요소의 주소를 가리킨다. 그렇다면 int형 배열의 두번쨰 주소는 (a+4) 일까? 아니다! (a+1)로 하면 컴파일러가 형을 읽어 계산해준다. 간점참조는 *(a+1)로 하면된다. 간접참조값으로 연산도 가능하다. 간접참조한 값은 변수이므로 l- value, r-value로 모두 사용될 수 있다. 배열명을 함수로 주고 포인터로 받아 print함수등을 만들수 있다. 대신 sizeof(a)도 같이 넘겨줘야한다. 함수로 넘어가서 구하면 포인터뱐수의 크기 4byte만 뜨기 떄문이다. 

int *p = a;
//를 할 경우 배열의 나머지 뒤 칸에는 관심이 없고 p는 a[0]만 바라본다.
//그러나 뒤에 주소연산은 가능

배열명을 저장한 포인터는 배열명으로 사용가능하다! 이게 굉장히 중요함. 하지만 p는 변수값, a는 상수값을 가지고 있는 사실을 잊지말기!(p는 ++,p=200모두 가능, a는 모두 불가) 

 

부분배열로 활용

int *p = a+2; 식을 부분배열로 활용할 수 있다.

 

배열순서 연산

q(116) - p(112) == 4/4 == 1 (양수가 나왔다는 것은 q가 p보다 뒤에 있다는 것을 알 수 있다.)

포인터가 배열의 어느부분을 가리키는 지 알 수 있다.

if(p>q)와 같이도 활용가능

 

포인터 연산

*p

sizeof(p)

&p

p++, p--

p+1, p-1

>,<,>=,<=

# 11 – 아스키모드값과 문자입출력 함수, 버퍼를 사용하는 입력 함수

아스키코드는 0~128까지 있다. 따라서 1byte 중 signed bit를 제외하고 7bit안에 표현이 가능하다.

따라서 'a'는 문자리터럴이라고 숫자이자 문자로 사용되기때문에 sizeof('a')를 하면 4byte가 나온다. 하지만 바이너리 값으로 4byte중 앞의 3byte가 0으로 차있기때문에 char형에 넣어도 문제가 없다. 

 

'a' - 'A' = space ASCII

ch -('a'-'A') 를 하면 대문자가 됨.

32는 2**5인데 이건 비트의 왼쪽에서 6번째 자리라고 읽을 수 있으므로 이게 0면 대문자, 1이면 소문자가 된다. 

 

문자전용출력함수

putchar(ch);
// 이렇게하면 찍어줌
int ch;
ch = getchar();
//반환값이 키보드로 아스키코드반환.
//ctrl+z를 하면 -1을 반환해서 반환형을 int로 씀
printf("%c",ch);

//문자열을 입력받고 한줄로 출력하는 반복문
while(ch!=\n){
	putchar(ch); //한문자만
    ch = getchar();
}

문자와 숫자가 다른 점은 white space로 구분이 되지않는다는 점이다.(white space도 아스키문자라서)

따라서 구분하고 싶다면 "%c %c"로 처음부터 스페이스처리해서 불러주어야한다.

참고로 엔터같은 문자가 아스키코드값으로 입력되어 들어가면 %c 출력시 기능이 수행된다.

 

scanf에 대하여

scanf는 버퍼에서 문자를 하나씩 가져오는 구조이다. tiger를 입력해도 scanf를 3번만 돌리면 tig만 가져오고 er\n은 버퍼에 남아닜다는 뜻이다. 버퍼란 운영체제가 메모리 별도공간에 할당하는 char배열형태인데, 읽은 문자의 수를 반환하며 ctrl+z인 경우 -1을 반환하고 입력중지를 한다. 이 걸 이용하여 -1이 아닌경우 계속 출력과 입력을 하는 함수를 만들수 있다.

res = scanf("%c",&ch);
while(res != -1){
	printf("%c",res);
    res = scanf("%c",&res);
}

한줄을 다 읽는 방법

char ch;
scanf("%c",&ch);
while(ch!='\n'){
	printf("%c",ch);
    scanf("%c",&ch);
}

그럼 숫자는 어떻게 읽느냐?

버퍼에 10이 문자열로 저장된 후 scanf("%d",&ch)로 읽어들이면  scanf문에서 어떤식으로 읽을지 형을 확인하여 \n전까지 읽어들인다. 위 문장에서는 부호를 고려한 10진수 정수형태로 읽는다(이때 \n도 정수이다. )

주의할 부분

int num;
char grade;
scanf("%d",&num);
scanf("%c", &grade);
//이런 경우 grand에 enter가 들어갈 수 있다. 
//따라서 scanf(" %c",&grade);로 처리하거나 스페이스자리에 \t,\n로 whilt space를 무시하라는 명령을 내어 해결 할 수 있다. 
//혹은 fflush(stdin)로 대행문자가 남아 문제가 생길수 있는 버퍼를 한번에 지울 수 있다. stdin은 버퍼의 이름
//white space까지 포함된 문장을 받고 싶다면 gets(str)형태를 사용
gets(str) //대행문자까지 넣어주고 자동으로 줄이 바뀜(엔터까지 들어가서)
fgets(str,sizeof(str),stdin);//크기 -1까지만 (+\0)
str[strlen(str)-1] = '\0'//null로 바꿔서 줄이 안바뀌도록 함.
puts(str) //배열의 모든것 출력 (자동으로 줄바뀜


# 12.1 – 문자열과 포인터

문자열 상수 구분 방법 

int main(void){
	printf("%s","apple"); /*문자열은 따로 저장해두고 그 주소값이 들어감
    *"apple" == a
    *("apple"+3) == l
    "apple"[3] == l
    */
}
char *cp = "apple";
while(*cp != '\0'){
	printf("%s\n",cp);
    cp++// apple,pple,ple이렇게 출력됨
}
int main(void){
	char *dessert = "apple";
    printf("%s\n", dessert);
    dessert = "banana"; //이때 banana의 주소값이 들어감
    printf("%s\n",dessert)
}

# 12.2 – 문자열 연산 함수

//"apple" + "pie" 는 주소+주소 연산이므로 성립되지않는다. 따라서 #include <string.h>하고
strcpy(str1,"apple");//복사받은 곳의 위치(공간이 꼭 필요,포인터), 복사할 곳의 위치(포인터도 가능)
//strcpy는 뒤의 문자를 하나씩 복붙하여 \0을 넣고 복사 끝내줌, 
//복사한 배열의 첫번째 주소를 반환한다. 

//strcpy함수 구현해보자
my_strcpy(str,"apple");

void my_strcpy(char *dp,char *sp){
	while(*sp != '\0'){
    	*dp = *sp;
        dp++;
        sp++;
    }
    *dp = '\0'; //중요한 포인트!
}

//strncpy함수는 마지막에 null문자없이 바꿔만 주는 함수이다. 일부교체시 좋다. (mango tree ->apple tree)
strncpy(str,"apple-pie",5);


//strcat함수는 널문자 위치부터 붙여넣기함. 이어쓰기시 좋다.
strcat(str, "pie");
//strcat함수를 만들어보자 
void my_strcat(char *dp, char *sp){
	while(dp!='\0'){
    	dp++;
    }
    while(sp!='\0'){
    	*dp = *sp;
        dp++;
        sp++;
    }
    *dp = '\0';
}
//strcat을 암것도 없을 때 쓰고싶다면 char str[20] = "" or {\0} or {0} or '\0'으로 초기화 필요

//strncat함수는 일부만 붙이고 싶을때 사용한다.
strncat(str, "plece", 3); //ple\0을 붙여줌

//strlen함수는 배열에 들어간 문자열 길이를 반환
str[20] = "apple";
sizeof(str) // 20(배열전체 크기)
strlen(str) // 5(null문자 포함안함)

//strcmp함수는 사전순서를 결정할때 사용한다. 앞에서부터 아스키코드값을 비교하는 원리이다.
strcmp(str1,str2); /*
str1>str2이면 1반환
str1<str2이면 -1반환
str1==str2이면 0반환 
이를 함수로 만든다면 \0으로 같아 \0이 나오는 것도 고려해야함.
대문자 소문자의 경우도 고려 필요*/

//strncmp함수는 일부분만 비교하는 함수이다.
strncmp(str1,str2,3);


# 13.1 – 변수 사용 영역

지역변수

지역변수는 함수 내에서만 사용가능하다.(main포함 블럭내부) 함수호출 후 활용하고 반환할 떄 사라진다. 자동초기화아님

(auto)int a;
int main(void){
	{//이런식으로 temp라는 블록안 블록에서만 사용가능한 변수를 선언할 수도 있다.
    //이때 밖의 변수와 같은 이름도 사용가능하지만 나갈때 사라진다.
    	int temp;
        temp = a;
        a = b;
        b = temp;
    }
}

 

전역변수

전역변수는 0으로 초기화 되는데, 함수 바깥에 선언되어 프로그램시작 시 활용되고 프로그램이 끝나면 반환된다.

int a;//전역 변수 선언

void assign10(void){
	a = 10;
}

int main(void){
	printf("%d", a); //a=0
    assign10(); //a==10
}
//그럼 만약 다른 함수에 같은 이름의 변수가 있다면? -> 자기함수 내의 변수를 우선으로 챙긴다.

 

정적지역 변수

전역변수와 같은데 함수 안에 선언한다. 사용범위도 함수 내로 같다.

static int a =0;

외부 함수안에 지역변수로 선언하는 것과 이렇게 정적지역 변수로 선언하는 것의 차이점은 전자는 호출할 때마다 함수를 불러오기 떄문에 증가 연산을 해도 저장이 되지않지만 전역지역변수는 저장되기때문에 증가연산을 해주면 불러올때마다 값이 변해있다. 함수내의 함수 호출횟수를 기억하는 용도로 많이 쓰인다. 

 

resister변수

resister int i;

resister는 지역변수로만 사용되는데 메모리가 아닌 cpu안의 레지스터 공간을 할당함. 빠르게 접근가능하지만 별로 비효율적이라능.. 메모리가 아니기 떄문에 &i가 잘 잡히지 않으나 이문제는 컴파일러가 도와주기도 한다


# 13.2 – 함수의 데이터 공유 방법

값을 복사해서 주고받는게 기본적이다(함수)

주소를 주고받는 경우도 있다.(포인터활용, 반환값없어도 됌)

주소를 반환하는 방법도 있다.(return &res //이때 main함수에서 res를 받는 형은 주소이므로 int *형이 된다.)

//지역변수를 하면 아무리 주소를 반환해도 값이 사라지기떄문에 성립이 안된다. 이런 구조를 피하거나 static int res는 가능하다.(정적지역변)


# 14.1 – 2차원 배열

여러학생들의 국,수,영 점수

int s[3][4]//뒤에 칸이 하나의 배열이라고 생각하면 가로세로 구분이 쉽다.

2차원 배열의 초기화

int s[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}}
//또는
int s[3][4] = {{1},{5,6},{9,10}} //이 경우 앞에서부터 채우고 나머지 자동 0처리
//초기화 할때 s[][4]처럼 행 부분은 생략가능하다. 행초기화 괄호의 갯수 또는 요소의 갯수로 결정해줌
//따라서 s[i]의 형태로 scanf("%s",s[i])로 받을 수 있다. 
//문자에서는 {"r","a" ...} or {"rabbit"}처럼 가능하다. 
//s[0] =  "rat" 는 s[0]이 주소상수이기때문에 안되고 
strcpy(s[0],"rat"); //해야한다.

2차원 배열에서 열의 수를 계산하기

sizeof(s)/sizeof(s[0]) //배열 전체의 크기/0번째 행 배열의 크기

3차원 배열

int s[2][4][3]={{{0}}}


# 14.2 – 포인터 배열

int a[5] = {1,2,3,4,5};
int b[5] = {11,12,13,14,15};
int c[5] = {21,22,23,24,25};
//이걸 이중 포문 돌리고 싶을 때 포이터 배열로 시용가능하다.
int *ap[3] ={a,b,c} //포인터가 배열의 이름을 저장하면 포인터를 배열의 이름처럼 사용가능
//따라서 ap[i][j]로 출력가능


# 15.1 2중 포인터, 배열 포인터

2중 포인터

int *p를 가리키는 포인터를 선언한다면 int **pp = &p로 선언가능하다. int*가 따로 *pp로 가 따로 읽어야 한다.

이름을 빼면 형이니까 형은 int**가 되고 이를 가리키는 포인터는 int***가 된다. 이중포인터 이상은 다중포인터락 한다.

 

다중포인터를 쓰는 이유

예1)

공간주소를 외부함수로 바꿀떄 공간주소에 대한 공간주소를 전달할 일이 생김.

char *ap = "success";//이는 실수니까 바꾸려면 변수인 ap,bp를 바꿔주어야 한다.
char *bp = "failure";
printf("%s %s \n",ap,bp); //여기서 ap와 bp를 교환하고 싶을때 변수의 이름을 주면 그게 복사되어 넘어가기 때문에 바꾸고자 하는 변수의 주소를 주어야함
swap(&ap,&bp)함수의 구현

void swap(char **app,char **bpp){
	char *tp;
    tp = *app
    *app = *bpp //l-value,r-value
    *bp =tp
}

예2)

char *sp[5] = {"dog","cat",....} 를 출력하는 함수를 만들어보자

print_str(sp);

void print_str(char **spp){//char *를 가리키는 배열의 주소인 이중포인터를 받는다.
	int i;
    for(i=0;i<5;i++){
    	printf("%s\n",spp[i]);//배열의 이름을 담은 포인터이므로 배열으 이름으로 사용
    }
}

배열 포인터

배열명에는 2가지 기능이 있는데

1. 배열명 == 시작주소상수 //가 우리가 원래 끄던 개념

2. 배열전체의 저장공간을 나타내는 논리적인 변수 //가 새로운 개념, 그릇을 담는 쟁반같은 역할이라고 보면된다. 

설명을 위해 아래 배열을 먼저 선언한다.

int a[4] = {0};

sizeof(a)를 하면 배열전체의 크기가 나오는 것이다. 또한 이러한 2번기능을 통해 원래 주소상수라면 주소연산이 불가능하지만 배열이름은 가능하다. (&100(x), &a(o)) 

그럼 배열명 a의 형은 무엇일까? 바로 int [4]라는 1차원 배열형인 것이다. 그럼 a를 가리키는 포인터형(=배열 포인터)은

int (*p)[4] (p는 포인터형인데 가리키는 자료형은 int[4]형이다. 라는 뜻이다.)

&a+1 //116(int [4]이라는 다음 형에 대한 시작주소)
a+1 //104(시작주소에서 다음칸 시작주소)
p = &a;
//이때 하나의 변수가 아니라 전체 배열을 가리키는 큰 주소라고 생각해야함.
(*p)[0] = 10; //로 사용가능
//[]가 우선순위가 높기때문에 ()를 먼저붙여 p가 가리키는 놈을 먼저 구해야한다.
p+1; //이것도 마찬가지로 int[4]형의 시작주소값 116을 가리킨다.
//즉 *(p+1)하면 이런 1차원 배열처럼 쓸 수 있는 것임. 그치만 확보하지않은 공간이므로 하지말기

2차원배열과 배열포인터 

int a[4][5]={0}

a+1 == a[1]의 시작주소

a+2 == a[2]의 시작주소

*(a+1) == a[1]의 시작주소

a[1]+2 == a[1]행의 3번째칸의 시작주소 == *(a+1)+2 

 

연산은 포인터로서 기능해야 가능하다. 변수로하려면 값을 가지고 해야하는데 이 경우 논리적인 변수라 불가함.

즉, 이때 *(a+1)은 a[1]과 같은 시작주소(상수)값으로 쓰인다. *(*(a+1)+2)를 하면 a[1][2]의 값 4byte를 사용할 수 있다. 이는 l-value, r-value모두 사용가능하다.

 

배열 포인터가 필요한 이유

a를 함수의 인수로 주고 그 함수의 매개변수로 받기 위해서이다. 

input_ary(a);를 구현해보자

void input_ary(int(*ap)[5]){
	int i,j;
    for(i=0;i<4;i++){
    	for(j=0;j<5,j++){
        	scanf("%d",&ap[i][j]);//포인터가 배열명을 저장
        }
    }
}


# 15.2 – 함수 포인터와 void 포인터

함수포인터

함수의 명령어 위치기억, 함수의 주소를 모아놓으면 언제든 찾아서 실행가능

int sum(int a,int b)
{
	int res;
    res = a+b;
    return res;
}

int main(void)
{
	int a = 10, b = 20, res;
    res = sum(a,b);
    printf("%d\n",res);
}

그럼 위의 sum함수가 있을때 반환자료형과, 매개변수 갯수, 자료형을 모두 고려해

int(int,int)가 이 함수의 자료형이다.  따라서 함수의 포인터는 

int(*fp)(int,int)//앞이 안붙게 괄호가 중요하다.
fp = sum; //fp가 가리키는 놈 == 함수
res = (*fp)(a,b);//이렇게 함수를 활용할 수 있는데, (*fp)로 쓰기에는 불편하니까 함수포인터만 가지고도 바로 호출할 수 있게 문법적으로 지원해주었다.
res = fp(a,b);

 

void 포인터

배열등 많은 종류 지정, 가리키는 자료형이 정해지지않은 포인터

int main(void){
	int a;
    void *vp;
    vp = &a; //하지만 형이 정해지지않아서 제대로 읽어들일 수가 없다. 
    //*(int *)vp라고 형변환을 하거나 (int *)vp + 1라고 형변환하여 연산하여야 한다.  
    //어떤 함수의 매개변수로 쓰면 효용성이 올라갈 수 있음.
}


# 16.1 – 동적 할당 함수

malloc함수 (#include <stdlib.h>)

malloc(sizeof(int)) //내가 할당하고자하는 크기

어떤 용도로 쓰일지 모르니 void pointer형태로 반환(주소반환) , 따라서 쓰고싶은 형태로 형변환하여 포인터변수로 받아주는 작업이 필요 

int *p;
p = (int *)malloc(sizeof(int));
if(p==NULL){
	printf("error",exit(1));//정상종료 0, 문제종료 1, return과 다른 점은 그 전함수로 돌아가는게 아닌 바료 종료된다는 점
}
*p = 10;
scanf("%d",&(*p)); //p
*p + 20;
free(p) //동적할당된거 반납해줘야함.

calloc(씨얼록)함수

calloc(5,sizeof(int)) //동적할당받은 영역을 모두 0으로 자동초기화, 하지만 초기화해야하기 떄문에 malloc보다 시간이 걸림.

 

realloc(리얼록)함수

p = (int *)realloc(p, 10*sizeof(int)); //이미 할당받아쓰던 주소, 내가 조정하고 싶은 크기(전체크기)

만약 조정된 공간이 없어 이사가는 과정이 필요할 수 있음. 따라서 다시 배정

만약 p 가 NULL이면 기존에 비어있다는 뜻이므로 malloc이랑 같은 기능(할당만)

 

# 16.2 – 동적 할당 저장 공간의 활용

만약 문자열을 여러개 받을 떄 가장 큰 문자열도 받으면서 가장 짧은 문자열도 낭비를 줄이기 위해서는 어떻게 해야할까?

int main(void){
	char temp[200];
    char *sp[3];
    int i;
    for(i=0;i<3;i++){
    	gets(temp);
        sp[i] = (char *)malloc(strlen(temp)+1); //NULL문자까지
        if (sp[i] == NULL){//검사코드 필요
        	printf("error",exit(1)
        }
        strcpy(sp[i],temp); //동적 할당 또 해준걸 복붙
    }
    for(i=0;i<3;i++){
    	printf("%s\n",sp[i]);
    }
    for(i=0;i<3;i++){//반납
    	free(sp[i]);
    }
}

동적할당받은 영역을 함수로?

print_str(sp);

void print_str(char **spp){//배열명을 저장한 포인터
	int i;
    for(i=0;i<3,i++){
    	printf("%s\n",spp[i]);
    }
}

운영체제가 주는 명령행 인수들을 쓰고싶을때

int main(void)에서 바로 void자리에 작성

 

도수상태에서 실행

C.>first second third

int main(int argc,int *argv) // 명령형 인수 갯수, 명령형 인수 포인터
for(i=0;i<argc,i++){//NULL이 아닌 동안 출력도 가능
	printf("%s\n",argv[i]);//모두 출력
}

while(*argv){
	printf("%s\n",*argv);
    argv++;
}


# 17.1 – 구조체

int,double은 컴파일러를 만들때부터 있는 내장 자료형이다. 주로 메인함수 위에 선언한다.

student라는 자료형을 만들어보자.

struct student
{
	int num;//학번, 구조체변수에 접근하기위해 a.num으로 접근 scanf("%d",&a.num)
    double grade;//학점
};
//변수선언이 아니므로 초기화를 못함 그저 신고이다.

int main(void){
	struct student a;//이렇게 형 선언
}

바이트 얼라인먼트 ->크기가 큰 멤버기준으로 잡혀서 실제 sizeof에서는 16정도로 잡힘

char형 두개와 short형 하나 int 형 하나 double형 하나가 있는 구조체를 선언하면 바이트 얼라인먼트에 의해 이렇게 아래처럽 배치되며 남는부분(까맣게 칠한 부분)에 패딩공간이 생기게되는데 순서조절을 하면더 효율적으로 공간을 사용할 수 있기때문에 순서조절이 필요하다. 

num이 short형

a.name = "hong gildong" (X) 이건 상수에 상수를 넣는 일이라 성립될 수가 없다. 

strcpy(a.name, "hong gildong")(O)

 

a.intro ="hello" //intro는 포인터 이므로 가능하다. 

 

a.intro = (char *)malloc(80); //주소를 넣는 것이므로 따로 메모리 할당을 해주어야 한다. 

scanf("%s),a.intro); //그럼 이 a.intro가 동적 할당 받은 영역이 된다. 

 

구조체의 형을 구조체의 멤버로

struct profile{
	char name[20];
    int age;
    double height;
}

struct student{
	struct profile pf; //32byte
    int num; // 4
    double grade;//8
}

strcpy(a.pf.name, "hong gildong")//이렇게 3단으로 참조가능

구조체 초기화(변수처럼 처리)

struct student a = {{"hong-gildong",17,187,5}}//이런식
strcpy(b.pf.name, a.pf.name) 
// b=a 구조체변수를 바로 대입연산가능. 구조체변수를 반환, 함수에 인수주기가능(즉, 변수처럼 처리)

 비트를 활용할 수도 있음 1~4정도

struct bit_field{
	unsigned int son:2; //2bit를 선언
    unsigned int daugter:2; //만약 이름 생략시 패딩비트가 된다.
    unsigned int pet:3;//이름생략하고 비트수도 0인 경우 첫번째 할당 남는 공간 전부 패딩비트, 즉 (32-7)bit 버려지는 공간
}

다만 주소연산이 불가(비트이므로)

scanf("%d", &s.a) (X)

scanf("%d", &temp), s.a = temp (O)

# 17.2 – 구조체 활용, 공용체, 열거형

 구조체 포인터는 간접참조로 접근가능하다. 

struct score{
	int kor;
    int eng;
    int mat;
};

int main(void){
	struct score *sp = &a //struct score *형 포인터
    struct score a;
    a.kor = 90;
    a.eng = 80;
    a.mat = 75;  
}

 

(*sp).kor우선순위문제로 괄호를  쳐줘야한다. 아래처럼 활용도 가능하다.

화살표 연산자 처리

sp ->kor = 90;
scanf("%d",&(sp->kor))
//sp -> kor + sp->eng + sp -> mat 같이 연산도 가능

print_st(a)//그럼 복사시간 걸리니까 아래와 주소만 줘보자
print_st(&a)
//구현
void print_st(struct score *p){
	printf("%d\n",p->kor)//또는 (*p).kor
}

구조체 배열

struct address{
	char name[20];
    int age;
    char tel[20];
    char addr[80];
}

int main(void){
	struct address a[5]//구조체 배열
}
/* 
p==a[0]{{        }}
a[1]{{        }}
a[2]{{        }}
a[3]{{        }}
a[4]{{        }}
*/

구조체 배열을 처리하는 함수

print_ary(a)

void print_ary(struct address *p)//포인터가 배열명저장.p[i].name가능
{
	int i;
	for(i=0;i<3;i++)
    {
    	printf("이름:%s\n",p->name);
        p++;
    }
}

자기참조구조체

struct list{
	int num;
    struct list next; //자기참조구조체
}

struct list a = {10};
struct list b = {20};
struct list c = {30}; 
//일 경우 num에는 각 숫자가 들어가고,나머지는 0으로 초기화
a.next = &b
b.next = &c
//으로 지정해줄 시 아래와 같은 연결리스트가 생성된다. 
//이때 처음주소를 기억해줄 필요가 있기떄문에 별도의 변수로 저장해준다.

struct list *tp = &a

//아래 문장들의 반복으로 다 찍을 수 있음. while(tp!=NULL)조건
printf("%d",tp->num);
tp = tp->next

struct list *head = &a 로 첫번째 구조체 위치를 기억해두기(보통 동적 할당되기 때문)

공용체(Union)

멤버들이 저장공간 공유함, 크기는 가장 큰 걸로

union student{
	int num;
    double grade;
};

a.num =315;
printf("%d", a.num); ->315
a.grade = 3.5;
printf("%d",a.num) ->315(X)

union student a;//로 선언

한 순간에 하나만 써야하지만 메모리를 아낄 수 있음. 또는 자료형을 다양하게 써야 할떄 유용하다.

초기화하면 첫번째거만 입력이 된다. grade를 입력하고 싶다면 ={.grade = 3.4}형식으로 해야한다.

 

열거형(enum)

형태가 있는게 아닌 열거형(=int형 (4byte))

enum season {spring,summer,fall,winter};
/* 차례로 0,1,2,3이다.이러한 인수는 실제로 정수형으로 바뀌어서 됨.
color나 define고 같음.
하나의 이름으로 관리가능. 정수형을 이름으로 비유해서 활용가능*/
enum season a;
a = spring
if (a==spring){
	printf(-----)
}

이름 간단하게 재정의(typedef)

typedef int INT;
INT a,b; //로 사용가능
------------------------
typedef int *IP;
IP ap.bp;
ap = &a;
-------------------------
typedef struct student{
....
}Student; //Student로 사용가능
--------------------------
typedef struct student Student;
Student a,*sp;//Student로 사용가능
--------------------------
typedef struct student ST;
struct student{
..
};//ST로 사용가능

# 18.1 – 파일 개방과 입출력

H.D에는 블럭번호, 작성자ID, 파일크기, 파일내용등을 같이 저장해준다. 

1.M.M의 구조체변수(FILE)로 파일에 여러 정보를 가리킨다.

(typedef struct_idonf FILE) - > #include <stdio.h>

fopen()은 이럼 stram file을 만들어주는 함수

r은 읽기,  w은 쓰기, a은 추가

text파일이면 형식을 추가해서 rt, wt등으로 작성해주어야 한다. text file이 아닌 동영상, 실행등은 바이너리모드로 오픈해야한다.rb,wb

fopen("a.txt",rt);
FIlE *fp;
fp = fopen("a.txt","r");//a.txt는 개방파일을 찾는 위치, 실행파일이 실행되는 위치이다.
// sub\\a.txt 또는 "C.\\shu\\chata\\a.txt"처럼 위치지정도 가능
//그냥 return하면 수정이 안되니 구조체변수의 주소를 반환

//a.txt가 찾지못하면 NUL(->(void*)0)을 반환하므로
if(fp==NULL){
	printf("파일X");
    exit(1);
}//예외처리가 꼭 필요함
fclose(fp); //파일닫기
char ch;
FILE *fp;
fp = fopen("a.text","r");
if(fp==NULL){
	printf("파일없음");
    exit(1);
}
ch = fgetc(fp); //H.D은 한글자씩 읽어서 버퍼에는 많은 값이 m.m로 온다. 
printf("%c",ch);
fclose(fp);
return 0;
/*근데 여기서 어떻게 문자열을 읽어서 가져오는 걸까
FILE구조체안에는 위치지시자라는 변수가 있는데, 이는 현재 버퍼에서 문자열을 읽은 위치를 저장하고 있다.
그래서 이가 자동으로 하나씩 증가하여 가져옴 (그리고 구조체안의 file크기에 대한 정보를 가져와 이 크기와 같아질때까지 읽는다.)
다 읽으면 EOF(end of file)을 반환(==-1)

화면에 모두 출력

ch = fgetc(fp);
while(ch != -1){//EOF
	printf("%c",ch);
    ch = fgetc(fp);
}
//w모드로 오픈시 파일이 없으면 빈 파일을 생성해줌, 그래도 검사는 해주자 있음 다 지워버리니까, 닫기도 해주자
fp = fgetc("a.txt","r");
ofp = fopen("b.txt","w");
ch = fgetc(fp);
while(ch!=-1){
	fputc(ch,ofp);
    ch = fgetc(fp);
}//복사하는법

기본 운영체제 개방 스트림파일 : stdin, stdout, stderr


# 18.2 – 다양한 파일 입출력 함수(**)

 

FILE *fp;
char name[20];
int kor,eng,mat;
int tot;
double avg;
ofp = fopen("b.txt","w");
fp = fopen("a.txt","r");
//키보드 입력(scanf) 파일로 입력(fscanf)
//키보드로 압력받는것도 똑같은방식이용해라라는뜻
fscanf(fp,"%s %d %d %d",&name,&kor,&eng,&mat);
//if null 확인
tot = eng+kor+mat;
avg = tot/3.0;
fprint(ofp"%10s %5d %7lf\n",name,tot,avg);//형식에 맞춰 파일로 출력
feof(fp)가 eof면 1, eof아니면 0반환

하나의 문자 입출력         :  fgetc, fputc

한 줄씩 문자 입출력        :  fgets,fputs

형식에 따라 문자 입출력 :  fscanf,fprint

//이 모두는 같은 입력 버퍼를 공유한다.

버퍼공유문제 / fflush()

 

# 19.1 – 전처리 지시자 (1)

전처리기가 편집해주는 작업을 전처리라고 하는데 아래와 같은 과정 속에 있다. (소스코드 == 모듈) 

#include 지시자

지정된 파일의 내용을 읽어 지시자 위치에 붙여넣기한다. <> 혹은 ""로 묶는다.

<>로 묶는 경우 컴파일러가 설정한 include 디렉터리 안에서 찾고(#include <stdio.h>) ""로 묶는 경우 시용자정의소스, 정의파일등 소스파일이 저장된 디렉터리에서 먼저 찾고 없는 경우 include 디렉터리에서 다시 찾는다. (#include "student.h")

#include "C:\user\myhdr.h" 처럼 찾는 경로로 직접 지정도 가능하다. 전처리기 처리이므로(!=컴파일) \를 한개만 써도 됨.

 

#define 지시자

복잡한 상수나 문장에 대해 매크로명을 정의, 매크로 상

#define MSG "passed!"
#define ERR_PRN printf("범위를 벗어났습니다!")
#define INTRO "perfect C Language\
& Basic Data Structure" // \는 다음 줄과 이어짐 표시

//사용예시
if(area > LIMIT) ERR_PRN //범위를 벗어났습니다 출력
else printf("원의 면적: %2lf (%s\n,area.MSG)")//면적과 메세지 출력

//define을 사용한 매크로 함수
#define SUM(a,b) ((a)+(b)) 
/*실행속도가 빠르다. 하지만 괄호없이 a+b 또는 (a+b)를 넣을경우 저 자체로 치환이 되어버린다. 
이때 *연산같이 순서가 필요한 연산의 경우 가져와서 앞에서 부터 컴파일해야하기 떄문에 문제가 생길 수 있음*/


# 19.1 – 전처리 지시자 (2)

이미 정의된 매크로들(취소나 변경이 불가능하다)

_DATE_ : 컴파일을 시작한 날짜 (%s)

_TIME_ : 컴파일을 시작한 시간 (%s)

_FILE_ : 컴파일하는 파일명 (%s) (c부터 시작해서 다 출력해줘서 _LINE_으로 시작위치바꿀 수 있음 _LINE_ = 100 -> 100,101,102

_FUNCTION_ : 매크로명이 사용된 함수 이름 (%s)

_LINE_ : 행 번호 (%d)

 

LINE지시자

매크로명 _LINE_ 의 형 번호의 시작값 설정 

#line 100

_FILE_이 치환되는 파일의 이름 설정 

#line 100 "macro.c"

 

조건부컴파일지시자

#if, #else, #elif ...

#undef 메크로 정의 취소

#error 메세지 출력하고 컴파일 중단

#pragma 지시자

       #pragma pack(1) -> 패딩바이트없애기

       # pragma warning (disable:4996)


# 19.2 – 분할 컴파일

여러개의 파일로 소스파일을 작성하고 링크 단계에서 합친다.

extern

다른파일의 전역 변수를 쓸 때 extern선언을 함

static

전역변수는 하나의 소스파일에서만 사용

 

헤더파일의 필요성

각 파일에 공통으로 필요한 코드를 모아만든다. (함수선언, 구조체 형 선언, extern선언)

헤더파일의 수정내용을 빠르고 정확하게 반영한다.

다른 프로그램에 헤더 파일의 내용을 재활용한다.

 

헤더파일의 함수명 중복포함 문제

#indef(정의되어있지않다면) ~ #define(정의)~ #endif(정의되어있다면)로 해결

728x90
반응형

'C' 카테고리의 다른 글

isascii 함수의 구현  (0) 2023.10.09
isalnum 함수의 구현  (0) 2023.10.09
isdigit 함수의 구현  (0) 2023.10.09
atoi 함수의 구현  (2) 2023.10.09
isalpha 함수의 구현  (2) 2023.10.06