행렬 각 원소를 입력 받고 행렬 출력 및 역행렬을 이용한 연립방정식의 해 등을 구하는 함수를 구현해보자.

사전지식:

1. pointer 개념

2. structure 구조와 개념

3. class 구조와 개념

4. friend keyword

5. overator overloading

6. pass by value, pass by reference

 

구조체와 클래스를 이용한 행렬 클래스 만들기 프로젝트는 총 6번에 걸쳐서 발전시킬 것이다. 차차 발전시킬 부분이 처음엔 다소 미흡해 보일 수 있으나 포인팅하여 모호함을 최대한 줄이려고 노력하겠다. 최종본을 원하면 (...url...)

 

앞서 "145. 구조체와 클래스의 비교"에서 우리는 structure와 class가 비슷하지만 다른 점을 살펴 보았다. 그러면 실제로 행렬을 구현하면서 structure와 class의 application에 대해 좀 더 자세히 다뤄보자.

 

structure는 data grouping을 한다고 했고, class는 data grouping에 더불어 member function과 OOP를 구현할 수 있다고 했다.그러면 행렬의 연산은 class를 통해 구현할 수 있고, data grouping은 structure를 통해 구현하여 역할을 명확하게 나눠 표현해볼까한다.

 
struct strMat {
    double **mat;    //pointer to pointer Type to dynamically assign
    int row;
    int col;
    strMat(int _row, int _col, double** _mat)    //constructor
    {
        row = _row;
        col = _col;

        mat = new double*[row];        //dynamic assign
        int rowlen = _col * sizeof(double);    //for memset
        for (int i = 0; row; i++)
        {
            mat[i] = new double[col];    //assign an array in each row element: array of array
            memset(mat[i], 0, rowlen);    //initialize one-dimensioanl array using memset 
            if (_mat) memcpy(mat[i], _mat[i], rowlen);    //copy if get '_mat' argument
        }
    }
    ~strMat()    //delete against memory leak
    {
        for (int i = 0; i < row; i++)
        {
            delete[] mat[i];
        }
        delete[] mat;
    }

};

부담감 없이 보길 바란다. 간단하게 위의 structure 또한 member field, constructor와 destructor로 구성되어 있다. 정말로 structure는 data grouping의 목적으로 쓰였음을 알 수 있다.

*CODE DESCRIPTION:

line3,4,5:

member field에 해당하는 부분이고, 행렬의 데이터 구성요소들이라고 생각하면 된다.

2차원배열(array of array)을 동적으로 할당하기 위해 pointer to pointer type(type**)으로 정의함

line6~19:

constructor 부분. 행렬의 필수 데이터인 row와 column data 그리고 array of array를 받을 수 있는 double pointer(pointer to pointer의 다른 표현)로 객체를 받음.

line7,8:

입력받은 _row, _col로 structure의 member field를 채운다.

우리는 **로 pointer level이 2임을 알 수 있고, mat에 row의 크기를 갖는 double* 타입의 배열을 동적 할당한다.

 (※ double(*)[row]와 구분)

우리는 row 크기를 갖는 array를 갖고 있다. 우리는 array를 index를 통해 접근할 수 있음을 이미 알고 있다.

line12:

memset과 memcpy의 3번 째 argument로 넣어줄 variable을 정의 세 번째 인자는 number of characters로 총 bytes 수를 의미

line13~18:

아까 선언한 row크기를 갖는 array의 각 element에 또 array를 for문을 통한 index로 접근해서 또 array를 정의해주고 초기화     및 대입을 한다. mat[i]로 접근하면서 pointer 마지막 레벨이므로 col의 크기를 갖는 double타입의 array를 정의해준다.

memset으로 초기화하고 만약에 mat object가 들어오면 해당 mat에 있던 데이터들을 그대로 copy한다. (Note. 이때 memset과 memcpy는 1차원 배열 단위로 각각 initialize 하고 copy하는 C-style 함수.)

line20~27. destructor부문. main에서 동적할당이 이루어지고 해제가 모두 이루어졌다면, class에 내에서 동적할당된 메모리는 destructor에서 해제를 정의해주면 된다.

이제 행렬(matrix)에 관한 data definition는 모두 이루어졌다. 이제 class로 무엇을 해야할 지 짐작이 가는가? 행렬에 값만 넣으면 모든 게 끝인가? 당연히 아니다. 우리는 행렬들을 가지고 곱하기 연산, row의 길이와 column의 길이를 얻어오는 연산 등 얼마든지 다양한 기능들을 구현 할 수 있다.  여기서는 row의 길이와 column의 길이를 얻어오고 행렬 특정 element의 값을 출력하는 3개의 기능을 구현해보자. 더불어 <<를 overloading하여 class 객체를 출력해보자( 객체 사이의 기본 연산은 불가능하지만 operator overloading을 이용하면 가능함을 이미 앞서 배웠음).

 
class Matrixs {
private:
    strMat* _matrix;    //declare a structure as a member field
public:
    Matrixs(int row, int col)    //constructor
    {
        _matrix = new strMat(row, col, 0);
    }
    ~Matrixs()                    //destructor
    {
        cout << "Matrix end~" << endl;
        delete _matrix;
    }
    //member function
    int getRow()const { return _matrix->row; }
    int getCol()const { return _matrix->col; }
    double getVal(int row, int col)const
    {
        return _matrix->mat[row][col];
    }
    //operator overloading
    friend ostream& operator << (ostream& ostrm, const Matrixs& m);
};


ostream& operator << (ostream& ostrm, const Matrixs& m)    //operator overloading
{
    for (int i = 0; i < m._matrix->row; i++)
    {
        for (int j = 0; j < m._matrix->col; j++)
        {
            ostrm << m._matrix->mat[i][j] << "\t";    //array of array structure can be assigned with indices
        }
        ostrm << endl;
    }
    return ostrm;
}

 *CODE DESCRIPTION:

line3:

우리는 structure를 통해 data grouping을 모두 끝냈다. 그렇다면 더이상의 data grouping은 할 필요없기때문에 class가

그 structure를 그대로 갖길 원한다. 따라서 strMat type의 object를 member field로 선언해주면 되는데, 여기서 이 strMat

을 동적으로 생성하기 위해 *가 필요하다.

line6~9: class constructor 부분.

parameter로 row, col 값을 요구. 구조체 strMat을 argument로 받은 값을 가지고 strMat 타입의 object를 동적으로(heap memory영역에) 할당. strMat의 constructor에 정의된 대로 class의 constructor를 통해 받은 row, col 값을 그대로 대입. 그리고 세번째 argument로 0은 strMat의 세번째 parmamet인 double** _mat으로 들어가게 되며, pointer 이든 pointer to pointer든 주소값을 가지며, 주소값이 0을 의미한다. 아무런 Matrix객체가 들어오지 않음을 의미한다. strMat의 constructor 부분 맨 마지막 부분의 if(_mat)분기에 접근하지 못한다. 아무런 객체가 들어오지 않았을때 strMat에서는 바로위에 memset으로 먼저 항상 초기화하여 문자가 발생하지 않도록 하였다. (<<if분기로 빠지는 부분은 우리가 앞으로 발전시켜야할 부분이다.)

line9~13: class destructor 부분.

line22,26~37: <<operator overloading.              (링크 필요)

<< 으로 Matrix class object도 출력하게 하기 위함.  

int main()
{

    Matrixs *mat = new Matrixs(10,5 );    //create Matrixs object dynamically assigned
    cout << *mat << endl;
    cout << "--------------------------------" << endl;
    Matrixs mat2(10,5);                    //create Matrixs object statically assigned
    cout << mat2 << endl;
    delete mat;
}
 

 *CODE DESCRIPTION:

line4: Matrixs object를 동적할당

line5: 우리는 << operator overloading 을 통해 class 객체를 출력할 수 있도록 하였고 pointer 변수가 가리키는 object를 출력

line7: 이번엔 class 객체를 정적 할당

line8: line5와 같은 이유로 출력 가능하고, 출력

line9: line4에서 동적할당한 객체를 memory leak를 방지하기 위해 memory 할당 해제(memory deallocate)

Q. main에서 두 개의 object를 생성했다. 하나는 동적할당 또 하난 정적할당이다. 어떤 object가 먼저 memory deallocate를 할까?

 

 

Concept1. Pointers to Pointers

일반적으로, type modifier(* or &)가 declarator에 적용되는 횟수에는 제한이 없다.

(declarator: The part of a declaration that includes the name being defined and an optional type modifier)

 

하나 이상의 modifier가 있을 때, 그것들은 논리적인 방식으로 합쳐지지만 항상 분명하진 않다.

하나의 예로, a pointer를 생각해보자. a pointer는 memory 안에 있는 하나의 object이다. 그래서 여느 object처럼 a pointer 또한 주소를 가지고 있다. 그러므로 우리는 다른 pointer to pointer의 주소를 저장할 수 있수 있게 된다.

 

*로 각 pointer level을 가리키게 된다. 즉, 우리는 pointer to a pointer를 **로 쓰고, pointer to a pointer to a pointer를 *** 써서 제한없이 쓸 수 있다. 

(array of array에 대한 pointer 구조 그림 추가)

 structure(구조체)와 class는 무엇이 다를까? 먼저 정의하는 구조는 비슷하다.

structure는 member field, constructor(생성자), destructor(소멸자) 이 세 가지로 구성된다.

class는 위의 구조체 성분에 member fuction과 OOP(Object-Oriented Programming) 개념이 추가적으로 들어있다.

즉, class는 structure의 구성을 포함하는 개념이다.

(각 구성성분에 대한 설명은 ...참고)

 

structure의 member field가 외부에 쉽게 노출되어 사용하기 편하고 속도도 빠르나, 데이터 보호에는 취약하다.

structure는 중요 데이터를 묶는 data grouping 역할을 한다.

클래스는 data grouping 외에도 중요 데이터를 보호(information hiding)하고, OOP을 할 수 있게 한다.

 

 

 

예를 들어서 설명해보자. 학생에 대한 정보를 담는 구조체를 정의해보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct Student {
    char name[50];
    int age;
 
    Student(char* _name, int _age) //constructor -초기화
    {
        strcpy_s(name, _name);
        age = _age;
    }
    ~Student()                    //destructor
    {
        cout << "Die!" << endl;
    }
};
cs

 

 

 

 *CODE DESCRIPTION:

1. 이름이 Student인 structure 생성

2. char형 배열 크기 50을 갖는 이름이 name인 변수 선언 (50byte)

3. 나이 data를 저장하는 이름이 age인 변수 선언(4byte)              member field - data grouping에 해당하는 부분       

5. constructor는 structure의 member field를 초기화 해주는 기능을 한다.

7. strcpy_s는 c-style 문자열(char [] 타입)에 복사해주는 함수이다.

이 함수의 내부는 우리가 배열을 copy할 때 for문을 직접 작성하는 수고로움을 덜어준다.

6. destructor

14. structure 생성 시 {} 뒤에 반드시 ;(semi-colon)을 붙여줘야 한다.

 

 

위에서 우리는 class는 structure를 포함하는 개념이라고 했다.

그러면 class로 이것을 표현하고 부가적으로 어떤 기능들을 추가할 수 있는지 코드를 통해 살펴보자.

 

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
29
30
31
32
33
34
35
36
class Student {
private:
    char name[50];
    int age;
public:
    Student(char* _name, int _age); //constructor -초기화
    ~Student();                    //destructor
 
    void change(char* _name, int _age);    //특정 structure object의 member field 변경하는 함수
    void change(Student &s);    //객체 주소를 받아(주소에 의한 전달) 값 변경
    void change(Student s);    //객체 copy본을 받아(값에 의한 전달) 값 변경
};
Student::Student(char* _name, int _age) //constructor -초기화
{
    strcpy_s(name, _name);
    age = _age;
}
Student::~Student()                        //destructor
{
    cout << "Die!" << endl;
}
void Student::change(char* _name, int _age)    //특정 structure object의 member field 변경하는 함수
{
    strcpy_s(name, _name);
    age = _age;
}
void Student::change(Student &s)    //객체 주소를 받아(주소에 의한 전달) 값 변경
{
    strcpy_s(s.name,name);
    age = s.age;
}
void Student::change(Student s)        //객체 copy본을 받아(값에 의한 전달) 값 변경
{
    strcpy_s(s.name,name);
    age = s.age;
}
cs

 

 

 

 

 

어떤 것이 달라졌는가?

첫번째, member field를 private으로 선언하여 class 내부에서만 보이도록한다.

이것을 information hiding(정보보호) 또는 encapsulation(은닉화)라고 한다.

은닉화된 class는 외부에서 안의 데이터를 알 수 없다.

 

두번째, member function을 정의할 수 있다.

(여기서는 declaration과 definition을 나누었는데, 클래스는 이렇게 따로 분리할 땐 CLASS_NAME:: 을 추가해야됨을 잊지 말자.)

그래서 우리는 class와 관련된 기능을 수행할 수 있다. 예를 들어 Banking이라는 class에는 withdrawal이라는 기능(member function)을 구현할 수 있게 된다. 이렇게 되면 그냥 main에서 함수를 정의해서 사용하는 것보다 구조와 기능들이 명료해짐을 느낄 수 있다.

 

 *CODE DESCRIPTION:

27. 우리는 객체를 받아와서 class와 관련된 기능을 수행할 수 있다. 외부로 부터 객체(&를 이용)를 argument로 입력해주면

외부로부터 넘겨받은 객체의 data를 변경하는 함수이다. 이것은 단지 "주소에 의한 전달"개념을 짚고 넘어가기 위한 함수이므로

내부 기능은 큰 의미가 없다.

 

32. overloaded function(parameter의 type 또는 parameter의 수로 구분)이다. line27과 기능은 같다. 단지 주소로 받지 않고

객체의 copy본을 받았기 때문에 값을 변경한다해도 s자체는 함수를 시작하면서 생성된 object이고 이것은 이 함수가 끝나면서 소멸된다. 이 함수는 단지 "값에 의한 전달"개념을 짚고 넘어가기 위한 것이므로 프로젝트의 목적과 기능에 따라 line27 함수와 구분하여 사용하면 된다.

 

그렇다면 structure는 그 structure와 관련된 함수를 전혀 수행을 못 하는 것인가?

다음 코드를 살펴보자.

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
29
30
struct Student {
    char name[50];
    int age;
 
    Student(char* _name, int _age) //constructor -초기화
    {
        strcpy_s(name, _name);
        age = _age;
    }
    ~Student()                    //destructor
    {
        cout << "Die!" << endl;
    }
};
Student change(char* _name, int _age)    //특정 structure object의 member field 변경하는 함수
{
    Student s(_name, _age);
    return s;
}
void change(Student &s, char* _name, int _age)    //객체 주소를 받아(주소에 의한 전달) 값 변경
{
    strcpy_s(s.name, _name);
    s.age = _age;
}
void change(Student s,char* _name, int _age)        //객체 copy본을 받아(값에 의한 전달) 값 변경
{
    strcpy_s(s.name, _name);
    s.age = _age;
}
 
cs

 

 

 

멤버함수들이 아닌 일반함수로 그 기능을 대체할 수 있다. 그러므로 CLASS_NAME::도 빠지게 되었다.

 

일반함수로 바뀌었기 때문에 class와 달리 객체와 연관된 member field와 member function의 연산을 바로 수행 할 수 없다.

(ex. Student S1을 선언하고 S1.change를 이용해 S1의 data와의 직접 연산을 할 수 없는 점)

 

따라서 line20, 25처럼 object를 받고 따로 데이터도 받아서 값을 바꿔주거나 line20 처럼 함수 내에서 object를 생성한 후

ojbect를 return하여 다른 object에 assign(할당)하는 방법 등을 생각해 볼 수 있다.

(Note. structure type도 fundamental type처럼 structure object에 assign이 가능하다는 점)

 

 

 

/*****************************************************************************************

Full Source Code: https://github.com/devgraphy/Cpp-200/tree/master/145

 

 

조언과 틀린 부분에 대해선 언제든지 지적부탁드리며, 보충설명이 필요한 부분에 대해 댓글을

남기시면 업데이트하거나 댓글 달아드리겠습니다.

 

매 포스팅과 source code는 추가적으로 업데이트 될 수 있습니다.

*****************************************************************************************/

+ Recent posts