'분류 전체보기'에 해당되는 글 99건

  1. 2018.10.23 Level Streaming
  2. 2018.09.27 C++ Milliseconds example
  3. 2018.09.16 중력가속도와 적분 응용 예
  4. 2018.09.14 C++ Object File IO example
  5. 2018.09.14 C++ Binary File Copy example
  6. 2018.09.13 Text File CRUD example 1
  7. 2018.09.12 C++ Text File IO example
  8. 2018.09.08 STL LinkedList 1
  9. 2018.09.08 C++ Template example
  10. 2018.09.04 Visual Studio 2017 Conformance Setup
Level Streaming Volume2018. 10. 23. 23:21

언리얼 엔진에서 레벨 스트리밍 방법


게임 환경(레벨)은 지하동굴과 같은 환경일 수도 있고 지상의 개활지와 같은 환경일 수도 있다. 또한 이런 환경들이 결합되어 시시각각 다른 환경으로 전환되면서 진행될 수도 있다.

동굴과 밖을 오가며 게임이 진행된다면 동굴 속에서는 보이지 않는 바깥 환경은 화면에 보일 필요가 없으므로 메모리에 저장해 둘 필요가 없을 것이다. 이런 경우에 한동안 화면에서 보이지 않는 일부의 레벨을 메모리에서 제거하려면 레벨 스트리밍(Level Streaming) 방법을 적용해야 한다


레벨 스트리밍을 사용하기 위해서는 하나의 큰 레벨을 사용하지 않고 다수개의 레벨 파일을 생성하여 각각의 레벨파일에 액터를 나누어 저장하고 해당 레벨을 실행시간에 필요시마다 로드/언로드하여 메모리 사용량을 줄일 수 있다.

블루프린트에서 특정 레벨파일을 메모리에 로드/언로드하려면 Load Stream Level / Unload Stream Level 함수노드를 사용하면 된다


Level Streaming Volume을 사용하면 트리거 볼륨과 블루프린트를 사용하지 않아도 카메라가 볼륨 안에 위치하면 해당 레벨파일을 자동으로 로드해주므로 편리하다. 볼륨을 약간 겹치도록 설정하면 레벨이 끊어지지 않도록 보이게 할 수도 있다


퍼시스턴트 레벨

 - 항상 로드되어 있는 레벨


스트리밍 레벨

 - 필요시마다 로드/언로드할 수 있는 레벨


디폴트로 생성된 레벨은 퍼시스턴트 레벨이다

퍼시스턴트 레벨과 분리하여 실행시간에 로드/언로드하고자 하는 레벨은 스트리밍 레벨이라고 한다

스트리밍 레벨파일은 레벨 관리창에서 생성할 수 있고 별도의 레벨파일에 저장해야 한다

퍼시스턴트 레벨은 언로드할 수 없고 항상 로드된 상태이다


스트리밍 레벨 관리창 열기

언리얼 에디터의 메뉴 > 창 > 레벨 


[레벨] 콤보박스를 누르고 [새로 생성...]을 선택하면 스트리밍 레벨을 저장할 레벨파일을 새로 생성할 수 있다


스트리밍 레벨 파일을 저장할 폴더를 생성하거나 선택한다


레벨파일이 생성되면 [퍼시스턴트 레벨] 노드의 하위에 포함되고 파란색으로 선택된 상태가 된다.


위와 같이 현재 파란색으로 선택된 레벨이 있을 경우에 에디터로 드래그하여 새로 액터를 추가하거나 새로 복사하여 생성되는 액터는 모두 현재 선택된 레벨에 포함되어 해당 레벨파일에 저장된다


스트리밍 레벨에 액터 추가하기

위와 같이 특정 스트리밍 레벨이 선택된 상태에서 언리얼 에디터로 나가서 퍼시스턴스 레벨에 포함된 플로어를 ALT키를 눌러 복사하면 복사된 플로어는 레벨 관리창에서 현재 선택된 스트리밍 레벨에 저장된다

모드 > 기본 을 선택하고 Sphere등도 드래그하여 레벨에 배치하는 경우에도 레벨 관리창에서 현재 선택된 레벨에 포함된다

CB에서 드래그하여 뷰포트에 놓는 경우에도 레벨 관리창에 현재 선택된 레벨에 포함되어 저장된다


카메라가 스트리밍 레벨의 근처로 접근할 경우 해당 레벨 로드하기

보이지 않는 스트리밍 레벨의 근처에 트리거 볼륨을 배치하고 카메라가 트리거 볼륨에 들어오면 특정 스트리밍 레벨을 로드하고 보여주는 예를 작성해보자


아래의 그림처럼 카메라가 스트리밍 레벨 근처에 접근하면 알 수 있도록 Trigger Volume를 배치한다. 이 때 볼륨을 드래그하여 레벨에 배치할 때는 반드시 레벨 관리창에서 퍼시스턴트 레벨이 파란색으로 선택된 상태이어야 한다. 레벨 관리창에서 퍼시스턴트 레벨을 더블클릭하거나 마우스 우측을 누르고 [현재로 만들기]를 누르면 파란색으로 선택된다. 그래야만 퍼시스턴트 레벨의 레벨블루프린트에서 트리거 볼륨의 참조에 접근할 수 있고 충돌 이벤트를 정의할 수 있다.



만약 트리거 볼륨을 드래그하여 배치할 때 레벨 관리창에서 퍼시스턴트 레벨이 아닌 다른 레벨이 파란색으로 선택되어 있다면 퍼시스턴트 레벨의 레벨블루프린트에서 트리거의 참조를 얻을 수 없으므로 먼저 트리거 볼륨을 퍼시스턴트 레벨로 이동해야 한다.

아래 그림처럼 레벨 에디터에서 트리거 볼륨을 선택하고 레벨 관리창의 퍼시스턴스 레벨에 마우스 우측을 누르고 [선택된 액터를 레벨로 이동] 항목을 클릭하면 이동할 수가 있다


레벨 관리창에서 퍼시스턴트 레벨의 [레벨 블루프린트 열기] 아이콘을 클릭한다



스트리밍 레벨의 로드 및 언로드

퍼시스턴트 레벨의 레벨 블루프린트 그래프에 다음과 같이 입력한다

카메라가 트리거 볼륨에 들어오면 Load Stream Level 함수 노드와 스트리밍 레벨 파일의 이름을 이용하여 레벨을 로드하는 명령이다. 언로드할 때는 Unload Stream Level 함수 노드를 사용하면 된다



게임을 실행하고 카메라를 트리거 볼륨 안으로 이동해보면 스트리밍 레벨이 로드되고 화면에 출력되는 것을 확인할 수 있다



레벨 스트리밍 볼륨(Level Streaming Volume) 사용하기

레벨 스트리밍 볼륨(Level Streaming Volume)을 사용하면 트리거 볼륨과 블루프린트를 사용하지 않고도 위와 같은 효과를 낼 수가 있다. 레벨 스트리밍 볼륨 안에 카메라가 들어오면 그 볼륨과 연결된 스트리밍 레벨 파일이 자동으로 로드되고 나가면 레벨파일이 언로드된다


레벨 스트리밍 볼륨 설정


각각의 스트리밍 레벨 파일은 반드시 필요하다

레벨 관리창에서 퍼시스턴트 레벨을 파란색이 되도록 선택하고 레벨 스트리밍 볼륨을 뷰포트에 배치한다

퍼시스턴트 레벨은 항상 로드되어 있어야 하는 레벨이므로 스트리밍 레벨이 로드되지 않았을 때라도 레벨 스트리밍 볼륨은 퍼시스턴트 레벨에 존재해야만 카메라의 접근을 볼륨이 감지하여 다른 스트리밍 레벨을 로드하는 트리거로 사용될 수 있기 때문이다.


레벨 관리창에서 연결하고자 하는 레벨파일을 선택하고 [레벨 디테일] 아이콘을 클릭하여 디테일 창을 연다

[Streaming Volumes] 항목 우측의 + 아이콘을 클릭하여 노드를 생성하고 

[Streaming Volumes] 항목 좌측의 조그만 삼각형을 클릭하여 노드를 노출시킨다

나타난 콤보박스를 클릭하여 현재의 레벨파일에 연결(바인드)하고자 하는 Streaming Volume의 이름을 찾아 선택한다

이렇게 바인드된 레벨 스트리밍 볼륨 안에 카메라가 들어가면 바인드된 레벨파일이 자동으로 로드되고 화면에 보여진다

레벨 스트리밍 볼륨의 크기는 제한이 없고 포함하는 레벨 전체를 덮을 필요도 없지만 상식적으로 레벨 전체를 덮어줘야 해당 레벨 안에서 이동 중일 때 볼륨 밖으로 나가게 되면 레벨이 사라지므로 볼륨이 해당 레벨 전체를 충분히 감싸 주는 것이 좋겠다.

레벨 스트리밍 볼륨의 영역과 포함하는 레벨의 영역이 반드시 일치할 필요는 없으나 카메라가 이동시에 없었던 레벨이 갑자기 나타나는 비현실적 상황을 방지하기 위해서는 레벨 전체를 덮고 인접 레벨도 일부 포함하는 것이 좋다


Posted by cwisky
Unreal C++/C++ Milliseconds2018. 9. 27. 14:49

C++에서 밀리초(Milliseconds) 사용하는 예


#include <chrono> // c++ 11부터 지원


// ...


using namespace std::chrono;


milliseconds start = duration_cast< milliseconds >(

    system_clock::now().time_since_epoch()

);


// 측정 대상 로직


milliseconds end = duration_cast< milliseconds >(

    system_clock::now().time_since_epoch()

);


cout <<"처리 경과시간:" << end.count() - start.count() << endl;


Posted by cwisky

중력가속도를 알고 적분을 이용하여 속도와 거리를 구하는 예


#include <iostream>


using namespace std;


int main()

{

cout << "적분을 이용한 중력가속도와 거리 테스트" << endl;


// 가속도의 시간에 대한 적분 결과는 속도(시간당 거리)이다

// 속도의 시간에 대한 적분 결과는 거리(위치, 좌표)이다


//가속도는 G = 9.8 * t^0 으로 볼 수 있으므로 시간 t에 대한 적분이 가능하다


float G = -9.8F;    // 중력가속도(아래로 작용하므로 음수,  스크린인 경우에는 양수이어야 함)

float t = 0.0F;      // 시작 시점부터 현재까지 누적된 시간(초)

float C1 = 0.0F;    // 초기속도를 나타내는 상수

float C2 = 0.0F;    // 초기위치를 나타내는 상수


// G를 시간 t에 대하여 적분하여 속도를 구하면 다음과 같다 ( G * t^0 를 적분한다)

float Vy = G * t + C1;     // 적분상수 C1는 t=0일 때의 초기속도


// 속도(Vy)를 시간 t에 대하여 적분하여 거리를 구하면 다음과 같다

float y = 0.5 * G * ( t * t ) + ( C1 * t ) + C2;    // 적분상수 C2는 초기 위치(초기 y값)


// 위의 식에서 0.5 대신에 1/2을 사용하면 안된다. 정수끼리 나눗셈하면 정수가 나오기 때문

cout << "초기의 위치 y=" << y << endl;


// 단위시간(1프레임)당 100미터의 속도로 바닥(0,0)에서 위를 향해 발사된 포탄이 

// 바닥에 도달하기 까지 위치(좌표)를 화면에 표시해보고 정점의 위치를 확인한다


C1 = 100;  //초기속도 

C2 = 0;  //초기위치


do {

y = 0.5 * G * ( t * t ) + ( C1 * t ) + C2;     // 0.5 대신에 1/2을 사용하면 안됨

cout << "y = " << y << endl;

t++;

} while (y >= 0);


return 0;

}




아래의 예는 화면 아래에서 위를 향하여 볼을 발사하면 점차 상승속도가 줄어들면서 결국 바닥으로 떨어지면서 가속된다


MainWindow.h


#ifndef MAINWINDOW_H

#define MAINWINDOW_H


#include <QMainWindow>

#include <QPaintEvent>

#include <QTimerEvent>


class MainWindow : public QMainWindow

{

    Q_OBJECT


public:

    MainWindow(QWidget *parent = 0);

    ~MainWindow();


    float G, t;

    int64_t startMS;

    float initSpd, initPos, spdX, spdY, x, y;


    void paintEvent(QPaintEvent *e);

    void timerEvent(QTimerEvent *e);


    void gameLoop();

};


#endif // MAINWINDOW_H




MainWindow.cpp


#include "mainwindow.h"

#include <QPainter>

#include <QDateTime>


MainWindow::MainWindow(QWidget *parent)

    : QMainWindow(parent)

{

    G = 300.0F;//9.8F;

    t = 0;

    initSpd = -500;

    initPos = 500;

    spdX = 0;

    spdY = initSpd;

    x = 300;

    y = initPos;

    startTimer(40);

    startMS = QDateTime::currentMSecsSinceEpoch();

}


MainWindow::~MainWindow()

{

}


void MainWindow::paintEvent(QPaintEvent *e)

{

    QPainter painter(this);

    painter.setRenderHint(QPainter::Antialiasing);


    painter.drawEllipse(x,y,100,100);

}

void MainWindow::gameLoop()

{

    int64_t duration = QDateTime::currentMSecsSinceEpoch()-startMS;

    t = duration/1000.0F;

    y = 0.5F * G * (t * t) + ( initSpd * t ) + initPos;

    repaint();

}

void MainWindow::timerEvent(QTimerEvent *e)

{

    gameLoop();

}



main.cpp


#include "mainwindow.h"

#include <QApplication>


int main(int argc, char *argv[])

{

    QApplication a(argc, argv);

    MainWindow w;

    w.resize(650,600);

    w.setWindowTitle("Acceleration Movement");


    w.show();


    return a.exec();

}

Posted by cwisky
Unreal C++/C++ Object IO2018. 9. 14. 18:15

바이너리 모드로 객체를 파일에 입/출력하는 예


다수개의 클래스를 이용할 때 클래스 간의 연결문제를 경험하고 해결하기 위한 연습이므로 클래스 구성에 대한 다른 견해가 있을 수 있습니다


Main.cpp

#include <iostream>

#include <fstream>

#include <string>

#include <cstring>


#include "Student.h"

#include "StudentDAO.h"

#include "StudentView.h"

#include "StudentMgr.h"


using namespace std;


int StudentDAO::StudentCnt = 0;


int main()

{

StudentMgr mgr;

mgr.Process();


cout << "프로그램 종료...." << endl;

return 0;

}


Student.h

#pragma once

#include <cstring>


class Student {

int id;

char name[50];

public:

Student() {}

Student(int id, char* name);

void SetId(int id);

void SetName(char* name);

int GetId();

char* GetName();

};


Student.cpp

#include "Student.h"


Student::Student(int id, char * name){

this->id = id;

strcpy(this->name, name);

}


void Student::SetId(int id){

this->id = id;

}


void Student::SetName(char * name){

strcpy(this->name, name);

}


int Student::GetId(){

return id;

}


char * Student::GetName(){

return name;

}


StudentMgr.h

#pragma once


#include "Student.h"

#include "StudentView.h"

#include "StudentDAO.h"


class StudentMgr

{

public:

StudentMgr(){}

StudentView view;

StudentDAO dao;


void Process();

void list();

void insert();

void search();

void update();

void remove();

};


StudentMgr.cpp

#include <iostream>

#include "StudentMgr.h"

#include "Student.h"


using namespace std;


void StudentMgr::Process()

{

bool go = true;


while (go)

{

char m = view.Menu();

switch (m)

{

case 's': list(); break;

case 'i': insert(); break;

case 'f': search();  break;

case 'u': update();  break;

case 'd': remove();  break;

case 'x': go = false; break;

default: view.PrintError("입력오류");

}

}

}


void StudentMgr::list()

{

Student** arr = dao.GetList();

view.PrintLine();

view.PrintList(arr);

view.PrintLine();

}


void StudentMgr::insert()

{

Student* pSt = view.InputSt();

bool inserted = dao.Insert(pSt);

if (inserted) view.PrintMsg("저장 성공");

else view.PrintMsg("저장 실패");

}


void StudentMgr::search()

{

int id = view.InputSearchKey();

Student* pSt = dao.Search(id);

view.PrintSt(pSt);

}


void StudentMgr::update()

{

Student* pSt = view.InputUpdateSt();

bool updated = dao.Update(pSt);

view.PrintUpdateResult(updated);

}


void StudentMgr::remove()

{

int del = view.InputDeleteKey();

bool deleted = dao.Delete(del);

view.PrintDeleteResult(deleted);

}


StudentDAO.h

#pragma once

#include "Student.h"


class StudentDAO {

public:

static int StudentCnt;


StudentDAO() {};

Student** GetList();

int GetCount();

bool Insert(Student* pSt);

Student* Search(int id);

bool Update(Student* pSt);

bool Delete(int id);

};


StudentDAO.cpp

#include "StudentDAO.h"

#include <iostream>

#include <fstream>


using namespace std;


Student ** StudentDAO::GetList() {

ifstream in;

in.open("D:\\test\\student.dat", ios::binary);

if (!in.is_open()) {

cout << "파일열기 실패"<<endl;

return NULL;

}

int StCnt = GetCount();


Student** arr = new Student*[StCnt];

for (int i = 0; i < StCnt; i++) {

Student* pSt = new Student;

if (in.read((char*)pSt, sizeof(*pSt))) {

arr[i] = pSt;

}

}

StudentDAO::StudentCnt = StCnt;

in.close();

return arr;

}


int StudentDAO::GetCount()

{

ifstream in("D:\\test\\student.dat");

if (!in.is_open()) {

cout <<"파일열기 실패"<<endl;

return 0;

}

int cnt = 0;


Student obj; 

while (in.read((char*)&obj, sizeof(obj))) cnt++;

in.close();

return cnt;

}


bool StudentDAO::Insert(Student * pSt)

{

bool inserted = false;

ofstream outFile;

outFile.open("D:\\test\\student.dat", ios::binary | ios::app);


outFile.write((char*)pSt, sizeof(*pSt));

if (outFile.good()) inserted = true;

else inserted = false;

outFile.close();

delete pSt;

return inserted;

}


Student * StudentDAO::Search(int id)

{

ifstream inFile; 

inFile.open("D:\\test\\student.dat", ios::binary); 

Student* pObj = new Student; 

bool found = false;

while (inFile.read((char*)pObj, sizeof(*pObj))) {

if(pObj->GetId() == id){

found = true;

break;

}

inFile.close();

if (!found) pObj = NULL;

return pObj;

}


bool StudentDAO::Update(Student * pSt)

{

fstream file; 

file.open("D:\\test\\student.dat", ios::binary | ios::in | ios::out); 

Student obj;

bool updated = false;

while (file.read((char*)&obj, sizeof(obj))) { 

if (obj.GetId() == pSt->GetId()) { 


obj.SetId(pSt->GetId());

obj.SetName(pSt->GetName());

int pos = -1 * sizeof(obj); 

file.seekp(pos, ios::cur); 

file.write((char*)&obj, sizeof(obj));

cout << "학생정보 변경 성공" << endl;

if (file.good()) {

updated = true;

break;

}

file.close();

return updated;

}


bool StudentDAO::Delete(int id)

{

Student obj; 

ifstream inFile; 

inFile.open("D:\\test\\student.dat", ios::binary); 


ofstream outFile;

outFile.open("D:\\test\\temp.dat", ios::out | ios::binary); 

bool found = false;

while (inFile.read((char*)&obj, sizeof(obj))) { 

if (obj.GetId() != id) { 

outFile.write((char*)&obj, sizeof(obj)); 

}

else {

found = true;

}

}


inFile.close(); 

outFile.close(); 

remove("D:\\test\\student.dat");

rename("D:\\test\\temp.dat", "D:\\test\\student.dat");

return found ? true : false;

}


StudentView.h

#pragma once

#include "Student.h"

#include <string>


using namespace std;


class StudentView {

public:

char Menu();

void PrintList(Student** arr);

Student* InputSt();

int InputSearchKey();

int InputDeleteKey();

Student* InputUpdateSt();

void PrintSt(Student* pSt);

void PrintUpdateResult(bool updated);

void PrintDeleteResult(bool delted);

void PrintMsg(string msg);

void PrintError(string msg);

void PrintLine();

};


StudentView.cpp

#include "StudentView.h"

#include "StudentDAO.h"

#include "Student.h"


#include <iostream>


char StudentView::Menu()

{

cout << "메뉴: 리스트(s), 추가(i), 검색(f), 수정(u), 삭제(d), 종료(x):";

char m;

cin >> m;

return m;

}


void StudentView::PrintList(Student ** arr)

{

cout << "  ***** 학생정보 리스트 *****" << endl;

for (int i = 0; i < StudentDAO::StudentCnt; i++) {

cout << arr[i]->GetId() << "\t";

cout << arr[i]->GetName() << endl;

}

for (int i = 0; i < StudentDAO::StudentCnt; i++) {

delete arr[i];

}

delete[] arr;

}


Student * StudentView::InputSt()

{

int id;

string name;


cout << "번호 이름 (공백으로 구분):";

cin >> id >> name;

Student* pSt = new Student(id, (char*)name.c_str());

return pSt;

}


int StudentView::InputSearchKey()

{

cout << "검색할 학생 번호:";

int id;

cin >> id;

return id;

}


int StudentView::InputDeleteKey()

{

cout << "삭제할 학생 번호:";

int id;

cin >> id;

return id;

}


Student * StudentView::InputUpdateSt()

{

cout << "수정할 학생번호 이름:";

int id;

string name;

cin >> id >> name;

Student* pSt = new Student;

pSt->SetId(id);

pSt->SetName((char*)name.c_str());

return pSt;

}


void StudentView::PrintSt(Student * pSt)

{

if (pSt) {

cout << "  ***** 검색된 학생정보 *****" << endl;

cout << pSt->GetId() << "\t" << pSt->GetName() << endl;

delete pSt;

}

else {

cout << "  검색된 정보가 없습니다" << endl;

}

}


void StudentView::PrintUpdateResult(bool updated)

{

string msg;

if (updated) msg = "정상적으로 회원정보를 수정했습니댜";

else msg = "회원정보를 수정하지 못했습니다";

cout << "  --> 회원정보 수정결과:" << msg << endl;

}


void StudentView::PrintDeleteResult(bool delted)

{

string msg;

if (delted) msg = "정상적으로 회원정보를 삭제했습니댜";

else msg = "회원정보를 삭제하지 못했습니다";

cout << "  --> 회원정보 삭제결과:" << msg << endl;

}


void StudentView::PrintMsg(string msg)

{

cout << "  --> " << msg << endl;

}


void StudentView::PrintError(string msg)

{

cerr << "  --> " << msg << endl;

}


void StudentView::PrintLine()

{

cout << "------------------------------------------------------------" << endl;

}



Posted by cwisky

C++ 을 이용하여 이미지 파일을 한번에 읽어서 복사하는 예


#include <iostream>

#include <fstream>

#include <string>


using namespace std;


int main()

{

cout << "바이너리 파일 다루기" << endl;

//파일의 크기를 미리 확인하여 한번에 읽어서 다른 파일에 복사하는 예


ifstream in;

in.open("D:\\test\\mountains.jpg", ios::binary);


// 파일의 전체 크기를 확인한다

in.seekg(0, ios::end);

int length = in.tellg();

cout << "읽어올 파일의 전체 크기:" << length <<" 바이트"<< endl;


// 파일의 전체 크기만큼 메모리에 로드한다

in.seekg(0, ios::beg);

char* buf = new char[length];

in.read(buf, length);

in.close();


// 메모리에 저장된 파일 데이터를 다른 파일에 저장한다

ofstream out;

out.open("D:\\test\\copy.jpg", ios::binary);

out.write(buf, length);

out.close();


cout << "파일 복사 성공(D:\\test\\copy.jpg)" << endl;

return 0;

}


Posted by cwisky
Unreal C++/Text File CRUD2018. 9. 13. 17:42

앞서 작성했던 기능을 클래스 사용 버전으로 변경한 예


헤더파일과 소스파일을 분리하지 않고 한개의 파일에 모든 클래스를 선언함


#include <iostream>

#include <string>

#include <fstream>

#include <sstream>

#include <cstring>


using namespace std;


class MemVO {

private:

int id; string name; string phone;

public:

MemVO() {}

MemVO(int id, string name, string phone) :id(id), name(name), phone(phone) {

}

void SetId(int id) { this->id = id; }

void SetName(string name) { this->name = name; }

void SetPhone(string phone) { this->phone = phone; }

int GetId() { return id; }

string GetName() { return name; }

string GetPhone() { return phone; }

};



class MemDAO {

public:

static int MemCnt;


MemDAO() {};

MemVO** GetList();

int GetCount();

bool Insert(MemVO* pMem);

MemVO* Search(int id);

bool Update(MemVO* pMem);

bool Rewrite(MemVO** arr, int sz);

bool Delete(int id);

};



class MemView {

public:

char Menu() {

cout << "메뉴: 리스트(s), 추가(i), 검색(f), 수정(u), 삭제(d), 종료(x):";

char m;

cin >> m;

return m;

}


void PrintList(MemVO** arr) {

cout << "  ***** 회원정보 리스트 *****" << endl;

for (int i = 0; i < MemDAO::MemCnt; i++) {

cout << arr[i]->GetId() << "\t";

cout << arr[i]->GetName() << "\t";

cout << arr[i]->GetPhone() << endl;

}

delete[] arr;

}


MemVO* InputMem() {

int id;

string name;

string phone;


cout << "번호 이름 전화(공백으로 구분):";

cin >> id >> name >> phone;

MemVO* pMem = new MemVO(id, name, phone);

return pMem;

}


int InputSearchKey() {

cout << "검색할 회원 번호:";

int id;

cin >> id;

return id;

}


int InputDeleteKey() {

cout << "삭제할 회원 번호:";

int id;

cin >> id;

return id;

}


MemVO* InputUpdateMem() {

cout << "수정할 회원번호 전화:";

int id;

string phone;

cin >> id >> phone;

MemVO* pMem = new MemVO;

pMem->SetId(id);

pMem->SetPhone(phone);

return pMem;

}


void PrintMem(MemVO* pMem) {

if (pMem) {

cout << "  ***** 검색된 회원정보 *****" << endl;

cout << pMem->GetId() << "\t" << pMem->GetName() << "\t" << pMem->GetPhone() << endl;

delete pMem;

}

else {

cout << "  검색된 정보가 없습니다" << endl;

}

}


void PrintUpdateResult(bool updated) {

string msg;

if (updated) msg = "정상적으로 회원정보를 수정했습니댜";

else msg = "회원정보를 수정하지 못했습니다";

cout << "  --> 회원정보 수정결과:" << msg << endl;

}


void PrintDeleteResult(bool delted) {

string msg;

if (delted) msg = "정상적으로 회원정보를 삭제했습니댜";

else msg = "회원정보를 삭제하지 못했습니다";

cout << "  --> 회원정보 삭제결과:" << msg << endl;

}


void PrintMsg(string msg) {

cout << "  --> " << msg << endl;

}


void PrintError(string msg) {

cerr << "  --> " << msg << endl;

}


void PrintLine() {

cout << "------------------------------------------------------------" << endl;

}

};



MemVO** MemDAO::GetList() {

MemView view;

ifstream in("D:\\test\\members.txt");

if (!in.is_open()) {

view.PrintError("파일열기 실패");

return NULL;

}

char input[50];

int lineCnt = GetCount();

int id;

string name;

string phone;

MemVO** arr = new MemVO*[lineCnt];

for (int i = 0; i < lineCnt;i++) {

in.getline(input, 50);

stringstream ss(input);

ss >> id >> name >> phone;

MemVO* pMem = new MemVO(id, name, phone);

arr[i] = pMem;

}

MemDAO::MemCnt = lineCnt;

in.close();

return arr;

}


int MemDAO::GetCount() {

MemView view;

ifstream in("D:\\test\\members.txt");

if (!in.is_open()) {

view.PrintError("파일열기 실패");

return 0;

}


char line[50];

int cnt = 0;


while (!in.eof()) {

in.getline(line, 50);

if (strlen(line)) cnt++;

}


in.close();

return cnt;

}


bool MemDAO::Insert(MemVO* pMem) {

bool Inserted = false;

MemView view;

ofstream out;

out.open("D:\\test\\members.txt", ios::app);


if (!out.is_open()) {

view.PrintError("파일열기 실패");

return false;

}


out << pMem->GetId() << " " << pMem->GetName() << " " << pMem->GetPhone() << endl;

if (out.good()) {

Inserted = true;

}

else {

Inserted = false;

}

out.close();

delete pMem;

return Inserted;

}


MemVO* MemDAO::Search(int id) {

MemView view;

ifstream in("D:\\test\\members.txt");

if (!in.is_open()) {

view.PrintError("파일열기 실패");

return NULL;

}


char line[50];

int usrId;


MemVO* pMem = NULL;

while (!in.eof()) {

in.getline(line, 50);

stringstream ss(line);

ss >> usrId;


if (id == usrId) {

string name;

string phone;

ss >> name;

ss >> phone;

pMem =  new MemVO(usrId, name, phone);

break;

}

}


in.close();

return pMem;

}


bool MemDAO::Update(MemVO* pMem) {

MemView view;

ifstream in("D:\\test\\members.txt");

if (!in.is_open()) {

view.PrintError("파일열기 실패");

return false;

}


int lineCount = GetCount();

MemVO** arr = new MemVO*[lineCount];


int usrId;

string name;

bool found = false;

int i = 0;

char* line;


for (int j = 0; j < lineCount; j++) {

line = new char[50];

in.getline(line, 50);

stringstream ss(line);

ss >> usrId;


if (pMem->GetId() == usrId) {

found = true;

ss >> name;

string newLine = usrId + " " + name + " " + pMem->GetPhone();

MemVO* pM = new MemVO(usrId, name, pMem->GetPhone());

arr[i++] = pM;

}

else {

string name,phone;

ss >> name>>phone;

MemVO* pM = new MemVO(usrId, name, phone);

arr[i++] = pM;

}

}

in.close();

if (found) {

Rewrite(arr, lineCount);

}

else {

view.PrintMsg("검색결과가 없습니다");

}

}


bool MemDAO::Delete(int id) {

MemView view;

ifstream in("D:\\test\\members.txt");

if (!in.is_open()) {

view.PrintError("파일열기 실패");

return false;

}


int lineCount = GetCount();

MemVO** arr = new MemVO*[lineCount-1];


int usrId;

string name;

bool found = false;

int i = 0;

char* line;


for (int j = 0; j < lineCount; j++) {

line = new char[50];

in.getline(line, 50);

stringstream ss(line);

ss >> usrId;


if (id == usrId) {

found = true;

}

else {

string name, phone;

ss >> name >> phone;

MemVO* pM = new MemVO(usrId, name, phone);

arr[i++] = pM;

}

}

in.close();

if (found) {

bool rewrited = Rewrite(arr, lineCount-1);

return rewrited;

}

return false;

}


bool MemDAO::Rewrite(MemVO** arr, int sz) {

MemView view;

ofstream out("D:\\test\\members.txt");

if (!out.is_open()) {

view.PrintError("파일열기 실패");

return false;

}


for (int i = 0; i < sz; i++) {

out << arr[i]->GetId()<<" "<< arr[i]->GetName()<<" "<< arr[i]->GetPhone() << endl;

}


bool updated = false;

if (out.good()) updated = true;

out.close();


for (int i = 0; i < sz; i++) {

delete arr[i];

}


delete[] arr;

return updated;

}



class MemMgr 

{

public:

MemView view;

MemDAO dao;

void Process() 

{

bool go = true;


while (go) 

{

char m = view.Menu();

switch (m) 

{

case 's': memList(); break; // 파일에서 리스트를 읽어와서 MemVO객체에 저장, View에 전달

case 'i': insert(); break;

case 'f': search();  break;

case 'u': update();  break;

case 'd': remove();  break;

case 'x': go = false; break;

default: view.PrintError("입력오류");

}

}

}


void memList() {

MemVO** arr = dao.GetList();

view.PrintLine();

view.PrintList(arr);

view.PrintLine();

}


void insert() {

MemVO* pMem = view.InputMem();

string msg;

if (dao.Insert(pMem)) msg = "회원정보 추가 성공";

else msg = "회원정보 추가 실패";

view.PrintMsg(msg);

view.PrintLine();

view.PrintList(dao.GetList());

view.PrintLine();

}


void search() {

MemVO* pMem = dao.Search(view.InputSearchKey());

view.PrintLine();

view.PrintMem(pMem);

view.PrintLine();

}


void update() {

MemVO* pMem = view.InputUpdateMem();


if (!pMem) {

view.PrintMsg("검색 결과가 없습니다");

return;

}


bool updated = dao.Update(pMem);

view.PrintUpdateResult(updated); 

view.PrintLine();

view.PrintList(dao.GetList());

view.PrintLine();

}


void remove() {

int del = view.InputDeleteKey();

bool deleted = dao.Delete(del);


view.PrintDeleteResult(deleted);

view.PrintLine();

view.PrintList(dao.GetList());

view.PrintLine();

}

};



int MemDAO::MemCnt = 0;


int main()

{

cout << "MVC 패턴으로 텍스트 파일 다루기" << endl;


MemMgr mgr;

mgr.Process();


cout << "프로그램 종료" << endl;

return 0;

}

Posted by cwisky
Unreal C++/Text File IO2018. 9. 12. 19:21

C++ 텍스트파일 CRUD 예제


구성 파일

Member 클래스 (Member.h, Member.cpp)

번호, 이름, 이메일, 전화번호를 저장할 수 있는 회원정보 데이터 모델


MemberIO 클래스 (MemberIO.h, MemberIO.cpp)

파일에 저장된 회원정보 4가지를 입출력 하는 모든 기능

추가, 수정, 삭제, 검색, 리스트, 회원수 확인 등


main.cpp ( main함수와 그 외 서비스 함수들)

프로그램의 흐름을 관리하며 이용자로부터 키보드 입력을 받아 기능을 호출하고 화면에 표시하는 모든 기능



Member.h

#pragma once

#include <string>


using namespace std;


class Member

{

int num;

string name;

string email;

string phone;

public:

Member();

Member(int num, string name, string email, string phone);

void set_num(int num);

void set_name(string name);

void set_email(string email);

void set_phone(string phone);

int get_num();

string get_name();

string get_email();

string get_phone();

};


Member.cpp

#include "Member.h"


Member::Member(){}


Member::Member(int num, string name, string email, string phone)

:num(num), name(name), email(email), phone(phone)

{}


void Member::set_num(int num) { this->num = num; }


void Member::set_name(string name){ this->name = name;}


void Member::set_email(string email){ this->email = email;}


void Member::set_phone(string phone){ this->phone = phone;}


int Member::get_num() { return num; }


string Member::get_name() { return name; }


string Member::get_email(){ return email; }


string Member::get_phone(){ return phone; }



MemberIO.h

#pragma once

#include <string>

#include "Member.h"


using namespace std;


class MemberIO

{

string filepath;

public:

int curr_cnt;

MemberIO();

MemberIO(string filepath);

Member* list();

bool add(Member& m);

Member search(string name);

bool update(Member& m);

bool del(string name);

int get_count();

private:

bool overwrite(string* arr, int cnt);

};


MemberIO.cpp

#include "MemberIO.h"

#include <fstream>

#include <iostream>

#include <sstream>


using namespace std;


MemberIO::MemberIO(){

this->filepath = "D:\\test\\mem.txt";

}


MemberIO::MemberIO(string filepath){

this->filepath = filepath;

}


Member * MemberIO::list(){

int cnt = get_count();

if (cnt == 0) { 

cout << "회원정보가 아직 없음" << endl; 

return nullptr;

}

Member* pm = new Member[cnt];

ifstream fin(filepath);

if (!fin.is_open()) {

cout << "파일열기 오류" << endl;

return nullptr;

}

string line, name, email,phone;

int num, idx = 0;

stringstream ss;

while (!fin.eof()) {

getline(fin, line);

if (line.length() == 0) break;


ss.str(line);

ss >> num >> name >> email >> phone;

pm[idx++] = Member(num, name, email, phone);

ss.clear();

line.clear();

}

fin.close();

return pm;

}


bool MemberIO::add(Member& m){

ofstream fout(filepath, ios::app);

if (!fout.is_open()) {

cout << "파일 열기 오류" << endl;

return false;

}

fout << m.get_num() << " " << m.get_name() << " " << m.get_email() << " " << m.get_phone() << endl;

fout.close();

return true;

}


Member MemberIO::search(string name){

ifstream fin(filepath);

if (!fin.is_open()) {

cout << "파일 열기 오류" << endl;

throw exception("파일열기 오류") ;

}

string line;

int cnt = 0;

stringstream ss;

int num;

string fname, email,phone;

Member m;

while (!fin.eof()) {

getline(fin, line);

ss.str(line);

ss >> num >> fname;

if (fname == name) {

ss >> email >> phone;

fin.close();

return Member(num, fname, email, phone);

}

}

fin.close();

throw exception("검색실패") ;

}


bool MemberIO::update(Member & m){

int cnt = get_count();

ifstream fin(filepath);

string* all_lines = new string[cnt];

string line,num,name,email,phone;

stringstream ss;

int idx = 0;

while (!fin.eof()) {

getline(fin, line);

if (line.length() == 0) break;

ss.str(line);

ss >> num >> name;

if (name == m.get_name()) {

ss >> email >> phone;

stringstream ss2;

ss2 << num <<" "<< name <<" "<<

m.get_email() <<" "<< phone;

all_lines[idx++] = ss2.str();

ss2.clear();

}

else {

all_lines[idx++] = line;

}

ss.clear();

}

bool ok = overwrite(all_lines, idx);

cout << "업데이트 결과:" << ok << endl;

return true;

}


bool MemberIO::del(string name){

int cnt = get_count();

ifstream fin(filepath);

string* all_lines = new string[cnt-1];

string line, num, fname;

stringstream ss;

int idx = 0;

while (!fin.eof()) {

getline(fin, line);

if (line.length() == 0) break;

ss.str(line);

ss >> num >> fname;

if (fname != name) {

all_lines[idx++] = line;

}

ss.clear();

}

bool ok = overwrite(all_lines, idx);

cout << "삭제 결과:" << ok << endl;

return true;

}


int MemberIO::get_count(){

ifstream fin(filepath);

if (!fin.is_open()) { 

cout << "파일 열기 오류" << endl;

return -1;

}

string line;

int cnt = 0;

while (!fin.eof()) {

getline(fin, line);

if(line.length()!=0) cnt++;

line.clear();

}

fin.close();

curr_cnt = cnt;

return cnt;

}


bool MemberIO::overwrite(string * arr, int cnt)

{

ofstream fout(filepath);

if (!fout.is_open()) {

cout << "파일 열기 오류" << endl;

return false;

}

for (int i = 0; i < cnt; i++) {

fout << arr[i] << endl;

}

fout.close();

delete[]arr;

return true;

}


main.cpp

#include <iostream>

#include <fstream>

#include <string>

#include <sstream>

#include "MemberIO.h"


using namespace std;


enum Menu { LIST, SEARCH, ADD, UPDATE, DELETE, EXIT };


void print_menu() {

cout << "\t----------------------------------------------------------------" << endl;

cout << "\n\t회원관리: 목록(p), 추가(a), 검색(s), 수정(u), 삭제(d), 종료(x) :";

}

Menu get_user_menu() {

string input;

getline(cin, input);

stringstream ss(input);

char m;

ss >> m;


Menu menu = LIST;

switch (m) {

case 'p': menu = LIST; break;

case 'a': menu = ADD; break;

case 's': menu = SEARCH; break;

case 'u': menu = UPDATE; break;

case 'd': menu = DELETE; break;

case 'x': menu = EXIT; break;

}

return menu;

}

void print_list(Member* pm, int cnt) {

cout << "\n\t----------------------------------------------------------------" << endl;

for (int i = 0; i < cnt; i++) {

if(pm[i].get_name().empty()) break;

cout << pm[i].get_num() << "\t\t" << pm[i].get_name() << "\t\t"

<< pm[i].get_email() << "\t\t" << pm[i].get_phone() << endl;

}

delete[]pm;

}

Member add() {

cout << "\n번호 이름 이메일 전화번호를 공백으로 구분하여 입력:";

int num;

string name;

string email;

string phone;

cin >> num >> name >> email >> phone;

Member m(num, name, email, phone);

string line;

getline(cin, line);

return m;

}

string search() {

cout << "\n검색할 회원의 이름:";

string name;

getline(cin, name);

return name;

}

void print_mem(Member& m) {

cout << "\n검색결과----------------------------------------------------------------" << endl;

cout << m.get_num() << "\t" << m.get_name() << "\t"

<< m.get_email() << "\t" << m.get_phone() << endl;

}

Member update_info() {

cout << "\n수정할 회원의 이름, 이메일 입력:";

string input, name, email;

getline(cin, input);

stringstream ss(input);

ss >> name >> email;

ss.clear();


Member m;

m.set_name(name);

m.set_email(email);

return m;

}

string get_del_name() {

cout << "\n삭제할 회원의 이름:";

string name;

getline(cin, name);

return name;

}


int main()

{

MemberIO mio("D:\\test\\mem.txt");


cout << "\t\t\t\t** 회원관리 프로그램 **" << endl;

cout << "\t\t\t\t=======================" << endl;

bool go = true, saved=false;

Member* pm = 0;

Member mem;


while (go) {

print_menu();

Menu m = get_user_menu();


switch (m) {

case LIST:

pm = mio.list();

print_list(pm, mio.curr_cnt);

break;

case ADD:

mem = add();

saved = mio.add(mem);

cout << "파일에 추가:" << saved ? "성공" : "실패";

cout << endl;

break;

case SEARCH:

try { 

Member m = mio.search(search()); 

print_mem(m);

}catch (exception& e) { cerr << e.what() << endl; }

break;

case UPDATE:

mem = update_info();

mio.update(mem);

break;

case DELETE:

mio.del(get_del_name());

break;

case EXIT: go = false; break;

}

}

cout << "\n프로그램 종료...." << endl;


return  0;

}


Posted by cwisky

C++ STL의 list는 Linked List를 구현한 템플릿 클래스이다


#include <iostream>

#include <string>

#include <list>


using namespace std;


int main() 

{

list<int> numList;


list<int> *pList = new list<int>; // 리스트를 동적으로 생성할 수도 있음


numList.push_back(13);

numList.push_back(12);

numList.push_back(15);

numList.push_back(11);

numList.push_back(12);

//각 컨테이너에 포함된 iterator는 포인터의 일반화 표현이기 때문에

//자료형을 표시하지 않는다


list<int>::iterator iterator = numList.begin(); //첫번째 원소를 가리키는 반복자


// list는 원소에 대해 순차접근만 지원되므로 vector처럼 begin()+5 과 같은

// 임의의 위치에 접근하는 방식은 지원하지 않는다

// list::end() 함수는 리스트의 마지막 원소가 아닌 마지막 원소의 뒷 부분을 가리킨다

// list::back() 함수는 리스트의 마지막 원소를 리턴한다(반복자가 아님)


for (iterator = numList.begin(); iterator != numList.end(); iterator++)

{

cout << *iterator << " ";

}

cout << endl;


numList.sort(); // 오름차순 정렬

numList.sort(greater<int>()); // 내림차순 정렬(큰 수부터 먼저 오도록)


numList.remove(13); // 리스트에서 데이터가 13인 모든 원소 삭제


//삭제조건을 지정한 함수객체

//함수객체란 ()함수(괄호함수)를 가진 구조체나 클래스의 객체를 말한다


struct RemNumber {

bool operator()(int n) { // 리스트의 원소가 13보다 크면 삭제

if (n > 13) return true;

else return false;

}

};

numList.remove_if(RemNumber());


//리스트의 원소를 역순으로 참조하려면 rbegin(), rend()를 사용하여 구한 반복자를 사용한다

list<int>::reverse_iterator rIterator = numList.rbegin();

for (; rIterator != numList.rend(); rIterator++)

{

cout << *rIterator << " ";

}

cout << endl;


// 리스트에 저장할 커스텀 객체 정의

struct Player 

{

int id; string name; int health;

Player(int id, string name, int health) 

:id(id),name(name),health(health){}

void printPlayer() {

cout << id << ":" << name << ":" << health << endl;

}

};


// 리스트에 커스텀 객체를 저장하는 예

list<Player> playerList;


Player p1(11, "권종식", 79);

playerList.push_back(p1);


Player p2(12, "김희용", 85);

playerList.push_back(p2);


Player p3(13, "이관춘", 77);

playerList.push_back(p3);


list<Player>::iterator playerIterator = playerList.begin();

list<Player>::iterator endIterator = playerList.end();

for (playerIterator = playerList.begin(); playerIterator != endIterator; playerIterator++)

{

playerIterator->printPlayer();

}

cout << endl;



// remove_if() 사용하여 특정 조건을 가진 원소를 삭제하기

// remove_if(Predicate); // Predicate:삭제 조건을 정의한 함수객체

struct RemPlayer {

bool operator()(Player& player) {

if (player.health < 80) return true;

else return false;

}

};


playerList.remove_if(RemPlayer()); // 삭제조건 함수객체 생성 및 전달


for (playerIterator = playerList.begin(); playerIterator != endIterator; playerIterator++)

{

playerIterator->printPlayer();

}

cout << endl;



//리스트에 저장된 포인터 다루기

list<Player*> ppList;


ppList.push_back(new Player(21, "트럼프", 88));

ppList.push_back(new Player(22, "폼페이오", 79));

ppList.push_back(new Player(23, "김정은", 92));

list<Player*>::iterator it;

for (it = ppList.begin(); it != ppList.end(); it++) {

(**it).printPlayer();

}

cout << endl;


// 리스트의 원소 정렬을 위한 조건을 정의한 객체 생성

struct PlayerSort {

bool operator()(Player*pp1, Player *pp2) {

if (pp1->health < pp2->health) return true;

else return false;

}

};


ppList.sort(PlayerSort());


for (it = ppList.begin(); it != ppList.end(); it++) {

(**it).printPlayer();

}

cout << endl;


return 0;

}


Posted by cwisky

Templates

함수나 클래스를 정의할 때 다룰 자료형(Data Types)을 구체적으로 지정하지 않고 일반화된 형식(Generic Types)으로 지정한다. 이 때 일반화된 형식으로 지정한 자료형을 Template Parameters라고 한다

함수의 리턴 자료형, 파라미터 자료형, 지역변수 자료형만 다르고 함수의 기능이 같은 경우 여러개의 함수를 선언할 필요 없이 함수 템플릿을 사용하면 된다.


클래스 안에서 사용되는 변수의 자료형을 일반화하여 선언하려면 함수 템플릿이 아니라 클래스 템플릿을 사용하면 된다.


코딩시 템플릿으로 정의된 함수를 호출할 때나 템플릿을 정의된 클래스의 인스턴스를 생성하는 코드 부분에서 Template Parameter에 구체적인 자료형을 지정해주면 컴파일러가 해당 함수나 클래스의 템플릿을 사용하여 구체적인 함수나 클래스를 생성하고 컴파일하게 된다.


자료형이 다르다는 이유로 여러개의 함수를 생성하거나 오버로드하는 불편을 덜고 컴파일러가 대신하도록 할 수 있다. 


C++의 STL에 포함된 자료구조나 알고리듬을 잘 이해하고 이용하려면 템플릿에 대한 개념이 필수적이다



함수 템플릿 선언 형식

class, typename 이라는 키워드를 사용할 수 있고 2개의 의미는 완전히 동일하다. 새로운 표준에서 typename 이라는 키워드를 도입했다

template <class identifier> function_declaration;

template <typename identifier> function_declaration;



함수 템플릿 선언 예

template <typename myType>

myType GetMax (myType a, myType b) 

{

    return (a>b?a:b);

}



함수 템플릿을 사용하여 구체적 자료형을 지정하고 함수를 호출하는 형식

function_name <type> (parameters);



함수 템플릿의 템플릿 파라미터(myType)에 실제 자료형을 지정하고 함수를 호출하는 예

int max_int            = GetMax<int>(2, 3);

double max_double = GetMax<double>(2.1, 3.2);



함수 템플릿을 실제로 사용하는 예

#include <iostream>

using namespace std;


template <typename T>

GetMax(T a, T b) 

{

T result;

result = (a > b) ? a : b;

return (result);

}


int main() 

{

int i = 5, j = 6, k;

float m = 10.3, n = 5.5, p;

k = GetMax<int>(i, j);

p = GetMax<float>(m, n);

cout << k << endl;

cout << p << endl;

return 0;

}



템플릿 파라미터(<typename T>)의 실제 자료형을 결정하는 위의 <int>, <float> 형식을 생략해도 아규먼트 자료형을 참조하여 자동으로 해당 자료형으로 구체화된 함수가 생성되고 호출된다

k = GetMax(i, j);

p = GetMax(m, n);



템플릿 파라미터가 다수개인 경우

template<typename T1, typename T2

double Add(T1 a, T2 b);



템플릿에 const와 참조(&)형을 사용하는 예

template <typename T

const T& Max(const T& a, const T& b )

{

return a > b ? a : b;

}



클래스 템플릿의 예

#include <iostream>

using namespace std;


template <typename T>

class mypair 

{

T values[2];

public:

mypair(T first, T second)

{

values[0] = first; values[1] = second;

}

};


int main() 

{

mypair<double> pair(1.2, 3.4);


return 0;

}



위의 예제에서 클래스 선언부 밖에 멤버함수를 정의하는 경우에는 각 멤버함수 정의부마다 클래스 템플릿 선언이 선행되어야 한다

template <typename T>

class mypair 

{

    T a, b;

  public:

    mypair (T first, T second)

    {

        a=first; b=second;

    }

    T getmax ();

};


// 선언부 밖에 함수 정의를 두는 경우

template <typename T>

T mypair<T>::getmax ()

{

  T retval;

  retval = a>b? a : b;

  return retval;

}




템플릿 전문화(Template Specialization)

템플릿 파라미터에 특정 자료형이 지정되는 경우 함수 안에서 해당 자료형의 처리방법이 달라서 템플릿에서 처리할 수 없는 경우에는 특별한 처리방법을 적용해야 한다. 이런 경우에 템플릿 전문화 방법을 사용하여 함수 템플릿을 오버라이드하면 된다



함수 템플릿 전문화의 예

#include <iostream>


using namespace std;


// 일반 함수 템플릿

template<typename T>

T Add(T a, T b)

{

T result = a+b;

return result;

}


// 함수 템플릿 전문화(위의 함수 템플릿을 오버라이드 한 것)

// 템플릿 파라미터가 모두 double 자료형이라면 이 함수가 우선적으로 호출된다

template<>

double Add(double a, double b)

{

double result = a+b;

return result;

}


int main() 

{

int sum = Add<int>(2, 3);                       // <int> 는 생략가능

cout << "정수 합계:" << sum << endl;


double dsum = Add<double>(2.1, 3.2);    // <double> 은 생략가능

cout << "실수 합계:" << dsum << endl;


return 0;

}



클래스 템플릿 전문화 선언 형식

template <typename T> class mycontainer { ... };     // 일반 클래스 템플릿 선언

template <> class mycontainer <char> { ... };   // 클래스 템플릿 전문화(위의 클래스 템플릿을 오버라이드함)



클래스 템플릿 전문화 선언 및 사용 예

// template specialization

#include <iostream>

using namespace std;


// class template:

template <typename T>

class mycontainer {

    T element;

  public:

    mycontainer (T arg) {element=arg;}

    T increase () {return ++element;}

};


// 클래스 템플릿 전문화: 기본 템플릿 클래스의 모든 멤버를 빠짐없이 다시 정의해야 한다

template <>

class mycontainer <char> {

    char element;

  public:

    mycontainer (char arg) {element=arg;}

    char uppercase ()

    {

      if ((element>='a')&&(element<='z'))

      element+='A'-'a';

      return element;

    }

};


int main () {

  mycontainer<int> myint (7);       // 템플릿 파라미터에 전달할 <int> 를 생략하면 안됨

  mycontainer<char> mychar ('j');  // 클래스 템플릿 전문화 사용 <char>를 생략하면 안됨

  cout << myint.increase() << endl;

  cout << mychar.uppercase() << endl;

  return 0;

}



Non-type 파라미터를 가진 템플릿

함수 템플릿이나 클래스 템플릿은 자료형을 일반화하여 선언하고 함수의 호출부나 클래스 객체 생성시에 실제 자료형을 지정하여 실제 함수나 클래스를 생성하는 수단으로 자주 사용된다. 그러나 자료형을 일반화하는 것이 아니라 자료형은 결정되어 있고 실제 값을 템플릿 파라미터로 지정하는 방법도 지원되며 이 경우 자료형(Type)을 지정하는 것이 아니라 실제 값을 지정하는 것이므로 Non-type 파라미터 템플릿이라고 한다



Non-type 파라미터 템플릿의 선언 및 사용 예


#include <iostream>

using namespace std;


template <typename T, int N// Non-type 파라미터(int N) 선언

class mysequence {

    T memblock [N];           // Non-type 파라미터 사용

  public:

    void setmember (int x, T value);

    T getmember (int x);

};


template <typename T, int N>

void mysequence<T,N>::setmember (int x, T value) {

  memblock[x]=value;

}


template <typename T, int N>

T mysequence<T,N>::getmember (int x) {

  return memblock[x];

}


int main () {

  mysequence <int,5> myints;            // Non-type 파라미터에 실제 값 5를 지정함

  mysequence <double,5> myfloats;

  myints.setmember (0,100);

  myfloats.setmember (3,3.1416);

  cout << myints.getmember(0) << '\n';

  cout << myfloats.getmember(3) << '\n';

  return 0;

}



템플릿 파라미터에도 디폴트 값을 지정할 수 있다

template <typename T=char, int N=10>

class mysequence {..};



위에 선언된 클래스 템플릿을 이용하여 객체를 생성하는 경우

아래의 2가지 템플릿 사용법은 완전히 동일한 결과를 갖는다

mysequence<> myseq;

mysequence<char,10> myseq;




부분 템플릿 전문화의 예

template< typename T1, typename T2 >

class Test

{

  public:

  T1 Add( T1 a, T2 b )

  {

     cout << "일반 클래스 템플릿." << endl;

     return a;

  }

};


template< typename T1 >

class Test<T1,float>

{

  public:

  T1 Add( T1 a, float b )

  {

     cout << "부분 전문화 템플릿." << endl;

     return a;

  }

};



// 위에 선언된 템플릿 클래스 부분 전문화 사용 예

void main()

{

   Test<int, int> test1;

   test1.Add( 2, 3 );

   Test<int, float> test2;

   test2.Add( 2, 5.8f );

}



템플릿 파라미터에 포인터 형을 사용할 경우의 템플릿 전문화 예

template< typename T >

class TestP

{

  public:

  void Add()

  {

    cout << "일반 템플릿." << endl;

  }

};


// T를 T*로 부분 전문화

template< typename T >

class TestP<T*>

{

  public:

  void Add()

  {

    cout << "포인터를 사용한 부분 전문화 템플릿." << endl;

  }

};


#include <iostream>

using namespace std;

void main()

{

  TestP<int> test1;

  test1.Add();

  TestP<int*> test2;

  test2.Add();

}


Posted by cwisky

VS 2015에서는 문자열 변수 선언시 아래의 문장은 아무런 문제가 없이 실행된다.


char * str = "Hello";


그러나 VS 2017에서는 에디터에서 상수 문자열을 일반 문자열로 형변환할 수 없다는 컴파일러 오류가 발생한다

VisualStudio 2017 15.5 버전부터는 모든 솔루션에 [준수모드]가 디폴트로 Permissive 상태로 설정되어 있기 때문에 위의 문장에서 오류가 발생한다. 


"const char *" 형식의 값을 사용하여 "char *" 형식의 엔티티를 초기화할 수 없습니다"



 VS 2017에서는 더 엄격한 문법 준수 모드로 설정되어 있기 때문이다

그러므로 아래와 같은 방법으로 표현해야 한다

const char * str = "Hello" 



이를 원래의 설정으로 변경할 필요가 있다면 아래의 절차에 따라서 설정하면 된다


프로젝트 > [프로젝트이름] 속성 > C/C++ > 언어 > 준수모드 > 아니오 선택 > 적용 > 확인



Posted by cwisky