ABOUT ME

Today
Yesterday
Total
  • 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학기' 카테고리의 다른 글

    댓글