2015. 3. 9. 21:21

복사생성자가 호출되는 시점은 언제일까?

복사생성자

: 자기 자신과 같은 형태의(자료형의) 객체를 인자로 받을 수 있는 생성자를 복사생성자라고 한다.

AAA(const AAA& a)

{

}



1. 기존에 생성된 객체로 새로운 객체를 초기화하는 경우

int main()

{

   Person p1;

   Person p2=p1;  //기존에 생성된 객체로 새로운 객체 초기화

}



2. 함수 호출 시 객체를 값에 의해 전달하는 경우

void func(Person p)


3. 함수 내에서 객체를 값에 의해 리턴하는 경우

Person fun2()

{
   Person p;
   return p;
}


>> 부록

디폴트 복사 새성자 존재

디폴트 복사 생성자 - 자동으로 삽입되는 복사 생성자

: 멤버 변수대 멤버 변수의 복사를 수행


출처: 열혈강의 c++프로그래밍_윤성우

Posted by Triany
2015. 3. 9. 21:15

const 함수

1.상수화된 함수는 상수화 되지 않은 함수의 호출을 허용x

2.멤버 변수의 포인터 리턴 허용 x


const 객체

1. 어떤 경로를 통해서든 멤버 변수 조작 불가능

2. 상수화된 멤버 함수만 호출 가능



static 멤버(=클래스변수)의 특징

1. main함수가 호출되기도 전에 메모리 공간에 올라가서 초기화 된다.

 따라서 public으로 선언이 된다면, 객체 생성 이전에도 접근이 가능하다.

2. 객체의 멤버로 존재하는 것이 아니다. 다만, 선언되어 있는 클래스내에서 직접 접근할 수 있는 권한이 부여된 것이다.

-> 데이터 영역에 위치


c++에서는 static 멤버 초기화 문법이 존재한다.

class AAA

{

public:

    static int n;

};

int AAA::n=1;


int main(void)

{

    std::cout<<AAA::n << std::endl;

    AAA::n++;

    return 0;  

}



출처: 열혈강의 c++프로그래밍_윤성우 저

Posted by Triany
2015. 3. 9. 18:42

멤버 이니셜라이저문법 : const 멤버를 초기화 하는 것이 가능!

class Student

{

    const int id;

    int age;

public:

    Student(int _id, int _age) : id(_id)

    {

        age=_age;

    }

};

생성자 : id(_id) 

멤버변수 id를 매개변수 _id로 초기화하라!




이 방법이 아니라

Student(int _id, int _age)

{

    id = _id;

   age = _age; 

}

로 초기화 하면 당연히 에러가 뜬다!! 

객체의 생성순서는

1. 메모리 할당

2. 초기화 인데,

객체가 생성당시 쓰레기 값으로 이미 초기화 된 것을,

생성자로 다시 초기화 하려고 하면서 문제가 발생한다!!

따라서 const멤버 변수를 초기화 하기 위해서는

반드시 위의

멤버 이니셜라이저! 라는 문법을 이용할것!!

Posted by Triany
2015. 3. 9. 14:43

생성자 내에서 동적할당을 한다면

반드시 제공해 주어야 할것!! 1)소멸자   2)복사생성자


class Person

{

    char *name;
    int age;
public:
    Person(char* _name, int _age)
   {
        name = new char[strlen(_name)+1];
        strcpy(name, _name);
        age = _age;
   }



1) 소멸자 제공

-> 메모리 누수(유출) 막음

~Person()

{

    delete []name;

}


2) 복자생성자 정의

->잘못된 메모리 참조 막음  ( 얕은 복사(주소값 복사)가 아닌 깊은 복사(공간할당하여 값 복사)를 하기 위함 )

Person(const Person& p)

{

     name = new char[strlen(p.name)+1];

     strcpy(name, p.name);

     age = p.age;

}


Posted by Triany
2015. 2. 17. 11:39


■ ( const char * ) 상수형 문자에 대한 포인터. 포인터가 가리키는 변수의 을 바꿀 수 없음

#include <iostream>

int main()

{

    char ch1 = 'a';

    char ch2 = 'b';

    const char * pch;

    pch = &ch1;

    std::cout << *pch << std::endl;

    //*pch = 'c';  //에러  error: assignment of read-only location ‘* pch’

    pch = &ch2;

    std::cout << *pch << std::endl;


    return 0;

}

$ constchar

a

b



■ ( char const  * ) 문자에 대한 상수형 포인터. 포인터 값을 바꿀 수 없음

#include <iostream>

int main()
{
    char ch1 = 'a';
    char ch2 = 'b';
    char * const pch = &ch1;
    std::cout << *pch << std::endl;
    *pch = 'c';
    std::cout << *pch << std::endl;
    //pch = &ch2;   //에러 error: assignment of read-only variable ‘pch’
    std::cout << *pch << std::endl;

    return 0;
}

$charconst

a

c



■ ( const char const  * ) 상수형 문자에 대한 상수형 포인터. 

  포인터가 가리키는 변수의 과 포인터 값 을 바꿀 수 없음


#include <iostream>

int main()
{
    char ch1 = 'a';
    char ch2 = 'b';
    const char * const pch = &ch1;
    std::cout << *pch << std::endl;
   //*pch = 'c'; //error: assignment of read-only location ‘*(const char*)pch’ !! 문자값 못바꿈 
    std::cout << *pch << std::endl;
    //pch = &ch2; // error: assignment of read-only variable ‘pch’ 포인터 값 못바꿈!
    std::cout << *pch << std::endl;

    return 0;
}


$charconst

a






여기서 새로 발견한것! 당연한거지만!

char * const pch = 'c';

 error: invalid conversion from ‘char’ to ‘char*’ [-fpermissive]

 문자열이 아닌 'c' 문자 하나만 써주면! 에러가 뜬다.



문자열로 선언해주면, warning이뜬다. 

char * const pch = "c";


 warning: deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]

 char * const pch = "c";



Posted by Triany
2015. 2. 13. 18:33

기존에 우리는 배열을 선언할때

int arr[5] = {1,2,3,4,5};

이렇게 선언이 가능했다.


그럼, class도 literal하게 선언이 가능할까?


해당사항은 c++98 컴파일러에서는 불가능 했는데,

c++11부터!  literal class로 선언 가능해 졌다.



#include <iostream>

class A
{
    public:
    int a;
    char b;
};


int main()
{
    A a = A{ 1, 'a'};
    std::cout << "a: " << a.a << "\tb: " << a.b << std::endl;
    return 0;

 



$ c++ -std=c+*98 main.cpp
main.cpp: In function ‘int main()’:
main.cpp:13:9: warning: extended initializer lists only available with -std=c++11 or -std=gnu++11
  A a = A{ 1, 'a'};
         ^ 

 

  c++98 컴파일러로 컴파일 하니 c++11 컴파일러로 하라고 친절하게 설명해 주었다. ㅋ

 extended initializer lists only available with -std=c++11 or -std=gnu++11

 

  

$ c++ -std=c++11 main.cpp
$ a.out
a: 1    b: a 


Posted by Triany
2014. 11. 24. 14:29

C컴파일러와, C++컴파일러가 호출하고자 하는 함수를 찾을때 참조하는 정보가 다르기 때문이다!!


* C 컴파일러 ( 함수 오버로딩 x )

 함수의 이름

C컴파일러는 호출하고자 하는 함수를 찾을 때 오로지 함수의 이름 정보만을 갖고 찾기 때문에 정의된 매개 변수의 형태가 달라도 동일한 이름의 함수 정의는 허용하지 않는다.


#overC.c

  1 #include <stdio.h>

  2

  3 int function(void)

  4 {

  5     return 0;

  6 }

  7 int function(int a, int b)

  8 {

  9     return a+b;

 10 }

 11

 12 int main()

 13 {

 14     function();

 15     function(1,2);

 16     return 0;

 17 }


$ gcc overC.c

overC.c:7: error: conflicting types for ‘function’

overC.c:3: note: previous definition of ‘function’ was here

overC.c: In function ‘main’:

overC.c:14: error: too few arguments to function ‘function’





* C++ 컴파일러 ( 함수 오버로딩O)

  함수의 이름 + 매개 변수의 정보

C++컴파일러는 호출하고자 하는 함수를 찾을 때 함수의 이름뿐만 아니라 매개 변수의 정보까지도 참조를 한다. 

즉 C++은 이름이 같고 매개 변수의 타입 혹은 개수가 다른 함수들의 정의를 허용한다.

 => 이를 함수 오버로딩이라고 한다!

#include <iostream>


int function(void)

{

    return 0;

}

int function(int a, int b)

{

    return a+b;

}


int main()

{

    function();

    function(1,2);

    return 0;

}

$ g++ -o overCPP overCPP.cpp

문제없이 동작한다.


출처 : 열혈강의 C++프로그래밍(윤성우저) 참고

Posted by Triany
2014. 6. 20. 14:56

# 전역 함수에 대한 friend 선언

friend 선언을 통해서 private로 선언된 멤버 변수의 접근을 허용


#include <iostream>


using std::endl;

using std::cout;


class Counter

{

    int val;

public:

    Counter()

    {

        val = 0;

    }

    void Print() const

    {

        cout << val << endl;

    }


    friend void SetX(Counter &c, int val); //friend선언


};

void SetX(Counter &c, int val)

{

    c.val = val; //private로 선언된 멤버 변수 접근

}




int main()

{

    Counter cnt;

    cnt.Print();


    SetX(cnt, 2002);

    cnt.Print();

    return 0;

}


$ 4-6-1

0

2002



출처 : 윤성우의 c++프로그래밍(열혈강의)

Posted by Triany
2014. 6. 20. 14:46

함수가 클래스의 멤버함수인 경우,

 const키워드를 뒤에 삽입가능한데,

이경우 함수에 속해있는 객체의 멤버변수를 변경할 수 없다.


#include <iostream>


using std::endl;

using std::cout;


class Counter

{

    int val;

public:

    Counter()

    {

        val = 0;

    }

    void Print() const

    {

        val = 1; //Error

        ocut << val << endl;

    }


};



int main()

{

    Counter cnt;

    cnt.Print();


    return 0;

}

$ gcc.sh 4-6-1.cpp

4-6-1.cpp: In member function ‘void Counter::Print() const’:

4-6-1.cpp:16: error: assignment of data-member ‘Counter::val’ in read-only structure

4-6-1.cpp:17: error: ‘ocut’ was not declared in this scope



Posted by Triany
2014. 6. 17. 11:52

[c++] 레퍼런스, reference, int &ref = val; 레퍼런스 변수, 별명!


 레퍼런스 

-> 별명!

# 레퍼런스를 선언하는 방법 

int & ref = val;



#레퍼런스의 특징

"레퍼런스를 가지고 하는 연산은 레퍼런스가 참조하는 변수의 이름을 가지고 하는 연산과 같은 효과를 지닌다."


#include <iostream>


using std::cout;

using std::endl;



int main(void)

{

    int val = 10;

    int &ref = val;


    val++;

    cout << " ref : " << ref << endl;

    cout << " val : " << val << endl;


    ref++;

    cout << " ref : " << ref << endl;

    cout << " val : " << val << endl;


    return 0;

}

$ 2-3-1

 ref : 11

 val : 11

 ref : 12

 val : 12



# 레퍼런스에 대한 명확한 이해

void function(void)

{

   int val;     //1

   val = 20;

   ...

   int &ref=val; //2

}


변수란? 메모리 공간에 할당된 이름을 의미한다. 우리는 그 이름을 통해 메모리 공간에 접근하게 된다.

위의 코드는 그 과정과 같다.

1) 메모리 공간 할당 & 이름 부여

2) 이름만 하나더 부여


C 언어에서는 하나의 메모리 공간에 하나의 이름만을 부여할 수 있었다.

즉 C 언어에서는 하나의 메모리 공간에 둘 이상의 이름을 부여하지 못했다.

하지만 C++에서는, 레퍼런스를 선언하게 되면

"이름이 존재하는 메모리 공간에 하나의 이름을 더 부여"할 수 있다.

"레퍼런스 변수는 생성되는 방법에 있어서만 차이를 보일 뿐 만들어지고 나면 완전히 같은 것이다.!"





여기서 이 예제가 가능한 이유가 되겠다!

(레퍼런스를 이용한 call by reference 예제!)

#include <iostream>


using std::endl;

using std::cout;



void Swap(int &fnum1, int &fnum2)

{

    int temp;

    temp = fnum1;

    fnum1 = fnum2;

    fnum2 = temp;

}



int main(void)

{

    int num1 = 10;

    int num2 = 20;


    cout << "[Swap]함수 호출 전 " << endl;

    cout << "num1 : " << num1 << endl;

    cout << "num2 : " << num2 << endl;




    Swap(num1, num2);

    cout << "[Swap]함수 호출 후 " << endl;

    cout << "num1 : " << num1 << endl;

    cout << "num2 : " << num2 << endl;


    return 0;

}

$ 2-3-2

[Swap]함수 호출 전

num1 : 10

num2 : 20

[Swap]함수 호출 후

num1 : 20

num2 : 10




call by reference예제를 막연히 봤을 때는,

"어떻게 저렇게 하는게 가능하지? 

 &는 C에서는 주소로 접근하는 것이었는데, 어떻게 swap연산이 되는걸까?"

하는 막연한 의문이 있었다! 바로 레퍼런스 변수를 제대로 몰랐기 때문이다.!


여기서 예제와 같이 사용하면, 

Swap함수에서 변수로 선언한 fnum1, fnum2가 메인함수에서 선언한 num1, num2의 

별명이 되어! 그대로 swap이 가능한 것이다.!

즉 같은 메모리 공간에 이름만 하나 더 부여해서 사용하기에! 

(지역함수가 매개변수에 대해 메모리 공간을 따로 할당받지 않는 것으로 보인다.!)

fnum1은 num1의 별명으로,

fnum2는 num2의 별명으로 쓰였다!



이 상황을 자세히 알아보기 위해!

아얘 변수들의 주소를 출력하기로 마음 먹었다.!


#include <iostream>


using std::endl;

using std::cout;



void Swap(int &fnum1, int &fnum2)

{

    int temp;

    temp = fnum1;

    fnum1 = fnum2;

    fnum2 = temp;

    cout << "fnum1의 주소: " << &fnum1 << endl;

    cout << "fnum2의 주소: " << &fnum2 << endl;



}



int main(void)

{

    int num1 = 10;

    int num2 = 20;

    cout << "num1의 주소: " << &num1 << endl;

    cout << "num2의 주소: " << &num2 << endl;



    cout << "[Swap]함수 호출 전 " << endl;

    cout << "num1 : " << num1 << endl;

    cout << "num2 : " << num2 << endl;




    Swap(num1, num2);

    cout << "[Swap]함수 호출 후 " << endl;

    cout << "num1 : " << num1 << endl;

    cout << "num2 : " << num2 << endl;


    return 0;

}


$ 2-3-2

num1의 주소: 0x7fffeb761724

num2의 주소: 0x7fffeb761720

[Swap]함수 호출 전

num1 : 10

num2 : 20

fnum1의 주소: 0x7fffeb761724

fnum2의 주소: 0x7fffeb761720

[Swap]함수 호출 후

num1 : 20

num2 : 10





주소들이 정확히 같은 것으로 볼 수 있다.!

같은 주소공간을 별명으로 가르키기에!

메인함수의 num1, num2값이 변경되지 않았다.!

레퍼런스를 이용하면 더 명시적으로 쉽게 코딩이 가능한 것으로 보인다.!



물론 포인터 연산으로 구현하는 방법도 있지만

레퍼런스를 사용하면 메인함수의 변수의 값을 바꾸는게 더 편해 보인다.!


(단점) main함수만 봐서는 swap함수가 call-ByValue인지 Call-By-Rerence인지 알 수 없다.

프로그램이 길어진다면 확인하기 힘들다. 이 단점때문에 레퍼런스를 사용하지 않는 편이 낫다고 하는 전문가들이 많다고 한다. 흠.. 나는 잘 모르겠다. 레퍼런스를 총한 call by Rerence편한듯ㅎ




(참고) 포인터를 이용한 call by reference

포인터를 이용해서 포인터가 가리키는 메모리 공간에 직접 접근 가능

#include <iostream>


using std::endl;

using std::cout;



void Swap(int * fnum1, int * fnum2)

{

    int temp;

    temp = *fnum1;

    *fnum1 = *fnum2;

    *fnum2 = temp;

}



int main(void)

{

    int num1 = 10;

    int num2 = 20;


    cout << "[Swap]함수 호출 전 " << endl;

    cout << "num1 : " << num1 << endl;

    cout << "num2 : " << num2 << endl;




    Swap(&num1, &num2);

    cout << "[Swap]함수 호출 후 " << endl;

    cout << "num1 : " << num1 << endl;

    cout << "num2 : " << num2 << endl;


    return 0;

}

$ 2-3-2

[Swap]함수 호출 전

num1 : 10

num2 : 20

[Swap]함수 호출 후

num1 : 20

num2 : 10




# 레퍼런스의 제약

"레퍼런스는 선언과 동시에 초기화 되어야 한다." 

따라서 아래의 코드는 Error!


int &ref1 //초기화 되지 않았으므로 ERROR

int &ref2 = 10; //상수가 올 수 없으므로 ERROR





#레퍼런스를 리턴하는 함수의 정의

#include <iostream>


using std::cout;

using std::endl;


int& increment( int &val )

{

    val++;

    return val;

}


int main(void)

{

    int n = 10;

    int &ref = increment(n);


    cout << " n : " << n << endl;

    cout << "ref: " << ref << endl;

    return 0;

}


$ 2-6-1

 n : 11

ref: 11




/*

매개변수로 선언된 레퍼런스 val은 지역 변수와 마찬가지로 함수의 호출이 완료되면 사라질 뿐이다. 

그러나 이름만 사라질 뿐, val이라는 이름이 붙어있는 메로리 공간까지 사라지는 것은 아니다!

*/




# 레퍼런스를 리턴하는 잘못된 형태의 함수

"지역변수는 레퍼런스로 리턴 할 수 없다"!

#include <iostream>


using std::cout;

using std::endl;


int & function(void)

{

    int val = 10;

    return val;

}



int main(void)

{

    int &ref = function();

    cout << ref << endl;


    return 0;


}



$ gcc.sh 2-6-2.cpp

2-6-2.cpp: In function ‘int& function()’:

2-6-2.cpp:8: warning: reference to local variable ‘val’ returned





/*

함수가 종료되면 function함수의 지역변수 val은 사라져 버린다.

제대로 출력 되었다고 하더라도 이는 보장받을 수 없는 값이다.

** 지역변수를 레퍼런스로 리턴하는 일은 없어야 한다.

*/





출처 : 윤성우의 열혈강의 c++ 프로그래밍

       

Posted by Triany