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 &ref1 = arr[0]; int &ref2 = arr[1]; int &ref3 = arr[2];
포인터 변수의 참조
int num = 12; int *ptr = # 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형 포인터므로 적절한 형 변환을 거쳐야 함
- malloc, free 의 불편사항
키워드 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; }