[따배C++] 6. 배열, 문자열, 포인터, 참조

Created: June 6, 2021 11:25 AM
Tag: array, const, dynamic allocation, for-each, reference variable, selection sort, vector, void pointer

6.1 배열 기초 1/2 array


배열과 구조체의 결합

#include <iostream>

using namespace std;

struct Rectangle
{
	int length;
	int width;
};

int main(void)
{
	Rectangle rect_arr[10];

	rect_arr[0].length = 1;
	rect_arr[0].width = 2;
	return (0);
}

배열과 열거형의 결합

#include <iostream>

using namespace std;

enum StudentName
{
	JACKJACK,
	DASH,
	VIOLET,
	NUM_STUDENTS,
};

int main(void)
{
	// case1
	int my_array[] { 1, 2, 3, 4, 5 };

	cout << my_array[JACKJACK] << endl;
	cout << my_array[DASH] << endl;
	cout << my_array[VIOLET] << endl;

	// case2
	int students_scores[NUM_STUDENTS];

	students_scores[JACKJACK] = 0;
	students_scores[DASH] = 100;
	return (0);
}

6.2 배열 기초 2/2 array


매개변수로서 배열

다음 두줄의 코드는 완전히 같게 기능한다. 배열을 매개변수로 받는것을 강조하기 위해 전자와 같이 작성할 수 있지만, 이경우에도 매개변수는 포인터 이다.

void doSomething(int students_scores[20])
void doSomething(int *students_scores)

배열이 이름과 포인터

#include <iostream>

using namespace std;

void doSomething(int students_scores[20])
{
	cout << (int)&students_scores << endl; // 다른 주소를 출력(주소값을 저장하는 다른 변수의 주소)
	cout << (int)&students_scores[0] << endl; // 같은 주소를 출력
	cout << students_scores[0] << endl;
	cout << students_scores[1] << endl;
	cout << students_scores[2] << endl;
}

int main(void)
{
	const int num_students = 20; // compile time에 값이 결정

	int students_scores[num_students] = { 1, 2, 3, 4, 5, };

	cout << (int)students_scores << endl; // 배열의 이름 자체가 포인터
	cout << (int)&students_scores << endl; // 같은 주소를 출력
	cout << students_scores[0] << endl;
	cout << students_scores[1] << endl;
	cout << students_scores[2] << endl;

	doSomething(students_scores); // 배열의 첫번째 주소값을 복사
}

6.4 배열과 선택정렬 selection sort


#include <iostream>

using namespace std;

void printArray(const int array[], const int length)
{
	for (int index = 0; index < length; ++index)
		cout << array[index] << " ";
	cout << endl;
}

int main(void)
{
	const int length = 5;

	int array[length] = { 3, 5, 2, 1, 4 };

	printArray(array, length);

	for (int startIndex = 0; startIndex < length - 1; ++startIndex)
	{
		int smallestIndex = startIndex;

		for (int currentIndex = startIndex; currentIndex < length; ++currentIndex)
		{
			if (array[smallestIndex] > array[currentIndex])
			{
				smallestIndex = currentIndex;
			}
		}
		// swap two values in the array
		// std::swap(array[smallestIndex], array[startIndex]); 로 줄일 수 있다
		{
			int temp = array[smallestIndex];
			array[smallestIndex] = array[startIndex];
			array[startIndex] = temp;
		}
		printArray(array, length);
	}
	return (0);
}

6.7 널 포인터 Null Pointer


Modern C++-style

#include <iostream>
#include <cstddef>

int main(void)
{
	double *ptr = nullptr;
	double *ptr{ nullptr };

	return (0);
}

6.10 C언어 스타일의 문자열 심볼릭 상수


포인터와 리터럴

#include <iostream>

using namespace std;

int main(void)
{
	// char name[] = "Jack Jack" // 가능!
	char *name = "Jack Jack"; // 불가능!
	// 왼쪽은 포인터 오른쪽은 리터럴
	// 포인터는 메모리의 주소를 가리킬 수만 있다
	// 실제로 잭잭이 담길 메모리가 없다
	// 다음과 같이 const를 붙여서 사용한다면 심볼릭 상수로 사용할 수 있다

	const char *name = "Jack Jack";
	const char *name2 = "Jack Jack";

	cout << (uintptr_t)name << endl;
	cout << (uintptr_t)name2 << endl;
	// 컴파일러 레벨에서 같은 문자열은 메모리를 공유하게 한다

	char c = 'Q';
	cout << &c << endl; // 문자열로 착각하여 쓰레기값이 함께 출력된다

	return (0);
}

6.11 메모리 동적 할당 new와 delete


메모리 할당의 종류

  • static memory allocation
  • auto memory allocation
  • dynamic memory allocation

동적할당 변수

#include <iostream>

using namespace std;

int main(void)
{
	int *ptr = new int{ 7 };
	// 동적할당에 실패하면 ptr에 Null pointer가 할당된다
	int *ptr = new (std::nothrow) int{ 7 };
	// 오류를 발생시키지 않고 메모리를 사용할 수 있을때까지 계속 구동한다

	delete ptr; // 동적할당 해제
	ptr = nullptr;

	// memory leak
	while (true)
	{
		int *ptr = new int;
		cout << ptr << endl;
	}
	return (0);
}

6.12 동적할당 배열


  • 정적할당 배열: 컴파일타임에 배열의 크기가 결정(고정)된다
  • 동적할당 배열: 런타임에 배열의 크기가 결정된다
#include <iostream>

using namespace std;

int main(void)
{
	int length;

	cin >> length;

	// int array[length];
	int *array = new int[length] { 11, 22, 33, 44, 55 };
	// int *array = new int[] { 11, 22, 33, 44, 55 };
	// 동적할당 시 컴파일타임에 배열의 크기를 결정하려 하면 에러발생

	array[0] = 1;
	array[1] = 2;
	for (int i = 0; i < length; ++i)
	{
		cout << array[i] << endl;
		cout << &array[i] << endl;
	}

	delete [] array;
	return (0);
}

* 6.13 포인터와 const


const int value = 5;
const int *ptr = &value;

변수에 저장된 값을 바꾸지 못함, 포인터 변수가 가리키고 있는 주소에 저장된 값을 바꾸지 못함

int const *ptr = &value;

포인터 변수가 가리키고 있는 주소값을 바꾸지 못함

const int const *ptr = &value;

모두 바꾸지 못함, 반드시 초기화 해줘야 함

6.14 참조 변수 reference variable


참조변수를 사용하면 특정한 경우 포인터보다 사용이 편리하다. 참조변수는 반드시 초기화 되어야 한다. 리터럴은 할당 불가능하다. 참조하는 변수와 마치 메모리를 공유하는 동일한 변수(별명)처럼 작동한다. 참조변수를 사용하면 역참조 기호를 붙일 필요가 없고 문법적으로 깨끗한 코드를 작성할 수 있다.

#include <iostream>

using namespace std;

int main(void)
{
	int value = 5;

	int &ref = value;

	const int y = 8;
	// int &ref = y; // ref에서 값을 바꿀 수 있기 때문에 문법상 허용되지 않는다
	const int &ref = y;

	ref = 10; // value의 값도 같이 바뀜

	cout << value << " " << ref << endl;
	cout << &value << endl;
	cout << &ref << endl; // 주소도 같게 나온다

	return (0);
}

참조변수를 매개변수로 사용

함수의 리턴값을 여러개 받는것이 가능해졌기 때문에 입력을 인풋 파라미터를 수정되지 않게 프로그래밍 하는것이 트랜드이다. 참조변수를 매개변수로 사용하는 경우 변수 자체가 함수로 넘어가서 주소를 복사할 필요조차 없으므로 퍼포먼스가 가장 좋다.

#include <iostream>

using namespace std;

void doSomething(int &n)
{
	n = 10;
	cout << "in do something" << n << endl;
}

int main(void)
{
	int n = 5;

	doSomething(n);
	cout << n << endl;
	return (0);
}

// result
in do something 10
10

참조변수를 통한 복잡한 구조체 맴버에 대한 접근을 간단히

struct Something
{
	int v1;
	float v2;
};

struct Other
{
	Something st;
};

int main(void)
{
	Other ot;
	ot.st.v1 = 1.0
	int &v1 = ot.st.v1;
	v1 = 1;
	return (0);
}

6.17 C++ For-each 반복문


#include <iostream>
#include <limits>
#include <algorithm>
#include <vector>

using namespace std;

int main(void)
{
	int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13,
								21, 34, 55, 89 };

	int max_number = std::numeric_limits<int>::lowest();

	// for each 구문
	for (const auto &n : fibonacci)
		max_number = std::max(max_number, n);

	cout << max_number << endl;
	return (0);
}

6.18 void 포인터 generic pointer


void 포인터는 포인터 연산이 불가능하며, 다형성 프로그래밍에 사용된다. 다음과 같이 적당히 casting하여 특정 자료형 포인터로 사용할 수 있다.

enum Type
{
	INT,
	FLOAT,
	CHAR
};

int main(void)
{
	using namespace std;

	int i = 5;
	float f = 3.0;
	char c = 'a';

	Type type = FLOAT;

	if (type == FLOAT)
		cout << *static_cast<float*>(ptr) << endl;
	else if (type == INT)
		cout << *static_cast<int*>(ptr) << endl;

	return (0);
}

6.20 std::array 소개


std::array를 통한 배열 선언과 초기화

#include <iostream>

using namespace std;

void printLength(const array<int, 5> &my_arr)
{
	cout << my_arr.size() << endl;
}

int main(void)
{
	array<int, 5> my_arr = { 1, 2, 3, 4, 5 };
	
	cout << my_arr[10] << endl;
	cout << my_arr.at(10) << endl; // 인덱스 접근 시 경계를 검사하여 오류가 발생하면 예외처리

	printLength(my_arr);
	return (0);
}

std::array를 활용한 순차정렬

#include <iostream>
#include <algorithm>

void printLength(const array<int, 5> &my_arr)
{
	cout << my_arr.size() << endl;
}

int main(void)
{
	array<int, 5> my_arr = { 1, 21, 3, 40, 5 };

	for (auto &element : my_arr)
		cout << element << " ";
	cout << endl;

	std::sort(my_arr.begin(), my_arr.end());
	// std::sort(my_arr.rbegin(), my_arr.rend()); // 역순정렬

	for (auto &element : my_arr)
		cout << element << " ";
	cout << endl;

	return (0);
}

6.21 std::vector 소개


std::vector를 통해 동적할당 배열을 대체할 수 있다. 백터를 활용하면 배열의 길이를 지정할 필요가 없고, 메모리가 누수를 걱정하지 않아도 된다.

#include <iostream>
// #include <array>
#include <vector>

using namespace std;

int main(void)
{
	vector<int> arr = { 1, 2, 3, 4, 5 };

	arr.resize(10); // 사이즈를 자유롭게 바꿀 수 있다

	for (auto &itr : arr)
		cout << itr << " ";
	cout << endl;

	cout << arr[1] << endl;
	cout << arr.at(1) << endl;
	cout << arr.size() << endl;
	return (0);
}

좋은 웹페이지 즐겨찾기