[Advanced C++] 3. Function, Local Variable, 임시객체

dev.kelvin·2024년 11월 29일
1

Advanced C++

목록 보기
3/74
post-thumbnail

1. Function

함수란

함수란 순차적으로 실행되는 명령문들의 집합이다, 조금 더 자세히 설명하면 함수는 특정 작업을 수행할 수 있도록 설계된 재사용 가능한 명령문 시퀀스이다

C++의 특수 함수인 main()에 모든 기능을 전부 넣기는 힘들다, 따라서 추가 함수를 구현하고 main()에는 해당 함수들을 호출하는 방식으로 프로그래밍 한다

프로그래머가 C++ Standard Library에 있는 함수 외 직접 제작한 함수를 사용자 정의 함수라고 칭한다

	반환형 함수이름(매개변수) //함수 헤더
    {
    	function body
    }
    
    void InFunction()
    {
    	cout << "InFunction()" << endl;
    }
    
    int TestFunction(int a)
    {
    	cout << a << endl;
        
        return a;
    }

함수 헤더에는 return type, function name, parameter 정보가 담겨있다, ()는 컴파일러에게 함수를 정의하고 있다는 것을 알려준다 그리고 함수 본문에는 해당 함수의 실질적인 구현을 한다

만든 함수는 아래와 같이 호출이 가능하다

	함수이름(매개변수); //함수 이름과 ()사이의 공백은 없다

함수의 가장 강력한 기능은 재사용성이다, 따라서 범용성 높은 기능들은 함수로 빼고 필요한 곳 마다 호출해서 사용해주면 된다
(중복 코드 방지에 굉장히 좋으며 유지보수(확장성 등)에도 좋다, DRY(반복X) <-> WET(반복O))

함수는 함수 본문에서 다른 함수를 호출할 수 있다

	int TestFunction(int a)
    {
    	cout << a << endl;
    	
        InFunction(); //함수 본문에서 다른 함수 호출
        
        return a;
    }

중첩 함수란 함수 내부에서 정의된 함수를 의미한다, C++에서 중첩 함수는 지원하지 않는다

	void TestFunctionA()
    {
    	void TestFunctionB()
        {
        	//불가능
        }
    }

반환 값이 있는 함수

반환값이 있는 함수를 만들기 위해서는 반환형을 지정하고 return으로 값을 반환해야 한다

이렇게 반환되는 값을 return value라고 하며 함수가 return을 만나게 되면 그 즉시 함수 호출을 종료하고 반환값을 return한다 이러한 과정을 return by value라고 한다

	//100을 반환하여 result에 할당한다
	int result = TestFunction(100); 
    
    int result { TestFunction(100) };

만약 반환형이 있는 함수라면 무조건 return으로 값을 반환해야 하며 반환값이 없을 경우 반환형을 void로 해주면 된다
(main()은 return하지 않아도 기본적으로 0으로 컴파일러가 알아서 return함)
(void함수에 return하는 것은 중복행위이다)

이때 함수의 return값을 두 번 이상 사용하게 된다면 return값을 따로 변수로 만들어서 사용하는게 좋다
(오버헤드 발생을 줄일 수 있도록 캐싱해서 쓰자)

함수는 단일타입만 return이 가능하다, 여러개의 타입의 값을 반환하는 방법은 추후 따로 정리함

main()

C++의 특수 함수인 main()에는 규칙이 존재한다

우선 main()은 반환형으로 int를 쓰며 명시적 함수 호출은 피하는것이 좋다

	void main() // Compile error
    {
    }
    
    //foo란 의미없는 함수나 변수 명을 사용할 때 자주 사용한다
    int foo()
    {
    	main(); //main()을 명시적으로 호출하지 말 것
    }

main()이 가장 먼저 실행되는 함수라고 생각할 수 있지만 전역변수 초기화가 main() 이전에 실행된다, 이때 전역 변수 초기화에서 함수를 호출한다면 main()보다 먼저 호출되게 된다

그렇다면 main()은 왜 0을 return할까?

프로그램이 정상적으로 종료된다면 main()은 0을 return해야 한다

0이 아니라면 프로그램의 정상적 종료가 아니라는 의미를 나타내는데 사용된다

C++ Standard Library의 < cstdlib > 에는 EXIT_SUCCESS, EXIT_FAILURE라는 상수 macro가 존재하는데 이들이 0과 1 혹은 0이 아닌 숫자로 정의되어 있으며 각각 정상적 프로그램 종료, 비정상적 프로그램 종료의 의미를 담고 있다

따라서 더 좋은 가독성을 위해서라면

	#include <cstdlib>
    
    int main()
    {
    	return EXIT_SUCCESS;
    }

이런 방식도 사용해도 괜찮다

이러한 결과값은 OS로 전달되고 OS에서는 해당 프로그램이 성공적으로 실행,종료 되었는지 판단한다

매개변수가 있는 함수

	void PrintValues(int a, int b, int c)
    {
    	cout << a << b << c << endl;
    }
    
    PrintValues(10, 20, 30);

매개변수는 ,로 구분지어 여러개를 넣을 수 있으며 호출 시 갯수와 타입에 맞는 값을 인자로 넘겨주어 사용이 가능하다

함수가 호출되면 매개변수들은 변수로 생성이 되고 넘겨준 인자값이 매개변수로 복사되어 들어간다 (복사초기화), 이러한 프로세스를 값 전달이라고 한다

이때 매개변수가 있지만 함수 본문에서 사용하지 않는다면 unreferenced param count warning이 발생하게 된다, 이때는 이름을 지정하지 않고 타입만 작성하면 warning이 발생하지 않는다, 이때 주석으로 매개변수가 무엇인지 알려주는게 좋다

	void Foo(int a) //Warning
    {
    	
    }
    
    void Foo(int /*count*/) //Good
    {
    	
    }

이렇게 매개변수가 존재하지만 함수 본문에서 사용하지 않는 함수가 왜 존재할까?

처음에 함수를 제작하고 나중에 이 함수가 업데이트 되어 매개변수가 필요 없어질 때 한번에 해당 매개변수를 제거하면 이 함수를 호출 하는 모든곳에서 error가 발생하게 된다, 하지만 빠른 빌드와 컴파일 테스트가 필요하다면 위와 같은 방식을 임시 방편으로 사용할 수 있는것이다

매개변수 default 값

함수의 매개변수에 default값을 추가할 수 있다

	void Foo(int a, int b = 100);
    
    Foo(100); //a에 100, b에 100이 자동으로 들어간다
    
    void Foo(int a ,int b = 100, int c = 200);
    
    Foo(50); //a에 50, b와 c는 기본값인 100과 200이 자동으로 들어간다

유의할점은 default값을 추가할 매개변수는 맨 끝에 몰아야 한다, 중간에만 default값이 들어갈 수 없다

언제 함수로 만들어 사용할까?, 함수 제작 시 기본 규칙

보통 두 번 이상 동일하게 나타나는 코드 그룹은 함수로 제작하여 사용한다, 또한 함수는 하나의 작업만 수행하는것이 좋다

함수가 길거나 복잡해지면 이를 여러 함수로 나누어 리팩토링을 진행하는것이 좋다


2. Local Variable

지역 변수란

지역 변수란 함수 내부에서 정의된 변수를 의미한다

	void Foo(int a, int b)
    {
    	int result = a + b; //result는 local variable
    }

이때 함수의 매개변수도 local variable로 취급된다

지역 변수의 수명

지역 변수는 { } 내부에서만 유효한다, 처음 선언되고 초기화 될 때 인스턴스화 되어 생성되고 }에서 소멸된다
(이러한 { }범위는 컴파일 속성이다)

하지만 컴파일러는 최적화를 위해 객체를 조금 더 일찍 생성시키거나 더 나중에 소멸시킬 수 있다 이때 프로그래머가 예상한 객체 수명 규칙을 깨지 않는다

그렇기 때문에 { }내부에서는 같은 이름을 공유할 수 없다

	void Foo(int a, int b)
    {
    	int c = a + b;
    }
    
    int main()
    {
    	int a {100};
        int b {200};
        
        Foo(a, b); //매개변수와 인자로 넘긴 변수의 이름은 같아도 됨, 유효범위가 다르기 때문
    }
	void Foo()
    {
    	//여기서는 a 사용이 불가능
        
    	int a = 100; //생성
    } //소멸
    
    //여기서는 a를 사용할 수 없다

변수의 생성과 소멸은 컴파일 타임이 아닌 런타임에 결정된다, 따라서 변수의 수명은 런타임 속성이다

소멸된 객체를 사용하게 되면 의도치 않은 동작이 발생한다, 객체가 소멸되면 메모리 재사용을 위해 메모리 할당이 해제된다

local variable을 정의하는 위치

지역변수는 맨 위에 몰아서 정의하는것 보다 해당 변수가 사용되는곳과 가장 가까운 곳에 정의하는게 좋다

	int a { 100 };
    cout << a << endl;
    
    int b { 200 };
    cout << b << endl;

해당 지역 변수가 언제, 어디서 사용되는지 명확하며 맨 위에는 해당 지역 변수를 초기화 할 값이 없을수도 있기 때문이다, 그리고 초기화 된 값이 몇인지 확인하기 위해 맨 위로 올려야 하는데 이는 프로그래밍에 방해가 된다

임시객체

임시객체란 짧은 기간 동안 필요한 값을 보관하는데 사용되는 이름 없는 객체이다, 임시객체는 필요할 때 컴파일러가 생성한다

	int GetValueFromKeyBoard()
    {
    	int input {};
        
        cin >> input;
        
        return input;
    }
    
    int main()
    {
    	cout << GetValueFromKeyBoard() << endl;
    }

지역변수인 input은 함수 호출이 종료되며 소멸되지만 main에는 이 지역변수를 return받아 사용한다, 이를 보관할 변수를 따로 지정하지 않았기 때문에 컴파일러는 임시객체를 생성하여 해당 return값의 사본을 생성하고 전달한다

임시객체는 해당 문장이 실행되고 다음 문장이 넘어가기 전에 소멸된다
(cout << GetValueFromKeyBoard() << endl;이 호출되고 바로 소멸)

	int x = 5 + 10; //5 + 10의 결과가 임시객체로 생성되고 x에 초기화한다, 그리고 바로 임시객체는 소멸된다

하지만 C++17기준 성능 최적화를 위해 임시객체를 생성하지 않고 바로 초기화 하는 방식으로 진행된다

	int getvalue() { return 40; }
    int x = getvalue();
    
    //임시객체 생성 안하고 x를 40으로 바로 초기화

이는 반환값을 바로 사용하는 경우에도 마찬가지다

profile
GameDeveloper🎮 Dev C++, DataStructure, Algorithm, UE5, Assembly🛠, Git/Perforce🌏

0개의 댓글