행렬, 문자열, 포인터, 참조 #1

윤형찬·2020년 10월 27일

C++

목록 보기
6/10
post-thumbnail

배열 기초

  • 배열은 같은 타입의 변수들로 이루어진 유한 집합으로 정의된다.
  • 하나의 자료형을 여러개를 사용할 때, 사용할 변수를 각각 선언하기보다는 배열로 만드는 것이 좋다.
  • 구조체나 클래스도 배열 자료형으로 사용이 가능하다.
  • enum을 이용하여 index 를 사용할 수 있다.
  • 배열 크기가 컴파일 타임에 고정이 되어야 한다.
    - cin으로 입력 받은 값은 런타임에 결정되므로, 배열 크기 변수로 사용할 수 없다.

선언 예제

  • int my_array[5]
    my_array[0] = 1; my_array[1] = 2; ...
  • int my_array[] {1, 2, 3, 4, 5 }
  • 구조체 활용
struct Rectangle
{
    int length;
    int width;
};

int main()
{
    Rectangle rect_arr[10];
    rect_arr[0].length = 1;
    rect_arr[0].width = 2;
    
    return 0;
}
  • enum 을 이용한 index 활용

{
    JACKJACK,     // = 0
    DASH,         // = 1
    VIOLET,       // = 2
    NUM_STUDENTS, // = 3
};

int main()
{
    int students_scores[NUM_STUDENTS];
    
    students_scores[JACKJACK] = 0;
    students_scores[DASH] =100;
    
    return 0;
}
  • 함수의 파라미터로의 활용
#include <iostream>

using namespace std;

void doSomething(int students_scores[])
{
    cout << students_scores[1] << endl;
    cout << students_scores[2] << endl;
}

int main() {
    const int num_students = 20;
    int students_scores[num_students] { 1, 2, 3, 4, 5, };
    
    doSomething(students_scores);
    
    return 0;
}


배열과 반복문

  • 같은 자료형만 여러개로 이루어진 배열은 반복문으로 처리하기 좋다.

예제

  • 합 구하기
  • 최댓값 구하기
  • 평균값 구하지
#include <iostream>

using namespace std;

int main() {
    int scores[] = { 84, 92, 76, 81, 56};
    
    const int num_students = sizeof(scores) / sizeof(int);
    
    int max_score = 0;
    int total_score = 0;
     
    for(int i = 0; i < num_students; ++i)
    {
        total_score += scores[i];
        max_score = (max_score < scores[i]) ? scores[i] : max_score;
    }
    
    double avg_score = static_cast<double>(total_score) / num_students;
    
    return 0;
}



배열과 선택 정렬

selection sort 구현

#include <iostream>

using namespace std;

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

int main()
{
    const int length = 5;
    
    int array[length] = { 3, 5, 2, 1, 4};
    
    printArray(array, length);
    
    for(int startIndex = 0; startIndex < length -1; ++startIndex)
    {
        int smallest = startIndex;
        for(int currentIndex = startIndex + 1; currentIndex < length; ++ currentIndex)
        {
            if(array[smallest] > array[currentIndex])
            {
                smallest = currentIndex;
            }
        }
        
        // swap two values in the array
        // std::swap(array[smallistIndex], array[startIndex]);
        {
            int temp = array[smallest];
            array[smallest] = array[startIndex];
            array[startIndex] = temp;
        }
        
        printArray(array, length);
    }
    
    return 0;
}

+ bubble sort 구현

int main()
{
    const int length = 5;
    
    int array[length] = { 3, 5, 2, 1, 4};
    
    printArray(array, length);
    
    for(int i = length - 1 ; i > 0 ; i--)
    {
        for(int j = 0; j < i; j++)
        {
            int temp;
            if(array[j] > array[j+1])
            {
                temp = array[j];
                array[j] = array[j+1];
                array[j+1] = temp;
            }
        }
    }
    printArray(array, length);
    return 0;
}


정적 다차원 배열

  • 일차원적인 메모리를 2(n)차원적인것 처럼 사용하는 배열

예제

#include <iostream>

using namespace std;

int main()
{
    const int num_rows = 3;
    const int num_columns = 5;
    
    int array[][num_columns] // =
    {
        {1, 2, },
        {2, 3, 4, 5, 6},
        {7, 8, 9, 10, 11}
    };
    
    for(int row = 0; row < num_rows; ++row)
    {
        for(int col = 0; col < num_columns; ++col)
        {
            //cout << '[' << row << ']' << '[' << col << ']' << '\t';
            cout << array[row][col] << '\t';
        }
        cout << endl;
    }
    cout << endl;

    return 0;
}

행렬 곱셈 예제

만들어서올려라


포인터

  • 지역 변수는 '스택'메모리를 사용하고
  • 동적 할당 메모리는 '힙'메모리를 사용한다.
  • 큰 메모리에 저장되어 있는 데이터 중에서 일부분을 CPU가 사용하기 위하여 메모리로부터 가져올 때는 메모리 전체를 모두 뒤지면서 찾는 것이 아니라 필요한 데이터가 저장되어 있는 주소를 사용하여 직접 접근하여 가져온다.
  • de-reference는, 포인터가 "저쪽 주소에 가면 이 데이터가 있어요" 라고 간접적으로 가리키기만 하는 것에 대해서, "그럼 거기에 진짜 뭐가 있는지 내가 들여다볼께"라며 직접적으로 접근하겠다는 의미이다.
  • 포인터는 메모리 주소를 담는 변수이다.
  • 포인터 변수의 크기는 64bit 컴퓨터에선 모두 8로 동일하다.

예제

#include <iostream>

using namespace std;

int main()
{
    int x = 5;
    double d = 1.0;

    int *ptr_x = &x;
    double *ptr_d = &d;
    
    cout << "x : " << x << "\tptr_x : " << *ptr_x << endl;
    cout << "d : " << d << "\tptr_d : " << *ptr_d << endl;
    cout << "x의 주소 : " << &x << "\t*ptr_x : " << ptr_x <<  endl;
    cout << "d의 주소 : " << &d << "\t*ptr_d : " << ptr_d <<  endl;
    
    cout << "int 자료형의 크기 : " << sizeof(x) << endl;
    cout << "double 자료형의 크기 : " << sizeof(d) << endl;
    cout << "int 자료형의 포인터 변수 크기 : " << sizeof(ptr_x) << endl;
    cout << "double 자료형의 포인터 변수 크기 : " << sizeof(ptr_d) << endl;

    return 0;
}

x : 5       ptr_x : 5
d : 1       ptr_d : 1
x의 주소 : 0x7ffeefbff468       ptr_x : 0x7ffeefbff468
d의 주소 : 0x7ffeefbff468       
ptr_d : 0x7ffeefbff460
int 자료형의 크기 : 4
double 자료형의 크기 : 8
int 자료형의 포인터 변수 크기 : 8
double 자료형의 포인터 변수 크기 : 8



널 포인터

  • 쓰레기 주소값이 들어가 있는데 디레퍼런싱을 시도하면 실제로 메모리에 데이터가 담겨있는곳이아니라 엉뚱한곳에 가서 찾는다 그래서 os가 문제있다고 말한다.
  • 이를 방지하기 위해 널포인터를 사용한다.

예제

#include <iostream>

void doSomething(double *ptr)
{
    if (ptr != nullptr)
    {
        // do sommethin useful
    std::cout << *ptr << std::endl;
    } else
    {
        // do nothing wit ptr
    std::cout << "Null ptr, do no thing" << std::endl;
    }
}

int main(int argc, const char * argv[]) {
    double *ptr{ nullptr }; //modern c++
    
    doSomething(ptr);
    doSomething(nullptr);
    
    double d = 123.4;
    
    doSomething(&d);
    
    ptr = &d;
    
    doSomething(ptr);
    
    return 0;
}


포인터와 정적 배열

  • 포인터와 정적 배열은 같다.
  • 배열에서 첫 번째 자료의 주소는 배열을 포인터로 찍었을때의 주소와 같다.
  • 배열은 첫 번째 자료부터 일렬로 쭉 주소를 할당 받은 자료구조이다.
  • integer 자료형 배열이면 4byte 간격으로 주소를 쭉 할당 받는다.

예제

#include <iostream>

int main()
{
    using namespace std;
    
    int array[5] = { 9, 7, 5, 3, 1 };
    
    cout << array[0] << " " << array[1] << endl;
    cout << array << endl;
    cout << &array[0] << endl;
    cout << &array[1] << endl;
    
    cout << *array << endl;
    
    int *ptr = array;
    cout << ptr << endl;
    cout << *ptr << " " << *(ptr + 1) << endl;
    
    cout << sizeof(array) << endl;
    cout << sizeof(ptr) << endl;
    
    return 0;
}

9 7
0x7ffeefbff430
0x7ffeefbff430
0x7ffeefbff434
9
0x7ffeefbff430
9 7
20
8

✓ 자료형 크기는 다름을 확인 할 수 있다.



포인터 연산과 배열 인덱싱

  • 포인터 연산은 포인터 변수에 +, - 연산자를 사용하여 값을 더하거나 뺀다.
  • 또는, ++, -- 연산자를 사용하여 값을 증가, 감소시킨다.
  • 단, *, / 연산자와 실수 값은 사용할 수 없다.

예제

#include <iostream>

using namespace std;

int main()
{
    int array[] = { 9, 7, 5, 3, 1};
    int *ptr = array;
    
    for(int i = 0; i < 5; ++i)
    {
        //cout << array[i] << " " << (uintptr_t)&array[i] << endl;
        cout << *(ptr+i) << " " <<(uintptr_t)(ptr + i) << endl;
    }
    
    
    char name[] = "Jack Jack";
    
    const int n_name = sizeof(name) / sizeof(name[0]);
    
    for(int i = 0; i < n_name ; ++i)
    {
        cout << *(name + i) ;
    }
    
    return 0;
}


메모리 동적 할당 new와 delete

  • 정적으로 할당되는 메모리는 stack에 들어감.
  • stack은 메모리가 작음
  • 조금 큰 수를 정적 배열에 할당하려하면 오류가 남
  • 동적으로 할당되는 메모리는 힙에 들어감.
  • 힙은 저장할 수 있는 용량이 큼
  • integer 자료형을 선언할 때 new int로 선언을 하면, 4byte만큼 할당하여 사용할 수 있는 주소를 할당해줌 -> 포인터로 받아야 함.
  • 남는 메모리가 없어서 할당이 되지 않을 수가 있으므로 에러를 내보내지 않게 하기 위해 new (std::nothrow) int ~~ 이런식으로 선언해주면 좋다.
  • 사용 후에는 delete 로 메모리를 반환을 해줘야함.
  • 단순히 delete 만 하면 사용했던 주소는 그대로 나오고 쓰레기 값이 저장되어 있으므로 nullptr 을 해주면 좋다.

예제

#include <iostream>

using namespace std;

int main()
{
    //int var
    //var = 7;
    
    int *ptr = new (std::nothrow) int { 7 };
    if(ptr)
    {
        cout << ptr << endl;
        cout << *ptr << endl;
    } else
    {
        cout << "Could not allocate memory" << endl;
    }
    
    delete ptr;
    ptr = nullptr;
    
    cout << "After delete" << endl;
    if (ptr != nullptr)
    {
        cout << ptr << endl;
        cout << *ptr << endl;
    }
    
    return 0;
}


동적 할당 배열

  • 런타임에 배열의 사이즈를 결정
  • 그때 그때 메모리 사이즈를 os로부터 받아오기 때문에 정적 할당 배열보다 유동적으로 활용 가능

예제

#include <iostream>

using namespace std;

int main()
{
    int length;
    
    cin >> length;
    
    // int array[length]
    int *array = new int[length] { 11, 22, 33, 44, 55, 66};
    
    array[0] = 1;
    array[1] = 2;
    
    for(int i = 0; i < length; i++)
    {
        cout << array[i] << endl;
        cout << (uintptr_t)&array[i] << endl;
    }
    
    delete [] array;
    
    return 0;
}


포인터와 const

  • 포인터에도 const를 사용할 수 있다.
  • 변수를 포인터 변수가 de-referencing 하여 값을 변경할 수 있다.
    int value = 5; int *ptr = &value; 일 때 *ptr = 6; 으로 value 값 변경가능
  • 변수가 상수로 (const) 선언 되었을 때 포인터 변수를 사용하려면 포인터 변수도 const 로 선언해야한다.
  • 이 때 위에 예시처럼 값을 변경할 순 없다.

비슷한 경우 예제

    int value = 5;
    const int *ptr = &value;
    //*ptr = 6;
    value = 6;
    
    cout << *ptr << endl;

이 때는 6이 출력된다.
*ptr = 6; 으로는 값을 변경할 수 없다.

*ptr 앞에 const는 가리키고 있는 주소에 값을 안바꾼다는 의미이다. ptr이 가지고 있는 주소 값을 안바꾼다는 것은 아니다.
따라서 아래 예제가 가능하다.

    int value1 = 5;
    const int *ptr = &value1;
    
    int value2 = 6;
    ptr = &value2;

6 이 출력된다.


위에 사례를 막고 싶다면 const 앞에 *를 붙인다.
- 포인터 변수로써 갖고 있는 값은 못바꾼다는 표현 -

    int value = 5;
    int *const ptr = &value;
    
    *ptr = 10;
    
    int value2 = 8;
    
    // 이렇게는 사용 불가
    ptr = &value2;

사용 정리

int value = 5;
const int *ptr1 = &value;
int *const ptr2 = &value;
const int *const ptr3 = &value;
    

조금 더 편하게 사용하기 위해서는 참조에서 다룬다.

profile
https://github.com/velmash

0개의 댓글