※ C++의 입출력 연산자, [>>, <<]
C++의 입출력은 C언어처럼 printf();를 사용해도 무방하다. 다만 입출력에 대해 istream& operator >>(istream&, Coord&);와 ostream& operator<<(ostream&, const Coord&); 처럼 이미 C++의 #include<iostream>에 연산자 오버로딩이 되어 있기 때문에 std::cout << 출력하고 싶은 내용or변수 << std::endl;으로 printf(%d)와 같이 변수의 data성격에 따른 출력 타입을 고려해줄 필요가 없어졌다.
C와 C++의 달라진 점은 여러가지가 있는데 bool이라는 자료형 추가, 함수 및 연산자 오버로딩, 클래스, inline, Call-by-Reference 등이 있다.
※ C가 아닌 C++ 사용시의 장점
- classes
- inheritance (derived classes)
- strong typing
- inlining
- default arguments
※ C++은 언어들의 연합체로 바라봐야 한다.
C++은 여러 개의 하위 언어를 제공하며 C++의 어느 부분을 사용하느냐에 따라 programming이 달라진다.
1. C언어
2. 객체지향 개념의 C++ : class, encapsulation, inheritance, polymorphism, virtual function(Dynamic binding)
3. template C++ : 주류 C++과는 다르며 Effective C++의 item 48에서 다루고 있다.
4. STL : container, iterator, algorithm, 함수객체 등 template library를 중심으로 다룬다.
※ l-value와 r-value
l-value: 특정 메모리 위치를 가리키며 변수로 존재한다. (즉, 주소값을 취할 수 있는 연산자)
r-value: 일시적이고 주로 literal 상수이다. (주소값을 취할 수 없는 연산자)
int setValue(){ return 6; }
setValue() = 4; // Err
setValue함수는 rvalue인 숫자 6을 반환한다.
따라서 대입연산자의 왼쪽 피연산자로 사용할 수 없기에 오류가 발생한다.
int global = 100;
int& setValue(){ return global; }
setValue() = 4; // ok
여기서 setValue함수는 참조를 반환한다. 참조는 global 변수의 메모리 장소를 가리키기 때문에 lvalue로 할당할 수 있다.
lvalue에서 rvalue로 변환은 가능하다.
int x = 1; // l-value
int y = 2; // l-value
int z = x+y; // l-value -> r-vlaue
하지만 rvalue에서 lvalue로의 변환은 불가능하다.
※ 함수 오버로딩
이때, 함수 오버로딩은 C언어와 다르게 함수의 매개변수 선언형태가 다르면 동일한 이름의 함수정의 허용이 가능해 졌다.
예를 들어 매개변수가 다른것 뿐만 아니라 const선언만으로도
int Func(int num) { return this->num; }
int Func(int num)const { return this->num; }
두 함수의 이름은 같지만 단지 const를 붙였을 뿐인데 오류가 발생하지 않는다.
※ 함수와 Default 값
int Func (int num = 7) {…}
int main(){
Func() // 7이 전달 될 것으로 간주
}
함수전달인자가 왼쪽부터 채워지기에 함수 작성시에는 오른쪽부터 Default값을 채워야 한다.
※ inline 함수: C의 #define 즉, 매크로 함수와 동일한 기능을 한다.
[차이점]
- #define: preprocessor로 compile전에 실행되며 값의 치환을 주목적으로 전처리기에 의해 실행
- inline: 전처리가 아닌 compiler에 의한 처리과정으로 Effective C++에 의하면 주로 inline함수의 애용을 추천한다.
#define square(x) ((x)*(x))
inline int square(int x){
return x*x;
}
위의 예시에서 두 내용은 서로 같은 의미를 내포하며 출력 시 5의 제곱은 sqaure(5)로 출력할 수 있다는 매력이 있다.
매크로 함수도 자료형 정의가 필요없다는 장점이 존재하지만 아래와 같은 치명적인 단점이 존재한다.
#define CALL_MAX(a,b) f((a) > (b) ? (a) : (b))
...
int x = 5, y = 0;
CALL_MAX(++x, y); // a가 2번 증가
CALL_MAX(++x, y+10); // a가 1번 증가
위의 경우, (++a>b) ? ++a : b 로 해석되기에 a가 2번 증가하게 되어 버리는 것이다.
따라서 다음과 같이 매크로의 효율은 그대로 유지하면서 타입안전성까지 취하는 inline함수에 대한 template을 사용한다.
template<typename T>
inline void callMax (const T& x, const T& y){
f (x > y ? x : y);
}
따라서 Effective C++에서는 #define을 쓰려거든 const, enum, inline을 떠올리라는 내용을 item2에서 볼 수 있다.
※ [Effective C++_item 2]: #define을 쓰려면 const, enum, inline을 떠올리자 (전처리기보다 컴파일러를 가까이 하자.)
매크로는 일단 정의되면 끝날 때 까지 유효하며 컴파일러로 넘어가기 전, 전처리기가 밀어버린 후 상수로 바뀌어 버린다.
따라서 매크로는 클래스 상수 정의에도 쓸 수 없을 뿐만 아니라 캡슐화 혜택도 받지 못한다.
이에 대해 매크로 대신 상수를 쓰는 총 3가지의 해결책을 제시한다.
1. const 사용
#define ASPECT_RATIO 1.653 를 아래와 같이 상수로 사용하는 것이다.
const double AspectRatio = 1.653; 이는 위와 달리 사본이 사용때 마다 생기지 않고 딱 1개만 생긴다.
주의할 점1) 포인터와 포인터가 가리키는 대상까지 const로 선언
const char* const name = "V2LLAIN";
주의할 점2) 클래스 멤버로 상수 정의 시, 상수의 사본개수를 1개 이하로 하고 싶을 때.
class Game {
private:
static const int Num = 5; //static 멤버로 만들어 상수로 선언한다.
};
2. enum hack을 이용한다. (const 보다는 #define에 가까운 방식, 쓸데없는 메모리 할당X)
class CostEstimate{
private:
enum { Num = 5 };
};
3. inline template (Effective C++_item 30)을 이용한다.
template<typename T>
inline void callMax (const T& a, const T& b) { f (a > b ? a : b); }
※ namespace:
범위지정연산자(::)를 이용해 namespace지정 시 사용하며 namespace는 변수명이 같아도 자신만의 namespace에 선언하면 충돌하지 않는다. 따라서 namespace를 중첩해서도 쓸 수 있다. (cf. ::를 변수앞에 붙이면 global variable을 의미.) 또한, std라는 namespace에 대해 using namespace std;라 선언해 std::cout을 cout이라 사용가능하다.
#include <iostream>
using namespace std;
namespace Parent{
int num = 0;
namespace Child{
int num1 = 1;
namespace GrandChild{
int num2 = 2;
}
}
}
int main(){
cout << Parent::num << endl;
cout << Parent::Child::num1 << endl;
namespace G = Parent::Child::GrandChild;
cout << G::num2 << endl;
}
※ reference variables (참조변수)
참조형 변수는 Ampersand 기호, &를 사용하여 나타내는데 우리는 이런 기호를 C언어에서 사용해본 적이 있다.
바로 포인터와 관련된 내용을 배울 때 사용한 적이 있는데, 아래와 같다.
int num = 3;
int *ptr = #
cout << ptr; // ptr변수로 num의 주소값인 0xc3dcbffb64 출력
cout << *ptr; // ptr변수로 num의 값인 3 출력
이때 사용된 &는 num의 주소값을 받아온다는 의미로 사용된 &연산자이다.
그렇다면 참조변수의 기호 &는 무엇일까?
자신이 참조하는 변수를 대신할 수 있는 일종의 별칭으로 참조자 수에는 제한이 없다.
★배열"요소"도 변수로 간주되어 참조자 사용이 가능하다.
int arr[10];
int &ref1 = arr[1];
※배열 전체를 참조값으로 함수에 전달하기
void func (const int (&arr)[3]){
for (int i = 0; i < 3; i++) {
cout << arr[i];
}
}
유의 사항: 괄호() 안에 &와 참조배열이름을 적어야 하며 [ ]안에는 반드시 배열 길이를 명시해야 한다.
※ 참조변수와 const
const변수를 참조하기위해서는 참조변수를 반드시 const로 선언해야 한다.
함수의 참조매개변수를 const로 선언하면 변수의 값을 변경할 수 없고 상수 또한 인자로 전달가능하다.
const int &ref = 30; //30이라는 const
※ Call - by - reference
참조변수로 같은 메모리 공간을 접근하면서 다른 변수의 이름으로 참조가능한 방식이다.
이로 인해 함수 밖의 메모리공간에서도 접근 및 변경이 가능하다.
변수를 매개변수로 하면 함수 호출 때 마다 메모리 할당이 발생한다.
참조변수를 매개변수로 하면 함수 호출을 해도 메모리 할당이 발생하지 않는다.
이때, 반환값은 참조형태여서 반드시 참조변수로 반환받아야 한다.
※ new와 delete
C언어의 malloc과 free를 대신해서 C++에서는 new와 delete를 사용한다.
객체 생성에서는 new를, 소멸에서는 delete를 사용하여 Heap segment에 할당, 반환을 하며
new를 이용해 heap에 할당된 변수에 대해 참조자 선언이 가능하다.
이와 관련된 설명은 뒤에 객체 생성 및 소멸과 관련하여 설명하겠다.
※ Prologue _ 이름짓기(Effective C++)
포인터의 이름을 지을 때, T에 대한 포인터 (pointer to T)를 줄여서 pt라고 하는 경우가 많다.
참조자에서도 비슷하게 T에 대한 참조자 (referenece to T)를 줄여서 rf라 사용할 수 있다.
gamecharcter *pgc;
gamecharcter& rgc;
'C | C++ > C++' 카테고리의 다른 글
★★this.code(5)_ 상속, 가상함수(동적바인딩 / 오버라이딩 / 추상클래스), 다형성, 다중상속 (0) | 2022.10.25 |
---|---|
★this.code(4)_ 복사생성자, 이동생성자, perfect forwarding, 임시객체, explicit, friend, static, mutable (0) | 2022.10.25 |
this.code(3)_ class, 객체생성과 소멸, *this (0) | 2022.10.25 |
this.code(2)_ const, constexpr (0) | 2022.10.25 |
this.code(0)_ How to Study C++ (2) | 2022.10.21 |