배움 저장소

[홍정모의 따라하며 배우는 C++] 6. 행렬, 문자열, 포인터, 참조 본문

Programming Language/C++

[홍정모의 따라하며 배우는 C++] 6. 행렬, 문자열, 포인터, 참조

시옷지읏 2021. 12. 19. 00:08

6.1 배열 기초 [1 of 2] array


배열 이름 뒤에 붙는 [ ]는 deferencing이다. 배열의 자료형 크기 * 해당 index 자리에 있는 값을 읽는다  

 

c++11은 배열의 uniform initialization을 지원한다.

int arr[]{ 1, 2, 3, 4, 5 };

 

배열의 Index에 Enum 열거형을 사용할 수 있다

- 열거형의 마지막 변수를 COUNT로 쓰면 편리하다.

enum StudentName
{
    JACK,
    ALI,
    VIOLET,
    NUM_STUDENT,
};

int main()
{
    // c++ 11 allow array's uniform initialization
    int arr[NUM_STUDENT]{ 0, 1, 2 };

    arr[JACK];
    arr[ALI];
    arr[VIOLET];
}

 

배열의 크기는 Compile time에 결정되어야 한다

- 배열의 크기는 runtime constant로 정할 수 없다. 따라서 배열의 정의도 Compile time에 결정되어야 한다.

const int count = 5;
int student_count[count];

 

배열을 함수의 Parameter로 지정하여도 내부적으로는 포인터로 변환하여 사용한다

 참고) - 10.7 배열을 함수에게 전달해주는 방법 https://hoplite.tistory.com/75

 

홍정모의 따라하며 배우는 C언어 10.배열과 포인터

10.1 배열과 메모리 - 배열의 인덱스는 (해당 값의 주소 - 첫 값의 주소) / sizeof(자료형)과 같다 - 배열의 이름은 첫 값의 주소를 가리킨다. 따라서 Ampersand(주소 연산자) 없이도 포인터에 할당할 수

hoplite.tistory.com

 

6.3 배열과 반복문

 

6.4 배열과 선택 정렬 selection sort


Selection Sort

void printArray(const int arr[], const int length)
{
    for (int i = 0; i < length; ++i)
        cout << arr[i] << " ";
    cout << endl;
}
int main()
{
    const int length = 5;
    int arr[length] = { 3, 5, 2, 1, 4 };
    printArray(arr, 5);

    for (int i = 0; i < length-1; ++i)
    {
        int min_i = i;
        for (int j = i+1; j < length; ++j)
        {
            if (arr[j] < arr[min_i])
                min_i = j;
        }
        int temp = arr[min_i];
        arr[min_i] = arr[i];
        arr[i] = temp;
    }
    printArray(arr, 5);
}

 

 

Bubble Sort

void printArray(const int arr[], const int length)
{
    for (int i = 0; i < length; ++i)
        cout << arr[i] << " ";
    cout << endl;
}

int main()
{
    const int length = 7;
    int arr[length] = { 64, 34, 25, 12, 22, 11, 90 };
    printArray(arr, length);

    for (int end = length; 0 <= end; --end)
    {
        bool isSorted = true;
        for (int i = 0; i < end-1; ++i)
        {
            if (arr[i] > arr[i + 1])
            {
                int temp = arr[i];
                arr[i] = arr[i+1];
                arr[i+1] = temp;
                
                isSorted = false;
            }
        }
        if(isSorted) break;
    }
    printArray(arr, length);
}

 

6.5 정적 다차원 배열

 

6.6 C언어 스타일의 배열 문자열


참고 https://hoplite.tistory.com/76

 

홍정모의 따라하며 배우는 C언어 11 문자열 함수

11.1 문자열을 정의하는 방법 'H' 'e' 'l' 'l' 'o' '\0' ? ? ? 특정 컴파일러는 비어있는 공간을 '\0'으로 채워준다. Put( ) 함수의 활용법 #define MESSAGE "A Symbolic string constant" #define MAXLENGTH 81 p..

hoplite.tistory.com

 

cin >> 을 사용하면 space, enter 직전까지 값을 받아들이고 나머지는 buffer에 저장한다

 

cin.getline( )를 이용하여 enter(next line)까지 값을 입력받아보자.

#include <iostream>
using namespace std;

int main()
{
    char str[255];
    cin.getline(str, 255);
    cout << str << endl;
}

 

C스타일 문자배열 함수를 사용하려면 <cstring>라이브러리를 포함하자.

- strcpy함수는 depreciated(오래되어 사용하지 않는다). 복사하려는 저장공간이 부족할 때 에러를 만든다.

- 참고) "11.5 ~11.6", "11.11" https://hoplite.tistory.com/76

#define _CRT_SECURE_NO_WARNINGS

#include <iostream>
#include <cstring>
using namespace std;

int main()
{
    char source[] = "Copy this!";
    char desti[50];
    //strcpy(desti, source);
    strcpy_s(desti, static_cast<size_t>(50), source);
    cout << desti << endl;
}

 

6.7 포인터의 기본적인 사용법


같은 자료형이라면 여러 변수를 선언/초기화할 수 있지만 포인터는 예외이다

- 자료형은 생략가능하지만 매 변수 앞에 Asterisk(*)를 붙여주어야 한다.

- typedef를 사용하면 이러한 수고를 덜 수 있다. 2중/3중 포인터를 만들 때 자주 사용한다.

#include <iostream>
using namespace std;

int main()
{
    int x;
    //int* ptr_1 = &x, ptr_2 = &x; // Error!, ptr_2 needs asterisk

    typedef int* int_ptr_t;
    int_ptr_t ptr1 = &x, ptr_2 = &x;
}

 

포인터에 자료형이 필요한 이유

- 주소 값 자체는 자료형을 구분하지 않으나 dereferencing을 했을 때 어떤 자료형으로 가져올지를 알아야한다.

- 배열을 가리키는 포인터를 사용하려면 각 element를 읽기 위해 자료형 크기를 알아야한다. 배열의 시작 주소값에서 자료형크기 * index 주소로 이동하여 해당 index 값을 가져온다.

 

6.7-2 널 포인터 Null Pointer


nullptr은 주소 0을 가리킨다.

int* ptr = nullptr;
cout << ptr << endl;
>> 00000000

 

nullptr은 여러 방식으로 초기화 할 수 있다. NULL과 0을 대신해서 쓰기도 한다

void checkPointer(double* ptr)
{
    if (ptr == nullptr)
    {
        cout << "function \"" << __FUNCTION__ << "\" take nullptr!!" << endl;
        return;
    }
}

int main()
{
    double *ptr = nullptr;   // Modern C++ sytle
    double* ptr2{nullptr};
    double* ptr3(nullptr);

    double *ptr_NULL = NULL;
    double *ptr_ZERO = 0;

    checkPointer(nullptr);
}
>> function "checkPointer" take nullptr!!

 

nullptr 자료형 사용하기 <cstddef>

- nullptr_t 자료형은 nullptr 값만 가지는 자료형이다. 자료형이 nullptr 값만 가지는 Parameter에 사용한다.

#include <cstddef>
using namespace std;

std::nullptr_t nptr;

 

6.8 포인터와 정적 배열


배열과 포인터는 주소값을 저장하고 있다

포인터 vs 배열

(1) sizeof( ) 출력값이 다르다

(2) pointer는 4byte로 크기가 고정되어 있다.

(3) array는 (해당 자료형 크기 * 배열의 크기) 값을 출력한다.

int main()
{
   int arr[5] = {0, };
   int* ptr = arr;

   cout << sizeof(arr) << endl; // >> 20
   cout << sizeof(ptr) << endl; // >> 4
}

함수의 Parameter로 전달된 배열은 Pointer로 변환된다

 

포인터의 활용: (*) Indirect를 사용하여  원본 값 변경하기

- C-style은 주소값에서 indirect하면 값을 변경할 수 있다. "Asterisk( * ) + 주소값" 형식으로 사용하면 된다.

- C++에서도 사용가능하나 Ampersand( & )를 더 많이 사용한다. 이후 강의에서 다룬다.

 

6.9 포인터 연산과 배열 인덱싱


16진수 주소값을 10진수로 변환하기

- int( ) 혹은 (int)을 사용하여 주소값을 10진수로 변형할 수 있다.

- uintptr_t( )을 사용해보자. uintptr_t는 포인터 크기만큼만(4byte) 주소값을 unsigned int로 변환하여 저장한다.

cout << uintptr_t(ptr) << endl;

 

참고) 10.3 포인터의 산술 연산( Pointer Arithmetic ) https://hoplite.tistory.com/75

 

홍정모의 따라하며 배우는 C언어 10.배열과 포인터

10.1 배열과 메모리 - 배열의 인덱스는 (해당 값의 주소 - 첫 값의 주소) / sizeof(자료형)과 같다 - 배열의 이름은 첫 값의 주소를 가리킨다. 따라서 Ampersand(주소 연산자) 없이도 포인터에 할당할 수

hoplite.tistory.com

 

Virtual Memory와 Physical Memory

(1) 프로그램의 실행을 process라 부른다. process는 서로 독립적인 virtual memory를 사용한다.
(2) memory는 virtual memory와 physical memory로 구분한다.
  - physical memory(실제 DRAM 상의 메모리)이다.
  - virtual memory는 프로그램상 표시되는 가상의 메모리로 physical memory를 이어붙여 연속적인 주소값을 만든다.

(3) physical memory와 virtual memory 사이에서 두 memory address를 translation을 해주는 page table이 존재한다.
(4) table이 virtual memory 와 physical memory 사이의 관계를 형성해주고 각 process 내에서는 서로 독립적인

    virtual memory 공간을 만든다. 이 때문에 크기가 거대한 데이터를 연결해도 연속된 것처럼 나타난다.

 

6.10 C언어 스타일의 문자열 심볼릭 상수


참고) text segment: 11.2 메모리 레이아웃과 문자열 - https://hoplite.tistory.com/76

 

C는 string literal 주소값을 Pointer에 저장할 수 있다. C++은 이를 금지한다

- C에서 아래 예제를 실행시켜보자. C는 text segment에 접근할 수 있다.

// C
char *test = "is this work in C?";
printf("%s", test);
is this work in C?

 

아래 예제는 C++에서 실행하였다. C++는 text segment에 접근을 막아놓았다.

// C++
char* test = "is this work in C++?";
cout << test << endl;

 

const 상수로 지정해주면 text segment에 접근할 수 있다. 상수이므로 에러 발생을 예방해준다.

// C++
const char* test = "is this work in C++?";
cout << test << endl;

 

COUT이 문자열을 출력하는 방법

- cout은 다양한 자료형을 출력할 수 있다. 그 중에 문자열은 다른 자료형과 다른 방식으로 처리된다.

- 배열을 출력하면 주소값이 나와야 하지만 문자열은 null character('\n')를 만나기 전까지 값을 출력한다.

- 이는 string 자료형이 아닌 c-style 문자열에만 해당한다. c++ string은 null chracter를 가지고있지 않다.

int arr_i[5] = {0,};
char arr_char[] = "Hello there?";
const char* ptr = "is this work in C++?";

cout << arr_i << endl;    // >> 00AFFC00
cout << arr_char << endl; // >> Hello there ?
cout << ptr  << endl;     // >> is this work in C++ ?

  

COUT이 자료형이 문자 주소일 때만 특별히 작동함을 확인해보자.

- 자료형이 문자 주소일 때 cout이 문자열을 출력함을 알 수 있다. null character('\0')까지 문자를 출력한다.

char test = 'A';
cout << &test << endl; // >> A儆儆윕쭄?

 

주소를 출력하고싶다면 형변환을 해보자

cout << static_cast<void*>(&test) << endl;
cout << (void*)(&test) << endl;

 

6.11 메모리 동적 할당 new와 delete


      static memory allocation : 프로그램이 끝날 때까지 저장공간을 할당받는다.

automatic memory allocation : Scope 내부에서 저장공간을 할당받고 반환한다.

                                          컴파일 타임에 얼마만큼 저장공간을 할당받을지 정해져 있다.

                                          메모리 레이아웃에서 Stack을 활용한다. stack의 저장공간은 heap보다 작다.

Dynamic Memory allocation : 필요한 만큼의 저장공간을 힙에서 할당받는다.

                                         컴파일 타임에 할당받을 저장공간이 정해져있지 않다.

                                         Stack보다 많은 공간을 할당받을 수 있다.

참고 12.1 메모리레이아웃 - https://hoplite.tistory.com/78

 

메모리의 동적 할당 예제

int *ptr = new int{7};

cout << ptr << endl;
cout << *ptr << endl;

delete ptr;
ptr = nullptr;

- 프로그램이 종료되면 메모리를 해제해주지 않아도 자동으로 해제가 된다. OS가 할당된 메모리의 주소를 간직하고 있기 때문에 가능하다.

- Debug 모드에서 delete ptr이후에 ptr 값은 00008123이 입력된다. 디버그모드에서 메모리가 해제되었음을 가리키는 주소값이다.

 

 

메모리 해제가된 주소에서 값을 불러오면 쓰레기 값을 가져오거나 에러가 발생할 수 있다

- 동적할당 메모리를 사용할 때 항상 조건문으로 nullptr인지 확인해보자.

- smart pointer를 사용하면 간단하게 해결할 수 있다.

if (ptr != nullptr)
{
    cout << ptr << endl;
    cout << *ptr << endl;
}

 

동적 메모리 할당시 에러 무시하기

- 동적 메모리할당에 실패하면 에러가 발생하며 프로그램이 종료된다. 이를 원하지 않으면 std::nothrow를 활용하자

- std::nothrow를 활용하면 메모리 할당 실패시 에러를 발생시키지 않는다. ptr에 nullptr이 입력된다.

int *ptr = new (std::nothrow) int{7};

 

Memory Leak(메모리 누수)

- 아래 예제는 메모리 누수를 발생시킨다.

while (true)
{
    int *ptr = new int;
}

- 아래 예제는 메모리 해제로 누수가 발생하지않는다.

while (true)
{
    int *ptr = new int;
    delete ptr;
}

 

6.12 동적 할당 배열


동적할당을 사용하면 런타임에 배열의 크기를 결정할 수 있다.

- 배열의 크기는 compile time에 결정되어야 하므로 배열은 항상 compile time에 정의되어야 했다. 동적할당은 가능하다

- 배열 메모리를 해제하려면 delete [ ] 를 사용하자. 

int length;
cin >> length;

int *arr = new int[length];

for (int i = 0; i < length; ++i)
{
    arr[i] = i;
    cout << arr[i] << " " << &arr[i] << endl;
}

delete [] arr;
arr = nullptr;

arr포인터를 더이상 사용하지 않으면 nullptr값을 할당해주지 않아도 된다.

 

아래와 같이 동적배열을 할당하면 모든 값을 0으로 초기화한다

int *arr = new int[length]();
int *arr = new int[length]{};

 

uniform initialization을 사용하여 정한 값만큼 초기화할 수 있다

- 할당받은 저장공간보다 많은 값을 입력받으면 에러가 발생한다. 할당받지 않은 공간에 값을 저장했기 때문이다.

int *arr = new int[length]{ 10, 11, 12, 13 };

 

동적할당 메모리 크기 확인해보기

- "_msize( )" 함수를 사용하면 힙에서 할당받은 메모리 크기를 확인할 수 있다.

size_t _msize(void* _Block);

 

6.13 포인터와 const


포인터에 const keyword를 사용하여 주소값을 상수로 만들거나 해당주소에 저장된 값을 상수로 만들 수 있다.

- 참고) 10.15 포인터의 호환성-상수 keyword const활용 https://hoplite.tistory.com/75

int value =5;
const int *ptr1 = &value;      // fix value
int *const ptr2 = &value;      // fix address
const int *const ptr3 = &value;// fix both value and ad

 

6.14 참조 변수 reference variable


참조변수는 주소값을 다룰 수 있게 하는 연산자로 pointer보다 사용하기 쉽다

- 변수명 + Ampersand( & )를 사용하여 referenced 변수를 만들 수 있다

- 참조하는 변수의 별명으로 사용할 수 있다. 이름은 다르지만 같은 주소값을 가진다.

int value =5;
int *ptr = &value;
int &ref = value;

cout << "&value " << &value << endl; // >> &value 0073F8B0
cout << "  &ptr " << &ptr << endl;   // >>   &ptr 0073F8A4
cout << "   ptr " << ptr << endl;    // >>    ptr 0073F8B0
cout << "  &ref " << &ref << endl;   // >>   &ref 0073F8B0
cout << "   ref " << ref << endl;    // >>    ref 5

위 예제에서 &ref 값과 &value 값이 동일함을 확인할 수 있다.

 

reference 참조의 특징

(1) 참조 vs 포인터

- 포인터: 주소값을 저장하고 자기 자신은 별도의 주소를 가지고 있다

            여러 레벨의 간접참조를 할 수 있다.

            포인터연산, 널포인터가 필요할 때 사용한다

-   참조 : 참조하고 있는 변수의 주소값 자신의 주소값이 동일하다. (참조는 내부적으로 포인터로 구현되었다)

            한 레밸의 간접참조만 할 수 있다.

            매개변수와 리턴값으로 자주 쓰인다

 

(2) reference(참조)는 선언하여 사용할 수 없다. 반드시 초기화해야 한다. 초기화 이후 다른 값을 참조할 수 없다.

- 참조하고 있는 주소에서 값을 바꿀 수는 있지만 참조 되는 주소를 바꿀 수 없다.

int x = 10;
int y = 20;
int &ref1 = x;

cout << "x = " << x << " " << &x << endl; // >> x = 10 006FFE34
cout << "y = " << y << " " << &y << endl; // >> y = 20 006FFE28
    
cout << ref1 << " " << &ref1 << endl; // >> 10 006FFE34
ref1 = y;
cout << ref1 << " " << &ref1 << endl; // >> 20 006FFE34
&ref1 = y; // Error!

 

(3) R-value는 참조 변수의 값으로 사용할 수 없다.

int &ref = 123; // Error!

단 const(상수) reference는 R-value를 참조할 수 있다

- 이를 활용하면 함수의 argument에서 literal 값을 받을 수 있다. 6.15 참조와 const에서 다룬다

const int &ref = 123;

 

(4) reference variable은 참조하는 값이 상수이면 자기 자신도 상수여야 한다.

- 아래 예제를 보자 reference variable은 해당 값을 바꿀 수 있기 때문에 C++은 이 문법을 금지하고 있다

const int const_i = 10;
int &ref = const_i; // Error!

따라서 상수를 참조하려면 reference variable도 const로 지정해야 한다

const int const_i = 10;
const int &ref = const_i;

일반 변수를 상수로 참조할 수 있다

int const_i = 10;
const int& ref = const_i;

 

(5) reference variable은 null값을 가질 수 없다

 

함수의 Parameter에서 reference variable(참조변수) 활용하기

- 포인터는 저장공간을 할당받아 argument의 주소값을 복사한다. reference variable은 argument 주소값을 자신의 주소로 가져온다. 저장공간을 아끼고 속도도 더 빠르다.

void takeRefPara(int& x)
{
    cout << __FUNCTION__ << " add = " << &x << endl; // >> main add = 005AF958
    cout << __FUNCTION__ << " val = " << x << endl;  // >> main val = 0
    x = 10;
}

int main()
{
    int x = 0;
    cout << __FUNCTION__ << " add = " << &x << endl; // >> takeRefPara add = 005AF958
    cout << __FUNCTION__ << " val = " << x << endl;  // >> takeRefPara val = 0

    takeRefPara(x);
}

 

함수의 배열 Parameter를 Reference variable(참조 변수)로 활용하기

- 아래 코드를 보자 int [5]배열을 대신사용할 수 있는 참조 변수이다. 매개변수로 활용한다.

int (&arr)[5]
void takeRefParaArr(int (&arr)[5])
{
    for (int i = 0; i < 5; ++i)
        cout << arr[i] << endl;
    cout << endl;
}

int main()
{
    const int length = 5;
    int arr[length] = { 1, 2, 3, 4, 5 };
    takeRefParaArr(arr);
}

 

복잡한 구성의 변수를 간단하게 부르는 예

- 아래 예제는 구조체 내부에 있는 구조체 멤버변수에 접근하기 위하여 dot( . ) operator를 여러번 활용하고 있음을 알 수 있다. 이 때 reference variable을 이용하면 원하는 멤버변수를 쉽게 사용할 수 있다.

struct Something
{
    int v1;
    float v2;
};

struct Other
{
    Something sth;
};

int main()
{
    Other one;
    one.sth.v1 = 1;
}

reference variable을 이용해 쉽게 해당 값에 접근하였다

int &v1 = one.sth.v1;
v1 = 1;

 

6.15 참조와 const


const 참조 사용해보기

const int x = 5;
const int &ref_x = x;
const int &ref_ref = ref_x;

int y = 5;
const int& ref_y = y;
const int& ref_refy = ref_y;

 

함수가 const상수 reference variable을 parameter(매개변수)로 활용하면 literal(고정된) 값을 사용할 수 있다

-   일반 reference variable: L-value만 참조할 수 있다.

- const reference variable: L-value와 R-value 모두 참조할 수 있다.

const int &ref = 3+4; // Work!
int &ref = 3+4;       // Error!

 

const reference variable이 없었다면 Argument에 임시값을 넣기 불가능하다

void takeRefPara(const int& x)
{
    cout << x << endl;
}

int main()
{
    int x = 3;
    takeRefPara(x);
    takeRefPara(3);
    takeRefPara(x+3);
    takeRefPara(x * 3);
}

 

6.16 포인터와 참조의 멤버 선택


포인터나 참조를 이용하여 특정 struct나 class의 멤버변수를 불러올 수 있다. 다음 예제를 보자.

struct Person
{
    int age;
    double weight;
};

int main()
{
    Person person;
    person.age = 10;
    person.weight = 30;

    Person &ref = person;
    ref.age = 15;
    ref.weight = 50;

    Person *ptr = &person;
    ptr->age = 20;
    (*ptr).weight = 70;

    Person &ref2 = *ptr;
    ref2.age = 25;
    ref2.weight = 80;
}

 

6.17 C++11 For-each 반복문


for 반복문을 간단하게 사용할 수 있는 for-each 반복문이다

- 동적할당으로 만든 배열에서 for-each 반복문을 사용할 수 없다. 정적 배열에서 사용가능하다.

int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };

for(auto& num :fibonacci)
    num *= 10;

for(const auto& num : fibonacci)
    cout << num << " ";
cout << endl;

 

std:vector는 동적할당으로 저장공간을 받는다. 내부에 for-each를 사용할 수 있도록 구현되었다

std::vector<int> v_fibonacci = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };

for(auto& num : v_fibonacci)
    num *= 10;

for(const auto& num : v_fibonacci)
    cout << num << " ";
cout << endl;

 

6.18 보이드 포인터


보이드 포인터는 generic 포인터라고도 부른다. 모든 주소값은 보이드 포인터로 저장할 수 있다

int   i = 5;
float f = 3.0;
char  c = 'a';

void *ptr = nullptr;

ptr = &i; // OK
ptr = &f; // OK
ptr = &c; // OK

보이드 포인터는 포인터 연산자를 사용할 수 없다. indirect되었을 때 자료형 크기를 확인할 수 없기 때문이다.

cout << ptr + 1 << endl; // Error!

dereference 연산도 불가능하다.

cout << *ptr << endl; // Error!

자료형이 정해지도록 casting을 하면 dereference 연산이 가능하다.

cout << *static_cast<char*>(ptr) << endl;

 

6.19 다중 포인터와 동적 다차원 배열


참고) 10.12 2중 포인터 작동원리 - https://hoplite.tistory.com/75

 

아래는 정적배열이다. 아래 배열을 동적으로 할당해보자

const int arr2d[row][col] ={
    {1,  2,  3,  4,  5 },
    {6,  7,  8,  9,  10},
    {11, 12, 13, 14, 15}
};

 

(1) 더블포인터 -> 포인터배열 { 배열1 포인터, 배열2 포인터, ... }

- 모든 배열 포인터와 더블포인터를 해제해야하므로 번거롭다.

const int row = 3;
const int col = 5;

int *r1 = new int[col]{1,  2,  3,  4,  5 };
int *r2 = new int[col]{6,  7,  8,  9,  10};
int *r3 = new int[col]{11, 12, 13, 14, 15};

int **rows = new int*[row]{ r1, r2, r3 };

for (int r = 0; r < row; ++r)
{
    for (int c = 0; c < col; ++c)
        cout << rows[r][c] << " ";
    cout << endl;
}

delete[] r1;
delete[] r2;
delete[] r3;
delete[] rows;

 

(2) for 반복문으로 더블포인터 -> 포인터배열 { 배열1 포인터, ... } 구현하기

const int row = 3;
const int col = 5;

// double pointer pointing to dynamic allocated 
// memory that is for array of pointer
int** matrix = new int* [row];

// each pointer pointing to array of int
for (int r = 0; r < row; ++r)
    matrix[r] = new int[col];

// assign value
for (int r = 0; r < row; ++r)
    for (int c = 0; c < col; ++c)
        matrix[r][c] = 10*r + c;

// print value
for (int r = 0; r < row; ++r)
{
    for (int c = 0; c < col; ++c)
        cout << matrix[r][c] << " ";
    cout << endl;
}

// free dynamic allocated memory
// free each array of pointer's elem
for(int r=0; r<row; ++r)
    delete[] matrix[r];

// after freeing every array
// freeing double pointer
delete[] matrix;

 

(3) 1차원 배열로 2차원 배열표현하기 포인터 -> 배열(배열의 크기는 행*열)

const int row = 3;
const int col = 5;

int* matrix = new int [row*col];

// assign value
for (int r = 0; r < row; ++r)
    for (int c = 0; c < col; ++c)
        matrix[r*col + c] = 10*r + c;

// print value
for (int r = 0; r < row; ++r)
{
    for (int c = 0; c < col; ++c)
        cout << matrix[r*col + c] << " ";
    cout << endl;
}

// freeing pointer
delete[] matrix;

 

6.20 std::array 소개


std::array는 정적 배열을 클래스에 구현하였다. 여러가지 기능이 구현되어있다.

 

다음은 standard library Array를 사용하는 방법이다. <array>를 포함시키자

- array<자료형, 크기> 변수명으로 초기화한다. 초기화 이후에도 여러 값을 동시에 할당할 수 있어 편하다.

std::array<int, 5> arr = { 1, 2, 3, 4, 5 };
arr = { 10, 11, 12, };

멤버함수 size( ): array의 크기를 반환한다.

cout << arr.size() << endl;

멤버함수 at( index ): 해당 index의 저장공간에 접근가능한지 검사한다. 접근가능하면 값을 반환한다

접근 불가능하면 Exception을 발생시킨다. Error를 만드는 arr[ index ]와 다르다.

cout << arr[10] << endl;    // Just Error!
cout << arr.at(10) << endl; // Make Exception

for-each 반복문을 활용할 수 있다

for(auto& i:arr)
    cout << i << " ";

sort( ) 함수를 사용하여 원소를 크기에 따라 분류할 수 있다

- sort함수를 사용하려면 <algorithm>을 포함시켜 주자.

std::array<int, 5> arr = { 5, 2, 3, 1, 4 };
sort(arr.begin(), arr.end());

for(auto& i : arr)
    cout << i << " ";
>> 1 2 3 4 5

위에서 begin - end의 순서를 역순으로 바꿀 수 있다.

sort(arr.rbegin(), arr.rend());
>> 5 4 3 2 1

 

함수의 Argument로 사용해도 개수 정보를 잃어버리지 않는다

void testArrayPara(const std::array<int, 5> &arr)
{
    cout << "size = " << arr.size() << endl;
}

int main()
{
    std::array<int, 5> arr = { 1, 2, 3, 4, 5 };
    arr = { 10, 11, 12, };
    
    testArrayPara(arr);
}
>> size = 5

 

6.21 std::vector 소개

std::vector는 동적배열을 대체하여 사용할 수 있다

vector는 메모리 해제를 자동으로 해준다. Scope 밖으로 나가면 메모리가 해제된다.

 

멤버함수 resize( )로 메모리 공간을 조절할 수 있다.

vector<int> v = {1,2,3,4,5};
v.resize(10);

for (const int& i : v)
    cout << i << " ";
cout << endl;
>> 1 2 3 4 5 0 0 0 0 0

 

vector의 멤버함수 활용하기

vector<int> v = { 1, 2, 3, 4, 5 };
v = { 5 };

cout << v.size() << endl;
cout << v.at(0) << endl;
Comments