728x90

파일 입출력

1. 파일 입출력 절차
- 어떤 파일에 입출력 할지에 대한 명시 필요
- 필요한 자료형 및 변수는 <stdio.h>에 선언

(1) FILE 포인터 선언: FILE *fp
(2) 파일 열기: fopen()
(3) 사용: fscanf(), fprintf()
(4) 파일 닫기: fclose()

1) 파일 포인터 선언
- FILE은 파일 입출력 시 필요한 정보를 담은 구조체
- 파일 입출력 시 각 파일마다 하나의 FILE 포인터를 연결하여 사용
- 선언 형식: FILE *fp;

<표준 스트림>
- 표준 입출력 장치도 논리적으로 파일로 간주하여 처리
- stdin: 표준 입력 스트림
- stdout: 표준 출력 스트림
- stderr: 표준 오류 출력 스트림

2) 파일 열기
- fopen() 함수
- 해당 파일에 대한 연결 요청을 위미
- 해당 파일을 사용할 수 있도록 파일 표인터를 반환
- fopen() 함수 호출 시, 이 함수의 반환 값을 반드시 검사하여 파일이 정상적으로 열렸는지 확인해야 함

<fopen() 함수>
- 함수 원형: FILE *fopen(char *filename, char *filemode)
- 함수 인자: filename(연결할 파일 이름), filemode(파일 접근 방식에 따른 모드)
- 반환값: 파일열기 성공 >> FILE 포인터 반환 / 파일열기 실패 >> NULL 반환

<함수 인자 filename>
- 해당 파일의 위치와 이름 명시
- 위치는 상대경로 또는 절대경로 표현
- 절대경로: 드라이브 명부터 해당 파일이 있는 위치까지의 전체 경로
- 상대경로: 현재 작업 폴더를 기준으로 해당 파일이 있는 위치까지의 경로

<함수 인자 filemode>
(1) 'r'  
- 읽기 전용
- 파일을 열 수 없는 경우 >> NULL 반환

(2) 'w'
- 쓰기 전용
- 파일이 없는 경우 >> 새로 빈 파일을 생성
같은 이름의 파일이 존재하는 경우 >> 해당 파일의 내용 삭제하고 새로 파일 생성

(3) 'a'
- 추가 전용
- 파일이 없는 경우 >> 새로 빈 파일을 생성
같은 이름의 파일이 존재하는 경우 >> 기존 파일의 마지막 부분에 내용을 추가

3) 파일 입출력 함수
- 문자 단위 입출력: fgetc(), fputc()
- 문자열 단위 입출력: fgets(), fputs()
- 지정 형식 입출력: fscanf(), fprintf()
- 블록 입출력: fread(), fwrite()

4) 파일 닫기
- fclose() 함수
- 현재 열린 파일과 FILE 포인터와의 연결 해제

<fclose() 함수>
- 함수 원형: int fclose(FILE *fp)
- 함수 인자: 파일 포인터 변수명 fp
- 반환 값: 파일 닫기에 성공 >> 0 반환 / 파일 닫기에 실패 >> EOF 반환

2. 텍스트 파일 입출력

1)  지정형식 파일 입력 함수: fscanf()
- 데이터를 지정한 형식으로 데이터를 읽어오는 함수
- 여러 형태의 자료들(정수, 문자, 문자열 등) 을 한번에 입력 가능
- 함수의 첫번째 인자로 파일 포인터가 사용된다는 것을 제외하고는 scanf() 함수와 사용법 동일
- 첫번째 인자를 stdin으로 지정하면, 표준 입력으로 부터 입력
- 첫번째 인자를 FILE 포인터로 지정하면, 해당 파일로부터 데이터 입력받음

<fscanf() 함수>
- 함수 원형: int fscanf(FILE *fp, char *format, ...);
- 함수 인자: fp(파일 포인터 변수명), format(형식 제어 문자열), ...(입력하고자 하는 변수 리스트)
- 반환 값: 성공 >> 입력한 변수의 개수를 반환 // 실패 >> EOF 반환

2) 지정형식 파일 출력 함수: fprintf()
- 지정한 형식에 맞추어 파일로 출력
- 함수의 첫번째 인자로 파일 포인터가 사용된다는 것을 제외하고는 printf() 함수와 사용법 동일
- 첫번째 인자를 stdout으로 지정하면, 표준 입력으로 부터 출력
- 첫번째 인자를 FILE 포인터로 지정하면, 해당 파일로부터 데이터 출력

<fprintf() 함수>
- 함수 원형: int fprintf(FILE *fp, char *format, ...);
- 함수 인자:  fp(파일 포인터 변수명), format(형식 제어 문자열), ...(출력하고자 하는 변수 리스트)
- 반환 값: 성공 >> 출력한 데이터의 바이트 수 // 실패 >> 음수 반환

3) 문자 단위 파일 입출력 함수: fgetc(), fputc()
- 문자 한개를 파일로부터 읽고 쓰기 위한 함수

<fgetc() 함수>
- 함수 원형: int fgetc(FILE *fp);
- 함수 인자: 파일 포인터 변수명 fp
- 반환 값: 성공 >> 읽은 문자 반환 // 실패 >> EOF 반환

<fputc() 함수>
- 함수 원형: int fputc(int ch, FILE *fp);
- 함수 인자: ch(출력하고자 하는 문자 상수 또는 변수),  fp(파일 포인터 변수명)
- 반환 값: 성공 >> 출력하는 문자 ch 반환 // 실패 >> EOF 반환

4) 문자열 단위의 파일 입력 함수: fgets()
- 파일에 쓰여진 문자열을 읽는데 사용하는 함수
- 파일 fp로부터 문자를 읽어서 s가 가리키는 곳에 저장
n-1개의 문자를 읽거나, 개행문자를 만나거나, 파일에 끝에 도달하면 종료
- 문자열 마지막에 널 문자 추가
- 공백이나 탭 문자를 만나도 계속 읽음
- 개행문자도 문자열에 저장

<fgets() 함수>
- 함수 원형: char *fgets(char *s, int n, FILE *fp);
- 함수 인자: s(파일로부터 읽은 문자열을 저장할 포인터), 
n(저장할 문자의 최대개수, 널문자 포함), fp(파일 포인터 변수명)
- 반환 값: 성공 >> 문자열 s 반환 // 파일의 끝에 도달 or 실패 >> NULL 반환

5) 문자열 단위의 파일 출력 함수: fputs()
- 파일에 문자열을 출력하는 함수
- 문자열의 끝을 나타내는 널 문자는 파일에 쓰지 않으며, 그 뒤에 개행문자도 자동으로 들어가지 않음

<fputs() 함수>
- 함수 원형: int fputs(char *str, FILE *fp);
- 함수 인자: str(출력하고자 하는 문자열), fp(파일 포인터 변수명)
- 반환 값: 성공 >> 출력한 바이트 수 반환 // 실패 or 오류발생 >> EOF 반환

6) 파일의 끝 확인하기: feof()
- 파일의 끝까지 데이터를 모두 읽어들인 상태인지를 확인
EOF(End Of File): 파일의 끝을 표현하기 위해 정의해놓은 상수 -1
- EOF도 파일의 내용으로 간주되므로 주의

<feof() 함수>
- 함수 원형: int feof(FILE *fp);
- 함수 인자: 파일 포인터 변수명 fp
- 반환 값: 파일의 끝이면 >> 0이 아닌 값 반환 // 파일의 끝이 아니면 >> 0을 반환


728x90
728x90

1. 비트연산자

1) 2진수와 16진수
- 컴퓨터의 기본 정보단위는 bit와 byte로 표현
- 2진수, 16진수 표기가 편리한 경우가 있음
1 bit: 2진수 한 자리
1 byte: 16진수 두 자리
2진수 4자리 == 16진수 1자리

2) 비트 연산
- 비트 단위로 처리되는 연산
1: 참 
0: 거짓
- 비트 연산의 종류: 비트 단위 논리 연산, 비트 단위 이동 연산

<비트 단위 논리 연산>
(1) x & y  
- x와 y 비트 단위 AND(논리곱) 연산
- 둘 다 비트가 1인 경우: 1
- 둘 중 하나라도 비트가 0인 경우: 0

(2) x : y 
- x와 y 비트 단위 OR(논리합) 연산
- 둘 중 하나라도 비트가 1인 경우: 1
- 둘 다 비트가 0인 경우: 0

(3) x ^ y 
- x와 y 비트 단위 XOR 연산
비트의 합이 홀수 or 1의 개수가 홀수 or 두 비트가 다른 경우: 1
비트의 합이 짝수 or 1의 개수가 짝수 or 두 비트가 같은 경우: 0

(4) ~ x

- x 에 대한 NOT 연산
- 비트가 1인 경우: 0
- 비트가 0인 경우: 1

<비트 단위 이동 연산>
(1) x << n
- x를 n비트 왼쪽 이동
- 왼쪽 n개의 비트는 삭제
- 오른쪽에 남은 공간은 0으로 채우기

(2) x>>n
- x를 n비트 오른쪽 이동
- 오른쪽 n개의 비트는 삭제
- 왼쪽에 남은 공간은 0으로 채우기

3) 비트연산의 피연산자
- 피연산자는 정수형(char, int, long 등) 에 대해서만 연산 가능
- 특별한 이유가 없으면 unsigned 정수 사용 (0과 양수)
- 이동 연산에서 빈자리는 0으로 채우는 것이 기본이지만, signed 정수는 1로 채워지는 경우도 있음

2. 재귀 함수

1) 함수 호출과 반환
- 어떠한 함수가 존재한다 가정할 때, 해당 함수를 여러번 반복하여 호출하면?
- 하나의 함수가 반복하여 여러번 요청을 처리하는 것이 아니다.
동일한 기능을 가진 3개의 함수가 각각의 요청을 처리하는 것이다.

- 따라서 하나의 함수의 내부에서 동일한 이름의 함수를 호출 할 수 있다.
- 일을 시행하고 있는 해당 함수가 다시 일을 처리하는 것이 아닌, 
- 동일 기능의 다른 함수에게 요청하는 것이기 때문

2) 재귀 함수 동작 과정
하위 함수를 주어진 만큼 반복하여 호출하여, 

- 최하위 함수까지 간 이후
최상위 함수로 차례대로 복귀한다

예시)

void circle(int x){
	printf("call: x=%d\n", x);
	if (x > 1) circle(x-1);
	printf("return: x=%d\n", x);
}

int main(){
	circle(3);
	return 0;
}



>> 출력

call: x=3 // circle(3) 호출
call: x=2 // circle(2) 호출
call: x=1 // circle(1) 호출
return: x=1 // circle(1) 마무리
return: x=2 // circle(2) 마무리
return: x=3 // circle(3) 마무리



3) 재귀 함수
함수 내부에서 자기와 동일한 이름의 함수를 호출하는 함수
- 유사 개념: 점화식
- 주의 사항: 재귀 호출의 종료 조건이 없으면 제대로 동작하지 않음
- 점화식에서 초기항이 없으면 정의되지 않는 것과 유사

3. 라이브러리 활용

1) 난수(Random Number) 생성
- 난수: 정의된 범위 내에서 임의로 추출되는 수
- C언어에서는 난수를 생성하는 함수 제공
- ex) rand(), srand(), time()

(1) rand() 함수
<stdlib.h>에 선언되어 있음
0 ~ RAND_MAX 사이의 임의의 수 리턴
- RAND_MAX는 stdlib.h에 정의된 상수 (32767)

예시)

#inlcude<stdio.h>
#include<stdlib.h>

int main(){
	for (int i = 0; i<5; i++){
		printf("%d", rand());
	}
	return 0;
}



(2) srand() 함수
<stdlib.h>에 선언되어 있음
rand() 함수의 시드(seed) 변경
시드(seed) 값이 동일하면, 특정 회차의 rand() 함수로 생성한 난수는 항상 동일하게 나온다.
- 따라서 완전히 랜덤한 난수 값을 생성하기 위해서는 srand()로 시드 값도 변경해 줄 필요가 있음

예시)

#inlcude<stdio.h>
#include<stdlib.h>

int main(){
	// 0. 시드값이 동일할 때 첫번째 난수 값 확인해보기
	for (int i = 0; i<5; i++){
		srand(30000); // 시드값 30000 고정
	printf("%d", rand()); // 시드값 30000의 첫 난수
	}

	// 1. 시드값 변경하면서, 난수 생성
	for (int i = 0; i<5; i++){
		srand(i); // 시드값 변경
		printf("%d", rand());
	}
	return 0;
}



(3) time() 함수
<time.h>에 선언되어 있음
현재 시스템의 시간에 의해 결정되는 정수 리턴
- 실행할 때마다 시드를 바꾸기 위해서 사용

예시) 실행할 때 마다 시드값을 랜덤하게 바꾸기

#inlcude<stdio.h>
#include<stdlib.h> // rand 함수 사용을 위해
#include<time.h> // time 함수 사용을 위해

int main(){
	srand( time(NULL) ); // 시드를 현재시간으로 지정
	// 시드가 시간에 따라 매번 바뀌어 완전히 랜덤한 난수 생성
	for (int i = 0; i<5; i++){
		printf("%d", rand()); 
	}
	return 0;
}



(4) min 이상 max 미만의 난수 생성하기
>> rand() / ((double)RAND_MAX +1) * (max-min) + min

rand() : 0~RAND_MAX 사이의 임의의 정수 생성
/ ((double)RAND_MAX +1) : 생성된 정수를 [0, 1) 사이의 소수로 변환
* (max-min) : [0, 1) 사이의 소수를 [0, max-min) 사이의 값으로 변환
+ min : 수를 min 만큼 이동시켜, [min, max) 사이의 수로 변환

2) 실행시간 측정
clock() 함수 사용
<time.h>에 선언되어 있음
호출 당시의 시스템 시각을 CLOCKS_PER_SEC (time.h에 정의된 상수) 단위로 반환
- 초 단위의 시간을 얻기 위해서는 clock() 함수에 의해 측정된 시각을 CLOCKS_PER_SEC로 나누어야 함

예시) 난수 생성 함수 실행시간 측정

#inlcude<stdio.h>
#include<stdlib.h> // rand 함수 사용을 위해
#include<time.h> // time 함수 사용을 위해

int main(){
	clock_t start, finish;
	double duration;

	start = clock(); // 시작 시각
	srand( time(NULL) ); // 시드를 현재시간으로 지정
	// 시드가 시간에 따라 매번 바뀌어 완전히 랜덤한 난수 생성
	for (int i = 0; i<5; i++){
		printf("%d", rand()); 
	}
	finish = clock(); // 종료 시각

	duration = (double)(finish - start) / CLOCKS_PER_SEC; // 실행시간 계산
	printf("실행 시간: %lf 초\n", duration); // 실행시간 출력

	return 0;
}



728x90
728x90

1. 동적 할당 개요

1) 정적 메모리 할당
- 지금까지 기억 장소 확보를 위해 사용한 방식은 변수 선언
- 변수는 모두 코드 작성 단계에서 정해지고, 할당되는 메모리 크기는 프로그램을 실행할 때마다 일정
- 이는 프로그램 실행 전에 메모리 할당 크기가 정해지고, 메모리할당 도중 변하지 않음
- 정적 할당

2) 정적 할당 방식의 문제점
필요한 메모리 크기를 알 수 없는 경우, 충분히 큰 크기를 가정해서 선언해야 함
- 프로그램 실행 전, 메모리를 얼마나 필요로 할 것인지 정확하게 알 수 없는 경우, 
메모리를 효율적으로 사용하기 어려움
- 예시) 몇명의 사람이 회원가입을 할지 알 수 없음 >> 충분히 큰 크기 설정 
>> 회원수가 설정한 크기보다 작은 경우 >> 남은 메모리 낭비
>> 회원수가 설정한 크기보다 큰 경우 >> 배열 크기를 재조정 해야함

3) 동적 메모리 할당
프로그램을 실행하는 동안 결정되는 크기에 따라 메모리를 할당하는 방식
메모리를 효율적으로 사용할 수 있음
- 앞 웹사이트 상황에서, 크기가 5만인 메모리를 할당 받아 사용하다
>> 회원수가 설정한 크기보다 작은 경우 >> 적은 용량의 메모리를 새로이 할당 받아 사용
>> 회원수가 설정한 크기보다 큰 경우 >> 더 많은 용량 메모리를 새로이 할당 받아 사용
>> 기존 메모리 반납

2. 동적 메모리 사용 절차

1) 메모리 사용 절차
- 메모리 사용을 위해서는 메모리를 할당하고 해제하는 과정 필요
(1) 할당: 필요한 메모리를 시스템으로부터 받기
(2) 사용: 할당된 메모리 사용
(3) 해제: 사용이 끝난 메모리를 시스템에 반납

- 정적할당: 이 과정이 프로그램과 함수의 실행 및 종료에 따라 자동으로 수행됨
- 동적할당: 프로그래머가 필요한 과정을 코드에 명시적으로 작성해 주어야 함

2) 동적 할당 헤더파일: <stdlib.h>
- 동적으로 메모리를 할당하고 해제하는 함수는 <stdlib.h> 헤더파일에 존재

3) 동적 메모리 할당: malloc() 함수
<malloc() 함수>
- 요청된 연속된 메모리 할당(memory allocation)
(1) 함수 원형
void *malloc(unsigned int size);

(2) 함수 인자 
- 할당받을 메모리 크기(바이트 단위)
- 보통 sizeof() 연산자 활용

(3) 기능 및 반환값
할당 받은 메모리의 시작 주소를 반환
할당에 실패하면 >> NULL 반환
- 반환 값의 유형: void형 포인터

(4) 사용 예시

// 정수 2개를 저장할 공간 할당
malloc( 2*sizeof(int) );
malloc( 8 );


4) 동적 메모리 연결
- 변수 이름을 붙일 수 없기에, 포인터 변수를 이용하여 간접적으로 참조

(1) 포인터 변수 선언 >> 형변환으로 간접 참조
자료형 *포인터_변수명 = NULL;
포인터_변수명 = (자료형 *)malloc(메모리_크기);
(2) 포인터 변수 선인 및 참조
자료형 *포인터_변수명 = (자료형 *)malloc(메모리_크기);

>> 사용하고자 하는 자료형으로 명시적 형변화, 
할당된 메모리를 어떤 자료형으로 사용할 지 모르므로 (void *)형 변환

- 예시)

int *p = NULL;
p = (int *)malloc( 10*sizeof(int) );



5) 동적 메모리 사용
- 할당된 메모리를 포인터 변수에 연결한 후에는 포인터를 사용하는 방식으로 이용
- 예시)

int *p = (int *)malloc( 10*sizeof(int) );
p[0] = 1; // *p = 1; 과 동일
*(p+2) = 3; // p[2] = 3; 과 동일



6) 동적 메모리 해제: free() 함수
<free() 함수>
- 지정된 메모리 공간을 해제(free)
(1) 함수 원형
void free(void *ptr);

(2) 함수 인자
- 해제할 메모리 공간을 가리키는 포인터 변수

(3) 기능
ptr이 가리키는 메모리 해제
ptr 변수 자체를 해제시키는 것이 아님

7) 동적 메모리를 안전하게 사용하기 위한 주의사항
malloc()의 반환 값을 검사하여 메모리 할당의 성공여부 확인하기
- NULL 포인터 반환하는 경우: 요청한 메모리 크기만큼 연속된 메모리공간을 확보하지 못할 때
>> 오류상황 알리고, 함수 종료
- NULL 포인터 반환하지 않는 경우
>> free()함수 호출

8) 메모리 할당 방식 비교
<정적 메모리 할당>
프로그램 작성단계에서 메모리 크기 결정
시스템이 자동으로 메모리 할당 및 해제
- 프로그램 실행 중 메모리 크기 변경 불가
- 메모리 누수 가능성 없음
- 메모리 사용 비효율적

<동적 메모리 할당>
프로그램 실행 중에 메모리 크기 결정
- 개발자가 명시적으로 할당 및 해제함수 호출
- 프로그램 실행 중 메모리 크기 변경 가능
- 메모리 누수 가능성 있음
- 메모리 사용 효율적

3. 동적 메모리 할당 프로그램 예제

1) 동적으로 메모리를 할당 받아 사용하는 기본 프로그램
>> 정수 하나를 입력받을 메모리를 할당하고 저장하기(동적할당의 의미 없음)

#include<stdio.h>
#inlcude<stdlib.h>

int *p = (int *)malloc(sizeof(int));
if (p == NULL) return -1;

*p = 1;



2) 일차원 배열을 동적 할당 받아 사용하는 프로그램
>> 정수 배열을 입력받을 메모리를 할당하고 저장하기

#include<stdio.h>
#include<stdlib.h>

// 정수 배열의 길이 n 입력 받기
int n;
scanf("%d", &n);
// n 길이의 정수 배열 포인터 생성
int *p = (int *)malloc(n * sizeof(int));
if (p == NULL) return -1;
// 정수 저장
for (int i = 0; i<n; i++){
	scanf("%d", &p[i]);
}



3) 동적 메모리 할당을 사용한 문자열 처리 프로그램
(1) 문자열을 입력받기 위한 충분한 크기의 문자 배열 tmp 선언 (정적할당)
(2) tmp에 문자열 입력받아 저장
(3) tmp의 길이를 계산하여 그 크기+1 사이즈의 메모리 할당
(4) 할당받은 메모리 공간에 tmp 문자열 복사

>> 문자열 입력받을 메모리 할당 및 저장

#include<stdio.h>
#include<string.h>
#inlcude<stdlib.h>

char tmp[100]; // 길이가 넉넉한 문자 배열 선언
scanf("%s", tmp); // 입력 받기

char *str = (char *)malloc(strlen(tmp)); // 문자를 저장할 포인터 str 생성 및 메모리 할당
if (str == NULL) return -1; // 메모리 할당 검사
strcpy(str, tmp); // 문자열 복사



4) 동적 메모리 할당을 사용하여 2차원 배열 할당하는 프로그램
이중 포인터 사용하기

>> 문자열의 배열을 입력받을 메모리 할당 및 저장

#include<stdio.h>
#include<string.h>
#include<stdlib.h>

int n;
scanf("%d", &n); // 문자열 배열의 길이 n

// 이중 포인터 선언 및 n 만큼 메모리 할당
char **pch = (char **)malloc(n * sizeof(char *));
if (pch == NULL) return -1; // 메모리 할당 검사

// 임시로 입력받을 문자열 tmp 선언
char tmp[100];
for (int i = 0; i<n; i++){
	scanf("%s", tmp); // tmp에 문자열 입력받기
	pch[i] = (char *)malloc(strlen(tmp) + 1); // tmp 길이 +1 만큼 메모리 할당하기
    if (pch[i] == NULL) return -1; // 메모리 할당 검사
	strcpy(pch[i], tmp); // tmp에 저장된 문자열 복사
}



4. 기타 동적 메모리 할당 함수

1) calloc() 함수
- 해당 개수의 자료형 만큼 메모리 할당 및 0으로 초기화
(1) 함수 원형
- void *calloc(unsigned int num, unsigned int size);

(2) 함수 인자
num: 동적으로 할당받을 원소의 개수
size: 원소 한개의 크기(바이트 단위)

(3) 반환 값
(num*size) 바이트 수 만큼 할당하고, 할당된 메모리를 모두 0으로 초기화 후 시작 위치 반환
실패하면 NULL 반환

(4) 사용 예시

int *p = NULL;
p = (int *)calloc(5, sizeof(int));
// malloc(5*sizeof(int)); 와 유사


2) realloc() 함수
- 해당 위치의 주소에 새로운 크기의 메모리 할당
(1) 함수 원형
- void *realloc(void *ptr, unsigned int size);

(2) 함수 인자
ptr: 확장 크기를 변경할 메모리의 시작 주소
new_size: 변경 후 메모리의 전체 크기(바이트 단위)

(3) 반환 값
- ptr이 가리키는 메모리 크기를 new_size 바이트 크기로 조정하고, 조정된 메모리의 시작위치 반환
실패하면 NULL 반환

(4) 사용 예시

char *p = (char *)malloc(5);
p = (char *)realloc(p, 10);
// 5 바이트 였던 p의 메모리 크기를 10으로 변경
// 기존 위치에 새로운 크기를 확보할 수 없으면, 기존위치의 공간을 해제하고 새로운 위치에 공간 할당






728x90
728x90

1. 구조체 개요

1) 구조체(Structure)
의미상 관계가 있는 항목을 그룹으로 묶어 표현한 자료형
- 구조체는 int, char와 같이 변수의 모양을 의미
- 차이는 int, char는 기본적으로 정해져 있는 기본 자료형이고, 
구조체는 사용자가 용도에 맞게 만들어 사용하는 사용자 자료형이다.
- 구조체를 구성하는 변수들을 멤버라고 부름

2. 구조체 정의, 선언, 사용

1) 구조체 정의
- 자료형을 정의하는 것 - 변수 선언과는 다른 개념
struct 라는 키워드를 사용
- 일반적인 구조체 정의 형식
>> 형식

struct 구조체자료형이름{
	멤버자료형 멤버변수;
	멤버자료형 멤버변수;
	...
}; // 세미콜론



>> 예시

struct student{
	int id;
	char name[8];
	double grade;
};



- 정의만 해서는 메모리에 공간이 할당되지 않음

2) 구조체 사용
- 일반적인 변수 선언과 동일한 형태
- 자료형 변수명;
- 구조체에서는 'struct 구조체자료형이름' 이 통채로 자료형을 나타냄

struct student st1, st2; // 구조체 변수 선언



변수 선언을 해야 비로소 메모리에 공간이 할당됨
- 구조체 변수 자체는 메모리 공간을 따로 가지지 않고, 멤버들의 메모리로 구성됨

3) 구조체 변수 초기화
- 다른 초기화와 유사하게 중괄호 안에 멤버 변수 순서대로 초기화 값 나열
>> 예시

struct student st1 = {10, "Tom", 3.2};



4) 멤버변수 사용
구조체 멤버 연산자 (.) 사용 : 구조체변수.멤버변수
- 비교) 구조체 변수는 멤버변수 전체를 나타냄
- 멤버변수의 자료형에  의해 사용방법이 결정됨
- int형 멤버 변수 id는 일반적인 int 변수를 사용하는 것과 동일한 방식으로 사용
>> 예시

stuct student st1, st2;
st1.id = 10;
st1.id = st1.id * 2;
printf("id: %d, st1.id);


5) 구조체 정의와 선언의 다양한 형태

(1) 자료형 정의와 변수 선언을 따로 (가장 일반적 형태)

struct student{ // 구조체 자료형 정의
	int id; char name[8]; double grade;
};

void main(){
	struct student st1; // 지역 변수 st1 선언
}



(2) 자료형 정의와 변수 선언을 동시에

struct student{ // 구조체 자료형 정의
	int id; char name[8]; double grade;
} st; // 전역 변수 st 선언
void main(){
	struct student st1; // 지역 변수 st1 선언
}



- 정의 선언과 동시에 초기화도 가능

struct student{ // 구조체 자료형 정의
	int id; char name[8]; double grade;
} st = {10, "Tom", 3.2}; // 전역 변수 st 선언 및 초기화



(3) 구조체 자료형 이름 생략 가능
- 다만, 자료형 이름이 없으므로, 다른 곳에서 선언 불가능

struct { // 구조체 자료형 정의 (이름 없음)
	int id; char name[8]; double grade;
} st; // 전역 변수 st 선언(가능)



(4) 구조체 자료형을 함수 안에서 정의
- 정의한 함수 안에서만 사용 가능

void main(){
	struct student{ // 구조체 자료형 정의 (함수 안)
		int id; char name[8]; double grade;
	};
	struct student st1; // 구조체 변수 st1 선언
}



6) 구조체에 사용 가능한 연산자
- 구조체는 사용자가 만든 자료형이기 때문에 
기본 자료형인 int, char, double 등에 비해 사용가능한 연산자가 제한적임
산술연산, 비교연산은 지원 안 됨
대입연산자(=), 주소연산자(&), 간접참조연산자(*), sizeof 연산자 등등.. 은 사용 가능

7) 구조체 변수의 대입 연산
- 모든 멤버 변수에 대해, 대입 연산이 수행됨 

>>예시

struct student st1 = {10, "Tom", 3.2};
struct student st2;
st2 = st1; // 가능



8) 구조체에 할당되는 메모리 크기
- 구조체 크기는 구조체 멤버의 크기의 합이 아닐 수도 있다
sizeof 연산자를 통해 구조체의 크기를 이용하자

3. 구조체 배열
- 구조체가 배열, 포인터, 함수등과 결합되어 확장될 수 있음

1) 구조체 배열
- 구조체가 원소로 사용된 배열
- 단, 구조체끼리의 묶음만 허용
- 선언, 접근, 초기화 등 구조체 배열에 대한 문법
- 일반 배열과 동일

2) 구조체 배열 선언과 접근: [] 사용

struct student ast[3];

ast[0].id = 10;
strcpy(ast[0].name, "Tom");
ast[0].grade = 3.2;



3) 구조체 배열 초기화 : 중괄호 {} 사용
- 생략된 부분은 0으로 초기화

int i;
struct student ast[3]
= { {10, "Tom", 3.2},
{20, "Alice"} };
// 생략된 부분은 모두 0으로 초기화



4. 구조체 포인터

1) 구조체 포인터
- 구조체 변수를 가리키는 포인터
- 구조체 변수의 시작주소가 저장
- 기본적인 사용법은 일반적인 포인터와 동일
- 다만, 구조체 포인터에서만 사용하는 표현법 존재

2) 구조체 포인터 변수 선언 및 연결
- 일반 포인터 선언과 동일: * 사용
주소연산자(&): 구조체 변수의 시작 주소
>>예시

struct student st1 = {10, "Tom", 3.2};
struct student *pst;
pst = &st1;



3) 간접 연산자(*)를 이용한 구조체 변수 접근

struct student st1 = {10, "Tom", 3.2}, st2;
struct student *pst = &st1;
st2 = *pst; // st2에 pst가 가리키는 구조체 변수를 대입



4) 간접 참조를 이용한 구조체 변수의 멤버 접근

(1) 방법 1: (*pst).id
- 포인터가 가리키는 변수에 접근하기 위해 간접연산자* 사용
- 구조체의 멤버에 접근하기 위해 멤버연산자. 사용
- *보다 .의 우선순위가 높기 때문에 괄호 반드시 필요

(2) 방법 2: pst->id
구조체 포인터에서만 사용하는 전용 연산자: ->
- 주로 이 연산자를 사용

5) 구조체 포인터 배열
- 구조체 포인터가 원소인 배열
>>예시

struct student st1 = {10, "Tom", 3.2}, st2;
struct student *past[3] = {&st1, &st2};

past[2] = past[1]; // 주소 값 대입: past[2]도 st2를 가리킴
*past[2] = *past[0]; // past[2]가 가리키는 구조체에 past[0]이 가리키는 구조체 대입



5. 구조체와 함수

1) 구조체를 함수의 인자로 사용
- 실인자의 값이 형식인자에 대입됨
- 구조체 변수라고 달라지는 건 없음
>> 예시

void print(struct student st) {
	printf("id: %d\n", st.id);
	printf("name: %s\n", st.name);
	printf("grade: %.2f\n", st.grade);
}



2) 구조체를 함수의 반환형으로 사용
- 구조체 전체 값이 통째로 호출함수에게 전달됨
>> 예시

struct student init(){
	struct student st = {0, "", 0};
	return st;
}



3) 구조체 포인터를 함수 인자로 사용
- 실인자의 값이 형식이자에 대입됨
>>예시

void init_p(struct student *pst){
	pst->id = 0;
	pst->name[0] = '\0';
	pst->grade = 0.0;
}



4) 구조체 주소를 반환하는 함수
- 일반 변수의 주소 반환과 동일
>>예시

struct student *next_addr(struct student *pst){
	return pst+1;
}



5) 함수 호출 총정리
- 함수 인자나 반환 값의 자료형에 관계없이 함수 호출 과정은 동일
- 함수 호출 시에는 전달된 값을 형식인자에 대입
- 함수 종료 시에는 리턴 값이 통째로 호출함수에 전달
- 결론: 자료형마다 따로 외우려 하지 말고 원리 이해가 중요

6. 중첩 구조체 및 자기참조 구조체

1) 중첩 구조체(nested structure)
다른 구조체가 구조체의 멤버로 사용
>>예시

struct address{ // 구조체 address 정의
	int zipcode;  char *city;
};
struct student{ // 구조체 student 정의
	int id; char name[8];  double grade;
	struct address addr; // 멤버 addr의 자료형은 struct address
};



2) 중첩 구조체 주의사항
- 자신과 동일한 구조체 자료형은 멤버로 사용될 수 없음
- 자기자신을 정의하기 위해 자기 자신이 필요함 >> 순환 오류

7. typedef 사용자형 정의

1) 사용자형 정의
- typedef문을 이용하여 자료형의 이름을 새로 정의할 수 있음

typedef int INT; // INT 자료형 정의

 

typedef struct student STUDENT; // STUDENT 형 정의



728x90
728x90

<문자열>

1. 문자열 개요

1) 문자열(string)
- 연속적으로 나열된 문자들의 묶음
- 문자열은 문자배열을 사용하여 저장

2) 문자열 표현
큰 따옴표("")로 감싸서 나타냄
- "Hello", "Program"
문자는 작은 따옴표('')로 감싸서 표현

3) 문자열 입출력
- scanf, printf에서 문자열 단위 입출력 지원
- 서식 문자: %s

2. 문자열 저장 및 기본 입출력

1) 문자열 표현 예시
- 일반 문자열: "Hello", "Sejong"
- 공백 문자열: " " (큰 따옴표 사이 공백)
- 길이가 0인 문자열: "" (사이에 아무것도 없음)

2) 큰 따옴표와 작은 따옴표의 차이
- "A" 는 문자열
- 'A'는 문자

3) 문자열 저장 및 초기화
- C언어에서는 문자 배열에 문자열 저장
- 문자 배열 선언 및 초기화 예시

char str1[8] = "Hello"; // 배열 크기 지정
char str2[] = "Hello"; // 배열 크기 미지정, 초기화 값에 의해 크기 결정


- 문자열로 초기화 하는 것은 선언 시에만 가능

4) 널(null) 문자
문자열의 끝을 의미하는 특수문자 '\0'으로 표현
널문자의 아스키 코드 값은 0, 즉 '\0' == 0
- 문자열을 처리하는 기준이 되는 매우 중요한 요소
문자열(%s) 는 항상 맨 마지막에 널 문자를 포함하고 있음

<< 문자와 문자열의 차이 >>
- 문자 'A' = A
- 문자열 "A" = 'A' + '\0'
- char str[] = "Hello";  == char str[] = {'H', 'e', 'l', 'l', 'o', '\0'}
따라서 문자배열에 문자열을 저장하기 위해서는 배열의 크기가 문자열의 길이보다 하나 더 커야 됨

5) C언어에서 문자열의 기준
널 문자 까지의 문자들의 묶음을 지칭
- 주의) 배열의 크기와 관계없음 >> 배열은 단순히 저장 공간으로서의 역할
- 문자열의 끝은 배열의 크기가 아니라 널문자에 의해 결정
- 입출력을 비롯한 모든 문자열 처리의 기준

6) printf() 함수를 이용한 문자열 출력
- 문자열을 하나의 단위로 취급
- 서식 지정자: %s
- 인자: 문자열의 시작주소 (보통은 문자열의 이름)

7) 출력 시 널 문자의 역할
- 배열에서 초기화가 명시되지 않은 원소는 0(즉 '\0')으로 초기화 됨
- 널 문자는 화면에 공백처럼 출력된다. 그러나 공백문자와는 다르다.
- 인자로 전달된 주소의 문자부터 널 문자 전까지 출력한다.
- 배열 크기만큼 출력하는 것이 아니다.
- printf 함수는 배열의 크기를 모른다.

>> 예시1. 문자열 하나하나 요소 출력

char str[20] = "Hello World";
for (int i = 0; i<20; i++){
	printf("%c", str[i]);
}
printf("!!\n");



결과:
Hello World         !!
// 공백이 나타나는 이유는 널 문자를 출력하면 공백처럼 출력되기 때문

>> 예시2. 문자열 한번에 출력

char str[20] = "Hello World";
printf("%s!!", str);


결과:
Hello World!!
// 공백이 나타나지 않는 이유는 널 문자 전까지만 인식해서 출력하기 때문

8) scanf() 함수를 이용한 문자열 입력
- 서식 지정자: %s
- 인자: 문자열을 저장할 시작 주소(보통 배열의 이름)
- 사용자로부터 입력받은 문자열을 인자로 전달된 주소부터 차례로 저장
- 개행문자, 공백문자, 탭 문자 직전까지 하나의 문자열로 인식
- 마지막에 널 문자를 자동으로 추가
- 따라서 문자열은 널문자를 저장할 충분한 공간이 확보되어 있어야 함

3. 문자열과 포인터

1) 문자형 포인터를 활용한 문자열 처리문
- 문자형 포인터를 선언하고 문자열 "Hello"를 가리키도록 초기화
- str에 "Hello"의 시작주소가 저장되어 있으므로, printf의 %s 서식을 이용해 출력

char *str = "Hello";
printf("%s!!\n", str);



2) 문자 배열과 문자열 상수
- "Hello"는 문자열 상수로, 사용자 프로그램에서 변경 불가능
- 반면 str은 사용자 변수로 값을 변경 할 수 있음
"Hello"의 값을 변경할 수는 없지만,
그냥 애초에 "World"가 저장되는 새로운 주소로 str을 변경할 수는 있음
- str이 해당 문자열을 가리키는 것이 아닌, 해당 문자열의 주소를 가리키기 때문

char *str = "Hello";
str[0] = 'h' // 변경 불가능
str = "World"; // 변경 가능



- 문자 배열로 선언하면 문자 배열 하나하나에 각 문자가 저장
>> 각각의 요소 변형 가능, 전체의 주소 변경 불가능

char str1[6] = "Hello";



- 문자형 포인터로 선언하면 불변하는 문자열 상수("Hello")의 시작 주소가 문자형 포인터에 저장 
>> 각각의 요소 변경 불가능, 전체의 주소 변경 가능

char str2[6] = "Hello";



4. 문자열의 배열

1) 다수의 문자열 처리하기

(1) 문자 배열을 여러개 사용

char num0[5] = "zero";
char num1[5] = "one";
char num2[5] = "two";


>> 문자열이 많아지면 불편

(2) 문자열의 배열 >> 2차원 문자 배열 이용

char num[3][5] = {"zero", "one", "two"};


>> num[0], num[1], num[2]의 자료형은 char *
>> 상수영역은 변경 불가능(문자 부분 변경 불가능)

(3) 문자형 포인터를 여러개 사용

char *pnum0[5] = "zero";
char *pnum1[5] = "one";
char *pnum2[5] = "two";


>> 문자열이 많아지면 불편
>> 상수영역은 변경 불가능(문자 부분 변경 불가능)
>> 입력을 받는 경우 사용 불가능 (해당 문자의 길이를 알아야 메모리를 할당할 수 있기 때문)

(4) 문자형 포인터 배열

char *pnum = {"zero", "one", "two"}


>> 상수영역은 변경 불가능(전체 변경 불가능)
>> 아예 새로운 포인터 배열을 생성해야 변경 가능
>> 입력을 받는 경우 사용 불가능 (해당 문자의 길이를 알아야 메모리를 할당할 수 있기 때문)

5. 문자열 및 문자 처리 함수

1) 문자열 처리 표준 함수
- C언어에서는 문자열 처리에 관련된 다양한 표준 함수 제공
- 대부분 <string.h> 헤더파일에 함수의 원형 선언되어 있음
#include <string.h>
- 대부분 문자열 처리 함수의 코드를 작성하는 것은 어렵지 않지만, 
이미 구현되어 있는 표준 함수를 사용하는 것이 편리

2) 문자열의 길이 구하기

(1) 직접 구현

char str[20] = "Hello World";
int length = 0;
while (str[length]){
	length++;
}
printf("Length: %d\n", length);



(2) strlen() 함수 사용
- 원형: unsigned int strlen(char *s)
- 기능: 문자열 s의 길이 반환

#include <stdio.h>
#include <string.h> // strlen() 함수가 선언된 헤더파일

int main(){
	char str[20] = "Hello World";
	printf("Length: %d\n", strlen(str));
	return 0;
}



3) 문자열 복사하기

(1) strcpy() 함수 사용
- 원형: char *strcpy(char *dest, char *src)
- 기능: dest의 공간에 src의 문자열 복사(문자열 대입), src는 변화 없음
- dest의 공간이 src의 문자열의 길이+1 이상이어야 가능(널문자 때문)

#include <stdio.h>
#include <string.h> // strcpy() 함수가 선언된 헤더파일

int main(){
	char str[20] = "Hello";
	strcpy(str, "hi");
	printf("str: %s\n", str);
	return 0;
}



// str = {'H', 'e', 'l', 'l', 'o', '\0'} 에서 str = {'h', 'i', '\0', 'l', 'o', '\0'} 로 변경

(2) strncpy() 함수
복사할 문자열의 길이를 지정하는 함수
- 원형: char *strncpy(char *dest, char *src, size_t count)

4) 문자열 접합

(1) strcat() 함수 사용
- 원형: char *strcat(char *dest, char *src)
- 기능: 문자열 dest 뒤에 src의 문자열 접합. src는 변화 없음
- 문자열 dest의 널 문자를 빼고 src 접합
- dest에 접합결과를 저장하기에 충분한 공간이 할당되어 있어야 함

#include <stdio.h>
#include <string.h> // strcat() 함수가 선언된 헤더파일

int main(){
	char str[20] = "Hello";
	strcat(str, "hi");
	printf("str: %s\n", str);
	return 0;
}



(2) strncat() 함수
접합할 문자열의 길이를 지정하는 함수
- 원형: char *strncat(char *dest, char *src, size_t count)

5) 문자열 비교하기

(1) strcmp() 함수 사용
- 원형: int strcmp(char *lhs, char *rhs)
- 기능: 사전 순으로 lhs와 rhs를 비교하여,
문자열 lhs < rhs 이면 음수, 
문자열 lhs == rhs 이면 0, 
문자열 lhs > rhs 이면 양수 반환
- 어떤 음수, 어떤 양수를 반환하는 지는 컴파일러마다 다를 수 있음
- 문자열 비교는 첫 문자부터 순서대로 비교한다

(2) strncmp() 함수
비교할 문자열의 길이를 지정하여 비교
- 원형: int strncmp(char *lhs, char *rhs, int n)
- 비교대상의 문자열에서 처음 n개 까지만 비교

6) 10진수로 표현된 문자열을 수로 변환
int atoi(char *str) : int형으로 계산하여 반환
long atol(char *str) : long형으로 계산하여 반환
double atof(char *str) : double형으로 계산하여 반환

7) 주요 문자열 처리 함수 요약
- strlen(s) : 문자열의 길이 반환
- strcpy(s1, s2) : 문자열 s1에 s2 복사
- strcat(s1, s2) : 문자열 s1의 끝에 s2를 접합
- strcmp(s1, s2) : 문자열 s1과 s2를 사전순으로 비교
- atoi(s) : 문자열 s를 int 형으로 계산하여 반환
- atol(s) : 문자열 s를 long 형으로 계산하여 반환
- atof(s) : 문자열 s를 double 형으로 계산하여 반환

6. 문자열 및 문자 입출력

1) 입출력 함수

(1) printf()와 scanf(): 다양한 기능을 가진 범용 입출력 함수
>> 함수의 크기가 크고, 속도 느림

(2) 문자열 및 문자 특화 입출력 함수
- 문자열 입출력 함수: puts, gets (gets_s, fputs)
- 문자 입출력 함수: putchar, getchar

- C언어에서는 문자열과 문자에 특화된 입출력 함수 제공
>> 속도가 빠르고, 문자 또는 문자열 입출력에 적합
- 위 함수들은 모두 <stdio.h>에 선언되어 있음

2) 문자열 출력 함수 : int puts(char *str)
- str이 가리키는 문자열을 화면에 출력하고 마지막에 '\n' 출력
- 반환 값: 출력에 성공하면 음수가 아닌 값, 실패하면 EOF

3) 문자열 입력 함수
(1) char *gets(char *s)
- 사용자로부터 문자열을 입력받아, s가 가리키는 메모리 영역에 저장하고, 포인터 s를 리텀
엔터('\n')가 입력될 때까지 입력된 모든 문자들을 저장
마지막에 입력된 '\n'은 무시하고, 맨 뒤에 '\0'를 붙임
- 문자열을 저장할 충분한 메모리 공간이 확보되어 있어야 함

(2) gets_s(char *s, int size)
- gets_s() 함수는 gets() 함수의 보안 버전으로, 문자열을 저장할 배열 크기를 인자로 전달
- 보안 상의 문제로 gets() 함수는 표준에서 제외되고, gets_s() 함수가 표준에 추가됨
- Visual Studio의 경우 2015버전부터 gets() 지원 안함
- gcc의 경우 gets() 만 지원

(3) fgets(char *s, int size, File *pfile)
- 대안의 대안으로 fgets() 함수 사용
- 문자열을 저장할 배열의 주소, 문자열을 저장할 배열 크기, 파일의 포인터까지 인자로 전달
개행 문자('\n')도 문자열에 저장

4) 문자 입출력 함수
(1) 문자 출력 함수 : int putchar(int c) 
- 변수 c에 저장된 문자를 화면에 출력
- 성공하면 문자 반환, 실패하면 EOF 반환 

(2) 문자 입력 함수 : int getchar(void) 
- 사용자로부터 입력된 문자 반환
- 성공하면 문자 반환, 실패하면 EOF 반환 

728x90
728x90

9.1 포인터 개요

1) 메모리
프로그램이 실행되기 위해 필요한 정보(값)을 저장하는 공간
- 1byte (8 bits) 단위로 물리 주소가 부여되어 있음
- 개념적으로, 메모리는 일렬로 연속되어 있는 크기가 1 byte인 방들의 모음이라고 볼 수 있음
- 일반적으로 주소의 길이는 4 bytes이고 주소는 16진수로 표현

2) 메모리와 변수의 관계
변수는 선언될 때, 메모리에 그 변수를 위한 공간이 할당
- 주소연산자(&): 변수에 할당된 메모리 공간의 시작 주소를 구해줌
- 한번 할당되면 주소는 고정된다.

3) C프로그램에서 변수의 의미
- 그 변수에 할당된 공간: 선언 또는 대입문의 왼쪽 변수로 사용될 때
- 그 변수에 저장된 값: 대입문의 오른쪽 변수, 조건식, 함수의 인수로 사용될 때

4) 포인터(자료형)
주소를 나타내는 특수 자료형
- 주소는 기본적으로 양의 정수로 표현됨
- 하지만 int와는 구별되어 처리됨

(1) 선언
- 변수명 앞에 *(참조연산자) 사용

char *pch;



(2) 연결
- &(주소연산자)를 이용해 포인터 변수를 다른 변수에 연결

pch = &ch;



(3) 참조
- *(참조연산자)를 이용해 포인터에 연결된 변수에 접근

printf("%c", *pch);



9.2 포인터 선언과 사용

1) 포인터(변수) 선언
- 변수명 앞에 *(참조연산자)만 덧붙이면 된다
- 기존의 자료형 표시 + 포인터라는 표시

int *pch;
char *pnum;



2) 초기화
- 일반변수 초기화 형태와 동일하다
- 초기화할 주소를 가진 변수(num)가 먼저 선언되어야 한다

int num, *pnum = &num;



3) 포인터 변수 선언의 다양한 형태
- 동일 기본 자료형에서 파생된 자료형의 변수는 모아서 선언 가능
- 그러나 가독성 때문에 추천 안함

4) 포인터 대입(연결)
- 포인터(변수)에 주소를 대입하여 특정 변수와 연결 시키는 것을 "가리킨다"라고 표현

pch = &ch;



5) 포인터 참조
- 포인터(변수)가 가리키는 변수에 접근하는 것
- 참조연산자 * 를 사용

printf("%c", *pch);



6) 참조 연산자를 이용한 대입
>>코드

char ch = 'A', *pch = &ch;
*pch = 'B' // 간접 접근: ch를 'B'로 변경
ch = 'C' // 직접 접근: ch를  'C'로 변경


7) 추가
- int *pnum = &num; 은 정수를 나타내므로 정수를 사용하는 어떤 형태나 계산에 사용가능
- 단, 참조연산자와 다른 연산자와의 우선순위에 주의해서 사용

8) 정리
주소연산자(&): 해당 변수의 주소값
- 변수 이름: 변수 영역 또는 변수에 저장된 값
참조연산자(*): 포인터가 가리키는 변수
- pnum과 &num의 값은 동일하지만, 지칭하는 부분은 다르다.
pnum은 num과 연결된 포인터 변수의 값이고, &num은 num에 할당된 메모리 시작 주소이다.

9) Null 포인터
주소 값 0을 나타내는 특별한 기호
- 아무것도 가리키지 않음을 의미
- null의 값은 0 이므로, 조건문에서 사용하면 거짓에 해당
- 예기치 못한 오류 방지를 위해 포인터 변수를 null로 초기화

int *pnum = NULL;



10) 포인터 주의 사항
- 주소연산자(&)는 포인터를 포함한 모든 변수에 사용 가능
참조연산자(*)는 포인터 변수에서만 가능
- 포인터의 자료형과 연결된 변수의 자료형은 일치해야 한다

11) 포인터의 크기
- 포인터의 종류(자료형)에 관계없이 주소를 저장하기 위해 필요한 공간은 동일
- 단, 포인터의 크기는 시스템에 따라 다를 수 있음

9.3 배열과 포인터

1) 배열 이름의 비밀
배열 이름은 배열의 0번 원소의 시작 주소를 의미한다. (특별하다)
- 만약 ar[10]이란 배열이 있다면 
>> ar은 ar[0]의 주소를 의미
>> &ar은 전체 배열(ar[])의 주소를 의미

2) 주소를 이용한 배열 참조
- 배열 이름은 주소를 의미하므로, 참조 연산자와 함께 사용 가능
- ar: 0번 원소의 주소( == &ar[0])
- *ar: 0번 원소의 주소에 저장된 값, 즉 0번 원소의 값을 의미

3) 배열 주소에 대한 증감 연산
- 배열 원소 하나의 크기만큼 증가 or 감소
ar+i: 배열 ar의 i번째 원소의 주소
*(ar+i): 배열 ar의 i번째 원소의 값

4) 배열을 포인터 변수에 연결하여 사용하기
- 배열 이름은 주소를 의미하므로, 포인터 변수에 대입가능
- 포인터 변수에 대한 증감연산 
포인터 변수가 나타내는 자료형의 크기 단위로 끝자리가 증가 or 감소

5) 포인터 변수도 배열의 첨자 형태로 값을 참조할 수 있다

int ar[5] = {1, 2, 3, 4, 5};
int *p = ar;



6) 배열과 포인터의 관계 정리
배열과 포인터는 동일한 형태로 사용가능(둘다 주소이기 때문)
주소에 1을 더하면 원소의 크기만큼 주소가 증가
- 주소가 주어졌을 때, 해당 주소에 저장된 원소값은 다음 두가지형태로 참조 가능
>> ar[3]과 p[3]: 배열의 첨자 연산자 [] 사용
>> *(ar+3)과 *(p+3): 포인터의 참조 연산자 * 사용

7) 배열과 포인터 주의 사항
포인터를 배열의 중간 원소에 연결 시키는 것도 가능
- 포인터는 단지 자신이 가리키는 주소를 기준으로 배열처럼 쓰는 것일 뿐
- 포인터의 참조 연산자 사용 시 괄호에 유의
포인터 변수의 증감량은 가리키는 배열의 원소 크기가 아니라 포인터 자신의 자료형에 의해 결정

728x90
728x90

<디버깅>

1. 프로그램 오류의 종류와 디버깅
버그프로그램에 존재하는 오류
디버깅오류를 고치는 행위
컴파일 에러문법적 오류. 컴파일러는 프로그램의 구문, 데이터, 의미없는 문장 등을 검사
컴파일 경고: 오류는 아니지만, 오류의 가능성이 있는 부분
런타임 오류실행시간 오류. 프로그램의 결과가 의도와 다르거나 비정상적으로 종료되는 경우

2. 프로그램 오류 비교

1) 컴파일 오류/경고 확인 및 수정
- 컴파일 요류/경고를 고치는 과정은 매우 쉬움
- 오류로 표시된 라인과 실제 오류가 있는 라인은 다를 수 있음
ex) 세미콜론 누락, 중괄호 오류
가장먼저 표시되는 오류내용부터 확인하고 수정
- 실제 오류는 하나이지만 이로 인해 여러군데 오류가 발생하는 것처럼 보일 수 있음
- 맨 처음 오류내용을 해결하면, 나머지는 저절로 해결되는 경우 많음

2) 컴파일 오류/경고와 빌드
컴파일 오류가 있으면 실행파일이 생성되지 않음
- 컴파일 경고는 있어도 실행파일 잘 생성됨

- VS의 경우( [Ctrl+F5] )
>> 소스가 변경된 경우 프로그램 빌드에 성공하면 > 프로그램 수행
>> 프로그램 빌드에 실패하면 > 기존 실행파일 수행

3) 런타임 오류 확인 및 수정
- 프로그램 실행 시 발생하는 오류
일반적으로 오류가 어디서 발생했는지 알기가 어려움
- 대부분의 시간을 이 오류를 찾고 수정하는데 소비
- 이러한 종류의 오류를 찾아 고치는 작업 >> 디버깅

3. 디버깅

1) 방법1. 프로그램 소스를 쭉 보면서 내가 어디서 틀렸나 찾아본다
- 코드가 짧은 경우에 유효
- 코드가 긴 경우 못찾을 가능성이 훨씬 큼

2) 방법2. 실제로 프로그램을 수행시켜보면서 프로그램이 내가 예상하고 원하는 과정대로 동작하는지 체크
- 계산된 결과가 맞는지 체크
- 화면에 결과값들을 출력해 보기

3) 디버깅의 기본
- 코드 중간에 있는 오류를 찾기 위해 프로그램이 내가 의도하는 과정대로 동작하는지 체크
제어 흐름과 변수에 저장된 값 및 주소를 확인

- 간단한 방법은 화면에 결과값을 출력해서 확인하기
코드에 값 체크용 조건문을 넣어서 확인
- 또는 개발툴에서 제공되는 디버깅 기능 활용하기

4) 반복문 체크 요령
반복문 시작 또는 끝 지점에서 체크

5) 함수 체크 요령
함수 시작과 끝 지점에서 체크

728x90
728x90

8. 함수

8.1. 함수 개요

1) C언어에서 함수
- 어떤 특정한 일을 수행하는 독립적인 단위
- 함수의 예시: printf(), scanf(), main()

2) 함수의 구성
- 함수 정의부: 함수를 구현하는 부분
- 함수 호출부: 함수의 기능을 이용하는 부분

8.2. 함수 정의

1) 함수 정의
- 함수가 수행해야 할 기능 명세하기
- 구문

반환형 함수이름 (인자선언1, 인자선언2, ...)
{
	// 함수 몸체
	// 함수의 수행코드
	return 문;
}



2) 함수 정의 요소

>> 함수이름
- 함수이름 규칙은 변수명 규칙과 동일
- 의미있는 이름 사용 권장

>> 함수인자 or 매개변수
- 함수의 입력 데이터를 나타내는 변수들
- 인자가 다수인 경우 콤마로 구분
- 인자가 없더라도 소괄호는 반드시 필요 (또는 Void 작성)

>> 반환형(return Type)
- 함수가 수행된 후에 반환하는 결과의 자료형
- 아무 결과도 반환하지 않으면 자료형에 void를 씀 (이 경우 return문이 필요없음)

>> 함수 몸체(body)
- 함수가 수행해야 할 작업을 {} 안에 명세
- 함수는 제어 흐름에 따라 수행: 맨 마지막 문장까지 수행되거나 return 문을 만나면 종료

>> return 문
- 함수를 종료하고 함수의 결과를 반환하는 역할
- 반환형이 void인 경우 보통 return문을 사용하지 않지만, 중간에 함수를 종료시키기 위해 사용하기도 함

8.3. 함수 호출과 반환
- 함수를 불필요하게 여러번 수행하지 않도록 유의
- 함수구성은 일을 독립적인 단위로 어떻게 나누느냐에 따라 달라짐
- 함수는 코드 작성의 효율성과 가독성을 높여줌

8.4. 함수와 변수의 종류

1) 함수와 변수
- 함수는 특정한 일을 하는 독립적인 단위
- 함수 기능 뿐만 아니라 함수에서 사용하는 변수에도 독립성이 적용
>> 즉, 함수에서 선언된 변수들은 그 함수에서만 유효
- 경우에 따라서 특정 함수에만 국한되지 않고, 함수와 무관하게 사용되는 변수가 필요

2) 변수 종류

(1) 지역 변수
- 선언 위치: 함수 내에서 선언
- 유효 범위: 변수를 선언한 함수 내에서만 (지역적으로) 유효
- 변수의 지속시간: 함수 호출 시 생성, 함수 종료 시 소멸
>>함수 호출과 동시에 자동으로 생성되고, 함수가 종료되면 자동으로 소멸되어 자동변수라고도 함
- 함수의 형식 인자도 지역변수임

(2) 전역 변수
- 선언 위치: 함수 밖에서 선언
- 유효 범위: 프로그램 내 어디서든 사용 가능
- 변수의 지속시간: 프로그램 실행 시 생성, 프로그램 종료 시 소멸

- 자동으로 0으로 초기화(그러나 모든 변수는 명시적으로 초기화 하는 습관을 가지자)
- 동일한 이름의 전역 변수와 지역 변수가 있는 경우 지역 변수가 우선된다.

- 전역 변수는 함수 사이의 데이터 전달을 위한 또하나의 수단
>> 그러나 함수의 독립성을 해칠 수 있어 신중하게 사용

(3) 정적 변수
- static 키워드 사용
>>

static int x = 0;



- 선언 위치: 함수 내 선언
- 유효 범위: 선언한 함수 내부
>> 지역 변수와 공통점: 변수의 지속시간. 프로그램 실행 시 생성, 프로그램 종료 시 소멸
>> 전역 변수와 공통점: 프로그램 실행 전체 과정 동안 딱 한번만 생성되고 초기화

8.5 함수에 배열 전달

1) 배열의 개별 원소 전달
- 함수의 인자에서 사용된 배열 원소는 수식의 일부일 뿐
- 배열 원소는 일반 변수와 동일하게 취급
- 형식 인자에서 그냥 변수로 명시

2) 배열 전체 전달
- 형식 인자에서 배열로 명시
- 다만, 형식인자에서 배열 크기 명시해도 의미 없음 >> 생략
- 만약, 배열 크기가 필요한 경우 별도의 함수 인자로 전달
- 배열 전체가 전달된 경우, 호출된 함수에서 배열의 값을 바꾸면, 호출한 원래 함수의 배열 값도 바뀜

- 다차원 배열인 경우, 2차원 이상부턴 배열 크기 명시해야함

8.6 함수와 라이브러리

1) 라이브러리
함수를 구현해 모아 놓은 것
- 필요 시 함수를 호출하여 사용

2) 표준 라이브러리
- C언어에서 정해놓은 표준 함수들로 구성: printf(), scanf() 등

3) 표준함수 사용
- 함수의 형태와 기능만 알고 있으면 활용할 수 있음
- 어떻게 구현되어 있는지는 몰라도 됨
- 호출하기 전에 함수 원형이 선언되어 있어야 함
- #include문으로 헤더파일을 소스코드에 포함시킴
- 헤더 파일: 확장자.h의 형태로 이루어짐. <stdio.h>등

4) 자주 사용되는 C 표준 헤더 파일 및 표준 함수
<stdio.h>: 입력, 출력, 파일
>> printf(), scanf(), putc(), getc(), fopen() 등


<stdlib.h>: 숫자 변환, 동적 할당
>> atoi(), rand(), srand(), malloc(), free() 등


<ctype.h>: 문자 검사 및 변환
>> Isalnum(), isalpha(), islower(), topper() 등


<math.h>: 수학 함수
>> sin(), asin(), exp(), log(), pow(), sqrt(), abs()등


<time.h>: 시간 처리
>> clock(), time(), difftime() 등


<string.h>: 문자열, 메모리 블록
>> strcpy(), strcat(), strcmp(), strlen(), memcpy() 등

728x90

+ Recent posts