spec의 정리노트

spec98.egloos.com

포토로그



C++ 기초플러스 정리-2 BackToTheBasic



3장. 데이터 처리

C++에 내장된 데이터형에는 기본형과 복합형이 있다.
기본형에는 정수를 표현할 수 있는 정수형과, 소수부가 있는 수를 표현할 수 있는 부동 소수점형으로 구분된다.
복합형은 4장에서 다루며 배열, 문자열, 포인터, 구조체와 같은 형이 있다.

어떠한 정보를 컴퓨터에 저장하기 위해서는 다음과 같은 사항이 필요하다.

  • 어디에 저장되는가?
  • 어떠한 값이 저장될 것인가?
  • 어떤 종류의 정보인가?

예를 들기 위해 “int braincount = 5;” 를 살펴보자.
일단 “int braincount”을 통해 정수형의 정보를 저장할 것이라는 것을 알 수 있다.
또한 마지막 “= 5” 문을 통하여 이곳에 정수 5 라는 값을 저장하라고 명령한다.
현재 코드에서는 어디에 저장될지는 정확하게 파악하기는 힘들지만 ”&braincount” 와 같이 이후에 나올 주소연산자 ‘&’를 이용하여
해당 정보가 저장될 위치가 어디일지 파악할 수가 있다.
즉 종합하면 &braincount 로 알 수 있는 메모리상의 주소에 정수형을 저장할 수 있는 공간을 확보하고 그 공간에 5라는 정수를 저장한다.

변수 이름 규칙

변수의 이름은 내가 원하는 대로 자유롭게 지어줄 수는 있지만 몇 가지 제한사항이 존재한다.

  • 변수 이름에는 영문자, 숫자, 밑줄(_) 문자만이 사용 가능하다.
  • 숫자를 변수 이름의 첫 문자로 사용할 수는 없다.
  • 변수 이름에서 대문자와 소문자는 구분된다.
  • C++의 키워드는 변수 이름으로 사용할 수 없다.
  • 두 개의 밑줄 문자롤 시작하는 이름이나, 밑줄 문자와 대문자로 시작하는 이름은 컴파일러와 리소스가 사용하기로 예약되어 있다. 하나의 밑줄로 시작하는 이름은 컴파일러와 리소스가 전역 식별자로 사용하기로 예약되어 있다.
    (이는 컴파일시 에러를 일으키지는 않으나 최종 프로그램에서 중복된 이름에 의해 문제가 발생할 여지가 존재한다)
  • 변수 이름의 길이에는 제한이 없다.(ANSI C에서는 63개까지의 문자만이 유효하였었다)

제한사항은 아니지만 다음과 같이 프로그래머로서 지켜야만 할 몇 가지 규칙이 있다.
다만 이는 필수 요소는 아니며 어떤 규칙을 따를 것인지는 프로그래머의 재량에 달려 있다.

  • 의미를 쉽게 알 수 있는 변수 이름을 사용하자.
  • 관례적으로 my_onions와 같이 밑줄로 단어를 구분하거나 myOnions와 같이 첫 단어를 제외하고 각 단어들의 첫 문자를 대문자로 사용한다.
  • 서로간에 약속된 접두어를 가능하면 사용하도록 한다.
    (‘g_’ 로 시작하면 전역 변수라거나 ‘m_’ 로 시작하면 클레스의 멤버 변수라고 하는 식)

자료형의 크기

C++의 표준에는 자료형의 크기가 정확히 얼마인지에 대한 정의가 있지 않다.
대신 다음과 같이 정의되어 좀더 유연함을 가진다.

  • shourt 형은 최소한 16비트 폭을 가진다.
  • int형은 최소한 short만큼은 크다.
  • long형은 최소한 32비트 폭을 가지며, 최소한 int만큼은 크다

따라서 변수의 크기를 외우는 것은 전혀 의미가 없다.
이보다는 sizeof() 를 사용하거나 climits(구식 C++에서는 limits.h)에 정의된 형_MAX, 형_MIN 과 같은 정의를 사용하는 것이 좋다.
특히 sizeof()는 함수가 아니라 연산자의 일부이므로 컴파일 할 때 고정된다.
따라서 아무리 많이 사용하더라도 실제 프로그램을 운용할 때 처리시간이 필요하지 않다.

연산자

기본적인 산술연산자

연산자 의미
+ 두 개의 피연산자를 더한다
- 두 개의 피연산자를 뺀다
* 두 개의 피연산자를 곱한다
/ 두 개의 피연산자를 나눈다
% 첫 번째 피연산자를 두 번째 피연산자로 나누었을 때의 나머지를 구한다.

C++에서는 위 표에서 나와 있는 5가지의 기본적인 연산자를 지원한다.
하지만 C++의 또 다른 특징 중 하나인 연산자 오버로딩에 의해 각 연산자가 수행할 동작을 변경할 수도 있다.
일단은 기본적인 동작이 위와 같다고만 알고 있으면 된다.

연산자를 이용한 계산에서 주의할 점이 있는데 피연산자가 동일한 형을 가지고 있다면 별다른 문제가 되지 않지만 만약 두 피연산자가 서로 다른 데이터형을 가지고 있다면 약간의 문제가 발생할 여지가 있다.
예를 들어 정수형과 실수형을 더한다면 어떠한 일이 일어날 것인가 이다.
물론 이때를 대비하여 C든 C++든 자동 형변환이 이루어져 좀더 상위의 데이터형으로 변경이 되어 정상적인 연산이 이루어진다.
이때의 변환 규칙에 대해서는 쉽게 찾아볼 수 있으므로 설명은 생략하기로 한다.

다만 나눗셈의 경우 주의할 점이 있다.
예를 들어 3/2를 하였을 경우 예상되는 값은 1.5이어야 하지만 실제 결과는 둘 다 정수형이기 때문에 1로 연산된다는 점이다.
이를 방지하기 위해 3/(double)2 와 같이 명시적으로 최소한 하나이상의 피연산자를 실수형으로 변경해 주거나 3/2. 과 같이 소수점을 찍어주는 방법을 택할 수도 있다.

 

4장. 복합 데이터형

복합 데이터형에는 클레스, 구조체, 열거체, 공용체, 포인터, 배열등이 포함되며 기본적으로는 기본 데이터형을 통해 만들어진다.
하지만 복합 데이터형을 통해서 또 다른 복합 데이터 형이 만들어지기도 한다.

데이터 형 용도
배열 동일한 데이터형의 값 여러 개를 연속적으로 저장
구조체 종류가 다른 여러 데이터형의 값을 함께 저장
포인터 데이터가 저장되어 있는 주소를 저장
공용체 종류가 다른 여러 데이터형이 동일한 영역을 함께 사용
열거체 기호 상수를 만드는데 사용된다.
클레스 종류가 다른 여러 데이터 형 및 함수를 함께 저장

배열(array)

배열은 데이터형이 같은 여러개의 값을 연속적으로 저장할 수 있는 데이터의 구조이다.
굳이 기본형 데이터형이 아닌 복합 데이터형이라 할지라도 배열로서 사용할 수 잇다.
배열의 선언은 다음과 같이 한다.

datatype arrayName[arrySize];

배열을 사용하면 각각의 배열의 요소에 접근하기가 매우 쉽다.
예를 들어 array[0]은 array 배열의 첫 번째 원소를 나타내며 array[1]은 array 배열의 두 번째 원소를 나타낸다.
또한 배열의 이름은 해당 배열의 첫 번째 원소가 저장되어 있는 주소를 의미한다.
이러한 점을 이용하여 포인터 연산자와 함께 다음과 같이 사용할 수도 있다.

int array[5];
*(array + 0); // array[0] 과 같은 의미이다
*(array + 3); // array[3] 과 같은 의미이다

다만 배열을 사용할 때 주의해야 할 점은 컴파일러가 배열의 첨자로 적합한 값을 사용했는지 검사를 하지 않는다는 점이다.
예를 들어 위의 배열에서 array[100]을 지정했다 하더라도 컴파일러는 전혀 에러를 발생시키지 않는다.
만약 배열의 크기를 프로그램이 자동으로 알게 하려면 다음의 코드를 쓰는 것은 나쁘지 않다.

short items[] = {1, 2, 3, 4, 5};
int num_elements = sizeof( items ) / sizeof( short )

또한 배열의 크기는 컴파일 할 때 고정되며 stack공간에 할당이 된다.
이는 별도의 메모리 해지과정을 거치지 않아도 되기 때문에 메모리의 누수 없이 편하게 사용할 수 있다는 장점이 있지만,
경우에 따라서는 필요한 공간이 얼마일지 모를때 적합한 배열의 크기를 미리 예측할 수 없다는 문제가 있다.
그렇다고 해서 매우 큰 배열을 사용하는 것은 오히려 메모리 낭비가 심해질 수 있다.
이럴때에는 차라리 new연산자등을 이용한 동적 배열을 생성하는 것이 좋다.

배열을 선언할 때 배열이 할당된 메모리에는 기본적으로 초기화 되지 않은 상태로 할당되어 지나 이를 선언시 다음과 같은 방법으로 바로 초기화 할 수도 있다.

int array[5] = {1, 2, 3, 4, 5};     // 배열의 원소를 각각 1, 2, 3, 4, 5 로 초기화 한다
int array2[5] = {0};                 // 배열의 모든 원소를 0으로 초기화 한다
int array3[5] = {1, 2, 0};          // 배열의 처음 두 원소를 각각 1, 2로 초기화 하며 나머지는 0으로 초기화 한다

또한 배열의 크기를 명시하지 않고 이후 초기화 하는 원소의 수 많큼 할당하도록 할 수도 있다.

char chArray[] = {‘a’, ‘b’, ‘c’};  // 크기 3인 배열을 선언하고 각각 ‘a’, ‘b’, ‘c’로 초기화 한다

C style문자열의 경우 char 배열을 이용하여 문자열을 구현하였으나 문자열입력이 어느 정도인지 예측하기 힘든 경우도 있고 문자열 비교가 쉽지 않다는 문제가 있었다.
그래서 C++에서는 string이라는 문자열 전용의 클레스를 제공하여 배열과 유사한 특성을 가지면서 문자열을 좀 더 쉽고 유연하게 다룰 수 있는 방법을 제공하고 있다.

구조체(struct)

생략

비트필드(union)

추후 정리

공용체(union)

생략

열거체(emum)

enum <열거형 이름> { 목록… };

  1. 열거체 변수에는 그 데이터 형을 정의하는데 사용된 열거체 값들만 대입이 가능하다.
  2. 열겨형을 가지고 연산을 수행하면 열거형은 int형으로 승급되어 연산을 할 수 있게 된다.
  3. 또한 열거체에 대한 열거변수의 대입은 가능하지만 정수형 대입은 불가능하다.
  4. 2와 3을 종합하였을 때 열거형에 열겨형의 연산을 수행하면 열거형의 연산은 int형으로 변화하였으므로 대입이 불가능하게 된다.
  5. 열거형을 정의할 때 기본적으로 첫번째 열거자에 0이 대입되고 이후 순서대로 1씩 증가한다.
       하지만 명시적으로 어떠한 값이 대입될지 정의할 수도 있다.
  6. 열거체에 증감연산자의 사용은 불가하다 하지만 일부 컴파일러의 경우 이러한 제한을 지키지 않기도 한다.
    단 이때 열거체 변수가 아닌 int형으로 변화한다.
  7. 만약 열거체 값만을 사용하고자 한다면 열거형 이름은 생략 가능하다.

포인터

포인터 변수는 일반 변수를 사용할 때와는 달리 변수에 어떠한 값의 주소를 저장한다.
그리고 ‘*’라는 포인터 연산자(책에서는 간접 참조 연산자라고 부르는)를 붙이면 그 주소에 저장되어 있는 값이 된다.

포인터를 선언할 때에는 그것이 지시하는 데이터 형의 크기가 어떠한 것인지 명시해 줄 수 있다.
하지만 void으로 지정하여 지시하는 데이터 형의 크기가 얼마인지에 대한 판단을 나중으로 유보할 수도 있다.
단 이때 void형 포인터가 가리키는 곳에 저장된 값을 사용하고자 할 때에는 명시적인 형 변환을 통해 자료형의 크기를 지정해 주어야만 한다.

int * a;   // 정수형 데이터형이 저장되어 있는 곳을 가리키는 포인터 변수이다.
void *b; // 가르키는 곳의 크기가 얼마인지는 모르지만 주소는 저장할 수 있는 포인터 변수이다.
*a = *(int *)b; // 정수형 포인터가 가리키는 곳에 void형 포인터가 가리키는 곳에 저장되어 있는 내용을 int형의 크기 만큼 대입한다.

void형을 제외하고는 포인터 변수에 대해 증감 연산자를 사용할 수 있다.
이때 증가되는 값의 범위는 포인터 변수를 정의할 때 정의한 그 포인터가 지시하는 데이터형의 크기만큼 변경된다.
또한 컴퓨터가 주소를 정수로서 다루고 있으므로 덧셈, 뺄셈 등도 수행할 수 있지만 곱하거나 나누는 것은 아무 의미가 없다.
왜냐하면 포인터 변수는 위치를 나타내기 때문이다.

포인터를 사용할 때에는 반드시 그 포인터가 지시하는 곳에 데이터가 저장할 메모리를 할당해 주어야만 한다.
이러한 작업을 수행해 주지 않는다면 포인터 변수를 이용하여 값을 저장할 때 어디에 저장될 지 아무도 알 수 없으며 최악의 경우 시스템이 이상 동작을 일으킬 수도 있다.

int *pnData;                  // 포인터 변수를 정의
// int nData;                  // 변수를 저장할 공간 정의
// pnData = &nData;       // 포인터 변수가 가리키는 저장공간을 위에서 정의한 변수공간으로 정의

*pnData = 123;              // 포인터가 가리키는 저장공간에 변수를 대입
                                   // 다만 위에서 포인터가 가리키는 공간에 대한 저장공간을 확보하지 않았으므로 
                                   // 어느 순간 문제가 될 여지가 있다.

컴파일러는 위와 같은 상황에서의 문제점을 알려주지 않으며 실제 프로그램의 동작시 운 좋게 정상 수행될 수도 있고 최악의 경우 시스템이 다운될 수도 있다.

포인터 변수에 값을 대입할 수도 있다.
구형의 컴파일러(C99이전에 나온)은 포인터 변수에 정수형 값을 그대로 대입할 수도 있었지만 이후 검사가 강화되어 포인터 변수에 값을 대입할 때에는 반드시 데이터 형 변환자를 이용하여 주소형으로 변환해 주어야만 한다.

int *pt;
//pt = 0xB8000000;       // 이는 정수형이므로 포인터 변수에 대입하지 못한다.
pt = (int *)0xB8000000;  // 이는 데이터형이 일치하므로 가능하다.

new와 delete

C++에서 새로 추가된 명령어로 new와 delete가 있다.
new는 이전의 malloc()과 같은 역활을 수행하며 delete는 free() 와 유사하다.
new를 통해서는 필요한 만큼의 메모리를 할당해 줄 수 있으며 delete는 new를 통해 할당된 메모리를 해제하는 역활을 수행한다.
단 delete를 통해 한번 해제한 메모리 블록을 다시 해제하려 시도해서는 안된다.
이럴 경우 어떠한 문제가 발생할지 알 수 없다.
다만 널 포인터(0)에 대해 delete를 사용하는 것은 안전하므로 한번 delete를 통해 메모리 블록을 해제하였다면 그것을 가리켰던 포인터 변수를 null(0)으로 변경해 주는 것이 좋다.

new를 사용할때 [] 를 이용하여 동적 배열을 생성할 수 있다.
이렇게 생성한 메모리 블록은 delete사용시 []를 사용해서 해제해 주어야만 한다.

int * pt = new int [100];
delete [] pt;
char * ps;
delete [] ps;         // 사용 불가

메모리 공간

자동공간 : 사용하는 함수 안네엇 정의되는 보통의 변수들을 자동 변수라고 한다. 이는 해당 변수들이 함수가 호출되는 순간에 자동으로 생겨나 그 함수가 종료되는 시점까지만 존재하기 때문이다.
메모리상의 스택 공간에 자동공간은 생성된다.

정적공간 : 정적공간은 프로그램이 실행되는 동안에 지속적으로 존재하는 공간이다. 변수를 정적 공간에 할당하도록 하기 위해서는 변수를 선언할 때 static이라는 키워드를 붙이거나 함수의 외부에서 변수를 선언하면 된다.
메모리상의 스택 공간에 정적공간은 생성되며 프로그램의 시작시 할당된다.

동적 공간 : new나 malloc을 통해 동적으로 생성되는 공간이다. 한번 생성한 공간은 이후 삭제할 때까지 계속 존재하게 된다.
메모리상의 힙공간에 동적 공간이 할당된다.