WEEK05 | C언어

wony·2022년 5월 4일

C언어

5주차는 'RB트리'다.
심화 자료구조에 대한 이해와 구현 실습? 정도가 되겠다.
근데 전제가 'C언어로'였다.
그래서 C부터 해야하는데, 사실 초면은 아니지만.. 학교에서도 C의 꽃이라는 포인터, 구조체까지는 안했었고 이참에 제대로 봐두면 좋을테니. 시간이 좀 들지만 C부터 잡고가기로 팀원과도 이야기가 됐다. 그래서 구름edu를 통해 C언어를 한 번 정주행하면서 기록을 남긴다🙂

기본형태

  #include <stdio.h>
  // 전처리문 -> 컴파일하기 전에 <stdio.h> 헤더파일을 추가할 것
  // 헤더파일 -> 이미 만들어진 함수를 가져와서 씀
  // stdio.h -> 표준입출력 헤더파일 -> 입출력, 반복문, 조건문 등을 위함
   
  int main(){
  // 실행시 main부터 돌아감
  // (return 자료형) 함수이름(입력자료형) {} : 기본형태
  	return 0;
  // 반환 -> 함수가 종료되었을 때, 변수나 값을 돌려주는 역할
  // ; -> 세미콜론, 코드의 마침을 의미함
  }

문법

입출력 - printf

  #include <stdio.h>
  
  int main(){
  	printf("한줄");
    printf("줄바꿈 \n");
  	return 0;
  }
  
  /* 
  -- 이스케이프 시퀀스 --
  \(백슬러시)와 특정 문자를 결합하여 c언어 특성상 표현할 수 없는 특정 기능이나 문자를 표시해주는 문자
  */

입출력 - scanf

  • printf랑 형식지정자 같이씀
  • double만 %lf를 사용
  • 저장할 변수 앞에 &연산자(해당 변수의 주소를 의미함) 붙여씀
  • space나 enter로 값을 구분 → 여러개 입력받음(마지막엔 enter여야함)
    #include <stdio.h>
    
    int main(){
      int a;
      
      printf("정수를 입력하세요 : ");
      scanf("%d", &a);
      printf("입력받은 정수 : %d", a);
      
      return 0;
    }

조건문 - if, else if, else

  • 조건을 만족하면 if문을 실행
  • 논리연산자, 관계연산자가 많이 쓰임
  • else if, else는 생략가능
  • 이것도 한줄은 중괄호 없어도됨
  • 여러줄이면 써야함
  • 참 : 0, ‘\0’, NULL을 제외한 값, 논리관계 참(true)
  • 거짓 : 0, ‘\0’, NULL값, 논리관계 거짓(false)
        #include <stdio.h>
        
        void main(){
        	a = 1;
        	b = 10;
        	if(a<b){
        		printf("%d가 큼",b);
        	}
        	else if(a>b){
        		printf("%d가 큼",a);
        	}
        	else{
        		printf("같음");
        	}
        	return
        }
  • C언어는 절차지향언어라서 순서대로 조건문을 판단
  • if문을 만족하지 않을 때 else if를 확인하고 이것도 만족하지 않을 때 else를 확인
  • if, else if 는 앞에서 만족하면 뒤에서 검사를 안함
  • if문을 여러개 쓰면 앞에서 만족해도 다시 검사함
  • ⇒ 조건이 중첩되는 경우, else if를 적절히 써줘야함

홀짝 판별하기 - 조건문 예제

     #include <stdio.h>
     
     int main(){
     	int num;
     	scanf("%d", &num);
     	
     	if(num%2==0)	{
     		printf("입력받은 정수는 짝수입니다.");
     	}
     	else	{
     		printf("입력받은 정수는 홀수입니다.");
     	}
     
       return 0;
     }

두가지 조건 동시에 판별하기 - 조건문 예제

  • if, else if, else 사용하기
  • 논리연산자, 관계연산자 사용하기
        #include <stdio.h>
        
        int main()
        {
        	int num;
        	scanf("%d", &num);
        	
        	if(num>10 && num%2==0)
        	{
        		printf("%d(은)는 10 초과의 짝수입니다.", num);
        	}
        	else if(num>10 && num%2)
        	{
        		printf("%d(은)는 10 초과의 홀수입니다.", num);
        	}
        	else if(num<=10 && num%2==0)
        	{
        		printf("%d(은)는 10 이하의 짝수입니다.", num);
        	}
        	else
        	{
        		printf("%d(은)는 10 이하의 홀수입니다.", num);
        	}
        }
  • break : 반복문을 빠져나옴
  • continue : 남은 코드를 스킵하고 다음 반복을 실행

조건문 - switch

  • 조건을 체크하고 케이스를 실행함
  • 관계식을 쓰는 건 아니고 특정한 정수값이나 문자를 확인(실수 안됨)
  • if문 처럼 조건을 하나씩 체크하면서 내려가는게 아니라
  • 해당 케이스로 바로 이동 → if문보다 빠름
  • // 조건이 4개 이상일때 switch가 성능이 더 좋대(어셈블리차원의 이야기)
  • 요즘은 성능비슷 상황에따라 사용
     switch(기준값)
       {
        case 비교값1:
        	기준값과 비교값1이 같을 때 실행
        case 비교값2:
        	기준값과 비교값2가 같을 때 실행
        default: // 생략가능
        	기준값과 비교값들이 같지 않을 때 실행
        }

예제

#include <stdio.h>

void main()
{
	int input;	
	scanf("%d", &input);
	
	switch(input)
	{
		case 1:
			printf("1을 선택하셨습니다.\n");
			break; // 선택이긴한데, break없으면 밑에거 다 실행함
		case 2:
			printf("2를 선택하셨습니다.\n");
			break;
		case 3:
			printf("3을 선택하셨습니다.\n");
			break;
		case 4:
			printf("4를 선택하셨습니다.\n");
			break;
		case 5:
			printf("5를 선택하셨습니다.\n");
			break;
		default:
			printf("범위에 맞지 않는 값입니다.\n");
			break;
	}
     	
	return 0;
}
  • 조건식을 쓸 수는 없지만, 범위에 따라 실행하는 건 가능
    #include <stdio.h>
    
    void main()
    {
    	int input;	
    	scanf("%d", &input);
    	
    	switch(input)
    	{
    		case 10:
    		case 9:
    		case 8:
    		case 7:
    			printf("학점은 A입니다.\n");
    			break;
    		case 6:
    		case 5:
    		case 4:
    			printf("학점은 B입니다.\n");
    			break;
    		case 3:	
    		case 2:
    		case 1:
    		case 0:
    			printf("학점은 C입니다.\n");
    			break;
    		default:
    			printf("범위에 맞지 않는 값입니다.\n");
    	}
    	
    	return 0;
    }
      

배열의 홀짝 판별 - 조건문 응용

  • 조건문, 배열, 반복문 사용
  • 정수배열 안에 홀짝이 몇개씩 있는지 판별하기
   #include <stdio.h>
   
   int main()
   {  
     int arr[10] = {5, 10, 15, 22, 23, 102, 99, 102, 130, 8};
     int odd = 0;
     int even = 0;
     
     for(int i=0; i<10; i++)
   	{
   		if(arr[i]%2==0)
   		{
   			even++;
   		}
   		else
   		{
   			odd++;
   		}
   	}
     
     printf("홀수의 갯수는 %d개 입니다.\n", odd);
     printf("짝수의 갯수는 %d개 입니다.\n", even);
     
     return 0;
   }

가장 작은 수 - 조건문 응용

  • 조건문 사용하기
  • 배열 중 가장 작은 수를 출력
  #include <stdio.h>
  
  int main()
  {
  	int arr[10] = {1032, 99, 1004, 234, 452, 8, 922, 445, 246, 2048};
  	int min = 1e9;
  
  	for(int i=0; i<10; i++)
  	{
  		if(arr[i]<min)
  		{
  			min = arr[i];
  		}
  	}
  
  	printf("배열 안에서 가장 작은 수는 %d입니다.\n", min);
  
  	return 0;
  }

반복문 - for

  • 초기식, 조건식, 증감식
  • 실행문이 한줄이면 중괄호없이 들여쓰기만 해도됨
  • 2줄 이상이면 무조건 중괄호
   #include <stdio.h>
   
   void main(){
   	a = 10
   	for(i=0; i<a; i++){
   		printf("지금은 {%d}.\n", &i);
   	}
   	return
   }

반복문 - while

  • 조건식
  • 초기식은 while문 밖에
  • 증감식은 while문 안에
   // while 문으로 "Hello, world!\n" 를 5번 출력
   int main(){
   	int i = 0;
   	while(i<5)	{
   		printf("Hello, world!\n");
   		i++;
   	}
   	return 0;
   }

반복문 - do while

  • 조건식
  • 초기식은 while문 밖에
  • 증감식은 while문 안에
  • 한번 실행하고 조건확인
   do{
   	적어도 한 번 실행될 내용
   }
   while(조건식);

주석

   // 한줄주석
   /*
   	여러줄 주석
   */

변수

  • 자료형 변수이름으로 선언
  • 선언과 동시에 초기화할 수 있음
  • 값을 변경시킬 수 있음
  #include <stdio.h>
  
  int main(){
  	// 자료형 이름 = 초기값
    int level = 1;
    int hp = 50;
    int damage = 5;
    int defense = 2;
    // 선언과 동시에 대입
    defense = 5; // 바꿀 수 있음
    
    return 0;
  }

자료형

부호

  • unsigned는 양수로 2배의 범위까지 표현가능
  • 실수형은 unsigned가 존재하지 않음

  • 비트 : 컴퓨터가 처리할 수 있는 최소 단위
  • 0, 1 의 값
  • 1 바이트 = 8 비트

  • 숫자를 저장할때는 해당 자료형의 범위만큼 사용가능
  • 1바이트는 영문자, 숫자, 특수문자 한 글자를 저장할 수 있음
  • 2바이트는 한글, 일본어, 중국어 등의 문자 하나를 저장

형식지정자

// 정수형
int a = 10;
printf("a : %d", a);
// 이때 a에 값이 없으면 쓰레기값이 출력됨
// 실수형
float b = 1.32f;
// f 안붙여주면 double 크기(8바이트)로 받아들임
// float 크기(4바이트)라고 명시해 주는거
double c = 1.321123;
printf("b : %f",b);
printf("c : %f",c);
// 실수형 출력은 기본 소수점 6자리까지 나옴
// float은 6자리, double은 15자리 까지 지원하는데
printf("c : %.3f",c);
// 이렇게 정해주면 3자리 까지 표시함

// 소수점이 정확하지 않음(10자리까지 늘려보면ㅇㅇ)
// 컴퓨터가 2진수로 구성되어 소수점인 10진수를 정확히 표현할 수 없음

상수

  • 처음 선언할때 값을 입력해야함
  • 선언 후에 바꿀 수 없음
  • 자료형앞에 const를 붙여서 선언
  #include <stdio.h>
  int main(){
      const double PI = 3.1415;
      PI = 5;
      return 0;
  }

연산자

기본연산자 : +, -, *, /, %(나머지연산)

  • 정수와 실수의 계산은 실수형에 저장
  • 정수끼리나누면 몫만(나머지는 버림), 실수끼리나누면 나머지도 저장
  • 나머지 연산은 정수끼리만 가능
  • % 출력하려면 %%

증감연산자 : ++, —

  • 1씩 증감시키는 연산자
  • 전위연산자는 증가시킨 후에 해당 코드가 실행됨
  • 후위연산자는 코드를 실행한 후에 변수 값만 증가시킴
   #include <stdio.h>
   
   int main()
   {
   	int a = 3;
   
   	printf("%d\n", ++a); // a가 1 증가됨 -> 4
   	printf("%d", a); // 1 증가된 a가 출력 -> 4
   
     int b = 3;
     
     printf("%d\n", b++); // b가 출력된 다음에 1 증가 -> 3
     printf("%d", b); // 1 증가된 b가 출력 -> 4
   
   	return 0;
   }

비교연산자

  • 관계연산자 라고도 함
  • ==, !=, <, >, <=, >=
  • c언어에서는 참이면 1, 거짓이면 0을 반환
  • 인식할때도 0은 거짓, 이외의 값은 참으로 판단

논리연산자

  • 참, 거짓
  • 논리곱 (&&, AND) 연산 : 둘다 참일 때 참을 반환
  • 논리합 (||, OR) 연산 : 둘 중에 하나만 참이어도 참을 반환

복합대입연산자

  • +=, -=, *=, /=, %= 다됨
  • 계산해서 넣는거
  • 비트연산도 가능

비트연산자

  • 정수나, 정수로 변환 가능한 타입만 가능
  • 실수나 포인터는 안됨
  • 비트는 2진수를 저장 : 0,1
  • &(AND), |(OR), ^(XOR:다를때 참), ~(NOT)
  • a<<b(a의 비트를 b만큼 왼쪽으로 이동)
  • a>>b(a의 비트를 b만큼 오른쪽으로 이동)
  • 각 자릿수에 대해 연산함
  • unsigned char 자료형으로 실습(큰 숫자가 필요없고
  • signed는 최상위비트(msb)를 부호비트로 사용
    → 0이면 양수, 1이면 음수를 나타냄
    → 첫자리가 1이되어 음수로 보면 보수연산을 해버림
    → 거기까지 안가려고 unsigned 로 실습
  • &=, |= 도 가능

비트이동연산자

  • <<, >> : 왼쪽 오른쪽으로 비트이동

왼쪽

  • 비트가 왼쪽으로 한자리씩 이동할때마다 정수값이 2배가 됨
  • 2진수니까 한칸갈때마다 2의 승수가 올라가는셈
  • 다시말해 n칸 이동 = 2^n 을 곱하는 거
  • !! : char일때 8비트인거, int나 더큰 자료형은 비트 이동시 잘리는 크기가 다름
  • !! : unsigned는 끝까지 정상적으로 이동하는데 부호비트있는 signed는 msb로 비트가 이동했을때 (1이 들어갔을때) 보수연산을 함

오른쪽

  • 비트가 오른쪽으로 밀림
  • 제일 오른쪽으로 밀려나는 건 버려지고
  • 왼쪽에서는 빈 비트를 채워야하는데
  • 양수이면 unsigned, signed 모두 0으로 채워도 상관없지만(채우긴 채운다는거?)
  • 음수(signed)이면 msb가 1이어야 함
  • 근데 이 연산은 cpu마다 다름
  • 1로 채우는 거도 있고 0으로 채우는 거도 있음
  • 일반적으로는 msb와 같은 숫자로 채움
  • 음수를 의미하는 숫자는 봤을때 직관적으로 수가 보이진 않음
    - 보수개념은 노트에~
    - 오른쪽으로 이동하니까 2의 n승만큼 나눠짐

배열

선언

int main(){
// 자료형 배열이름[크기] = {값1, 값2, ...};
	int arr1[5] = {1, 33, 47, 100, 155}; // 선언과 동시에 초기화해줌
	int arr2[5] = {5}; // 0인덱스는 5, 나머지는 0으로 초기화
	int arr3[5] = {5, 10}; // 0인덱스는 5, 1인덱스는 10, 나머지는 0으로  초기화
	int arr4[5] = {};  // 모두 0으로 초기화
  int arr5[5]; // 선언만 하고 초기화하지 않음
	int arr6[] = {11, 22, 33, 44}; // 배열의 크기가 초기값 개수에 맞게 4로 정해짐
// 인덱스로 접근, 0부터 시작
	return 0;
}

// 접근
// 배열이름[인덱스]
arr1[2] -> 47

// 배열의 크기를 넘어서는 인덱스나 초기화하지 않은 배열을 출력함면 대부분 쓰레기값이 나옴
// 변수는 선언되는 동시에 메모리를  차지하는데 값을 정해주면 해당 영역은 변수 값을 가지지만
// 정해주지 않으면 메모리 자체에서 가지고 있던 값을 보여줌 -> 쓰레기

반복문이랑 같이 많이 씀

#include <stdio.h>
    
int main(){
	int arr[5] = {1, 33 , 47, 102, 155}; // 선언과 동시에 초기화

	for(int i=0; i<5; i++)	{
		printf("arr 배열의 인덱스 %d 의 값 : %d\n", i, arr[i]);
	}

	return 0;
}

입력받아서 출력하기

#include <stdio.h>
    
int main(){
	int arr[5];
    
	for(int i=0; i<5; i++)	{
		printf("인덱스 %d에 들어갈 값을 입력하세요. : \n", i);
		scanf("%d", &arr[i]);
	}
	printf("-----결과-----\n");	
	for(int i=0; i<5; i++)	{
		printf("인덱스 %d의 값 : %d\n", i, arr[i]);
	}	
	printf("-----종료-----\n");

	return 0;
}

배열의 주소

  • 변수를 선언하면 메모리에 무작위로 저장됨(각각의 주소를 가짐)
  • 배열은 선언한 크기만큼 연속적으로 연결되어있음
#include <stdio.h>
        
int main(){
	int arr[3];
	// int -> 4byte짜리 메모리가 3개 연결되어 있음
	// 0번째 원소가 1000번지에 저장되어있다고 가정하면
	// 1번째는 1004, 2번째는 1008번지에 각각 저장되는 거
// int니까 4씩 증가, char 였으면 1씩, double이었으면 8씩 주소가 증가했을 것
// 즉, 연속된 각 원소들의 주소는 자료형의 크기만큼 차이남
	return 0;
}

배열의 크기

  • sizeof()
  • length 구해서 반복문 조건에 쓰기도함
#include <stdio.h>

int main(){
  int arr[] = {519, 31, 7988, 165326, 100, 150};

  printf("%d\n", sizeof(arr)); // 배열이 메모리 상에서 차지하고 있는 용량
  // int 는 4 바이트이고 6개가 있으므로 출력 결과는 : 24

  printf("%d\n", sizeof(arr) / sizeof(arr[0])); 
  // 배열의 전체크기 / 요소 하나의 크기
	// -> 24 바이트 / 4바이트
  // 전체 배열을 요소 하나로 나누면 길이를 구할 수 있음
  // 출력 결과는 24 / 4 이므로 6
  
  return 0;
}

문자열

  • 아스키코드 : 문자 하나에 숫자를 대응시키는 표현 방식
  • char 자료형의 크기만큼 → 8비트 256가지 글자
  • 유니코드는 문자를 2바이트로 처리해서 65,546개를 표현할 수 있지만 여기선 안다룸

문자 출력

#include <stdio.h>

int main(){
  char ch = 'a';
    
  printf("%d\n", ch); // a 와 매칭되는 97 출력
  printf("%c\n", ch); // a 출력
  
  return 0;
}

문자열 출력 - char

#include <stdio.h>

int main(){
 char ch = 'ab';
   
 printf("%d\n", ch); // 98
 printf("%c\n", ch); // b
	// char : 1byte -> 한 글자만 담을 수 있음
	// 마지막에 입력된 b만 저장됨
 
 return 0;
}

문자열 출력 - char[]

#include <stdio.h>

int main(){
  char ch[5] = "abcd";
	// 문자들 마지막에 0, NULL, \0 등의 값이 들어있음 -> 종료문자
  
  printf("ch 는 %s", ch); // 여러글자 즉 문자열을 출력할때는 '%s'
  
  return 0;
}

종료문자

#include <stdio.h>

int main(){
  char ch[7] = { 'a', 'b', 'c', 'd', 0, 'e', 'f' };
  
  printf("ch 는 %s", ch);
  
  return 0;
}

문자 입력받기

#include <stdio.h>

int main(){
  char ch;
  
  printf("한 글자만 입력해주세요 : ");
  scanf("%c", &ch); // 한글자 이상 받으면 첫글자만 남음
  printf("%c", ch); 

  char ch2[201];
  
  printf("200 자 이내로 입력해주세요 : ");
  scanf("%s", ch2);  // & 표시 없이 scanf 입력 받기
	// 배열은 배열이름에 주소를 담고있어서 없어도됨
  printf("%s", ch2);
  
  return 0;
}

이중반복문

  • 별 출력해보기
  • 증감식과 조건식에 사칙연산을 활용할 수 있음
  • 피라미드 출력하기

이차원배열

1차원 배열

int main(){
  int arr[5] = {1, 33, 47, 102, 155};
  
  return 0;
}

2차원 배열

  • 선언
    int main(){
    	int tarr[2][3]; // 행열 크기지정
    	// 이것도 인덱스는 0부터 시작
    }
    • 이거도 인덱스 0부터
    • 이중 포문이랑 같이 많이 씀
#include <stdio.h>

int main(){
	int tarr[3][3] =	{  // 초기화 - 중괄호 사용
											{1, 2, 3},
											{4, 5, 6}
										};

	for(int i=0; i<2; i++)	{ // 출력은 이중for문으로
		for(int j=0; j<3; j++)	{
			printf("%d ", tarr[i][j]); // i:row, j:col
		}
		printf("\n");
	}

	return 0;
}
  • 입출력 - 이중반복문으로 구현
#include <stdio.h>

int main()
{
	int tarr[2][3];

	printf("2차원 배열 입력\n");
	for(int i=0; i<2; i++)
	{
		for(int j=0; j<3; j++)
		{
			printf("tarr[%d][%d] 입력 : ", i, j);
			scanf("%d", &tarr[i][j]);
		}
	}

	printf("\n2차원 배열 출력\n");
	for(int i=0; i<2; i++)
	{
		for(int j=0; j<3; j++)
		{
			printf("\t%d", tarr[i][j]);
		}
		printf("\n");
	}

	return 0;
}
  • 문자열 입력받기
    • 문자열은 char 배열로 받아야함
    • 마지막 자리는 종료문자(0, \0, NULL)가 들어감
#include <stdio.h>

int main()
{
	char tarr[3][10];

	printf("이차원 배열 문자열 입력\n");
	for(int i=0; i<3; i++)
	{
		printf("10자 이내의 문자열을 입력해주세요 : ");
		scanf("%s", tarr[i]); // 문자열 저장
	}

	printf("\n이차원 배열 문자열 출력\n");
	for(int i=0; i<3; i++)
	{
		printf("%s\n", tarr[i]); // 문자열 출력
	}

	return 0;
}
  • 문자열은 %s 로 한번에 출력가능

숫자 정렬하기

  • 두 변수의 값 바꾸기
  • 변수 a,b의 값 바꾸기
#include <stdio.h>
int main()
{
    int a = 5;
    int b = 10;
    int temp;
    temp  = a;
    a = b;
    b = temp;
    printf("a : %d ", a);
    printf("b : %d", b);
    return 0;
}

버블 정렬

  • 이웃한 값을 비교하여 큰값을 뒤로 넘기며 정렬하는 방식
  • 앞에서부터 앞뒤를 비교해서 앞숫자가 뒷숫자보다 크면 위치를 바꿈
  • 마지막에 도착하면 제일 큰 수 일 거
  • 그럼 두번째로 큰 숫자를 찾음
  • 배열 길이만큼 도는데 그안에서 반복하는 횟수는 1씩 감소
  • // 한번 돌때마다 하나씩 정해질거니까 그거 빼고 비교
#include <stdio.h>

int main()
{
	int arr[10] = {9, 17, 5, 6, 124, 112, 1, 3, 87, 55};
	int temp;
	int length = sizeof(arr)/4;
	
	for (int i=0; i<length-1; i++)
	{
		for (int j=0; j<length-1-i; j++)
		{
			if (arr[j]<arr[j+1])
			{
			if (arr[j]<arr[j+1])
				temp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = temp;
			}
		}
	}

	for(int i=0; i<length; i++){
		printf("%d ", arr[i]);
	}
	
	return 0;
}

삽입정렬

  • 앞에서부터 선택한 값과 앞인덱스들의 값을 비교해서 자신보다 작은 요소를 찾을때까지 옆숫자랑 자리를 교환함(앞으로오면서)
  • 두번째 인덱스[1]부터 시작
  • 탐색할 인덱스의 값을 tmp에 따로 담아두고
  • 탐색하는 애보다 작은애가 나올때까지 앞에것들을 한칸씩 뒤로 옮김(덮어씀)
  • 그리고 자리를 찾으면 그자리에 tmp를 삽입(덮어씀)
#include <stdio.h>
int main()
{  
	int arr[10] = {9, 17, 5, 6, 124, 112, 1, 3, 87, 55};
	int length = sizeof(arr)/4;
	int j;
	int temp;
	for(int i=1; i<length; i++)
	{
		temp = arr[i];
		j = i-1;
		while(arr[j]<temp && j>=0)
		{
			arr[j+1] = arr[j];
			j--;
		}
		arr[j+1]=temp;
		}
	for(int i=0; i<length; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

함수

  • 함수 : 특정한 기능을 분리해놓은 것
  • 함수화의 장점
    1. 유지보수 및 가독성
    2. 재활용성
  • 인자, 인수, 매개변수 - 를 통해 함수로 값을 전달할 수 있음
  • 인자를 넘기면 값이 매개변수에 복사됨
    → 함수 내에서 값을 변경해도 원래 변수는 영향을 받지 않음
    → 영향을 주고싶으면 포인터를 사용
  • 결과를 return으로 반환
  • 필요한 기능을 구현해두면 가져다 쓰면됨
    → 재사용성 높아지고, main길이 줄어들고
  • 함수 이름은 기능을 설명할 수 있는 이름으로 짓도록
  • 함수의 형태
    • return은 함수가 종료되었다는 의미로 변수나 값을 반환함
      반환자료형 함수이름 (매개변수){        	
      	호출 시 실행될 함수 내부 코드        
      }
  • c에서 main()은 운영체제에서 호출함
  • main의 return은 탈출코드로, 0을 반환함으로써 에러없이 끝났다는 것을 운영체제에 알려줌
  • 반환형이 없을 때는 void를 사용, 꼭써야함
  • 인자가 없을 때도 void사용, ()비워도됨
  • 함수의 선언
    - c언어는 절차지향언어이기 때문에 함수가 선언되기 전에 호출하면 에러남
    - 정의와 선언을 main위에하거나, main 위에 원형만 선언하고 아래에 정의해주거나.
    - // 아직은 필요없어 보이지만 기능이 많아지고 코드가 길어지면 원형만 선언하고 main을 볼 수 있는게 좋음
  • 함수 연습 - 두 수의 합 구하기
#include <stdio.h>

int sumNumbers(n1, n2)
{
	return n1+n2;
}

int main()
{
	int num1;
	int num2;
	scanf("%d", &num1);
	scanf("%d", &num2);
	
	int result = sumNumbers(num1, num2);
	
  printf("a와 b의 합 : %d", result);

  return 0;
}

전역변수와 지역변수

지역변수 : 한 지역(중괄호에 의해 만들어지는 영역) 내에서만 사용할 수 있는 변수

  • main 안에 있는 것들도 다 지역변수
  • 그래서 다른 함수내에서 사용할 수 없음
  • 다른 함수에서 쓰려면 전달인자로 넘겨주거나, 전역변수로 선언한 후 사용해야함
  • 다른 함수에서 선언된 지역변수도 main에서 쓰려면 return해 줘야함
  • 지역변수들은 해당 지역을 벗어나면 소멸하게 됨
  • → 함수에서 선언된 변수는 함수가 호출될 때마다 새롭게 할당됨

전역변수 : 어느 지역에서나 사용할 수 있는 변수 // 괄호밖에 쓴 변수

  • 전역변수로 선언하면 main이든 어디든 쓸 수 있음(접근, 사용 다 가능)
  • 프로그램 시작과 동시에 메모리공간에 할당되어서 프로그램이 종료될때까지 존재함
  • 지역변수랑 다르게 별도의 값으로 초기화하지 않으면 0으로 초기화 됨
  • 덜쓰는게 좋음
  • if 지역변수와 전역변수 중에 이름이 같은 변수가 있으면 지역변수를 우선적으로 접근

포인터

  • 포인터 : 주소를 가리키는 것
  • 이것도 변수, 포인터 변수 라고도 함
  • 자료형에 따른 변수의 주소값을 저장함
#include <stdio.h>
    
int main()
{
	// 선언
	// 담고자 하는 자료형에 *(참조연산자)를 붙여서 선언
	// 자료형과 상관없이 포인터변수의 크기는 동일함
	// 운영체제 시스템이 가지는 주소값크기에 따라 결정됨
	// 32bit 시스템은 4byte, 64bit 시스템은 8byte
	// 가리킬 자료형에 따라 선언이 달라지는 이유는, 
	// 가리킬 주소가 어떤 자료형을 갖는지 알려주기 위함
	// 포인터 연산은 그 주소로 찾아가 자료형의 크기만큼 값을 읽어들여야 하니까
	// 어떤 자료형의 주소를 가리키는지 알아야함 
	// 그리고 밑에 보다시피 *의 위치는 상관없음
	int *p = NULL;  // int* p == int * p 모두 같음
	// 포인터는 주소값을 담는 변수이기 때문에 특정 숫자로 초기화할 수 없음
	// 그래서 NULL(0)로 초기화함(권장), 초기화 안하고 주소값 넣을 수도 있음(초기값은 쓰레기값)
	// 방금 NULL의 0은 0번지 아니고, **아무것도 없다는 뜻**
	int num = 15;

	p = &num; // 값을 넣을 때는 *을 붙이지 않음

	printf("int 변수 num의 주소 : %d \n", &num); // num의 주소값
	printf("포인터 p의 값 : %d \n", p);
	printf("포인터 p가 가리키는 값 : %d \n", *p);

	// int 변수 num의 주소 : 62607116
	// 포인터 p의 값 : 62607116
	// 포인터 p가 가리키는 값 : 1

	return 0;
}
  • 포인터 p는 변수 num(주소값 1000이라 치고)을 가리킴

참조연산자 *

  • 다항연산자로 쓰면 곱셈연산자, 단항연산자로 쓰면 참조연산자
  • 포인터의 이름이나 주소앞에 사용하며, 포인터가 가리키는 주소에 저장된 값을 반환함
  • *p : p에 들어있는 주소로 가서 거기있는 값을 가져옴
  • 포인터의 사칙연산도 됨, 증감연산자 조심
#include <stdio.h>

int main()
{
	int *p = NULL; 
	int num = 15;

	p = &num;
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num의 값 : %d\n\n", num);

	*p += 5;
	// p의 주소로가서 5를 증가시킴
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num 값 : %d\n\n", num);

	(*p)++;
	// 증감연산자가 참조연산자보다 우선순위가 높음
	// 괄호 쳐줬으니까 주소를 먼저 참조하고 증가시킴
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num 값 : %d\n\n", num);

	*p++;
	// 주소값이 들어있는 p를 먼저 증가시키고 참조
	// 증가한 주소에 선언해준게 없으므로 쓰레기값이 출력
	printf("포인터 p가 가리키는 값 : %d\n", *p);
	printf("num 값 : %d\n", num);

	return 0;
}
  • 포인터를 왜 쓰느냐 포인터는 함수를 사용할 때 진가를 발휘함
    함수에서 인자를 전달할때는 값에 의한 전달(복사)를 하는데,
    이러면 원래변수를 함수에서는 수정할 수 없음
    하지만 포인터를 써서 메모리주소를 넘겨주면 함수에서도 메모리에 직접적으로 참조할 수 있기 때문에 변수값을 바로 수정하는 게 가능해짐
  • 예제
#include <stdio.h>

void pointerPlus(int *num)
{
	*num += 5;
}

void numPlus(int num)
{
	num += 5;	
}

int main()
{
	int num = 15;
	printf("num 값 : %d\n", num);
	// num 값 : 15

	numPlus(num);
	printf("numPlus 사용 후 : %d\n", num);
	// numPlus 사용 후 : 15
	// 값이 복사되어 넘겨졌기 때문에 함수내에서 변경된 내용이 반영되지 않음

	pointerPlus(&num);
	printf("pointerPlus 사용 후 : %d\n", num);
	// pointerPlus 사용 후 : 20
	// 포인터를 이용해 직접적으로 메모리에 접근해 값을 변경했기 때문에 반영됨

	return 0;
}

Call by value & Call by reference

Call by value : 값을 복사해서 전달하는 방식

  • 인자로 전달되는 변수의 값을 함수의 매개변수에 복사함
  • 인자와 매개변수는 별개가 되며, 함수내에서 매개변수를 변경해도
  • 원래의 변수에 반영되지 않음
  • 원본값을 바꿀 필요가 없을 때 사용
  • swap 함수 - call by value
#include <stdio.h>

void swap(int a, int b)
{
	int temp;
	
	temp = a;
	a = b;
	b = temp;
}

int main()
{
	int a, b;
	
	a = 10;
	b = 20;
	
	printf("swap 전 : %d %d\n", a, b);
	
	swap(a, b);
	
	printf("swap 후 : %d %d\n", a, b);
	
	return 0;
}

Call by reference : 주소값을 전달하는 방식

  • 함수에서 값을 전달하는 대신 주소값을 전달하는 걸 말함
  • 참조연산자를 써서 포인터로 주소를 넘겨주긴 하는데,
    C에서는 공식적으로 call by reference를 지원하진 않음
    주소값을 복사해서 넘겨주는 것이므로 엄밀히 따지면 call by value임
    이렇게 주소값을 복사해서 넘겨주는 걸 call by address라고 함
  • 그래도 주소값을 주니까 call by reference처럼 쓸 수 있음
  • swap 함수 - call by reference
#include <stdio.h>

void swap(int *a, int *b)
{
	int temp;

	temp = *a;
	*a = *b;
	*b = temp;
}

int main()
{
	int a, b;

	a = 10;
	b = 20;

	printf("swap 전 : %d %d\n", a, b);

	swap(&a, &b);

	printf("swap 후 : %d %d\n", a, b);

	return 0;
}
  • 포인터 연습
  • 포인터 연산과 배열
    • 배열의 이름은 포인터변수와 같은 기능을 함
    • 첫번째 요소의 주소값을 나타냄
#include <stdio.h>

int main()
{
	int arr[5] = {10, 20, 30, 40, 50};
	int *arrPtr = arr;

	printf("%d\n", *arrPtr); // 10
	printf("%d\n", arr[0]); // 10

	return 0;
}
  • 이름 자체가 주소값이기 때문에 바로 포인터에 대입이 가능
  • scanf로 입력받을때 문자열은 &안붙여도 되는거도 이거였음
  • 포인터연산
  • 포인터 변수도 일반 변수처럼 값이 들어있기때문에 연산이 가능
  • 증감, 덧셈, 뺄셈이 가능하고 곱셈, 나눗셈은 안됨
  • 증감연산 - 포인터변수
    • 일반 변수의 증감연산은 1씩 증감했는데 포인터는 다름
    • 포인터 변수를 n만큼 증감시킬 때, 자료형의 크기 * n 만큼 증감함
    • 여기서 알 수 있음 → 포인터를 배열처럼 사용할 수 있다
    • *(arr+i) == arr[i]
    • 배열을 이루는 원소들의 주소는 각 자료형의 크기만큼 차이남
      배열의 이름은 첫번째 원소의 주소를 가리키므로
      i * 자료형의 크기만큼 더해지면 배열의 i 인덱스를 가리키는 셈
#include <stdio.h>

int main()
{
	int arr[5] = {10, 20, 30, 40, 50};
	double arr2[5] = {10.1, 20.2, 30.3, 40.4, 50.5};
	int *arrPtr = arr;
	double *arrPtr2 = arr2;

	printf("포인터 주소 : %d %d\n", arrPtr++, arrPtr2++);
	printf("증가 연산 후 : %d %d\n", arrPtr, arrPtr2);
	printf("변수 값 : %d %.2f\n", *arrPtr, *arrPtr2);
	// 포인터 주소 : 172507**7360** 172507**7392**
	// 증가 연산 후 : 172507**7364** 172507**7400**
	// 변수 값 : 20 20.20

	arrPtr += 2; // 배열을 구성하는 자료형의 크기 *2 만큼 증가
	arrPtr2 += 2;

	printf("증가 연산 후 : %d %d\n", arrPtr, arrPtr2);
	printf("변수 값 : %d %.2f\n", *arrPtr, *arrPtr2);
	// 증가 연산 후 : 172507**7372** 1725077416
	**// 변수 값 : 40 40.40**

	return 0;
}
  • 포인터로 버블정렬 함수만들기
#include <stdio.h>

void bubbleSort(int arr[])
{
	int temp;	
	int n = sizeof(arr)/sizeof(arr[0]);
	// printf(n);
	for(int i=0; n-1; i++)
	{
		for(int j=0; n-1-i; j++)
		{
			if(*(arr+j)>*(arr+j+1))	// 왼쪽이 더 크면
			{
				temp = *(arr+j);
				*(arr+j) = *(arr+j+1);
				*(arr+j+1) = temp;
			}
		}		
	}
}

int main()
{
	int arr[10];
	for(int i=0; i<10; i++)
	{
		scanf("%d", &arr[i]);
	}

	bubbleSort(arr);

	for(int i=0; i<10; i++)
	{
		printf("%d ", arr[i]);
	}
	
	return 0;
}
//---다시---//
#include <stdio.h>

void bubbleSort(int *arr)
{
	int temp;	
	// int n = sizeof(arr)/sizeof(arr[0]);
	// printf(n);
	for(int i=0; i<10; i++)
	{
		for(int j=0; j<9-i; j++)
		{
			if(*(arr+j)>*(arr+j+1))	// 왼쪽이 더 크면
			{
				temp = *(arr+j);
				*(arr+j) = *(arr+j+1);
				*(arr+j+1) = temp;
			}
		}		
	}
}

int main()
{
	int arr[10];
	for(int i=0; i<10; i++)
	{
		scanf("%d", &arr[i]);
	}

	bubbleSort(&arr);

	for(int i=0; i<10; i++)
	{
		printf("%d ", arr[i]);
	}
	
	return 0;
}
  • 상수 포인터
    • 값을 변경할 수 없는 상수처럼 주소값을 바꿀 수 없는 상수 포인터
    • 상수포인터도 const를 사용
    • 사용하는 위치에 따라 의미가 달라짐
    • const int *ptr : ptr을 사용해서 변수에 값을 변경하는 것을 방지
#include <stdio.h>

int main()
{
	int num = 10;
	int *ptr1 = &num;
	const int *ptr2 = &num;

	*ptr1 = 20; // 이건 가능
	num = 30;   // num이 상수화되는 건 아니라서 이것도 가능

	*ptr2 = 40; // 오류
	// ptr2를 이용해 값을 변경할 수 없음

	return 0;
}
  • int* const ptr : 포인터변수 자체가 상수화
  • 주소값을 변경할 수 없음
  • 원래 포인터를 선언할 때 은 어디든 붙일 수 있는데
    포인터를 상수화 시킬때에는 const전에
    연산자를 써줘야함
    // 만약 int const *ptr 로 쓰면 const를 맨 앞에 써준 셈
#include <stdio.h>

int main()
{
	int num1 = 10, num2 = 20;
	int *ptr1 = &num1;
	int* const ptr2 = &num1;
	
	ptr1 = &num2;
	
	*ptr2 = 30; // 포인터를 이용해서 값을 변경하는 건 가능
	ptr2 = &num2; // 오류
	// 포인터가 가리키는 주소값을 변경하는건 불가능
	// -> 이 **포인터가 오로지 num1만을 가리키**며, 절대 다른 변수를 가리키지 않겠다
	//    라고 할 때 사용
	return 0;
}
  • 포인터로 값을 변경하는 것도, 다른 변수를 가리키는 것도 막고 싶다면
    const를 두 번 쓰는 것도 가능
#include <stdio.h>

int main()
{
	int num = 10, num2 = 20;
	int *ptr1 = &num;
	const int* const ptr2 = &num;

	ptr1 = &num2;

	*ptr2 = 30;
	ptr2 = &num2;

	return 0;
}
  • 이중 포인터와 포인터 배열

이중 포인터

  • 이중 for문이 있듯이 포인터에도 이중 포인터가 있음
  • 이중 포인터 : 포인터의 주소값을 담는 변수로, 포인터의 포인터인 셈
#include <stdio.h>

int main()
{
	int num = 10; // 변수 
	int *ptr;     // 변수의 주소값을 가리키는 포인터
	int **pptr;   // 포인터의 주소값을 가리키는 포인터

	ptr = &num;
	pptr = &ptr;

	// 참조연산자를 사용하면 포인터가 가리키고 있는 변수의 값을 반환하는데
	// **이중 포인터는 포인터가 가리키는 곳(포인터)으로 가서,
	// 그 포인터가 가리키는 곳(변수)의 값을 반환함**
	printf("num : %d, *ptr : %d, **ptr : %d\n", num, *ptr, **pptr);
	// num : 10, *ptr : 10, **ptr : 10
	// -> 그래서 다 같은 값을 가리킴
	
	printf("num 주소 : %d, ptr 값 : %d, **ptr 값 : %d\n", &num, ptr, *pptr);
	// num의 주소를 ptr이 가지고 있고 ptr의 주소를 pptr이 가지고 있으므로
	// num의 주소 == ptr의 값 == pptr이 가리키는 곳(ptr)의 값
	// num 주소 : -288438268, ptr 값 : -288438268, **ptr 값 : -288438268

	printf("ptr 주소 : %d, pptr 값 : %d", &ptr, pptr);
	// ptr 주소 : -288438264, pptr 값 : -288438264

	return 0;
}
  • 이중 포인터는 포인터의 주소값을 담는 주소를 바꾸거나
    // 다른 변수를 가르키거나? 이말인가?
  • 함수에서 문자열을 바꿀때 사용
  • 다중 포인터도 있음

포인터 배열

  • 포인터를 담을 수 있는 배열
#include <stdio.h>
       
int main()
{
	int num1 = 10, num2 = 20, num3 = 30;
	int *parr[3];
	// 참조연산자 붙이는 거 말고는 일반배열이랑 똑같이 선언
       
	// 대입할 때는 변수의 주소값을 넣어줌
	parr[0] = &num1;
	parr[1] = &num2;
	parr[2] = &num3;
       
	for(int i=0; i<3; i++)
	{
		printf("parr[%d] : %d\n", i, *parr[i]);
	}
       
	return 0;
}
  • 정리 문제
#include <stdio.h>

int main()
{
		int num1 = 10, num2 = 20, num3 = 30;
		int *parr[3];

		parr[0] = &num1;
		parr[1] = &num2;
		parr[2] = &num3;

		for(int i=0; i<3; i++)
		{
			printf("parr[%d] : %d\n", i, *parr[i]);
			// 2 3.200000 1000 2008
		}

		return 0;
}

구조체

구조체란?

  • 하나 이상의 변수를 묶어서 좀 더 편리하게 사용할 수 있도록 도와주는 도구
  • 자료형이 다른 변수를 사용하기 위해 원래 하나하나 선언해줬었음
  • 똑같은 구조의 변수를 여러번 사용하는 프로그램을 만들때 사용
  • 예를 들어 동아리 주소록 시스템을 만든다
    • 동아리에 가입한 학생의 이름(문자열)
    • 학번(정수형)
    • 나이(정수형)
    • 전화번호(문자열)
    • 를 저장해야함
    • 학생이 여러명이니 구조체를 사용하지 않으면 변수를 하나하나 선언해줘야함
    • 조금 더 간단히는 배열도 가능하지만,
      같은 자료형으로만 묶을 수 있기 때문에 결국 다른 자료형을 가진 변수들은 따로 선언해야함
    • 구조체를 사용하면 편리하게 여러개의 변수를 사용하고 관리할 수 있음
      // 사용자가 직접 자료형을 만들어서 사용한다고 생각하면됨
    • 구조체를 정의
      • 구조체는 새로운 자료형을 만드는 거라 보통 main전에 선언
      • 함수 안에 선언하면 그 안에서만 쓸 수 있음
#include <stdio.h>
struct student
{
	char name[15];
	int s_id;
	int age;
	char phone_number[14];
};
// 세미콜론 붙여야 함!
// struct 구조체 이름 { 구조체 멤버들 };
// 구조체 멤버 : 구조체 안에서 사용할 변수들

int main()
{
	// - 선언하고 정의하고나면 main안에서 따로 선언해줘야함
	// - struct로 선언했던 구조체 이름과, 앞으로 사용할 변수이름을 써줌
	// - struct 구조체이름 : 자료형
	struct student goorm;	// goorm의 자료형은 student 구조체 인거
	// - 선언하고 나면 구조체 멤버를 사용함
	// - 변수이름.구조체멤버이름 으로 사용
	printf("이름 : ");
	scanf("%s", **goorm.name**);
	printf("학번 : ");
	scanf("%d", &**goorm.s_id**);
	printf("나이 : ");
	scanf("%d", &**goorm.age**);
	printf("번호 : ");
	scanf("%s", **goorm.phone_number**);
	
	printf("이름 : %s, 학번 : %d, 나이 : %d, 번호 : %s\n", goorm.name, goorm.s_id, goorm.age, goorm.phone_number);
	
	return 0;
}

구조체의 기본형

  • 선언하고 정의하고나면 main안에서 따로 선언해줘야함
  • struct로 선언했던 구조체 이름과, 앞으로 사용할 변수이름을 써줌
  • struct 구조체이름 : 자료형
  • struct student goorm // goorm의 자료형은 student 구조체 인거
  • 선언하고 나면 구조체 멤버를 사용함
  • 변수이름.구조체멤버이름 으로 사용
  • 구조체 멤버의 초기화
  • 멤버의 값을 main에서 선언할때 초기화할 수 있음
  • 초기화할때는 멤버연산자 . 와 중괄호를 사용
  • 구조체는 배열처럼 멤버전체를 초기화할 수 도 있고,
    원하는 변수만을 초기화할 수도 있음
#include <stdio.h>

struct student // student라는 구조체
{
	int age; // 멤버변수 age
	char phone_number[14]; // 멤버변수 phone_number
	int s_id; // 멤버변수 s_id
};

int main()
{
	struct student goorm = { .age = 20, .phone_number = "010-1234-5678", 10 };
	// 초기화시키는 방법1 : {.변수이름 = 값, .변수이름 = 값};
	struct student codigm = { 22, "010-8765-4321"};
	// 초기화시키는 방법2 : {값, 값}; 
	// -> 구조체를 정의했던 순서대로 들어가고 안 넣어준건 0으로 초기화됨
	
	printf("나이 : %d, 번호 : %s, 학번 : %d\n", goorm.age, goorm.phone_number, goorm.s_id);
	printf("나이 : %d, 번호 : %s, 학번 : %d\n", codigm.age, codigm.phone_number, codigm.s_id);

	return 0;
}

typedef를 이용한 구조체 선언

  • 조금 더 편리한 구조체 선언 방법
  • typedef를 쓰면 구조체이름을 생략할 수 있음
  • typedef : c언어에서 자료형을 새롭게 이름붙일때? 쓰는 키워드
  • main 에서 매번 struct를 안써도됨
  • 구조체 별칭을 지어주고 사용함
  • 구조체 별칭은 구조체정의할때 중괄호뒤에 써줌
#include <stdio.h>

typedef struct _Student { // _Student 라는 구조체
	int age;
	char phone_number[14];
} Student;  // 별칭이 Student

int main(){
	Student goorm; // 별칭만으로 선언이 가능
	
	printf("나이 : ");
	scanf("%d", &goorm.age);
	printf("번호 : ");
	scanf("%s", goorm.phone_number);
	
	printf("----\n나이 : %d\n번호 : %s\n----", goorm.age, goorm.phone_number);
	
	return 0
  • 구조체 별칭은 구조체 이름과 동일하게 써도 되는데, 일반적으로 둘다 쓸때는 구조체 이름 앞에 _를 붙임

익명 구조체

  • 구조체 이름도 생략가능 하다 했던거
  • typedef를 쓰면 구조체 이름을 쓰지않고별칭만 사용하는 것도 가능
#include <stdio.h>

typedef struct { // 이름이 없음
	int age;
	char phone_number[14];
} Student;  // 별칭만 있음 - 이름없이 별칭만 있는 걸 **익명구조체**라고 함

int main(){
	Student goorm;
	
	printf("나이 : ");
	scanf("%d", &goorm.age);
	printf("번호 : ");
	scanf("%s", goorm.phone_number);
	
	printf("----\n나이 : %d\n번호 : %s\n----", goorm.age, goorm.phone_number);
	
	return 0;
}
  • typedef로 구조체 별칭을 사용할때도 문법은 똑같
    → 접근할때는 .연산자 사용

구조체 배열

  • 구조체에도 배열이 있음
  • 구조체 배열도 일반 배열처럼 선언 및 초기화
#include <stdio.h>

typedef struct {
	char name[30];
	int age;
} Student;

int main(){
	Student goorm[3] = { {.name = "해리 포터"}, {.name = "헤르미온느 그레인저"}, {.name = "론 위즐리"} };
	// 구조체도 문자열은 선언할때만 초기화할 수 있음 -> 선언과 동시에 초기화
	// 똑같이 배열 크기 적고 중괄호로 요소 나열

	goorm[0].age = 10;
	goorm[1].age = 10;
	goorm[2].age = 10;
	// 인덱스를 통해 구조체타입의 원소를 가리키고
	// .연산자를 통해 각 구조체의 멤버변수에 접근할 수 있음
	
	printf("이름 : %s / 나이 : %d\n", goorm[0].name, goorm[0].age);
	printf("이름 : %s / 나이 : %d\n", goorm[1].name, goorm[1].age);
	printf("이름 : %s / 나이 : %d\n", goorm[2].name, goorm[2].age);
	
	return 0;
}

구조체 포인터

  • 잠깐 복습하자면,
    포인터는 어떤 변수의 주소를 담아서 변수를 가리키는 변수
  • 구조체 포인터도 마찬가지
    구조체를 가리키는 포인터를 말함
  • 구조체는 자료형이 만들어준거 임 : struct 구조체이름 → 이게 자료형
  • 그래서 선언할때도 자료형을 나타내줘야함
  • struct student *ptr; 이렇게 선언해야함
  • ptr이 구조체는 아님! 구조체를 가리키는 포인터임
#include <stdio.h>

typedef struct {
	int s_id;
	int age;
} Student;

int main(){
	Student goorm;
	Student *ptr; // 구조체 포인터 ptr선언
	
	ptr = &goorm; // goorm의 주소값을 대입
	
	(*ptr).s_id = 1004;
	(*ptr).age = 20;
	// ptr이 가리키는 구조체의 멤버변수 s_id에 1004를 대입
	// 참조연산자보다 .연산자가 우선순위가 높아서 그냥쓰면
	// 구조체가 아닌 포인터변수를 구조체처럼 참조하려고 해서 오류발생함
	// 구조체 포인터 사용시에는 항상 괄호 사용!
	// 해야하는데 더 편한 방법을 만듦
	// 화살표 기호 -> 를 쓰면 괄호없어도 **알아서 주소로 찾아가서 구조체를 참조**
	prt->age = 20;

	printf("goorm의 학번 : %d, 나이: %d\n", goorm.s_id, goorm.age);
}

중첩 구조체

  • 구조체안에 구조체를 선언할 수있음
#include <stdio.h>

typedef struct {
	char name[15];
	int age;
} Teacher;

typedef struct {
	char name[15];
	int age;
	Teacher teacher; // **다른 구조체를 멤버로 포함할 수 있음**
} Student;

int main(){
	Student Student;
	Teacher Teacher;
	
	Student.teacher.age = 30; // 이렇게 **접근**
	Teacher.age = 40;
	
	return 0;
}
  • 이렇게 구조체를 중첩으로 사용하면 여러가지 정보를 관리하기에 용이함

자기참조구조체

  • 구조체는 자기 자신을 참조하도록 자기와 똑같은 타입의 구조체를 멤버로 가질 수 있음
typedef struct {
	char name[15];
	int age;
	struct Student *ptr; // 자기자신을 가리키는 포인터를 멤버로 가질 수 있음
} Student;
  • 이러한 자기참조구조체는 연결리스트나 트리를 만들때 사용됨

구조체와 함수

  • 구조체를 함수에 전달
  • 구조체를 인자로 전달할 때에는 두가지 방법이 있음

1. 포인터로 전달 - 보통 이걸 씀

- 값을 복사하면 구조체는 크기가 커질수록 복사할 공간도 더 필요하게됨
- 낭비되어 비효율적이기 때문에 값을 바꿀 필요가 없어도 보통 포인터로 전달
        
  #include <stdio.h>
            
  typedef struct {
  	int s_id;
	int age;
  } Student;
            
	void print_student(Student *s){ // 포인터로 받음
		s->s_id = 2000;
		s->age = 25;
            	
		printf("학번 : %d, 나이 : %d\n", s->s_id, s->age);
		// 2000, 25
	}
            
	int main(){
		Student s;
            
		s.s_id = 1000;
		s.age = 20;
            	
		print_student(&s); // 주소로 전달
                
		printf("학번 : %d, 나이: %d\n", s.s_id, s.age);
		// 2000, 25 
	}

2. 구조체 그대로 전달

#include <stdio.h>

typedef struct {
	int s_id;
	int age;
} Student;

void print_student(Student s){
	s.s_id = 2000;
	s.age = 25;
	
	printf("학번 : %d, 나이 : %d\n", s.s_id, s.age);
	// 2000, 25
}

int main(){
	Student s;

	s.s_id = 1000;
	s.age = 20;
	
	print_student(s);
    
	printf("학번 : %d, 나이: %d\n", s.s_id, s.age);
	// 1000, 20 
}
  • 함수내에서는 바뀐값으로 출력되지만 main에서의 값은 바뀌지 않았음

구조체 실습

#include <stdio.h>

typedef struct
{
	char name[15];
	int kor; // 국어
	int eng; // 영어
	int math; // 수학
	float avg; // 평균
} Student;

int main()
{
	Student s[3];
	
	for(int i=0; i<3; i++)
	{
		scanf("%s", s[i].name);
		scanf("%d", &s[i].kor);
		scanf("%d", &s[i].eng);
		scanf("%d", &s[i].math);
		s[i].avg = (s[i].kor+s[i].eng+s[i].math)/3.0;
	}

	for(int i=0; i<3; i++)
	{
		printf("%s", s[i].name);
		printf(" %.1f\n", s[i].avg);
	}
	
	return 0;
}

+@

  • 정적변수(static)
    1. static이라는 키워드를 사용해 선언
    2. 함수가 호출되어 선언되고 실행이 끝나도 정적변수는 사라지지 않음
      // 원래 함수 내에 호출된 지역변수는 함수가 종료되면 메모리를 시스템에 반환하면서 변수도 사라지는데 static 을 사용하면 함수 종료시에도 반환되지 않음
    3. 정적변수는 프로그램 실행 중에 딱 한번만 생성되고 초기화됨
#include <stdio.h>

void bell();

int main(){
    bell(); // ➊ 첫 번째 주문
    bell(); // ➋ 두 번째 주문
    bell(); // ➌ 세 번째 주문
}

void bell() { // ➍ bell( ) 함수 실행
  static int order = 0; // ➎ 정적 변수 order 선언
  order++;

	printf(<span class="brown">"현재 주문 번호는 %d입니다.\n"</span>, order);
	// 현재 주문 번호는 1입니다.
	// 현재 주문 번호는 2입니다.
	// 현재 주문 번호는 3입니다.
	return 0
}

참고자료

0개의 댓글