C 에 이어서 C++ 입니다. 따로 공부 하시는게 의외로 상성 효과로 많은 도움이 되실겁니다. 처음부터 약간 객체지향적인 코딩 부분이 있기 때문에 생각을 다른 시각에서 보셔야 합니다. C 에서도 그랬지만 이렇게 작동한다는 객체, 도구로 보셔야 합니다. C 문법을 지녔지만 하는 기능과 역활은 전혀 새로울수 있습니다.
저작권 공지 : 이 강의 노트의 저작권은 고려대학교 신호처리연구실에 있습니다. 그러니 원저작자의 허락없이 무단으로 짜집기하시거나 자기가 만들었다고 살짝 바꿔서 우기시면 안 됩니다; http://signal.korea.ac.kr/~ychlee 에서 받았습니다.
문제가 새로운게 많았습니다. 조금 난해한 부분도 있었지만 슬기롭게 해낸거 같습니다. 2004년 까지만 해도 크게 차이가 없었는데 2005년 부터는 확실히 무언가 달라지고 있다는 것을 느꼈습니다. 비록 이게 마지막 시험이라 아쉽지만 이번학기에 나올 객체 시험 문제에 대해 기대하고 있습니다. 방학때 다시 C++ 언어를 새로 공부 할수 있을거 같군요. 클래스를 상속까지 진보 시켰으며 포인터 연산자에 대해 새롭게 알았습니다.
해설 - 코드가 약간 비효율적입니다. - 문제를 제대로 이해하시면 상당히 쉽습니다. 사전정렬을 하되 소문자 > 대문자 > 숫자 그룹별로 숫자가 크며, 사전순으로 알파벳 순서가지날수록 값이 커집니다. 이 숫자를 각 문자열의 첫째 글자만 맞춰서 문자열을 내림차순으로 맞춥니다.
- 문제를 이해하지 못해서 값을 더하면 엉뚱한 결과가 나옵니다. - 예제에서 'V' > 'M' > 'I' > 'H' 이라서 순서가 실행결과 처럼 나왔습니다. - 첫째 글자가 검출되면 계산후 값을 저장하고 break로 탈출합니다.
참고 - 해답에는 vector 알고리듬을 쓰던데 크게 차이는 없습니다. - 유사 문제 검색
해설 - 이전 문제의 업그레이드 판으로 상속에 대해서 이해를 하셔야지 풀수 있는 문제입니다. - 상속은 ThreeD -> Cubic 으로 되는데 protected 된 자료형과 public 으로 된 자료형을 상속 받습니다. - 상속 받은 클래스의 객체가 초기화 될때 부모 클래스에서 자동으로 생성자를 실행하기 위해 상속 클래스에 : [부모 클래스의 생성자 함수] 를 써줍니다. 그러면 리턴되어 자식 클래스에서도 똑같이 적용이 됩니다.
- 셋다 0이 아닐때 수행되며 셋중 하나가 0이 되면 종료되는 판정문이 있습니다. - 이전에 생성된 객체는 그다지 중요하지 않기 때문에 1차원 포인터로 객체를 마구 찍을수 있게 했습니다.
int main(void) { char sIn[255]; long lCount, lPay, i; lCount = lPay = i = 0;
cout << "몇 명의 급여를 계산할까요? : "; cin >> lCount;
// Employee *CStaff[200]; Employee **CStaff; CStaff = new Employee*[lCount];
/* 일차 포인터를 담는 변수는 스택에 저장이 됩니다. 스택에 저장되는 자료의 크기는 일정(constant) 해야 하므로 Employee *CStaff[lCount] 를 쓰면 문제가 생깁니다. Employee *CStaff[200]; 처럼 숫자를 쓰면 일시적으로 해결을 보나 크기에 제약이 있으므로 동적이라 하기에는 문제가 있습니다.
이를 막기 위해서 만들어진 이중 포인터 **CStaff 는 저장된 주소를 저장한 공간을 가리키는 포인터로써 2차원적인 자료 구조를 가지고 있습니다. 실제 저장된 공간을 가리키는 주소도 포인터 이므로 동적으로 생성 되며 동적으로 생성되었을때에는 배열에다가 변수(variable)를 넣을수 있습니다.
/* 이전에 키보드에 저장했던 '\n' 이 남아서 이후의 sIn 입력에 영향을 끼칩니다. 따라서 ignore 함수를 통해서 앞으로 있을 한줄 내림을 무시하고 입력을 받겠다고 선언을 해서 남아 있는 '\n' 값을 한번 넘깁니다. */
cout << "직원의 이름 : "; cin.getline(sIn,255);
cout << "시간당 급여 : "; cin >> lPay;
CStaff[i] = new Employee(sIn,lPay); CStaff[i]->m_PrintMonthPay();
i++;
} for(i=0; i < lCount; i++) delete CStaff[i];
delete[] CStaff;
/*
첫번째 for 문에서 delete 되는 CStaff 는 CStaff[i] = new Employee(sIn,lPay); 에서 생성된 CStaff[i] 삭제용
두번째 delete[] CStaff 에서 CStaff 는 CStaff = new Employee*[lCount]; 에서 CStaff 를 삭제 합니다.
*/
return 0; }
해설 - 복사 생성사는 매너(?) 상 넣었습니다. 포인터 맴버 변수를 가진 클래스에서는 언제 쓰일지 모르기 때문에 써주시는게 예상치 못한 오류를 막는데 도움이 될수 있습니다.
- inline 함수들만 정의 되어 있어서 굳이 Employee.cpp 파일을 작성할 필요가 없습니다. Employee.h 에서 다 정의 했습니다.
- 코드에 해설이 주석으로 직접적으로 쓰여져 있습니다.
- 이번엔 이중 포인터라는 중요한 개념을 익혔습니다. 일차포인터 배열은 가리키는 주소가 스택에 있기 때문에 그 크기가 변할수 없지만 이중포인터로 이차원으로 선언했을때에는 가리키는 주소가 힙에 저장되기 때문에 배열의 크기가 코드상에서 동적으로 지정이 가능합니다. 중요한 조언을 주신 선배님께 감사드립니다.
- cin 으로 엔터키를 입력하면 엔터 값이 버퍼에 남아 있기 때문에 cin.getline 을 해버리면 넘어가버리는 문제가 있었습니다. cin.ignore(1,'\n') 버퍼에서 값을 가져올때 첫번째 '\n' (줄내림) 까지 값을 무시하고 받는다고 명령을 넣고 다음 키 입력 getline 함수를 이용하여 저장을 할수 있습니다.
- 동적 배열을 구현할수 있지만 사용자 정의된 클래스에서는 약간 복잡해집니다. 따라서 MFC 나 STL 에서 동적 배열을 구현하는 자료형을 제공합니다.
- inline 함수는 일반함수의 개념보다 매크로의 개념이 강합니다. 해당 함수가 호출되었을때 inline으로 저장이된 구문이 함수를 호출한 코드에서 복사/붙여넣기 처럼 그 자리에서 실행을 합니다. 원래 함수는 호출 -> 현재 상태 저장 -> 복귀값 저장 -> 처리 -> 이전 상태 복귀 -> 리턴값과 함께 이전 상태로 복귀 이런 식으로 가는데, inline 함수는 코드 호출 -> 치환 -> 수행 -> 리턴값 이런식으로 간단하게 수행됩니다. 수행 시간이 짧아지는 장점과 동시에 코드가 그떄 그때 붙어버리는 바람에 커져버리는 단점이 있습니다. 구조체나 클래스 처럼 마지막에 } 하면서 세미콜론을 잊지 마시기 바랍니다. 정의의 마지막에는 꼭 필요합니다.
해설 - F 학점은 fTotal 더하지 않기 때문에 판정을 하지 않아도 문제 없습니다. - 다중 if 문으로 else 없이 써도 한 루프에 하나의 if 만 걸리기 때문에 문제 없이 작동할수 있습니다. - float 형으로 선언하여 소숫점 계산을 할수 있습니다. - 문자형 이나 기타 단순 비교용 == 연산자로 sGrade == "A+" 이렇게 될거 같지만 문자열에 대한 비교는 이 연산자로는 되지 않습니다. 대신 strcmp 함수를 쓰면 가능합니다.
해설 - 시간은 전체 초를 3600초로 나누고 분은 전체초를 3600 한 나머지를 60으로 나누고 초는 전체 초를 60으로 나눈 나머지를 구하면 됩니다. 즉 그 단위에 맞는 숫자로 나누면 그 단위에 맞는 숫자가 나오며 그이하의 단위의 값을 구할려면 바로 상위 단위의 동등 숫자를 나눈 나머지를 그 아래 동등 숫자로 나눈 몫을 대입하면 나옵니다.
cout << sId << ' ' << sName << " 의 평균 점수는 " << dTotal / i << " 입니다." << endl; delete sName, sId;
return 0; }
해설 - vector STL 자료형은 이전 문제에서 자세히 다루었습니다. 참고 하시기 바랍니다. - 나머지 부분은 크게 어렵지 않습니다. - 문자열 포인터를 생성후 동적으로 입력 받은 글자의 길이만큼 새로 생성하여 추가하는 방식을 썼습니다. 생성 및 삭제에 new / delete 명령어가 쓰입니다.
해설 - 이번 문제에선 복사 생성자가 그다지 역할을 하지 않았습니다. 포인터를 쓰기 때문에 의무적으로 적은 거 뿐입니다. 만약 할당 연산자나 복사시 이 생성자는 서로 다른 주소로 자료를 저장하기 위해서 필수적으로 적어 두셔야 합니다.
- new 로써 동적으로 포인터에 배열을 붙여서 접근을 할수 있습니다. 프로그램 종료시 delete 하여 자원의 낭비를 줄입니다.
- 마찬가지로 생성시 new 로 문자열을 늘렸기 때문에 소멸시에 delete 로써 문자열을 삭제 합니다. 이는 파괴자(소멸자)에서 그 역할을 할수 있기 기술합니다.
- CStu[0]->m_ViewProfie() 에서 -> 연산자는 (*CStu[0]).m_ViewProfile() 과 같습니다. 우선순위로 인해 괄호를 넣기가 번거로워서 -> 연산자를 추가했습니다.
참고 - 객체지향적 프로그래밍은 C++ 기초 다음 단계이므로 차근 차근 공부 하시면 이해하기가 쉽습니다. 구조체를 먼저 이해하시고 클래스를 배우신후 private / public / protect 형 그리고 맴버함수와 변수 생성자와 파괴자 상속과 연산자, 가상함수... 이런식으로 계속 배우시면 됩니다.
vecOut = find(vecIn); cout << "가장 작은 값 : " << vecOut[2] << endl; cout << "첫 번째로 큰 값 : " << vecOut[0] << endl; cout << "두 번째로 큰 값 : " << vecOut[1] << endl;
return 0; }
해설
+ 첫번째 풀이 - vector STL 자료형은 <vector> 를 include 함으로써 선언할수 있습니다. vector STL 클래스에서 제공하는 맴버 함수는 여러가지가 있습니다. - sort 함수는 STL 자료를 정렬 합니다. <algorithm> 에서 선언해서 쓸수 있습니다.
- find 함수는 STL 자료를 검색하여 해당 위치의 주소를 리턴하는 기능을 가집니다. 마찬가지로<algorithm> 에서 선언해서 쓸수 있습니다.
- begin() 맴버함수는 해당 자료의 시작부분의 주소를 반환합니다. 마찬가지로 end() 맴버함수는 자료의 끝을 반환하는데 중요한 것은 마지막 자료가 아니라 자료의 끝 주소를 가리킵니다. 따라서 -1 을 하여 마지막 값을 얻을수 있습니다. - iterator 맴버함수는 그 자료를 가리키는 일종의 포인터와 같습니다. dereference(역참조)를 할수 있습니다.
+ 두번째 풀이 - 두번째 프로그램은 동적 할당으로 double 형 배열을 받아서 자료를 입력하고 포인터로 접근하여 원하는 값을 추출하는 내용입니다. typedef 로 double 을 vector 로도 쓸수 있게 만들었기 때문에 동일하다고 보시면 됩니다.
- STL 의 존재를 모르고 코드를 짠 결과 입니다. - 동적 할당은 이전에 많이 풀어 봐서 그렇게 생소하지 않을 것입니다.
+ 세번째 풀이 - find 함수를 직접 정의해서 사용했습니다. - 내부 함수에서 임의의 벡터 STL 을 생성후 거기다가 결과값을 넣고 return 하면 됩니다. 리턴형이 vector<double> 이기 때문에 결과값을 일괄적으로 받아서 할당 받을수 있습니다. - aVec.end() 는 그 벡터의 마지막 위치를 가리킵니다. 포인터로 =! 조건을 사용하여 끝까지 접근할수 있게 합니다.
해설 - bool isComma(char*) 함수는 문자열을 받아서 그 중에 쉼표가 있으면 true 를 없다면 false 를 반환합니다. - 쉼표를 만들어 출력하는 내용은 주석을 참조 하시기 바랍니다. (방향이 반대로 시작하되 출력은 정식으로 하여 쉼표 출력을 위한 자릿수를 맞춤니다.
참고 - 유사 문제 - Visual basic 에서는 쉼표 숫자 입력을 함수로 이용 하여 계산할수 있습니다.
예제 코드
Value = Val(Format("1,234", "####")) + 11 MsgBox Format(Value, "#,###")
/* 달러 - 달러 : 소숫점 버림 = 센트 부분 (0.0 ~ 0.99) 센트 % 25 는 쿼터 인데 위 식에서 0 ~ 99 가 아니고 0.0 ~ 0.99 까지 이므로 0.25 로 나누면 몫은 쿼터고 나머지는 센트 센트를 구하는 과정에서 나머지 연산자는 정수형에만 적용되므로 * 100 하여 소숫점을 강제로 올린후에 int 형으로 강제 형변환하여 나머지 연산을 적용
해설 - 포인터는 주소를 가리키므로 해당 행렬의 첫번째 번지를 가리키게 합니다. 포인터 값을 +1 한다는 것은 해당 주소형인 int 형의 자료크기 만큼 이동하므로 행렬에서 옆 원소로 이동하며 by 줄(행)이 끝이 나면 다음 줄(열)로 이동합니다. - 포인터에 * 가 붙은것은 참조 하라는 뜻으로 해당 주소에 저장된 값을 불러 냅니다. 6칸읜 셀(원소가 각각 aiMatrixA 와 aiMatrixB 로 저장되어 있습니다. 포인터로 한 원소씩 접근할수 있게 했습니다. - 행렬이 저장된 구조와 포인터가 접근하는 방식에 대해 이해하면 코드 풀이가 한결 쉬워집니다.
해설 - fabs() 함수는 절대값을 구하되 소숫점 까지 받아서 출력할수 있습니다. - 부호를 비교해서 0 보다 작을때 음의 부호를 계산할때 -1을 하여 반올림 할수 있습니다. - 0.5 보다 크거나 같으면 음일때는 -1 양일때는 +1 을 하며 계산후 소숫점아래는 버립니다. - 0 이 되면 판정을 하지 안하고 종료하게 했습니다. 참고 - 유사 문제
cout << endl << endl << "10이 나올 확률 : " << i10 / 100.0 << endl; cout << "20이 나올 확률 : " << i20 / 100.0 << endl; cout << "30이 나올 확률 : " << i30 / 100.0 << endl; cout << "40이 나올 확률 : " << i40 / 100.0 << endl; cout << "50이 나올 확률 : " << i50 / 100.0 << endl;
return 0; }
해설 - rand() % 5 을 통해 0 부터 4까지 + 1 을 통해 1 부터 6까지 출력합니다. - switch-case 문으로 확률을 더하고 출력은 10배를 하여 원하는 값을 만들어 냅니다. - 10 ~ 50 가 10의 단위로 나올 확률과 1 ~ 5 가 나올 확률은 동일합니다.