-
c++ 프로그래밍 (2)방송대/3-2학기 2021. 11. 30. 13:45
04 함수
04-1. 함수의 정의와 호출
- 함수: 필요한 작업을 수행하는 프로그램 문장들을 모아서 이름을 부여한 것
(2) 함수 사용형식
ReturnType functionName (fParameterList) // 머리부 { // 몸체 블록 ... return returnExpression }
- fParameterList 인수를 받기 위한 형식 매개변수 선언
- ReturnType 함수의 결과로 반환하는 값의 자료형
- returnExpression 함수의 결과로 반환하는 값
- returnExpression 은 함수 머리부에 선언한 ReturnType 과 일치하는 자료형의 수식 또는 묵시적 형 변환이 가능한 자료형의 수식을 사용함
- 함수 호출형식
functionName (aParameterList); varName = functionName(aParamterList);
- 함수의 원형: 함수의 머리부만 따로 선언해준 것
float FahrToC(float fahr);
(5) 함수 사용에 따른 장단점
- 단점: 함수 호출과 복귀 과정에서 처리시간에 추가됨 → 매우 효율적으로 동작해야 하는 함수라면 inline 함수로 선언
04-2. 인수의 전달
(1) 인수와 매개변수
- 인수: 함수 호출 문장에서 함수에 전달하는 값, 매개변수를 통해 인수를 전달함
- 실 매개변수 (actual parameter): 함수 호출 문장에서 함수의 형식 매개변수에 전달할 값
cTemp = FahrToC(fTemp); // fTemp 가 실매개변수
- 형식 매개변수 (formal parameter): 인수를 전달받기 위해 함수에 선언된 매개변수, 함수 헤더에 매개변수의 자료형과 이름을 선언함
float FahrToC(float fahr) // float fahr 가 형식 매개변수 { ........... }
(2) 인수 전달방식
- 값 호출 (call-by-value): 실 매개변수의 값을 형식 매개변수에 복사
- 장점: 실 매개변수와 형식 매개변수는 별개의 데이터이므로 불필요한 부작용이 발생하지 않음
- 단점: 구조체와 같이 많은 양의 데이터로 구성된 인수를 전달할 경우 데이터 복사량이 많아짐
- 참조 호출 (call-by-reference): 실 매개변수의 참조를 형식 매개변수에 전달
- 함수에서 형식 매개변수의 값을 변경하는 것은 실 매개변수의 값을 변경하는 것과 같음
void SwapValues (int &x, int &y); // 함수의 머리부, 원형
- 많은 양의 데이터로 구성되는 구조체, 객체를 인수로 전달하는 경우 값 호출에 비해 참조 호출이 효율적임
- 실 매개변수의 값이 변경되는 것을 원치않는 형식 매개변수에는 const 한정어를 지정해 실 매개변수를 보호함
(3) const 매개변수
- const 한정어를 이용한 실 매개변수 보호
void PrSalesRec (const SalesRec &srec) // const 매개변수 { ......... // 내부에서 매개변수 변경하면 Compile Error }
(4) 디폴트 인수
- 인수의 디폴트 값을 지정하는 방법: 일반적으로 사용되는 디폴트 값이 있는 인수의 경우, 함수를 정의할 때 그 값을 미리 지정할 수 있음
double Round(double x, int d = 0);
int main() { cout << "반올림 -->" << Round(a) << endl; // Round(a) = Round(a, 0) }
- 주의: 디폴트 인수는 인수 중 끝에만 위치할 수 있음
void f(int x, int y=10, int z=20); // OK void g(int x, int y=10, int z); // Error
f(5); // x=5, y=10, z=20 전달 f(5, 100); // x=5, y=100, z=20 전달 f(5, 100, 200); // x=5, y=100, z=200 전달 f(5, , 300); // Error: f(5, 10, 300); 으로 작성해야 함
04-3. 함수의 다중정의
- 다중정의overloading: 동일한 이름에 대해 여러 의미를 부여하는 것
- 함수 다중정의
- 동일한 이름을 갖는 함수를 여러 개 정의하는 것
- 동일한 개념의 처리를 여러 가지 데이터나 객체에 대해 각각의 대상에 맞는 처리를 해야할 경우 사용함
- 다중정의된 함수의 구분: 인수의 개수/ 자료형
*함수의 반환 자료형으로 함수를 구분할 수는 없음
void AddTime(TimeRec& t1, const TimeRec& t2); void AddTime(TimeRec& t, int minutes);
- 같은 처리여도 대상이 다르므로 내용이 다르다
void AddTime(TimeRec &t1, const TimeRec &t2) { .... t1.hours += t2.hours + t1.minutes / 60; }
void AddTime(TimeRec &t, int minutes) { .... t.hours += t.minutes / 60; }
- 모호한 함수 다중정의: ① 반환 자료형만 다를 때는 오류 발생
② 매개변수 갯수가 다를 때 선택 기준이 모호하다
void g(int a) {} void g(int a, int b = 100) {} int main() { g(10, 20); g(10); // 에러: 선택 기준이 모호함 }
③ 형 변환 대상이 모호하다
void h(int a) {} void h(float a) {} int main() { h(10); h(10.0f); h(10.0); // 에러: 형 변환 대상이 모호함 }
04-4. inline 함수
- 함수 호출 절차를 따르지 않고 함수 호출 위치에 함수의 처리문장이 삽입되게 번역하도록 선언된 함수
- 코드 최적화가 가능해짐
- 매우 빈번히 호출되고 빠른 실행이 요구되는 함수를 inline 함수로 선언하면 성능을 높이는 데 도움이 됨
* inline 선언을 무시하고 일반 함수로 번역하는 경우
- 함수가 너무 큰 경우
- 순환 호출 recursive call 을 하는 경우
- 프로그램 내에서 그 함수에 대한 포인터를 사용하는 경우
inline void SwapValues(int &x, int &y) { .... } int main() { if (a < b) SwapValues(a, b); }
05 클래스와 객체 (1)
05-1. 객체지향 프로그래밍의 주요 개념
(1) 객체object
소프트웨어 시스템 안의 어떠한 대상을 표현한 것으로,
- 정해진 처리를 수행 → 행위, 메소드, 멤버함수
- 처리 과정에 따라 내부 상태가 변화할 수 있음 → 속성, 데이터 멤버
- 다른 객체와 상호작용할 수 있음 → 메시지 전달 (멤버함수 호출)
(2) 클래스
- 객체의 설계도, 객체가 포함할 속성에 대한 명세와 메소드의 정의를 포함함
(3) 캡슐화 encapsulation
- 내부 속성 등 구현에 대한 부분은 공개하지 않으며(정보은닉), 객체 외부에서는 공개된 인터페이스를 통해 객체를 사용
*캡슐화의 장점
- 소프트웨어의 유지보수가 용이함: 프로그램의 다른 부분에 영향을 미치지 않고 객체 내부 구현 방법을 수정할 수 있음
- 재사용이 용이함: 잘 설계된 캡슐화된 객체는 다른 응용에서도 재사용할 수 있어 소프트웨어 개발 비용을 줄일 수 있음
(4) 상속
- 클래스의 계층적 설계: 기초 클래스, 파생 클래스
05-2. 클래스 선언과 객체 정의
(1) 클래스 선언
- 표현하고자 하는 대상의 메소드와 속성을 선언한 것
클래스 내부
- 속성: 객체의 상태를 표현, 데이터 멤버
- 메소드: 객체의 행위를 정의, 멤버 함수
가시성 지시어
- 클래스의 멤버가 공개되는 범위를 나타냄
private (디폴트): 소속 클래스의 멤버함수, 친구 클래스의 멤버함수 및 친구함수, 그 외의 범위에는 비공개
- 정보은닉을 위해 사용함, 클래스의 구현을 위한 내부 상태(데이터 멤버)는 일반적으로 private 으로 지정함
public : 전 범위, 주로 외부에 제공할 인터페이스를 공개하기 위해 사용됨
class CircleClass { C2dType center; double radius; public: void init(double cx, double cy, double r) { ... } double area() const { ... } ..... }
05-3. 예제 - Counter 클래스
Counter 클래스
- 행위, 메소드: void reset(), void count(), int getValue()
- 속성: int value
(3) 헤더파일 내용의 중복 include 방지
① 조건부 컴파일
// Counter.h #ifndef COUNTER_H_INCLUDED #define COUNTER_H_INCLUDED class Counter { ....... } #endif
② pragma once
// Counter.h #pragma once class Counter { ....... };
(5) Counter 객체의 정의 및 사용
Counter cnt; cnt.value = 0; // 오류, private 멤버에 직접 접근할 수 없음 cnt.reset(); // value = 0; 으로 만드는 메소드 사용 (간접적 접근, 변경방법) cnt.count(); // value++; 계수기를 1 증가시킴
(6) const 멤버함수
- 데이터 멤버의 값을 수정하지 않는 멤버함수
// Counter.h class Counter { int value; public: int getValue() const { return value; } }
const Counter c; int n = c.getValue(); // OK c.count(); // Error
- ㅇ
// Counter.h class Counter { int value; public: int getValue() { return value; } }
void f(Counter& c) { c.count(); cout << c.getValue(); ..... }
void g(const Counter& c) { cout << c.getValue(); // Error, 객체 값을 바꾸면 안되니까 ..... }
05-4. 생성자
(1) 생성자constructor
- 객체가 생성될 때 수행할 작업을 정의하는 특수한 멤버함수
- 매개변수를 통해 인수를 전달할 수 있으며, 다중정의를 할 수 있음
* 생성자의 특성
- 클래스의 이름을 사용해 선언함
- 생성자 머리에 반환 자료형을 표시하지 않으며, return 명령으로 값을 반환할 수 없음
- 생성자를 public 으로 선언해야 클래스 외부에서 객체를 생성할 수 있음
(2) 생성자의 예 - Counter 클래스
-
(3) 초기화 리스트
- 생성자의 머리에 데이터 멤버를 초기화하는 값들을 나열한 리스트
- '데이터멤버이름{초깃값}' 형태로 초깃값을 지정
class Counter { int value; public: Counter() : value{0} { } // 생성자 ..... }
05-5. 소멸자
(1) 소멸자destructor
- 객체가 소멸될 때 수행할 작업을 정의하는 특수한 멤버함수
*소멸자의 특성
- 클래스의 이름에 `~` 를 붙여 선언함
- 소멸자 머리에 반환 자료형을 표시하지 않으며, return 명령으로 값을 반환할 수 없음
- 매개변수를 포함할 수 없음
- 다중정의할 수 없으며, 클래스에 하나만 정의함
- public 으로 선언하는 것이 일반적
(2) Person 클래스의 명세
// ClassName.h class ClassName { ..... public: ClassName(fParameterList) { ..... // 객체 생성을 위한 준비 작업 } ~ClassName() { // 소멸자 ..... // 객체 제거를 위한 정리 작업 } }
- ㅇ
(3) Person 클래스의 선언 - Person.h, Person.cpp
- ㅇ
#include "Person.h" Person::Person(const char *name, cost char *addr) { this->name = new char[strlen(name)+1]; // 이름을 저장할 공간 할당 strcpy(this->name, name); // 데이터 멤버 name 에 이름을 복사 ........ }
- ㅇ
06 클래스와 객체 (2)
06-1. 디폴트 생성자
- 디폴트 생성자default constructor: 매개변수가 없는 생성자, 묵시적 디폴트 생성자
- 예시) 디폴트 생성자가 없는 클래스
class CounterM { const int maxValue; int value; public: CounterM(int mVal) : maxValue{mVal}, value{0} {} }
int main() { CounterM cnt1(999); // 생성자에 맞게 매개변수를 지정해줘야 함 CounterM cnt2; // Error, 디폴트 생성자가 없음 }
- 디폴트 생성자가 있는 경우, 객체 배열의 선언이 가능함. 동적 메모리 할당으로 배열 할당도 가능함
class Counter { int Value; public: // Counter() {} }
int main() { Counter cntArr[4]; // Ok Counter *pt = new Counter[10]; // Ok, Counter 객체가 10개인 객체 배열 }
- 디폴트 생성자가 없으면, 객체 배열의 선언 시 생성자에 맞게 선언해줘야 함
class CounterM { const int maxValue; int value; public: CounterM(int mVal) : maxValue{mVal}, value{0} {} }
int main() { CounterM cntMArr1[3]; // Error CounterM cntMArr2[3] = { CounterM(9), CounterM(99), CounterM(999) }; // Ok CounterM *pt = new CounterM[10]; // Error }
06-2. 복사 생성자
(1) 복사 생성자의 개념
- 복사 생성자copy constructor: 같은 클래스의 객체를 복사해서 객체를 만드는 생성자
- 묵시적 복사 생성자: 객체의 데이터 멤버들을 그대로 복사해 객체를 만들도록 묵시적으로 정의된 복사 생성자
class CounterM { const int maxValue; int value; public: CounterM(int mVal) : maxValue{mVal}, value{0} {} // CounterM(const CounterM& c) // : maxValue{c.maxValue}, value{c.value} {} }
int main() { CounterM cnt4{99}; CounterM cnt5{cnt4}; // 객체로 객체를 다시 만드는 묵시적 복사 생성자 CounterM cnt6 = cnt4; // 객체를 만들면서 초기화 → 복사 생성자 동작 }
- ㅇ
(2) 얕은 복사의 문제점 - VecF 클래스
class VecF { ~VecF() { delete[] arr; } }
int main() { float a[3] = { 1, 2, 3 }; VecF v1(3, a); VecF v2(v1); // v1 을 복사하여 v2 를 만듦 (복사 생성자를 만들지 않았으므로 묵시적 복사 생성자 동작) ..... return 0; // 함수가 끝나면 자동적으로 소멸자 동작 }
- v1 에서 이미 메모리를 반납했으므로, v2 는 가지지 않은 메모리를 반납 → Error
- 두 객체가 완전히 별개로 분리되게끔 완전한 복사가 이뤄지도록 해야 함
- 복사 생성자를 명시적으로 만든다. 별개의 메모리, 별개의 새로운 객체가 만들어지도록 생성자를 선언한다
class VecF { VecF(const VecF& fv) : n{ fv.n } { arr = new float[n]; memcpy(arr, fv.arr, sizeof(float)*n); } }
- 이렇게 하면, 소멸자가 동작하여도 이상이 없다
06-3. 이동 생성자
(1) 불필요한 복사의 비효율성
- 더하기 연산 후, 임시 객체를 만들어서 계산 후, 그 값을 복사해서 저장하고 하나는 제거하면 비효율적이다
- r-value 참조를 이용한 이동 생성자로 효율 개선이 가능하다
(2) r-value 참조
- l-value 와 r-value
a = b + 10; // 대입 연산자 기준 오른쪽이 l-value / a 에 저장될 값을 제공하는 것이 r-value
- lvalue: 변수/ 포인터가 가리키는 메모리 저장 공간이 있거나, 값을 받아야 한다. 실체가 있는 것 (l-value 참조)
- xvalue: 함수 처리결과, 소멸될 값 (r-value 참조)
- prvalue: 그 외 값, 상수값, 상수 해당 결과를 만드는 수식값, 함수가 리턴하는 값 등 pure value (r-value 참조)
- r-value 참조의 선언
- & 기호로 선언하는 l-value 참조와 달리 r-value 참조는 && 기호로 선언한다
VecF v1(3), v2(3); VecF& vLRef = v1; // l-value 참조로 l-value 를 참조함 int& a = 10; // Error: l-value 참조로 r-value 를 참조할 수 없음 const int& b = 20; // 상수 l-value 참조로는 r-value 를 참조할 수 있음 int&& c = 30; // r-value 는 r-value 참조로 참조할 수 있음 VecF&& vRRef1 = v1.add(v2); // 함수의 반환 객체는 r-value 임 VecF&& vRRef2 = v2; // Error: r-value 참조로 l-value 를 참조할 수 없음
(3) 이동 생성자의 개념
- 이동 생성자move constructor: r-value 참조로 전달된 같은 클래스의 객체 내용을 이동해 객체를 만드는 생성자
class ClassName { ..... public: ClassName(ClassName&& obj) { // 매개변수 const 사용 못함, 자기 내용이 뺏기는 것. 객체의 내용을 갖다써라 ..... // 생성되는 객체에 obj 의 내용을 이동하는 처리 } }
class VecF { VecF(VecF&& fv) : n{ fv.n }, arr{ fv.arr } { // 메모리를 새로 만들지 않고 똑같이 가리키게 함 fv.arr = nullptr; // fv 내용 이동 → 뺏김 fv.n = 0; } }
-
06-4. static 데이터 멤버와 static 멤버함수
'방송대 > 3-2학기' 카테고리의 다른 글
c++ 프로그래밍 정리 (0) 2021.11.29 컴퓨터구조 04 처리장치 (0) 2021.11.19 컴퓨터구조 (0) 2021.11.10 자료구조 03 스택 04 큐 05 연결 리스트 (0) 2021.11.08 선형대수 과목 행렬부분 02~ (0) 2021.10.28