Const 위치

#include <stdio.h>

int main(){
   int a = 30;
   int b = 20;

   const int *p = &a;
   //*p = 40;
   /*
const.c: In function 'main':
const.c:8:8: error: assignment of read-only location '*p'
    *p = 40;
       ^
   */
   p = &b;
   
   int const *p2 = &a;
   //*p2 = 40;
   /*
const.c: In function 'main':
const.c:12:9: error: assignment of read-only location '*p2'
    *p2 = 40;
        ^
   */
   p2 = &b;

   int* const p3 = &a;
   //p3 = &b;
   /*
const.c: In function 'main':
const.c:16:8: error: assignment of read-only variable 'p3'
    p3 = &b;
       ^    
   */
   *p3 = 100;

   return 0;
}

첫번째

    const int *p = &a;
   //*p2 = 40;
   p2 = &b

const는 현재 int*에 걸려있는 것이다. 그래서 *p는 수정이 안되지만, p는 수정이 가능하다.

두번째

    int const *p2 = &a;
   //*p2 = 40;
   /*
const.c: In function 'main':
const.c:12:9: error: assignment of read-only location '*p2'
    *p2 = 40;
        ^
   */
   p2 = &b;

이 케이스 또한 첫번째와 마찬가지이다. const가 걸리는 대상은 *p2다. 그러므로 *p2의 값이 상수, p2는 수정 가능하다.

세번째

    int* const p3 = &a;
   //p3 = &b;
   /*
const.c: In function 'main':
const.c:16:8: error: assignment of read-only variable 'p3'
    p3 = &b;
       ^    
   */
   *p3 = 100;

const가 p3에 걸리는 case이다. 그러므로 p3가 상수여서 참조하는 값을 바꿀 수는 없으나 *p3의 값은 수정 가능하다.

일반적으로 const는 맨 앞에 붙이는 것을 선호한다고 한다.

'언어, git > C++' 카테고리의 다른 글

복사 생성자  (0) 2017.11.19
가상함수?  (0) 2017.11.19

진짜 가고 싶었던 게임사 1차 면접에서 복사 생성자가 무엇인가에 대해 나왔습니다.

이 용어 자체를 잘몰라서 잘모르겠다고 대답했고, 이어서 얕은 복사와 깊은 복사에 대해 물어보셨습니다.

두 용어가 헷갈리긴 했지만, 무엇을 뜻하는지는 기억이 나서 잘 대답했던 경험이 있네요.

어쨋든 면접에서 나온 내용을 복습하고자 합니다!! 복사생성자!!!!!!

얕은 복사와 깊은 복사는 복사 생성자의 사전 지식입니다. 모르시면 찾아서 공부하고 참고해주세요!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
 
class test {
public:
    char* name;
    int score;
 
    test(const char* name, int score) {
        this->score = score;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
 
    void print() {
        cout << this->score << '\n';
        cout << this->name << '\n';
    }
 
    void nameDelete() {
        delete name;
    }
};
 
 
int main(void)
{
    test a("홍길동"30);
    a.print();
 
    test b = a;//얕은 복사 발생
    b.print();
    
    printf("%x\n", b.name);
    printf("%x\n", a.name);
 
    a.nameDelete();
 
    a.print();
    b.print();
    return 0;
}
cs



이 코드의 결과는

30

홍길동

30

홍길동

28e030

28e030

30

硼硼硼硼硼硼硼硼3쬋왹

30

硼硼硼硼硼硼硼硼3쬋왹


가 출력이 될것입니다.

홍길동 밑의 16진수와 아래의 깨지는 문자열은 다르게 출력될 수 있습니다.


이렇게 출력되는 이유에 대해서 알려드리겠습니다.

어떤 클래스의 객체에서 =을 이용하여 객체의 값을 복사하려고 할 때(대입할 때) 따로 오버라이딩을 하지 않는다면

해당 변수의 값만 복사됩니다.

이게 무슨말이냐하면..


위의 test클래스에는 name과 score가 있습니다.

근데 디폴트(오버라이딩을 하지 않았을 떄)

test b = a;

를 실행하면 발생되는 코드는

b.name = a.name;

b.score = a.score;


입니다.


이게 무슨 문제일까요?

네 문제입니다.

왜냐하면 name은 문자열이기 때문입니다. a.name과 b.name은 문자열 자체를 저장하는 것이 아닌 해당 문자열의 시작 주소를 저장하고 있습니다.

그러므로 포인터이겠죠. "홍길동"이라는 새로운 인스턴스가 생성되서 b에 대입되는 것이 아닌, a의 "홍길동"을 같이 가르키게 되는 것입니다.


이게 왜 문제인지 또 모르실 수 있습니다. 이 문제는 위에서 출력된 깨진 문자열을 보시면 됩니다.

문자열이 깨진 이유는 a.nameDelete();을 실행했기 때문입니다. 이것은 함수명 그대로 name에 할당한 메모리를 지워버립니다.

그렇다면.. a의 문자열 "홍길동"이 지워지면 b의 문자열 "홍길동"도 같이 지워지는 것입니다.

(엄연히 따지면 b의 문자열이 아니고 a와 b는 같은 메모리를 가르키고있음)


이를 해결하기 위해서는 복사생성자를 오버라이딩하면 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <iostream>
#include <string.h>
#include <stdio.h>
using namespace std;
 
class test {
public:
    char* name;
    int score;
 
    test(const char* name, int score) {
        this->score = score;
        this->name = new char[strlen(name) + 1];
        strcpy(this->name, name);
    }
 
    void print() {
        cout << this->score << '\n';
        cout << this->name << '\n';
    }
 
    //복사생성자 오버라이딩
    test(const test &T) {
        score = T.score;
        name = new char[strlen(T.name) + 1];
        strcpy(name, T.name);
    }
 
    void nameDelete() {
        delete name;
    }
};
 
 
int main(void)
{
    test a("홍길동"30);
    a.print();
 
    test b = a;//얕은 복사 발생
    b.print();
 
    printf("a.name이 가리키는 주소 : %x\n", a.name);
    printf("b.name이 가리키는 주소 : %x\n", b.name);
 
    a.nameDelete();
 
    a.print();
    b.print();
    return 0;
}
 
cs


위의 주석 부분 //복사생성자 오버라이딩 부분을 추가해주시면 해결할 수 있습니다.(이로써 깊은 복사가 되는 것이다!)


위 코드의 출력은

30

홍길동

30

홍길동

a.name이 가리키는 주소 : 10ddc8

b.name이 가리키는 주소 : 10da80

30

硼硼硼硼硼硼硼硼?k멓

30

홍길동


입니다.


위의 예제와는 다르죠??

a.name과 b.name이 가리키는 주소가 다릅니다.

이는 복사를 할 때 우리가 오버라이딩한대로 실행되기 때문입니다.

단순히 a.name이 가리키는 값을 복사하는 것이 아닌 새로 동적할당을 하여 문자열을 새로만들고 그 주소를 b.name에 대입을 하는 것입니다.


그리고 a.name만 삭제했기 떄문에 그대로 b는 살아있을 수 있는거죠!!!!!!!!!!!!!!!!


이만 포스팅을 마치겠습니다. 궁금한 점은 댓글달아주세요 ^^


ps....

근데 cpp의 string클래스를 사용하면 그냥 알아서 값을 복사해주더라구요..ㅎㅎㅎ..엄청 편리합니다..

'언어, git > C++' 카테고리의 다른 글

Const(상수) 선언 위치  (0) 2019.03.26
가상함수?  (0) 2017.11.19
  • 가상함수란?

    • C++ 클래스에서 virtual 키워드를 사용하는 함수

    • virtual 키워드를 이용하면!! 동적 바인딩이 됨.

    • 동적 바인딩이란 ? (출처: http://itguru.tistory.com/210)

      • 컴파일 시에 어떤 함수가 실행될 지 정해지지 않고, 런타임 시에 정해지는 일을 가리켜서 동적 바인딩(dynamic binding)이라고 부릅니다.

        
      #include <iostream>
      #include <string>
      using namespace std;

      class Parent
      {
      string s;
      public:
      Parent () : s("부모")
      {
      cout << "부모 클래스" << endl;
      }

      virtual void what() { cout << s << endl;}
      };
      class Child : public Parent
      {
      string s;

      public:
      Child () : s("자식"), Parent()
      {
      cout << "자식 클래스" << endl;
      }

      void what() { cout << s << endl;}

      };
      int main()
      {
      Parent p;
      Child c;

      Parent* p_c = &c;
      Parent* p_p = &p;

      cout << " == 실제 객체는 Parent == " << endl;
      p_p->what();

      cout << " == 실제 객체는 Child == " << endl;
      p_c->what();

      return 0;
      }

      41번째 코드 p_c->what()의 출력은 "자식"이다. 이유는 동적 바인딩이 되기 때문이다. virtual 키워드를 사용하지 않았을 때는 p_c->what()은 출력은 "부모"이다. 이유는 p_c가 부모의 포인터이기 때문에 컴파일러는 '부모의 포인터네? 부모의 함수를 실행해야지.'라고 생각을 하기 때문이다. 하지만 virtual키워드를 넣으므로써 컴파일러는 한번 더 생각하게 된다. '부모의 포인터네? 어 근데.. 이게 부모의 객체가 맞을까? 아니네 자식을 출력하자'라고 생각하게 되는 것이다.

    • 사실 클래스의 상속을 사용함으로써 중요하게 처리해야 되는 부분이 있습니다. 바로, 소멸자를 가상함수로 만들어야 된다는 점입니다.

        
      #include <iostream>
      using namespace std;

      class Parent
      {
      public :
      Parent()
      {
      cout << "Parent 생성자 호출" << endl;
      }
      ~Parent()
      {
      cout << "Parent 소멸자 호출" << endl;
      }
      };
      class Child : public Parent
      {
      public:
      Child() : Parent()
      {
      cout << "Child 생성자 호출" << endl;
      }
      ~Child()
      {
      cout << "Child 소멸자 호출" << endl;
      }
      };
      int main()
      {
      cout << "--- 평범한 Child 만들었을 때 ---" << endl;
      {
      Child c;
      }
      cout << "--- Parent 포인터로 Child 가리켰을 때 ---" << endl;
      {
      Parent *p = new Child();
      delete p;
      }
      }

      30번째 코드는 Child c;에서 부모 생성자 -> 자식 생성자 호출이 되고, }에서 지역이 끝나므로 소멸자가 호출이된다. 이때 자식 소멸자 -> 부모 소멸자 순으로 호출이된다. 근데 문제는 아래의 34번째 코드에서 발생한다. Parent *p = new Child();에서 delete p;를 호출한다면 자식의 소멸자가 아닌 부모의 소멸자가 호출(부모의 소멸자가 호출되기 때문에 자식의 메모리 누수가 발생한다)이 된다. 하지만 이 소멸자들을 virtual로 선언을 해주면 우리가 원하는 30번째 코드처럼의 호출이 가능해진다.

      이와 같은 연유로, 상속될 여지가 있는 Base 클래스들은 (위 경우 Parent), 반드시 소멸자를 virtual 로 만들어주어야 나중에 문제가 발생할 여지가 없게 됩니다.

      (출처: http://itguru.tistory.com/211 [Programming IT])

  • 순수 가상함수함수와 추상 클래스

      
    class Animal
    {
    public:
    Animal() {}
    virtual ~Animal() {}
    virtual void speak() = 0;//순수 가상함수 (자바의 추상 메소드)
    };

    추상 클래스란? 순수 가상함수를 적어도 하나 이상 포함하고 있는 함수를 추상클래스라고 함.(자바랑 같음, 여기서 순수 가상함수란? 자바의 추상 메소드와 같음, 즉 구현이 안되어있어야함. cpp에서는 virtual void 함수명 = 0;으로 순수가상함수임을 알림)


'언어, git > C++' 카테고리의 다른 글

Const(상수) 선언 위치  (0) 2019.03.26
복사 생성자  (0) 2017.11.19

+ Recent posts