'C++'에 해당되는 글 4건

  1. 2008.08.28 C++: const keyword는 어떻게 쓸까?
  2. 2008.08.20 상속과포함_20080820_20:60
  3. 2008.08.20 인라인 함수
  4. 2008.08.12 C++ 성적표 관리 프로그램
Programming/C++ 언어2008. 8. 28. 10:10

C나 C# 그리고 Java 같은 언어와 C++이 가장 많이 다른 점 중에 하나는 const 라는 키워드를 지원한다는 점이다.
(Java의 final이 살짝 비슷하긴 하지만 목적과 기능이 확실히 다르다)

const 키워드가 지정할 수 있는 것은 2가지다.

  • 대상(변수)이 변하지 않는 무언가라고 지정한다.
  • 동작(함수, 연산자)이 동작의 주체를 변화시키지 않는다.

가장 간단한 예가 다음과 같은 거다.

void foo( const int bar );

이런 함수의 내부(body)에서는 bar의 값을 바꿀 수 없다.

직접적으로 변수가 아니라 포인터에 const가 붙게 되면 상수성을 부여할 수 있는 곳이 2가지가 된다.
우선 포인터가 가리키는(pointing하는) 주소를 바꿀 수 없게 할 수 있다. 그리고 나머지 한 가지로, 포인터가 가리키는 곳의 값이 바뀌지 않게할 수 있다.

흔히 사용하는 C-style 문자열은 const char* str = “C style string”; 이다. 가리키는 곳의 값이 바뀌지 않는 경우다. (한마디로 str = another_c_style_string; 이 가능한 것.(저렇게 선언하면 해당 문자열 자체는 상수가 되니 char* str 처럼 사용하진 말기를 -_-; )

반대로 int* const a = &some_int_type_variable; 처럼 선언해서 쓴 경우 가리키는 주소가 바뀌지 않는 경우다.(바로 이 경우가 C++의 참조자 - reference -와 같은 의미가 된다; 의미에 맞게 this 포인터는 언제나 T* const다)

그리고 const는 어떤 동작(함수, 연산자)에 붙어서, 해당 함수가 행해지는 객체(주어에 해당하는 객체)의 값이 바뀌지 않는 것을 보장하게 한다. 예를 들어 STL의 std::map의 원소 수가 0인지 확인하는 std::map::emtpy()의 함수의 경우, bool empty() const; 로 선언되어 있다. 즉 원소 수가 0개인지 확인하는 것은 map의 내용을 바꾸지 않는다는 것이다.
(물론 mutable 변수는 바꿀 수 있지만 나중에 생각하자;) )

실제로 들어가면(…), return_type member_func() const; 의 경우 this 포인터가 T* const가 아니라 const T* const(가리키는 것도 못 바꾸고, 가리키는 주소자체도 변경할 수 없는 포인터)가 된다. 그래서 멤버 변수를 못 바꾸고, const가 안붙은 멤버 함수도 못 부른다.

그럼 이런 기능을 하는 const keyword를 어떤 목적으로 쓰는게 맞을까?

우선 함수의 인자를 “정제”하는 목적으로 사용할 수 있다. 지금 작성하는 코드를 나중에 다른 사람이 고칠 수도 있다고 했을 때, 함수의 인자를 원래 의미대로사용하게 강제할 수 있다 - 참조자로 넘어 온 것을 바꾸지 말아야하는데 바꾸거나 할 수 있으니.

그리고 이런 기능은 멤버 함수에 부여할 수 있기 때문에, 특정 타입의 객체의 특정 기능이 상수성을 보장하는지를 강제하는 강력한 도구가 된다. 멤버 함수가 const인 경우, 그 안에서는 (mutable이 아닌) 모든 멤버를 바꿀수 없고 -단 const가 아닌 인자는 예외 - , const 멤버 함수만 호출할 수 있게 된다.

실제 사용되는 예를 들어보면, A라는 타입이 B타입의 변수를 내부에 갖고 있는데, B타입이 struct 타입이라 수많은 변수를 가지고 있다고 치자. 그런데 이 B타입 변수엔 멤버 변수가 굉장히 많은데 대부분 read-only로 사용되고 일부만 사용된다 치자. C++은 C#이나 Python의 프로퍼티 기능을 지원하지 않기 때문에, 이에 대한 getter를 일일이 만들어야 할 수도 있다.

만약 B타입 변수를 통채로 값으로 복사해서 받는 경우는 어떨까? B타입의 크기가 크다면 non-sense가 된다. 복사 가능하다는 보장도 항상 있는게 아니고.

이때 사용되는 것이 const reference를 반환하는 const 멤버 함수다. 모든 getter들은 - 값 캐슁을 하지 않는 경우; 값 캐슁을 해도 mutable로 충분하면 포함하고 - const 멤버 함수여야 한다.(대부분의 경우 이게 맞다; 기억하자) 이 조건을 만족하면서도 복사하는 오버헤드가 없고, 번잡한 작업을 줄이는 것은 const reference 반환이 좋다 - 이 외에도 내부 변수를 외부에 보여줘야하는 경우엔 const 복사나 const refrence 반환이 적합하다.

pimpl idiom을 쓰는 경우에는 이런 경우에 해당할 수도 있고, 그렇지 않을 수도 있지만, 그렇지 않은 많은 경우 번잡한 getter들이나 내부 변수의 조심스러운 참조를 위해서는 const 키워드가 참 유용하다. (값 제한 면에서도 쓸모있긴 하지만)

ps. 이 글을 쓴 이유는 짐작이 가지 않는가? 어제 오후 내내 non-const 반환이 달린 코드 부분이 내 수많은 const 함수들에서 호출되야해서 코드를 싹 고치고 있어야 했다. 제발 C++이라면 const를 적절히 좀 써줘 Orz

ps2. ps에 대한 amihk군의 코멘트: 특히 const안붙은 코드를 자기가 고칠 권한이 없으면 더 (미치지)

Update: const 멤버 함수의 this 포인터 설명 추가 (thanks to lapiz), const 관련 사례(?)추가(thanks to amihk)



원문  -  http://rein.upnl.org/wordpress/archives/229
Posted by 시긔양
TAG C++, const

댓글을 달아 주세요

Programming/C++ 언어2008. 8. 20. 20:07
 
========================== *.txt 문서저장 클래스 ==========================================================   docwriter.h  파일 ==
 
#ifndef DOCWRITER_H
#define DOCWRITER_H

#include <string>
using namespace std;

class DocWriter
{
public:
  DocWriter();
  DocWriter(const string& fileName, const string& content);
  ~DocWriter();

  void SetFileName(const string& fileName);             // 파일 이름을 지정

  void SetContent(const string& content);               // 저장할 텍스트를 지정

  void Write();                                         // 파일에 텍스트를 저장시킨다.

protected:
  string _fileName;
  string _content;
};

#endif
 
 
 
========================== *.txt 문서저장 클래스 ==========================================================   docwriter.cpp  파일 ==
 
#include "docwriter.h"
#include <fstream>
using namespace std;


DocWriter::DocWriter()
{
  // 파일이름, 텍스트를 디폴트로 지정
  _fileName = "NoName.txt";
  _content = "There is no content.";
}

DocWriter::DocWriter(const string& fileName, const string& content)
{
  _fileName = fileName;
  _content = content;
}

DocWriter::~DocWriter()
{
}

// 파일 이름을 지정
void DocWriter::SetFileName(const string& fileName)
{
  _fileName = fileName;
}

// 저장할 텍스트를 지정
void DocWriter::SetContent(const string& content)
{
  _content = content;
}

// 파일에 텍스트를 저장시킨다.
void DocWriter::Write()
{
  // 파일을 연다.
  ofstream of( _fileName.c_str() );

  // 헤더문구 저장.
  of << "# Content #\n\n";

  // 문자열 저장저장한다.
  of << _content;
}

 
========================== *.txt 문서저장 클래스 ==========================================================   example.cpp  파일 ==
 
#include "docwriter.h"

int main()
{
  DocWriter dw;
  dw.SetFileName( "test.txt" );
  dw.SetContent("You must be a good programmer~!!");
  dw.Write();

  return 0;
}
 
 
========================== *.txt 문서저장 클래스 ==========================================================   실행후  test.txt  파일내용==
 
# Content #
 
You must be a good programmer~!!
 
 
 
 
 
################################################################################################################
 
========================== *.html 문서저장 클래스 ==========================================================   htmlwriter.h  파일 ==
 
#ifndef HTMLWRITER_H
#define HTMLWRITER_H

#include "docwriter.h"

class HTMLWriter : public DocWriter
{
public:
  HTMLWriter();
  ~HTMLWriter();

  // 텍스트를 파일로 저장시킨다.
  void Write();

  // 폰트를 지정한다.
  void SetFont(const string& fontName, int fontSize, const string& fontColor);


protected:
  string  _fontName;
  int    _fontSize;
  string  _fontColor;
};

#endif

 
========================== *.html 문서저장 클래스 ==========================================================   htmlwriter.cpp  파일 ==
 
#include "htmlwriter.h"
#include <fstream>
using namespace std;

HTMLWriter::HTMLWriter()
{
  // 디폴트 파일 이름만 바꾼다.
  _fileName = "NoName.html";

  // 디폴트 폰트를 지정한다.
  _fontName = "굴림";
  _fontSize = 3;
  _fontColor = "black";
}

HTMLWriter::~HTMLWriter()
{
}

// 파일에 텍스트를 저장시킨다.
void HTMLWriter::Write()
{
  // 파일을 연다.
  ofstream of( _fileName.c_str() );

  // HTML 헤더 부분을 저장한다.
  of << "<HTML><HEAD><TITLE>This document was generated by HTMLWriter</TITLE></HEAD><BODY>";
  of << "<H1>Content</H1>";

  // 폰트 태그를 시작한다.
  of << "<Font name='" << _fontName << "' size='" << _fontSize << "' color='" << _fontColor << "'>";

  // 텍스트를  저장한다.
  of << _content;

  // 폰트 태그를 닫는다.
  of << "</FONT>";

  // HTML을 마무리 한다.
  of << "</BODY></HTML>";
}

// 폰트를 지정한다.
void HTMLWriter::SetFont(const string& fontName, int fontSize, const string& fontColor)
{
  _fontName = fontName;
  _fontSize = fontSize;
  _fontColor = fontColor;
}

========================== *.html 문서저장 클래스 ==========================================================   example.cpp  파일 ==
 
#include "htmlwriter.h"

int main()
{
  HTMLWriter hw;
  hw.SetFileName( "test.html" );
  hw.SetContent("You must be a good programmer~!!");
  hw.SetFont("Arial", 16, "blue");
  hw.Write();

  return 0;
}

========================== *.txt 문서저장 클래스 ==========================================================   실행후 test.html  파일내용==
 

Content

You must be a good programmer~!!
Posted by 시긔양

댓글을 달아 주세요

Programming/C++ 언어2008. 8. 20. 09:10

16-3.인라인 함수

이 절 이후의 인라인 함수, 디폴트 인수, 오버로딩은 C++에서 새로 추가된 기능들이다. 따라서 C 컴파일러에서는 컴파일되지 않는다.

16-3-가.인라인 함수

함수는 반복된 동작을 정의함으로써 프로그램의 기본 부품을 구성하는 단위가 된다. 입력과 출력, 내부 동작을 한 번만 잘 작성해 놓으면 필요할 때마다 불러서 똑같은(또는 비슷한) 작업을 여러 번 수행할 수 있다. 다음 예제는 인수로 전달된 n보다 작은 정수 난수를 하나 생성한 후 돌려 주는 randfunc라는 함수를 정의하고 main에서 이 함수를 세 번 호출한다.

 

: randfunc

#include <Turboc.h>

 

int randfunc(int n)

{

     return rand()%n;

}

 

void main()

{

     int i,j,k;

 

     i=randfunc(10);

     j=randfunc(100);

     k=randfunc(50);

     printf("난수=%d,%d,%d\n",i,j,k);

}

 

매번 범위를 다르게 하여 호출했는데 실행 결과는 "난수=1,67,34"과 같으며 컴파일러에 따라 조금씩 달라질 수 있다. 이 프로그램의 동작을 그림으로 그려 보면 다음과 같다.

main에서 randfunc 함수를 호출할 때 정수 인수 하나를 전달하는데 이때 randfunc 함수로 분기가 일어나며 randfunc 함수는 인수 범위까지의 난수를 생성한 후 호출원으로 리턴한다. 매번 randfunc 함수를 호출할 때마다 이런 과정이 반복되는데 함수가 한 번 호출될 때 내부적으로 어떤 일들이 벌어지는지 살펴보자. 앞에서 이미 알아본 내용들인데 다시 한 번 더 정리해 보았다.

 

인수를 전달하기 위해 인수값을 순서대로 스택에 밀어 넣는다.

호출원은 바로 다음 번지를 스택에 기록함으로써 함수가 복귀할 번지를 저장한다.

함수가 정의되어 있는 번지로 점프하여 제어권을 함수에게 넘긴다.

함수는 스택에 자신의 지역변수를 위한 공간을 만든다.

함수의 코드를 수행한다.

리턴값을 넘긴다.

복귀 번지로 리턴한다.

⑧ 인수 전달에 사용한 스택을 정리한다.

 

함수가 호출되고 복귀되는 과정은 이렇게 복잡하다. 물론 동작에 필요한 이런 코드들은 컴파일러가 작성해 주므로 우리는 단순히 이름과 인수만으로 함수를 호출할 수 있다. 함수의 동작이 복잡하고 길이가 길다면 이 정도 호출 시간은 얼마든지 무시할 수 있을 것이다. 하지만 rand()%n이라는 간단한 연산을 위해 이런 복잡한 호출 절차를 거쳐야 한다는 것은 큰 부담이 아닐 수 없는데 함수의 실행에 걸리는 시간보다 호출에 걸리는 시간의 비율이 너무 크기 때문이다.

함수의 실행 시간은 0.001초밖에 안걸리는데 함수를 호출하는 시간이 0.1초나 걸린다면 이럴 때는 이 코드를 함수로 만들지 않고 호출부에 바로 삽입하는 것이 훨씬 더 이득이다. 이런 개념이 바로 인라인(inline) 함수이다. 인라인 함수는 함수이기는 하되 호출될 때 함수가 있는 곳으로 점프하지 않고 함수의 본체 코드를 호출부 자리에 바로 삽입하는 방식의 함수이다. 위 예제의 randfunc 함수를 인라인으로 바꿔 보자. 함수 정의부 앞에 inline이라는 키워드만 붙이면 된다.

 

inline int randfunc(int n)

{

     return rand()%n;

}

 

이렇게 수정하면 randfunc 함수는 인라인 함수가 된다. 실행 파일에는 main 함수만 있고 인라인 함수는 따로 작성되지 않으며 대신 인라인 함수가 호출될 때마다 이 함수의 본체가 호출부에 삽입된다.

컴파일러는 randfunc 함수의 본체 코드 rand()%n을 기억하고 있다가 인라인 함수가 호출되는 곳에 이 코드를 바로 삽입한다. i=randfunc(10) 호출문에서 randfunc(10)이 rand()%10으로 대체되어 버리는 것이다. 이어지는 j=randfunc(100), k=randfunc(50) 호출문도 마찬가지 방식으로 처리된다.

인라인 함수는 실제로 호출되지 않으므로 호출에 걸리는 시간이 필요없어 전체적인 실행 속도가 대단히 빠르다. 복귀 번지를 기록하거나 인수, 리턴값을 전달하는 시간만큼을 절약할 수 있다. 대신 인라인 함수가 호출되는 곳마다 함수의 본체가 삽입되므로 실행 파일의 크기가 커지는 단점이 있다. 즉, 인라인 함수는 속도에 유리하고 크기에 불리한 방법이다.

본체 코드가 아주 작고 속도가 중요할 때 인라인 함수를 사용한다. 함수의 본체가 길고 동작이 복잡하다면 이런 함수는 인라인으로 만들지 않는 것이 더 좋다. 긴 함수는 호출에 걸리는 시간의 비율이 함수의 실행 시간에 비해 아주 작아서 호출 부담이 크지 않으며 또한 장문의 코드를 매번 반복한다면 실행 파일의 크기가 무시못할 정도로 커져 버리기 때문이다.

함수를 인라인으로 만들 때는 함수의 원형이나 정의부에 inline 키워드만 써 주면 된다. 또는 양쪽에 다 inline을 표기해도 상관없다. 보통 인라인 함수는 길이가 짧기 때문에 별도로 원형 선언을 하지 않고 원형이 들어갈 자리에 inline 키워드와 함께 본체를 같이 정의하는 것이 일반적이다. 앞 예제에서 작성한 randfunc 함수가 바로 이 방식으로 작성되었다. 여러 모듈에서 공유하는 함수라면 헤더 파일에 작성해야 한다. 인라인 함수의 본체는 정의가 아닌 선언이므로 메모리를 소모하지도 않으며 중복 선언해도 상관없으므로 보통 헤더 파일에 작성한다. 단 같은 모듈에서 두 번 선언하는 것은 안된다.

함수가 인라인이 될 것인가 아닌가는 프로그래머가 지정하지만 최종 결정은 컴파일러가 한다. 프로그래머가 함수 선언앞에 inline 키워드를 붙이더라도 컴파일러는 이 지정을 무시하고 일반 함수로 만들어 버릴 수도 있다. 프로그래머가 inline 키워드를 사용하는 것은 이 함수가 인라인이 되었으면 좋겠다는 희망 사항일 뿐이며 컴파일러는 조건이 맞지 않을 경우 이 지정을 무시할 수 있다. 마치 register 기억 부류와 유사하다.

예를 들어 재귀 호출 함수는 인라인이 될 수 없다. 왜냐하면 재귀 호출이란 스택을 기반으로 동작하기 때문에 실제로 호출되어야만 자기 자신을 호출할 수 있다. 만약 인라인 함수가 재귀 호출을 하도록 내버려 둔다면 이 프로그램의 크기는 무한대가 되어 버릴 것이다. 프로그램의 다른 곳에서 이 함수의 주소를 참조하는 경우가 있다면 이 경우도 인라인 함수가 되지 못한다. 인라인 함수는 번지를 가질 수 없다.

또한 함수의 길이가 너무 길 경우도 득보다 실이 더 많기 때문에 컴파일러는 이 함수를 강제로 일반 함수로 만들어 버린다. 비주얼 C++의 경우 디버그 버전에서는 디버깅의 편의를 위해 모든 인라인 함수를 일반 함수로 컴파일하는데 프로젝트 옵션으로 이를 조정할 수 있다. 프로젝트 설정 대화상자의 C/C++탭의 Optimizations 탭에 보면 인라인 함수 확장에 대한 옵션이 있다.

반대로 inline 키워드를 명시적으로 쓰지 않아도 자동으로 인라인 함수가 되는 경우도 있는데 클래스 선언에 코드가 작성되어 있는 멤버 함수는 자동 인라인 속성을 가진다. 함수가 인라인이 될 것인가 아닌가는 어디까지나 속도와 크기의 차이가 있을 뿐이지 프로그램의 동작 자체가 달라지는 것은 아니므로 컴파일러가 재량껏 inline 지정을 무시하거나 강제 지정하더라도 별 상관은 없다. 프로그래머는 다만 인라인으로 만들고 싶은 함수 앞에 inline 키워드만 써 주면 된다.

 [원본] http://www.winapi.co.kr/clec/cpp2/16-3-1.htm

Posted by 시긔양

댓글을 달아 주세요

Programming/C++ 언어2008. 8. 12. 21:01
#include <iostream>
#include <iomanip>

using namespace std;

int main()
{
  int    S1_No,     S2_No,     S3_No;     // 학번
  int    S1_Kor,    S2_Kor,    S3_Kor;    // 국어 점수
  int    S1_Math,   S2_Math,   S3_Math;   // 수학 점수
  int    S1_Eng,    S2_Eng,    S3_Eng;    // 영어 점수
  float  S1_Ave,    S2_Ave,    S3_Ave;    // 개인 평균
  float  TotalAve    = 0.0f;              // 전체 평균

  int    NumberOfStudent = 0;             // 현재까지 입력된 학생수



  while(1)
  {
    cout << "    -------------메 뉴-------------" << endl;
    cout << "     1. 학생 성적 추가" << endl;
    cout << "     2. 전체 성적 보기" << endl;
    cout << "     Q. 프로그램 종료 " << endl;
    cout << "    -------------------------------" << endl;
 
    cout << "    메 뉴의 번호를 입력하세요 : ";

    char select;  // 메뉴 번호를 위한 char변수
    cin >> select;

    switch(select)
    {

      /* 성적 추가 case */
      case '1':      
        {
          if(3 == NumberOfStudent)
          {
            cout << "\n더이상 입력할수 없습니다.\n" ;
            cout << "다른메뉴를 선택해 주세요 \n\n"  ;
            continue;
          }
         
          int Kor, Eng, Math;

          cout << endl;
          cout <<'[' << NumberOfStudent+1 << "] 번째 학생의 "<< "국어, 영어, 수학점수를 입력해 주세요 : ";  
          cin >> Kor >> Eng >> Math ;

          float Ave = float(Kor + Eng + Math) / 3.0f;      // 개인 평균
         

          if( 0 == NumberOfStudent)
          {
            S1_No  =  NumberOfStudent + 1;  
            S1_Kor  =  Kor;
            S1_Eng  =  Eng;
            S1_Math  =  Math;
            S1_Ave  =  Ave;

            TotalAve = S1_Ave;
          }
          else if ( 1 == NumberOfStudent)
          {
            S2_No  =  NumberOfStudent + 1;  
            S2_Kor  =  Kor;
            S2_Eng  =  Eng;
            S2_Math  =  Math;
            S2_Ave  =  Ave;

            TotalAve = (S1_Ave + S2_Ave) / 2;
          }
          else
          {
            S3_No  =  NumberOfStudent + 1;  
            S3_Kor  =  Kor;
            S3_Eng  =  Eng;
            S3_Math  =  Math;
            S3_Ave  =  Ave;

            TotalAve = (S1_Ave + S2_Ave + S3_Ave) / 3;
          }
         
         
          NumberOfStudent ++;

          cout << '[' << NumberOfStudent << "] 번째 학생의 성적이 입력 되었습니다\n\n\n";

          break;


         
        }


      /* 성적 보기 case */
      case '2':      
        { 

          cout.precision(2);    // 실수 출력시 소수점 이하 두자리만 출력
          cout << fixed;        // 실수 출력시 소수점 이하 두자리만 출력

          cout << "\n\n      < 전체 성적보기 >\n\n";
          cout << "      학번  국어  영어  수학    평균 \n";
         
          int iCnt;
         
          for( iCnt = 1 ; iCnt <= NumberOfStudent ; ++iCnt)
          {
           
            if ( 1 == iCnt )
            {
              cout << setw(17) << '[' << S1_No << ']' << setw(7) << S1_Kor << setw(6) << S1_Eng << setw(6) << S1_Math << setw(8) << S1_Ave << endl;
            }
            if ( 2 == iCnt )
            {
              cout << setw(17) << '[' << S2_No << ']' << setw(7) << S2_Kor << setw(6) << S2_Eng << setw(6) << S2_Math << setw(8) << S2_Ave << endl;
            }
            if ( 3 == iCnt )
            {
              cout << setw(17) << '[' << S3_No << ']' << setw(7) << S3_Kor << setw(6) << S3_Eng << setw(6) << S3_Math << setw(8) << S3_Ave << endl;
            }
          }

          cout<< '\n' << setw(41) << "전체 평균 = " << TotalAve << endl;


        }
     

      /* 종료 case */
      case 'Q':  
        {
          cout << "\n      ****프로그램을 종료 합니다.****\n" << endl;
          return 0;
        }


      /* 잘못입력 case */
      default:    
        {
          cout << "\n잘못된 입력입니다. 다시입력 해주세요.\n" << endl;
          break;
        }
    }

  }
  return 0;
}
 
Posted by 시긔양

댓글을 달아 주세요