The C++ Programming Language - 추가 (1)

an_yan_yang·2025년 3월 2일

본 글을 개인적 학습을 위한 글입니다. 틀린 내용이 있을 시 마구 지적해주시면 감사합니다.

  이번 글은 C++의 한 층 더 심화적인 내용을 다룹니다. 앞의 글들에서 다루었던 내용을 다시 살펴보는 시간도 가집니다. 그렇기에 오늘은 자세한 설명을 하기 보다는 코드와 주석을 통해 글을 진행해보도록 하겠습니다.


Inline 함수

컴파일 시 그 주소를 지워버리고 호출한 위치에 함수가 복사, 붙여넣기 된다.

특징 : 실행 파일이 길어지지만, 속도가 빨라진다. (함수를 호출하는 시간이 사라짐)

일반적인 함수의 작동 원리

함수를 컴파일하면 그 내용을 호출해서 실행 한다. (함수의 원본만 존재하고, 이를 대신할 주소가 존재한다.)

// 인라인 덧셈 함수
inline int add(int a, int b) {
    return a + b;
}

int main() {
    int x = 5, y = 3;
    std::cout << "Sum: " << add(x, y) << std::endl;
    return 0;
}


연산자 중복

  지난 글에서 언급되었던, 연산자 중복(Operator Overloading)에 대해 각각의 케이스를 확인하면서 조금 더 자세히 알아보는 시간을 가져보겠습니다.

원칙

  • C++에 본래 있는 연산자만 중복 가능
  • 피연산자 타입이 다른 새로운 연산을 정의함
  • 연산자는 함수 형태로 구현텍스트 → Operator Function
  • 반드시 클래스와 관계를 가짐 (클래스 ≅ 피연산자 타입)
  • 피연산자의 개수를 바꿀 수는 없음
  • 연산의 우선 순위 변경 불가
  • 모든 연산자가 중복 가능하지는 않음 (’.’, ‘*’, ‘::’, ‘?’, ‘:’)

선언

보통 선언은 크게 두 가지로 나뉘어집니다.

  • 외부 함수(전역)로 구현되고, 클래스에 friend 키워드로 선언
  • 클래스의 멤버 함수로 작성되는 경우

하지만 가능하면 멤버 함수로 작성하는 것이 좋다. (캡슐화의 원칙)



Binary Operator Overloading

먼저 2항 연산자의 오버라이딩부터 세세하게 보겠습니다.

+ 연산자

c = a + b;
// 아래는 컴파일러에 의한 변환
c = a.+(b);
// 즉 피연산자 b가 인자로써 전달된다.
  • 연산자 오버라이딩의 예시
Power Power::operator+(Power op2){
	Power tmp;
	tmp.kick = this->kick + op2.kick;
	tmp.punch = this->punch + op2.punch;
	return tmp;
}

예외 사항

b = 2 + a;
// 아래는 컴파일러가 변환한 형태
b = 2.+(a); // 하지만 이 형태는 불가
b = +(2, a); // 이 형태는 가능
// 즉, 2와 a를 각각 인자로 전달한다. (이 경우에, 전역 함수를 이용한다.)
Power operator+(int op1, Power op2){
	Power tmp;
	tmp.kick = op1 + op2.kick;
	tmp.punch = op1 + op2.punch;
	return tmp;
}
// 이후 이 function을 friend keyword로 작성한다.
// 이렇게 전역 함수를 사용하면, 예외뿐만 아니라 모든 경우에 사용할 수 있다.

== 연산자

a == b;
// 아래는 컴파일러에 의한 변환
a.==(b); // 피연산자 b가 인자로써 전달된다.
bool Power::operator==(Power op2){
	if(kick == op2.kick && punch == op2.punch){
		return true;
	}
	return false;
}

+= 연산자

c = a += b // 물론 보통 이 형태를 사용하지는 않는다.
// 아래는 컴파일러에 의한 변환
c = a.+=(b);
Power Power::operator+=(Power op2){
	kick = kick + op2.kick;
	punch = punch + op2.punch;
	return *this; // 자기 자신을 다시 내보낸다.
}


Unary Operator Overloading

  단항 연산자의 종류는 두 가지로 분류됩니다. 또한 연산자 중복 방식은 이항 연산자의 경우와 거의 유사합니다.

  • Prefix Operator : 전위 연산자
  • Postfix Operator : 후위 연산자

++ 전위 연산자

Power Power::operator++(){
	// kick과 punch는 a의 멤버
	kick++;
	punch++;
	return *this; // 변경된 객체 자신(객체 a) 리턴
}

Friend로 작성하기

++a;
// 아래는 컴파일러에 의한 변환
++(a);
Power operator++(Power& op){
	op.kick++;
	op.punch++;
	return op;
}

++ 후위 연산자

Power Power::operator++(int x){
	Power tmp = *this; // 증가 이전 객체 상태 저장
	kick++;
	punch++;
	return tmp; // 증가 이전의 객체(객체 a) 리턴
}

Friend로 작성하기

a++;
// 아래는 컴파일러에 의한 변환
++(a, 0); // '0'은 의미 없는 값으로 전위 연산자와 구분하기 위해 사용함
Power operator++(Power& op, int x){
	Power tmp = op;
	op.kick;
	op.punch++;
	return tmp;
}

! 연산자

!a;
// 아래는 컴파일러에 의한 변환
a.!(); // 매개 변수는 없다.
bool Power::operator!(){
	if(kick == 0 && punch == 0) return true;
	return false;
}

[] 연산자

보통 배열에서 사용되는 인덱스 연산자입니다.

T & operator[](int idx){
	return m_data[idx];
}
// reference를 return하기 때문에 R/W 모두 가능한 형태


추가

대입 연산자와 복사 생성자

  이미 이전의 글로 살펴 보았던 대입 연산자와 복사 생성자를 코드로써 다시 한 번 더 살펴보도록 하겠습니다. 아주 중요한 것이기에 꼭 이해해야 합니다. 먼저, Assignment와 Copy Constructor를 구분해야 합니다.

  • 만일 새로운 객체가 복사되기 전에 생성되면 → Copy Constructor
  • 만일 새로운 객체가 복사되기 전에 생성되지 않으면 → Assignment Operator
// 아래는 Assignment Operator의 코드입니다.

MyString& MyString::operator=(const MyString & str){
	// self-assignment check
	if(this == &str)
		return *this;
	
	// if data exists in the current string, delete it
	if(m_data) delete [] m_data;
	m_lenght = str.m_length;
	
	// copy the data from str to the implicit object
	m_data = new char[str.m_length];
	
	for(int i = 0; i < str.m_lenght; i++){
		m_data[i] = str.m_data[i];
	}
	
	// return the existing object so we can chain this operator
	return *this;
}


  C++이라는 프로그래밍 언어에 대해 굵직한 것들만 빠르게 다루다보니, 어느새 이 시리즈의 끝이 다가오는 것 같습니다. 다음 글에서는 함수의 바인딩, 가상 함수, 템플릿 등 조금 더 실전적인 부분들에 대해 다루어보도록 하겠습니다. 긴 글 읽어 주셔서 감사합니다.


참고 자료

profile
개발자가 되고 싶은 공대생

0개의 댓글