C++ 레퍼런스에 대해 정리해보는 시간을 가지자.

사실 정의만 놓고 봐서는 어려운 내용이 아니지만

정확하게 알고가지 않으면 나중에 상당히 혼동될 염려가 있을 것 같다.

이번 포스팅에서 완벽하게 정리하고 가겠다.



1. 레퍼런스란 쉽게 말해서 변수에 하나의 이름을 더 붙여주는 것. 하나의 메모리 공간에 여러 이름을 붙여줄 수 있다.


& 연산자는 포인터에 주소값을 전달하기 위해 사용할 수 있고

레퍼런스 선언을 위해 사용할 수 있다.


ex)

int a = 10;

int *ptr = &a;

int &ref = a;


변수란? 메모리 공간에 할당된 이름 --> 레퍼런스는 그러한 이름에 또 다른 이름을 붙여준다.

--> 그 이름으로도 변수를 원래대로 사용할 수 있다.


int i = 5;

int &j =i;


선언과 동시에 초기화가 되어야 하며

선언된 후에는 원래 변수와 완전 똑같이 사용 가능하다.

이름이 존재하지 않는 대상을 레퍼런스로 선언할 수 없다. (ex 상수)


int fct(){


int val = 20;

int &ref = val;

return ref;


}

--> int형 레퍼런스를 int형으로 반환해도 문제없다! --> 레퍼런스는 완전히 똑같은 것으로 생각하면 된다.

--> 만들어지는 방식만 차이가 날 뿐 똑같다.


반대로


int& Get(int& x){ return x; }


int main(){


int _x = 10;

int y = Get(_x);  ---> Get은 레퍼런스를 리턴받아서 바로 변수에 대입. --> 레퍼런스는 변수와 똑같이 사용가능하기 때문


}

------------------------------------------------------------------------------ex2


int i = 0;

int &ref1 = i;

int &ref2 = ref1;


--> i에 ref1 ,ref2라는 이름을 붙여주고 있다. (하나의 메모리 공간에 이름이3개 --> (C에서는 상상도 못하는 일)

--> 역시 문제 없다.


-------------------------------------------------------------------------------



2. call by value , call by reference


1. 포인터를 이용한 call by reference --> 포인터 연산에 의한 것이기 때문에 위험성이 존재한다.


void swap (int *xxx, int *yyy){

...

xxx++;  ---> 이러한 코드가 잘못들어갈 경우 심각한 오류가 생길 수 있다.

...

}



2. 레퍼런스를 이용한 call by reference --> 함수의 호출 형태를 구분하기 어렵다.


void swap (int &xxx, int &yyy){ ... }


int main(){

int a,b;

....

...

swap(a,b);             --> a,b 변수가 xxx, yyy 라는 별명으로 함수내에서 적용된다.

--> call by value인지 call by reference 인지 구분이 모호하다.

}



포인터 연산을 할 필요가 없으므로 보다 안정적이다.



3. call by value


void show(int _a){


cout<< _a <<endl;

}


int main(){


int a = 5;

show(a);    --> 변수a를 매개변수 _a에 복사하게 된다. --> 전달 내용이 커질 수록 복사량이 많아짐 --> 기능저하


}


void show(int &_a){ ... }

--> 레퍼런스를 이용하면 복사가 일어나지 않는다. --> 성능 향상

--> 레퍼런스를 이용할 경우 함수내에서 데이터가 조작될 가능성이 있다.


void show(const int &_a) { ... } --> 조작될 위험을 방지 (const)


-----------------------------------------------------------------------------------------------


3. 레퍼런스를 리턴하는 함수


int& GetInt(int &val){


return val;

}


int main(){


int n = 10;

int &ref = GetInt(n);  -----------> 1. n이라는 메모리 공간에 val이라는 이름을 붙여준다

---> 2. 레퍼런스를 리턴하는 함수이기 때문에 n은 ref라는 이름을 하나 더 갖게 된다. ( n = val = ref )

---> 3. 함수가 종료되면 함수 내의 레퍼런스도 일반 변수와 마찬가지로 스택에서 없어지게 된다. --> (이름만 없어짐, 메모리는 그대로)

---> 4. 결과적으로 n이라는 메모리 공간에 ref라는 이름을 하나 더 가지게 된다. (val은 함수 종료와 함께 없어짐)

}


------------------------------------------------------------------------------------------------------------------------------------


int GetInt(int &val){ 

...

return val;      ------------> int형으로 리턴된다. 위의 경우에 에러가 난다. 왜?? 레퍼런스는 상수에 정의를 할 수 없다!!

 } 


-------------------------------------------------------------------------------------------------------------------------------------


int& fct(){


int val = 10;

return val;


}


int main(){


int &ref  = fct(); ------------> 오류가 난다. 왜?? 함수가 끝나면 val이라는 메모리 공간은 없어지기 때문에

--> 없는 공간에 이름을 붙일 수 없다!


}


--> 지역변수는 레퍼런스로 리턴할 수 없다.

--> 지역객체 또한 레퍼런스로 리턴할 수 없다.



?? 객체 레퍼런스를 리턴하는 함수 대한 이해가 필요함  --> 복사생성자에 해답이 있는 듯 하다


AAA 클래스{

int x,y;


public:

AAA(int _x, int _y) : x(_x), y(_y){};


}


AAA Get(){


AAA temp(3,4);

return temp;

}


int main(){


AAA a(1,2);

AAA &f = GetA();     ---> 이게 왜 실행이 가능한거지?


}



AAA& Get(){


AAA temp(3,4);     -----> 이것도 실행가능한데 쓰레기 값이 들어간다.

return temp;


}


-------------------------------------


AAA& ref_AAA(AAA& _a){


return _a;

}

---------->> 일반 변수와는 다르게 클래스는 메인 함수에서 AAA &ref = GetAAA(a); 로 호출해도 문제가 없다. 왜??

AAA GetAAA(AAA & _a){            ---------->> 예를 들어 int형이라면 문제가 생긴다. 상수를 리턴하기 때문. 클래스는 뭐가 다른거지?


return _a;


}

열혈 강의 10장

static복습하고 오기. 전역함수로 선언(friend 선언 이해하기)


왜 연산자 오버로딩인가

함수 오버로딩이란 이르이 같아도 전달 인자에 따라 호출되는 함수가 달라지는 것

int a = 1+2;             --> 정수형 변수

Point p3 = p1+p2;  --> 객체

--> 같은 +인데도 피연산자의 종류에 따라서 다른 +기능을 한다.


1. 객체를 리턴하는 함수

1. AAA& GetName(){ return a; }

2.AAA GetName() { return a; }

--> 차이가 뭐지


class Point {

private:

int x, y;


public:

Point(int _x=0, int _y=0) : x (_x), y(_y) P{}

void show();

Point& operator++();

friend Point& operato--r(Point & _p);

}


void Point::show() {

cout <<x<<"  "<<y<<endl;

}


Point& Point::operator++() {  --> 왜 레퍼런스로 리턴을 받는가? 

x++;

y++;

return *this;  --> this는 포인터 *가 붙었기 때문에 자기자신을 리턴

}


Point& operator--(Point& _p){  --> 왜 참조로 리턴을 하는가?

_p.x--;

_p.y--;

return _p;

}


int main(){

Point p(1,2);

++p;

p.show();   ///2,3


--p

p.show();    ///1,2


++(++p);    ///3,4

p.show();


--(--p);

p.show();    ///1,2

}

----------------------------------------------------------------------------------문제 제기


Point Point::operator++(){

x++;

y++;

return *this;

}


--> 참조로 리턴을 받지 않는다면?? --> 나 자신을 복사해서 리턴을 한다.  --> 리턴과 동시에 새로운 객체를 만들게 된다. (복사본)

--> ++(++p)  --> 밑줄 친 부분이 p자기 자신이 아니라 복사본이 오게 된다.

--> 그 결과 p.show()에는 2,3 즉, p는 ++연산을 한번만 하게 되고, 괄호 밖의 ++연산은 p의 복사본에 하게된다. 

--> 복사생성자 호출조건 case3 참고


--> 조로 리턴을 받는다면?? --> ++(++p) --> 밑줄 친 부분에 복사본이 아닌 p의 레퍼런스가 위치하게 된다.

--> 그 결과 p에 ++연산이 두번 실행된다.


--> 함수를 void로 선언하지않고 Point&로 선언하는 이유이다.


check!! 

--> p3    =  p1   +   p1;

      p3    =  리턴된 객체  ---> 복사생성자에 의해 멤버 대 멤버 복사가 일어난다.


2. 단항 연산자 오버로딩 전위 후위


++p --> p.operator++();

p++ --> p.operator++(int);   --> int는 단순히 구분하기 위한 용도


Point operator++(int){

Point temp(x,y);            --> 증가시키기 전의 값을 저장

x++;

y++;

return temp;

}                           --> 참조를 리턴하지 않는 이유 --> 지역객체이기 때문에 레퍼런스로 리턴할 수 없다.


int main(){

Point p(1,2);

(p++).show();    --> temp를 리턴받았기 때문에 1 2 출력

p.show();           --> 증가된 x, y

}

--> 참조가 불가능 하기 때문에 (p++)++ 는 불가능



3. 교환 법칙


Point p(1,2);

p+3; ---> 가능

3+p; ---> 불가능

가능하게 하려면?


class Point{

private:

int x,y;

public:

Point operator+(int val);

friend Point operator+(int val, Point& p);   ---> friend는 연산자 오버로딩 외에는 사용하지 말자.

}


Point::Point operator+(int val){

Point temp(x+val, y+val);

return temp;

}


Point operator(int val, Point& p){                        Point operator(int val, Point& p){

Point temp(p.x+val, p.y+val);        ---->          return p+val;                                   --> 바꾸어주는 기능으로도 구현할 수 있다.

return temp;                                                 }

}


전역함수로 선언해줌으로써 해결된다.


4. 임시 객체


Point p(1,2);

Point (1,2);   ---> 임시 객체 생성방법  --> 임시 객체는 생성된 후 다음 줄에서 바로 소멸된다.


임시 객체를 만드는 이유는 뭐냐?

-->임시 객체는 생성과 소멸을 최적화 시킨다. 임시 객체를 쓸 수 있는 부분에서는 써주는 것이 좋다.

교환법칙의 예에서 임시 객체를 사용할 수 있는 부분을 찾아보자.


Point Point::operator+(int val){

return Point(x+val, y+val);  ---> 소멸이 되기 전에 리턴!!

}


5. 대입 연산자 오버로딩


class Point{ ... };

int main(){

Point b(1,2);

Point a = b   --->  AAA a(b);  ---> 복사생성자에 의한 대입연산은 생성될 땐 가능하지만


Point a(1,2);

Point b(10,20);


a=b;   ----> a(b) ---> 객체 생성 문장이 아님 --> 복사생성자에 의한 대입 연산 불가능

but 디폴트 대입 연산자가 제공되기 때문에 가능하다.

}


------------------------------------------------------------------디폴트 대입연산자 ex


Point& operator=(const Point& p){

x=p.x;

y=p.y;

return *this;

}


대입 연산자의 문제점


class Person{

private:

char* name;

public:

Person(char* _name){

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

strcpy(name,_name);

}

Person (const Person& p){

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

strcpy(name,p.name);

}

~Person(){ delete[] name }

};


int main(){                                        스택           힙

Person a("ddd");                          a ---->  ddd

Person b("fff");                            b ---->  fff


a=b;   --------------->> 문제 발생

}


b가 가진값을 a에 복사해준다. a가 b가 가리키는 fff를 가리키게 된다. (복사생성자의 문제와 유사)

디폴트 대입연산자는 얕은 복사가 가지지 않는 또 다른 문제 하나를 더 가지고 있다.

--> 메모리 유출이 발생한다.

--> a가 원래 가리켰던 ddd가 힙에 계속 남아있게 된다.


--> 올바르게 동작하도록 바꿔보자!

1. a가 가리키던 영역을 해제시켜준다.

2. 메모리 할당을 다시해서 b와 같은 문자열을 할당해준다.


Person& operator=(const Person& p){

delete []name;                                              --> 메모리 해제

name = new char[strlen(p.name)+1];            --> 깊은 복사에 의한 할당

strcpy(name,p.name);


return *this;

}


Point!!

--> Point&을 통해 자기자신을 리턴하는 이유 --> p3 = p1 + p2; 와 같은 연산을 수행하기 위해

--> 생성자에서 동적할당을 하게 되면  1. delete

    2. 깊은 복사를 하는 복사생성자.

    3. 해제, 깊은 복사를 하는 대입 연산자.

'C++' 카테고리의 다른 글

[C++] 복사생성자 정리  (0) 2016.12.07
[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01

열혈 강의 9장


함수 포인터 공부하자.


oop 프로젝트 6단계

--> AAA 클래스 내의 함수 virtual void show() { ... }

BBB 클래스 내의 함수 virtual void show() { 


AAA::show();

....

....


}


---> 유용하게 쓸 일이 많으니 꼭 기억할 것!!



1. virtual 소멸자의 필요성


/////////////////////////////////////////// Base 클래스 AAA 선언


class AAA{


private:

char* name1;


public:

AAA(char* _name1) {


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

strcpy(name, _name);

}

~AAA(){

delete []name1;

}

}


////////////////////////////////////////// AAA를 상속하는 Derived 클래스 BBB


class BBB : public AAA{


private:

char* name2;


public:

BBB(char* _name1, char* _name2) : AAA(_name){


name2 = new char(strlen(_name2)+1];

strcpy(name2, _name2);


}

~BBB(){

delete []name1;

}

}


////////////////////////////////////////////// 메인 함수


int main(){


AAA* a = new BBB("aaa", "bbb");  ///// 1번

BBB* b = new BBB("aaa","bbb");  ///// 2번


}



2번의 경우 :


소멸의 주체가 되는 포인터가 BBB 객체 포인터이다.

BBB의 소멸자 호출 후 AAA의 소멸자도 호출

--> 메모리 반환이 정상적으로 일어난다.


1번의 경우 :


소멸의 주체가 되는 포인터가 AAA 객체 포인터이다.

AAA의 소멸자만 호출하고 BBB의 소멸자는 호출하지 않는다.

--> 메모리 유출이 일어난다.


--> 가리키는 객체가 무엇이든 AAA의 포인터이기 때문에 AAA의 객체로 바라본다.



////////////////////////////////////////////////////////////// 해결책


---> 해결책 : virtual 소멸자를 이용한다!!


virtual ~AAA(){

delete[] name1;

}


소멸자를 호출하려 들어갔는데, 가상함수로 선언 되어있다,

때문에 AAA를 상속하는 BBB의 소멸자를 호출한다.

그리고 또 다시 AAA의 소멸자를 호출하게 된다.

--> 특성 그 자체로 이해하기!!



2. virtual table


멤버함수는 객체 내에 존재하지 않는다.

코드영역이란?


객체를 여러개 만들 경우 여러개의 멤버함수 또한 만들어지게 된다. --> 비효율적이다.


때문에 코드영역에 멤버함수를 단 하나만 만들어 놓고

객체들이 공유하며 사용한다


////////////////////////////////////////////////// ex


class AAA{


int n;


public:

void Add(){

n++;


}

}


Add() 함수가 코드 영역에 들어가게 된다.


단,

--> Add(A* a){

(a->n)++;

}


이러한 형태로 변형되어 저장되며, 호출할 경우 Add의 인자로 객체의 포인터가 전달되어진다.


////////////////////////////////////////////////virtual table


virtual table은 key 값과 value 값이 있다.

key값은 어떠한 함수인지 구분해주는 역할을 하고, value값은 그 함수의 주소값을 지닌다.



객체 내에서 함수가 단 하나라도 virtual로 선언될 경우 (1개 이상)

그 클래스 내의 모든 함수(virtual이 아니라도)의 정보를 가지는 virtual table이 만들어진다.


때문에 함수가 호출 될 때에는 virtual table을 거쳐서 코드영역에 있는 함수를 찾게 된다.


virtual을 선언하지 않을 경우 바로 코드영역의 함수를 찾았지만 virtual table을 거쳐서 함수호출을 하게 된다.(virtual이든 아니든) ---> 단점.

따라서 메모리 또한 추가된다. (4byte)


AAA클래스의 멤버함수 virtual void fct()

virtual void show();


AAA를 상속하는 BBB클래스의 멤버함수 virtual void fct()

void fct2();


BBB클래스에서 virtual이 선언 되었기 때문에 virtual  table이 만들어지게 된다.

그 테이블에는 fct(), fct2() 그리고 AAA를 상속했기 때문에 show()의 정보 또한 저장된다.


AAA클래스의 virtual table에는 fct()와 show()의 정보


AAA의 fct() BBB의 fct()는 virtual table에 저장된 주소값이 다르다 --> 오버라이딩 되었지만 다른 함수이기 때문


--> 때문에 fct()는 AAA의 virtual table에도 BBB의 virtual table에도 저장되게 된다.


다만 BBB의 virtual table에는 재정의 되기전의 fct()의 정보가 전혀 없다. --> 가상함수의 원리


AAA* a = new BBB(); 의 가상함수 원리

'C++' 카테고리의 다른 글

[C++] 레퍼런스 정리  (0) 2016.12.07
[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30

열혈강의 8장


객체 리스트를 만들 때

포인터를 사용하는 이유

: 미리 생성되어져 버림.


포인터 배열로 선언할 경우 할당될 시에 객체가 생성됨.


단순 오버라이딩 했을 때  a b c 선언에 따라 함수가 어떻게 나오나

버추얼로 했을 때 버추얼된 함수를 나타내는방법



1. 객체 포인터의 권한


class Character(){


public:

void fct1();


}


class Gunner() : public Character{


public:

void fct2();


}


class Sniper() : public Gunner{


public:

void fct3();


}


Base 클래스의 객체 포인터는 자기 자신의 객체 뿐만 아니라

Derived 클래스의 주소값을 가질 수도 있다.


IS - A 관계에 의한 이해

-->  스나이퍼와 건너는 캐릭터이다. 라고 말할 수 있다. 때문에 

Base클래스인 Character의 포인터는 건너와 스나이퍼를 포함 할 수 있다.


--> 반대로 캐릭터는 스나이퍼이다.

캐릭터는 건너이다. 라고 말할 수 없다.

게임에서 존재하는 캐릭터가 모두 스나이퍼일리는 없다.


따라서 스나이퍼 클래스와 건너 클래스의 객체는 캐릭터 클래스의 객체이다. 라고 말할 수 있다.


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

///// Base 클래스의 객체 포인터는 하위 클래스의 주소값을 모두 저장할 수 있기 때문에 문제없는 선언이 된다.


4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();


6. Sniper* c = new Sniper();



1. Character 객체 타입으로 선언된 포인터가 Character 객체를 가르키고 있다.

--> 따라서 Character 객체 내의 fct1(); 함수만 호출가능하다.


2. 이번에는 Gunner객체를 가르키고 있다.

--> Gunner객체의 fct2() 함수의 호출이 가능할 것 같지만, Character 객체의 멤버 함수 fct1()만 호출 된다.


왜?? Character 객체 포인터는 가르키는 대상이 무엇이건 Character 객체 내의 멤버와 Character가 상속한 멤버에만 접근이 가능하다.


3. 위와 똑같은 이유 때문에 Character 객체의 멤버 함수 fct1()만 호출된다.


4. 이번 같은 경우는 Gunner객체의 포인터가 Gunner의 객체를 가르키고 있다. 

Character에 의해 상속되어지고 있기 때문에 Character의 fct1()와 Gunner의 fct2() 에 모두 접근이 가능하다.


5. 이번 경우에도 Gunner 객체의 포인터로 선언이 되었기 때문에

Gunner이 가지고 있는 fct2()와 상위 클래스 fct1()의 호출이 가능하다.


6. Sniper객체 포인터가 자기 자신을 가르키고 있다.

이 경우 Sniper 클래스는 Character 과 Gunner에 모두 상속되어지고 있기 때문에

fct1(), fct2(), fct3() 모두 접근 가능하다.




2. 함수 오버라이딩


class Character(){


public:

void fct();


}


class Gunner() : public Character{


public:

void fct();


}


class Sniper() : public Gunner{


public:

void fct();


}


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();

6. Sniper* c = new Sniper();


c -> fct();


1. Character 객체 내의 fct()가 호출된다. (당연한 말)

2. Character 객체 내의 fct()가 호출된다.

3. 마찬가지

4. Gunner의 멤버함수 fct()가 호출된다.

왜?? --> 이름이 같기 때문에 오버라이딩에 의해 Character의 fct()가 은닉되어 Gunner 객체의 fct()가 호출된다.


5. Gunner의 멤버함수 fct()가 호출

6. Sniper의 멤버함수 fct() 호출

--> 마찬가지로 함수 오버라이딩에 의해 Character, Gunner 의 fct()가 은닉되어 Sniper의 fct()가 호출 된다.




3. virtual


class Character(){


public:

virtual void fct();


}


class Gunner() : public Character{


public:

virtual void fct();


}


class Sniper() : public Gunner{


public:

virtual void fct();


}


1 . Character* c = new Character();

2. Character* c = new Gunner();

3. Character* c = new Sniper();

4. Gunner* c = new Gunner();

5. Gunner* c = new Sniper();

6. Sniper* c = new Sniper();


c -> fct();



1. Character의 fct();

2. Gunner의 fct();

--> Character의 객체 포인터임에도 불구하고 Gunner의 멤버함수가 호출된다. 왜??

가상함수로 선언되었기 때문에 어떠한 객체를 가르키던 오버라이딩된 함수만 호출되어지게 된다.

말그대로 '가상'함수이기 때문에


3. Sniper의 fct();   --->  fct는 가상함수이기 때문에 오버라이딩된 Sniper의 fct()가 호출된다.

4. Gunner의 fct();

5. sniper의 fct();

6. sniper의 fct();



--> 함수오버라이딩의 은닉의 기능과 더불어 virtual을 사용할 경우 재정의의 기능까지 할 수있다.


'C++' 카테고리의 다른 글

[C++] 연산자 오버로딩 // 열혈강의  (0) 2016.12.07
[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초2 // 열혈강의  (0) 2016.11.30

열혈강의 5장


복사생성자 정리해야함 꼭!!


class test{

private:


char *name;


public:


test(char* _name){


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

strcpy(name, _name);


}


}


char* 에 대한 이해


const


일반 함수

const 함수


우선 순위는 일반 함수가 더 높다.


=====================================


const 정리


1. const 멤버변수를 정의할 때에는 초기화 리스트를 사용해야 한다.


class aaa{

private:

const int x;


public:


aaa(int _a) : x(_a){};


}


--->> const로 정의 되었기 때문에 초기 생성자가 호출 될 때에는 이미 변수에 쓰레기 값이 들어가있어 재정의가 불가능!


2. const 멤버 함수


1. 멤버함수가 상수화되면 이 함수를 통해 어떠한 멤버변수의 변경이 불가능해진다.

2. 상수화된 멤버함수 내에서는 const함수를 제외한 모든 함수를 부를 수 없다.

3. 상수화된 멤버함수는 멤버변수의 포인터를 리턴받을 수 없다.



3. const 객체


상수화된 객체는 어떠한 멤버변수의 조작도 불가하며, 오직 상수화된 멤버 함수만 불러올 수 있다.

(일반 객체에서 상수화된 멤버함수를 불러오는 것은 가능하다.)


-->> const에 의한 함수 오버로딩이 가능하다.



=============================================


explicit 키워드를 자주 사용하는 습관도 필요할 수 있다.


'C++' 카테고리의 다른 글

[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초2 // 열혈강의  (0) 2016.11.30
[C++] 기초1 // 열혈강의  (0) 2016.11.28

열혈강의 3장


C++ 문자열에 대해서

char *a --> 함수 넘겨줄 때

void show(char *_a);


or


char a[20];

void show(char* _a);


이 부분 좀더 공부하고

char* 로 문자열을 받을 수 있는 이유

C++ string에 대해 공부하기



생성자 : 가급적 초기화 용도로 사용하기




생성자를 private로 선언하면 오류가 난다.

하지만 private로 선언하는 경우도 있다.


매개변수가 있는 생성자를 정의했을 경우

기본 생성자를 호출하려면 따로 정의를 해주어야 한다.


생성자에서 동적할당을 했을 때

소멸자에서 delete를 통해 효율적으로 해제 할 수 있다.


객체 배열에도 생성자를 이용한 초기화가 가능한가?

포인터는 스택에 있고 포인터가 가르키는 부분이 힙에 저장된거?

동적할당을 할때 a = new char[strlen(_a)+1] 해주는 이유


malloc 는 단순히 메모리를 할당해주는 것

new를 써야 생성자 호출이 이루어진다. - 162p

객체의 조건을 만족시키려면 생성자의 호출은 반드시 이루어져야 한다.



객체 포인터가 가르키는 객체의 멤버 변수, 함수 참조 ->


this는 자기 자신을 가르키는 포인터이다.


어떠한 변수이건 지역변수보다 우선일 수 없다.



char* a;

char a[20];


cin >> a;

왜 밑에꺼만 되지?


객체의 포인터 배열 선언 방법으로 하면

new를 써서 할당해주기까지 메모리를 안잡아먹나?

'C++' 카테고리의 다른 글

[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초2 // 열혈강의  (0) 2016.11.30
[C++] 기초1 // 열혈강의  (0) 2016.11.28

열혈강의 2장


oop 프로젝트1


배열을 함수로 넘겨줄 때.


int array[100]; --> 함수 선언


void arr(int *_arr){ ... }


배열의 이름은 배열의 첫주소를 나타낸다.

때문에 함수에 넘겨줄 때 & 주소값표시가 없어도 된다.


단, 배열 전체가 아닌 특정 값을 넘겨줄 때에는


arr(&array[2]); --> 주소값을 넘겨줘야한다.

arr(array) --> 배열의 첫 주소값이 넘어감.


arr(int *arr){


arr[i] = 3;

이런 형식으로 값을 바꿔줄 수 있다.


}



구조체 배열을 함수로 넘겨줄 때도 마찬가지.


typedef struct sss{


char name;

int num;


} SSS;


SSS aaa[100]; --> 구조체 배열 선언


void ss(SSS *_ss){


_ss->name = "ddd";


}


--> ss(&aaa[3]);

마찬가지로 배열의 한 부분을 넘겨줄 떄에는 주소값

그리고 변수 내에서는 -> 를 사용해서 접근한다.


ss(SSS *_ss){


_ss.name = "ddd";


}


ss(aaa);


배열을 통째로 넘겨줄 땐 &사용하지 않음


근데 왜 함수 안에서 ->를 사용하지 않는거지?

또 함수 내에서 배열에 *를 붙이지 않는 이유는?


-->??



========================================================================================



포인터 정리


typedef struct num{


int x;

int y;


}NUM;


int i[10] = {1,2,3,4,5,6,7,8,9,10};

NUM n[10]; --> 초기화가 되었다고 가정.


void Show(int * _val){}

void n_Show(NUM* _val){}



1 . Show(i);

2. Show(&i[2]);

3. n_Show(n);

4. n_Show(&n[2]);


1. 배열의 이름은 배열의 첫 인자 주소값을 가진다. 때문에 &키워드가 필요가 없다.

   다만, 함수 내에서 배열을 참조할 때

   _val 만 쓰면 --> 주소값

   *_val 만 쓰면 --> 첫번째 값

   배열의 모든 인자에 접근할 때

   _val[x]  or  *_val+x


2. 배열 전체가 아니라 하나의 인자만을 전달 할 때에는 주소값을 넣어줘야 한다. (하나의 값이기 때문에)

    함수 내에서 배열을 참조 할 때

    _val --> 주소값

   *_val --> 값


3. 구조체 또한 배열과 마찬가지


4. 주소값을 넘겨 받았기 때문에

    함수에서 구조체의 멤버변수를 참조할 때

    _val->x;

    -val ----> 주소값



=================================================================================



1. const int n = 10;

2. const int* n;

3. int* const n;

4. const int* const n;


어떤 의미를 지니는가?


const : 변수를 상수화


2 --> 데이터 상수화 n이라는 포인터가 가르키는 값을 상수화

(포인터를 상수화하는게 아님)

포인터를 이용해서 값을바꾸는것을 방지

포인터가 가르키는 변수는 상수가 아님!


3 --> 포인터 상수화

가르키는 변수를 바꿀 수 없음

주소값이 상수화가 되서 하나의 변수만 가르킬 수 있다.


4 --> 데이터도 상수화 포인터도 상수화


포인터를 이용해서 값을 바꿀 수 없음

또한 하나의 변수만 가르킬 수 있다.




프로그램이 실행되면 운영체제가 메모리공간을 할당 해줌.


데이터 영역 --> 스태틱, 전역

프로그램의 실행과 동시에 생성 프로그램이 종료될 때 소멸


힙 --> 런타임 크기 결정

실행시에 동적으로 할당 됨.

프로그래머가 할당하고 해제함.


스택 --> 컴파일 타임 크기 결정

지역변수, 매개변수


스태틱에 대한 이해, malloc, free




int i;

cin >> i;

int a[i];

이거 실행 되나??


--> 실행 안됨!! --> int i = 7;  int *a;     a= new int[i]; --> 동적할당 해주어야 한다.



특정메모리 공간에 이름을 붙여주기 위한것 --> 레퍼런스

변수에 이름을 하나 더 붙여주기 위한 용도 --> 메모리 할당이 따로 일어나나?

call by reference 좀 더 확인


-->레퍼런스는 단순히 이름만 붙여주는 것!! 한 메모리 공간에 이름만 붙여주기 때문에 메모리할당이 따로 일어나지 않는다 -->>효율적!!


씨언어는 로우레벨 랭귀지와 하이레벨 랭귀지의 특성을 동시에 지닌다.

로우레벨의 대표적인 예 : 포인터


레퍼런스


레퍼런스와 변수는 완전히 같은 것이라 말할 수 있다.

변수로 할 수 없는 일은 레퍼런스로도 할 수 없고

레퍼런스로 하지 못하는 일 또한 변수로도 할 수 없기 때문이다.


레퍼런스를 이용한 콜바이레퍼런스는

값(메모리)의 복사가 없기 때문에 기능이 향상된다.


void showData(const person &p) {



} -->혹시모를 변경에 대한 위험 --> const 키워드



new and delete

malloc 와 free의 기능

malloc는 형변환과 함께 사이즈 계산을 해주어야 한다.

malloc는 주소값을 void로 리턴하기 때문에 형변환

주소값을 리턴한다는 점!


int * val = (int*)malloc(sizeof(int));

int * arr = (int*)malloc(sizeof(int)* size);


free(val);

free(arr);


---> new로 바꾼다면


int * val = new int;

int *arr = new int[size];


delete val;

delete []arr;


new 연산자를 사용했을 때 제대로 되었다면

변수에 주소값이 들어갈 것이고

아니라면 NULL을 리턴한다.


new는 객체의 동적생성도 가능하다.


if(arr==NULL){

cout<<"메모리 할당 실패"<<endl;

}


#if #endif 를 사용해서 좀 더 세련된 예외처리를 해보자.


'C++' 카테고리의 다른 글

[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초1 // 열혈강의  (0) 2016.11.28

열혈강의 1장



1. 함수 오버로딩


오버로딩의 조건 1 : 매개변수의 타입


오버로딩의 조건 2 : 매개변수의 개수


오버로딩의 조건 3 : const냐 아니냐에 따라


void fct(int n) const {}

void fct(int n){}


함수의 반환 타입으로는 함수 오버로딩이 되지 않는다.



2. in-line 함수 

함수를 매크로로 정의하면 전처리기에 의해서 함수 호출 문장이

함수의 몸체 부분으로 완전히 대치돼 버린다. --> 함수 호출과정이 사라짐 --> 치환의 개념

(함수 호출은 스택 메모리 공간의 할당과 많은 시간이 요구된다.)

매크로 함수는 자료형의 선언이 필요없다.


-> C++에서는 inline 키워드로 인라인화 할 수 있다.

-> 매크로 함수의 단점 : 함수구현이 까다롭다. 재귀함수 구현 불가

디버깅이 어렵다. 함수의 크기가 크면 프로그램의 크기도 커진다. 왜?


--> 치환의 개념이기 때문에 함수가 호출되는 것이 아니라

코드가 삽입 되는 것! 때문에 사용할 때마다 코드의 크기가 증가



3. namespace

::val -> 전역변수에 접근하라.


int val;


int main(){

int val;


val = 1; ->> 지역변수에 접근

::val = 1; ->> 전역변수에 접근

}



전처리기 란?


컴파일 하기 전 일련의 작업지시 명령으로 컴파일 되기 전의 준비작업을 수행

'C++' 카테고리의 다른 글

[C++] virtual 소멸자, virtual table // 열혈강의  (0) 2016.12.05
[C++] 상속에 대하여 // 열혈강의  (0) 2016.12.01
[C++] 기초4 // 열혈강의  (0) 2016.12.01
[C++] 기초3 // 열혈강의  (0) 2016.11.30
[C++] 기초2 // 열혈강의  (0) 2016.11.30

+ Recent posts