©copyright 2010
C프로그래밍I (CPA130)
교과목 소개
한국기술교육대학교 한국기술 육대학 컴퓨터공학부 김상진
강의목표 컴퓨터 하드웨어는 그 자체만으로는 아무런 쓸모가 없음. 컴퓨터가 유용하게 사용되기 위해서는 컴퓨터가 필요한 일을 할 수 있도록 그 절차를 자세히 서술해 주어야 함. 이와 같이 컴퓨터가 수행해야 하는 일을 컴퓨터가 이해할 수 있는 언어로 표현한 것을 프로그램 프로그램이라 하며, 하며 프로그램을 작성하는 과정을 프로그래밍 프로그래밍이라 함. 프로그램 기술할 때 사용되는 언어를 프로그래밍 언어 언어라 함. 실제 컴퓨터(중앙처리장치, CPU(Central Processing Unit))는 기계어라고 하는 언어밖에 이해하지 못함. 기계어 하지만 이 언어로 프로그래밍하는 것은 어렵기 때문에 우리가 일상에서 사용하는 언어와 가까운 언어로 프로그래밍을 하고, 결과를 번역기를 통해 기계어로 바꿈. 프로그래밍 언어 중 자연어와 유사하면 고급프로그래밍언어 고급프로그래밍언어라 함. 함 이 과목에서는 대표적인 고급 프로그래밍 언어인 C언어를 이용하여 프로그래밍을 래밍을 하는 방법을 학습함 학습함. 2/10
강의목표 고급프로그래밍언어를 컴퓨터가 직접 이해하지 못하므로 기계어로 번역되어야 하며, 이와 같은 번역을 해주는 시스템 소프트웨어를 컴파일러( 컴파일러(compiler)라 컴파일러 il )라 함. 함 어떤 엄격한 규칙에 따라 작성되지 않으면 이것을 번역하기 힘듦. 따라서 고급프로그래밍언어는 정해진 규칙에 따라 작성되어야 하며, 하며 이와 같은 규칙을 그 언어의 문법 문법이라 함. 프로그래밍 언어의 문법은 한정되어 있으므로 문법을 익히는 것은 어렵지 않음. 않음 따라서 문법보다는 어떤 문제를 해결하기 위한 방법(알고리즘, algorithm)을 g )을 고안하고 안하 그것을 것을 프로그래밍언어로 래밍언어 표현하는 현하는 능력을 배양하는 것이 보다 중요함. 이 과목의 목표는 C언어의 문법을 익히고, C언어를 이용하여 프로그래밍할 수 있는 능력을 배양하는 것임. 것임 프로그래밍은 어렵지 않다. 프로그래밍은 래밍은 재미있다 재미있다. 3/10
Why C? C 언어는 1972년 Dennis Ritchie에 의해 개발된 범용 언어이며, 고급프로그래밍언어임. 38년 전??? too t old ld NO: 아직 C언어를 이용하여 개발하는 경우도 많으며, C로 구현되어 있는 기존 소프트웨어가 많음. 최근 프로그래밍 경향: 객체지향 언어, 웹 지향 언어 현재 많이 사용되는 언어: C++, C#, 자바 이들 언어는 C를 기반으로 만든 언어이다. 언어이다 따라서 C언어를 잘 이해하면 이들 언어를 이해하는데 도움이 됨. 하지만 C언어는 배우기 쉬운 언어는 아님. 시스템 수준에 가까운 요소가 포함되어 있는 언어임. 언어임 여러 고급 기능을 오용할 수 있는 소지가 많은 언어임. 이 과목에서는 시스템 수준에 가까운 요소와 오용하기 쉬운 고급 기능 보다는 고급프로그래밍언어를 이용한 기초 프로그래밍 기술 위주로 강의함.
4/10
프로그래밍을 잘하기 위해서는 많은 연습이 필요하다. 처음에는 문법이 어렵지만 나중에는 문법은 아무것도 아니다. 오류가 발생하면 실망하지 마라. 오류를 디버깅하면서 프로그래밍 능력이 향상되는 것이다. 문법은 외우는 것이 아니라 이해해야 한다. 한다 호기심 발동 문법. 변수를 선언할 때 타입 정보를 주어야 한다. 왜?????? 다른 사람들이 작성한 코드를 통해 여러 가지 기법을 배워야 한다. 한다 자료구조, 알고리즘, 프로그래밍언어론, 소프트웨어공학 등의 수업을 통해 고급 프로그래밍 기술을 배워야 한다. 운영체제, 컴퓨터구조 수업 등을 통해 컴퓨터 하드웨어와 컴퓨터 동작 원리를 이해해야 한다.
5/10
C 언어의 이해 필수적인 요소 우리가 작성한 코드는 컴파일러에 의해 CPU가 이해할 수 있는 언어로 번역된다. 컴파일러는 융통성이 없다. 컴파일러는 보통 순차적으로 번역한다. 많은 문법적 오류는 이 특성을 상식적으로 잘 생각하면 대부분 왜 오류인지 이해할 수 있다. (내가 무식한 컴파일러 입장이 되어보자)
6/10
강의정보 교수 정보 연구실: 제4공학관 B동 301호 전화번호: 교내 1490 (041-560-1490) 전자우편: sangjin@kut.ac.kr 강의홈페이지: 강의홈페이지 http://el.kut.ac.kr/ http://infosec.kut.ac.kr/sangjin/class/c2010-01 실습조교 정보: 전자우편: 교재 강환수, 신용현, Perfect C, 인피니티 북스, 2007
7/10
강의정보 – 계속 강의 평가 방법 출석: 5%, 과제/실습: 10%, 실습시험 25%, 중간: 30%, 기말: 30% 참고. 바뀔 수도 있음 1시간 이상 지각할 경우 지각 처리함 합당한 사유를 사전에 통보하면 결석 처리하지 않음 결석할 경우에도 실습 내용을 숙제로 전부 제출해야 함 강의 준비물 강의 노트를 강의 홈페이지에서 다운받아 준비해야 함.
8/10
강의정보 – 계속 강의 부교재 B. Kernighan, D. Ritchie, The C Programming Language, 2nd Ed 1989. Ed., 1989 S. Harbison, G. Steele, C: A Reference Manual, 5th Ed., 2002. H. Deitel, P.J. Deitel, C: How to Program, g 4th Ed., 2003. A. Kelley, I. Pohl, A Book on C, 4th Ed., 1997 D. Gookin, C All-in-One Desk Reference for Dummies, Wiley, 2004. 2004
9/10
강의계획 1
강의 소개
9
함수 기본
2
컴퓨터와 프로그램, 프로그램 개발
10
함수 활용
3
C 언어의 개요
11
배열
4
자료 유형
12
변수범위
5
전처리와 입출력
13
실습시험
6
연산자 기본
14
포인터
7
조건문/반복문
15
포인터
8
중간고사
16
기말시험
10/10 10 /10
©copyright 2010
C프로그래밍I(CPA130) 강의노트 01
컴퓨터와 프로그래밍
한국기술교육대학교 한국기술 육대학 컴퓨터공학부 김상진
강의목표 컴퓨터와 프로그래밍 관련 기본지식 함양 컴퓨터에서 자료표현 프로그래밍언어 소프트웨어공학 프로그래밍 개발 환경
2/21
컴퓨터의 정의와 구성요소 – 컴퓨터 컴퓨터(computer): 전자적으로 계산을 수행하는 장치 컴퓨터
컴퓨터는 창조적으로 사고할 능력이 없음. 주어진 명령어들을 기계적으로 수행하여 주어진 일을 수행함. 컴퓨터가 사용자가 원하는 일을 수행하도록 컴퓨터가 이해하는 언어로 작성된 명령들의 집합을 프로그램 프로그램(program)이라 함. 함
3/21
컴퓨터의 정의와 구성요소 – HW/SW 컴퓨터는 크게 하드웨어와 소프트웨어로 구성되어 있음. 하드웨어는 컴퓨터를 구성하는 물리적인 장치(device)들을 말함. 중요 구성요소: 중앙처리장치(CPU, Central Processing Unit), 기억장치(memory), 입출력(input/output)장치, 상호연결 중앙처리장치: 제어장치(control unit), unit) 연산장치(ALU, 연산장치(ALU Arithmetic Logic Unit), 레지스터(register), 기억장치: 주기억장치와 보조기억장치(HDD) 상호연결(bus, 예: AGP, PCI, EIDE, SATA 등) 소프트웨어: 시스템소프트웨어 운영체제, 시스템소프트웨어: 운영체제 컴파일러 등 응용소프트웨어: 오피스, 게임 등
4/21
프로그램의 실행
프로그램을 실제 실행하는 것은 CPU임. CPU는 주기억장치만 접근할 수 있음. 프로그램은 비휘발성인 HDD에 보관되어 있으며, 사용자가 프로그램을 램을 실행하면 CPU가 가 실제 실행할 수 있도록 있 록 운영체제는 운영체제 프로그램을 주기억장치에 적재함. 프로그램이 실행되면 프로그램은 각종 데이터를 처리하게 되며, 이런 데이터는 역시 주기억장치에 유지됨. 프로그램은 “명령어+데이터”이다. 데이터가 주기억장치에 있어야 CPU가 접근할 수 있음. 있음 주기억장치는 데이터를 저장할 수 있는 일차원 셀들로 구성되어 있으며, 각 셀에는 한 바이트( 바이트(8 비트)를 유지함 유지함. 5/21
컴퓨터의 자료표현 컴퓨터는 기본적으로 0(전기가 흐르지 않음)과 1(전기가 흐름)만 인식할 수 있음. 이와 같은 0과 1의 데이터를 비트 비트(bit)라 함. 연속적인 8개의 비트가 모인 정보 단위를 바이트 바이트(byte)라 함. 워드(word)는 컴퓨터의 기본 데이터 단위를 말하며, 보통 그 워드 컴퓨터에서 정수를 표현하기 위해 사용되는 단위임. 단위임 단위(IEC 60027-2)
SI(국제단위 규격)
1 KiB(kibibyte)
210 byte 1024 byte
1 KB(kilobyte)
103 byte
1 MiB(mebibyte)
210
1000 byte
1 MB(megabyte)
103
KB
106 byte
1 GiB(gibibyte)
210 MiB
230 byte
1 GB(gigabyte)
103 MB
109 byte
1 TiB(tebibyte)
210 GiB
240 byte
1 TB(terabyte)
103 GB
1012 byte
1 PiB(pebibyte)
210 TiB
250 byte
1 PB(petabyte)
103 TB
1015 byte
1 EiB(exbibyte)
210 PiB
260 byte
1 EB(exabyte)
103 PB
1018 byte
1 ZiB(zebibyte)
210 EiB
270 byte
1 ZB(zettabyte)
103 EB
1021 byte
1 YiB(yobibyte)
210 ZiB
280 byte
1 YB(yottabyte)
103 ZB
1024 byte
KiB
220
byte
6/21
진수와 수의 표현 우리는 보통 10진법을 사용하여 수를 표현함. 컴퓨터는 10진법보다는 2진법, 8진법, 16진법을 사용함. 2진법은 0과 1만 사용하여 수를 표현하며, 8진법은 0, 1, …, 7만 사용하여 수를 표현하고, 16진법은 0, 0 1 1, …, 9 9, A(10) A(10), B(11) B(11), C(12) C(12), D(13) D(13), E(14) E(14), F(15)만 사용하여 수를 표현함. 기본적으로 b진법으로 표현된 수 x2x1x0를 10진법으로 바꾸는 방법은 다음과 같음. 같음 x2b2+x1b1+ x0b0 2진수 8진수, 2진수, 8진수 16진수 간 상호관계 2진수를 8진수, 16진수로 변환하는 방법 2진수를 8진수로: 3비트씩 묶어 변환 2진수를 16진수로: 4비트씩 묶어 변환 7/21
진수와 수의 표현 – 계속 10진수를 2진수로 변환하는 방법 2
100 50
0
25
0
12
1
6
0
3
0
1
1
10010 = 11001002 100
50 2 0 (25 2 0) 2 0 25 22 0 21 0 20 (12 2 1) 22 0 21 0 20 12 23 1 22 0 21 0 20
(6 2 0) 23 1 22 0 21 0 20
8/21
진수와 수의 표현 – 계속 소수부분을 2진수로 표현하기 0.7510 = 0.112 0.75 2 = 1.5 0.5 2 = 1.0 = 2-1 + 2-2 05+0 0.25 25 = 0.5
0 3510 = 0.01011010110… 0.35 0 01011010110 0.35 2 072 0.7 0.4 2 0.8 2 062 0.6 0.2 2
= 0.7 = 1.4 14 = 0.8 = 1.6 = 1.2 12 = 0.4
= 2-22 + 2-44 + 2-55 + … = 0.25 + 0.0625 + 0.03125 + … = 0.34375 + … 비고. 10진수에서도 1/3은 표현할 수 없음
9/21
2진수의 음수 표현 컴퓨터는 음수를 표현하기 위해 크게 두 가지 방법(부호 부호 크기 표현법 표현법, 2의 보수 표현법 표현법)을 사용할 수 있음. 이 때 중요한 것은 음수와 양수를 구분할 수 있어야 함. 보통 이것을 위해 최상위 비트 비트(MSB, Most Significant Bit)를 부호 비트 비트(sign bit)로 사용함. 사용함 각 표현방식의 차이점은 나머지 비트의 해석에 있음. 부호 크기 표현법 첫 비트는 1이면 음수이고, 나머지 비트 값은 양수일 때처럼 해석된 후에 그 수의 음수를 취하게 됨. 2의 보수 표현법: 보수(complement)란 1를 0으로 0을 1로 바꾸는 것을 말함. 음수의 해석: 첫 비트는 1이면 음수 첫 비트를 제외한 나머지 비트 값을 양수일 때처럼 해석한 값이 a이고, 수를 표현하는 총 비트 수가 n이면 –2n–1+a로 해석 예) 1111 = –2 244–11+7 = –1 1 10/21 10 /21
10진수 표현
부호 크기 표현
2의 보수 표현
+8
–
–
+7
0111
0111
+6
0110
0110
+5
0101
0101
+4
0100
0100
+3
0011
0011
+2
0010
0010
+1
0001
0001
+0
0000
0000
–0
1000
–
–1
1001
1111
–2
1010
1110
–3
1011
1101
–4
1100
1100
–5
1101
1011
–6
1110
1010
–7
1111
1001
–8 8
–
1000
부호 크기 표현법은 0을 나타내는 방식이 두 가지 존재한다는 문제점이 있어 부호 크기 방식보다는 2의 보수 표현방식을 많이 사용함. 수의 범위 부호 크기(n비트) –2n–1–1 ~ 2n–1–1 2의 의 보수(n비트) 수( 비 ) –2n–1 ~ 2n–1–1
11/21 11 /21
2진수의 음수 표현 – 계속 2의 보수로 수를 표현하는 방법
12/21 12 /21
문자와 논리 문자 컴퓨터는 0과 1밖에 인식하지 못하지만 인간은 다양한 문자를 인식할 수 있다음. 컴퓨터가 인간이 사용하는 문자를 인식할 수 있도록 문자에 코드를 할당함. 할당함 대표적인 코드: 아스키코드(ASCII), 유니코드(unicode) 예) 우리는 ‘A’, 컴퓨터는 0100 0001 아스키코드는 영문과 각종 기호와 관련된 미국 표준 코드임. 따라서 다양한 국제 문자 체계를 표현하지 못함. 이에 하나의 코드로 전세계 문자 체계를 수용하기 위해 제안된 코드가 유니코드임. 논리 참(true)과 거짓(false)을 의미하는 두 가지 정보를 논리값이라 함.
13/21 13 /21
프로그래밍언어 컴퓨터가 수행해야 할 일련의 절차를 알려주기 위해 사용하는 언어를 프로그래밍언어라 하며, 프로그래머는 이 언어를 이용하여 프로그래밍언어 프로그램을 구현한다. 구현한다 프로그래밍언어 중 컴퓨터가 이해하는 유일한 언어는 기계어 기계어이며, 기계어는 컴퓨터 종류( 종류(CPU의 의 종류)에 따라 다름 다름. 일반 프로그래머가 기계어로 프로그램을 구현하는 것은 매우 어렵기 때문에 인간이 사용하는 언어와 유사한 언어로 프로그래밍할 수 있도록 해주는 언어를 고급프로그래밍언어 고급프로그래밍언어(high level programming 고급프로그래밍언어(high-level language)라 함. 컴퓨터는 고급프로그래밍언어로 작성된 코드를 이해할 수 없으므로 이것을 컴퓨터가 이해하는 기계어로 번역해주어야 함. 이 때 사용되는 시스템 소프트웨어를 컴파일러 컴파일러(compiler)라 함. 각 프로그래밍언어마다 다른 컴파일러가 필요함. 필요함
14/21 14 /21
컴파일러 vs. 인터프리터 컴파일러는 고급프로그래밍언어로 작성된 코드인 소스 코드 코드(source code)를 컴퓨터가 이해할 수 있는 기계어로 번역하여 주며, 그 결과 코드를 목적 코드 코드( bj t code)라 코드(object d )라 함. 함 목적 코드: 목표한 시스템이 이해할 수 있는 코드 인터프리터는 소스 코드를 구성하는 각 명령어를 한 문장 씩 번역하여 실행함. 단점.. 프로그램 수행 속도가 느리다. 단점 장점.. 기계와 독립적인 코드를 만들 수 있다. 예) 자바 장점
15/21 15 /21
프로그래밍언어 – 계속 고급프로그래밍언어의 종류 C, Pascal, Fortran, Basic, Cobol C++, C#, JAVA C, Pascal, Fortran, Basic, Cobol 등은 객체지향 프로그래밍 개념이 등장하기 이전에 개발된 프로그래밍 언어들임. 언어들임 초기 개발 환경에서는 컴퓨팅 능력이 떨어지고, 하드웨어 장비들이 고가이었으므로 효율성이 매우 중요한 요소이었음. 하지만 최근에 효율성 보다는 유지보수성, 편리성 등이 더 강조되고 있으며, 소프트웨어 자체가 초기보다 매우 복잡해지고 있음. 또한 컴퓨팅 환경 자체가 바뀌고 있음. 있음 (중앙집중 분산 유비쿼터스) 따라서 기존 프로그래밍 패러다임으로는 개발 한계를 느끼기 시작하여 등장한 언어들이 C++, C#, JAVA 등이며, 이들 모두 객체지향 프로그래밍을 지원하는 언어임.
16/21 16 /21
소프트웨어 개발 우리가 프로그래밍언어를 학습하는 중요한 이유 중 하나는 소프트웨어 개발에 필요한 능력 중 하나를 배양하기 위함. 소프트웨어는 단순히 프로그래밍언어를 이용하여 구현하는 것이 전부라고 생각하는 경우도 있음. 그러나 소프트웨어 개발과정은 매우 복잡함. 복잡함 주어진 문제를 분석하는 기술 분석을 바탕으로 설계하는 기술 구현하는 기술 구현된 소프트웨어를 검사하는 기술 유지보수하는 기술 고품질 소프트웨어를 개발하는 모든 과정과 관련된 학문을 소프트웨어공학 소프트웨어공학이라 웨어공학이라 하며, 웨어공학 소프트웨어공학 원리들을 어떻게 효과적으로 적용하느냐가 소프트웨어 품질을 결정하는 매우 중요한 요소임. 17/21 17 /21
프로그래밍 개발 환경 프로그래밍 언어를 이용하여 프로그램을 개발하기 위해서는 크게 문서편집기, 컴파일러(compiler), 링커(linker)가 반드시 필요하며, 디버거(d b 디버거(debugger)가 )가 있으면 매우 유용하게 사용할 수 있음. 있음 프로그래밍언어로 작성된 코드가 들어있는 소스파일 소스파일을 컴파일러를 이용하여 번역하면 기계어 형태의 목적파일 목적파일을 얻을 수 있음 있음. 여러 목적파일들을 결합하여 실행파일을 만들어주는 시스템 소프트웨어를 링커라 함. 이 때 라이브러리 라이브러리(lib 라이브러리(library)라 )라 불리우는 컴파일러와 함께 미리 제공되는 목적파일들이 이 과정에서 사용자가 만든 목적파일들과 결합됨. 컴파일과 링킹 과정을 통해 실행파일을 만드는 전체 과정을 빌드 (build)라 함. 하나의 실행파일을 만들기 위해 여러 개의 소스파일을 사용할 수도 있음. 보통 큰 소프트웨어는 여러 프로그래머가 각자 작성한 여러 개의 소스파일들을 이용하여 구현됨. 18/21 18 /21
실행파일을 만드는 과정 stdio.h
hello.c
hello.obj
컴파일 헤더파일(.h) 헤더파일( h) 소스파일(.c)
링킹
목적 파일
hello exe hello.exe
실행파일
라이브러리 파일
컴파일 헤더파일 소스파일
목적 파일
링킹
라이브러리 파일
실행파일
19/21 19 /21
프로그래밍 개발 환경 – 계속 오늘날에는 쉽게 프로그래밍을 할 수 있도록 위에 나열된 모든 소프트웨어들이 통합되어 있는 통합개발환경 통합개발환경(IDE, Integrated D Development l tE Environment)을 i t)을 사용함. 사용함 컴파일러와 같은 소프트웨어는 인공지능적 능력을 가지고 있지 않으므로 엄격한 규칙에 따라 번역을 하게 됨 않 됨. 따라서 소스파일은 이 규칙에 맞게 작성될 경우에만 번역될 수 있음. 이 규칙을 문법 문법(syntax)이라 하며, 문법이 틀린 코드를 만나면 그 이유와 위치를 컴파일러는 표시해 줌. 줌 이와 같이 번역할 때 발생하는 오류 오류(error)를 문법오류 또는 구문오류라 구문 류라 함 함. 컴파일러가 제시하는 오류메시지를 이해하고 이해하고,, 각 오류메시지마다 그것을 어떻게 제거할 수 있는지를 알고 있어야 함. 보통 문법오류는 쉽게 고칠 수 있음. 있음 컴파일러는 오류는 아니지만 뭔가 이상한 상황이 있음을 알려주기 위해 경 경고(warning) 경고 ( g) 메시지를 줄 수 수도 있음 있음. 20/21 20 /21
프로그래밍 개발 환경 – 계속 문법 오류가 없으면 궁극에는 실행파일을 만들 수 있음. 그러나 이 파일을 실행하여도 원하는 결과를 얻지 못할 수 있음. 이것은 프로그램이 잘못 구현되어 있기 때문이며, 이와 같은 오류를 논리오류 또는 버그 버그(bug)라 함. 논리오류를 발생시킨 원인을 찾고 수정할 수 있도록 도와주는 시스템 소프트웨어를 디버거 디버거(debugger)라 함. 디버깅((debugging)이란 발견된 오류를 제거하는 과정을 말함. 디버깅
21/21 21 /21
©copyright 2010
C프로그래밍(CPA130) 강의노트 02
C 프로그래밍 첫걸음
한국기술교육대학교 한국기술 육대학 컴퓨 컴퓨터 터공학부 김상진
강의목표 C 언어의 구조 프로그램 환경에 대한 간단한 사용법 간단한 hello.c 프로그램을 통한 간단한 C 프로그램 소개 주석 #i l d 문 #include main 함수 오류의 종류와 오류를 수정하는 방법
2/30
C 언어의 역사 C 언어는 1972년에 미국 AT&T Bell Lab의 Dennis Ritchie와 Ken Thompson이 개발한 범용프로그래밍언어이며, 고급프로그래밍언어임. 고급프로그래밍언어임 유닉스라는 운영체제를 고급프로그래밍언어로 개발하기 위해 개발된 프로그래밍언어임. 래밍언어임 (시 (시스템수준의 템수준의 기능이 포함된 함된 이유) C 언어는 1989년에 미국표준인 ANSI C가 제정되었으며, 1990년에 국제표준인 ISO C가 제정되었음. 이 표준을 C89라 한다. 한다 1994년에 이 표준은 개정되었으며, 개정된 이 표준을 C95라 함. 1999년에 C 표준에 대한 전면 재검토를 하여 C99라는 새 표준을 제정하였음.
3/30
C 언어의 특징 구조화 프로그래밍 지원 지원: 구조화 프로그래밍이란 프로그램의 구조를 파악하기 쉽도록 프로그램을 작성하는 방식을 말함. 구조화 프로그래밍을 위해서는 프로그램 실행흐름을 제어하는 제어구조(control structure)와 각 프로그램의 세부 절차를 별도로 제어구조 정의할 수 있는 서브프로그램(subprogram)이 서브프로그램 서 램 램( p g )이 지원되어야 함 함. 이식성: 한 시스템에서 사용되던 것을 다른 컴퓨터 시스템으로 옮길 수 이식성 있는 특성을 말함. 실제 한 시스템에 작성된 C 프로그램을 다른 시스템에서 사용하기 위해서 그 시스템에 C 컴파일러가 존재해야 함. 효율성 율성 현재는 효율성보다는 유지보수, 편리성이 중요함. 간결성: 다양한 연산자 제공 간결성 프로그램의 가독성과 오류 발생의 원인이 될 수 있음.
4/30
C 언어의 구조 C 언어로 작성된 소스코드의 최소 단위는 문장(statement)이며, C 프로그래밍 문장은 ’;’ 기호로 구분됨. C 언어는 함수 함수(function)들로 구성되며, 각 함수는 여러 개의 프로그래밍 문장으로 구성됨. 함수란 입력을 받아 무언가 계산/처리하여 출력을 주는 것을 말함. 말함 예2.1) 두 개의 정수를 더하는 함수 입력: 두 개의 정수 계산: 두 정수를 더함 출력: 두 정수를 더한 결과 전체 프로그램을 하나의 함수로 작성할 수 있지만 이 경우 가독성(readability)이 떨어지며, 유지보수가 힘듦. 따라서 전체 프로그램을 램을 여러 개의 함수 함수로 나누어 작성하며, 이들 함수를 적절한 순서로 실행하여 원하는 결과를 얻게 됨. 이와 같이 전체 작업을 여러 개의 함수로 나누는 것을 모듈화 모듈화라 함. 5/30
C 언어의 구조 – 계속 모듈화를 어떻게 잘 하느냐가 C 언어를 이용한 프로그램 개발에 있어 매우 중요함. C 프로그램은 함수들의 집합으로 구성되어 있으며, 이 중 하나의 함수가 가장 먼저 실행되며, 이 함수가 종료되면 프로그램도 종료됨. 이 함수에서 다른 함수들을 실행하여 원하는 목적을 달성하며, 달성하며 한 함수에서 다른 함수를 실행하는 것을 호출 호출(call)이라 함. C 언어는 대소문자를 구분 구분함. C 언어에서 ABC, ABc, AbC, Abc, aBC, aBc, abC, abc는 모두 다른 이름으로 인식됨.
6/30
IDE 사용하기 이 수업에서는 콘솔 프로그램만 사용함. 콘솔 프로그램이란 명령창에서 실행되는 프로그램을 말함. 우리가 사용하는 Visual Studio 2008은 C++ 컴파일러임. 우리는 C 언어를 배우고 있으므로, “.c” 확장자를 꼭 사용해야 함. IDE 편집기의 구문 강조기능을 이해하고 사용해야 함. 함 주석은 녹색, 키워드는 파란색, 문자열은 적색 등 컴파일러가 제시하는 오류와 경고 메시지를 이해하고 이를 바탕으로 프로그램을 수정할 수 있는 능력을 배양해야 함. 디버깅과 Release 모드의 차이점 디버깅 모드로 생성된 실행 파일은 디버깅에 필요한 정보를 추가로 가지고 있지만 release 모드는 이런 추가 정보를 가지고 있지 않음. 실행파일의 파일크기가 작음.
7/30
개발환경 사용법
단계 1. 새 프로젝트를 생성함. 방법 1. 파일 메뉴 방법 2. 새 프로젝트 버튼 방법 3. 단축키: ctrl ctrl-shift-n shift n 단계 2. win32 콘솔 응용 프로그램 선택 단계 3. 프로젝트 이름 입력 8/30
개발환경 사용법 – 계속
단계 4. win32 응용 프로그램 마법사가 실행되면 [다음] 다 버 을 선택함. 버튼을 선택함 단계 5. 빈 프로젝트를 선택하고 [마침] 버튼을 선택함. 선택함 9/30
개발환경 사용법 – 계속
단계 6. 새 항목 추가를 선택 단계 7. C++ 파일 선택 하지만 우리는 C 언어로 프로그래밍함. 단계 8. 소스코드 이름을 입력하고 [추가] 버튼을 선택함. 이 때 확장자 .c를 c를 반드시 입력해야 함. 함 10/30 10 /30
개발환경 사용법 – 계속
단계 9. 코드를 작성함 단계 10. 빌드함(F7) 단계 11. 오류가 있으면 수정하고 다시 빌드해야 함. 하지만 오류가 없이 실행 파일이 생성되면 실행해 볼 수 있음. (ctrl F5) (ctrl-F5)
11/30 11 /30
Hello.c 프로그램 – 주석 /* ** File: hello.c ** Programmed by Sangjin Kim (2010 (2010.3) 3) */ …
/* hello.c */ #include <stdio.h> int main(void){ printf("Hello, World!\n"); return 0; }
주석 주석(comment): 컴파일러에 의해 번역되지 않는 부분 프로그램의 이해를 돕기 위해 프로그램에 대한 설명을 주석을 이용하여 첨가함. 프로그램 문서화의 중요한 부분 언어의 주석: 석 /*로 시작하여 */로 끝남. 남 C언어의 주석은 여러 줄을 걸쳐 작성할 수 있음. 문자열 상수( 상수(“…”)) 안에 등장하면 주석으로 간주되지 않음. 않음 프로그램 시작 부분에는 프로그램 주석이라고 주석 하는 주석으로 보통 시작하며, 이 부분은 프로그램에 대한 전체적인 설명이 포함됨. 12/30 12 /30
Hello.c 프로그램 – 주석 // : end-of-line 주석 (C++에서 도입한 주석 형태) 이 기호부터 줄 끝까지는 주석으로 처리함. C99 이후부터 C에도 이 형태의 주석이 도입되었음. 주석 사용의 주의점 주석은 프로그램의 어떤 위치에도 추가할 수 있지만 프로그램의 가독성을 저해하는 위치에 추가하는 것은 바람직하지 않음. 중첩된 주석은 허용하지 않음 않음. /* File: hello.c /* Programmed by Sangjin Kim (2008.3) */ */
13/30 13 /30
Hello.c 프로그램 – 전처리기 지시자 #include <stdio.h> …
전처리기 지시자 지시자(preprocessor directives) 주목. 일반 프로그래밍 문장은 ’;’ 기호로 끝남. 따라서 위 문장은 일반 프로그래밍 문장이 아님. 전처리기( 전처리기(preprocessor)란 전처리기 )란 컴파일러가 번역하기 전에 처리한다는 것을 의미함. 소스코드를 를 전처리기가 처리한 다음에 그 결과를 컴파일러가 번역함. 전처리기 지시자들은 전처리기가 할 일을 가르쳐주는 문장으로서 # 기호로 시작함. 시작함 ‘#’ #include는 무엇을 포함한다는 것을 의미함. ???
14/30 14 /30
Hello.c 프로그램 – 전처리기 지시자(계속) 라이브러리 라이브러리(library) library라는 영어 단어는 보통 도서관을 뜻하지만 수집 도서/문서를 library라고도 함. 프로그래밍언어에서 라이브러리는 이미 다른 사람들이 만든 프로그램들의 모음(함수들의 모음)을 말함. 말함 자주 사용하는 것을 매번 만들어야 한다면 번거롭다. 따라서 한번 만든 후에 동일한 용도가 필요하면 이미 만들어진 것을 다시 사용하고 싶다. 싶다 이것을 해주는 것이 라이브러리임. 라이브러리임 표준라이브러리(standard library)란 프로그래밍언어와 함께 표준라이브러리 제공되는 라이 라이브러리를 러리를 말함 말함. 다른 프로그래머가 만든 프로그램을 내 프로그램에서 사용하고 싶으면 그것을 사용할 것이라는 것을 컴파일러에게 알려주어야 함. 이 때 사용하는 것이 #include #i l d 전처리기 명령임. 명령임
15/30 15 /30
Hello.c 프로그램 – 전처리기 지시자(계속) /* hello.c */ 프로그램을 보면 printf라는 함수를 호출하여 사용하고 있음. #include <stdio.h> <stdio h> printf 함수는 표준 라이브러리에서 제공하는 함수로서, 표준출력(화면)에 int main(void){ printf("Hello, p ( World!\n"); ) 사용함 데이터를 출력할 때 사용함. return 0; 이 함수에 대한 정보는 stdio.h라는 } 파일에 있으며, 이 정보를 컴파일러에게 알려주기 위해 #include #i l d 문을 사용함. 사용함 stdio는 standard input output을 의미함. stdio h처럼 #include 문을 통해 프로그램에 포함하는 파일을 stdio.h처럼 헤더 파일 파일(header file)이라 함. C 언어에서 헤더 파일은 확장자가 “.h”임.
16/30 16 /30
Hello.c 프로그램 – 전처리기 지시자(계속) #include문의 문법 #include <file명> #include "file명" 두 방식은 명시된 파일을 찾는 순서가 다름. 전자는 표준 라이브러리 헤더 파일들이 저장되어 있는 디렉토리부터 검색하며, 후자는 현재 디렉 디렉토리부터 리부터 검색함 검색함. 따라서 표준 라이브러리 헤더 파일을 포함할 때에는 전자 방식을 사용함.
17/30 17 /30
Hello.c 프로그램 – main 함수 앞서 말한 바와 같이 C 프로그램은 함수들로 구성됨. 함수는 특정한 작업을 수행하여 주는 목적으로 구성된 일련의 프로그램 문장들의 집합을 말함. 프로그램: 함수들의 집합 함수: 프로그램 문장들의 집합 함수는 입력을 받아 처리하여 출력을 주는 것이 일반적인 형태임. 함수는 필 필요할 할 때마다 여러 번 호출하여 출하여 실행할 수 있음 있음. 따라서 각 함수를 독특하게 구분할 수 있는 함수의 이름이 필요함. 따라서 함수를 정의할 때에는 입력부분, 처리부분, 출력부분을 각각 정의해야 하며, 하며 함수 정의 형태는 다음과 같음. 같음 반환값의유형 함수이름(형식매개변수목록){ 함수몸체 입력부분 } 처리부분
출력부분
18/30 18 /30
Hello.c 프로그램 – main 함수(계속) 함수를 정의할 때 입력은 여러 개 존재할 수 있지만 출력은 오직 하나만 존재함. 입력 데이터나 출력 데이터는 어떤 유형(type)의 값이 입력되는지 컴파일러가 알아야 함. int: integer의 준말로서, 준말로서 값이 정수라는 것을 나타냄. 나타냄 입력이 여러 개 있을 수 있으므로 이들을 구별하여 사용하기 위해서는 각 입력마다 이름이 있어야 함. int sum(int a, int b){ return a+b; } 입력은 모두 정수 유형인 a와 b 두 개를 가지는 이름이 sum인 함수이고, 출력도 정수 유형이다.
19/30 19 /30
Hello.c 프로그램 – main 함수(계속) 프로그램이 여러 개의 함수로 구성되어 있으면 어떤 함수부터 실행해야 하는지 정해져 있어야 함. 이름이 main인 함수는 C 프로그램에 반드시 하나 존재 존재해야 하며, 프로그램이 실행되면 이 함수가 가장 먼저 실행되고, 이 함수가 종료되면 종 되면 프로그램도 램 종 종료함. 함 Hello.c 소스파일에서 main 함수는 다음과 같이 정의되어 있다. int main(void){ } 입력을 나열하는 부분에 void라고 쓰여져 있음. 이것의 의미는 이 함수의 입력이 없다는 것을 말함 말함. 이 함수의 출력 값의 유형은 int로 표시되어 있으므로 정수임. 이 함수의 몸체는 두 개의 프로그램 문장으로 구성되어 있음. 20/30 20 /30
Hello.c 프로그램 – main 함수(계속) main 함수는 두 개의 프로그램 문장으로 구성되어 있으며, 첫 번째 문장은 printf 함수 호출 문장임. 함수 호출의 문법은 다음과 같음. 함수이름(실제인수목록)
예2.1) sum(2, 3) printf는 p 는 표준출력인 준출력인 화면에 데이터를 출력할 때 사용함 사용함. main에 있는 printf는 Hello, World!를 화면에 출력함. 큰 따옴표를 묶여 있는 일련의 문자를 문자열 문자열(string) 유형이라 함. 두 번째 문장은 함수의 반환값을 주기 위해 사용하는 return 문임.
21/30 21 /30
Hello.c 프로그램 – printf 함수 printf("Hello, World\n"); 화면에 출력하고 싶은 문자 중 문자열 내에 표시하기 어려운 문자들이 있음. 예2.2) 줄바꿈 문자, 탭문자 문자열은 큰 따옴표로 구분하기 때문에 큰 따옴표를 출력하기 위해 printf( printf("Hello Hello, "World"!"); World ! ); 와 같이 사용할 수 없음. 없음 해결책: printf("Hello, \"World\"!"); ‘\’ 문자를 확장 문자라 하며, ‘\’을 포함한 문자열 나열을 확장 문자열 문자열(escape sequences)이라 함. 자주 사용하는 확장 문자열 \n: 줄바꿈 \t: 탭 \": 큰 따옴 따옴표 \\: ‘\’ (확장 문자를 나타내기 위해 \를 사용하기 때문)
22/30 22 /30
Hello.c 프로그램 – printf 예2.3) printf("Hello, World\n");와 다음 두 프로그램 문장의 실행 결과는 같음. printf("Hello, "); printf("World\n"); 하지만 함수가 두 번 호출하고 있으므로 효율적이지는 못함. 못함 예2.4) printf("Hello\nWorld!");의 실행 결과는 다음과 같음. Hello World
23/30 23 /30
main은 누가 호출하나? hello.c의 main 함수는 printf 함수를 호출하고 있음. 그러면 main 함수는 누가 호출하여 실행한 것일까? C 언어로 작성된 프로그램이 실행되면 제일 먼저 실행되는 것은 main 함수임. 즉 사용자가 C 언어로 작성된 특정 프로그램의 실행을 즉, 운영체제에게 요청하면 운영체제는 이 프로그램을 메모리에 적재하고, 이 프로그램의 main 함수를 실행함. 따라서 이 프로그램이 종료되면 0이라는 정수값을 반환하는데 이 값을 받는 주체는 운영체제임. 0 값을 돌려받 돌려받으면 면 운영체제는 해당 프로그램이 램이 아무런 문제없이 실행된 후에 종료하였다는 것을 알게 됨. 운영체제 (Windows)
인수: 없음 출 호출
인수 "Hello, World!\n" 전달하여 호출함 출함
main 복귀 반환값 0 반환값:
printf 복귀 반환값 ? 반환값:
24/30 24 /30
C 프로그래밍을 위한 좋은 습관 적절한 들여쓰기를 해야 한다. 보통 IDE는 알아서 적절한 들여쓰기를 해준다. /* hello.c */ #include <stdio.h>
/* hello.c */ #include <stdio.h> printf("Hello, World!\n"); int main(void){ printf( Hello, World!\n ); return 0; }
int main(void){ printf("Hello, World!\n"); return 0; 0 }
int main(void) { printf("Hello, World!\n"); return 0; }
나만의 프로그래밍 스타일을 가져야 함. 이름을 부여할 때에는 의미가 잘 전달되도록 부여해야 함. 적절한 주석을 반드시 사용함. 사용함
25/30 25 /30
오류의 종류 경우1. print("Hello, World!"); 경우1. print라는 단어를 컴파일러가 이해하지 못함. 컴파일 시간 오류, 문법 오류 오류(syntax error) 경우2. 경우 2. printf("Hello, World!); 큰 따옴표가 끝나지 않고 있음. 있음 이 문장 때문에 이 문장이 아닌 다른 이웃 문장에서 거짓 오류 메시지가 출력될 수 있음 있음. 경우3. 경우 3. printf("Hell, World!"); 어떤 오류 메시지도 출력되지 않음. 실행 시간 오류, 논리 오류 오류(logical error) 가장 흔한 오류 철자 오류 C 언어는 대소문자를 구분함.
26/30 26 /30
잘못된 프로그램의 수정 /* hello.c * / #incude <stdio.h> int Main(void){ print("Hell, World!\n"); return 0; }
오류메시지 오류발생 줄(9) 주석에서 예기치 않은 파일의 끝이 나타났습니다. 항상 쌍으로 등장해야 하는 것이 있음. 예를 들어 왼쪽 괄호 ‘(’가 있으면 반드시 대응되는 오른쪽 괄호 ‘)’가 ) 가 있어야 함. 함 주석은 “/*”로 시작하여 “*/”로 끝나야 하지만 “*”과 “/” 사이에 공백이 있어 주석 끝 기호로 인식하지 못함. 파일 끝까지 찾았지만 주석 끝 기호가 없이 끝났음. 컴파일러가 제시한 위치와 오류의 있음 위치가 항상 일치하지 않음을 알 수 있음.
27/30 27 /30
잘못된 프로그램의 수정 – 계속 /* hello.c */ #incude <stdio.h> int Main(void){ print("Hell, World!\n"); return 0; }
오류메시지 류메시지 오류발생 류발생 줄( 줄(3)) ‘incude’ 전처리기 명령이 잘못되었습니다. 많은 문법오류는 철자오류에서 발생함.
28/30 28 /30
잘못된 프로그램의 수정 – 계속 /* hello.c */ #include <stdio.h> int Main(void){ print("Hell, World!\n"); return 0; }
경고메시지 'print'(이)가 정의되지 않았습니다. 앞의 오류들은 치명적 오류(fatal error)임. 치명적 오류가 있으면 실행파일을 만들 수 없음. 컴파일한 결과를 보면 면 치명적 오류는 류는 없 없고,, 경고(warning)만 있음. 경고는 무시하고 실행파일을 만들 수 있지만 실행파일을 만드는 과정에서 추가적인 문제가 발생할 수 있음. 있음 “정의되어 있지 않다”라는 것은 컴파일러가 print함수에 정보를 때문임. p 함수에 대한 정 를 모르기 기 때문임 보통 이 경우도 철자 오류나 필요한 헤더 파일을 포함하지 않아 발생함.
29/30 29 /30
잘못된 프로그램의 수정 – 계속 /* hello.c */ #include <stdio.h> int Main(void){ printf(“Hell, World!\n”); return 0; }
컴파일 오류는 없지만 링크 오류가 있음. 컴파일은 한 소스파일을 목적파일로 바꾸는 과정임. 원래 프로그래머가 의도한 것은 Main이 아니라 main이지만 컴파일러는 Main이라는 함수를 정의한 것으로 간주함. 하지만 링크하는 과정을 실행하면 main 함수가 없어 실행파일을 만들 수 없음. 없음
30/30 30 /30
©copyright 2010
C프로그래밍I(CPA130) 강의노트 03
C 프로그래밍 기초
한국기술교육대학교 한국기술 육대학 컴퓨 컴퓨터 터공학부 김상진
강의목표 키워드 식별자 변수 자료유형 오버플로우/언더플로우/ 오버플로우/언더플로우/round-off d ff 오류 변수: 선언위치, 선언방법, 초기화, 제한사항, 가시영역 상수
2/27
키워드 프로그래밍 언어에서 키워드 키워드(keyword)란 고유한 의미를 가지는 예약되어 있는 단어를 말함. 이들 단어는 사용자가 다른 용도로 사용할 수 없음. 없음 auto
_Bool
break
case
char
_Complex Complex
const
continue
default
restrict
do
double
else
enum
extern
float
for
goto
if
_Imaginary
inline
int
long
register
return
short
signed
sizeof
static
struct
switch
typedef
union
unsigned
void
volatile
while
C99에 정의된 키워드
main은 키워드가 아니다. 아니다 하지만 main처럼 다른 용도로 사용하면 혼란을 가져다 줄 수 있는 이름들이 있음. 컴파일러마다 예약하여 사용하는 단어들도 있음. 3/27
식별자 프로그램을 작성할 때 프로그래머가 새롭게 이름을 부여해야 하는 경우가 많음. 이들을 식별자 식별자(identifier)라 함. 예3.1) 함수 이름, 변수 이름, 상수 이름 등 식별자는 정해진 규칙에 따라 명명되어야 함. 식별자 명명 규칙 영문자, 숫자, 밑줄문자(_)만을 사용할 수 있으며, 숫자로 시작할 수 없음. 예3.2) _total, sum, box01 등은 유효, 12dice, white-box 등은 유효하지 않음 중간에 공백 문자가 포함될 수 없음. 없음 int와 같은 키워드는 식별자로 사용할 수 없음. 식별자는 대 대소문자를 문자를 구분함 구분함.
4/27
식별자 – 계속 식별자의 종류에 따라 통일성 있게 이름을 명명하는 것이 바람직함. 식별자의 형태만 보면 이것이 어떤 식별자인지 알 수 있어야 함. 주로 낙타 형태(camel case)의 표기법(예: MasterCard)을 사용함. 두 개 이상의 단어를 연결하여 하나의 식별자로 사용할 때 각 단어의 첫 문자를 대문자로 사용하고 나머지는 모두 소문자로 사용하는 형태를 말함. 종류에 따른 표기법 상수: 모두 대문자, 예3.3) MAX 변수: 소문자로 시작하여 낙타 형태 표기법 사용, 예3.4) lastName 함수 변수와 동일, 함수: 동일 예3.5) 3 5) printStudent() i tSt d t() 타입: 대문자로 시작하여 낙타 형태의 표기법 사용, 예3.6) Student
5/27
데이터 프로그램을 작성하기 전에 반드시 분석해야 하는 것 중 하나는 이 프로그램에서 어떤 데이터가 필요한지 분석하는 것임. 프로그램에서 사용되는 데이터는 크게 상수 상수(constant)와 변수 (variable)로 구분될 수 있음. 상수: 프로그램이 실행되는 동안 변하지 않는 값 변수: 프로그램이 실행되는 동안 값이 변할 수 있는 데이터 프로그램에서 필요한 데이터의 종류는 다양하며, 데이터의 종류를 자료유형 또는 타입 타입(type)이라 함. 학생의 나이를 기록할 때에는 정수가 필요하고, 학생 성적의 평점을 기록할 때에는 실수가 필요하고, 필요하고 'F'와 같은 문자 데이터, "KUT"와 같은 문자열 데이터도 필요함. C 언어에서는 이런 데이터를 표현하기 현하기 위해 몇 가지 기본 자 자료유형을 유형을 제공함. 문자형, 정수형, 실수형(부동소수형), 문자열형 6/27
변수 long
변수 어떤 값을 저장하는 곳 변수: int 컴퓨터에서 값이 저장되는 곳은 주기억장치 프로그램 과정에서 데이터를 일시적으로 보관하기 위해 사용함. 일시적으로 저장한다는 것은 나중에 다시 사용한다는 것을 말함. 저장하는 값마다 이름을 할당해야 그것을 나중에 접근할 수 있음. 있음 이 이름을 변수 이름이라 함. 변수를 사용하기 위해서는 먼저 변수를 선언해야 하며 하며, 이 때 어떤 종류의 데이터를 저장할 것인지(자료유형 자료유형), 그리고 이 데이터를 어떤 이름(변수이름 변수이름)으로 접근할지를 알려주어야 함.
sum
7/27
자료유형 자료유형은 다음 두 가지 특성에 의해 정의됨. 도메인: 어떤 한 자료유형이 가질 수 있는 값들의 집합 자료유형에 적용할 수 있는 연산들의 집합 자료유형에 따라 필요한 공간의 크기가 다르며, 크기에 의해 자료유형에 저장할 수 있는 값의 범위가 결정됨. 결정됨 예3.6) 크기가 1바이트이고 양의 정수만 저장한다면 0에서 255까지만 저장할 수 있음. 정수형 타입에는 곱셈이라는 연산이 존재하지만 문자형의 경우에는 의미가 없는 연산임. 정수형과 부동소수형은 모두 나눗셈 연산을 가지고 있지만 그것의 평가 방법은 다름. 예3.7) 3/2 = 1, 3.0/2.0 = 1.5 어떤 연산을 데이터에 적용하기 전에 해당 연산이 적용하고자 하는 자료유형에 정의되어 있는지 있다면 어떤 기능을 수행하는지 파악하여야 함. 8/27
기본 자료유형 C95 문자형 정수형
정수형
부동소수형
char
signed g char
unsigned g char
[signed] short [int]
[signed] [int]
[signed] long [int]
unsigned short [int]
unsigned int
unsigned long [int]
float
do ble double
long double do ble
C99에 추가된 자료형 long long 우리가 사용하는 Visual Studio 2008은 long long 타입 지원 부동소수점 수(floating point number): C 언어에서는 실수를 부동소수점 수라 함. 여기서 점(point)은 소수점을 말하며, 수를 표현할 때 소수점을 위치를 수의 어느 위치에나 사용할 수 있기 때문에 부동소수점 수라 함.
9/27
signed vs. unsigned signed와 unsigned의 차이 signed는 음수/양수를 모두 나타낼 수 있지만 unsigned는 양수만 표현할 수 있음. 부호가 있는 경우에는 첫 비트는 부호 비트로 사용되며, 양수만 표현할 경우에는 부호 비트가 필요 없음. 없음 따라서 4비트로 수를 표현하면 signed(2의 보수)는 –8에서 7, unsigned는 0에서 15까지 표현할 수 있음. unsigned을 사용하고 싶을 경우에만 unsigned를 사용한고, signed는 주로 생략함. 따라서 정수형들은 기본형과 unsigned형 두 가지만 기억하면 됨. 됨
10/27 10 /27
기본 자료유형의 크기 보통 char는 1 byte, short는 2 byte, int는 4 byte, long은 4 byte 이상이다. 하지만 컴파일러마다 차이가 있을 수 있다. 하지만 다음은 항상 성립함. 성립함 sizeof(char) ≤ sizeof(short) ≤ sizeof(int) ≤ sizeof(long) 정수형에서 기본이 되는 형: int 보통 float는 4 byte, double는 8 byte, long double은 8 byte 이상이다. 하지만 컴파일러마다 차이가 있을 수 있다. 하지만 다음은 항상 성립함. 성립함 sizeof(float) ≤ sizeof(double) ≤ sizeof(long double) 부동소수형에서 기본이 되는 형: double 기본이 되는 형이란 특별한 표시를 하지 않으면 해당 형으로 간주 한다는 것을 말함.
11/27 11 /27
기본 자료유형의 크기 – sizeof 연산자 sizeof는 기본 자료유형의 크기(단위: 바이트)를 알려주는 연산자 연산자임. 하지만 사용은 마치 함수처럼 사용됨. 문법 1. sizeof (변수명) 문법 2. sizeof (자료형명) 문법 3. 3 sizeof 변수명 예3.8) int n = 10; sizeof n == sizeof(n) ( ) == sizeof(int) ( )
12/27 12 /27
오버플로우 특정한 자료유형은 컴파일러에 따라 다를 수 있지만 고정된 크기의 공간을 사용함. 따라서 특정 자료유형의 변수에 저장할 수 있는 값의 범위는 제한적임. 제한적임 예3.9) unsigned char 변수의 크기는 보통 1 byte이므로 이와 같은 변수에는 0에서 에서 255까지의 까지의 값만 유지할 수 있음 있음. unsigned char n1 = 200; unsigned char n2 = 100; n1 = n1 + n2;
하지만 n1에는 크기 제한 때문에 300이 저장될 수 없다.
이와 같이 유지할 수 있는 값보다 큰 값을 저장하고자 할 때 발생하는 현상을 오버플로우 오버플로우(overflow)라 함. 함 C 언어는 자료유형에 대해 엄격한 언어가 아님. 따라서 오버플로우가 일어나면 오류가 발생하지 않고, 현재 형이 수용할 수 있는 형태로 바뀜. 예3.10) 예3.9에서 300의 이진형태 100101100이므로 n1에 실제 저장되는 것은 00101100 = 44이다. 13/27 13 /27
언더플로우 정수형들을 사용할 때에는 오버플로우만 걱정하면 되지만 부동소수형들을 사용할 때에는 언더플로우 언더플로우(underflow)도 걱정해야 함. 언더플로우: 0과 가까운 매우 작은 수를 나타낼 수 없는 현상 하지만 부동소수형을 사용할 때 가장 주의해야 하는 것은 round round--off 오류이다 예를 들어 8비트를 이용하여 부동소수를 표현한다고 오류이다. 가정하면 256개의 다른 소수값 밖에 나타낼 수 없음. 즉, 특정 소수값의 표현은 불가능하며, 가장 가까운 값으로 대신 표현할 수 밖에 없으며, 없으며 이 차이에 의해 발생하는 오류를 말함. 말함 underflow overflow
overflow 0
14/27 14 /27
부동소수 표현방식 부동소수 표현방식
부호(1비트) 지수(8비트)
가수(23비트)
예3.11) 8비트: 1/2/5 3.7510 = 11.112 = 1.111 21 0 10 11100 위 예서 알 수 있듯이 가수 부분으로 나타낼 수 있는 수는 총 25이며, 지수 부분이 나타낼 수 있는 값의 범위는 –1부터 1부터 2이다. 2이다 양수, 음수, 지수를 고려하여도 전체적으로 8비트를 사용하므로 나타낼 수 있는 수는 총 28개임. round-off 오류: 이 표현방식으로 나타낼 수 있는 실수는 매우 제한적이기 때문에 발생하는 오류. 언더플로우: 2-1보다 작은 수는 표현할 수 없음.
15/27 15 /27
변수의 선언 선언(declaration) 위치: 함수 외부 또는 함수 내부 함수 외부에 선언된 변수: 전역 변수 변수(global variable) 함수 내부에 선언된 변수: 지역 변수 변수(local variable) 함수 내부에 변수를 선언할 때 선언 위치 함수 시작 부분에 선언하며, 선언하며 변수를 선언하는 문장이 아닌 다른 프로그래밍 문장이 기술된 이후에는 선언할 수 없음. 변수 선언 문법 typename variablename; sum 예3.12) int sum; 변수는 선언과 동시에 초기화할 수 있다. 즉, 값을 선언과 동시에 저장할 수 있으며, 그것의 문법은 다음과 같음. typename variablename = initial_value; initial value; 예3.13) int sum = 0; sum 16/27 16 /27
변수의 선언 – 계속 변수를 선언한 후에 나중에 대입문을 이용하여 초기화할 수 있다. 하지만 두 가지 방법의 차이가 프로그램에 영향을 주지 않으면 선언과 동시에 초기화하는 것이 효율적임. 효율적임 예3.14) int sum; sum = 0;
초기화하지 않은 변수에는 어떤 값이? 예3.15) int sum; printf("%d\n", sum);
변수의 종류에 따라 다르지만 함수 내부에 선언되는 지역변수가 초기화되지 않은 경우에는 이 변수가 초기에 어떤 값을 가지게 될지는 알 수 없다.
17/27 17 /27
변수의 선언 – 계속 동일 유형의 여러 개의 변수를 동시에 선언할 수 있음. 예3.16) int sum, n1, n2; 하지만 한 줄에 하나의 변수만 사용하며, 변수의 용도를 주석처리 하는 것이 가장 좋은 변수 선언 방법임. int n1; int n2; int sum;
// 사용자로부터 입력 받을 정수 // 사용자로부터 입력 받을 정수 // 사용자로부터 입력 받은 두 정수의 합
int n1, n2, sum;
// 사용자로부터 입력 받을 정수 // 사용자로부터 입력 받을 정수 // 사용자로부터 입력 받은 두 정수의 합
18/27 18 /27
변수의 선언 – 계속 변수는 선언된 이후에만 사용할 수 있음. 선언된 줄 이후부터 사용할 수 있음. 예3.17) 옆 코드 지역변수: n1, n2, n3 전역변수 g1 전역변수: 1 지역변수는 그것이 선언된 함수 내에서만 사용할 수 있음 있음. 전역변수는 그것이 선언된 이후에 정의된 모든 함수에서 사용할 수 있음. 변수가 사용될 수 있는 범위를 가시영역(scope)이라 함. 가시영역 사용할 수 있는 범위가 같은 영역에 같은 이름의 변수를 선언할 수 없음.
void f1(void) { int n1; } // f4() int g1; ( ) void f2(void) { int n2; } // f2() void f3(void) { int n3;; } // f3()
19/27 19 /27
변수의 사용 예3.18) 두 개의 정수를 입력 받아 합을 출력하는 프로그램 /* sum.c */ #include <stdio.h> int main(void){ int sum; int n1; int n2; printf("첫 번째 정수를 입력하시오: "); scanf("%d", &n1); printf("두 printf( 두 번째 정수를 입력하시오: "); ); scanf("%d", &n2); sum = n1+n2; printf("%d + %d = %d\n", n1, n2, sum); return 0; }
20/27 20 /27
문자의 저장 단일 문자는 char 유형을 사용함. 문자 상수는 작은 따옴표를 사용하여 표현함. 예3.19) char c = 'a'; 실제 c 변수에 문자 'a'에 대한 7비트 아스키코드 값이 저장됨. 대부분의 문자코드는 알파벳 순서로 코드 값이 부여됨. 부여됨 즉 'c' –'a'는 2임. 따라서 c = 'n'+2이면 n +2이면 c 변수에 문자 'p'에 p 에 해당하는 코드가 저장됨.
21/27 21 /27
scanf/printf scanf는 표준입력(키보드)으로부터 데이터를 입력 받기 위해 사용하고, printf는 표준출력(화면)에 데이터를 출력하기 위해 사용함. 표준입력으로 데이터를 입력 받기 위해서는 어떤 자료유형의 값을 받을지 알려주어야 함. 이를 위해 변환 변환(conversion) 지시자 지시자를 사용하며, 사용하며 변환 지시자는 % 기호로 시작됨. 정수 데이터를 입력 또는 출력 받고 싶을 경우에는 %d를 보통 사용함. 사용함 제어문자열 또는 출력문자열 내에 변환 지시자와 이들 문자열 다음에 나열되는 값의 개수가 같아야 하며, 순서에 따라 1:1로 매핑됨 매핑됨. printf("%d + %d = %d\n", n1, n2, sum);
입력 받을 경우에는 입력 받을 값을 임시로 저장할 변수이름을 제어문자열 다음에 나열함. 이 때 변수이름 앞에 & 기호를 사용함. 출력의 경우에는 &기호가 기 가필 필요 없음 없음. 22/27 22 /27
대입문과 표현식 대입문(assign statement)은 변수에 값을 저장하기 위해 사용하는 프로그램 문장임. sum = n1+n2; 1+ 2 variable = expression; l-value = expression; p 표현식(expression): 항(term)과 연산자 표현식 연산자(operator)로 구성된 식을 말하며, 표현식은 평가(evalutate)되어 어떤 결과 값을 주게 됨. 항이 될 수 있는 것 상수, 변수, 함수 호출, 괄호로 된 표현식 3 + 6 n+2 이항 산술 연산자 sqrt(2) + 3.5 +: 덧셈 (단항연산자로 단항연산자 사용 가능) 가능 (4+2) / 3 –: 뺄셈 (단항연산자로 사용 가능) *:: 곱셈 /: 나눗셈 (정수 나눗셈과 부동소수 나눗셈의 의미가 다름) %: 나머지 (두 피연산자가 모두 양의 정수) 23/27 23 /27
표현식의 평가 표현식의 평가에 있어 중요한 것은 연산자의 우선순위 우선순위(precedence) 와 결합순서 결합순서(associativity)임. 표현식 a+b*c는 +b* 는 a+(b*c)와 +(b* )와 같이 평가됨. 평가됨 즉, + 연산자보다 * 연산자가 우선순위가 높음. 두 개의 연산자가 공통되는 피연산자를 가지면 우선순위가 높은 연산자부터 평가됨. 표현식 a*b*c는 (a*b)*c와 같이 평가됨. 첫 번째 * 연산자와 두 번째 * 연산자는 모두 b라는 공통된 피연산자를 가진다. 따라서 우선순위를 고려해야 하는데, 동일한 연산자이므로 우선순위가 같음. 우선순위가 같으면 결합순서에 따라 평가방법이 결정됨. 결합순서란 평가하는 순서 방향을 나타냄. * 연산자는 왼쪽에서 오른쪽으로 평가됨. 평가됨
24/27 24 /27
상수 상수 상수(constant): 변하지 않는 값을 상수라 함. 보통 자료값 자체를 프로그램에 직접 표현하면 이들은 모두 상수로 간주됨. 예3.20) int sum = 0; 여기서 0은 정수 상수 값임. 값임 동일한 상수를 여러 번 사용할 경우에는 값 자체를 직접 표현하지 않고, 상수에도 이름을 할당하여 값 대신에 이름을 사용할 수도 있음.
25/27 25 /27
상수 – 계속 정수 상수 보통 10진수로 표현하지만 8진수 또는 16진수로 표현 가능 8진수는 앞에 '0'을, 16진수는 앞에 "0x" 또는 "0X"를 붙인다. 예3.21) 10, 012, 0xA 접미사를 사용하지 않는 정수 상수는 int i t 타입임. 타입임 U 또는 u 접미사: unsigned 타입의 상수 정의 L 또는 l 접미사: long int 타입의 상수 정의 부동소수점 상수 일반적인 표기법과 과학적 표기법 두 가지 방법으로 표현 가능 예3.22) 2.9978, 2.99E-2(=0.0299) 접미사를 사용하지 않는 부동소수점 상수는 double 타입임. F 또는 f 접미사: 접미사 float fl t 타입의 상수 정의 L 또는 l 접미사: long double 타입의 상수 정의
26/27 26 /27
상수 – 계속 문자열 상수: 큰 따옴표를 붙여 표현 예3.23) "KUT" 문자열 상수는 끝에 null 문자 '\0'(=0)를 포함함. 문자열 상수들은 컴파일 시간에 결합할 수 있으며, 이 기능은 긴 문자열을 여러 줄에 걸쳐 작성하는데 널리 사용됨. 사용됨 예3.24) "한기대 " "컴퓨터공학부"와 "한기대 컴퓨터공학부"는 같음. 문자 상수: 작은 따옴표를 붙여 표현 예3.25) 'K' 'K'와 "K"는 큰 차이가 있음. 있음 전자는 단일 문자이며, 문자이며 후자는 단일 문자로 구성된 문자열임.
27/27 27 /27
©copyright 2010
C프로그래밍(CPA130) 강의노트 04
전처리와 입출력
한국기술교육대학교 한국기술 육대학 컴퓨터공학부 김상진
강의목표 #define을 이용한 기호상수와 매크로 정의 scanf/printf 사용법
2/24
전처리기 지시자 ‘#’ 기호로 시작하며, 컴파일러가 번역하기 전에 처리함. 예4.1) #include는 주어진 헤더 파일을 현재 파일에 포함시켜 줌. 전처리 과정 소스파일
전처리기
전처리된 소스파일
컴파일러
목적파일
3/24
#define을 이용한 매크로 정의 #define은 매크로 매크로(macro)를 정의하기 위해 사용되는 전처리기 지시자임. 매크로는 인자를 가질 수 있으며, 인자가 없는 매크로는 보통 기호상수(symbolic constant)를 정의하기 위해 사용됨. 기호상수 문법.. 인자가 없는 매크로 정의 문법 #define 매크로명 치환문자열 예4.2) #define PI 3.14 #define의 동작 인자가 없는 매크로를 정의하면 전처리기가 컴파일러가 번역하기 전에 매크로 정의 이후에 등장하는 매크로명에 해당하는 문자열이 등장하면 이것을 모두 치환문자열로 바꾸어 줌. 워드프로세서에서 전체 파일을 대상을 문자열 찾아 바꾸기를 실행하는 것과 같은 원리
4/24
#define을 이용한 매크로 정의 – 계속 예4.3)
void main(void){ double area; double radius = 5.0; area = 2 * PI * radius * radius; }
전처리기
#define PI 3.14 3 14
void main(void){ double area; double radius = 5.0; area = 2 * 3.14 * radius * radius; }
#define 사용의 장점 가독성 증가 (매직수 대신에 의미있는 이름을 사용함) 매직 수란 아무런 설명 없이 등장하는 수치 상수를 말함. 수정 용이 (한 곳을 수정하면 여러 곳이 동시에 바뀜)
5/24
#define을 이용한 매크로 정의 – 계속 #define은 기호상수 뿐만 아니라 다음과 같이 사용 가능함.
void main(void){ output("Hello World!\n"); }
전처리기
#include <stdio.h> #define output printf
#include <stdio.h> void main(void){ printf("Hello World!\n"); }
하지만 이와 같은 사용은 바람직하지 않음. 인자 없는 매크로는 기호상수를 정의하기 위해서만 사용하는 것이 바람직하며, 다른 용도로 사용하는 것이 가급적 피해야 함. 최근에 기호상수도 #define을 사용하지 않고 const 키워드를 사용하는 경우가 많음. 예4.4) const double PI = 3.14;
6/24
const와 #define의 차이점 const를 이용한 상수변수 선언 변수를 선언과 동시에 초기화하면서 const 수식어를 사용하면 이 변수는 초기화된 값으로 고정됨. 이 변수에는 새로운 값을 대입할 수 없음. 예4.5) 4 5) void main(void){ const int MAX = 10; int n = 5; MAX = 5; // error n = 10; }
const와 #define의 차이점 const
#define
공간차지
전처리 과정에서 치환됨
즉시 문법 검사 가능
전처리 이후 가능
가시영역에 대한 제한
가시영역 제한이 없음
복잡한 형태의 상수 정의 가능
단순한 상수만 정의 가능 7/24
#define을 이용한 매크로 정의 – 계속 #define을 이용하여 인자가 있는 매크로를 정의할 수 있음. 문법 #define 매크로명(인자명) 치환문자열 매크로명과 (인자명) 사이에 공백이 없어야 함. 예4.6) 4 6) #define PI 3.14 #define SQUARE(x) ((x) * (x)) void main(void){ double area; double radius = 5.0; area = 2 * PI * SQUARE(radius); } // main()
void main(void){ double area; double radius = 5.0; area = 2 * 3.14 * ((radius) * (radius)); } // main()
8/24
#define을 이용한 매크로 정의 – 계속 주의사항: 괄호의 적절한 사용이 반드시 필요함. 예4.7) #define PI 3.14 #define SQUARE(x) x * x void main(void){ double area; double radius = 5.0; area = 2 * PI * SQUARE(3+5); } // main()
void main(void){ double area; double radius = 5.0; area = 2 * 3.14 * 3 + 5 * 3 + 5; } // main()
9/24
#define을 이용한 매크로 정의 – 계속 함수와 매크로의 차이점 함수는 호출되지만 매크로는 전처리 과정에서 대체되는 것임. 따라서 속도 측면에서 매크로가 우수함. 함수는 정의된 입력/출력의 자료유형에 의해 제한되지만 매크로는 이런 제한이 없음. 없음 하지만 매크로는 전처리 명령이기 때문에 한계가 있음.
#define SQUARE(x) ((x) * (x)) void main(void){ double d = SQUARE(3.2); int n = SQUARE(3); } // main() ()
int square(int x){ return x * x; } // square(int) void main(void){ double d = square(3.2); // 9.0; q ( ); int n = square(3); } // main()
10/24 10 /24
#define에 대한 결론 #define은 옛 스타일이다. const가 C 언어에 도입되기 전에는 #define을 이용하여 기호상수를 정의할 수 밖에 없었음. 과거는 효율성이 가장 우선시 하였음. 과거 코드를 이해하기 위해서는 #define 기능을 알고 있어야 함. 함 새로 프로그램을 작성하면 #define을 사용할 필요가 없음. 인자 없는 매 매크로,, 즉 상수를 정의해야 하는 경우에는 const를 를 사용하는 것이 바람직함. 인자 있는 매크로를 사용하기 보다는 함수를 정의하여 사용하는 것이 바람직함. 바람직함
11/24 11 /24
표준입출력 표준입출력에서 표준입력은 보통 키보드를 말하며, 표준출력은 화면을 말함. 표준 입력은 보통 scanf 함수를 사용하고, 표준 출력은 printf 함수를 사용함. Visual Studio에서는 scanf 대신에 scanf_s를 scanf s를 사용 사용자로부터 입력 받을 때에는 반드시 어떤 종류의 입력을 기대하는지 사용자에게 알려주어야 하며, 이 때 사용하는 설명 문구를 프롬프트( 프롬프트(prompt)라 프롬프트 t)라 함. 함 예4.8) int n; printf("정수 하나를 입력하시오: "); // prompt scanf_s("%d", &n);
12/24 12 /24
scanf scanf 함수와 printf 함수는 인수의 개수가 가변적임. 그러나 항상 첫 번째 인수에는 제어문자열 제어문자열(control string)이 와야 함. 제어문자열 내에는 %로 시작하는 변환 명세 명세(conversion specification)가 포함되며, 일반적으로 변환 명세의 개수만큼 추가적인 인수가 제어문자열 다음에 오게 됨. 됨 scanf를 이용한 데이터의 입력 입력 받고 싶은 데이터의 형식과 개수를 결정해야 함. 입력 받은 데이터를 어디에 보관할지 결정해야 함. 변수 이름 앞에는 & 연산자가 보통 사용됨. scanff 사용시 주의 사항 제어문자열에 있는 변환명세의 개수와 그것에 대응되는 변수의 개수가 일치해야 함 함. 제어문자열에 있는 변화명세를 통해 입력 받고자 하는 자료유형과 그것에 대응되는 변수의 자료유형이 일치해야 함.
13/24 13 /24
scanf 예4.8) 사람의 나이를 입력 받고 싶다. 사람의 나이는 정수형 데이터임. 이 데이터를 age라는 정수 변수에 보관하고자 함. int age; printf("나이를 f( 나이를 입력하시오: 입력하시 "); ) scanf_s("%d", &age);
예4.9) 4 9) 사람의 키와 몸무게를 입력 받고 싶다. 싶다 사람의 키와 몸무게는 부동소수형 데이터임. 이 데이터를 height, weight에 보관하고자 함. float height, weight; printf("키와 몸무게를 입력하시오: "); scanf_s( %f %f" %f , &height, scanf s("%f &height &weight);
14/24 14 /24
scanf – 계속 scanf에 사용되는 변환 명세 입력 형태
설명
s
일련의 문자
문자열
c
문자
문자
u
[–|+]dd…d
양의 정수
d
[–|+]dd…d
10진수 정수
i
[–|+][0[x|X]]dd…d
8 10, 8, 10 16진수 정수
o
[–|+][0]dd…d (여기서 d는 8진수)
8진수 정수
x
[–|+][0[x|X]]dd…d (여기서 d는 16진수)
16진수 6진수 정수
[–|+]dd…d.dd…d
부동소수 (lf: double, Lf: long double)
conversion
a, e, f, g
[–|+]dd…d.dd…d[e|E][–|+]dd…d
15/24 15 /24
scanf – 계속 예4.10) int n; char c; printf("정수 하나와 문자 하나를 입력하시오\n"); scanf_s("%d", &n); scanf_s("%c", &c); printf("n = %d\n", n); printf("c = %c\n", c);
결과 10 a n = 10 c=
사용자가 키보드로부터 입력한 내용은 바로 컴퓨터 프로그램으로 전달되는 것이 아니고 입력버퍼(buffer)라는 곳으로 전달됨. (출력도 프로그램에서 바로 화면으로 전달되는 것이 아니라 출력버퍼로 전달됨 ) 전달됨.) 1
0
a
\n
scanf_s는 이 버퍼에서 입력을 처리한다. 첫 번째 %d에 의해 10이 n 변수로 전달되고, 두 번째 %c에 의해 공백문자가 c 변수로 전달된다. 16/24 16 /24
scanf – 계속 int n1; int n2; scanf_s("%d", &n1); scanf_s("%d", &n2);
입력의 예 10 20
1
0
\ \n
2
0
\ \n
scanf_s는 %d를 처리하기 위해 버퍼에서 정수데이터를 취하며 공백문자, 줄바꿈 문자 등은 건너뛰므로 앞 예와 달리 이 경우는 문제 없음.
결론: %c를 사용할 경우에만 주의해야 함. 여기서 잠깐. 버퍼를 왜 사용하나? 버퍼는 데이터가 생성되는 속도와 데이터를 처리하는 속도 사이의 완충 장치 역할을 해줌.
17/24 17 /24
printf 출력 변환 명세의 구조 [flag][field-width][.precision][size spec.][conversion-character] % –#0
시작기호
플래그
12
.4 정확도
최소 폭
h
d 변환명세 크기
18/24 18 /24
printf – 계속 플래그: 아무런 순서로 나열할 수 있지만 함께 사용할 수 없거나 함께 사용하면 의미가 없는 플래그가 있음. flag
의미
유효한 변환명세
–
데이터가 왼쪽으로 줄맞춤된다. 데이터는 기본적으로 오른쪽으로 줄맞춤된다
+
수치 테이터 앞에 항상 + 또는 - 기호가 붙여진다.
a, A, d, e, E, f, g, G, i
수치 테이터 앞에 항상 공백 또는 는-기 기호가 가 붙여진다 붙여진다.
a,, A,, d,, e,, E,, f,, g, G,, i
공백 0
수치 데이터에서 필드의 빈 공간을 0으로 채운다. 빈 공간은 기본적으로 공백문자로 채워진다.
#
변환명세가 o이면 0이 출력 앞에 붙여지고, 붙여지고 x 또는 X 이면 0x 또는 0X가 출력 앞에 붙여진다. 부동소수 데이터의 경우에는 항상 .이 출력된다.
i o, i, o x, x X, X e, e E E, f, f g g, G
19/24 19 /24
printf – 계속 예4.11)
int n1 = 235; int n2 = –235;; printf("%+d\n", n1); printf("%+d\n", n2);
+235 -235
int n1 = 235; int n2 = –235; printf("% printf( % d\n", d\n , n1); printf("% d\n", n2);
235 -235 235
예4.12) 4 12)
20/24 20 /24
printf – 계속 최소 필드 폭: 출력 필드에서 사용되는 최소한의 문자 수를 지정함. 필드 폭에 의해 지정된 폭보다 적은 공간을 차지하는 값을 출력할 때에는 지정된 필드 크기가 될 때까지 공간을 채움 문자(padding 문자 character)로 채움. 공백 문자가 기본적인 채움 문자로 정의되어 있으며, 있으며 0 플래그를 사용하면 ‘0’이 채움 문자가 됨. 필드 폭 대신에 *를 사용할 수 있으며, 이 경우에는 정수형 인수의 값을 필드 너비 값으로 사용함. 사용함 예4.13) int n1 = 235; int i t n2 2 = –235; 235 int width = 5; printf("%10d\n", n1); printf("%–10d\n", p ( % , n1); ); printf("%010d\n", n1); printf("%*d\n", width, n2); printf("%2d\n", n1);
235 235 0000000235 -235 235
21/24 21 /24
printf – 계속 정밀도: 소수점 다음에 명시하며, 변환 명세에 따라 의미가 다르다. f, e, E: 소수점 이하에 출력되는 자리수 정밀도를 사용하지 않으면 항상 소수점 이하 6자리를 출력함 g, G: 유효 숫자의 최대 개수 s: 출력할 수 있는 최대 문자의 수 d, i, o, x, X, u: 출력하여야 하는 최소 문자의 수 예4.14) 4 14) 2.850000 double era = 2.85; printf("%f\n", era); 2.85 printf("%.2f\n", era); 2.9 printf("% printf( %.1f\n 1f\n", era); 2.85 printf("%10.2f\n", era); 0000002.85 printf("%010.2f\n", era); double f = 3.14159265358979323846; printf("%f\n", era); printf("%10f\n", era); i tf("% 3f\ " era); ) printf("%.3f\n", printf("%10.12f\n", era);
3.141593 3.141593 3.142 3.141592653590 22/24 22 /24
printf – 계속 변환 명세 conversion
설명
s
문자열
c
문자
u
양의 정수
d, i
10진수 정수
o
8진수 정수
x, X
16진수 정수
e, E
과학적 표기법
f
부동소수점 표기법 (lf: double, double Lf: long double)
g, G
과학적 표기법 또는 부동소수점 표기법 중 하나로 출력
a, A
16진수 형태로 부동소수점 표기법
%
%를 출력
23/24 23 /24
getchar, putchar 문자 단위로 입출력할 때에는 scanf 대신에 getchar, putchar를 사용할 수 있음. 예4.15) 소문자를 대문자로 void main(void){ char c1; char c2; printf("소문자를하나입력하시오: "); c1 = getchar(); c2 = c1 - 'a' + 'A'; putchar(c2); putchar('\n'); }
각 영문자의 아스키코드 값은 연속적이다. 즉 ‘a’ 문자의 아스키 코드 값이 n이면 ‘b’ b 문자의 아스키 코드 값은 n+1이다. 따라서 c1이 소문자일 때 이 값에서 ‘a’을 빼면 0부터 25 사이의 값을 가지게 됨.
24/24 24 /24
©copyright 2010
C프로그래밍I(CPA130) 강의노트 05
연산자
한국기술교육대학교 한국기술 육대학 컴퓨터공학부 김상진
강의목표 표현식 우선순위와 결합순서 대입연산자 산술연산자 증감연산자 관계연산자 논리연산자 비트연산자
2/23
표현식 표현식 표현식(expression): 항(term)과 연산자 연산자(operator)로 구성된 식을 말하며, 표현식은 평가(evalutate)되어 어떤 결과 값을 주게 됨. 항이 될 수 있는 것: 상수, 변수, 함수 호출, 괄호로 된 표현식 연산자는 피연산자 수에 따라 단항 단항(unary)연산자, 이항(binary)연산자 삼항 이항(binary)연산자, 이항 삼항(ternary)연산자로 분류함. 분류함 하나의 연산자가 여러 의미로 사용될 수 있음. 예5.1) 하나의 연산자가 단항과 이항연산자로 사용되는 경우 a – b, –a a * b, *p (포인터 연산) 두 개의 문자를 결합하여 하나의 연산자로 사용할 경우에는 반드시 두 문자를 붙여서 사용해야 함. 표현식 현식 끝에 ;을 붙이면 프로그래밍 래밍 문장이 됨 됨. 예5.2) 3+2; // 하지만 의미 없는 문장임.
3/23
표현식 – 계속 표현식의 평가에 있어 중요한 것은 연산자의 우선순위 우선순위(precedence) 와 결합순서 결합순서(associativity)임. 표현식 a+b*c는 a+(b*c)와 같이 평가됨. 즉, + 연산자보다 * 연산자가 우선순위가 높음. 두 개의 연산자가 공통되는 피연산자를 가지면 우선순위가 높은 연산자부터 평가됨. 우선순위를 모르면 항상 괄호를 이용하여 원하는 우선순위를 나타낼 수 있으며, 괄호를 사용하면 가독성이 높아질 수 있음. 표현식 a*b*c는 (a*b)*c와 같이 평가됨. 첫 번째 * 연산자와 두 번째 * 연산자는 모두 b라는 공통된 피연산자를 가진다. 하지만 동일한 연산자이므로 우선순위가 같음. 우선순위가 같으면 결합순서에 따라 평가방법이 결정됨. 결합순서란 평가하는 순서 방향을 나타냄. * 연산자는 왼쪽에서 오른쪽으로 평가됨.
4/23
연산자의 분류 분류
연산자
산술 연산자
+ - * / %
증감 연산자
++ --
비교 연산자
> < == != >= <=
논리 연산자
&& || !
조건 연산자
?:
비트 논리 연산자 & | ^ ~ 비트 이동 연산자 << >> sizeof 연산자
sizeof
대입 연산자
= + += -= *= / /= % %= & &= | |= ^= >> >>= << <<=
분류
연산자
단항 연산자
- ++ -- s sizeof eo
이항 연산자
산술 연산자, 비교 연산자, 논리 연산자 등
삼항 연산자
?:
5/23
우선순위/결합순서 요약 알기 쉬운 연산자 우선순위 단항 연산자가 이항 연산자보다 우선순위가 높음. 산술 연산자 중에는 곱셈 계열의 연산자가 덧셈 계열의 연산자보다 우선순위가 높음. 산술 연산자는 관계 연산자 보다, 보다 관계 연산자는 논리 연산자 보다, 보다 논리 연산자는 약식형태를 포함한 대입연산자보다 우선순위가 높음. 알기 쉬운 결합순서 단항 연산자는 대부분 오른쪽에서 왼쪽으로 이항 연산자는 대부분 왼쪽에서 오른쪽으로 이항 연산자 중 대입 연산자는 오른쪽에서 왼쪽으로
6/23
대입 연산자 예5.3) sum = n1+n2; variable = expression; l-value = expression; 대입 연산자 오른쪽에 있는 표현식을 평가하여 결과 값을 왼쪽 변수에 저장함. 저장함 따라서 대입문 왼쪽에 반드시 변수가 와야 함. 보다 다 정확하게 표현하면 현하면 l-value가 가 와야 함 함. 대입문 자체도 표현식이다. 대입되는 값이 결과 값이 됨. 예5.4) n = (m = 2); 예5.5) n = m = 2; 두 개의 = 연산자는 m이라는 공통 피연산자를 가지고 있음. 동일 연산자이므로 결합순서에 의해 평가 순서가 결정됨. 결정됨 = 연산자의 결합순서는 오른쪽에서 왼쪽 왼쪽이다. 즉 m = 2가 먼저 평가됨. 7/23
대입 연산자 – 계속 예5.6)
int n; n = 5.5;;
// n = 5
대입식에서 왼쪽 평가 결과의 자료유형과 오른쪽 변수의 자료유형은 기본적으로 같아야 바람직함. 바람직함 하지만 다를 경우에는 왼쪽 평가 결과 값이 자동으로 오른쪽 타입으로 변환됨. 이 때 정보손실이 발생 발생할 수도 있음. 만약 자동으로 변환할 수 없는 경우에는 오류가 발생함. 예5.7) int n; n = "abc"; // error
이항 연산자의 두 피연산자 타입이 다르면 정보손실이 발생하지 않도록 하나의 타입을 다른 타입으로 바꾼 후에 연산을 수행함. 수행함 예5.8) 5/2.5 5/2가 아니고 5.0/2.5 8/23
약식 대입 연산자 대입 표현식을 축약하여 주는 약식 대입 연산자를 제공함. variable = variable operator expression variable operator= expression 예5.9) +=, -=, *=, /=, %= 비고.. 약식 대입 연산자를 사용하면 보다 빠른 코드를 생성하는 비고 컴파일러도 있음. 약식 대입 연산자는 대입 연산자와 같은 우선순위를 가지며, 대입 연산자와 마찬가지로 결합순서는 오른쪽에서 왼쪽임. 예5.10) a1 += a2 += a3 + 2; int a1 = 2, a2 = 3, a3 = 4; a1 += a2 += a3 + 2; // a1 = 11, a2 = 9, a3 =4
+는 는 += 보다 다 우선순위가 높음 높음. +=의 결합순서는 오른쪽에서 왼쪽임. a1 += (a2 += (a3+2));
a1 += a2 += 4 + 2;; a1 += a2 += 6; a1 += a2 = a2 + 6; a1 += a2 = 3 + 6; a1 += a2 = 9; a1 += 9; a1 = a1 + 9; a1 = 2 + 9;; a1 = 11; 9/23
증감연산자 정수/부동소수형 변수 값을 1 증가하거나 1 감소시킬 때 약식 대입 연산자보다 축약하여 사용할 수 있는 단항연산자인 증감연산자가 있음. 있음 ++는 피연산자를 1 증가시키고 --는 피연산자를 1 감소시킴. 예5.11) 5 11) x++;는 x += + 1; 그리고 x = x+1;과 같음. 같음 이 연산자는 피연산자 앞에 올 수도 있고(전치(prefix)형태), 뒤에 올 수 도(후치(postfix)형태) 있다. 그러나 그것의 의미와 우선순위는 다름. 예5.12) int x, y, z; int n, y, z; x = 3; y = 1 + x++; z = x; // x = 4, y = 4, z = 4
x = 3; y = 1 + ++x; z = x; // x = 4, y = 5, z = 4
피연산자가 뒤에 오면 면 평가값은 1 증가 증가//감소하기 감 하기 이전 값이 되며, 피연산자가 앞에 오면 평가값은 1 증가 증가//감소한 값이 됨. 피연산자는 그것의 위치와 관계없이 다음 시퀴언스 포인트 (sequence point) 전에 1 증가 증가//감소 감소함 감소함. 10/23 10 /23
증감연산자 – 계속 장점: 코드의 간결화와 속도 향상 단점: 가독성을 저해할 수 있으며, 피연산자의 위치에 따라 평가 방법이 다르기 때문에 주의해야 함. 예5.13) 출력값
변수 x 값
출력값
변수 x 값
int x = 10;
10
int x = 10;
10
x = x + 1;;
11
x++;;
11
x = x – 1;
10
--x;
10
printf("%d\n", x+1);
11
10
printf("%d\n", x++);
10
11
i tf("%d\ " x); ) printf("%d\n",
10
10
i tf("%d\ " ++x); ) printf("%d\n",
12
12
printf("%d\n", x–1);
9
10
printf("%d\n", x--);
12
11
printf("%d\n", x);
10
10
printf("%d\n", --x);
10
10
예5.14) 여기서 잠깐! a---b는 어떻게 해석? a-- - b로 해석됨 큰 입 떼어먹기( 떼어먹기(maximal munch)) 규칙 11/23 11 /23
증감연산자 – 계속 시퀴언스 포인트 포인트란 프로그램 실행 위치 중 위치 전에 등장한 모든 부작용(side-effect)의 실행이 완료되어 있어야 하는 위치를 말함. 부작용이란 표현식의 평가과정에서 변수의 값이 변경되는 것을 말함. 증감연산자는 부작용이 있는 대표적인 연산자임. 대표적인 시퀴언스 포인트는 현 프로그램 문장의 실행이 종료된 후를 말함. 지금 시점에서는 다음 프로그램 문장이 실행되기 전에 값이 증가 또는 감소된다고 생각하면 됨. 증감연산자는 하나의 정수 또는 부동소수형 변수를 피연산자로 가져야 함. 함 예5.15) int x, y, z; x = 3; y = ++(x+1); z = 300++;
// error // error
12/23 12 /23
증감연산자 – 계속 독립적으로 사용할 경우에는 전치형태를 사용하는 것이 바람직함. 예5.16) iintt x; x = 3; x++;
iintt x; x = 3; ++x; // more efficient, 별로 중요하지 않음
복잡하게 사용하지 마라. 마라 프로그래머의 스타일에 따라 다르지만… 다르지만 예5.17) int x, y, z; x = 3; 3 y = ++x * x++; //?
그러면 왜 알아야 하나? 다른 사람이 작성한 코드를 이해하기 위해
13/23 13 /23
산술연산자 산술 연산자의 종류: 모두 이항 연산자 연산자
피연산자 유형
+
정수/부동소수
-
정수/부동 수 정수/부동소수
*
정수/부동소수
/
정수/부동소수
%
정수
비고
두 피연자가 모두 정수일 경우에만 정수 나눗셈을 함
정수 나눗셈과 부동소수 나눗셈 예5.18) 3/4 = 0 예5.19) 3/2.0 = 1.5 % 연산자는 두 피연산자가 모두 양의 정수일 때 그 해석이 명확함. 명확함 두 피연산자 중 하나가 음수이면 그 결과는 컴파일러마다 다를 수 있음 있음. 14/23 14 /23
관계연산자 관계 연산자의 종류: 모두 이항 연산자이며 결과가 참이면 1, 거짓이면 0으로 평가됨. C언어에서는 0은 거짓을 의미하며, 0 이외에 수는 모두 참으로 인식됨. 연산자
피연산자
>
정수/부동소수
<
정수/부동소수
==
정수/부동소수
!=
정수/부동소수
>=
정수/부동소수
<=
정수/부동소수
비고
= 연산자와 혼동하지 않도록 주의해야 한다.
15/23 15 /23
논리연산자 논리연산자는 보통 관계연산자를 통해 비교하는 여러 조건들을 결합하기 위해 사용함. 예5.20) x가 100보다는 크지만 200보다 작은 수인지 판단하여라 첫 번째 조건: x>100 두 번째 조건: x<200 두 조건을 동시에 만족해야 하면 논리곱: x>100 && x<200 C언어는 언어는 논리곱 &&,, 논리합 ||, 논리부정 !,, 세 개의 논리연산자를 제공함. 논리곱과 논리합은 이항 연산자이며, 논리부정은 단항 연산자임. 논리곱은 두 피연산자의 값이 모두 참일 때에만 참으로 평가됨. 평가됨 논리합은 두 피연산자의 값이 모두 거짓일 경우에만 거짓으로 평가됨 평가됨. 논리부정은 피연산자 앞에 붙으며, 피연산자의 값의 반대로 평가됨.
16/23 16 /23
논리연산자 – 계속 C 언어에서 논리곱과 논리합 연산자는 왼쪽 피연산자를 항상 먼저 평가함. 이 때 왼쪽 피연산자의 평가값에 따라 오른쪽 피연산자를 평가하지 않아도 전체 표현식의 결과가 정해지면 평가가 중단되며, 이렇게 평가하는 방식을 단축 계산 계산(short-circuit evaluation)이라 계산( )이라 함 함. 예5.21) x, y, z가 정수 변수일 때 (x != 0) && (z = y/x); 위 표현식에서 (x != 0) 부분을 먼저 평가하게 되며, 이 때 x가 0이면 (x != 0) 부분이 거짓으로 평가되기 때문에 전체 표현식은 (z = y/x)의 평가값과 상관없이 거짓이 된다 된다. 따라서 ((x != 0)을 )을 평가한 후에 이 표현식의 평가는 종료된다. 즉, (z = y/x) 부분은 평가되지 않음. 이와 같은 형태로 평가되기 때문에 &&와 ||은 시퀴언스 포인트가 됨. 예5.22) 5 22) int x, y, z; x = 0; y = 0; z = x++ || (y=x);
// x = 1, 1 y = 1, 1 z=1 17/23 17 /23
타입 변환 한 타입의 데이터가 다른 타입으로 변환하는 것을 타입 변환이라 함. 타입 변환은 크게 자동 타입 변환과 강제 타입 변환으로 구분됨. 자동 타입 변환이 일어나는 곳 경우 1. 산술 연산: 예5.23) 1+3.2 1.0 + 3.2 이 경우에는 정보 손실이 없게 타입 변환됨. 변환됨 이것을 확장변환 확장변환(widening conversion, promotion)이라 함. 경우 2. 2 대입 연산: 예5.24) 5 24) int n = 3.5; 3 5; // n에서 정수 3이 대입됨. 대입됨 대입연산의 경우에는 확장변환이 일어날 수도 있지만 위 예처럼 축소변환(narrowing conversion, demotion)이 일어날 수 있음. 축소변환 경우 3. 인수 전달 (추후에 설명함) 경우 4. 함수에서 값 반환 (추후에 설명함) 경우 2부터 4는 목적 타입으로 타입 변환되며, 변환되며 이 때 정보 손실이 발생할 수 있다. 뿐만 아니라 자동 타입 변환이 가능하지 않으면 오류가 발생함. 18/23 18 /23
강제 타입 변환 강제 타입 변환은 프로그래머가 강제 타입 변환 연산자 연산자를 이용하여 한 타입에서 다른 타입으로 변환하는 것을 말함. 문법.. 바꾸고자 하는 타입명이 x이고 바꾸고자 하는 데이터가 y일 때 문법 (x)y 형태로 쓰면 y 데이터는 x 타입으로 강제 변환됨. 타입 변환 연산자는 단항 연산자임. 연산자임 (우선순위가 높음) 예5.25) void f(void){ int n = 5;; int m = 2; double f1 = n/m; double f2 = (double)n/m; printf("%lf\n", i tf("%lf\ " f1) f1); // 2 2.000000 000000 printf("%lf\n", f2); // 2.500000 } // f
19/23 19 /23
비트 논리 연산자 비트 논리 연산자의 종류 논리곱 연산자: & 논리합 연산자: | 배타적 논리곱: ^ 보수 연산자: 연산자 ~ 예5.26) x
y
x&y
x|y
x^y
~x
0
0
0
0
0
1
0
1
0
1
1
1
1
0
0
1
1
0
1
1
1
1
0
0
20/23 20 /23
비트 이동 연산자 왼쪽 이동 연산자: << 오른쪽 이동 연산자: >> (signed/unsigned에 따라 그 결과가 다름) 둘 다 이항 연산자이며, a << b 또는 a >> b에서 b는 양의 정수이어야 하며, a의 크기보다 b가 작아야 함. 예5.27) 5 27) unsigned char c = 0x0F; n = n >> –4; 4; // warning n = n >> 9; // warning
21/23 21 /23
비트 이동 연산자 – 계속 예5.28) unsigned char c = 0x07; c = c << 1;
0000 0111 0 0000 1110
unsigned char c = 0x07; c = c >> 1;
0000 0111 0000 0011 1
n비트 왼쪽 이동은 x2n과 같고, n비트 오른쪽 이동 /2n과 같음. 예5.29) 5 29) char c = 0x87; c = c << 1;
1000 0111 1 0000 1110
char c = 0x87; c = c >> 1;
1000 0111 1100 0011 1
unsigned i d char h c = 0x87; 0 87 c = c >> 1;
1000 0111 0100 0011 1
표준에는 signed 값의 이동에 대한 구체적인 방법이 정의되어 있지 않음
22/23 22 /23
비트 이동 연산자 – 계속 예5.30) 특정 부분을 제외하고 나머지 비트들을 모두 0으로 바꾸고 싶으면 1111 1111 1111 1111 unsigned i d short h t n = 0xFFFF; 0 FFFF n = n & 0x00F0;
0000 0000 1111 0000 0000 0000 1111 0000
예5.31) 5 31) 특정 4비트의 값을 얻고 싶으면 unsigned short n1 = 0xFFFF; unsigned short n2; n2 0x00F0; 2 = n1 1&0 00F0 n2 = n2 >> 4;
예5.32) 5 32) 특정 부분을 제외하고 나머지 비트들을 모두 1으로 바꾸고 싶으면 unsigned short n = 0xAAAA; n = n | 0x00F0;
1010 1010 1010 1010 0000 0000 1111 0000 1010 1010 1111 1010
23/23 23 /23
C프로그래밍I(CPA130) 강의노트 06
©copyright 2010
조건문/반복문
한국기술교육대학교 한국기술 육대학 인터넷미디어공학부 김상진
강의목표 조건문 if 문 switch 문 반복문 while hil 문 do while 문 for 문 중첩 반복문 쉼표 연산자 break, continue
2/35
제어흐름 프로그램에서 실행되는 문장들의 실행 순서 기본. 한 문장씩 순차적으로 실행됨. 순차적인 실행순서만을 이용해서는 복잡하거나 반복적인 작업을 수행할 수 없음. C에서 실행 순서를 제어하기 위해 제공하는 문장 (순차적 실행 순서가 아닌 다른 방법으로 문장을 수행하도록 해주는 문장) 조건문: if, switch 조건에 따라 실행되는 문장 또는 문장들을 선택할 수 있는 문장 반복문: while, for, do while 조건에 따라 주어진 문장 또는 문장들을 여러 차례 반복하여 실행할 수 있도록 해주는 문장 분기문: g 분기문 goto,, return,, break,, continue 특정한 위치로 무조건 이동하게 하는 문장 함수호출 3/35
if 문 조건문이란 어떤 조건을 만족할 경우에만 그에 해당하는 일이 처리되는 문장을 말함. C 언어에는 if와 switch 조건문이 있음. if 문의 문법 1. if(expression) statementT; if(expression){ statementsT; }
if 다음의 소괄호는 반드시 나와야 함. 예6.1) double grade; … if(grade<2.0) printf("학사경고입니다.");
4/35
복합문 C 언어에서는 어떤 단일문장도 복합문 복합문(compound statement)으로 대체할 수 있음. 복합문은 다음과 같이 중괄호로 둘러싸인 문장들의 집합을 말하며, 이와 같은 복합문을 블록 블록(block)이라고도 함. { declarations; statement1; statement2; … statementsn; }
새 블록에는 변수나 상수를 선언할 수 있으며, 이렇게 선언된 변수나 상수의 가시영역은 이 블록 블록으로 제한됨 제한됨.
5/35
if-else 문 if 문 문법 2.
if(expression) statementT; else statementF;
if(expression) statementT; else statementF;
if(expression){ statementsT; } else{ statementsF; }
예6.2) int n; … if(n%2==0) ( % )p printf("짝수입니다."); ( 짝수입니다 ); else printf("홀수입니다.");
6/35
중첩된 if 문 if 문 문법 3.
거짓
if(expression ( p 1){ statements1; } else if(expression2){ statements2; } … else{{ }
거짓
조건
조건
참
참
예6.3) int score; … if(score>=90) printf( printf("A A 학점 학점"); ); else if(score>=80) printf("B 학점"); else if(score>=70) printf("C 학점"); else printf("F 학점");
7/35
if 문 – 계속 예6.4) 정수 변수 x가 0과 10사이의 수이면 OK를 출력하시오. if(0<=x<=10) printf( OK ); printf("OK");
위처럼 잘못 코딩할 수 있음. 0 0<=x<=10은 10은 문법적 오류는 아님. 아님 <= 연산자는 왼쪽에서 오른쪽 방향으로 평가됨. 따라서 0< 0<=x<=10은 x< 10은 (0< (0<=x)<=10과 x)< 10과 같음. 같음 x가 0보다 크면 (0<=x)은 참이므로 평가값은 1이 되며, 반대로 x가 0보다 작으면 (0<=x)은 거짓이므로 평가값은 0이 됨. 따라서 (0<=x)<=10은 항상 참이 됨. 이 예는 다음과 같이 코딩해야 올바른 코딩임. if(x>=0&&x<=10) printf("OK");
8/35
Tip. 코드를 작성할 때에는 적절한 들여쓰기를 해야 하며, 때로는 가독성을 높이기 위해 불필요한 중괄호를 사용해야 한다. if(num1) if(num2) printf("두 수는 모두 0이 아닙니다."); if(num1) if(num2) printf("두 수는 모두 0이 아닙니다."); if(num1) if(num2) printf( printf("두 두 수는 모두 0이 아닙니다. 아닙니다 "); ); if(num1){ if(num2) printf("두 수는 모두 0이 아닙니다."); } if(num1&&num2) printf("두 p ( 두 수는 모두 0이 이 아닙니다 아닙니다."); );
9/35
dangling-else 문제 dangling-else 문제: else는 가장 가까운 if문과 연관됨. 예7.5) if(y==8) else 문은 가장 가까운 if문 연관되므로 if(x==5) printf("$$$$$"); else printf("@@@@@"); if(y==8){ if(x==5) printf("$$$$$"); else printf("@@@@@"); }
이 예제에서 y=8, x=4이면 @@@@@이 출력되고, y=3이면 아무것도 출력되지 않으며, $$$$$ 출력된다. 출 y=8, x=5이면 $$$$$이 if(y==8){ if(x==5) printf("$$$$$"); } else printf("@@@@@");
들여쓰기와 중괄호의 사용이 매우 중요
10/35 10 /35
if 문 사용 Tip 즉, if 문은 조건식을 어떻게 작성하느냐에 즉 따라 효율성의 차이가 있음.
Tip.
int n; … if(n%2==0) printf("짝수입니다."); else printf("홀수입니다.");
int n; … if(n%2) printf("홀수입니다."); else printf("짝수입니다.");
두 예제는 모두 동일한 결과를 주는 문장이지만 효율성 측면에서는 두 번째 예제가 더 효율적임. ① n%2 ② n%2의 계산 결과와 0 비교 ③ 위 결과를 가지고 판단
① n%2 ② 위 결과를 가지고 판단
Tip. if(a <= b) b = b – a; if(a if( > b) b = b – c;
if(a <= b) b = b – a; else l b = b – c;
경우에 따라 b는 있음. 두 번 변할 수 있음
무조건 b는 한번 변함
if 문 조건식에 사용되는 변수가 if 문의 결과에 따라 변할 경우에는 사용하는 방식에 따라 결과가 다르므로 주의해서 사용해야 함.
11/35 11 /35
if 문 사용 Tip – 계속 Tip. 중첩된 if/else 구조는 중간에 중단될 수 있으므로 일련의 if문보다 효율적임. 예6.6) if(grade=='A') printf("very good"); else if(grade== if(grade=='B') B ) printf("good"); printf( good ); else printf("poor");
if(grade=='A') printf("very good"); if(grade=='B') if(grade== B ) printf( printf("good"); good ); if(grade=='F') printf("poor");
Tip. p 중첩 if/else 구 구조에서는 에서는 참이 될 확률이 높은 것을 먼저 검사하는 것이 효과적임. 예6.7) if(score>=90) printf("A"); else if(score>=80) printf("B"); else printf("F");
if(score<80) printf("F"); else if(score<90) printf("B"); else printf("A");
12/35 12 /35
조건 연산자 조건 연산자는 C언어에서 유일한 삼항 연산자임. 이 연산자는 다음과 같은 if 문을 축약할 때 사용함. 예6.8) if(n1>n2) max = n1;
max = (n1>n2)? n1: n2;
else max = n2;
문법 expressionC ? expressionT : expressionF expressionC를 평가하여 이 값이 0이 아니면(참이 아니면) expressionT의 평가값이 전체 표현식의 값이 되며, 반대로 0으로 평가되면 expressionF의 평가값이 전체 표현식의 평가값이 됨. 됨 예6.9) 정수 변수 x의 절대값을 정수 변수 abs에 저장 abs = (x>0)? x: –x; x;
13/35 13 /35
switch 문 중첩된 if 문에서 모든 조건식이 동일한 표현식을 문자나 정수형과 같은지 비교하고 있으면 중첩된 if 문 대신에 switch 문을 사용할 수 있음. 있음 문법 switch(expressionC){ case value l 1: statements1; break; case value2: statements2; break; … case value l n: statementsn; break; default: statementsD; break; }
if(expression if( i C==value l 1){ statements1; } ( p else if(expression C==value2){ statements2; } … else l if(expression if( i C==value l n){ statementsn; } else{{ statementsD; }
14/35 14 /35
switch 문 – 계속 case부터 다음 case 또는 default가 나올 때까지 그 사이에 있는 문장들을 case 절이라 함. default와 연관된 문장들을 default 절이라 함. C 언어 구조 중 유일하게 일련의 프로그램 문장을 중괄호를 이용하여 묶고 있지 않음. 않음 case 절의 상수는 정수형 상수나 문자 상수이어야 함. 상수가 같은 case 절은 허용되지 않음. case 절의 상수 위치에 변수가 포함된 표현식을 사용하는 것은 오류임. digit a; int digit, 예6.10) 6 10) … switch(digit){ case 1–1: printf("one"); break; // ok case a–1: printf("two"); break; // error default: printf("error"); break; } // switch
15/35 15 /35
switch 문 – 계속 break 문은 switch 문에서 case 절이 실행이 끝난 후에 switch 문을 벗어나게 해줌. case 절마다 반드시 있어야 하는 것은 아님. case 절에서 break를 사용하지 않으면 그 다음 case 절이 수행됨. 이런 현상을 “fall fall through” through 행위라 함. 함 예6.11) switch(month){ case 4: case 6: case 9: case 11: numberOfDays = 30; break; case 2: numberOfDays b OfD = 28; 28 break; default: numberOfDays = 31; } // switch
16/35 16 /35
switch 문 – 계속 default 절은 선택적으로 사용할 수 있으나, 항상 사용하는 것이 좋은 프로그래밍 습관임. default 절의 위치는 고정되어 있는 것은 아니지만 보통 switch 문의 마지막 절로 사용함. 마지막 절로 사용할 때에 default 절은 break 문이 필요 없음. 없음
17/35 17 /35
반복문 반복문이란 일련의 문장들을 되풀이하여 실행할 수 있도록 해주는 프로그램 구조를 말함. 어떤 조건이 만족되면 되풀이되는 과정이 종료됨. 아무리 되풀이하여도 조건이 종료되지 않으면 프로그램은 종료되지 않고 이 과정만 계속 되풀이함. 되풀이함 이와 같은 현상을 무한루프 무한루프라 하며, 무한루프가 되지 않도록 반복문을 작성할 때에는 주의해야 함. 따라서 반복문의 크게 제어줄 제어줄(control line)과 몸체 몸체(body)로 구성됨. 제어줄은 반복을 종료하는 조건을 제어함. 몸체는 제어줄에 의해 계속 되풀이되는 프로그램 문장들로서 프로그램의 가독성을 높이기 위해 별도의 줄에 작성하며, 다른 문장들을 구별하기 위해 들여쓰기를 함.
18/35 18 /35
while 문 while 반복문의 구조 while(condition) ( ) { body }
false condition true
condition이 거짓이 될 때까지 반복함. 몸체가 한번 한번도 실행되지 않을 수 있음 있음.. 예6.12) 1에서 10까지의 합을 구하시오.
body
int sum = 0; int i = 1; while(i<=10) { sum += i; i++; }
19/35 19 /35
off-by-one error 예6.13) 투자가 두 배가 되기 위한 년 수를 계산하라 int years = 0; double interest; while(balance<targetBalance){ years++; interest = balance * rate / 100; balance = balance + interest; }
years의 값을 0으로 초기화해야 할지 1로 초기화해야 할지? balance<targetBalance로 비교해야 할지 balance<=targetBalance로 비교해야 할지? 단순한 사례를 생각해보면 그 답을 쉽게 얻을 수 있음. 초기 잔액이 100이고, 이자율이 50%라 하자. 1:150, 2: 225 years는 0부터 시작하는 것이 옳음. 초기 잔액이 100이고, 이자율이 100%라 하자. 1:200 balance<targetBalance로 비교해야 함. 20/35 20 /35
do while 문 do while 반복문의 구조는 다음과 같음. do{ body }while(condition);
body
condition이 diti 이 거짓이 될 때까지 반복함. 반복함 do while 문의 경우에는 몸체가 true 한 문장이더라 문장이더라도 항상 중괄 중괄호를 를 사용하는 것이 바람직함. 몸체가 최소한 한번은 실행됨 실행됨.. 모든 do d while hil 문은 while hil 문으로 바꿀 수 있음. 있음 예6.14) 1에서 10까지의 합을 구하시오.
false condition
int sum = 0; int i = 1; do{ sum += i; i++; } while(i<=10);
21/35 21 /35
do while 문 – 계속 예6.15) 정수의 자릿수를 거꾸로 바꾸기 56723 32765 int number = 56723; int reverseNumber = 0; do{ reverseNumber = reverseNumber*10 + number%10; number = number/10; } while(number != 0); reverseNumber
number
루프전
0
56723
Loop 1
3
5672
Loop 2
32
567
Loop 3
327
56
Loop 4
3276
5
Loop 5
32765
0
22/35 22 /35
for 문 for 문의 구조 init
for(init; condition; step){ body }
condition이 거짓이 될 때까지 반복함. 반복함 init, condition, step 부분은 모두 생략할 수 있다. 그러나 생략하는 것은 바람직하지 않음. 몸체가 한번도 실행되지 않을 수 있음. 모든 for 문은 다음과 같이 while 문으로 바꿀 수 있음. 있음
condition
false
true body step
init; while(condition){ body b d step; }
23/35 23 /35
for 문 – 계속 반복 횟수가 정해져 있는 경우에는 for 문을 사용하고, 그렇지 않은 경우에는 while 문을 사용한다. 이 때 최소 한번 반복된다는 것을 강조하고 싶으면 do d while hil 문을 사용함. 사용함 예6.16) 1에서 10까지의 합을 구하시오. int sum = 0; for(i = 1; i<=10; i++){ sum += i; }
여기서 i를 for 문의 색인 변수 또는 제어 변수라 하며, 이 제어 변수는 반복 횟수를 제어하기 위해 사용함. 사용함 보통 i를 제어 변수로 많이 사용하며, 주로 정수 변수를 제어 변수로 사용함. (i, j, k)
24/35 24 /35
for 문
– 계속
부동소수형을 제어변수로 사용할 경우에는 round-off 오류 때문에 예측한 것과 다른 결과를 얻을 수 있음. 예6.17) 부동소수를 사용한 for 문 1.00 double x; for(x = 1 1.0; 0; x<=1 x<=1.5; 5; x += 0 0.1){ 1){ printf("%.2f\n",x); }
1.10 1 20 1.20 1.30 1.40
1.50은 않았음. 50은 출력되지 않았음
C99부터는 for 문의 제어줄의 init 부분에서 색인 변수를 선언할 수 있음. 참고.. 제어줄에 색인 변수를 선언하면 이 변수의 가시영역은 for 참고 f 문 몸체로 제한됨. int sum = 0;; for(int i = 1; i<=10; i++){ sum += i; } // C99
int i,, sum = 0;; for(i = 1; i<=10; i++){ sum += i; } // C99 이전
25/35 25 /35
for 문 – 계속 for 문의 작성원칙: for 문은 기본적으로 init 부분에 제어 변수를 초기화하고, condition 부분에는 반복 횟수 조건을 서술하고, step t 부분에서는 제어 변수를 증가 또는 감소하여 결국에는 for f 문이 종료되도록 함. for 문의 제어줄은 이 원칙대 원칙대로 꼭 작성할 필 필요는 는 없지만 이 원칙에 충실하게 작성하는 것이 가장 바람직함. for 문의 제어줄의 세 가지 요소는 모두 생략이 가능하지만, 위 원칙이 더 중요함. 중요함 for 문의 제어줄에는 같은 타입의 여러 개의 변수를 초기화할 수 있음. for 문의 제어줄에는 반복 횟수와 관련된 색인 변수만 사용하 사용하고,, 다른 요소들은 제어줄에 포함하지 않는 것이 바람직함. 예6.18) int j = 10; for(i=0, ( , j=10; j ; i<10;; i++,, j--){ j ){ … for 문의 제어줄에 사용된 } 쉼표는 실제로 C에서는 연산자임.
for(i=0; i<10; i++){ f (i 0 i<10 i ){ … j--; } 26/35 26 /35
쉼표 연산자 두 개의 표현식이 쉼표 연산자로 분리되면 쉼표 왼쪽에 있는 피연산자 부터 평가되며, 전체 표현식의 값은 오른쪽 피연산자의 평가 값이 됨. C 연산자 중에 가장 우선순위가 낮은 연산자임. 예6.19) int x, y; 예6.20) 예6.21)
y = (x = 1, 2)
// y = 2, x = 1
int x, y; y = (x = 1, x + 3)
// y = 4, x = 1
int x, y, z; y = (x = 1, z = 3, x + z)
// y = 4, x = 1, z = 3
주의. 표준에 의하면 쉼표 연산자의 결합성은 왼쪽에서 오른쪽이다. 주의. 하지만 이것을 따르지 않는 컴파일러가 종종 있음. 예6.22) int x = 1, y = 1, z; z = (x++, x) z = (++y, y)
// x = 2, z = 2 // y = 2, z = 2
27/35 27 /35
쉼표 연산자 즉, 쉼표 연산자도 시퀴언스 포인트임. 지금까지 배운 시퀴언스 포인트는 다음과 같음. 프로그램 문장 끝 &&, || 연산자 쉼표 연산자 함수 호출에서 인자를 분리할 때 사용하는 쉼표는 쉼표 연산자가 아님. 즉, C에 아님 에 등장하는 모든 든쉼 쉼표가 가 연산자는 아님 아님. 예6.23) f1(++a, a);는 두 개의 인자를 가진 함수 호출이며, 이 때 평가되는 순서는 정의되어 있지 않다. f2((++a, a));는 하나의 인자를 가진 함수 호출이며, 호출이며 이 경우 ++a가 먼저 평가됨. 평가됨
28/35 28 /35
for 문 – 계속 루프가 반복되는 횟수 (가정. b>a) for(i=a; i<b; i++){ … }
b–a
for(i=a; i<b; i+=c){ … }
(b–a)/c
ffor(i=a; (i i< i<=b; b i++){ i ){ … }
b–a+1
ffor(i=a; (i i< i<=b; b i+=c){ i ){ … }
(b–a)/c+1
for(i=0; i<10; i++){ … }
10–0=10
for(i=0; i<=10; i++){ … }
10–0+1=11
for(i=10; i<=100; i+=5){ … } (100–10)/5+1=19
29/35 29 /35
for 문 – 계속 for 문 사용의 주의점 부동소수형을 제어변수로 사용할 경우에는 round-off 오류를 주의 해야 함. 종료 조건에서 != 연산자를 사용하는 것은 피해야 함. 예6.24) 6 24) for(i=0; i!=n; i+=2){ … } n이 짝수가 아니면 무한루프가 됨. for 문의 본문에서 제어 변수의 값을 변경하는 것은 바람직하지 않음. 이것은 가독성을 크게 저해하는 요소가 될 수 있음.
30/35 30 /35
중첩 반복문 한 반복문 내에 또 다른 반복문을 사용할 수 있음. 예6.25) 구구단을 출력하시오. int i, j; for(i = 1; i<10; i++){ for(j = 1; j<10; j++){ printf("%d x %d = %2d\n", i, j, i*j); } }
31/35 31 /35
break, continue 문 break 문: 루프 중간에서 루프를 빠져나올 때 사용하는 문 switch 문의 case, default 절에서도 사용 continue 문: 루프 중간에서 루프 끝으로 이동할 때 사용하는 문 예6.26) for 문의 경우 for 문 중간에서 continue 문을 만나면 그 다음에 for 문의 step 부분이 실행됨. 실행됨 중첩된 루프인 경우에는 break와 continue는 해당 문이 포함되어 있 는 가장 안쪽 루프에만 적용됨. Tip. 모든 루프는 break와 continue 문을 사용하지 않고도 작성할 수 있음.
32/35 32 /35
루프중간탈출 문제 예6.27) –1이 입력될 때까지 정수들을 입력받아 그것의 합을 구하라. sum = 0;; while(1){ scanf("%d", &n); if(n==–1) break; sum += n; } printf("%d\n", n);
sum = 0;; scanf("%d", &n); while(n!=–1){ sum += n; scanf("%d" &n); scanf("%d", } printf("%d\n", n);
sum = 0; notDone = 1; while(notDone){ scanf("%d", &n); if(n== 1) notDone = 0; if(n==–1) else sum += n; } printf("%d\n", ( n); )
루프중간탈출 문제 문제(loop-and-a-half problem): 루프 중간에서 루프가 종료되어야 하는 경우
33/35 33 /35
루프중간탈출 문제 – 계속 루프중간탈출 문제를 해결하는 방법
반복
break 문 사용
루프 시작 전에 미리 한번 수행
while(1){ prompt user and read a value if( l if(value==sentinel) ti l) break; b k process the data }
prompt user and read a value while(value!=sentinel){ process the th data d t prompt user and read a value }
루프 종료시키기 위한 추가 변수 사용 done = 1; while(done){ hil (d ){ prompt user and read a value if(value==sentinel) done = 0; else process the data }
34/35 34 /35
기타 무한루프를 만드는 방법 while(1){ … }
실제 웹서버처럼 무한루프를 통해 구현되는 프로그램도 있음. 있음
for(;;){ … }
반복문 내에 값이 변하지 않는 표현식은 사용하지 않아야 함. 함 예6.28) int i, j, a, b, n; int i, j, a, b; for(i=0; i<10; i++){ j = a+b+i; printf("%d\n", j); }
n = a+b;; for(i=0; i<10; i++){ j = n+i; printf("%d\n", j); }
프로그램 내에 약간의 지연을 추가하는 방법 for(i=0; i<10; i++);
35/35 35 /35
©copyright 2010
C프로그래밍I(CPA130) 강의노트 07
함수기초
한국기술교육대학교 한국기술 육대학 컴퓨터공학부 김상진
강의목표 함수 개요 함수를 사용하는 이유 모듈화의 중요성 함수 호출 함수 선언 함수 정의 return 문 함수 호출 과정
2/31
함수 개요 함수 함수(function)는 수학에서 유래한 개념으로서, 대응관계 대응관계(mapping)를 의미함. 함수라고 하는 상자에 수가 들어가면 그것에 대응되는 수가 생성됨. 3 인수(argument) 예7.1) 7 1) f(x) = x+3 프로그래밍에서는 함수를 함수(function) “작은 작은 프로그램 프로그램”이라는 의미로 +3 사용됨 사용됨. 서브프로그램(subprogram) 개념 서브프로그램 프로그래밍에서 함수는 결과 값을 주는 "KUT" 6 반환 값(return value) KUT 것뿐만 아니라 다른 효과를 일으킬 수 있음. KUT 결과 값 외에 부수적으로 수행되는 효과를 부수효과 부수효과( id ff t)라 함. 부수효과(side-effect)라 함 printf 부수 효과
3
3/31
함수 개요 – 계속 C 프로그램은 이미 설명한 바와 같이 함수들로 구성되며, 함수는 프로그램 문장들로 구성됨. 함수는 입력을 받아 처리하고 그 결과를 출력해줌. 입력이 없을 수도 있고, 출력이 없을 수도 있음. 함수는 특정한 작업을 수행하는 독립적인 단위임. 단위임 전체 프로그램을 적절한 개수의 함수로 구성해야 하는데 이 구성이 C 프로그래밍에서는 매우 중요함.
4/31
프로그램의 구성과 함수 종류 지금까지는 main 함수 하나만으로 구성된 C 프로그램을 작성하였음. C 프로그램은 반드시 main 함수가 존재해야 하며, 프로그램이 실행되면 가장 먼저 실행되는 함수이며, 이 함수가 종료되면 프로그램도 종료됨. 따라서 다른 함수들은 직접 또는 간접적으로 main 함수에서 호출되어 실행됨. 프로그램에서 사용되는 함수는 크게 사용자가 새롭게 만든 함수와 함수 시스템에서 제공되는 함수 함수로 구분됨. 구분됨 시스템에서 제공되는 함수를 사용하기 위해서는 그 함수의 정보가 기록되어 있는 헤더 파일을 #include 전처리문을 이용하여 프로그램에 포함해야 함. 예7.2) printf나 scanf_s 함수를 사용하기 위해서는 stdio.h 헤더 파일을 포함해야 함. 함
5/31
함수를 이용하는 이유 코드의 중복을 피하기 위해 동일한 작업을 여러 번해야 하면 이것을 한 번 정의하고 필요할 때마다 호출해서 사용할 수 있음. 예7.3) 출력할 때마다 printf 함수를 호출하지 않고, 화면에 데이터를 출력하는 코드를 작성해야 하면 매우 번거롭다. 번거롭다 코드가 중복되어 있지 않으면 프로그램을 수정하기가 용이함. 동일한 작업을 함수로 분리해 작성하지 않은 경우, 이 작업의 내용을 일부 수정해야 하면 중복된 모든 코드를 수정해야 함. 큰 문제를 여러 개의 작은 문제로 나누어 생각할 수 있음. 복잡성을 줄여 문제 해결을 쉽게 해줌. 해줌 (divide-and-conquer) (divide and conquer) 여러 프로그래머가 작업을 나누어 프로그래밍을 할 수 있음.
6/31
모듈화 프로그램을 여러 개의 함수로 나누는 작업을 모듈화 모듈화라 하며, 각 함수를 모듈이라고도 함. 이 때 각 함수/모듈은 다음과 같은 특성을 가지고 있어야 바람직함. 느슨한 결합성 결합성(loosely-coupled): 모듈은 상호 독립적이어야 함. 한 모듈의 변경이나 오류가 다른 모듈에 영향을 주지 않아야 함. 함 예7.4) 나쁜 예: 두 함수가 공통 전역변수를 사용하는 경우 (common coupling) 높은 응집성 응집성(highly-cohesive): 잘 정의된 단일 작업만해야 함.
7/31
함수 호출 함수 호출의 형태 function-name(arguments) 인수는 호출하는 측에서 함수에게 전달하는 정보이며, 인수가 두 개 이상이 필요할 경우에는 쉼표로 구분함. 함수 호출은 하나의 표현식이며, 표현식이며 ()은 함수 호출 연산자임. 연산자임 따라서 함수호출은 표현식의 항으로 사용 가능함. 단, 이 함수에서 반환하는 값이 있어야 하며, 반환하는 값의 타입이 표현식에서 요구되는 타입과 일치해야 함. 함수를 호출할 때 제공하는 인수의 개수와 각 인수의 타입은 함수의 정의된 입력의 개수와 타입과 일치해야 함. 함 함수는 복귀 연산의 일부로서 수행 결과를 호출한 측에 넘겨 줄 수 있음. 함 함수를 호출하여 사용하기 사 하기 위해서는 위해서 시스템 정의 함수: 해당 헤더 파일을 포함해야 함 사용자 정의 함수: 함수의 선언 또는 정의가 먼저 선행되어야 함 8/31
함수 호출 – 계속 함수가 호출되어 실행된 후에 다시 호출된 위치로 돌아와 작업을 계속 수행하기 위해서는 함수가 호출될 때마다 시스템의 현재 상태를 보관해야 하며, 하며 함수가 종료되면 보관된 상태를 복원해야 함. 함 이 때문에 함수 호출은 비용(시간)이 많이 드는 연산임. 따라서 불필요한 함수 호출은 최소화하는 것이 좋음. 좋음 하지만 오늘날 프로그래밍에서는 중복을 최대한 줄여 유지보수와 가독성을 높이는 것이 효율성을 높이는 것보다 더 중요함. 예7.5) int sum(int a, int b) { return a+b; }
int n, x, y, z; x = y = z = 2; n = sum(x, sum(x y); n = sum(x, 3) + z; n = sum(x, y, z);
// n = 4; // n = 7; // error
void oid printInteger(int a) { printf("%d", a); }
iintt n, x; x = 2; printInteger(x); n = printInteger(x);
// error 9/31
사용자 정의 함수 사용자가 새롭게 함수를 만들어 사용하기 위해서는 먼저 그 함수를 정의해야 함. 반환값의타입 함수이름(매개변수목록) 함수를 정의할 때 필요한 요소 { 함수몸체 반환 값의 타입: 없으면 void } 함수 이름 int max(int a, int b) { 매개변수 목록(parameter): 없으면 void return (a>b)? a: b; 함수의 몸체 } 함수를 정의하였다고 호출할 수 있는 것은 아님. 항상 기억해야 하는 것은 컴파일러는 순차적으로 번역을 함. 함 함수의 몸체는 0개 이상의 문장이 올 수 있으며, 적절한 들여쓰기를 해야 함 함. 함수 몸체 앞에 나타나는 부분을 함수 헤더(header)라 하며, 함수를 호출하기 위한 사용법을 알려줌. 예7.6) max: (int, int) int
10/31 10 /31
함수의 선언 #include <stdio.h> <stdio h> #include <stdio.h> int factorial(int n){ int prod = 1, i; for(i = 1; i<=n; i++){ prod *= i; p } return prod; } // factorial int main(void){ int n; ( ){ while(1){ printf("정수 입력: "); scanf("%d", &n); if(n==-1) break; printf("%d! printf( %d! = %d\n" %d\n , n, factorial(n)); } // while return 0; } // main
int main(void){ int n; while(1){ printf("정수 입력: "); scanf("%d", &n); if(n== 1) break; if(n==-1) printf("%d! = %d\n", n, factorial(n)); } // while return 0; } // main int factorial(int n){ int prod = 1 1, i; for(i = 1; i<=n; i++){ prod *= i; } return prod; } 아래에 factorial 함수가 정의되어 있지만 이 시점에서 컴파일러는 factorial이 이 뭔지 알 수 없다. 11/31 11 /31
함수의 선언 – 계속 앞 예에서 알 수 있듯이 함수를 호출하여 사용하기 위해서는 호출이 이루어지는 문장 이전에 컴파일러가 이 함수와 관련된 정보를 알 수 있도록 해주어야 함. 함 따라서 f라는 어떤 함수를 호출하고 싶으면 이 함수가 호출되는 함수 앞에 f 함수가 먼저 정의되어 있어야 함 함. 하지만 이와 같은 형태로만 프로그래밍을 해야 한다면 f 함수 내에서 g 함수를 호출하고 또 g 함수 내에서 f 함수를 호출 해야 하는 특수한 경우는 프로그래밍을 할 수 없으며, 없으며 가독성을 저해할 수 있고, 함수를 별도 파일에 정의할 수 없게 됨. 됨 따라서 컴파일러가 번역하기 위해 필요한 정보만 미리 제공해 줄 수 있으며, 이것을 함수의 선언 또는 원형 원형(prototype)이라 함.
12/31 12 /31
함수의 선언 – 계속 함수 선언을 이용한 프로그래밍 #i l d <stdio.h> #include < tdi h>
함수 원형 끝에는 일반 문장과 마찬가지로 ;;으로 끝 끝난다. 다
int factorial(int); // 함수의 선언(함수의 원형) int main(void){ … printf("%d! = %d\n", n, factorial(n)); … } // main int factorial(int ( n){ ){ // 함수의 정의 … }
즉, stdio.h 헤더 파일에 printf 함수 코드가 포함되어 있는 것은 아니고 printf 함수의 원형이 포함되어 함되어 있는 있 것이다. 것이다
13/31 13 /31
함수의 선언 – 계속 함수의 선언은 컴파일러가 번역할 수 있도록 미리 번역에 필요한 정보를 컴파일러에게 알려주는 것이다. 컴파일러는 식별자를 보면 이것이 어떤 용도의 식별자인지 알아야 한다. 이 때 식별자가 함수이름이라는 것을 알고, 이 함수를 호출하는 코드로 인식되면 다음을 검사한다 검사한다. 올바른 개수의 입력이 주어지고 있는지, 각 입력의 유형이 올바른지, 함수의 반환 값과 함수의 사용 용도가 일치하는지 검사 예7.6) // 함수 선언 void f(void); int sum(int, int);
int a, b, n; n = f(); // 오류: f함수는 값을 반환하지 않음 n = sum(a); // 오류: sum 함수는 두 개의 입력이 필요함 n = sum(a,b); sum(a b); // OK
14/31 14 /31
함수의 선언 – 계속 따라서 함수 선언에는 반드시 다음 요소가 있어야 함. 반환 값의 타입 함수 이름 매개변수들의 타입 매개변수의 이름은 꼭 제시하지 않아도 됨. 됨 하지만 이름을 제시하면 가독성을 향상시킬 수 있음. 함수의 선언은 함수의 정의에서 몸체부분만 제외하 제외하고 ;을 붙여주면 됨 됨. 함수의 선언과 그 함수의 정의는 일치해야 함. 즉, 함수의 선언과 함수의 정의는 함수 이름, 반환 값의 타입, 인수들의 개수와 타입이 일치해야 함. 함 동일한 이름의 함수를 여러 개 정의할 수 없음. C++는 가능함.
15/31 15 /31
함수를 별도 파일에 정의하는 방법 #include <stdio.h> #include "factorial.h"
#ifndef __FACTORIAL_H #define __FACTORIAL_H
int main(void){ int n; while(1){ printf("정수 입력: "); scanf("%d", &n); if(n==-1) break; printf("%d! = %d\n", n factorial(n)); n, } // while return 0; } // main
int factorial(int n);
main.c i
#endif
factorial.h
#include "factorial.h" int factorial(int n){ int prod = 1, i; for(i = 1; i<=n; i++){ prod *= i; } return prod; } // factorial
factorial.c
16/31 16 /31
return 문 반환 값이 있는 함수의 경우에는 함수 몸체에 최소한 하나의 return 문이 있어야 함. return 문의 문법 반환해야 하는 값이 있는 경우 return (expression); 반환해야 하는 값이 없는 경우 return;; 함수를 실행하는 도중에 return 문을 만나면 그 함수의 실행은 종료됨. 만약 return 문의 표현식의 평가 값의 타입과 함수 반환 값의 타입이 일치하지 않으면 평가 값은 자동으로 함수의 반환 값의 타입으로 변환됨. 이 때 자동 변환이 가능하지 않 않으면 면 오류임. 류임
17/31 17 /31
return문 관련 오류 예7.7) int f(void){ … return "hello"; // 오류 }
int f(void){ … } // return 문이 없으면 오류
void f(void){ int n; … if(…) ( ) return;; … return n; // 오류 } // 매개변수 목록 오류 void f(double a, b){ … } 변수를 선언할 때에는 가능하지만 매개변수를 선언할 때에는 각 변수이름마다 타입이 있어야 한다. 정확하게는 없으면 int로 간주함
int f(void){ … if(…) return 1; } 함수가 반환하는 값이 있는 경우 함수가 종료되는 모든 경우에 return 문이 있어야 한다.
18/31 18 /31
순수 함수 함수는 연산자와 매우 유사함. 예7.8) int add(int a, a int b){ return a+b; }
int x, y, z; x = 2; y = 3; z = x+y; z = add(x, y);
x+y와 add(x, y)는 동일한 효과를 줌. 보통 연산자는 피연산자를 이용하여 새 값을 생성하여 주지만 ++ 연산자처럼 부가적인 일(피연산자의 값을 증가시킴)을 하는 연산자도 존재함. 함수 중에도 위 예의 add처럼 dd처럼 받은 인수를 근거로 새 값을 생성하는 것 외에 다른 일을 하지 않는 함수들이 있음. 이와 같은 함수를 순수 함수 함수(pure function)라 함수(p )라 하며, 사용자 정의 연산자라고 볼 수 있으며, 가급적 프로그래머는 순수 함수를 많이 정의하여 사용하는 것이 좋음 좋음.
19/31 19 /31
프로시저 순수 함수와 정반대로 완전히 부수효과에 의존하는 함수도 존재함. 반환값이 없는 함수를 프로시저 프로시저(procedure)라 하는데, 이와 같은 함수들이 부수효과에만 의존하는 함수라 할 수 있음. C 프로그램의 명령어들 if, switch 등도 어떤 값을 생성하지 않기 때문에 프로시저는 C 프로그램의 명령어와 유사함. 유사함 이와 같은 측면 때문에 프로시저는 사용자 정의 명령어라고 볼 수 있음. void printInteger(int a) { printf("%d", a); }
20/31 20 /31
Tip 함수는 단일 작업을 해야 하며, 함수의 이름은 그 작업을 잘 나타내야 함. 만약 간결한 함수 이름을 정하는 것이 어려우면 그 함수도 아마도 너무 많은 일을 하고 있을 확률이 높음. 매개변수가 너무 많이 필요한 함수도 너무 많은 일을 하고 있을 확률이 높음. 매개변수가 가급적 3개 이내로… 함수의 이름뿐만 아니라 매개변수의 이름도 그 용도를 쉽게 파악할 수 있도록 의미있는 이름을 사용하는 것이 바람직함. 함수의 길이는 반 페이지 정도가 적당함. 적당함
21/31 21 /31
함수 호출 과정 main
myFunc() 호출
myFunc();
if(…) return; return 문에 의한 복귀 함수 끝에 도달하여 복귀
함수 호출이 이루어지면 호출한 함수(caller)는 호출된 함수(callee)가 종료할 때까지 수행을 멈추고 기다림. 위 예의 경우 main에서 myFunc 함수를 호출하면 main은 잠시 멈추고 myFunc 함수가 실행됨. 호출된 함수는 return 문을 만나거나 더 이상 실행할 문장이 없으면 종료되며, 호출한 함수는 다시 실행을 재개함.
22/31 22 /31
함수 호출 과정 – 계속 추상적으로 함수가 실행되면 함수를 위한 공간이 만들어지며, 이 공간에 함수의 매개변수와 지역변수를 저장하기 위한 공간이 만들어짐. 함수 호출 과정 단계 1. 1 각 인수를 나타내는 표현식을 평가한다. 평가한다 표현식의 평가 순서는 정의되어 있지 않음. 단계 2. 인수를 나타내는 모든 표현식의 평가가 완료되면 평가된 각 인수 값은 해당되는 매개변수로 복사됨. 이 때 필요하면 자동 타입변환이 일어날 수 있음. 단계 3. 3 return 문을 만날 때까지 또는 함수 끝에 도달할 때까지 함수 몸체의 문장을 차례대로 수행함. 단계 4. 반환 값이 있는 경우에는 함수 호출 자리에 넘겨진 값으로 대치하고 함수가 호출된 위치 이후부터 다시 수행을 계속함.
23/31 23 /31
함수 호출 과정 – 계속 #include <stdio.h> stdio.h int sum(int, int); void id main(void){ i ( id){ int n1, n2; scanf_s("%d", &n1); scanf s("%d", scanf_s( %d , &n2); printf("%d+%d = %d\n",n1, n2, sum(n1,n2)); } // main i t sum(int int (i t a, int i t b){ int c = a+b; return c; }
main n1 n2 임시
4 3 7
sum a b c
4 3 7
24/31 24 /31
함수 호출 과정 – 계속 #include <stdio.h> int sum(int, int); void id main(void){ i ( id){ int n1 = 3; double f = 2.5; printf("%d\n", printf( %d\n , sum(n1 sum(n1+2, 2, f)); } // main int sum(int a, int b){ i t c = a+b; int b return c; }
main n1 f 임시
3 2.5 7 5
sum a b c
5 2 7
25/31 25 /31
지역변수의 수명 이전 슬라이드를 통해 알 수 있듯이 함수가 호출되면 함수를 위한 공간이 만들어지며, 이 공간에 함수의 매개변수를 위한 공간과 지역변수를 위한 공간이 생성됨. 생성됨 이 공간은 함수가 종료되면 반납됨. 따라서 지역변수와 매개변수의 수명 수명(lifetime)은 함수의 수명과 같음. 같음 즉, 함수가 호출되면 공간할당 받고, 함수가 종료되면 할당받은 공간을 반납하게 됨.
26/31 26 /31
달력을 출력하는 프로그램 년도를 입력 받아 아래 그림과 같이 그 해의 달력을 출력하는 프로그램을 작성하시오. 어떤 정보가 필요한가? 그 해 1월 1일이 무슨 요일인지 알아야 함. 각 달의 총 일수를 알아야 함. 함 윤년에 따라 2월의 총 일수는 다름. 어떤 함수가 필 필요할까? 할까 각 달을 출력하는 함수 (함수1) 이것을 12번 반복하면 2010년 2월 프로그램 완성!!!
sun mon tue wed thu fri sat 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
27/31 27 /31
달력을 출력하는 프로그램 – 계속 각 달을 출력하기 위해 필요한 것은? 각 달의 1일이 무슨 요일인지 알아야 한다. (함수2) 각 달의 총 일수를 알아야 한다. (함수 3) 각 달의 총 일수 중 2월은 윤년에 따라 일수가 다르다. 윤년 여부를 알아야 한다. 한다 (함수 함수 4)
28/31 28 /31
달력을 출력하는 void 프로그램 – 계속 printCalendarMonth(int year, int month){ #include <stdio.h> constt int i t SUN = 0; 0 const int MON = 1; const int TUE = 2; const int WED = 3;; const int THU = 4; const int FRI = 5; const int SAT = 6;
int weekday, nDays, day; printf("%d년 %d월\n", year, month); printf(" sun mon tue wed thu fri sat\n"); nDays = monthDays(year, monthDays(year month); weekday = firstDayOfMonth(year, month); indentFirstLine(weekday); for(day = 1; day <= nDays; day++){ printf("%4d", day); } } // printCalendarMonth
void printCalendarMonth(int year, int month); ( ){ void main(void){ int year, month; printf("년도를 입력하세요: "); scanf_s("%d", &year); for(month=1; month<=12; month++){ printCalendarMonth(year, month); printf("\n"); } } // main 29/31 29 /31
달력을 출력하는 프로그램 – 계속 monthDays 4,6,9,11월은 30일 1,3,5,7,8,10,12월은 31일 2월은 윤년이면 29일 아니면 28일 i t isLeapYear(int int i L Y (i t year)) 함수 필요 윤년이기 위한 조건 (다음 두 조건 중 하나만 만족하면 된다.) 4의 배수이지만 100의 배수가 아니다. 아니다 400의 배수이다. 여기서 잠깐. 함수이름이 is로 시작되면 보통 이 함수는 술어함수(predicate function)라 하며, 결과 값으로 참(1) 술어함수 또는 거짓(0)을 반환한다.
30/31 30 /31
달력을 출력하는 프로그램 – 계속 firstDayOfMonth 함수 1900년 1월 1일은 월요일이다. 1년은 365일 또는 366일(윤년인 경우)이다. indentFirstLine은 해당 월의 첫 날의 요일에 따라 필요한 공백 출력 일 0, 일: 0 월: 월 4, 4 화: 화 8, 8 …
31/31 31 /31
©copyright 2010
C프로그래밍I(CPA130) 강의노트 08
함수활용
한국기술교육대학교 한국기술 육대학 인터넷미디어공학부 김상진
강의목표 지역변수 전역변수 타입변환 재귀 시스템 라이브러리: 라이브러리 stdlib.h, tdlib h math.h, th h ctype.h t h
2/27
지역변수 지역변수는 함수 내부에 선언된 변수를 말함 보다 정확하게 말하면 중괄호 { }에 의해 형성되는 지역에 선언된 변수를 말함. 이 지역을 다른 말로 블록 블록(block)이라 함 C에서 지역변수는 블록이 시작되는 부분에 선언해야 함 프로그램 문장이 기술된 후에는 선언할 수 없음 예8.1) void f(void){ i t n = 5; int 5 printf("%d\n", n); int m = 0; // error } // f() ()
새 블록마다 변수를 선언할 수 있음 예8.2) 8 2)
void f(void){ int n = 5; printf("%d\n", n); { int m = 0; // ok printf("%d\n", m); } } // f()
3/27
지역변수 – 계속 같은 블록 내에는 같은 이름의 변수를 선언할 수 없음 매개변수도 지역변수이며, 이 변수의 블록은 함수 몸체 전체임 예8.3) void f(void){ void f(int n){ int n = 5; int m = 3; int n = 2; // error printf("%d\n", n); } // f()
int n = 5; // error printf("%d\n", p ( , n); ); } // f()
서로 블록(가시영역)이 구분되면 같은 이름의 변수를 선언할 수 있음 예8.4) void f(void){ ( ){ int n = 5; printf("%d\n", n); { int n = 0; // ok printf("%d\n", n); } printf("%d\n", p ( n); ) } // f()
void f(int m){ int n = 5; printf("%d\n", n); { int m = 0; // ok printf("%d\n", m); } } // f()
4/27
지역변수 – 계속 변수와 관련하여 반드시 알아야 하는 것은 가시영역 가시영역(scope)와 수명(lifetime)임 수명 변수의 가시영역이란 그 변수를 사용할 수 있는 영역을 말함 지역변수의 가시영역은 선언된 위치부터 그 변수가 선언된 지역의 끝까지임 예8.5) void f(void){ void f(void){ int n = 5, 5 i; for(i = 0; i<n; i++){ int m = 2; } } // f void g(void){ int m = 3;
int n = 5; } // f void g(void){ int m = 3; n = 5; // error } // f n을 n의 의 가시영역 밖에서 접근을 시도하고 있음
} // g
5/27
지역변수 – 계속 동일한 이름의 두 변수의 가시영역이 중첩되면 보다 작은 영역의 변수가 그 지역 내에서는 다른 변수를 접근할 수 없도록 만듦 이것을 다른 말로 한 변수가 다른 변수를 가린다고 말함 예8.6) void f(void){ int n = 5; printf("%d\n", n); { int n = 0; // ok printf("%d\n", n); } } // f()
6/27
전역변수 함수 밖에 선언된 변수를 전역변수 또는 광역변수라 함 전역변수의 가시영역은 선언된 위치부터 파일 끝까지이며, 다른 파일에서도 접근할 수 있음 지역변수의 수명은 함수의 수명과 같지만 전역변수의 수명은 프로그램 수명과 같음 예8.7) void f(void); int g global_n = 0;
void main(void){ printf("%d\n", global_n); f(); global_n++; printf("%d\n", global_n); } // main()
가시영역
수명
지역변수 블록영역
함수
전역변수 파일영역 프로그램 램
void f(void){ global_n = 5; printf("%d\n" global n); printf( %d\n , global_n); } // f() 7/27
전역변수 – 계속 앞서 설명한 바와 같이 전역변수와 지역변수는 서로 영역이 구분되므로 전역변수와 동일한 이름의 지역변수를 선언할 수 있음 이 때 지역변수가 전역변수를 가리게 되어, 그 지역변수의 영역에서는 같은 이름의 전역변수를 접근할 수 없게 됨 예8.8) 8 8) void f(void); int n = 0;
voidmain(void){ printf("%d\n", n); f(); n++; printf("%d\n", n); } // main() void f(void){ int n = 5; printf("%d\n", n); // 지역 n 값이 출력 } // f() 8/27
전역변수 – 계속 전역변수를 사용하면 함수로 입력으로 전달하지 않아도 되며, 함수로부터 결과를 직접 반환 받지 않아도 됨 예8.9) void findMax(void); int gMax = 0; int gA = 0; int gB = 0; void main(void){ scanf_s("%d", &gA); scanf_s("%d", &gB); findMax(); printf("%d\n" printf( %d\n , gMax); } // main() void findMax(void){ gMax = (gA>gB) ? gA : gB; } // findMax()
int max(int, int); void main(void){ int a, b; scanf_s("%d", &a); scanf s("%d" scanf_s( %d , &b); printf("%d\n", max(a,b)); } // main() int max(int a, int b){ return (a>b) ? a : b; } // max(int, int)
9/27
지역변수 VS. 전역변수 지금까지 지역변수/전역변수의 문법적 특징을 설명하기 위해 여러 가지 예시를 제시하였지만 중첩되는 영역에 동일한 이름이 변수를 선언하여 사용하는 것은 바람직하지 않음 전역변수를 활용하면 편리한 측면이 있지만 문제가 발생하였을 때 이를 수정하기가 어렵 어렵고,, 프로그램을 램을 분할 정복하여 구현하기가 힘듦 프로그램이 50개의 함수로 구성되어 있고, 전역변수가 여러 개 사용되고 있다고 하자. 그러면 이 전역변수는 50개 함수 모두에서 사용되고 있을 수 있다. 있다 끔직하다. 끔직하다 프로그램을 이해하기 어렵고 나중에 수정하기 매우 힘듦 반면에 지역변수가 사용되는 영역이 매우 제한적이므로 이 변수 때문에 발생하는 문제는 이 영역으로 제한됨
10/27 10 /27
재귀(recursion) 함수에서 다른 함수를 호출할 수 있음 실제 다른 함수뿐만 아니라 자기 자신도 호출할 수 있음 이처럼 함수 내에서 자기 자신을 호출하는 것을 재귀 호출 (recursive call)이라 함 예8.10) 8 10) n! int factorial(int n){ int prod = 1 1, i; for(i = 1; i<n; i++){ prod *= i; } return prod; } // factorial(int)
int factorial(int n){ if(n<=1) return 1; else return n * factorial(n-1); } // factorial(int)
11/27 11 /27
재귀 – 계속 재귀 호출 호출: 호출하는 함수와 호출받는 함수가 같은 함수 호출 직접 재귀 재귀(direct recursion): 자기 자신을 바로 호출하는 함수 간접 재귀 재귀(indirect recursion): 둘 이상의 함수 호출이 최초의 호출 함수로 되돌아 오는 경우 재귀의 예: factorial if n 0 1, n! n ( n 1) 1 if n 0
예8.11) 4!=4321=24 위 factorial의 정의를 다시 표현하면 if n 0 1, n! n ( n 1)! if n 0
base case: 재귀가 아닌 경우 general case
이와 같이 자신의 작은 버전으로 자신을 정의하는 정의를 재귀 정의 정의(recursive definition)라 함 12/27 12 /27
재귀 프로그래밍
facto orial(4)
int factorial(int n){ if(n<=1) 1; if( 1) return t 1 else return (n * factorial(n-1)); } // factorial(int)
4
3 n=4 n 4
24
recursive 방법 보통 선택문(if, switch)으로 구현 함수 호출이 많이 이루어짐
2 n=3 n 3
6
int factorial(int n){ int val = 1; for(int i=2; i<=n; i++) val = val * i; return val; } // factorial(int)
1 n=2 n 2
2
n=1 n 1 1
non-recursive 방법 보통 통 반복문(for, 반복문(f while)으로 hil )으 구현
13/27 13 /27
문제에 대한 재귀적 해결책 어떤 문제를 재귀적으로 해결하고자 할 때 고려해야 하는 네 가지 사항 문제를 같은 종류의 작은 문제로 재귀 정의를 할 수 있는가? 각 재귀호출 때 문제의 크기를 어떻게 줄이는가? base case 역할을 하는 문제의 인스턴스는 무엇인가? 각 재귀호출이 결국에는 base case에 도달할 수 있는가?
14/27 14 /27
재귀 프로그래밍의 검증 세 가지 질문 base--case question base question: 메소드를 종료할 수 있는 비재귀적인 방법이 있나? base-case가 제대로 구현되어 있나? n=1일 n 1일 때가 base base-case이며 case이며, 이 때 1을 반환함 smaller--case question smaller question: 재귀 호출이 원 문제의 작은 버전을 호출하고 있는가? 결국에는 base-case까지 b 까지 도달하나? 아니면 무한 재귀 재귀호출에서 인자는 n-1을 사용하고 있다. 일련의 재귀 호출은 인자의 값을 하나씩 감 감소하므로 하 결국에는 n이 이 1이됨 이됨 general--case question general question: 전체적으로 제대로 구현되어 있나? f t i l( 1)이 (n-1)!을 factorial(n-1)이 ( 1)!을 반환해주면 전체적으로 n!을 !을 반환함
15/27 15 /27
재귀의 예: 피보나츠 수 피보나츠 수: 0, 1, 1, 2, 3, 5, 8, 13, 21, …, f0 = 0, f1 = 1 fn = fn-1 + fn-2 예8.12) int fibonacci(int n){ int fn2 = 0; int fn0 = 0, fn1 = 1; if(n<=1) return n; else for(i = 2; i<=n; i++){ fn2 = fn0+fn1; fn0= fn1; fn1 = fn2; } return fn2; } // fibonacci(int)
int fibonacci(int n){ if(n<=1) return n; else return fibonnaci(n-1) + fibonacci(n-2); } // fibonnaci(int)
F(5) F(3)
F(4) F(3)
F(2) F(1)
F(2) F(0)
F(1)
F(1) F(0)
16/27 16 /27
최대공약수 구하기 최대공약수를 구하는 알고리즘: 유크리드 알고리즘 a=bq+r, 0≤r<b일 때 gcd(a, b) = gcd(b, r) 예8.13) int gcd(int a, a int b){ int r; while(b!=0){ r = a % b; a = b; b = r; } return a; } // fibonacci
int gcd(int a, a int b){ if(b==0) return a; else return gcd(b, a%b); } // gcd
17/27 17 /27
재귀 Tip 재귀 방식으로 해결할 수 있는 문제는 항상 반복문을 이용하여 구현 가능함 재귀가 이해하기가 더 쉽고, 작성하기 편리한 경우가 있음 효율성 측면에서는 반복문을 이용하여 구현하는 것이 효과적임 특히 피보나츠 수와 같은 예는 절대 재귀 방식으로 구현하지 않아야 함
18/27 18 /27
하노이 타워
begin peg
aux peg
end peg
peg 1
peg 2
peg 3
제약 (1) 한번에 하나의 링만 움직일 수 있다. 있다 (2) 작은 링 위에 큰 링을 올려놓을 수 없다. 힌트
19/27 19 /27
하노이 타워 하노이 타워 알고리즘 단계 1. n-1개의 링을 peg 1(시작 peg)에서 peg 2(보조 peg)로 옮겨라. 옮겨라 단계 2. n번째 링을 peg 3(목적 peg)으로 옮겨라. 단계 3. p peg g 2(보조 ( peg)에 p g) 있는 n-1개의 링을 p peg g 3(목적 ( peg) p g) 으로 옮겨라. void main(void){ int n = 3; towersOfHanoi(1, 3, 2, n); } // main void towerOfHanoi(int begin, int end, int aux, int n) { if(n==1) printf("막대 %d에서 막대 %d로 옮겨라\n", begin, end); else{ // 완성하시오. } } // towerOfHanoi 20/27 20 /27
시스템 라이브러리 랜덤한 정수 stdlib.h 헤더 파일에 선언되어 있는 rand() 함수 int rand(void); 이 함수는 0과 RAND_MAX 사이에 임의의 정수를 생성하여 준다. 주사위 예8.14) int n = rand()%6+1; 컴퓨터에서 난수는 실제 난수가 아님 (컴퓨터는 유한상태 기계) 대신 난수처럼 보이는 의사난수(pseudorandom)을 생성함 이와 같은 의사난수 생성 알고리즘은 랜덤한 초기값을 이용하여 이 값으로부터 일련의 난수를 생성한다. 이 초기값을 seed라 함 따라서 초기값이 같으면 동일한 난수를 생성하므로, 생성하므로 매번 다른 난수를 생성하도록 하기 위해서는 이 초기값을 바꾸어야 함
21/27 21 /27
stdlib.h rand()의 초기값은 stdlib.h에 선언되어 있는 다음 함수를 통해 지정하여 줌 void srand(unsigned int); 예8.15) long seed; seed = time(NULL); srand(seed); int n = rand()%6+1;
time 함수는 time.h에 선언되어 있는 함수로 1970년 1월 1일 이후의 경과된 시간을 초 단위로 반환하는 함수임 즉, 현재 컴퓨터 시간을 알아내기 위해 사용되는 함수임 NULL은 stdlib.h에 정의되어 있는 상수임
22/27 22 /27
간단한 주사위 게임 게임 방법 컴퓨터와 사용자는 두 개의 주사위를 던짐 두 주사위의 합계가 높은 측이 이긴다. 단, 합계가 같을 경우에는 한 쪽의 두 주사위가 동일한 숫자가 나온 경우에는 이김 void main(void) { int compA, compA compB, compB compSum; int userA, userB, userSum; char isFin; srand((unsigned)time(NULL)); while(1) { } // while } // main()
23/27 23 /27
math.h 제곱근을 구해주는 함수: double sqrt(double x); 자연로그를 구해주는 함수: double log(double x); 상용로그를 구해주는 함수: double log10(double x); 기저 2에 대한 로그를 구해주는 함수: double log2(double x); 거듭제곱을 구해주는 함수 xy: double d bl pow(double (d bl x, double d bl y); ) 자연로그의 거듭제곱을 구해주는 함수: double exp(double x); 삼각함수: double sin(double x); 삼각함수: double cos(double x); 삼각함수: double tan(double x); 반올림: double round(double x); // C99 소수부분 버림: double trunc(double x); // C99 x보다 보다 큰 정수 중 가장 작은 정수: 정수 double d bl ceil(double il(d bl x); ) x보다 작은 정수 중 가장 큰 정수: double floor(double x);
24/27 24 /27
2차 방정식의 근 구하기 2차 방정식 이용하여 구할 수 있다. 근의 공식:
의 해는 다음과 같은 근의 공식을
먼저 주어진 2차방정식의 근 종류를 식별하시오. 식별하시오 1) 두 개의 실근, 2) 중근, 3) 두 개의 허근 두 개의 실근 또는 중근을 갖는 경우에는 근을 출력하시오. 제곱 은 sqrt 함수 이용 제곱근은
void main(void) { int a; int b; int c; printf("2차방정식의계수를입력하시오: "); scanf s("%d%d%d" scanf_s( %d%d%d , &a, &a &b &b, &c); … } // main()
25/27 25 /27
ctype.h int isalnum(int c); char 대신에 int를 입력으로 받고 있음 c가 '0',…, '9', 'A', …, 'Z', 'a', …, 'z' 중 하나면 1 아니면 0을 반환함 int isalpha(int c); c가 가 'A', 'A' …, 'Z', 'Z' 'a', ' ' …, 'z' ' ' 중 하나면 1 아니면 0을 반환함 int isdigit(int c); c가 '0', 0 , …, '9' 9 중 하나면 1 아니면 0을 반환함 int isxdigit(int c); c가 '0', …, '9', 'A', …, 'F', 'a', …, 'f' 중 하나면 1 아니면 0을 반환함 int islower(int c); 'a', …, 'z' 중 하나면 1 아니면 0을 반환함 i t isupper(int int i (i t c); ) 'A', …, 'Z' 중 하나면 1 아니면 0을 반환함
26/27 26 /27
ctype.h – 계속 int tolower(int c); c가 대문자이면 그것의 소문자를 반환함 int toupper(int c); c가 소문자이면 그것의 대문자를 반환함 fflush(stdin)는 입력버퍼를 fflush(stdin); 비워주는 역할을 해줌 printf("새 게임(y/n)?"); scanf_s( %c , &isFin, scanf s("%c" &isFin 1); if(tolower(isFin)=='n') done = 0;
27/27 27 /27
C프로그래밍I(CPA130) 강의노트 09
©copyright 2010
배열/문자열
한국기술교육대학교 한국기술 육대학 컴퓨 컴퓨터 터공학부 김상진
강의목표 배열 일차원/다차원 배열의 선언 배열의 초기화 배열 인수 const와 t와 배열 문자열
2/25
배열 지금까지 살펴본 데이터의 유형은 원시타입 원시타입(primitive type) 또는 sum 기본타입(basic type)이라 함. 즉, 한 가지 값만 저장함 기본타입 같은 종류의 여러 개의 값을 하나의 데이터로 취급(하나의 하나의 이름을 이용하여 접근 접근)할 수 있도록 해주는 타입을 배열 배열(array)이라 함 배열처럼 여러 개의 값으로 구성되는 타입을 복합 타입 타입(composite type)이라 함 예를 들어 학생 40명의 성적을 처리해야 하면, 40개의 정수 변수를 사용하여 처리할 수 있다. 있다 하지만 이렇게 하는 것은 번거로움 대신 정수 40개로 구성된 배열을 선언하여 사용하는 것이 편리함
학생성적
3/25
배열 – 계속
여기에 제시된 배열의 특성은 C언어만의 특성은 아니다.
배열의 특성 동질 구조 구조(homogeneous structure): 구조에 있는 모든 요소는 같은 타입임 요소들간에 순서가 존재 존재함 배열의 요소는 위치에 의해 접근됨 색인/첨자(index)를 사용하여 개별 요소를 접근하며, 이 색인은 0부터 시작 시작함 [ ] 연산자 이용. 배열의 용량 용량(capacity) (capacity)은 은 컴파일 시간에 정해짐 배열의 모든 슬롯에 유효한 요소가 들어있을 필요는 없음 배열의 용량을 변경할 수 없다. 즉, 정적 구조 구조임 임의 접근 제공 제공: 모든 요소를 바로 접근할 수 있음 연속된 공간으로 할당되며, 동질구조(각 요소의 크기가 동일) 이기 때문에 임의 접근이 가능함 4/25
배열의 선언 배열 선언의 형태 type arrayName[arrayCapacity]; 예9.1) int c[12]; 배열의 용량 용량을 지정할 때 상수 변수나 기호 상수를 사용할 수 있으며, 이들을 사용하는 것이 바람직함 (Visual Studio 2005 미지원) 예9.2) const int MAX_STUDENT = 100; int scores[MAX_STUDENT]; scores[MAX STUDENT];
#define MAX_STUDENT 100 int scores[MAX_STUDENT]; scores[MAX STUDENT];
상수 변수가 아닌 일반 변수로는 배열의 용량을 지정할 수 없음 예를 들어 학생 성적을 처리하기 위해 한 반의 최대 학생 수를 고려하여 용량이 45인 배열을 만들었다. 하지만 특정 반의 경우에는 30명밖에 명밖에 수강하지 않 않고 있음 있음. 이 경우 준비된 45개의 개의 공간 중 30개의 개의 공간밖에 사용하지 않게 된다. 이 때 30을 배열의 크기 크기(size)라 함 크기/용량은 이 수업에서만 사용되는 용어임 5/25
배열의 선언 – 계속 배열도 일반 변수와 마찬가지로 여러 배열을 동시에 선언할 수 있음 예9.3) i t a[10], int [10] b[10] b[10]; int c[10], d;
// d는 배열이 아님
하지만 배열도 한 번에 하나씩 선언하는 것이 바람직함 시스템은 배열 변수를 위해 연속적인 공간을 할당하여 줌 예9.4) 9 4) int c[5];
이 때 배열 변수의 이름 c는 이 공간의 시작 위치를 가리킴 시작위치란? 다음 노트… c
6/25
배열의 요소 접근 배열의 접근: [ ] 연산자 사용 예9.5) int c[12]; c[3] = 2; a = c[4]; arrayName[expression]: 이 때 사용되는 표현식의 평가 값은 정수이어야 함 색인은 0부터 시작 시작함 배열의 범위를 벗어난 접근은 문법적 오류가 류가 아니라 실행 시간 오류임 (배열을 사용할 때 가장 주의할 점) 유효한 색인: 0부터 (용량–1)까지 실제 사용해야 할 색인: 색인 0부터 (크기–1)까지 (크기 1)까지 왜 0부터 시작할까? (다음 시간에…) 난 1부터 시작하고 싶다. 필요한 용량보다 하나 더 많이 사용하고, 0번째 공간은 사용하지 않음 (좋은 방법은 아님)
7/25
일차원 배열의 초기화 배열은 선언과 동시에 초기화할 수 있음 예9.6) int c[5] = {1, 2, 3, 4, 5}; 주어진 초기값의 개수가 배열의 용량보다 적고 배열의 타입이 수치 데이터 타입이면 나머지는 0으로 초기화 초기화됨 예9.7) 9 7) int a[5] = {0} {0}, b[5] = {1}; 위 예는 아래와 동일함 int a[5] [ ] = {0, { , 0,, 0,, 0,, 0}, }, b[5] [ ] = {1, { , 0,, 0,, 0,, 0}; }; 주어진 초기값의 개수가 배열의 용량보다 많으면 이것은 문법 오류임 예9.8) int c[2] = {1, 2, 3, 4}; // error 선언과 동시에 초기화할 때 배열의 용량을 생략하면 주어진 초기값의 개수에 의해 배열의 용량이 결정됨 예9.9) int c[ ] = {1, 2, 3, 4, 5}; // 예9.6과 동일
8/25
일차원 배열의 초기화 – 계속 Tip. 초기화 목록을 사용하는 것이 실행시간에 대입문을 이용하여 초기화하는 것보다 실행시간이 빠름 예9.10) int c[5]; int c[ ] = {1,2,3,4,5};
c[0] = 1; c[1] = 2; c[2] = 3; c[3] = 4; c[4] = 5;
하지만 배열의 용량이 클 경우에는 초기화 목록을 사용하는 것이 불편한 측면도 있음 예9.11) 9 11) int c[100], i; for(i=0; i<100; i++) c[i] = 1;
int c[100], i; for(i=0; i<100; i++) c[i] = 0;
int c[100] = {0}
9/25
sizeof 연산자와 배열 sizeof 연산자를 배열 이름에 적용하면 배열의 전체 용량을 줌 예9.12) int a[10]; int size = sizeof a; // = sizeof(int)10 sizeof 연산자를 이용하여 배열의 용량을 계산하는 방법 예9.13) 9 13) int a[] = {1, {1 2, 2 3 3, 4 4, 5}; int size = sizeof(a)/sizeof(int); // = 5 예9.14) int a[] = {1, 2, 3, 4, 5}; int size = sizeof(a)/sizeof(a[0]); // = 5 이 방법은 배열의 타입을 모르는 상태에서도 배열의 용량을 계산할 수 있음 주의.. 배열의 용량보다 배열의 현재 크기가 더 중요함 주의
10/25 10 /25
배열의 사용 배열에 데이터를 저장할 때 보통 연속적으로 저장함 예9.15) 용량이 10인 배열에 5개의 데이터를 저장해야 할 때 (일반적인 방법)
예외적 예외적으로 검색을 빠 빠르게 게 하기 위해 이와 같이 사용할 수 있음 이 경우에는 데이터가 없는 모든 배열 요소에 이 사실을 나타내는 값을 저장해야 하므로 실제로는 5개의 데이터가 저장된 것이 아니라 배열의 용량만큼 데이터가 저장되어 있다고 생각해야 함
11/25 11 /25
다차원 배열 보통 3차원 이상의 배열을 정의하여 사용할 수 있지만 가장 많이 사용되는 다차원 배열은 2차원임 2차원 배열의 선언 예9.16) int a[3][3]; 행 열 순서로 2차원 배열의 용량을 지정함 행, 요소의 접근도 [] 연산자를 두 개 사용함 논리적으로 다음과 같은 모습이지만 논리적 습이지만 실제 시 시스템에서는 템에서는 일차원으로 a[0][0] a[0][1] a[0][2] 공간이 할당됨
a[0][0]
a[0][1]
a[0][2]
a[1][0]
a[1][1]
a[1][2]
a[2][0]
a[2][1]
a[2][2]
a[1][0]
a[1][1]
a[1][2]
a[2][0]
a[2][1]
a[2][2]
12/25 12 /25
다차원 배열의 초기화 2차원 배열의 초기화 1 예9.17) int a[2][2] = {{1,2},{3,4}}; 3 만약 충분한 초기값이 없으면 나머지 항의 값은 0으로 초기화됨 1 예9.18) 9 18) int b[2][3] = {{1},{3,4,5}}; {{1} {3 4 5}}; 3 다음과 같이 초기화할 수도 있음 1 예9.19)) int c[2][3] [ ][ ] = {1,2,3,4,5}; { , , , , }; 4 초기화 목록을 사용하면 행 용량 1 정보는 생략할 수 있음 3 예9.20) 9 20) int i t c[][3] [][3] = {{1 {{1,2},{3,4,5}}; 2} {3 4 5}} 일반화하면 다차원 배열에서 초기화 목록을 사용할 때 첫 번째 용량은 항상 생략이 가능함 예9.21) int c[][3] = {1,2,3,4}; 1 4
2 4 0
0
4
5
2
3
5
0
2
0
4
5
2
3
0
0
13/25 13 /25
배열 인수 원시타입과 마찬가지로 배열도 함수의 입력으로 전달할 수 있음 원시타입을 함수로 전달하는 방식은 값에 의한 호출(call-by-value) 호출 방식이라 함 이 방식을 사용하면 전달된 데이터 값이 함수의 매개변수로 복사됨 예9.22) 9 22) main f void f(int n){ n = 1; } void main(void){ int n = 0; f(n); printf("%d\n", n); } // main
n
0
n
1 0
main의 지역변수 n과 f 함수의 매개변수인 n은 서로 다른 것임 값을 전달할 때 사용한 변수의 값은 함수 호출에 의해 영향을 받지 않음 여기서 출력되는 값은 0임
14/25 14 /25
배열 인수 – 계속 배열도 같은 방법으로 전달된다고 가정하여 보자. 예9.23)
main
void f(int n[100]){ n[0] [ ] =1;; } void main(void){ int n[100] = {0}; f(n); printf("%d\n", n[0]); } // main
f
이 프로그램을 실행하면 1이 출력된다. 출력된다 함수에서 수행된 변경이 함수가 끝난 후에도 반영되고 있다. ???
100개의 원소를 모두 복사한다 Oh! My God
15/25 15 /25
배열 인수 – 계속 그러면 어떻게? 주소 주소를 전달함 주소가 뭐야? (다음 시간에…) 예9.24)
main
n 1 0
0
0
0
0
void f(int n[]){ n[0] =1; } void main(void){ int n[100] = {0}; f(n); printf("%d\n", n[0]); } // main
f n
배열 변수의 이름은 배열 공간의 시작위치 정보를 가지고 있음 따라서 배열 매개변수를 선언할 때 용량은 아무런 의미가 없기 때문에 보통 생략함 주소를 주 를 이용하면 가시영역 밖에 있는 데이터를 접근할 수 있음 16/25 16 /25
배열 인수 – 계속 배열을 입력을 받는 함수는 보통 배열의 현재 크기 또는 용량을 받아야 함 함수에서는 배열 전체를 받는 것이 아니라 시작 위치만 받기 때문에 배열의 용량이나 배열의 현재 크기를 알 수 없음 예9.25) 9 25) #include <stdio.h>
출력: main:400 i 400 f:4
void f(int n[]){ printf("f:%d\n", sizeof(n)); } void main(void) { int n[100] = {0}; printf("main:%d\n" printf( main:%d\n , sizeof(n)); f(n); }
배열이 선언된 지역 내에서는 배열 이름과 sizeof 연산자를 통해 배열의 용량을 알 수 있지만 매개변수를 통해서는 전달받은 배열의 용량을 알 수 없음
17/25 17 /25
배열 인수 – 계속 따라서 함수로 배열로 전달하는 경우에는 함수에서 배열의 올바른 범위를 알 수 있도록 배열의 현재 크기 또는 작업 범위를 추가로 전달해야 함 예9.26) n의 용량은 10이지만 현재의 크기는 5이다.
이정 정보가 가 함수에 전달되지 않 않으면 면 0부터 부터 어디까지 찾아야 하는지 findMax는 알 수 없음
int findMax(int n[], int size){ int i, max = n[0]; for(i=1; i<size; i++) if(n[i]>max) max = n[i]; return max; } void main(void){ int n[10] = {1, 5, 2, 8, 4}; printf("%d\n" printf( %d\n , findMax(n,5)); findMax(n 5)); } // main
18/25 18 /25
배열 인수 – 계속 이와 같은 배열 인수 전달 방법은 다음과 같은 추가 장점이 있음 용량이 다른 배열을 같은 함수에 전달할 수 있음 예2.27) void fill(int n[], int size, int value){ int i;; for(i=1; i<size; i++) n[i] = value; } void main(void){ int a[10]; [ ]; int b[20]; fill(a, 10, 1); fill(b, 20, –1); } // main
19/25 19 /25
const와 배열 인수 배열을 인수로 전달하면 함수 내에서 이루어진 변경이 함수가 종료된 후에도 계속 유지됨 const 키워드를 사용하면 함수 내에서 전달한 배열의 내용을 변경할 수 없게 됨 예2.28) 2 28) void f(int b[]){ b[0] = 10; } void main(void){ int a[5] = {1,2,3,4,5}; f(a); ( ) printf("%d\n", a[0]); } // 10이 출력
void f(const int b[]){ b[0] = 10; // error } void main(void){ int a[5] = {1,2,3,4,5}; f(a); ( ) printf("%d\n", a[0]); }
void g(int b[]){ … } void f(const int b[]){ g(b); // error } void main(void){ int a[5] = {1,2,3,4,5}; f(a); }
20/25 20 /25
다차원 배열 인수 일차원 배열을 인수로 받는 함수를 선언할 때 배열의 용량은 지정하지 않음 2차원 배열의 경우에는 열의 용량은 반드시 지정해야 함 일반화하면 첫 번째 용량은 지정하지 않아도 되지만 나머지 용량은 반드시 지정해야 함. 함 이것은 함수 내에서는 각 색인의 위치를 계산할 수 있어야 하기 때문임 예9.29) void printArray(int b[][3], int size){ int i, j for(i=0; i<size; i++) for(j=0; j<3; j++) printf("%d printf( %d ", b[i][j]); printf("\n"); } void main(void){ int a[3][3] = {{1,2,3},{4,5,6},{7,8,9}}; printArray(a, 3); }
21/25 21 /25
문자열 문자열도 여러 개의 문자 값으로 구성된 복합타입임 문자 배열과 매우 유사하지만 한 가지 차이점이 있음 예9.30) char string[] = "abc";는 char cArray[] = {'a', 'b', 'c'}; char string[] = "abc"; printf("%d\n" printf( %d\n , sizeof(cArray)); printf("%d\n", sizeof(string));
char string[4] = "abc"; char string[] = {{'a', a , 'b', b , 'c', c , '\0'};와 \0 };와 동일 결과: 3 4
문자열은 그 끝에 항상 널 문자('\0')를 가지고 있음 널 문자는 정수 값으로 0임 #define NULL ((void *)0) )0) 널 문자와 NULL은 다름 널 문자가 추가된 것을 제외하고 문자열은 문자의 배열이므로 배열 접근 연산자를 이용하여 문자열의 개별 문자를 접근할 수 있고, 있고 수정도 가능함 예9.31) string[1] = 'c'; 22/25 22 /25
문자열 – 계속 예9.32) char str[3] = "abc"; 용량이 실제 부족하지만 C에서는 오류가 아님 문자열을 조작할 때 마지막 널 문자가 삭제되지 않도록 주의해야 함 예9.33) a b c \0
char string[4] = "abc"; string[3] = 'd';
a b c d
문자열이 문자배열로 …
23/25 23 /25
문자열의 입출력 printf, scanf에서 %s 사용 줄 단위 입력/출력할 때에는 gets, puts 사용 가능 예2.33) char name[100]; scanf("%s", name); printf("%s\n" printf( %s\n , name);
scanf를 이용하여 변수에 값을 입력 받고 있는데 & 연산자를 사용하지 않고 있다. 왜 왜??? 배열 이름, 이 주소 (다음 다 노트…) char name[100]; gets(name); puts(name);
화면에서 다음과 같이 입력하면 화면 Sangjin3 Kim 다음과 4같이 출력됨 Sangjin 즉, 첫 번째 공백까지만 입력을 받음. scanf는 공백 기준으로 입력을 처리함 화면에서 다음과 같이 입력하면 Sangjin Kim 다음과 같이 출력됨 Sangjin Kim 즉, enter 키를 누르기 전까지 입력한 모든 문자를 문자열로 입력 받음. gets, puts는 stdio.h에 선언되어 있음
24/25 24 /25
기타 차후 설명할 내용 배열을 일반 정수처럼 반환할 수 없지만 포인터를 이용하여 비슷한 효과를 낼 수 있음 (포인터) int[] f(…){ // error … } 문법적 오류
int[] f(){ i t n[10]; int [10] … return n; }
만약 이것이 문법적 오류가 아니더라도 큰 문제가 있다. 그 문제점은?
static 배열을 이용한 효율성 증대 방안 (정적 변수 vs. 자동 변수) char *s = "abc"와 char s[] = "abc"의 차이점 (포인터) 배열을 값에 의한 호출로 전달하는 방법 (구조체) 문자열의 비교 등 다양한 처리 방법
25/25 25 /25