삶의 공유

[C++] C++와 표준 라이브러리 초단기 속성코스(함수, 배열, 벡터) 본문

Programing/C++

[C++] C++와 표준 라이브러리 초단기 속성코스(함수, 배열, 벡터)

dkrehd 2021. 2. 1. 07:01
728x90
반응형

출저 : 책, 전문가를 위한 C++ 

 

함수

규모가 어느 정도 큰 프로그램에서 코드를 모두 main()안에 담으면 관리하기 힘들어진다. 프로그램의 가독성을 높이려면 함수 단위로 간결하게 나눠서 작성해야 한다.

 

C++에서 함수를 사용하려면 반드시 사용하려는 위치보다 앞에서 선언해야 한다. 특정한 파일 안에서만 사용할 함수는 선언과 구현(정의) 모두 소스 파일 안에 작성한다. 반면 함수를 다른 모듈이나 파일에서도 사용해야 한다면 그 함 수는 헤더 파일에 작성하고 구현은 소스파일에 작성한다.

 

※ 함수를 선언하는 문장을 함수 프로토타입 또는 함수 헤더라 부른다. 함수의 구체적인 내용은 보지 않고, 그 함수에 접근하는 방식만 표현한다는 의미가 강하다. 또한 함수의 리턴 타입을 제외한 함수 이름, 매개변수 목록을 함수 시그니처 라고 부른다.

 

다음 코드는 함수를 선언하는 방법을 보여준다, 예제에서는 리턴 타입을 void로 지정했는데, 이 함수는 호출한 측으로 리턴 값이 없다는 것을 의미 한다. 또 한 이 함수를 호출 하려면 반드시 두 개의 인수(정수 타입의 인수와 문자 타입 인수)를 지정해야 한다.

 

void myFunction(int i char c);

 

이렇게 선언만 하고 구체적인 동작은 구현(정의) 하지 않은 채 이 함수를 호출한 문장이 담긴 코드를 컴파일 하면 실제로 코드에 존재 하지 않는 함수를 호출 하기 떄문에 링크 과정에서 에러가 발생한다.

 

위에 선언한 함수를 구현하는 방법은 다음과 같다. 

void myFuntion(int i, char c)
{
	std::cout << "the value of i is " << i << std::endl;
	std::cout << "the value of c is " << c << std::endl;
}

※ C언어에서는 컴파일러에 따라 매개변수를 받지 않은 함수를 작성할때 void를 적어야 할 수도 있다. 이와 달리 C++에서는 함수의 매개변수 리스트 자리에 void대신 그냥 비워두면 된다.

하지만 리턴 값이 없다는 것을 표시할 때는 C언어와 마찬가지로 리턴 타입 자리에 반드시 void라고 적어야 한다.

 

함수는 당연히 호출한 측으로 값을 리턴 할수 있다. 예를 들어 두 수를 더한 결과를 리턴하려면 다음과 같이 작성한다.

 

int addNumbers(int number1, int number2)
{
	return number1 + number2;
}

 

함수 리턴 타입 추론

C++14부터는 함수의 리턴 타입을 컴파일러가 알아서 지정할 수 있다. 이 기능을 적용하려면 다음과 같이 리턴 타입에 auto 키워드를 적는다

 

auto addNumbers(int num1, int num2)
{
	return num1 + num2;
}

그러면 컴파일러는 return문에 나오는 표현식의 타입에 따라 리턴 타입을 추론한다. 함수 안에는 return문이 여러개가 있을 수 있는데, 이때 각 타입은 모두 같아야한다 래턴 값으로 재귀 호출(자기 자신을 호출하는 문장)을 지정할 수도 있는데, 이때 비재귀 호출 return 문도 반드시 함께 작성한다.

 

현재 함수 이름

함수마다 내부적으로 __func__라는 로컬 변수가 정의돼 있다. 이 변수는 현재 함수의 이름을 값으로 갖고있으며, 주로 로그를 남기는데 활용한다.

int addNumbers(int number1, int number2)
{
	std::cout << "Entering function " << __func__ << std::endl;
	return number1 + number2;
}

 

 

C언어 스타일 배열

 

배열은 같은 타입의 값을 나란히 저장하며, 각 항목은 배열에 놓인 위치로 접근한다. C++에서 배열을 선언할 때는 반드시 배열의 크기를 지정해야하는데, 변수로 지정할 수는 없고 반드시 상수 또는 상수 표현식으로 지정해야한다.

 

예를 들어, 정수값 세 개 가진 배열을 선언하려면 다음과 같이 작성한다.

int myArray[3];

myArray[0] = 0;
myArray[1] = 0;
myArray[2] = 0;

※ C++에서 배열의 첫 번째 원소의 위치는 1이 아닌 0이다. 또한 배열의 마지막 원소의 위치는 항상 배열의 크기에서 1을 뺀 값이다.

 

이렇게 각 원소마다 초기화 하지 않고 다음에 설명할 반복문을 활용해도 된다. 그런데 앞에 나온 방법이나 반복문 말고도 다음과 같이 제로 초기화 구문으로 한번에 초기화 하는 방법도 있다.

※ 제로 초기화 : 주어진 객체를 디폴트 생성자로 초기화 하는 것으로 기본 정수 타입은 0으로 기본 부동 소수점 타입은 0.0으로 포인터 타입은 nullptr로 초기화한다.

int myArray[3] = {0};
int myArray[3] = {}; // 0은 생략 해도 된다

또한 이니셜라이즈 리스트(initializer_list, 초기자/초기화 리스트)를 사용해도 된다. 그러면 배열의 크기를 컴파일러가 알아서 결정한다.

int myArray[] = {1, 2, 3, 4} // 컴파일러는 네 개의 원소를 가진 배열을 생성한다.

 

배열의 크기를 지정 할때 이니셜라이저 리스트에 나온 원소 수가 배열의 크기로 지정한 개수보다 적으면 나머지 원소는 0으로 초기화 된다.

예를들어, 다음과 같이 첫번째 원소 값만 2로 지정하면 나머지 원소는 모두 0으로 초기화 된다.

 

int myArray[3] = {2};

 

스택 기반의 C 스타일 배열의 크기는 C++17부터 제공하는 std::size()함수로 알아낼수 있다. (이 함수를 사용하려면 <array>헤더를 include해야 한다.

예를 들면 다음과 같다.

unsigned int arraySize = std::size(myArray);

 

현재 사용하는 컴파일러가 C++17을 지원하지 않는 다면 예전 방식 처럼 sizeof연산자로 크기를 구하면 된다. sizeof 연산자는 인수로 지정한 대상의 크기를 바이트 단위로 리턴한다.

스택 기반 배열에 담긴 원소 수를 알아내려면 이 연산자가 리턴한 값을 첫 번째 원소의 크기로 나눠야한다. 

예를 들면 다음과 같다.

 

unsigned int arraySize = sizeof(myArray) / sizeof(myArray[0]);

 

이 코드는 일차원 배열에 대한 예를 보여주고 있다. 이 배열은 매겨진 칸 마다 정수가 일렬로 담겨 있다고 볼 수 있다.

C++는 다차원 배열도 지원한다. 이 차원 배열은 바둑판에 비유할수 있다. 여기서 각 원소의 위치는 x축의 위치와, y축의 위치의 조합으로 표현한다.

 

예를 들면 틱택토 보드를 다음과 같이 이차원배열로 표현할 수있다. 여기서는 보드 한 가운데 칸에 'o'문자를 대입했다.

char ticTacToeBoard[3][3];
ticTacToeBoard[1][1] = 'o';

 

그림으로 표현하면 다음과 같다.

출저 : 책, 전문가를 위한 C++

C++스타일 배열

C++에서는 std::array라는 고정 크기 컨테이너를 제공한다. 이 타입은 <array>헤더 파일에 정의돼 있다. 사실 C 스타일 배열 위에 한 꺼풀 덮어 쓴 것에 불과 하다.

 

std::array는 C 스타일 배열에 비해 여러가지 장점이 있다. 항상 크기를 정확히 알수 있고, 자동으로 포인터를 캐스팅(동적 형변환)하지 않아서 특정한 종류의 버그를 방지할 수 있고, 반복자(iterator)로 배열에 원소에 대한 반복문을 쉽게 작성 할 수 있다. 

 

다음 예제는 array컨테이너를 사용 하는 방법을 보여주고 있다. array<int, 3> 과 같이 array뒤에 나오는 꺽쇠 괄호 표기 법은 템플릿을 소개하는 장에서 자세히 설명한다.

 

여기서는 일단 꺽쇠 괄호에 두 개의 매개 변수를 지정해야 한다는 정도만 알고 넘어가자.

첫 번째 매개 변수는 배열에 담길 원소의 타입을, 두 번째 매개 변수는 배열의 크기를 나타낸다.

 

array<int, 3> arr = {9, 8, 7};
cout << "Array Size = " << arr.size() << endl;
cout << "2nd element = " << arr[1] << endl;

※ C스타일 배열과, std::array는 둘다 크기가 고정된다. 따라서 반드시 컴파일 시간에 결정되야 하며 실행 시간에 늘이거나 줄어들수는 없다.

 

벡터

C++ 표준 라이브러리에서는 저장 공간의 크기가 고정 되지 않은 컨테이너를 다양하게 제공한다.

대표적인 예로 <vector> 헤더 파일에 선언된 std::vector가 있다. vector는 C 스타일의 배열을 대체할 수 있는 것으로 훨씬 유연하고 안전하다.

프로그래머가 메모리 관리를 신경쓸 필요가없다. 원소를 모두 담을 정도로 메모리를 충분히 확보하는 작업은 vector가 알아서 처리하기 때문이다. vector는 동적이다. 다시말해 실행 시간에 원소를 추가하거나 삭제 할수 있다.

다음 코드는 vector의 기본 기능을 보여주고 있다.

 

// 정수 타입 벡터를 생성한다.
vector<int> myVector = {11, 22};

// push_back()을 이용하여 생성한 백터에 정수값을 몇 개 추가한다.
myVector.push_back(33);
myVector.push_back(44);

// 원소에 접근한다
cout << "1st element: " << myVector[0] << endl; // 11

myVector는 vector<int>로 선언했다. 여기서 std::array와 마찬가지로 꺾쇠 괄호로 템플릿 매개 변수를 지정해야 한다. vector는 제네릭 컨테이너(범용 컨테이너)다. 즉 거의 모든 종류의 객체를 담을 수 있다. 그래서 vector를 사용할 때반드시 꺽쇠 괄호 안에 원하는 객체 타입을 명시해야 한다.

 

vector에 원소를 추가하려면 push_back()메서드를 사용한다.

 

구조적 바인딩

C++17부터 구조적 바인딩 이란 개념이 도입 됐다. 구조적 바인딩을 이용하면 여러개의 변수를 선언할 때 배열, 구조체, 페어 또는 튜플의 값으로 초기화 할 수 있다. 

예를 들어 다음과 같이 배열이 정의 되어 있다고 하자

std::array<int, 3> values = { 11, 22, 33 };

이 상태에서 x, y, z란 변수를 선언 할때 각각 앞에 나온 values배열에 담긴 값으로 구조적 바인딩을 이용하여 초기화 할수 있다. 구조적 바인딩을 적용하려면 반드시 auto 키워드를 붙여야 한다. 예를 들어 auto 자리에 int를 지정하면 안된다

auto [x, y, z] = values;

구조적 바인딩에서는 왼쪽에서 나온 선언할 변수 개수와 오른쪽에 나온 표현식 값 개수가 반드시 일치해야한다.

 

구조적 바인딩은 배열 뿐만 아니라 모든 멤버가 non-static이면서 public으로 선언된 데이터 구조라면 어떤것 (struct, pair, tuple등)도 적용할 수 있다. 

#include <iostream>
#include <array>


struct Point
{
	double mX, mY, mZ;
};

int main()
{
	Point point;
	point.mX = 1.0; point.mY = 2.0; point.mZ = 3.0;
	auto [x, y, z] = point;
}

 

반응형