본문으로 이동

C++

위키백과, 우리 모두의 백과사전.

C++
패러다임프로그래밍 패러다임: 절차적 프로그래밍, 함수형 프로그래밍, 객체 지향 프로그래밍, 제네릭 프로그래밍
설계자비야네 스트롭스트룹
발표일1985년(39년 전)(1985)
최근 버전C++20 (ISO/IEC 14882:2020)
최근 버전 출시일2020년 12월 15일(4년 전)(2020-12-15)
미리보기 버전C++23
미리보기 버전 출시일2022년 3월 17일(2년 전)(2022-03-17)
자료형 체계C++
파일 확장자.cc, .cpp, .cxx, .C, .c++, .h, .hh, .hpp, .hxx, .h++
웹사이트isocpp.org
주요 구현체
LLVM 클랭, GCC, 마이크로소프트 비주얼 C++, 엠바카데로 C++ 빌더, IBM XL C++
영향을 받은 언어
에이다, 알골 68, C, CLU, ML, 시뮬라
영향을 준 언어
에이다 95, C#, C99, 샤프, D, 자바, 루아, , PHP, 파이썬, 러스트, , V

C++AT&T 벨 연구소비야네 스트롭스트룹이 C언어 기반으로 1985년에 발표하여 발전한 프로그래밍 언어이다.

역사

[편집]
C++ 표준
연도 C++ 표준 비공식 명칭
1998 ISO/IEC 14882:1998[1] C++98
2003 ISO/IEC 14882:2003[2] C++03
2011 ISO/IEC 14882:2011[3] C++11, C++0x
2014 ISO/IEC 14882:2014[4] C++14, C++1y
2017 ISO/IEC 14882:2017[5] C++17, C++1z
2020 ISO/IEC 14882:2020[6] C++20, C++2a

1979년, 덴마크의 컴퓨터 과학자 비야네 스트롭스트룹은 C++의 선구자격 언어인 "C with Classes" 작업에 착수하였다.[7] 새로운 언어를 만들려는 의의는 박사 논문을 위한 스트롭스트룹의 프로그래밍 경험에서 비롯되었다.

처음에 스트롭스트룹의 "C with Classes"는 C 컴파일러(Cpre)에 클래스, 상속 클래스, 스트롱 타이핑, 인라인 확장, 기본 인수를 포함한 여러 기능들을 추가하였다.[8]

1983년, "C with Classes"라는 이름은 가상 함수, 함수 이름, 연산자 오버로딩, 참조, 제약조건, type-safe free-store 메모리 할당 (new/delete), 개선된 자료형 검사, BCPL 스타일의 1줄 코멘트(//)를 포함한 새로운 기능들을 추가하면서 "C++"로 변경되었다.

1985년 C++ 프로그래밍 언어 제1판이 출시되었으며 당시 공식표준이 없었기 때문에 이 언어의 절대적인 참조 문헌이 되었다.[9] 최초의 C++ 상용 구현체는 같은 해 10월에 출시되었다.[7]

특징

[편집]

C 언어에 객체지향 프로그래밍을 지원하기 위한 내용이 덧붙여진 것이라고 할 수도 있지만[10], 애초부터 객체지향을 염두에 두고 만들어진 언어와는 다르게, 단지 더 좋은 C 언어로서 수속형 언어로 취급하기도 한다. 초기의 C++은 C 위에 놓인 트랜스레이터로 구현되었다. 즉, C++ 프로그램을 일단 C 프로그램으로 변환하고 나서 C 컴파일러로 컴파일하는 식이었고 따라서 C 언어에 대해 상위 호환성을 갖는 언어였다.

그 후, C 언어의 표준 규격이 바뀔 때 const 수식 등 C++의 기능이 C 언어에 받아들이기도 했다. 현재 C 언어와 C++와의 사이에는 엄격한 호환성은 없다. 특히 C99의 출현으로 C 언어와의 호환성은 깨져 버렸다. 따라서 C99 이후로 C++은 C의 수퍼셋(superset)이 아니다. 그러나, C++17 표준안에서는 다시 C언어가 C++의 진부분집합이 될 것이 확실해 보인다. 현재 C와 C++가 명확한 구별 없이 혼재되어 사용되는 컴파일러가 대부분이지만 C99 이후의 문법을 C++ 컴파일러에서 컴파일할 경우 오류가 발생할 수 있다.

다음과 같은 다양한 기능을 가지고 있어 C++ 언어의 표준 규격은 몹시 복잡하다. C++ 표준의 모든 사항을 완전하게 지원하는 컴파일러는 현재 손꼽을 정도이다.

  • 다중 상속
  • 템플릿
  • 연산자 오버로드
  • 예외 처리
  • 실행시 형 식별

덧붙여 예부터 전해 내려온 절차적인 성격도 그대로 남아 "만능" 설계로, 유연함과 강력함은 다른 언어에 비할 바가 아니지만, 복잡한 언어가 되고 말았다. 이런 점 때문에 보다 객체지향성을 강화하여, 만능설계를 지향하기보다는 단순한 설계를 목표로 한 새로운 언어들인 자바, C#, D 언어 등이 나오기도 했다.

캡슐화

[편집]

캡슐화는 2가지 요소를 만족하도록 언어적으로 구현 되어야 한다.

  • 데이터와 메서드 결합

C++에서는 객체의 저장공간의 위치(this로 표현되는 객체의 데이터 위치 주소값)를 함수에 넘김으로써 데이터와 메서드를 결합한다. this는 객체의 위치 주소값인 포인터이다.

  • 외부에 데이터나 메서드를 은닉

객체 지향 프로그래밍에서 작성된 프로그램 코드는 재사용이 중요한 요소이다. 재사용하려면 다른 개발자가 사용할 때, 필요한 사용방법만 알면된다. 클래스 내면 깊숙히 알 필요가 없는 경우가 많다. 따라서 객체를 설계할 때, 경우에 따라 멤버 변수멤버 함수를 다 알릴 필요가 없다. 어떤 요소들은 내부에서 사용하고 비공개로 설정할 필요가 있다. 해당 객체를 사용하거나 상속해서 사용할 때, 필요한 요소만을 공개하고 API을 작성하여 공개하면 된다. 이런 비공개의 방법으로 은닉을 설정하고 캡슐화의 한요소가 된다.

키워드 3개가 있다. private, protected, public을 사용해서 접근을 제한할 수 있다.

class MyObject {
public:
     MyObject() { }

     int getData();  // 은닉된 데이터는 메서드를 사용하여 인터페이스로 쓸 수 있다.

private: // 이 지정자는 자기 클래스에서만 액세스를 한정한다. 은닉하는 방법으로 사용.
     int m_data;
};

int MyObject::getData()
{
   return this->m_data; // this가 자동으로 넘어온다.
}

여기서 데이터와 메서드 결합은 멤버함수를 구성할 때 this라는 포인터로 상징되는 것을 개발자 코드내에 코딩없이도 자동으로 넘겨 주어 해당 클래스 내의 멤버변수들을 사용하게 하였다. 이렇게 언어적으로 결합한다.

this는 자동으로 객체의 공간위치값이 메모리의 주소값이다. 이것을 포인터로 사용하였다. 이것으로 결국 어떤 객체인지를 구별하는 수단으로 사용하여, 데이터와 메서드를 결합한다. MyObject::가 붙는 클래스의 멤버함수는 실행할 때 자동으로 모두 this가 넘어간다. static 메서드는 this가 제외된다.

this가 멤버 함수에 넘어가는 방법으로 CPU의 레지스터를 사용할 수도 있다. 멤버 함수에서 멤버 변수를 사용할 때 레지스터에 저장된 객체의 위치 주소값으로 하여 내부 멤버변수의 상대주소값과 합하여 위치값을 다시 설정하고 액세스 하면 된다. 이것은 마치 struct의 시작주소와 내부변수의 상대적 위치값의 합으로 표현하는 주소체계와 유사하다.

예제

[편집]

Hello world 프로그램

[편집]

C++ 표준 라이브러리 스트림 기능을 사용하여 표준 출력을 통해 Hello, world!를 출력한다.

# include <iostream> //iostream이라는 헤더를 가져온다

using namespace std; //namespace의 std 모듈을 사용한다

int main() { //메인메소드의 시작지점이다.

    cout << "Hello, world!" << endl; //"Hello, World!" 콘솔로 출력한다.

    return 0; //0을 리턴해 프로그램을 확실히 종료시킨다.
}//메인메소드를 끝낸다

C++20의 모듈 기능을 이용하면 다음과 같이 쓸 수 있다.

import std.core;

using namespace std;

int main() {

    cout << "Hello, world!" << endl;

    return 0;
}

가급적이면 void main을 사용하지 말자. 컴퓨터는 프로그램이 0이 아닌 수를 반환하면

그 프로그램이 제대로 종료되었다 생각하지 않는다. 즉 int main으로 숫자 0을 리턴하여

잘 종료되었다는 걸 컴퓨터에게 알려주자.

객체의 생성과 소멸

[편집]

객체를 구현하기 위해 클래스의 선언이 필요하다. 구조적인 설정을 하고 생성하면 된다.

객체의 선언

[편집]

객체의 모양은 클래스를 사용하여 형을 선언하여 프로그램 한다. C언어에서 전역변수, 지역변수가 있듯이 객체도 선언 위치에 따라 생성과 소멸이 다르다.

객체 만들기 :

  1. 클래스로 객체의 구조를 만든다. 멤버 변수와 멤버 함수로 구성된다.
  2. 정적 또는 동적으로 객체를 생성한다.
  • 전역 정적 객체 : main() 함수 전에 객체의 메모리 공간을 생성하고 생성자가 자동 실행된다. 전역형은 메모리 맵에서 할당된 특정영역을 사용한다. 따라서 응용 프로그램이 시작하기 위해 운영 체제로부터 할당 받아 사용한다.
  • 지역 정적 객체 : 객체가 선언된 시점에서 객체의 메모리 공간을 생성하고 생성자가 자동 실행된다. 지역변수처럼 주로 스택에 할당된다.
  • 동적 객체 : new에 의해 힙영역에 객체의 메모리 공간을 생성하고 생성자가 자동 실행된다.

객체 소멸 :

  • 전역 정적 객체 : main() 함수 return후 소멸된다. 응용 프로그램의 종료와 동시에 소멸된다.
  • 지역 정적 객체 : 함수 블럭 또는 객체가 생성된 임의의 자기 블럭이 끝나면 소멸된다.
  • 동적 객체 : delete에 의해 소멸된다.

접근자

[편집]

클래스는 캡슐화가 가능하다. 따라서 객체를 설계할 때는 특정 멤버변수나 멤버함수를 외부에서 접근을 제한할 필요가 있다.

접근 제어 키워드 3가지:

  • private : 자기 클래스에서만 접근을 허용한다.
  • public : 모두 접근을 허용한다.
  • protected : 자기 클래스와 상속을 받은 자식 클래스에서만 접근을 허용한다.

객체의 생성

[편집]

C언어에서는 전역변수와 지역변수 그리고 동적할당에 의한 데이터 저장공간을 만들 수 있다. 마찬가지로 C++에서도 전역변수로 객체를 생성할 수도 있고 지역변수로 생성하는 것도 가능하다. 또한 new를 통해 동적으로 객체를 생성 시킬 수 있다.

정적 할당 객체

[편집]

C++에서 변수를 잡듯이 객체도 선언을 통해 이루어진다. C에서 전역변수와 지역변수로 나누어는 것과 같이, 객체 역시 같은 방식으로 전역 또는 지역 객체로 선언할 수 있다.

전역 정적 객체는 main() 함수가 실행되기 전 저장공간이 생성되고, 생성자가 호출된다. 객체가 생성되면 메모리에 데이터 저장공간이 생기고 바로 생성자가 호출된다. 따라서 전역 정적 객체 생성자의 호출은 main함수보다 먼저 이루어진다.

동적 할당 객체

[편집]

new에 의해 생성 되고, delete에 의해 삭제된다. new로 생성 되면, new 실행 시점에서 객체의 저장공간인 메모리를 확보하고 생성자가 자동실행된다. 동적객체는 힙영역에 존재하고 포인터를 넘겨 받아서 객체 포인터 변수에 주소값을 저장하고 처리한다.

'new'의 코딩은 함수에 넣을 수 밖에 없으므로 main 함수 시작 이후에야 가능하다.

이미 설정된 메모리 공간을 이용하여 객체 정의하기

[편집]

C/C++은 포인터를 사용한다. 이것은 객체의 크기만 확보되면 객체를 사용하는데 아무 문제가 없다. 따라서 이미 설정된 변수를 포인터를 이용하여 객체화 해서 사용할 수 있다.

#include <iostream>

typedef struct MsgBuff {
    MsgBuff *link;
    int  szpkg;
    int  szdata;
    void *data;
} MsgBuff;

class A {
public:
    A() { num = 0; }
    int num;
};

char gbuff[1024];

int main(int argc, char* argv[])
{
    MsgBuff *pmsg = (MsgBuff*) gbuff;
    int szpkg = sizeof(A)+sizeof(pmsg->link)+sizeof(int)*2;
    pmsg->link = NULL;
    pmsg->szdata = sizeof(A);
    pmsg->szpkg = szpkg;

    A *pa = (A*) &pmsg->data;
    pa->num = 10;
    std::cout << pa->num << std::endl;

    return 0;
}

블럭에서의 정적 클래스 생성과 소멸

[편집]
#include <iostream>
using namespace std;

class A {
public:
   A() { num = 0; }
   A(int n) : num(n) { cout << "생성자 호출 : " << this << endl;  }
   ~A() { cout<< "소멸자 호출 : " << this << endl; }
   int getNum() { return num; }

private:
   int num;
};

int main()
{
   int num;

   cout << "블럭 시작 합니다." << endl;
   {  // 블럭 시작하면 지금부터 선언되는 변수나 클래스는 블럭이 끝나면 사라진다.
      A a(10);

      num = a.getNum();
      cout << "블럭 안에서 a.getNum() = " << a.getNum() << endl;

   } // 블럭이 끝나면 객체 a는 소멸됨. 소멸자가 호출된다.
   cout << "블럭 끝났습니다. num = " << num << endl;
   return 0;
}

실행결과:

블럭 시작 합니다.
생성자 호출 : 0039FCD0
블럭 안에서 a.getNum() = 10
소멸자 호출 : 0039FCD0
블럭 끝났습니다. num = 10

모든 함수는 블럭을 포함하므로 함수내에서 생성된 정적객체는 함수의 블럭이 끝나면 소멸자가 호출되고 객체의 저장공간은 사라진다. 위의 예처럼 임의의 블럭에서 생성된 정적객체 역시 자기의 블럭이 끝나면 소멸자가 호출되고 사라진다.

클래스 변수 선언 시, 클래스 간 교착상태

[편집]

클래스 내에서 멤버변수로 다른 클래스를 사용할 수 있다. 정적 멤버변수와 포인터가 가능하다. 그러나 2개의 클래스가 서로 정적인 객체를 선언하면 교착상태로 빠져 객체의 크기를 결정할 수 없다. 따라서 불가능해 진다.

교착상태 한쪽의 크기 결정
class B;
class A {
   int num;
   B b;
};
class B {
   int num;
   A a;
};
class B;
class A {
   int num;
   B *b;
};
class B {
   int num;
   A a;
};
이 경우 A 클래스는 B 클래스를 멤버변수로 잡았다. 이렇게 하려면 우선 B 클래스의 멤버변수들의 데이터 저장공간의 크기가 결정되어야 한다. 그러나 B 클래스에서 다시 A 클래스를 선언함으로써 크기를 결정할 수 없는 상태가 된다. 따라서 이런경우는 불가능 하다. 결국 한쪽에서 포인터를 사용하여 먼저 크기를 확정해 주어야 한다.
위와 같은 경우 A클래스의 객체 저장공간의 크기는 결정할 수 있다. 모든 포인터는 CPU의 메모리 주소체계가 이미 결정되어 있으므로 크기 계산이 가능하고 각 멤버변수의 주소위치도 결정할 수 있다. A클래스가 결정되었으므로 이제 B클래스도 결정할 수 있다.


클래스 내에서 클래스 변수 선언 시 교착상태

재귀적 선언 변수의 크기 결정
class A {
   int num;
   A a;
};
class A {
   int num;
   A *a;
};
마찬가지로 저장공간의 크기를 결정할 수가 없다. 포인터 변수이므로 크기를 결정할 수 있어서 가능하다.

객체의 예

[편집]
/// 파일 : MyObject.h ////////////////////////////////////

class MyObject { // 클래스의 시작
public:
   MyObject();   // 생성자1
   MyObject(int age, char *name);  // 생성자2 : 생성자는 여러가지가 가능하다.
   virtual ~MyObject();  // 소멸자

   // 멤버변수 age, name, fdyn가 privated이므로 다른 객체에서 접근이 불가능 하다.
   // 따라서 멤버함수로 해결한다. setAge, getAge,setName,getName

   void setAge(int age) { this->age = age; }  // 함수 본체를 클래스 내에 코딩할 수 있다.
                    // 모든 함수를 헤더의 클래스 안에 코딩하면 클래스 전체를 파악하는데 불편할 수 있다.
                    // 그리고 파일을 cpp와 h로 나누는 의미가 훼손될 수 있다.
   int getAge() { return age; }

   void setName(char *n, int opt = 0);  // 만약 opt을 사용하지 호출하면 자동으로 0을 설정한다.
   char* getName();

   static int counter;

private:
   int age;
   char *name;
   int  fdynm;  // name 변수의 메모리 공간을 new (malloc())로 만들었는지를 설정
};

/// 파일 : MyObject.cpp //////////////////////////////////
#include <iostream>
#include "MyObject.h"
using namespace std;

MyObject::MyObject() // 생성자1
{
   age = 0;
   name = NULL;
   fdynm = 0;

   cout << "+생성자1 MyObject() : " << this << endl;
}

MyObject::MyObject(int age, char *name) // 생성자2
{
   this->age = age;
   this->name = name;

   cout << "+생성자2 MyObject("<< age
 <<","<< name << ") : " << this << endl;
}

MyObject::~MyObject() // 소멸자
{
	cout << "-소멸자 MyObject("<< name <<") : " << this << endl;

	if (fdynm) { // name을 저장할 공간을 new로 만들었다면 삭제 한다.
		cout << " delete name = " << name << endl;
		delete [] name;
		name = NULL;
	}
}

int MyObject::counter = 0; // static은 각 객체마다 저장공간이 확보 되지 않는다.
                            // 전체에서 단 하나의 변수 공간 만이 존재한다.
                            // public이므로 어디에서나 접근이 가능하다.

char* MyObject::getName() { return name; }

void MyObject::setName(char *n, int opt)
{
	if (fdynm) delete [] name;
	name = n;
	fdynm = opt;
}

/// 파일 : main.cpp //////////////////////////////////
#include <iostream>
#include "MyObject.h"
using namespace std;

//// 전역변수 //////////////////

MyObject gMe(19, "홍길동"); // 객체를 생성하고, 생성자2을 호출 한다.

int main(int argc, char* argv[])
{
	cout << "main() 시작.\n";

	MyObject *pm = &gMe;

	MyObject::counter++;
	cout << MyObject::counter << " " << pm->getName() << " / "
 << pm->getAge() << endl;

	MyObject kim; // 객체를 생성하고, 생성자1을 호출 한다.
	pm = &kim;
	char *pname = new char[50];
	strcpy(pname, "이순신");
	pm->setName(pname, 1);
	pm->setAge(33);

	pm->counter++;
	cout << MyObject::counter << " " << pm->getName() << " / "
 << pm->getAge() << endl;

	pm = new MyObject(); // 객체를 동적으로 만든다. 생성자1 사용.
	pm->setName("세종대왕");
	pm->setAge(33);

	MyObject::counter++;
	cout << MyObject::counter << " " << pm->getName() << " / "
 << pm->getAge() << endl;
	delete pm;

	cout << "main() 끝.\n";

	return 0;
}
</source >
실행결과 :
 +생성자2 MyObject(19,홍길동) : 00FDD174
 main() 시작.
 1 홍길동 / 19
 +생성자1 MyObject() : 0031F9C4
 2 이순신 / 33
 +생성자1 MyObject() : 004D4948
 3 세종대왕 / 33
 -소멸자 MyObject(세종대왕) : 004D4948
 main() .
 -소멸자 MyObject(이순신) : 0031F9C4
    delete name = 이순신
 -소멸자 MyObject(홍길동) : 00FDD174

=== 객체의 메모리 구조 ===

객체는 클래스에 의해 규정된 구조에 따라 메모리 공간을 확보하고 메모리에 [[액세스 (마이크로프로세서)|액세스]] 함으로써 데이터 처리가 된다.  구조는 기본적으로 struct와 별로 다를 바가 없다. 멤버변수만을 모아 순서대로 나열하여 특정 크기의 메모리를 확보하면 된다. struct에 C++ 필요한 몇가지 추가될  있다.

위의  프로그램의 객체구조를 알기 위해 다음과 같이 프로그램 하면:

<syntaxhighlight lang="cpp">
#include <iostream>
#include <stdio.h>
#include "MyObject.h"
using namespace std;

MyObject hong(19, "홍길동");

int main(int argc, char* argv[])
{
	MyObject *pm = &hong;

	printf("sizeof(MyObject)=%d\n", sizeof(MyObject) );
	printf("&pm=0x%08X, sizeof(pm)=%d\n", &pm, sizeof(pm) );
	printf("pm=0x%08X\n", pm );

	printf("&hong=0x%08X, sizeof(hong)=%d\n", &hong, sizeof(hong) );
	printf("&counter=0x%08X, value=%d\n", &MyObject::counter, MyObject::counter);
	printf("&age=0x%08X, value=%d\n", &hong.age, hong.age );
	printf("&name=0x%08X, value=%s\n", &hong.name, hong.name );
	printf("&fdynm=0x%08X, value=%d\n", &hong.fdynm, hong.fdynm );

	MyObject lee(23, "이순신");
	printf("&lee=0x%08X\n", &lee);

        return 0;
}

실행결과 (x86, 32비트) 예 :

+생성자2 MyObject(19,홍길동) : 00ACC170
sizeof(MyObject)=16
&pm=0x003EFBA8, sizeof(pm)=4
pm=0x00ACC170
&hong=0x00ACC170, sizeof(hong)=16
&counter=0x00ACC184, value=0
&age=0x00ACC174, value=19
&name=0x00ACC178, value=홍길동
&fdynm=0x00ACC17C, value=0
+생성자2 MyObject(23,이순신) : 003EFB90
&lee=0x003EFB90
-소멸자 MyObject(이순신) : 003EFB90
-소멸자 MyObject(홍길동) : 00ACC170
x86 32비트 환경에서 객체 구조 예. virtual사용한 경우.

소멸자에서 virtual을 붙이는 가장 중요한 이유는 상속과 관련하여 소멸자를 정확히 실행하기 위해서이다. 소멸자를 호출할 때, 이 테이블을 보고 호출한다.

'static int MyObject::counter' 변수는 객체와 분리하여 한개의 전역변수로 처리된다. 따라서 이것은 객체의 메모리 크기에 들어가지 않는다. sizeof연산자에 의해 바이트수를 얻은면 16바이트가 된다. 여러개의 같은 클래스의 객체가 존재해도 이 변수는 하나만 존재할 뿐이다. 그리고 정적으로 처리되므로 전역변수만 가능하다.

이제 virtual을 삭제하고 객체를 만들면 멤버변수들로만 구성된 구조를 갖는다.

실행결과 (x86, 32비트) 예 :

+생성자2 MyObject(19,홍길동) : 0083C138
sizeof(MyObject)=12
&pm=0x003CFBFC, sizeof(pm)=4
pm=0x0083C138
&hong=0x0083C138, sizeof(hong)=12
&counter=0x0083C148, value=0
&age=0x0083C138, value=19
&name=0x0083C13C, value=홍길동
&fdynm=0x0083C140, value=0
+생성자2 MyObject(23,이순신) : 003CFBE8
&lee=003CFBE8
-소멸자 MyObject(이순신) : 003CFBE8
-소멸자 MyObject(홍길동) : 0083C138
x86 32비트 환경에서 객체 구조 예. virtual이 없는 경우.

이것은 다음 struct와 메모리 구조가 같다.

/// 파일 : MyStruct.h ///
#ifndef MYSTRUCT_H
#define MYSTRUCT_H

typedef struct {
   static int counter;

   int age;
   char *name;
   int  fdynm;
} MyStruct;

void setMyStruct(MyStruct* pthis, int age, char *name, int opt = 0);

#endif

// 파일 : MyStruct.cpp /////////
#include <stdio.h>
#include "MyStruct.h"

int MyStruct::counter = 0;

void setMyStruct(MyStruct* pthis, int age, char *name, int opt)
{
   pthis->age = age;
   pthis->name = name;
   if (pthis->fdynm) delete [] pthis->name;
   pthis->fdynm = 0;
}

// 파일 : main.cpp ////////
#include <stdio.h>
#include "MyStruct.h"

int main()
{
   MyStruct stdata;
   stdata.fdynm = 0;
   setMyStruct(&stdata, 11, "Song");

   printf("&stdata=0x%08X, sizeof(stdata)=%d\n", &stdata, sizeof(stdata) );
   printf("&counter=0x%08X, value=%d\n", &MyStruct::counter, MyStruct::counter);
   printf("&age=0x%08X, value=%d\n", &stdata.age, stdata.age );
   printf("&name=0x%08X, value=%s\n", &stdata.name, stdata.name );
   printf("&fdynm=0x%08X, value=%d\n", &stdata.fdynm, stdata.fdynm );
   // ...
}

실행결과 (x86, 32비트) 예 :

&stdata=0x0036FBC8, sizeof(stdata)=12
&counter=0x011EC14C, value=0
&age=0x0036FBC8, value=11
&name=0x0036FBCC, value=Song
&fdynm=0x0036FBD0, value=0
x86 32비트 환경에서 C언어의 struct 메모리 구조 예.

멤버 변수

[편집]

클래스 내부의 변수를 말 한다. 클래스의 속성(attribute)이다. 기본 자료형과 다른 클래스 들도 정의가 가능하다.

멤버함수는 프로그램 코드이므로 결국 기계어 코드 묶음에 존재한다. 멤버함수가 실행될 때, 객체의 변수들로 구성된 메모리 구조체의 시작 주소값으로 전달되어 조작된다.

객체 공간의 액세스 시, 객체영역 초과 액세스 문제

[편집]

C에서도 마찬가지이지만, C++는 단지 메모리의 주소값을 이용하여 액세스하는 단순한 방법을 사용한다. 따라서 배열등에서 정의된 크기보다 큰 인덱스로 쓰기하면, 쓰기 자체에는 아무 문제없이 써진다. 그러나 변수가 차지하고 있는 공간을 넘어서는 문제는 C++에서도 그대로 적용된다. 자바와는 달리 어떤 보호책이 없다.

#include <iostream>

class A {
public:
   A() { num = 0; }
   A(int n) { num = n; }

   int num;
};

int main(int argc, char* argv[])
{
    A a(10);
    char buff[4];
    char *pstr = buff;

    std::cout << "num=" << a.num << std::endl;
    printf("&a=0x%08X\n", &a);
    printf("buff=0x%08X\n", buff);

    for (int cnt = 0;cnt < 16;cnt++)
	*pstr++ = cnt;

    std::cout << "num=" <<  a.num << std::endl;

    return 0;
}

실행 결과 예 (x86 32비트 CPU, 마이크로소프트 비주얼스튜디오):[note 1]

num=10
&a=0x0027F848
buff=0x0027F83C
num=252579084

여기에서 10이어야 하는 값이 252579084로 바뀌어 졌다. 이것은 16바이트를 쓰면서 buff영역을 넘어서기 때문이다. buff와 객체 a와는

0x0027F848-0x0027F83C=0xC=12

의 공간차이가 난다. 따라서 12바이트 이상 쓰기 하면 a 영역의 값이 변한다.


Notes:

  1. 실행결과에서 메모리 배치는 CPU와 컴파일러/링커에 따라 다를 수 있다. 3개의 변수 위치는 컴파일러 마다 조금씩 차이가 있을 수 있다.

클래스의 행위(behavior)를 정의한 것으로 기능적으로 설정된 프로그램 묶음이다.

this

[편집]

모든 멤버함수는 멤버변수들로 구성된 구조적 저장공간이 필요하다. 각각의 다른 객체는 다른 메모리 공간에 존재 한다. 따라서 여러개의 객체를 구별한 포인터 변수가 필요하다. this는 객체의 위치를 정의하는 포인터 변수라고 생각하면 된다.

어느 객체에 조작을 할것인가를 결정하는 포인터이다. 'this'는 클래스 내에 변수 선언이 필요없고 이미 정의된 키워드이다.

캡슐화의 방법으로 멤버함수는 this을 통해 멤버변수와 묶는 방법으로 사용한다.

다음 예의 구조체와 클래스에서

struct MyStruct {
   int opt;
   int age;
   char *name;
};

class MyObject
{
public:
   MyObject() { opt = age = 0; name = NULL;}
   void MyObject::setMyObject(int age, char *name, int opt = 0);

private:
   int opt;
   int age;
   char *name;
};

void setMyStruct(MyStruct* pthis, int age, char *name, int opt/* = 0*/)
{
   pthis->opt = opt;
   pthis->age = age;
   pthis->name = name;
}

void MyObject::setMyObject(int age, char *name, int opt/* = 0*/)
{
   this->opt = opt;
   this->age = age;
   this->name = name;
}

위의 예에서 C언어의 struct는 구조체 내의 변수들을 묶어서 메모리에 할당한다.

  • C 언어의 struct : void setMyStruct(MyStruct* pthis, int age, char *name, int opt = 0);
  • C++ 객체의 멤버함수 : void MyObject::setMyObject(int age, char *name, int opt = 0);

위의 2개의 차이는 'MyObject::'을 붙이면 this 포인터가 자동으로 들어간다. 이를 통해 멤버함수를 클래스와 통합한다. 그러나 C언어의 함수는 어떤 변수의 구조체와 연결이 없다. 필요하면 포인터나 변수를 인수를 통해 넘길 뿐이다. 그러나 C++에서는 this는 반드시 포함 시키는 차이가 있다. static 멤버함수는 this가 넘어가는 것은 제외된다.

static 멤버 함수

[편집]

this가 객체의 위치 주소값을 취급하는 자동 포인터로써 멤버함수가 호출되면 자동으로 따라 다닌다. 이말은 이미 객체가 존재 한다는 것이다. 그러나 static을 사용하면 특정 객체의 this을 사용하지 않는다. 객체가 이미 존재하는 것은 상관없이 이미 존재하는 객체의 포인터 값을 모른다. 따라서 호출 할때, 특정 객체가 미리 생성되지 않아도 된다. 이미 생성되었다면 오히려 인수로 넘겨 주어야 한다.

class A {
public:
   A(int n) { num = n; }
   int num;
   int add(int n2) { return (num += n2); }

   static int add(A *pa, int n2);
};

int A::add(A *pa, int n2)
{
    //num += n2;  // 오류 : this을 사용할 수 없다. 객체의 포인터가 넘어오지 않는다.
 // error C2597: illegal reference to non-static member 'A::num'
    pa->num += n2;
    return pa->num;
}

int main()
{
    A a(10);
    int r = A::add(&a, 20);     // 결과 30
    std::cout << r << std::endl;

    r = a.add(&a, 10);           // 결과 40
    std::cout << r << std::endl;

    return 0;
}

위의 프로그램 예제 코드 처럼, 객체가 없는 상태에서 실행되는 것이 static 멤버함수이기 때문에 객체의 멤버변수를 사용하지 않아도 된다. 클래스 이름으로 호출하면 되고 설령 이미 존재하는 객체의 멤버변수로 하더라고 this 가 넘어가지 않으므로 별 차이가 없다. 캡슐화 되지 않는 C 언어의 함수와 별 차이가 없다. C 언어의 함수와의 차이는 단지 '어느 클래스에 속하는냐'만의 의미만 갖는다.

struct 사용 함수, 객체 멤버변수, static 멤버함수 비교

[편집]
struct 사용 함수 객체 멤버변수 static 멤버함수
struct MyStruct {

   int num;

};




int add(struct MyStruct *pthis, int n)
{ return (pthis->num += n); }

// 사용하기
   struct MyStruct st = { 10 };
   int num;
   num = add(&st, 1);
class MyObject {
public:
   int num;

   MyObject(int n) : num(n) { }
   int add(int n);

};

int MyObject::add(int n)
{  return (this->num += n); }

// 사용하기
   MyObject obj(20);
   int num;
   num = obj.add(1);
class MyObject {
public:
   int num;

   MyObject(int n) : num(n) { }
   static int add( MyObject *pthis, int n);
   static int add(int n1,int n2){return n1+n2;}
};

int MyObject::add(MyObject *pthis, int n)
{ return (pthis->num += n); }

// 사용하기
   MyObject obj(20);
   int num = MyObject::add(&obj, 1);
   num = obj.add(&obj, 1);
   num = MyObject::add(1, 2);

struct(구조체) 와 class(클래스)는 동일

[편집]

흔히 구조체는 클래스 이전에 선언되고 주로 여러종류의 기억공간을 선언하기 위한 형태로만 보는 경우가 일반적인데, 이는 틀린 생각이다. 구조체 역시 클래스와 동일하게 생성자와 소멸자를 지정할 수 있고, 코딩이 가능하며 내부호출과 캡슐화가 가능하다. 따라서, 하나의 물체(Object)를 하나의 구조체(struct) 로 선언하고 사용할 수 있다. 따라서, 구조체 내에서도 모든 함수와 명령을 클래스와 동일하게 사용 가능하다. 다만, 멤버에 대해 접근 지정자를 써 주지 않는 경우 구조체는 public이 되고, 클래스는 private이 된다는 차이가 있다.

같이 보기

[편집]

각주

[편집]
  1. “ISO/IEC 14882:1998”. International Organization for Standardization. 2017년 1월 15일에 원본 문서에서 보존된 문서. 2018년 11월 23일에 확인함. 
  2. “ISO/IEC 14882:2003”. International Organization for Standardization. 2021년 8월 13일에 원본 문서에서 보존된 문서. 2018년 11월 23일에 확인함. 
  3. “ISO/IEC 14882:2011”. International Organization for Standardization. 2016년 5월 27일에 원본 문서에서 보존된 문서. 2018년 11월 23일에 확인함. 
  4. “ISO/IEC 14882:2014”. International Organization for Standardization. 2016년 4월 29일에 원본 문서에서 보존된 문서. 2018년 11월 23일에 확인함. 
  5. “ISO/IEC 14882:2017”. International Organization for Standardization. 2013년 5월 17일에 원본 문서에서 보존된 문서. 2017년 12월 2일에 확인함. 
  6. “ISO/IEC 14882:2020”. International Organization for Standardization. 2020년 12월 16일에 원본 문서에서 보존된 문서. 2020년 12월 16일에 확인함. 
  7. Stroustrup, Bjarne (2010년 3월 7일). “Bjarne Stroustrup's FAQ: When was C++ invented?”. 《stroustrup.com》. 2010년 9월 16일에 확인함. 
  8. Stroustrup, Bjarne. “A History of C ++ : 1979− 1991” (PDF). 
  9. Stroustrup, Bjarne. “The C++ Programming Language” Fir판. 2010년 9월 16일에 확인함. 
  10. C++ 은 C 언어를 배경으로 만들어졌기 때문에 C 언어에는 없는 객체지향 프로그래밍을 올린거라 말하면 된다.

외부 링크

[편집]