본문 바로가기

코딩/객체지향프로그래밍

[C++] 포인터, 벡터, 배열 (2)

출처: http://tcpschool.com/c/c_array_oneDimensional

 


 

1. 함수 리턴값으로 벡터 이용하기

#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

bool is_prime(int n) {
	if (n <= 1)
		return false;

	for (int i = 2; i < n; i++)
		if (n % i == 0)
			return false;

	return true;
}

vector<int> primes(int low, int high) {
	vector<int> vec;
	for (int i = low; i <= high; i++) {
		if (is_prime(i))
			vec.push_back(i);
	}
	return vec;
}

void print(const vector<int> &v) {
	for (int elem : v)
		cout << setw(4) << elem;
	cout << endl;
}

int main() {
	int low, high;
	cin >> low >> high;
	vector<int> vec = primes(low, high); //[low, high] 모든 소수
	print(vec);

	return 0;
}
  • print함수는 벡터의 원소를 수정하지 않음 → const, &를 걸어줘야 메모리 복사가 일어나지 않고 값도 변경되지 않음!
  • vector<int>는 무거운 데이터 타입, 객체니까 가급적이면 메모리 복사를 피하기 위해 &를 걸어준다!
  • &를 걸어줬으므로 const로 의도치 x 변하지 않게!
  • is_prime 함수: 입력 파라미타 n이 소수이면 true, 그렇지 않으면 false 반환
  • primes 함수: 입력 파라미타 low와 high 사이의 수 중에서 소수이면(if is_prime함수가 true이면) 1차원 벡터에 추가(push_back)하여 벡터를 반환 (리턴타입이 vector<int>)
  • print 함수: 입력 파라미타인 1차원 벡터 v의 모든 원소들을 모니터에 출력, 이 때 4칸 띄워 우측정렬함

 

2. 2차원 벡터 생성하기

vector<vector<int>> v(2, vector<int>(3));
  • vector<vector<'열'에 해당하는 데이터 타입>> 벡터이름(행의 개수, vector<데이터 타입>(열의 개수));
  • 위의 예제같은 경우 2행 3열, 원소가 2 * 3 = 6개인 2차원 벡터! (matrix, 행렬, table...)
for(vector<int>& row : vec) {
	for(int elem : row)
		cout << elem;
	cout << endl;
}
  • 2차원 벡터의 '행'의 원소는 1차원 벡터!
  • vector<int>& row : vec → row(행)의 각 원소가 1차원 벡터이므로 & 걸어주기!!
  • 인덱싱 : [0][0], [0][1], [0][2], [1][0], [1][1], [1][2]

 

3. 다차원 벡터 선언 단순화하기

1. using 구문

using Matrix = vector<vector<double>>;

using: 'Matrix'라는 새로운 타입의 이름은 정의한다. 이를 이용하면 2차원 벡터 중에서도 double형의 원소들을 열에 가지는 벡터를 간편하게 선언할 수 있다.

 

#include <iostream>
#include <iomanip>
#include <vector>
using namespace std;
using Matrix = vector<vector<double>>;

void print(const Matrix &m) {
	for (const vector<double>& row : v)
		for (double elem : row)
			cout << setw(5) << elem;
		cout << endl;
}

*)교수님께서는 별로 추천하지 않으셨다.

 

2. auto 만능키

  • 컴파일러가 데이터 타입을 명확히 알 수 있을 때 타입을 auto로 치환할 수 있다.
#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

void print(vector<vector<int>> &v) {
	
	//벡터 v는 각각의 elem(여기선 row)마다 1차원 벡터를 가짐
	//그 elem(row)안에 다시 정수형 element가 들어있음
	//vector<int>는 &로 메모리 복사 피하기, const로 수정 x
	//int는 메모리 복사 하나 안하나 상관x, &안걸어줘도 됨

	for (const auto &row : v) { //const vector<int> 와 동일
		for (auto elem : row) //int와 동일
			cout << setw(4) << elem;
		cout << endl;
	}
}

int main() {

	vector<vector<int>> v(2, vector<int>(3)); //2x3 matrix, elem 3개인 1차원 벡터가 2개
//	v[0][0] = 1;	v[0][1] = 2;	v[0][2] = 3;
//	v[1][0] = 4;	v[1][1] = 5;	v[1][2] = 6;
	vector<vector<int>> v{  {1, 2, 3},
				{4, 5, 6} };

	print(v);

	//초기화 할 때 값의 type이 명확하니까 auto 가능
	auto a = 10; //int와 동일
	auto b = 'c'; //char과 동일
	auto c = 10.0; //double고 동일
	
	return 0;
}

 

4. 배열

1. 배열이란?

  • 벡터(vector)와 배열(array)은 같은 형태와 기능은 유사하나, 배열은 built-in(벡터보다 더 오래된 데이터 타입, 컨테이너)이라 벡터처럼 헤더를 include할 필요가 없다.
  • 배열은 객체가 아니기 때문에 멤버 데이터와 멤버 함수(메소드)도 없다.
  • 특정 메모리와 그에 맞는 값에 즉각적으로 매핑된다.
  • 배열의 크기는 프로그램 실행 중에 바뀔 수도 있고(dynamic array-동적 배열), 그렇지 않을 수도 있다.(static array-정적 배열)

2. 배열의 선언

int list[4] = {1, 2, 3, 4};

'배열의 원소 데이터 타입' '배열의 이름'[배열의 크기] = {원소1, 원소2, 원소3, ...};

*)이 때 int list[]로 크기를 비워놔도 컴파일러는 허용한다! → 배열의 원소들로 크기를 유추할 수 있기 때문이다.

3. 배열의 시작 주솟값

  • 배열의 이름 == 배열의 첫 번째 인덱스[0]의 시작 주솟값 == 즉, 배열의 시작 주솟값 이다!!
  • 포인터 변수로 임시로 타입이 변환된다. (배열의 이름이 포인터 변수 그 자체는 아님!)
  • *연산자로 참조 시 [0]데이터를 가리킨다. ( ex) *list == list[0] )

4. 배열의 크기

 위에서 언급했듯, 배열은 객체가 아니기 때문에 멤버 데이터, 메소드도 없다. 벡터의 메소드 중에서 자주 사용되는 .size()함수 역시 배열은 사용하지 못한다. 배열의 크기(size)가 배열의 멤버 데이터가 아니기 떄문이다. → 따라서 배열의 크기를 사용해야하는 경우, ①함수를 호출할 때 직접 입력 파라미터로 보내주는 방법②sizeof(배열이름) 이라는 글로벌 함수(global function)을 사용하는 방법 등이 있다.

 

*) 방법 ①의 example

#include <iostream>

void print(int a[], int size) { //size: 배열의 크기
	for (int i = 0; i < size; i++)
		cout << a[i] << " ";
	cout << endl;
}

int sum(int a[], int size) {
	int result = 0;
	for (int i = 0; i < size; i++)
		result += a[i];
	return result;
}

int main() {
	int list[4] = { 2, 4, 6, 8 };
    
	print(list, 4); //배열의 크기 4를 함수의 입력 파라미타로 보내주기
	cout << sum(list, 4) << endl;
    
	//list의 모든 원소를 0으로 만들기
	for (int i = 0; i < 4; i++)
		list[i] = 0;

	print(list, 4);
	cout << sum(list, 4) << endl;
}
  • 함수의 입력 파라미타로 배열을 받을 때: int a[] (매개변수 a는 배열의 이름, 배열의 원소 타입은 int)

 

5. 포인터와 배열

  • 앞서 배열의 이름은 (다른 연산자와 쓰이지 않을 때) 배열의 첫 번째 원소[0]를 가리키는 = 배열의 첫 번째 원소[0]의 시작 주솟값을 가지는 포인터 변수로 잠시 임시로 타입 변환된다고 했다.
  • 배열의 첫 번째 원소[0]의 시작 주솟값: &a[0] 으로 표현
  • int형 변수를 가리키는 포인터 p가 있을 때 → p = &a[0];p = a; 는 같은 표현!! (a는 원소가 int형인 배열)
  • 배열의 시작, 끝 주솟값만 알면 배열의 모든 원소에 접근할 수 있다!

*)포인터 연산 & 주솟값의 연산

앞서 증감 연산자에 대해 알아볼 때, a++/++a 와 같이 전위, 후위에서 1을 증가시키는 연산자에 대해 알아보았다.

그런데, 이 연산자들이 포인터 변수에 대해서도 적용 가능하다! → p++/p-- 와 같은 연산이 가능하다는 뜻이다!

int형 원소들로 이루어진 배열 arr와, int형 변수를 가리키는 int*형 포인터 p가 있다고 하자.

p는 arr의 첫 번째 원소 arr[0]을 가리키고 있다. (p = arr; 혹은 p = &arr[0])

이 때 p++; (p = p + 1) 연산을 실행하면 p의 원래 주솟값에 16진수로 4만큼 증가하여 다음 원소(arr[1])를 가리키게 된다.

int는 4byte크기의 데이터 타입이기 때문에 메모리 상에서 변수가 차지하는 방 한 칸의 크기가 4이기 때문이다. 따라서 실제 16진수로 이루어진 주솟값은 4만큼 증가했을지라도, 배열에서는 그 다음(+1) 원소인 arr[1]을 가리키는 것이다! →여기서 우리는 arr[0]와 arr[1]이 배열 상에서는 1칸 거리를 가지지만, 메모리 공간 상에서는 4칸(즉, 4byte)만큼의 거리를 가지고 있다는 사실을 알 수 있다. (double이면 8byte! char이면 1byte!)

 

*) 4~5 example

#include <iostream>
#include <iomanip>
using namespace std;

int sum(int* list, int size) { //int* list == int list[]
	// sizeof(list)쓰면 array크기 계산 가능
	int result = 0;
	for (int i = 0; i < size; i++)
		result += list[i];

	return result;
}

void print(int *begin, int *end) {
	int *curr = begin; //curr: 배열 원소의 주솟값을 계속 업데이트하는 포인터
	while (curr != end) {
		cout << setw(4) << *curr;
		curr++; //curr의 주솟값에 16진수로 4 증가 (int는 4byte니까 방 한칸 크기 4)
	}

/* 	위의 while문과 동일한 for문
	for (int *curr = begin; curr != end; curr++) {
		cout << setw(4) << *curr;
	} */

	cout << endl;
}

int main() {
	int list[3] = { 10, 20, 30 };
	//list를 함수로 보낼 때 크기도 함께 보내줘야함
	cout << sum(list, 3) << endl;  

	//[0]의 주솟값+1 == [1]의 주솟값 (주솟값의 덧셈에서 1증가->16진수로 4증가)
	cout << list << '\t' << *list << endl;
	cout << (list + 1) << '\t' << *(list + 1) << endl;
	cout << (list + 2) << '\t' << *(list + 2) << endl;

	int *begin = list;
	int *end = list + 3;
	print(begin, end);

	return 0;
}
  • 함수의 입력 파라미타로 배열을 보낼 때, int arr[]와 int* arr는 동일한 표현이다!
  • 위의 예제에서 사실 end포인터는 list[2]+1의 주솟값을 저장하고 있기 때문에 '배열의 마지막 원소의 주솟값'이라기 보다는 엄밀히 따지자면 '마지막 원소+1의 주솟값'이다.

 


 

*)sample codes: https://github.com/halterman/CppBook-SourceCode

반응형