Skip to main content

[C++ Primer Plus] Chapter 4. 복합 데이터형

C++ 기초 플러스 책을 읽고 공부한 노트입니다.




배열 #

  • 배열을 선언할 때 원소의 개수는 다음 중에 하나여야 한다.
  1. 정수 상수
  2. 상수 수식
int values1[40];  // 1

const int length = 40;
int values2[length];  // 2

int values3[10 * sizeof(int)];  // 3

  • 생성과 동시에 초기화할 수 있다. 반면, 초기화를 나중에 할 수는 없다.
int arr[3] = {1, 2, 3}; 

  • 초기화할 때 처음 원소를 명시적으로 하면 나머지는 알아서 0이 된다.
int arr[3] = {0};

  • [] 속을 비우면 초기화 개수 대로 배열이 만들어진다.
  • 배열의 원소 개수는 sizeof(arr) / sizeof(int) 로 구할 수 있다.
int arr[] = {1, 2, 3};  // 3개의 int형 데이터를 가진 배열로 만들어진다. 

  • 배열 초기화 시 = 가 없어도 된다.
  • 중괄호 공백으로 0으로 다 초기화할 수 있다.
  • 초기화 시 narrowing은 안 된다.
int arr[] {1, 2, 3, 4};
int arr[] {};
int arr[] {1, 2, 3.3333, 4};  // (X)



문자열 #

  • 모든 문자열의 마지막 문자는 널 문자(\0)여야 한다.
  • 문자열 상수로 배열을 초기화할 때 널 문자까지 고려해야 한다.
  • [] 속을 배우면 알아서 널 문자까지 고려 해 준다.
  • 배열이 문자열 상수보다 더 크면 널 문자로 계속 채운다.
char arr[] = "Hello";  // 6개의 char형 데이터를 가진 배열로 만들어진다. 

  • ‘S’는 문자이다.
  • "S"는 문자열이며 S문자와 널 문자가 합쳐진 두 개의 문자로 이루어진 문자열이다.
    • 따라서 아래와 같은 것은 안 된다.
char keyword = "S"; (X)

  • White space(빈칸, 탭, 캐리지리턴)로 분리된 두 개의 문자열 상수는 하나의 문자열 상수로 결합된다.
cout << "Hello " "My friend";  // "Hello My friend"

  • <cstring> 헤더 파일에 있는 strlen()함수는 배열의 전체 크기가 아니라 배열에 저장된 문자열의 크기를 리턴한다. 널 문자는 제외한다.



문자열 읽기 #

  • cin은 White Space를 문자열의 끝으로 간주한다. 그래서 중간에 White Space가 있는 문자열을 입력하면 White Space 전까지만 저장하고 그 이후 문자들은 입력 큐에 남겨 놓는다. 그러면 다음 cin에서는 사용자 입력을 받지 않고 입력 큐에 남아있던 문자들을 저장한다.

  • 전체 한 줄의 문자열을 저장하기 위해서는…
  • cin.getline(배열이름, 배열크기)
    • getline은 매개변수로 입력받은 (배열크기 – 1)크기를 넘어서거나, 개행 문자를 만나면 개행 문자를 널 문자로 교체하고 입력한 후 읽기를 종료한다.
    • getline이 지정된 배열 크기를 넘어서는 문자를 입력받으면 나머지는 입력 큐에 남겨두고, failbit를 설정한다.
  • cin.get(배열이름, 배열크기)
    • get은 개행 문자를 입력 큐에 남겨둔다. 다음 입력이 개행 문자를 입력하는 것을 방지하기 위해 cin.get()을 사용해 문자 하나를 읽어버릴 수 있다. 아니면 cin.get(배열이름, 배열크기).get() 이렇게 할 수도 있다.
    • get이 빈 행을 읽으면 failbit라는 것이 설정되어서 추가적은 입력을 막는다. 이것을 복원하려면 cin.clear()를 해주어야 한다.

함수 입력 버퍼남김 무엇을 읽으면 failbit?
cin White Space 이전까지 개행 문자
cin.getline(배열이름, 크기) 개행까지 X (개행은 널로 대체) 초과 크기의 문자열
cin.get(배열이름, 크기) 개행까지 개행 문자 빈 행
if (cin.fail() == 1) 
{
    cin.clear();                // 내부 상태 플러그 초기화
    cin.ignore(INT_MAX, '\n');  // 해당 길이 만큼 or 개행 문자까지 읽어서 입력 버퍼를 비운다. 
}



string 클래스 #

  • 리스트 초기화가 가능하다.
char arr[] = {"Hello"};  
string str = {"Hello"};

문자배열 string
크기 선언만 하면 크기가 제각각이다.
이후에 cin으로 입력을 저장하면 지정된 크기 만큼만 저장된다.
선언만 하면 크기가 0이다.
이후에 cin으로 입력을 저장해도 자동으로 크기를 조절해 문자열을 넣는다.
대입과 추가 C라이브러리 <cstring>를 사용해서 strcpy(arr1, arr2)로 복사한다.
strcat(arr1, arr2)로 덧붙인다.
=로 대입한다.
+, += 으로 덧붙인다.
길이구하기 길이는 strlen(arr) 로 구할 수 있다. 길이는 str.size() 로 구할 수 있다.

// 문자배열
cin.getline(arr, arrSize);

// string
// <istream>은 string형을 인식하지 못한다. 
getline(cin, str);



다른 형태의 문자열 상수 #

  • wchar_t, char16_t, char32_t
    • L, u, U 접두사를 사용해서 문자열 상수로 초기화할 수 있다.
wchar_t arr[] = L"Hello";
  • 접두사 R을 붙여서 raw 문자열을 사용할 수도 있다.



구조체 #

  • 여러 종류의 데이터를 모아서 하나의 단위로 묶어서 저장할 수 있다.
  • 함수도 멤버로 가질 수 있다.
struct Student 
{ 
    char name[20]; 
    int age; 
};

Student s1 = { "KimKim", 1 };
Student s2 {};    // =를 생략할 수도 있다, 0으로 모두 초기화.
cout << s1.age;

struct            // 데이터형 이름 없음. 구조체의 정의와 동시에 변수생성.
{ 
    char name[20];
    int age; 
} s1, s2;   

  • 구조체 배열
Student sArr[2] = { { "KimKim", 1 }, { "SoSo", 2 } };



공용체 #

  • 여러 종류의 데이터를 모아서 하나의 단위로 묶었지만, 하나의 데이터만 보관할 수 있다.
  • 공용체의 크기 = 제일 큰 멤버의 크기
union OneForAll 
{ 
    int Num; 
    char Name[20]; 
};



열거체 #

  • 0부터 순서대로 대입되며, 명시적으로 지정하면 기본값은 무시된다.
enum Color { Red, Orange, Yellow, Blue, Green, Purple };

  • int형으로 자동으로 승급될 수 있다.
int number = 3 + Red;

  • 반대로 int형이 자동으로 enum형으로 되진 않는다.
Color c = Color (3);

  • 대입연산자만 사용할 수 있고, 산술 연산자는 안 된다.
++c;  // (X)

  • 주로 기호 상수들을 정의하는 용도로 사용되므로 이름을 생략할 수도 있다.
enum { Red, Orange, Yellow, Blue, Green, Purple };

  • 어떤 정수값이 열거체 값 범위 안에 들어있으면, 그 값이 열거자 값이 아니더라도, 데이터형 변환을 통해서 열거체 변수에 대입할 수 있다.



포인터 #

  • int * 이든 double *이든 그 크기는 같다. (컴퓨터 시스템에 따라 다름)
int number = 7;
int * pointer = &number;

// *pointer == number  == number의 값
//  pointer == &number == number의 주소값

int * ptr, num; // ptr은 포인터형으로 num은 int형으로 생성된다. 

  • 포인터에 직접 주소를 대입할 수 있다. 데이터형을 반드시 명시해야 한다.
int * number = (int *) 0xB8000000;



new연산자로 런타임에 메모리 할당 (C의 malloc 대체) #

int * ptr = new int;  
  • 여기서 ptr이 가리키고 있는 메모리의 이름(number같은 변수 이름)이 없다. 그냥 메모리 블록을 가리키고 있다. 그래서 ptr을 통해서만 메모리 접근이 가능하다.
  • 컴퓨터 메모리가 부족해서 new의 메모리 할당 요청을 허용할 수 없으면 new0을 리턴한다. 이렇게 값이 0인 포인터는 널 포인터라고 부른다. 널 포인터는 무언가 일이 잘못되었다는 것을 나타낼 때 사용된다.



delete 연산자로 메모리 해제 #

int * ptr = new int;  
delete ptr;
  • 메모리 누수를 방지하기 위해 다 쓴 메모리는 delete 연산자를 사용해 메모리를 해제해 주어야한다.
  • delete로 해제된 메모리를 또 다시 delete로 해제하면 안 된다. 보통의 변수로 대입한 메모리도 delete로 해제할 수 없다.
  • 널 포인터를 delete하면 아무일도 일어나지 않는다 (안전하다)



new를 사용해 생성한 동적 배열 (동적 바인딩) #

int * ptr = new int [10]; 
delete [] ptr;
  • *ptr이나 ptr[0]으로 배열의 첫번째 원소에 접근 가능 하다.
  • ptr + 1을 하면 ptr이 가리키는 데이터형의 크기만큼 한 칸 뒤로 가서, ptr[0]은 두번째 원소인 ptr[1]을 가리키게 된다.



포인터와 배열 #

int numbers[3] = {1, 2, 3};
int * ptr = numbers; 
  • numbers == &numbers[0]

    • numbers 배열의 첫 번째 원소의 주소값
  • numbers != &numbers

    • &numbers는 배열 전체의 주소값이다.
    • 따라서 numbers + 1은 다음 원소의 주소값이지만,
    • &numbers + 1을 하면 전체 배열을 넘어간 다음의 주소값이다.
  • *numbers == numbers[0]

    • numbers 배열의 첫 번째 원소의 값
  • *(numbers + 1) == numbers[1]

    • numbers 배열의 두 번째 원소의 값

  • 여기서 numbers 대신 ptr을 넣어도 똑같다.
  • 다른점:
    • (1) ptr은 값을 변경할 수 있지만 배열 이름인 numbers는 값을 변경할 수 없다.
    • (2) sizeof(numbers)는 배열 전체의 크기이지만 sizeof(ptr)은 포인터의 크기이다.

  • 포인터 배열과 배열 포인터
short * ptr [20];	// short* 형 포인터가 20개 있는 배열
short (*ptr)[20];	// short 형 자료가 20개 있는 배열을 가리키는 포인터



포인터와 문자열 #

  • 배열의 이름, 포인터, 문자열 상수 모두 동등하게 첫번째 문자열의 주소를 나타낸다.
char arr[10] = "beautiful";    
cout << arr;

char * ptr = "beautiful";      
cout << ptr;

cout << "beautiful"

  • 문자열 상수나 초기화되지 않은 포인터를 문자열 입력에 절대 사용하지 않아야 한다. 포인터의 경우, 초기회되지 않았다면, 입력된 문자가 어디에 저장될지 알 수 없어진다.
const char * ptr1 = "notgood";  
cin >> ptr1;    

char * ptr2;
cin >> ptr2  //(X)

  • strncpy(arr1, arr2, 최대 문자 수);
    • 이것은 최대 문자수가 다 안 담기면 널 문자를 추가하지 않는다. 따라서 반드시 수기로 추가해야 한다.



new를 사용한 동적 구조체 생성 #

연산자 형식
도트 멤버 연산자 [구조체의 이름.멤버], [(*포인터).멤버]
화살표 멤버 연산자 [구조체를 지시하는 포인터->멤버]
struct Flower 
{ 
    char name[20]; 
    double price; 
};

Flower * f = new Flower;

cin.get(f->name, 20);
cin >> f->price;    
cin >> (*f).price;

delete f;



데이터 저장을 위한 메모리 공간의 종류 #

  • 자동 공간(automatic)
    • 변수들이 자신이 정의되어있는 함수가 호출되는 순간에 생겨나서, 함수가 종료되는 시점까지만 존재한다. (블록 안에서만 유효하다)
    • 스택에 저장된다. 따라서 순차적으로 저장되고 역순으로 해제된다.

  • 정적 공간(static)
    • 프로그램이 실행되는 동안에 지속적으로 존재하는 공간이다.
    • 함수 외부에서 변수를 정의하거나 static 키워드로 정의하면 된다.
static double fee = 56.5;

  • 동적 공간(힙)(dynamic)
    • newdelete을 사용해서 동적으로 메모리를 할당하고 해제한다.
    • 힙에 저장된다.



배열의 대안 #

  • vector 템플릿 클래스
    • string 클래스처럼 자동으로 런타임에 메모리가 할당/해제 된다. (힙)
vector<int> vec(10);  
// 10개 원소를 가진 int형 배열. 
// 10자리에 변수가능.
// 자동으로 0으로 모두 초기화 됨.

  • array 템플릿 클래스
    • 크기가 고정되어 크기를 런타임에 바꿀 수 없다. (스택)
array<int, 10> arr;    // 10자리에 변수 불가능.