C++ from A to Z

 · 21 mins read

Table of Contents

Basic of C++

입출력

  • 입출력 수행을 위해 헤더 파일 선언문 #include

  • 입력

    • std::cin >> "변수1" >> "변수2";
      
  • 배열기반 문자열 입출력

    • char name[100];
      std::cin >> name;
      std::cout << "my name is " << name << ".\n"
      
  • 출력

    • std::cout << "출력대상1" << "출력대상2" << "출력대상3" << std::endl; 
      

함수의 오버로딩

  • (Function Overloading)

  • 매개변수의 선언형태가 다르다면, 동일한 이름의 함수정의를 허용

  • 단, 매개변수의 자료형 또는 개수가 달라야 함

    • void Func(int n) { ... }
      int Func(int n) { ... }
      // 위 두 함수는 오버로딩 불가
      

매개변수의 디폴트 값

  • (Default Value)

  • 매개변수에 디폴트 값이 설정되어 있으면, 선언된 매개변수의 수보다 적은 수의 인자전달이 가능

  • 그리고 전달되는 인자는 왼쪽에서부터 채워져 나가고, 부족분은 디폴트 값으로 채워짐

  • int Func(int num1, int num2, int num3=30) { ... }  // O
    int WrongFunc(int num1=10, int num2, int num3) { ... }  // X
    

인라인 (inline) 함수

  • C언어의 매크로 함수와 유사하지만 일반 함수처럼 정의가 가능

  • inline int SQUARE(int x)
    {
        return x*x;
    }
      
    int main(void)
    {
        std::cout<<SQUARE(%)<<std::endl;
        return 0;
    }
    
  • 하지만 자료혀엥 의존적인 함수가 되어 데이터 손실이 발생

    • 이 부분은 template 으로 해결 가능

    • template <typename T>
      inline T SQUARE(T x)
      {
          return x*x;
      }
      

이름공간(namespace)

  • 특정 영역에 이름을 붙여주기 위한 문법적 요소

  • 이름과 매개변수가 동일한 함수라도 이름의 공간을 마련하여 분리하여 사용 가능

  • :: 연산자는 범위지정 연산자(scope resolution operator)라고 하며, 이름공간을 지정할 때 사용

  • #include <iostream>
      
    namespace BestCom
    {
        void SampleFunc(void)
        {
            std::cout<<"BestCom Function"<<std::endl;
        }
    }
      
    namespace ProgCom
    {
        void SampleFunc(void)
        {
            std::cout<<"ProgCom Function"<<std::endl;
        }
    }
      
    int main(void)
    {
        BestCom::SampleFunc();
        ProgCom::SampleFunc();
        return 0;
    }
    
  • 이름공간 기반의 함수 선언과 정의의 구분

    • #include <iostream>
          
      namespace BestCom
      {
          void SampleFunc(void)
      }
          
      namespace BestCom
      {
          void PrettyFunc(void)
      }
          
      namespace ProgCom
      {
          void SampleFunc(void)
      }
          
      int main(void)
      {
          BestCom::SampleFunc();
          ProgCom::SampleFunc();
          return 0;
      }
          
      void BestCom::SampleFunc(void)
      {
          std::cout<<"BestCom Function"<<std::endl;
          PrettyFunc();	// 동일 이름공간을 명시할 필요가 없음
      }
      void ProgCom::SampleFunc(void)
      {
          std::cout<<"ProgCom Function"<<std::endl;
      }
      
  • 이름공간의 중첩

    • namespace Parent
      {
          int num = 2;
          namespace SubOne
          {
              int num = 3;
          }
          namespace SubTwo
          {
              int num = 4;
          }
      }
          
      int main(void)
      {
          std::cout<< Parent::num <<std::endl;
          std::cout<< Parent::SubOne::num <<std::endl;
          std::cout<< Parent::SubTow::num <<std::endl;
          return 0;
      }
      

파일의 분할

  • 헤더파일 : main 함수를 제외한 나머지 두 함수의 선언을 삽입
  • 소스파일 1 : main 함수를 제외한 나머지 두 함수의 정의를 삽입
  • 소스파일 2 : main 함수만 삽입

using을 이용한 이름공간 명시

  • using

  • namespace BestCom
    {
        void SampleFunc(void)
        {
            std::cout<<"BestCom Function"<<std::endl;
        }
    }
    
    • 여기서 매번 BestCom::SampleFunc 같이 선언하기에 불편함이 있음

      • #include <iostream>
        using BestCom::SampleFunc;
        using std::cin;
        using std::cout;
        using std::endl;
              
        int main(void)
        {
            SampleFunc();
            return 0;
        }
        
      • using 선언은 SampleFunc를 이름공간 BestCom에서 찾으라는 일종의 선언
    • 일일이 using 선언을 하는 것도 번거롭다면, 이름공간 std에 선언된 모든 것에 대해 이름공간 지정의 생략을 명령 가능

      • #include <iostream>
        using namespace std;
              
        int main(void)
        {
            ...
            return 0;
        }
        
      • 단, 프로그래밍에 조금 편하지만 그만큼 이름충돌이 발생할 확률이 상대적으로 높아짐

      • 상황을 판단하여 적절히 혼용하는 지혜가 필요
  • 이름공간이 과도하게 중첩될 경우는 드물지만 상황에 의해서 과도하게 사용되었을 때,

    • namespace ABC=AAA:BBB:CCC;
      
    • 위 방법으로 별칭을 줄일 수 있음
  • 범위지정 연산자의 또 다른 기능으로 전역변수가 지역변수에 의해 가려진다는 특징을 해결

    • int val = 100; 	// 전역변수
          
      int SampleFunc(void)
      {
          int val = 20;	// 지역변수
          val += 3;		// 지역변수 val의 값 3 증가
          ::val += 7;		// 전역변수 val의 값 7 증가
      }
      

Const

  • 상수화

  • const int num=10;		// 변수 num을 상수화
    const int * ptr1 = &val1;		// 포인터 ptr1을 이용하여 val1 값 변경 불가능
    int * const ptr2 = &val2;		// 포인터 ptr2가 상수화 됨
    const int * const ptr3 = &val3;	
                         // 포인터 ptr3가 상수화 되었으며, ptr3를 이용하여 val3 값 변경 불가능
    
  • 상수화 변수에 대한 참조자 선언은 아래와 같이 한다.

    • const int num=20;
      const int &ref=num;
      
    • 상수화가 되었다면 어떠한 경로를 통하더라도 값의 변경을 허용하지 않는다.
  • const의 상수 참조

    • const int &ref=50;
      
    • 참조자는 변수만 참조 가능하다고 했지만, 임시로 생성한 변수를 상수화하여 이를 참조자가 참조하게끔 하는 경우는 가능

    • 결과적으로 상수화된 변수를 참조하는 형태

    • 아래와 같이 간단한 함수 호출을 위해 사용

      • int Adder(const int &num1, const int &num2)
        {
            return num1 + num2
        }
        

참조자(Reference)

  • 참조자는 자신이 참조하는 변수를 대신할 수 있는 또 하나의 이름 = 별명(별칭)
  • 참조자가 메모리 공간에 다른 값이 선언되면 변수도 값이 변경됨(같은 메모리 공간을 사용)

  • int num1 = 2020;
    int &num2 = num1;	// num1이라는 이름이 붙어있는 메모리 공간에 num2라는 이름이 하나 더 생김
    
    • 이미 선언된 변수의 앞에 이 연산자가 오면 주소 값의 반환을 명령하는 뜻이 되지만, 새로 선언되는 변수의 이름앞에 등장하면, 참조자의 선언을 뜻함

    • int *ptr = &num1;	// 변수 num1의 주소 값을 반환하여 포인터 ptr에 저장
      int &num2 = num1;	// 변수 num1에 대한 참조자 num2를 선언
      
  • 참조자 수에는 제한이 없으며, 참조자를 대상으로도 참조자를 선언할 수 있음

    • int num1 = 2759;
      int &num2 = num1;
      int &num3 = num1;
      int &num4 = num1;
      
  • 참조자의 선언 가능 범위

    • 참조자는 무조건 선언과 동시에 변수를 참조하도록 해야 함

    • 참조자는 변수에 대해서만 선언 가능, 선언됨과 동시에 누군가를 참조

    • 상수를 대상으로 참조자를 선언할 수 없음

    • 참조의 대상을 바꾸는 것은 불가능

    • 배열 요소의 참조

      • int arr[3] = {1, 3, 5};
        int &amp;ref1 = arr[0];
        int &amp;ref2 = arr[1];
        int &amp;ref3 = arr[2];
        
    • 포인터 변수의 참조

      • int num = 12;
        int *ptr = &num;
        int **dptr = &ptr;
              
        int &ref = num;
        int *(&pref) = prt;
        int **(&dpref) = dptr;
        

참조자와 함수

  • call-by-value & Call-by-reference

    • // Call-by-value : 값을 인자로 전달하는 함수의 호출방식
      // 함수외부에 선언된 변수에 접근이 불가능
      int Adder (int num1, int num2)
      {
          return num1+num2;
      }
          
      // Call-by-reference : 주소 값을 인자로 전달하는 함수의 호출방식
      // 주소 값을 이용한 Call-by-reference
      // 두 개의 주소 값을 받아서, 그 주소가 참조하는 영역에 저장된 값을 직접 변경
      void SwqpByRef(int * ptr1, int * ptr2)
      {
          int temp = *ptr1;
          *ptr1 = *ptr2;
          *ptr2 = temp;
      }
          
      // 참조자를 이용한 Call-by-reference
      void SwqpByRef2(int &ptr1, int &ptr2)
      {
          int temp = ptr1;
          ptr1 = ptr2;
          ptr2 = temp;
      }
      
  • 참조자를 이용한 Call-by-reference와 const 참조자

    • 함수 내에서 참조자를 통한 값의 변경을 진행하지 않을 경우, 참조자를 const로 선언해서 함수의 원형만 봐도 값의 변경이 이뤄지지 않음을 알 수 있게 해야 함

    • int num=24;
      HappyFunc(num);
          
      // 아래의 경우 함수의 몸체까지 문장 단위로 확인해서
      // 참조자를 통한 값의 변경이 이루어지는지를 확인해야 함
      void HappyFunc(int &ref) { ... }
          
      // 위와 같은 경우를 대비하기 위해
      // 함수 내에서 참조자를 통한 값의 변경을 진행하지 않을 경우 참조자를 const로 선언
      void HappyFunc(const int &ref) { ... }
          
      
  • 반환형이 참조형(Reference Type)인 경우

    • int& RefRetFuncOne(int &ref)
      {
        ref++;
        return ref;
      }
          
      int main(void)
      {
          int num1=1;
          int &num2 = RefRetFuncOne(num1);
      }
      
    • 위와 같은 경우 num1은 RefRetFuncOne 함수의 참조형 반환으로 num2라는 별칭이 생기는데, 매개변수로 선언된 참조자 ref는 지역변수와 동일한 성격을 갖으므로 RefRetFuncOne 함수가 반환을 하면, 참조자 ref는 소멸됨

    • 여기서 주의해야 할 부분은 지역변수를 참조형으로 반환하면 안된다는 것

      • int& RetuRefFunc(int n)
        {
            int num=20;
            num+=n;
            return num;
        }
              
        int main(void)
        {
            int &ref = RetuRefFunc(10);
        }
        
      • 위 함수는 지역변수 num에 저장된 값을 반환하지 않고, num을 참조 형태로 반환하므로

      • 지역변수 num에 ref라는 또 하나의 이름이 붙고 함수 반환이 완료되면 정작 지역변수 num은 소멸되어 버림

new & delete

  • malloc & free 를 대신하는 함수

    • malloc, free 의 불편사항
      • 할당할 대상의 정보를 무조건 바이트 크기단위로 전달해야 함
      • 반환성이 void형 포인터므로 적절한 형 변환을 거쳐야 함
  • 키워드 new(=/malloc)

    • int * ptr1 = new int;		// int형 변수의 할당
      double * ptr2 = new double;	// double형 변수의 할당
      int * arr1 = new int[3];	// 길이가 3인 int형 배열의 할당
      double * arr2 = new double[7];	// 길이가 7인 double형 배열의 할당
      
  • 키워드 delete(=/free)

    • delete ptr1;		// 앞서 할당한 int형 변수의 소멸
      delete ptr2;		// 앞서 할당한 double형 변수의 소멸
      delete []arr1;		// 앞서 할당한 int형 배열의 소멸
      delete []arr2;		// 앞서 할당한 double형 배열의 소멸
      
  • malloc & free와 new & delete의 비교

    • #include <iostream>
      #include <string.h>
      using namespace std;
          
      char * MakeStrAdr(int len)
      {
          // char * str = (char*)malloc(sizeof(char)*len);
          char * str = new char[len];
          return str;
      }
          
      int main(void)
      {
          char * str = MakeStrAdr(20);
          strcpy(str, "I am so happy~");
          cout<<str<<endl;
              
          // free(str);
          delete []str;
          return 0;
      }
      
  • 객체의 생성에는 반드시 new & delete 를 사용

  • new 연산자를 이용해서 할당된 메모리 공간도 변수로 간주하여 참조자의 선언이 가능

C++의 표준함수

  • C언어 헤더파일의 확장자인 .h를 생략하고 앞에 c를 붙이면 C언어에 대응하는 C++의 헤더파일 이름이 됨

    • #include <cstudio>		// #include <stdio.h>
      #include <cstdlib>		// #include <stdlib.h>
      #include <smath>		// #include <math.h>
      #include <cstring>		// #include <string.h>
      


Basic of Class

구조체

  • 구조체(struct)는 연관 있는 데이트를 묶을 수 있는 문법적 장치

  • 구조체 안에 함수 삽입

    • ...
      #define ID_LEN		20
      #define MAX_SPD		200
      #define FUEL_STEP	2
      ...
          
      struct Car
      {
          char gameID[ID_LEN];
          int fuelGauge;
          int curSpeed;
              
          void ShowCarState()	 // 구조체 밖에 선언되었다면 car.gamerID 로 변수에 접근 
          {   // 함수가 구조체 내에 삽입되면서 구조체 내에 선언된 변수에 직접접근이 가능
              cout<<gamerID<<endl;	
              cout<<fuelGauge<<"%"<<endl;
              ...
          }
          void Accel()
          {
              ...
          }
          void Break()
          {
                  
          }
      }
          
      ...
              
      int main(void)
      {
          Car run01 = {"run01", 100, 0};
          run01.Accel();
          run01.Break();
          run01.ShowCarState();
          ...
      }
      
  • 구조체 안에 enum 상수의 선언

    • struct Car
      {				
          enum	// 열거형 enum을 이용하여 구조체 내에서만 유요한 상수를 정의
          {		// 전역 매크로 상수는 #define ID_LEN	20 형태로 선언
          	ID_LEN		=20,
              MAX_SPD		=200,
              FUEL_STEP	=2,
              ...
          };
              
          char gameID[ID_LEN];
          int fuelGauge;
          int curSpeed;
              
          void ShowCarState()	
          {   
              cout<<gamerID<<endl;	
              cout<<fuelGauge<<"%"<<endl;
              ...
          }
          void Accel()
          {
              ...
          }
          void Break()
          {
                  
          }
      }
          
      ...
              
      int main(void)
      {
          Car run01 = {"run01", 100, 0};
          run01.Accel();
          run01.Break();
          run01.ShowCarState();
          ...
      }
      
  • 이름공간을 이용한 상수 선언(enum보다 높은 가독성)

    • ...
              
      namespace CAR_CONST
      {
           enum	
          {		
          	ID_LEN		=20,
              MAX_SPD		=200,
              FUEL_STEP	=2,
              ...
          };
      }
      struct Car
      {	// 구조체 안에서 namespace 내 상수를 사용할 경우 범위지정 연산자를 사용하여 접근
          char gameID[CAR_CONST::ID_LEN];
          int fuelGauge;
          int curSpeed;
              
          void ShowCarState()	 
          {   
              cout<<gamerID<<endl;	
              cout<<fuelGauge<<"%"<<endl;
              ...
          }
          void Accel()
          {
              ...
          }
          void Break()
          {
                  
          }
      }
          
      ...
              
      int main(void)
      {
          Car run01 = {"run01", 100, 0};
          run01.Accel();
          run01.Break();
          run01.ShowCarState();
          ...
      }
      
  • 함수를 외부로 분리

    • 구조체 내의 정의된 함수의 수가 많거나 길다면, 함수의 종류와 기능이 한눈에 들어오게끔 작성

    • 함수의 원형선언을 구조체 안에 두고, 함수의 정의를 구조체 밖으로 빼내는 것

    • 구조체 안에 함수가 정의되어 있으면 ‘‘함수를 인라인으로 처리하라’‘는 의미가 내포됨

      • 반면, 함수를 아래와 같이 구조체 밖으로 빼내면, 이러한 의미가 사라지므로 inline을 이용하여 인라인 처리를 명시적으로 지시해야 함

      • inline void Car::ShowCarState()
        inline void Car::Accel()
        inline void Car::Break()
        
    • #include <iostream>
      using namespace std;
          
      namespace CAR_CONST
      {
      	enum
      	{
      		ID_LEN		 = 20,
      		MAX_SPD		 = 200,
      		FUEL_STEP	 = 2,
      		ACC_STEP	 = 10,
      		BRK_STEP	 = 10
      	};
      }
          
      struct Car
      {
      	char gamerID[CAR_CONST::ID_LEN];
      	int fuelGauge;
      	int curSpeed;
          
      	void ShowCarState();
      	void Accel();
      	void Break();
      };
          
      void Car::ShowCarState()
      {
      	cout << "소유자ID: " << gamerID << endl;
      	cout << "연료량: " << fuelGauge << "%" << endl;
      	cout << "현재속도: " << curSpeed << "km/s" << endl << endl;
      }
      void Car::Accel()
      {
      	if (fuelGauge <= 0)
      		return;
      	else
      		fuelGauge -= CAR_CONST::FUEL_STEP;
          
      	if ((curSpeed + CAR_CONST::ACC_STEP) >= CAR_CONST::MAX_SPD)
      	{
      		curSpeed = CAR_CONST::MAX_SPD;
      		return;
      	}
          
      	curSpeed += CAR_CONST::ACC_STEP;
      }
      void Car::Break()
      {
      	if (curSpeed < CAR_CONST::BRK_STEP)
      	{
      		curSpeed = 0;
      		return;
      	}
          
      	curSpeed -= CAR_CONST::BRK_STEP;
      }
          
      int main(void)
      {
      	Car run01 = { "run01", 100, 0 };
      	run01.Accel();
      	run01.ShowCarState();
      	run01.Break();
      	run01.ShowCarState();
      	return 0;
      }
      

클래스와 객체