반응형
728x90
반응형

복사 생성자

l  초기화 스타일

다음은 초기화를 나타내는 예제이다.

위와 같이 11번 라인처럼 항상 대입 연산자를 통하여 초기화 해왔으나, 12번 라인과 같이 새로운 C++ 스타일로 초기화가 가능하다. 그리고 두 결과는 값이 대입되었다는 점에서 같다. 이는 복사 생성자와 관련 있는 개념이다.

 

  • 객체의 초기화 방법

객체 초기화 방법 또한 여러 방법이 존재하는데 먼저 38번 라인은 26번 라인에서 정의해 둔 생성자를 통해서 상수를 받아 초기화를 진행한다. 그리고 39번 라인에서는 대입 연산자를 통하여 초기화를 진행한다. C언어에서는 구조체끼리 대입 연산 시킬 경우 멤버 대 멤버의 복사가 일어났다. 마찬가지로 39번 라인 역시 클래스 멤버들에 대해서 멤버 대 멤버 복사가 일어난다. 40번 라인 또한 C++언어 스타일의 초기화 방법으로 결과는 39번 라인과 똑같다. 39번 라인은 결국 40번 라인 형태로 변환이 일어난 후 초기화가 진행된다.

 

그런데 중요한 것은 객체가 생성될 때에는 무조건 생성자가 호출되어야 하는데, 38번 라인의 객체 생성에서는 26번 라인의 생성자가 호출되었다. 그러나 39, 40번 라인의 경우 호출될 수 있는 생성자가 존재하지 않는다. (이미 생성자가 정의되었기 때문에 디폴트 생성자로는 현 상황을 설명할 수 없음)

이러한 경우는 디폴트 복사 생성자가 존재하기 때문에 에러 없이 실행이 가능한 것이다.

디폴트 복사 생성자를 예측해보자면 다음과 같은 형태로 구성되어 있다 복사 생성자를 따로 정의하지 않는다면 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 삽입된다.

다음은 정말 위와 같은 형태로 복사 생성자를 정의한 예제이다.

17번 라인에서 매개 변수를 참조자로 받는 복사 생성자를 직접 정의한 것을 확인할 수 있다. 이러한 복사 생성자는 33번이나 34번 라인과 같이 객체 간의 복사가 이루어질 때 호출되는 것을 확인할 수 있다.  

 

  • explicit

39번 라인의 초기화 형태는 38번 라인 형태의 초기화 형태로 변환이 일어난 후에 복사 생성자를 호출하는데 이러한 변환은 묵시적인 변환이기 때문에 결과를 예측하기 힘들 수 있다. 그렇기 때문에 이러한 묵시적인 변환에 의한 호출을 허용하지 않기 위해서는 다음과 같이 explicit 키워드를 복사 생성자에 붙임으로써 묵시적인 변환을 일으키는 초기화 스타일에 대해 에러 메시지를 내어준다.

  • 얕은 복사와 디폴트 복사 생성자의 문제점

디폴트 복사 생성자는 멤버 대 멤버의 복사를 진행하는데 이러한 방법의 복사 형태를 얕은 복사라고 한다. 일반 변수 형태의 멤버 변수의 경우 문제가 없으나, 멤버 변수로 힙 메모리 영역을 가리키고 있는 포인터를 지니고 있다면 그 때에는 문제가 된다. 다음은 그 문제의 경우를 나타내는 예이다.

분명히 객체를 세 개 생성하였으니 소멸자는 세 번 호출되어야 한다. 그러나 위의 예에서는 소멸자가 딱 한 번 호출되었음을 확인할 수 있다. 소멸자를 호출하면서 delete 연산에서 에러가 나서 프로그램이 종료된 것이다. 그렇다면 delete 연산에서 오류가 난 이유는? 디폴트 복사 생성자의 얕은 복사 때문이다

a1 객체의 생성자가 호출 -> a1 객체 내의 number 할당 -> a2 객체의 디폴트 복사 생성자 호출 -> a1 객체 내의 number 가 가지고 있는 주소 값을 a2 객체 내의 number에 대입 -> a3 객체의 디폴트 복사 생성자 호출 -> a2 객체 내의 number 가 가지고 있는 주소 값을 a3 객체 내의 number에 대입

여기 까지는 생성자 호출 과정이다. 여기서 문제는 세 개의 객체들의 생성자가 모두 호출되면 a1, a2, a3 객체 내의 number는 모두 같은 메모리 위치를 가리키고 있다는 것이다. 그렇기 때문에 첫 소멸자에 해체한 이미 메모리를, 두 번째 소멸자의 실행에서 한번 더 해체하려는 과정에서 오류가 나는 것이다.

이렇게 멤버로 동적 할당이 필요한 객체 간의 복사 생성자는 멤버 대 멤버의 복사의 얕은 복사 형태로 이루어져서는 절대 안 된다. -> 깊은 복사를 위한 복사 생성자를 꼭 정의해야 한다.

다음은 깊은 복사를 행하는 복사 생성자를 정의한 예이다.

18번 라인에서 깊은 복사를 수행하는 복사 생성자를 정의한 결과 소멸자가 모두 정상 출력되는 것을 확인할 수 있다.

 

  • 복사 생성자가 호출되는 시점

복사 생성자가 호출되려면 먼저 공통적으로 꼭 포함해야 할 기본 전제에 대해서 알고 있어야 한다.

1.     객체를 새로 생성해야 함

2.     그리고 생성과 동시에 동일한 자료형의 객체로 초기화 해야 함.(할당과 동시에 초기화)

 

복사 생성자의 호출되는 시점은 다음과 같이 세 가지 경우에 호출되는 시점이 될 수 있다. 이 세 가지 경우 모두 겉보기에는 다른 형태이지만 결국에는 객체를 새로 생성하고, 생성과 동시에 동일 자료형의 객체로 초기화 되는 경우인 것이다.

1.     기존에 생성된 객체를 이용해서 새로운 객체를 초기화 하는 경우

-> int num1=num2; 와 같은 상황으로 할당과 동시에 초기화가 일어 남.
-> AAA a1=a2; 의 경우 a1의 복사 생성자가 호출되어 a2의 값이 a1으로 복사됨.

2.     Call-by-Value 방식의 함수 호출 과정에서 객체를 인자로 전달하는 경우

-> 함수가 호출되는 순간에 매개 변수가 할당됨과 동시에 전달된 인자의 값으로 초기화된다.
-> void func(AAA arg);에서 func(a1);으로 호출할 경우 arg의 복사 생성자가 호출되어 a1의 값이 arg로 복사됨.

3.     객체를 반환하되, 참조형으로 반환하지 않는 경우

-> 함수가 값을 반환하면, 별도의 메모리 공간이 할당되고, 이 공간에 반환 값이 저장(초기화)된다. 이런 식으로 변수 혹은 객체로서 별도 메모리에 저장되어 임시 변수 혹은 객체로 남아 있어야 함수 등을 이용하여 출력을 하거나, 변수에 대입을 할 수 있기 때문이다. 이렇게 객체가 반환될 때, 임시 객체의 복사 생성자가 호출된다.

이렇게 생성된 임시 객체는 다음 행으로 넘어가면 바로 소멸되어 진다. , 참조자에 의해 참조될 경우 그 임시 객체는 바로 소멸되지 않는다.

 

  • 참고

클래스 외부에서 객체의 멤버 함수를 호출하기 위해 필요한 것.

객체에 붙여진 이름, 객체의 참조 값(객체 참조에 사용되는 정보), 객체의 주소 값

 

 

 

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

17 상속  (0) 2020.09.14
16 friend, static, const, mutable  (0) 2020.09.14
14 클래스와 배열 및 this 포인터  (0) 2020.09.13
13 생성자와 소멸자  (0) 2020.09.12
11, 12 정보 은닉과 캡슐화  (0) 2020.09.11
728x90
반응형

클래스와 배열 및 this 포인터

  • 객체 배열

객체 배열은 다음과 같이 일반 배열 방식으로 선언할 수도 있으며, 동적 할당을 통한 방식으로 선언할 수 있다.

 

다만, 동적 할당이든 일반 배열이든 객체 배열을 선언할 때에는 각 배열의 요소 객체들마다 생성자 및 소멸자를 호출하지만 생성자의 경우 호출할 생성자를 명시하지 못한다 인자 전달이 불가능하고 무조건 인자가 없는 생성자만을 호출한다. , 인자가 없는 생성자가 반드시 정의되어 있어야 한다. 각 배열의 요소를 초기화 시키기 위해서는 일일이 초기화를 시켜줘야 한다.

  • 객체 포인터 배열

객체 포인터 배열은 객체의 주소 값 저장이 가능한 포인터 변수로 이뤄진 배열이다.

  • this 포인터

this 포인터는 객체 자신을 가리키는 용도로 사용되는 포인터로 멤버 함수 내에서 this라는 이름으로 사용할 수 있는 포인터이다. 객체 자신의 주소 값을 나타내는 포인터이다.

11번 라인에서 출력한 this 포인터를 출력한 결과와 17번 라인의 객체 a의 주소 값을 출력한 결과가 같은 것으로 보아 this 포인터는 객체 자기자신의 주소를 나타내는 포인터이며 다음과 같이 활용한다.

  • 자기 참조자의 반환

This 포인터를 이용하여 자기 참조가 가능하도록 참조 정보를 반환할 수 있다. 17번 라인에서 참조 정보가 반환되고, 30번 라인에서 거듭된 접근 연산을 통해 멤버 함수를 호출하는데 이는 참조 값을 반환하고 그 반환된 참조 값을 이용해서 다시 접근하기를 반복하기 때문에 가능하다.

28번 라인에서 대입 연산자를 기준으로 a의 참조 정보(참조 값이) rA 참조자에 전달이 된다.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

16 friend, static, const, mutable  (0) 2020.09.14
15 복사 생성자  (0) 2020.09.13
13 생성자와 소멸자  (0) 2020.09.12
11, 12 정보 은닉과 캡슐화  (0) 2020.09.11
10 클래스와 객체  (0) 2020.09.11
728x90
반응형

포인터와 함수

  • 값의 복사 인자 전달 방식 ( 기본 인자 전달 방법 )

먼저, 기본적으로 함수에서 인자를 전달할 때는 값의 복사 방법으로 인자가 매개변수로 전달이 된다.

위의 예제에서 num 변수를 func 함수의 인자로 넣어 값을 전달하는데 이 때 전달되는 것은 num 변수가 아니라 num 변수의 값이 전달되어 arg 변수에 저장된 것이다. 결국, 지역 변수 num과 함수의 매개 변수 arg는 서로 전혀 관련 없는 별개의 변수이다. ( arg 매개 변수 값을 아무리 변경하더라도 num 지역 변수의 값의 변화는 일어나지 않는다. )

 

  • 배열을 함수의 인자로 전달하는 방법

배열을 기본 인자 전달 방법인 값의 복사 방법으로 통째로 복사하여 전달하는 방법은 존재하지 않는다.

위와 같은 소스는 컴파일 가능하지 않다. 왜냐하면 매개 변수로 배열을 선언하는 것은 허용되지 않기 때문이다. 그러나 gcc 컴파일러 6.4.0 version을 기준으로 위의 소스가 실행이 되고 정확한 결과가 나와서 가능한 것처럼 보였으나, 

위와 같이 지역 변수의 배열과 매개 변수의 배열의 배열 이름의 값을 출력해 보았다. 그 결과는 다음과 같다.

두 배열 이름이 같은 주소를 지니고 있다. 이 뜻은 지역 변수의 배열 arr는 문제 없이 정상적인 방법으로 메모리 할당이 이루어져 배열을 이루고 있는 것이지만, 매개 변수의 배열 arg는 외형과 다르게 포인터 변수로서 선언되어 있다.

포인터 변수이기 때문에 arg의 값의 변경이 가능하다. 그리고 다음은 그 결과이다.

아파트를 보고 싶어 하는 사람 앞에 아파트를 통째로 복사해 놓을 수 없다면, 아파트를 직접 찾아가도록 주소를 알려주듯이 배열을 통째로 복사가 불가능하다면 배열의 시작 주소 값을 전달하고, 그에 맞는 포인터 형을 매개 변수를 이용하여 포인터 연산으로 각각의 배열 요소에 접근하여 원하는 값을 참조하면 된다.

18번 라인에서 넘겨주는 배열 이름(배열의 시작 주소)5번 라인에서 포인터 변수를 통하여 받고 포인터 연산을 통하여 ( arg[i] = *(arg+i) ) 각각의 요소를 참조하여 출력을 한다. 매개 변수 한정으로 5번 라인에서 주석 친 부분과 같은 형식으로 매개 변수를 선언하고 주소를 받을 수 있다.

  • Call by value 그리고 Call by reference

두 방법에 대해 정리하기 이전에 두 변수의 값을 서로 교환하는 “SWAP” 문제에 대해서 두 방법을 적용해보고 해결하는 과정으로 두 방법을 정리할 것임.

먼저 call by value이다. 이 방법은 함수 인자 전달의 기본 방식이다.

 swap 함수 내에서는 값이 변경되었으나, 정작 바뀌어야 할 main 함수 내의 변수들은 swap이 이루어지지 않았다 이는 복사에 의한 인자 전달이기 때문에 변경이 되지 않는 것은 당연한 일이다. 변경된 것은 swap 함수 내의 매개 변수끼리 값이 변경된 것이다. 이러한 swap은 진정한 의미의 swap이라고 할 수 없다. 이를 해결하기 위해선 주소가 주어지고 해당 주소를 직접 참조하여 값을 변경하는 방식으로 전개를 해야 한다.

다음은 call by reference이다.

주소를 전달하고, 전달된 주소를 참조하여 값을 변경하는 방법으로 값이 성공적으로 swap 되었음을 확인할 수 있다. scanf 함수 또한 call by reference 방법으로 인자를 전달하는 형태이고, 문자열(%s)을 입력 받을 때 ‘&’ 를 붙이지 않는 이유와 그 외의 값을 받을 때 ‘&’ 를 붙이는 이유를 call by reference 방법으로부터 알아낼 수 있다.

 

  • 포인터에서의 const 키워드

다음과 같은 변수가 선언되어 있는 상황이다.

이러한 상황에서 const 키워드를 어느 위치에 사용하는지에 따른 결과들을 분석할 것.

1.     포인터 변수가 참조하는 대상을 상수화(const )한다.

포인터 변수 p를 통하여 number의 값이 변경되는 것을 허용하지 않는다.

2.     포인터 변수를 상수화(const )한다.

포인터 변수 p를 상수화 하여 포인터 변수 p의 값이 변경되는 것을 허용하지 않는다.

3.     둘 다 상수화(const )한다.

p를 통한 number의 값의 변경, p의 값 변경 모두 허용하지 않는다.

4.     일반 변수를 상수화(const ) 한다.

변수 n을 상수화 하여 n의 값을 변경할 수 없다.

Const 키워드는 C++에서만 제공했던 키워드, 표준을 재정립하는 과정에서 C언어서도 지원을 해준다. Const 키워드는 값이 바뀌어서는 안 되는 변수의 변경을 막아준다. 이러한 경우는 프로그램을 실행하는데 있어서는 치명적인 오류로 작용할 수 있지만 문법 상으로는 문제가 없기 때문에 컴파일 오류 하나 받아볼 수 없다. 그렇기 때문에 효율적이고 현명한 프로그램을 원한다면 const 키워드를 자주 사용하는 습관을 들여놓아야 함.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

12 포인터의 포인터  (0) 2020.09.15
11 다차원 배열  (0) 2020.09.14
09 포인터와 배열  (0) 2020.09.12
08 포인터 기본  (0) 2020.09.11
07 1차원 배열  (0) 2020.09.11
728x90
반응형

생성자와 소멸자

  • 생성자

위의 예제에서 11번 라인에 나타나는 함수를 생성자라고 한다.

생성자는 먼저 클래스의 이름과 동일한 함수의 형태를 띄고 있으며, 반환형이 선언되어 있지 않으며, 실제로 반환 또한 하지 않는다.

생성자의 특징으로는 객체 생성 시 딱 한번 호출되며, 함수의 일종이니 오버로딩이 가능하며, 디폴트 매개변수를 설정할 수 있다. 그리고 객체가 생성될 때에는 생성자가 무조건 호출된다. 생성자가 호출되지 않고서 객체가 생성될 수 없다.

위의 예제는 생성자를 선언하고 활용하는 예이다. 11번 라인부터 23번 라인에서는 생성자를 정의하고 있는데 생성자도 일종의 함수이기 때문에 함수 오버로딩이 적용되고, 디폴트 매개변수의 적용 또한 가능함을 볼 수 있다. 그리고 29번 라인 ~ 42번 라인까지 일반 변수 형태의 선언과 동적 할당 형태의 선언으로 나뉘어서 각각 선언될 시 생성자의 호출을 볼 수 있다. 32번 라인의 형태는 객체가 생성되지 않는다. 왜냐하면 반환 타입이 AAA형이고 매개 변수는 void형이고 함수 이름이 a0인 함수의 원형 선언으로 간주되기 때문이다.

 

  • 멤버 이니셜라이저

다음 상황에서 멤버 이니셜라이저는 멤버 변수로 선언된 객체의 생성자 호출에 활용된다.

28번 라인을 통해서 멤버 이니셜라이저를 진행하고 있는데 AAA 클래스는 BBB의 클래스의 객체를 멤버 변수로 가지고 있다. 객체 b의 초기화는 첫 번째 방법으로는 객체 b를 이용하여 set 함수를 통해 혹은 그에 준하는 함수의 호출을 통해 정보 은닉에 위배되지 않게 b 객체의 number 멤버 변수를 초기화 시키거나, 두 번째 방법으로는 생성자를 이용하여 초기화를 시켜야 한다. 그러나 위와 같이 클래스 내에서 선언되어 객체의 생성과 동시에 초기화 값 전달이 불가능하다. (BBB b(123) 형태의 선언이 불가능하다는 의미) 그러나 멤버 이니셜라이저를 이용하면 객체 형태의 멤버의 생성자를 이용한 초기화가 가능하다. 28번 라인의 내용이 멤버 이니셜라이저이다. 위 예제에서의 의미는 객체 b의 생성 과정에서 num을 인자로 전달받는 생성자를 호출한다라는 의미이다.

위와 같이 객체가 아닌 일반 기본 자료형에 대한 멤버 변수 또한 이니셜라이저를 통해서 초기화가 가능하다. 12번 라인처럼 멤버 변수 num을 생성자를 통해 넘겨온 매개 변수 n을 이용하여 초기화 하라는 의미이다. 이렇게 되면 멤버 변수를 초기화 하는 방법은 두 가지가 된다.

1.     생성자의 몸체에서 초기화
2.     이니셜라이저를 이용한 초기화

두 가지가 존재하는데 이니셜라이저를 통하여 초기화를 진행하는 경우 다음과 같은 이점을 얻을 수 있다.

1.     초기화의 대상을 명확히 인식할 수 있다.
2.     성능에 약간의 이점이 있다.
->  선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다.
->  생성자 몸체의 경우 int num; num=10;/이니셜라이저의 경우 int num=10; 효과를 볼 수 있다.

const 멤버 변수/참조자 멤버 변수의 초기화

이니셜라이저를 이용할 경우 선언과 동시에 초기화와 같은 효과를 얻는다는 이유로 const 멤버 변수의 초기화를 진행할 수 있다! 또한 선언과 동시에 초기화가 이루어져야 하는 참조자 멤버 변수 또한 초기화가 가능하다.

  • 객체 생성 과정의 정리

1.     메모리 공간의 할당
2.     이니셜라이저를 이용한 멤버 변수(객체)의 초기화
3.     생성자의 몸체 부분 실행

모든 객체는 세 과정이 순서대로 거치고 생성이 완료된다.

  • 디폴트 생성자

객체가 되기 위해서는 반드시 하나의 생성자가 호출되어야 한다. 그러나 생성자가 없는 클래스의 경우 객체를 생성하지 못하는 것인가? 이런 문제를 해결하기 위해서 생성자를 정의하지 않은 클래스에는 C++ 컴파일러에 의해서 디폴트 생성자가 자동으로 삽입된다.

디폴트 생성자는 전달 인자를 받지 않고, 내부적으로 아무런 일도 하지 않는 생성자이다.

이러한 디폴트 생성자는 생성자가 하나도 정의되어 있지 않을 때만 컴파일러에 의해 자동으로 삽입된다. 다른 말로 생성자가 하나라도 추가되어 있다면 디폴트 생성자는 컴파일러에 의해 자동으로 삽입되지 않는다.

  • Private 생성자

클래스 내부에서만 객체의 생성을 허용하려는 목적으로 생성자를 private으로 선언.

  • 소멸자

위와 같은 예에서 12번 라인과 같은 형태를 소멸자라고 한다.

소멸자는 클래스의 이름 앞에 ‘~’가 붙은 형태의 이름을 갖으며, 반환형이 선언되어 있지 않고 실제로 반환하지 않는다. 그리고 매개변수는 항상 void형으로 선언되어야 하고 오버로딩 및 디폴트 매개변수 설정 모두 불가능하다.

소멸자의 특징은 먼저 객체 소멸 과정에서 자동으로 호출이 된다. 그리고 아무런 소멸자가 생성되어 있지 않으면 디폴트 생성자가 컴파일러에 의해 자동으로 삽입된다. 소멸자는 생성자에서 할당한 리소스의 소멸에 사용된다.

다음은 소멸자 사용의 예이다.

 

 

 

 

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

15 복사 생성자  (0) 2020.09.13
14 클래스와 배열 및 this 포인터  (0) 2020.09.13
11, 12 정보 은닉과 캡슐화  (0) 2020.09.11
10 클래스와 객체  (0) 2020.09.11
09 C언어의 표준 함수 호출  (0) 2020.09.10
728x90
반응형

포인터와 배열

l 배열 이름과 포인터의 관계

다음은 배열의 이름이 무엇인지를 나타내는 예제이다.

위의 소스에서 확인할 수 있는 부분은 배열의 이름 그 자체를 출력했을 때의 값과 배열의 [0]번째 요소의 주소 값이 같다는 점이다. , 배열의 이름은 해당 배열의 시작 주소 값을 의미한다.

결국 배열의 이름 또한 주소 값을 지니고 있다는 관점에서 포인터와 그 개념이 매우 유사하다. 그렇다면 포인터 변수처럼 배열의 이름 또한 주소를 대입해볼 수 없을까? 배열의 이름에 주소 값을 대입하는 예제이다.

8번 라인에 새로운 변수 number를 선언하고, 10번 라인에서 배열의 이름에 number의 주소 값을 넣었다. 왜 배열의 주소가 아니라 일반 변수의 주소를 넣었느냐? 왜냐하면 배열의 이름은 배열의 시작 주소하나를 저장하기 때문에 배열 이름의 입장에서 보면 어떤 하나의 변수의 주소 값을 대입한 것과 다를 바 없이 보이기 때문이다. 그렇기 때문에 배열의 이름에 하나의 변수 number의 주소 값이 대입되는 것은 문제가 되지 않는다. 이렇게 배열의 이름에 변수의 주소를 넣은 결과, 10번 라인에서 배열 타입의 표현 식에 할당하는 부분에서 에러가 발생한다. 그 이유는 배열의 이름은 주소 값을 저장하고 가리키지만 처음 정해진 배열 이름의 값은 변경이 불가능하다. , 배열의 이름은 상수 값이다. 그도 그럴 것이 특별하게 배열을 선언하여 만들었는데 그 배열의 이름을 대표하는 배열의 이름이 가리키는 요소를 마음대로 바꾸게 되면 무엇을 위해 배열 이름을 지정해가며 배열을 생성했겠는가?

다음은 위에서 배열과 포인터의 관계에 대해 내린 결론을 정리하여 나타낸 표이다.

배열 이름은 포인터 변수와 같은 특성들을 지니고 있지만 상수라는 차이점이 있다. 그래서 배열 이름을 포인터 상수라고도 표현한다. 포인터 변수와 배열 이름은 변수이냐, 상수이냐 의 차이점 밖에 없기 때문에 그 차이를 제외하면 포인터 변수와 배열 이름은 완전히 같다. 그렇기 때문에 다음의 예제가 가능하다.

10번 라인에서 배열 이름(포인터 상수)의 값을 포인터 변수에 대입시키고 있다. 이것이 가능한 이유는

1.     포인터 변수에 포인터 상수를 대입 -> 전혀 문제될 것이 없다.

2.     대입 연산자를 기준으로 l-valuer-value의 형(type)이 같아야 한다.
-> l-value : int * / r-value : int * == int
(시작) 주소 로 문제될 것이 없다.

배열 이름의 포인터 형은 배열의 이름이 가리키는 대상을 기준으로 결정되기 때문에 r-value 배열 이름의 자료형은 정수형 변수 공간의 시작 주소이므로 int * 가 맞다.

포인터 변수에 포인터 상수를 대입하게 되면 포인터 변수를 이용하여 배열 이름처럼 인덱스로써 접근하는 방법으로 각 요소에 접근을 할 수 있다. , 포인터 변수를 배열의 이름처럼 사용할 수 있다는 것이 결론.

그렇다면 그 반대로 배열 이름(포인터 상수)을 가지고 포인터 연산을 할 수 있을까? 다음은 이에 관한 예제이다.

11번 라인에서 배열 이름과 ‘ * ‘, 애스터리스크 연산자를 이용한 포인터 연산이 진행되고, 12번 라인에서는 배열 이름과 인덱스를 이용한 배열의 0번째 요소에 접근하여 출력을 한다. 11번 라인에서는 배열 이름이 가지고 있는 값 즉, 배열의 시작 주소에 접근하여 해당 값을 출력하기 때문에 0번째 요소를 출력한다.

  • 포인터 연산

포인터를 이용하여 참조 뿐만 아니라 증감 연산이 가능하다. 다만 일반 수를 이용한 연산에 비해서 연산이 많이 제한 된다.  -> 가능한 연산자 수의 제한

포인터 연산에서 중요한 포인트

1.     덧셈, 뺄셈, 참조 연산만 가능하다.
->
곱셈, 나눗셈 등의 연산을 불가능하다.

2.     덧셈, 뺄셈 연산은 일반 수를 통한 덧셈, 뺄셈 연산과는 다르다.
->
포인터 연산에서의 +1과 수에 대한 +1에서 1은 각각 의미가 다르다.

다음은 포인터에 + 연산자를 이용하여 1씩 더하는 예제이다.

int, char, double 자료형 별로 배열을 선언하고, 각각에 대한 포인터 변수를 선언하여 각 배열의 첫 시작 주소(배열 이름)를 대입시킨 상황에서 각각 포인터 연산을 진행하는 예제이다.

먼저, 자료형에 상관없이 배열 이름과 인덱스를 이용하여 요소별 주소 값을 출력한 결과와 포인터 연산에 대한 결과를 출력한 결과가 모두 동일함을 알 수 있다. 그렇다면 도대체 + 연산자를 이용하여 포인터 변수를 포인터 연산한 것이 어떤 의미가 있길래 각 요소의 주소를 출력한 결과와 같은 결과인가?

 

1.     A의 결과에서 pArrpArr+1 값의 차이는 4이다.

2.     B의 결과에서 pCrrpCrr+1 값의 차이는 1이다.

3.     C의 결과에서 pDrrpDrr+1 값의 차이는 8이다.

 

단순히 포인터 변수에 +1 연산을 했을 뿐인데 그 증가의 차이는 자료형 별로 다른 결과가 나타났다. 포인터 연산은 피연산자로 오는 그 대상이 메모리 주소인 만큼 메모리 주소에 대한 연산이 이루어지는데 이 메모리는 무작정 작은 단위인 1바이트 별로 보는 것은 의미가 없다. int 형의 경우에는 4바이트 전부 참조해야 비로소 값이 의미가 있듯이 자료형 별로 최소 참조 바이트가 존재한다. 그렇기 때문에 주소에 대한 연산인 포인터 연산은 자료형 별 최소 참조 바이트를 반영하여 증감을 진행한다. 각 자료형 별로 포인터 연산에 대해서 1의 의미가 모두 다른 이유가 바로 이 때문이다. 1의 증감은 최소 참조 바이트 한 블록을 증감한다는 의미가 되기 때문이다.

포인터 연산에서 1의 의미는 sizeof(해당 자료형)*1 이 된다.
포인터 연산에서 2의 의미는 sizeof(해당 자료형)*2 이 된다.

포인터 연산에서 n의 의미는 sizeof(해당 자료형)*n 이 된다.

그렇다면 다음의 예는 무엇을 의미하는가?

*(p + 1)
p는 임의의 변수를 가리키고 있는 포인터 변수

이는 다음과 같이 해석할 수 있다.

P가 가리키고 있는 주소에 sizeof(해당 자료형) * 1만큼을 더한 주소를 참조(*)한다.

 다음은 그 예제이다.

위와 같은 포인터 연산에서의 성질과 예제를 통해서 알 수 있는 결론은 다음과 같다.

배열 이름[i] == *(배열 이름 + i)
혹은
포인터 변수[i] == *(포인터 변수 + i)

완전히 같다.

  • 문자열과 포인터

c언어에서 문자열을 표현할 때 두 가지 방법으로 표현한다.

1.     배열을 통한 문자열의 저장

2.     포인터 변수를 통한 문자열의 저장

먼저, 첫 번째 방법인 배열을 통한 문자열의 저장이다. 이 경우에는 str 배열의 길이가 자동 계산되어 각 인덱스 별로 문자를 하나씩 저장하여 해당 문자열의 끝에 ‘\0’을 저장함으로써 문자열의 저장이 비로소 완료되는 형태이다.

그러나 두 번째 방법인 포인터 변수를 통한 문자열의 저장의 경우이다.  c언어 프로그램 상의 모든 상수는 CPU를 이용한 연산의 대상이 되기 위해서는 해당 상수가 메모리 상에 적재되어야 한다. “hello, my name is yoo seung ho!” 문자열은 상수로서 메모리에 적재되고, 그 시작 주소인 ‘h’의 주소 값이 반환되어 pStr 포인터 변수에 대입이 된다.

대입 연산자를 기준으로 l-valuechar * 형이고, r-value는 문자열의 시작 주소 값 즉, char * 이기 때문에 대입 연산에는 아무런 문제가 없다.

각 두 경우는 위 그림의 왼쪽과 오른쪽 형태로 동작한다. 배열의 이름의 경우 포인터 상수이기 때문에 가리키는 타겟 문자열을 변경할 수는 없지만, 변수 형태, 배열에 값이 저장되어 있기 때문에 대상이 되는 문자열의 내용에 대해서는 변경이 가능하다. 포인터의 경우 포인터 변수이기 때문에 가리키는 타겟 문자열을 변경할 수 있다. 그러나 그 대상은 상수 형태의 문자열이기 때문에 대상이 되는 문자열의 내용에 대해서는 변경이 불가능하다.

사실은 아래와 같이 되는 것이다.

  • 포인터 배열

말 그대로 포인터 변수들로 배열을 이룬 형태를 말한다.

 

 

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

11 다차원 배열  (0) 2020.09.14
10 포인터와 함수  (0) 2020.09.13
08 포인터 기본  (0) 2020.09.11
07 1차원 배열  (0) 2020.09.11
06 함수와 변수의 생명주기  (0) 2020.09.10
728x90
반응형

벡터

벡터가 무엇인지 한번 찾아보았다. 간단하게 정의하자면 순차 컨테이너에 속하는 동적 배열이다. C의 배열과 똑같이 행동한다.

그렇다면 C의 배열과 벡터의 차이점은 무엇일까? 

1. 타입에 상관없이 모든 타입에 대해서 일반적인 배열을 만들 수 있다.
=> int, double, char, int *, 객체 가릴 것 없이 모든 타입에 대해서 배열을 만들 수 있다는 이야기이다.
2. 배열의 크기 조절이 자동으로 이루어지며, 추가 및 삭제에 대한 인터페이스를 제공한다
=> 배열에 값을 추가하기 위해 새롭게 더 큰 메모리를 할당하고, 값을 복사하고 기존 메모리를 해체하고... 이러한 과정들을
알아서 해준다는 것이다! 개꿀!!!!ㅋㅋㅋㅋㅋㅋㅋㅋㅋ

그러나 2번과 같은 장점에 따른 단점 또한 존재한다.

C++의 벡터는 무조건 데이터를 선형적으로 만들려고 한다.( 그렇기에 이름도 순차 컨테이너. ) 중간에 끊긴다거나 하면 안된다는 소리이다. 

왼쪽과 같은 형태는 가능하지만, 오른쪽과 같은 형태는 안된다는 소리이다. 이러한 선형을 유지해야하는 특징 때문에 값을 추가할 때 단점이 발생한다.

메모리 공간이 충분하여 기존에 이어서 확장한 후 값을 그냥 더 추가할 경우에는 문제가 안된다.
아래의 그림처럼 말이야.

그런데 다음 그림과 같이 기존의 6개 메모리 뒤에 다른 관련없는 값이 이미 할당되어 기존의 6개 메모리 뒤에 연이어 추가 확장이 불가능한 경우는 문제가 발생한다.

이러한 경우에는 

이렇게 복사, 확장, 추가, 소멸 등의 여러 작업들이 필요하니 속도가 느려지는 단점이 있다.

설명은 이 정도로 정리하도록 하고, 매우 간단한 코드를 기록해보자.

7번 라인에서 볼 수 있듯이 템플릿 문법을 활용하여 사용한다. 반복자를 활용하지 않은 가장 기본 기본 기본적인 벡터 예제이다. 실행 결과는 매우 뻔하기 때문에 생략~

9번 라인에서 처럼 .size() 함수를 통해 벡터가 정의된 길이를 확인할 수 있다.

다음 vector 에서 제공하는 push_back 함수를 사용한 예이다. push_back 함수를 사용하면 벡터 열에 손쉽게 데이터를 추가할 수 있다.

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
#include <iostream>
#include <vector>
 
using namespace std;
 
int main(void)
{
    int max_number = 0;
    int val = 0;
    vector<int> num_vector;
 
    while (true)
    {
        cout << "벡터에 추가할 숫자를 입력하세요 (-123123 입력 시 종료) : ";
        cin >> val;
        if (val == -123123)
            break;
        else
            num_vector.push_back(val);
    }
 
    max_number = num_vector[0];
 
    for (int i = 0; i < num_vector.size(); i++)
    {
        if (num_vector[i] > max_number)
            max_number = num_vector[i];
    }
 
    cout << " 입력한 값 중에서 최대 값은 " << max_number << " 입니다." << endl;
    return 0;
}
cs

 

 

728x90
반응형

'프로그래밍응용 > Modern & STL' 카테고리의 다른 글

Deque  (0) 2020.09.13
모든 컨테이너 공통 멤버  (0) 2020.09.13
Iterator( With vector )  (0) 2020.09.13
STL:Container, Iterator, Algorithm 개념  (0) 2020.09.10
C++ STL(Standard Template Library이란?  (0) 2020.09.07
728x90
반응형

정보 은닉

  • 정보 은닉

정보 은닉이란? 멤버 변수를 private 접근 제어 지시어로 선언하여 외부에서의 모든 접근을 차단하고, 해당 변수에 접근하는 함수를 별도로 정의해서, 안전한 형태로 멤버 변수의 접근 및 초기화, 갱신 등이 이루어지도록 유도하는 것이 정보 은닉이다.

이러한 정보 은닉이 나오게 된 이유는 멤버 변수의 외부 접근 허용으로 대입되는 값이 전혀 검증이 이루어지지않은 채로 대입되기 때문이다. 그렇기 때문에 외부의 접근을 허용하지 않고(private), 해당 멤버 변수에 접근할 수 있는 함수를 별도로 정의하고 그 함수 내에 해당 멤버 변수에 대입하려는 값에 대한 검사를 이룰 수 있도록 하는 것이다.

8번의 private 접근 제어 지시자를 통하여 정보 은닉의 조건 중 하나를 충족시켰고, 13, 23번 라인에서 멤버 변수에 접근에 대해서 검증을 함으로써 정보 은닉을 성립시킨다. 13, 23, 33, 34번 라인에서 get, set 함수를 볼 수 있는데 이러한 함수들을 엑세스 함수라고 한다. 이러한 엑세스 함수들은 당장 사용되지는 않지만 필요할 수 있는 가능성이 있기 때문에 항상 생성해두기도 한다.

  • const 함수

위의 예제에서 get 메소드에 대해서 변경된 부분이 있다.

Get 엑세스 함수에 const 키워드가 붙은 형태인데 이러한 함수를 const 함수라고 한다. get 엑세스 함수만 적용되는 것이 아닌 모든 함수들이 const 키워드만 붙어 있다면 적용된다.

이러한 함수에 const가 붙게 되면 의미하게 되는 것은 이 함수 내에서는 멤버 변수에 저장된 값을 변경하지 않겠다 라는 의미를 갖게 된다. , const 함수에서는 멤버 변수의 값을 변경하는 코드가 존재하면, 혹은 그럴 가능성, 변경할 수 있는 능력이 존재하는 코드가 존재한다면 컴파일 에러를 발생한다.

멤버 변수의 값을 변경할 수 있는 능력이나 가능성이 존재하는 코드란 const화 되지 않은 함수의 호출을 뜻한다. 왜냐하면 const화 되지 않은 함수는 호출됨으로써 멤버 변수를 어떠한 형태로든 변경할 수 있는 능력과 가능성이 존재하기 때문이다. 그렇기 때문에 const 함수 내에서는 const화 되지 않은 함수의 호출을 제한한다.

+ const 참조자를 이용한 함수 호출 또한 const화된 함수만을 호출할 수 있다.

위와 같은 두 가지 조건에 의해서 const 함수를 선언하고 사용하게 되면 많은 함수들을 const화 시켜야 한다. 그렇지만 그만큼 작성된 코드는 안정성이 높아진다.

캡슐화

  • 캡슐화

캡슐화는 관련 있는 함수와 변수를 하나의 클래스 안에 묶는 것. 그러나 어려운 개념이다 왜냐하면 캡슐화의 범위를 결정하는 일이 쉽지 않기 때문이다.

캡슐화에는 정보 은닉 개념이 기본적으로 포함 된다.

728x90
반응형
728x90
반응형

클래스와 객체

  • 구조체

연관 있는 데이터를 하나로 묶으면 프로그램의 구현 및 관리가 용이하다.

소프트웨어란 데이터의 표현 + 데이터의 처리로 표현이 되는데, 여기서 데이터는 항상 관련 있는 주제, 특징, 특성에 따라 부류를 형성한다. 이러한 부류를 형성하는 데이터들은 항상 함께 생성되고, 함께 사용되고, 함께 소멸되는 특성이 있고, 그래야만 하는 경우가 대부분이다. 구조체는 연관 있는 데이터를 묶고 부류를 형성할 수 있도록 하는 문법적 장치이다.

위와 같이 구조체를 생성하고, 사용할 수 있다. 13번 라인에서는 C언어와 다르게 struct 키워드를 사용하지 않고 구조체를 선언할 수 있도록 문법적으로 구성되어 있다.

  • 구조체에 함수 삽입하기

구조체는 연관 있는 부류의 데이터를 묵는 문법적인 수단이었다. 그렇다면 소프트웨어의 구성에서 데이터의 처리 부분에 해당하는 함수 또한 관련 데이터 부류와 함께 묶어서 처리하면 되지 않을까?

위의 예제에서 데이터 표현 부분은 관련 부류를 형성하여 구조체를 이루었다. 그러나 deposit, withdraw 함수는 기능적으로 account 구조체의 데이터 부류에 대해서 처리를 담당하는 함수 임에도 불구하고 구조체와 서로 분리되어 있다. 이 함수들 또한 부류를 형성하여 같이 묶는다면 다음과 같다.

                          <a. 멤버 함수를 클래스 내부에 정의>                                                <b. 멤버 함수를 클래스 외부에 정의>             

관련 있는 부류의 데이터와 처리에 해당하는 모든 부분을 하나의 구조체에 넣은 형태이다. 이런 식으로 구조체안으로 들어간 함수를 해당 구조체의 멤버 함수라고 한다. 구조체로 들어간 함수를 호출할 때에는 a 그림의 32번 라인, 33번 라인과 같이 구조체의 멤버 변수를 지정할 때와 마찬가지로 직접 접근 연산자 (혹은 포인터의 경우 간접 접근 연산자)를 이용하여 함수를 호출한다.

그림 a와 그림 b의 차이는 함수의 정의를 구조체 내부에 하는지, 구조체 외부로 따로 정의하는 지의 차이이다. 함수의 정의가 너무 길고 복잡할 때에는 가독성을 위해 함수의 선언 만을 구조체 정의 내부에 두고 함수의 정의는 구조체 외부에 따로 둔다. 이 때 구조체 이름과 범위 지정 연산자를 이용하여 함수의 정의를 외부에 두는데 그림 b25, 31, 36번 라인과 같은 형식으로 정의하면 된다. 구조체 내부에 함수의 정의가 존재하면 함수를 인라인(in-line)으로 처리하라는 의미이다. 함수를 구조체 외부에 둔 경우에 인라인 처리를 원한다면 따로 출력 타입 앞에 inline 키워드를 넣음으로써 인라인 화 할 수 있다.

지금까지의 구조체는 모두 클래스의 일종으로 간주되며, 멤버 변수와 멤버 함수를 넣을 수 있었던 것 모두 클래스의 일종이기 때문이고 이러한 정의의 구조체를 ‘클래스’ 라고 표현할 수 있다.

  • 클래스와 구조체의 차이점

위의 왼쪽 예는 구조체라고 정의되어 있지만 사실 멤버 함수를 포함할 수 있는 클래스의 일종 혹은 클래스라고 할 수 있다. 그렇기 때문에 오른쪽과 같이 키워드를 class로 변경하고 클래스를 정의한다.

그러나 왼쪽 16번 라인에서 구조체일 때는 가능했던 초기화가 클래스로서 인정하고 변경을 하고 보니 오른쪽 16번 라인에서는 초기화가 불가능하다. 16번 라인과 같은 초기화 뿐만 아니라 다음과 같은 경우도 불가능하다.

위의 예의 공통점들은 클래스의 외부에서 클래스 내의 모든 멤버들에 접근하여 쓰는 혹은 읽는 작업을 하고 있다. 즉 외부에서 접근한다는 것이 공통점이다. 외부에서 접근하는 모든 문장에 대해서는 컴파일 에러를 내고 있다.

클래스는 접근에 대한 지시를 따로 명시하지 않는다면 기본적으로 외부로부터의 모든 접근을 허용하지 않으며, 이 때 클래스 내에서 선언된 모든 멤버들은 모두 클래스 내에서만 접근하는 내부 접근만 가능하다. 이렇게 클래스는 구조체와 다르게 외부 접근에 대해서 허용 범위를 별도로 선언해야 하는 것이 차이점이다.

  • 접근 제어 지시자(접근 제어 레이블)

위의 예제는 접근 제어 지시자를 적용한 account 클래스를 나타낸 것이다. 먼저 접근 제어 지시자의 효력의 범위는 접근 제어 지시자를 명시한 라인부터 다음 접근 제어 지시자가 나오는 라인 바로 전 까지가 효력이 끼치는 범위이다. , 8번 라인의 private 영역의 효력은 다음 접근 제어 지시자인 public의 바로 전 라인인 11번 라인까지 private의 효력을 유지한다. 8번 라인부터 11번 라인까지 있는 멤버 변수들은 외부에서 접근이 절대적으로 금지된다. 반면에 12번 라인부터 클래스의 끝 까지는 public 접근 제어 지시자의 영역으로 멤버 함수들에 대해서는 외부에서의 접근을 허용한다. 그러므로 외부에서는 account 클래스의 멤버 함수에 접근하여 원하는 기능을 실행할 수 있도록 적절한 멤버 함수를 정의 및 선언해주어야 하고, 이러한 멤버 함수를 통해 멤버 변수의 조작이 이루어져야 한다. 아니 그렇게 설계되어야 한다!

  • 파일 분할 시 클래스의 선언과 정의에 대한 정보

클래스의 선언

컴파일러가 클래스와 관련된 문장의 오류를 잡아내는데 필요한 최소한의 정보, 클래스를 구성하는 외형적인 틀을 보여준다. 위의 예에서 account 클래스와 관련된 문장의 옳고 그름을 판단하는데 사용된다.

-> 컴파일의 정보로 사용되므로 헤더 파일에 저장한 후, 필요한 위치에 포함될 수 있도록 하면 됨.
, 인라인 함수의 경우에는 함수 정의가 분리되었다고 하더라도 헤더 파일에 함께 포함해야 한다. 왜냐하면? 컴파일 과정에서 함수의 호출 문이 있는 곳에 함수의 몸체가 대치되어야 하기 때문이다.

클래스의 정의

멤버 함수의 정의는 다른 문장의 컴파일에 필요한 정보를 가지고 있지 않다. 따라서 컴파일 된 이후에 링커에 의해 하나의 실행파일로 묶이기만 하면 된다.

-> 소스 파일에 저장해서 컴파일이 되도록 하면 된다.

  • 객체지향 프로그래밍

먼저, 객체는 현실에 존재하는 모든 사물 또는 대상이 될 수 있다. 이런 객체의 개념을 이용한 객체지향 프로그래밍이란? 현실에 존재하는 사물과 대상, 그리고 그에 따른 행동을 있는 그대로 실체화를 시키는 형태의 프로그래밍

이러한 객체를 이루는 것은 하나 이상의 상태 정보(데이터)하나 이상의 행동(기능)으로 구성된다

클래스 기반의 객체 생성 방법 및 활용 (두 가지)

   - 일반적인 변수 선언 방식

   - 동적 할당 방식

Message Passing 방법

 -> 하나의 객체가 다른 하나의 객체에게 메시지를 전달하는 방법은 함수 호출을 기반으로 한다! 함수 호출을 기반으로 하는 객체 간의 대화법에서 함수 호출하는 행위를 메시지 전달이라고 함.

728x90
반응형

'컴퓨터 언어 정리 > C++ 언어' 카테고리의 다른 글

13 생성자와 소멸자  (0) 2020.09.12
11, 12 정보 은닉과 캡슐화  (0) 2020.09.11
09 C언어의 표준 함수 호출  (0) 2020.09.10
08 Malloc과 free의 대체 – new와 delete  (0) 2020.09.10
07 참조자(Reference)  (0) 2020.09.08
728x90
반응형

포인터 기본

  • 포인터

포인터란 변수 형태의 포인터와 상수 형태의 포인터를 어우르는 표현

  • 포인터 변수

포인터 변수는 주소 값의 저장을 목적으로 선언되는 변수이다. c언어의 low 레벨 특성을 나타내는 요소이고, 이러한 포인터를 이용하여 메모리에 직접 접근이 가능하다.

  • c언어에서의 주소 표현

c언어에서는 메모리 상의 주소를 표현할 때 시작 번지만을 가지고 위치를 표현한다. 왜냐하면 각 자료형마다 지니는 바이트 값을 시작 주소로부터 더하여 계산하면 해당 변수의 끝 번지를 계산할 수 있기 때문이다.

  • 포인터 변수의 선언과 & 연산자

다음은 포인터 변수의 선언과 & 연산자의 활용 예를 나타낸다.

위의 예에서 시사해야 할 점은 두 가지이다. 첫 번째는 포인터 변수의 선언 및 사용이고 두 번째는 포인터 변수와 일반 변수의 크기의 차이이다.

먼저 포인터 변수의 선언 및 사용에 관한 이야기이다.

7번 라인에서 일반 변수를 선언하였고, 8번 라인에서는 * (asterisk) 를 이용하여 ‘ int * ‘ (정수형 포인터 타입) 형 변수 pNum을 선언하고 num시작 주소 값을 대입한다. (char, int, double 등과 같이 char *, int *, double * 등 또한 새로운 자료형 타입으로 인정해야한다.) 그리고 12번 라인에서 pNumnum 변수의 주소 값을 출력해 보았을 때 결과가 같은 결과로 나왔는데 이는 당연한 것이다. pNum에는 num시작 주소 값을 대입했기 때문이다. 이처럼 포인터 변수인 pNum은 일반 변수의 주소 값을 저장한다! 그리고 13번 라인에서 * 연산자를 이용하여 pNum이 지니고 있는 주소 값으로 직접 찾아가 직접 접근을 하여 직접 접근한 메모리에 들어 있는 값을 출력했다. 그렇기 때문에 num과 마찬가지로 출력 값이 100으로 같은 결과를 보였다. , *(Asterisk) 연산자는 포인터 변수가 저장하고 있는 주소에 접근 혹은 참조(= 포인터가 가리키고 있는 메모리에 접근 혹은 참조)하는 연산을 수행한다.

다음으로는 포인터 변수와 일반 변수의 크기 차이이다.

12번 라인에 해당하는 결과 num4, pNum8이 나왔는데 int라는 유사점이 있는 변수인데 이렇게 다른 결과가 표출되는 이유로는 포인터는 주소를 표현하는 변수이기 때문에 컴퓨터 시스템이 표현할 수 있는 메모리의 크기 범위와 밀접한 연관이 있다. 64비트 시스템에서는 2^64 만큼의 메모리 번지 표현 경우의 수가 존재하기 때문에 포인터 변수는 자연스럽게 8바이트(=64비트)를 나타내는 것이다. 포인터 변수의 크기는 자료형에 상관없이 컴퓨터 시스템의 메모리 범위에 따라 4바이트(32비트 시스템) 혹은 8바이트(64비트 시스템)로 표현된다.

그렇다면 포인터 변수에서 자료형을 어떻게 일치시켜서 대입해야 하는지, 자료형은 왜 필요한지에 대해 알아보자. 다음은 포인터 변수에 일반 변수의 주소 값을 대입 연산 시 일치시켜야 할 타입들에 대한 정리 표이다.

먼저, 자료형을 어떻게 일치시켜서 대입해야 하는지에 대한 정리이다.

어차피 포인터 변수의 크기는 다 똑같은데 포인터 변수에 타입이 필요한 이유가 무엇인가? 그 이유는 해당 포인터 변수에 저장된 시작 주소로부터 몇 바이트를 읽어 들여야 하고, 그 읽어 들인 값을 정수로 해석해야 할지, 실수로 해석해야 할지를 결정한다. , 포인터의 타입()은 메모리 공간을 참조하는 기준이 된다.

int a = 10;
int * p = &a;
printf(“ %d “, *p); //
이 문장에서 p를 참조할 때 int * 는 참조의 기준이 된다.

  • 포인터의 잘못된 사용과 널 포인터

잘못된 초기화

int * ptr = 1234; // 1234 번지로 들어가게 된다.
*ptr = 100; // 1234
번지는 메모리 상에 중요한 프로그램이 적재되어 있을 수 있는데 해당 공간의 값을 변경 -> 에러

초기화 할 경우에는 NULL 포인터로 초기화를 한다. 아무 곳도 가리키지 않음을 의미한다. ( 0번지 의미가 아님 )

int * ptr = NULL; // 아무 곳도 가리키지 않는 포인터.


728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

10 포인터와 함수  (0) 2020.09.13
09 포인터와 배열  (0) 2020.09.12
07 1차원 배열  (0) 2020.09.11
06 함수와 변수의 생명주기  (0) 2020.09.10
05 반복과 분기  (0) 2020.09.08
728x90
반응형

1차원 배열

  • 배열

둘 이상의 변수를 모아 놓은 것.

  • 배열의 선언

일반 변수의 선언과 같다.

‘ 자료형 + 배열 이름 + 길이 정보(인덱스) ‘ 형식.

위의 선언을 메모리 공간 상의 그림으로 표현하면 다음과 같이 나타낼 수 있다.

arr 배열

 

  • 배열의 접근

배열의 인덱스는 배열의 개수를 [n] 으로 두었을 때, 0부터 시작하여 n-1까지 존재하며 [0]은 첫 번째 요소를, [n-1]은 마지막 요소를 나타낸다.

  • 배열의 선언과 동시에 초기화

다음은 배열의 선언과 동시에 초기화하는 예이다.

여기에서 우리는 중괄호 내의 초기화 값 리스트를 ‘초기화 리스트’ 라 부른다. 8번 라인의 경우에는 일반적인 선언과 동시의 초기화 방법이다. 9번 라인의 경우는 배열의 길이 정보가 주어지지 않은 상태인데 이는 초기화 리스트를 보고 자동으로 계산하여 배열의 길이가 세팅 된다. (위의 예에서는 5) 마지막으로 10번 라인의 경우는 배열의 길이보다 적은 초기화 리스트가 오게 되는데 0번째 인덱스부터 순차적으로 초기화 리스트의 값들이 할당되고 할당할 초기화 값이 없는 인덱스 요소에 대해서는 전부 0으로 자동 초기화한다.

  • 배열 이름을 대상으로 하는 sizeof 연산자



sizeof(배열이름); 을 실행할 경우, ‘ 바이트 단위의 배열의 크기 ’가 반환된다.

  • 문자 배열과 문자열 배열

같은 ‘ hello ‘를 표현하는 배열인데 두 배열은 전혀 다른 배열이다. 9번 라인은 문자만을 저장한 문자 배열이고, 12번 라인은 문자열을 저장한 문자열 배열이다. c언어에서 배열에 문자열을 저장할 때에는 ‘\0’ 이라는 문자열의 끝을 나타내는 특수 문자인 널(null) 문자를 문자열의 제일 끝에 자동으로 삽입하여 대입 및 저장을 행한다. ( 이는 문자열을 입력 받는 라이브러리를 통한 변수로의 입력 시에도 마찬가지로 null 문자의 자동 삽입이 이루어진다. )
null문자를 이용하여 메모리 공간 상에서 배열 이름의 첫 번지부터 어디 까지가 의미 있는 문자열의 끝인지를 확인한다. 또한 이러한 문자열 배열에 대한 입출력 서식 문자는 ‘ %s ‘ 로 사용한다.

728x90
반응형

'컴퓨터 언어 정리 > C 언어' 카테고리의 다른 글

09 포인터와 배열  (0) 2020.09.12
08 포인터 기본  (0) 2020.09.11
06 함수와 변수의 생명주기  (0) 2020.09.10
05 반복과 분기  (0) 2020.09.08
04 상수와 자료형  (0) 2020.09.07

+ Recent posts