본문 바로가기

개발 공부/C++

따라하며 배우는 C++ 6. 행렬, 문자열, 포인터, 참조

따라하며 배우는 C++ 

 

 

6. 행렬, 문자열, 포인터, 참조

 

6.1 배열 기초 [1 of 2] array

 

#include <iostream>
using namespace std;

int getInt()
{
	int one_student_score; //1 variable
	int student_scores[5];

	cout << sizeof(one_student_score) << endl;
	cout << sizeof(student_scores) << endl;

	one_student_score = 100;
	student_scores[0] = 100; //1st element
	student_scores[1] = 80; //2nd element
	student_scores[2] = 90; //3rd element

}

 

배열의 범위에 주의하자.

수학에선 첫번째부터 시작이지만 컴퓨터에선 0번째부터 시작한다.

#include <iostream>
using namespace std;

struct Rectangle
{
	int length;
	int width;
};
int main()
{
	cout << sizeof(Rectangle) << endl;
	Rectangle rect_arr[10];
	cout << sizeof(rect_arr) << endl;
	return 0;
}

구조체 배열을 사용할 수도 있다.

rect_arr[0].length = 1; //이런 식으로 대입 가능.

 

초기화는 이런 식으로

 

int my_array[5] = {1, 2, }; //이런 식으로 선언 및 초기화 시, 대응되지 않는 원소는 0으로 자동 초기화

int my_array[] = {1, 2, 3, 4, 5}; //오른쪽 초기화에서 원소 개수가 명확하면 개수를 명시하지 않아도 됨.

int my_array[] {1, 2, 3, 4, 5}; //이런 초기화도 가능! --> 최신 버전에서

 

enum을 배열 넘버링으로 사용 가능. (권장하진 않음)

 

 

#include <iostream>
using namespace std;
enum StudentName
{
	JACKJACK, // =0
	DASH,	// = 1
	VIOLET, // = 2
	NUM_STUDENTS, // = 3
};
int main()
{
	int students_scores[NUM_STUDENTS];
	students_scores[JACKJACK] = 0;
	students_scores[DASH] = 100;
	students_scores[VIOLET] = 200;
	return 0;
}

enum의 마지막 원소로 배열의 전체 개수를 표현할 수도 있다.

 

#include <iostream>
using namespace std;

int main()
{
	int num_students = 0;
	cin >> num_students;

	int students_scores[num_students];
	//입력받는다는 것은 배열의 크기가 런타임에 설정된다는 것.
	//그러나 [] 배열은 컴파일 타임에 배열의 사이즈가 정해져 있어야 한다.
	return 0;
}

즉 배열의 크기가 정해져 있을 땐 그 배열의 사이즈(=길이)를 런타임에 설정할 수 없다.

C에선

#define NUM_STUDENTS 100000

int students_scores[NUM_STUDENTS];

이렇게 큰 상수를 설정하고 정적 배열 선언도 가능(권장하진 않음, c스타일)

 

const int num_students = 5;

int students_scores[num_students];

//도 가능. 그러나 const를 꼭 붙여주어야 한다.

 

 

6.2 배열 기초 [2 of 2] array

 

#include <iostream>
using namespace std;

int main()
{
	const int num_students = 20;

	int students_scores[num_students];
	cout << (int)&students_scores << endl;
	cout << (int)&(students_scores[0]) << endl;
	cout << (int)&(students_scores[1]) << endl;
	cout << (int)&(students_scores[2]) << endl;
	cout << (int)&(students_scores[3]) << endl;

	cout << sizeof(students_scores) << endl;
	return 0;
}

배열 변수 자체는 0번째 배열의 주소를 가지고 있다.

그 뒤로는 변수형이 int 배열임에 따라 4bytes씩 늘어난다.

 

#include <iostream>
using namespace std;

void doSomething(int students_scores[20])
{
	cout << students_scores[0] << endl;
	cout << students_scores[1] << endl;
	cout << students_scores[2] << endl;
}
int main()
{
	const int num_students = 20;

	int students_scores[num_students] = { 1, 2, 3, 4, 5, };
	cout << students_scores[0] << endl;
	cout << students_scores[1] << endl;
	cout << students_scores[2] << endl;

	doSomething(students_scores);
	return 0;
}

배열을 함수의 파라미터로 넘길 수 있다.

 

그러나 원래 변수와 매개변수로 넘긴 변수의 주소가 다르다.

배열의 식별자=identifier는 내부적으로 자체가 주소로 사용된다.

배열의 경우는 그래서 (int)students_scores도 주소고, (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()
{
	const int num_students = 20;

	int students_scores[num_students] = { 1, 2, 3, 4, 5, };
	cout << (int)&students_scores << endl;
	cout << students_scores[0] << endl;
	cout << students_scores[1] << endl;
	cout << students_scores[2] << endl;

	doSomething(students_scores);
	return 0;
}

 

(int)&students_scores 는 포인터 변수의 주소.

(int)students_scores [0]로 그 포인터의 값을 찍을 수 있는 것.

(int)&students_scores [0]는 포인터 변수가 가리키고 있는 값의 주소를 가져옴.

 

sizeof(students_scores)의 경우 선언+초기화가 되어 있는 부분에선 80으로, 매개변수로 가져온 함수 안에선 4로 나옴.

(여기서 32바이트에선 포인터 변수의 사이즈가 4bytes지만, 64바이트에선 포인터 변수의 사이즈가 8bytes이다!!

메모리 주소가 더 늘어났기 때문)

 

 

void doSomething(int students_scores[])

//매개변수의 경우 이렇게만 정의 가능(어차피 포인터이기 때문)

 

 

6.3 배열과 반복문

 

 

#include <iostream>
using namespace std;

int main()
{
	const int num_students = 5;
	int score0 = 84;
	int score1 = 92;
	int score2 = 76;
	int score3 = 81;
	int score4 = 56;
	int total_score = score0 + score1 + score2 + score3 + score4;
	double avg_score = static_cast<double>(total_score) / num_students;
	//Note : double(total_score)/num_students!=double(total_score/num_students);

	//array로 사용할 시
	int scores[num_students] = { 84, 92, 76, 81, 56 };
	int total_scores = 0;
	for (int i = 0; i < num_students; ++i)
	{
		total_scores += scores[i];
	}
	double avg_scores = static_cast<double>(total_scores) / num_students;

	return 0;
}

 

#include <iostream>
using namespace std;

int main()
{
	int scores[] = { 84, 92, 76, 81, 56 };
	int total_score = 0;

	//개수를 오히려 이렇게 구할 수도 있다

	const int num_students = sizeof(scores) / sizeof(int);

	return 0;
}

 

#include <iostream>
using namespace std;

int main()
{
	int scores[] = { 84, 92, 76, 81, 56 };
	int total_score = 0;

	//개수를 오히려 이렇게 구할 수도 있다

	const int num_students = sizeof(scores) / sizeof(int);

	return 0;
}

 

 

6.4 배열과 선택 정렬 selection sort

 

 

#include <iostream>
using namespace std;

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

}
int main()
{
	const int length = 5;
	int array[length] = { 3, 5, 2, 1, 4 };
	printArray(array, length);

	for (int i = 0; i < length; i++)
	{
		int index = i;
		for (int j = i + 1; j < length; j++)
		{
			if (array[index] > array[j]) index = j;
		}
		int temp = array[index];
		array[index] = array[i];
		array[i] = temp;
	}
	printArray(array, length);
	return 0;
}
for (int startIndex = 0; startIndex < length - 1; ++startIndex)
	{
		int smallestIndex = startIndex;
		for (int currentIndex = startIndex + 1; currentIndex < length; ++currentIndex)
		{
			if (array[smallestIndex] > array[currentIndex])
				smallestIndex = currentIndex;
		}
		//std::swap(array[smallestIndex], array[startIndex]);
		{
			int temp = array[smallestIndex];
			array[smallestIndex] = array[startIndex];
			array[startIndex] = temp;
		}
	}

 

 

6.5 정적 다차원 배열 Multi-dimensional Array

int main()
{
	const int num_rows = 3;
	const int num_columns = 5;
	for (int row = 0; row < num_rows; ++row)
	{
		for (int col = 0; col < num_columns; ++col)
			cout << '[' << row << ']' << '[' << col << ']' << '\t';
		cout << endl;
	}
	cout << endl;
	return 0;
}

 

const int num_rows = 3;
	const int num_columns = 5;
	int array[num_rows][num_columns] =
	{
		{1, 2, 3, 4, 5},		// row 0
		{6, 7, 8, 9, 10},		// row 1
		{11, 12, 13, 14, 15}	// row 2
	};

	for (int row = 0; row < num_rows; ++row)
	{
		for (int col = 0; col < num_columns; ++col)
			cout << array[row][col] << '\t';
	}
	cout << endl;

 

내부적으로 주소는 위와 같다.

1차원인 것을 2차원인 것처럼 접어서 사용하게끔 해주는 것

 

int array[num_rows][num_columns] = 

   {1, 2, },

   {6, 7, 8, 9, 10},

    {11, 12, }

};

//요소 생략 가능, 0으로 자동 초기화

 

 

int array[][num_columns] =

   {1, 2, },

   {6, 7, 8, 9, 10},

    {11, 12, }

};

//행의 개수는 생략 가능, 그러나 열의 개수는 생략 불가.

 

int array[num_rows][num_columns] = { 0 };

//전부 0으로 초기화도 가능.

 

int main()
{
	int array[5][4][3];
	//3차원 배열도 가능.
	return 0;
}

 

 

 

6.6 C언어 스타일의 배열 문자열

 

#include <iostream>
using namespace std;

int main()
{
	char myString[] = "string";
	//6글자이지만 7자로 읽어진다. 왜냐하면 맨 끝에 
	//문자열의 끝을 알리는 null값이 들어가기 때문

	for (int i = 0; i < 7; ++i)
	{
		cout << myString[i] << " ";
		cout << (int)myString[i] << endl;
	}

	cout << sizeof(myString) / sizeof(myString[0]) << endl;
	return 0;
}

#include <iostream>
using namespace std;

int main()
{
	char myString[255];
	cin >> myString;
	cout << myString << endl;
	myString[0] = 'A';
	cout << myString << endl;
	myString[4] = '\0';
	cout << myString << endl;
	return 0;
}

문자열도 배열처럼 사용 가능.

입력도 받을 수 있다.

'\0'이 나오는 순간 문자열의 끝이라고 인식한다.

cin으로 입력받을 경우 space에서도 문자열이 끝나는 것처럼 인식한다.

 

cin.getline(myString, 255);

//위 방법으로 space도 받을 수 있다.

 

#include <iostream>
using namespace std;

int main()
{
	char myString[255];
	cin.getline(myString, 255);
	int ix = 0;
	while (true)
	{
		if (myString[ix] == '\0') break;
		cout << myString[ix] << " " << (int)myString[ix] << endl;
		++ix;
	}
	return 0;
}

 

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

int main()
{
	char source[] = "Copy this!";
	char dest[50];
	strcpy_s(dest, 50, source);

	cout << source << endl;
	cout << dest << endl;
	return 0;
}

strcpy는 source에서 dest로 문자열 복사.

중간에 있는 숫자는 dest의 문자열 개수

 

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

int main()
{
	char source[] = "Copy this!";
	char dest[50];
	strcpy_s(dest, 50, source);

	cout << source << endl;
	cout << dest << endl;

	strcat_s(dest, source);
	cout << source << endl;
	cout << dest << endl;

	cout << strcmp(source, dest) << endl;
	return 0;
}

strcat은 source의 문자열을 dest 뒤에 이어 붙인다.

strcmp는 두 문자열을 비교한다. 같으면 0, 다르면 -1 return

 

 

6.7 포인터의 기본적인 사용법

 

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

int main()
{
	//지역변수는 스택 메모리를 사용하고
	//동적 할당 메모리는 힙메모리를 사용함
	
	//큰 메모리에 저장되어 있는 데이터 중에서 일부를
	//CPU가 사용하기 위해 메모리로부터 가져올 땐
	//메모리 전체를 모두 뒤지면서 찾는 것이 아니라
	//필요한 데이터가 저장되어 있는 주소를 이용해
	//직접 접근해 가져오는 것.

	int x = 5;
	cout << x << endl;
	return 0;
}

모든 변수는 다 주소를 가지고 있다.

cout<< &x<<endl; //메모리 주소 출력 가능

&: address-of operator

기본적으론 16진수로 출력됨.

 

메모리 주소를 담는 변수가 바로 포인터.

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

int main()
{
	//de-reference operator(*)
	//reference : 간접적으로 얘기한다는 개념, 포인터는 reference의 하위 개념
	int x = 5;
	cout << *(&x) << endl;
	//메모리에 담겨 있는 값을 들여다보는 것.
	return 0;
}

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

int main()
{
	int x = 5;
	
	typedef int* pint;
	//int *ptr_x = &x, ptr_y = &x; //전자만 포인터
	//int *ptr_x = &x, *ptr_y = &x; //둘다 포인터
	pint ptr_x = &x, ptr_y = &x; //둘다 포인터
	return 0;
}
#include <iostream>
#include <cstring>
using namespace std;

int main()
{
	int x = 5;
	int* ptr_x = &x, * ptr_y = &x;
	//포인터에 저장되는 것은 주소이다.
	cout << ptr_x << endl;
	cout << *ptr_x << endl; //포인터에 들어 있는 값을 가져올 때
	return 0;
}

 

 

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

int main()
{
	int x = 5;
	int* ptr_x = &x;
	cout << typeid(ptr_x).name() << endl;
	return 0;
}

 

 

 

 

 

포인터 자체의 사이즈는 고정이다.

포인터도 형이 필요한 이유는 이것을 de-referencing(원래 값을 가져올 때)의 형을 결정하기 위해서이다.

 

그래서 double d = 123.0;

의 경우, int * 형으로 포인터 선언을 할 수 없음. //double *ptr_d = &d; //는 가능.

 

포인터가 초기화가 되지 않았을 때 발생하는 오류가 잦다.

 

 

6.7 널 포인터 Null pointer

 

포인터가 쓰레기값을 가질 때, 이상한 메모리에서 값을 참조할 수 있다.

그럴 떄 오류를 발생시키는데 이를 방지하는 방법 중 하나가 널 포인터이다.

 

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

int main()
{
	double* ptr = NULL; // c-style
	double* ptr2 = nullptr; //modern c++
	//double *ptr {0}; //double *ptr{nullptr}; 둘 다 가능
	if (ptr2 != nullptr)
	{
		//do something userful
	}
	else
	{
		//do nothing with ptr
	}
	return 0;
}
#include <iostream>
#include <typeinfo>
using namespace std;

void doSomething(double* ptr)
{
	if (ptr != nullptr)
	{
		cout << *ptr << endl;
	}
	else
	{
		cout << "Null ptr, do nothing" << endl;
	}
}
int main()
{
	double* ptr{ nullptr };
	doSomething(ptr);
	doSomething(nullptr);
	double d = 123.4;
	doSomething(&d);
	return 0;
}

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

void doSomething(double* ptr)
{
	if (ptr != nullptr)
	{
		cout << *ptr << endl;
	}
	else
	{
		cout << "Null ptr, do nothing" << endl;
	}
}
int main()
{
	double* ptr{ nullptr };
	doSomething(ptr);
	doSomething(nullptr);
	double d = 123.4;
	doSomething(&d);

	nullptr_t nptr; //파라미터 중 null pointer만 받을 때 이 형도 쓸 수 있음.
	return 0;
}
#include <iostream>
#include <cstddef>
using namespace std;

void doSomething(double* ptr)
{
	cout << "address of pointer variable in doSomething() " << &ptr << endl;
	if (ptr != nullptr)
	{
		cout << *ptr << endl;
	}
	else
	{
		cout << "Null ptr, do nothing" << endl;
	}
}
int main()
{
	double* ptr{ nullptr };
	doSomething(ptr);
	doSomething(nullptr);
	double d = 123.4;
	doSomething(&d);
	ptr = &d;
	doSomething(ptr);
	cout << "address of pointer variable in main() " << &ptr << endl;
	return 0;
}

매개변수로 들어온 포인터도 메모리 값이 다시 복사된다.

 

6.8 포인터와 정적 배열

 

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

int main()
{
	int array[5] = { 9, 7, 5, 3, 1 };
	cout << array << endl;
}

array와 &array[0] 값은 같다. 배열은 기본적으로 포인터 역할을 한다.

 

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

int main()
{
	int array[5] = { 9, 7, 5, 3, 1 };
	cout << *array << endl;
	char name[] = "jackjack";
	cout << name << endl;
	cout << *name << endl;
}

정적 어레이도 결국 포인터이다.

 

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

int main()
{
	int array[5] = { 9, 7, 5, 3, 1 };
	int* ptr = array;
	cout << ptr << endl;
	cout << array << endl;
	cout << *ptr << endl;

}

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

int main()
{
	int array[5] = { 9, 7, 5, 3, 1 };
	cout << sizeof(array) << endl;
	int* ptr = array;
	cout << sizeof(ptr) << endl;
	return 0;
}

포인터처럼 사용되지만 배열과 포인터는 내부적으로 약간 다르다.

 

#include <iostream>
using namespace std;
void printArray(int array[]) {
	cout << sizeof(array) << endl;
}
int main()
{
	int array[5] = { 9, 7, 5, 3, 1 };
	cout << sizeof(array) << endl; //20
	int* ptr = array;
	cout << sizeof(ptr) << endl; //4
	printArray(ptr);
	printArray(array);
	return 0;
}

void printArray(int array[]) = void printArray(int *array)

매개변수로 들어가는 배열은 포인터 변수가 된다.

 

 

#include <iostream>
using namespace std;
void printArray(int array[]) {
	cout << sizeof(array) << endl;
	*array = 100;
}
int main()
{
	int array[5] = { 9, 7, 5, 3, 1 };
	cout << sizeof(array) << endl; //20
	int* ptr = array;
	cout << sizeof(ptr) << endl; //4
	printArray(ptr);
	printArray(array);
	cout << array[0] << " " << *array << endl;
	return 0;
}

함수 바깥에서도 값을 바꿀 수 있다.

 

#include <iostream>
using namespace std;
struct MyStruct
{
	int array[5] = { 9, 7, 5, 3, 1 };
};

void doSomething(MyStruct ms)
{
	cout << sizeof(ms.array) << endl;
}

void doSomethingWithPointer(MyStruct *ms)
{
	cout << sizeof((*ms).array) << endl;
}
int main()
{
	MyStruct ms;
	cout << ms.array[0] << endl;
	cout << sizeof(ms.array) << endl;

	doSomething(ms);
	doSomethingWithPointer(&ms);
	return 0;
}

 

array가 스트럭쳐나 클래스 안에 들어 있을 경우 포인터로 변환하지 않는다.

배열 자체가 간다.

 

6.9 포인터 연산과 배열 인덱싱

 

#include <iostream>
using namespace std;

int main()
{
	int value = 7;
	int* ptr = &value;
	cout << uintptr_t(ptr) << endl;
	cout << uintptr_t(ptr + 1) << endl;
	cout << uintptr_t(ptr + 2) << endl;
	cout << uintptr_t(ptr - 1) << endl;
	//포인터 저장을 할 수 있는 uintptr_t

	double value2 = 7.0;
	double* ptr2 = &value2;
	cout << uintptr_t(ptr2) << endl;
	cout << uintptr_t(ptr2 + 1) << endl;
	cout << uintptr_t(ptr2 + 2) << endl;
	cout << uintptr_t(ptr2 - 1) << endl;

	return 0;
}

double pointer는 +1마다 8bytes씩, int pointer는 +1마다 4bytes씩 변함

 

#include <iostream>
using namespace std;

int main()
{
	int array[] = { 9, 7, 5, 3, 1 };
	cout << array[0] << " " << (uintptr_t) & array[0] << endl;
	cout << array[1] << " " << (uintptr_t) & array[0] << endl;
	cout << array[2] << " " << (uintptr_t) & array[0] << endl;
	cout << array[3] << " " << (uintptr_t) & array[0] << endl;
	return 0;
}

 

#include <iostream>
using namespace std;

int main()
{
	int array[] = { 9, 7, 5, 3, 1 };
	
	for (int i = 0; i < 5; ++i)
		cout << array[i] << " " << (uintptr_t) & array[i] << endl;
	return 0;
}

위도 같은 결과가 나온다.

 

#include <iostream>
using namespace std;

int main()
{
	int array[] = { 9, 7, 5, 3, 1 };
	int* ptr = array;
	for (int i = 0; i < 5; ++i)
	{
		cout << array[i] << " " << (uintptr_t) & array[i] << endl;
		cout << *(ptr + i) << (uintptr_t)(ptr + i) << endl;
	}
	return 0;
}

#include <iostream>
using namespace std;

int main()
{
char name[] = "Jack jack";
const int n_name = sizeof(name) / sizeof(name[0]);

for (int i = 0; i < n_name; ++i)
{
cout << *(name + i) << " ";
}
cout << endl;
return 0;
}

 

#include <iostream>
using namespace std;

int main()
{
	char name[] = "Jack jack";
	const int n_name = sizeof(name) / sizeof(name[0]);
	char* ptr = name;
	for (int i = 0; i < n_name; ++i)
	{
		cout << *(ptr + i) << " ";
	}
	cout << endl;
	return 0;
}

 

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

#include <iostream>
using namespace std;

int main()
{
	//char name[] = "Jack jack";
	char* name = "Jack Jack";
	//이것은 작동하지 않는다. 
	//오른쪽은 리터럴, 왼쪽은 포인터.
	//실제로 잭잭이 담길 메모리를 어디서 만들 것인가에 대한 정보가 없다.

	return 0;
}

포인터엔 주소만 담을 수 있으므로 실제로 저 문자열을 어디에 담을 것인가에 대해 불분명하다.

#include <iostream>
using namespace std;

int main()
{
	//char name[] = "Jack jack";
	const char* name = "Jack jack";
	return 0;
}

문자열을 기호적 상수처럼 사용할 수 있다(const 붙이면)

메모리를 통해 접근할 수 있도록 컴파일러가 도와줌.

 

#include <iostream>
using namespace std;

int main()
{
	//char name[] = "Jack jack";
	const char* name = "Jack jack";
	const char* name2 = "Jack jack";
	cout << (uintptr_t)name << endl;
	cout << (uintptr_t)name2 << endl;
	return 0;
}

컴파일러가 어딘가에 문자열이 담길 장소를 const에 대해선 만들어주므로,

같은 문자열에 대해선 같은 주소를 참조하게 된다.

 

#include <iostream>
using namespace std;

const char* getName()
{
	return "Jackjack";
}
int main()
{
	//char name[] = "Jack jack";
	const char* name = getName();
	const char* name2 = getName();
	cout << (uintptr_t)name << endl;
	cout << (uintptr_t)name2 << endl;
	return 0;
}

위도 똑같은 결과. return에 대해서도 적용 가능한 것이다.

 

#include <iostream>
using namespace std;


int main()
{
	int int_arr[] = { 1, 2, 3, 4, 5 };
	char char_arr[] = "Hello, World!";
	const char* name = "Jack Jack";
	cout << int_arr << endl;
	cout << char_arr << endl;
	cout << name << endl;
	return 0;
}

cout에서 문자열은 특별히 처리함.

문자의 포인터는 C 스타일의 문자열 배열이 가능성이 높다고 생각하고,

널이 나올 떄까지 배열을 쭉 출력해준다.

 

#include <iostream>
using namespace std;


int main()
{
	char c = 'Q';
	cout << &c << endl;
	return 0;
}

 

문자열 주소는 무조건 배열처럼 출력하기 때문에,

널 값이 없을 경우 쓰레기값이 출력될 수 있다.

 

 

6.10 메모리 동적 할당 Dynamic Memory Allocation. new와 delete

 

메모리 크기를 초과하는 너무 큰 크기의 배열은 선언이 불가.

 

메모리에서 정적 할당 메모리는 스택에 들어감, 스택은 용량이 적음.

힙도 있는데, 힙은 동적 메모리가 할당되는 장소임.

이런 문제 때문에 동적 할당이 중요함.

 

#include <iostream>
using namespace std;


int main()
{
	//os에게 integer 하나 만큼의 사이즈를 달라고 요청하는 것.
	int* ptr = new int;
	//os로부터 int만큼 메모리를 받아 그 주소를 할당함

	*ptr = 7; //int var; var= 7; 까지와 동일.

	cout << ptr << endl;
	cout << *ptr << endl;
	return 0;
}

#include <iostream>
using namespace std;


int main()
{
	//os에게 integer 하나 만큼의 사이즈를 달라고 요청하는 것.
	int* ptr = new int{ 7 };
	cout << ptr << endl;
	cout << *ptr << endl;

	delete ptr;
	return 0;
}

프로그램이 끝나면 OS가 알아서 메모리를 거둬가므로 적은 용량에선 delete를 굳이 안써도 된다.

delete를 사용하면 OS가 거둬가기 전에 알아서 메모리를 반납하는 것.

 

delete 후에 cout<<*ptr<<endl;를 하면 이상한 값이 나온다.

주소는 알고 있지만, 해당 주소의 값에 가면 쓰레기값이 나올 수 있다.

 

#include <iostream>
using namespace std;


int main()
{
	//os에게 integer 하나 만큼의 사이즈를 달라고 요청하는 것.
	int* ptr = new int{ 7 };
	cout << ptr << endl;
	cout << *ptr << endl;

	delete ptr;
	ptr = nullptr;
	//delete 후에 null을 넣어주는 것이 안전하다.
	 
	if (ptr != nullptr) //if(ptr)과 동일
	{
		cout << ptr << endl;
		cout << *ptr << endl;
	}
	return 0;
}
#include <iostream>
using namespace std;


int main()
{
	int* ptr = new (std::nothrow)int{ 7 };
	return 0;
}

가령 다른 프로그램에서 메모리를 모두 사용할 경우 새 메모리 할당에서 에러가 발생하는 경우가 있다.

이럴 경우 nothrow를 지정하면 에러가 발생하지 않는다.

#include <iostream>
using namespace std;


int main()
{
	int* ptr = new (std::nothrow)int{ 7 };
	int* ptr2 = ptr;

	delete ptr;
	ptr = nullptr;
	//*ptr2 도 에러를 발생시킨다.
	// ptr2에도 nullptr을 넣을 수 있지만 번거롭다.
	//최근에는 스마트 포인터가 있어 프로그래머가 번거롭지 않도록 해준다.

	return 0;
}
#include <iostream>
using namespace std;


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

위는 말도 안되는 코드.

동적 할당할 때 메모리를 지우지 않으면 곤라해질 수 있다.

 

진단 툴 : Debug > tool > 진단도구 실행

 

6.12 동적 할당 배열 Dynamically Allocating Arrays

 

#include <iostream>
using namespace std;


int main()
{
	int length;
	cin >> length;
	int* array = new int[length];
	
	array[0] = 1;
	array[1] = 2;

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

	delete[] array;
	return 0;
}

#include <iostream>
using namespace std;


int main()
{
	int length;
	cin >> length;
	int* array = new int[length]();
	
	array[0] = 1;
	array[1] = 2;

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

	delete[] array;
	return 0;
}

0으로 전부 초기화

int *array= new int[length]{}; //모두 0으로 초기화

int *array = new int[length] {11, 12, 13, 14, 15};

//모두 0으로 초기화

//여기서 새로 입력받는 length가 지정된 원소 개수보다 적으면 에러 발생.

 

#include <iostream>
using namespace std;


int main()
{
	int fixedArray[] = { 1, 2,3, 4, 5 };
	int* array = new int[] {1, 2, 3, 4, 5};

	delete[] array;
	return 0;
}

빌드가 되지 않는다. 컴파일 타임에 결정하지 않으려는 것.

 

 

6.13 포인터와 const

 

#include <iostream>
using namespace std;


int main()
{
	const int value = 5;
	const int* ptr = &value;
	//*ptr = 6; //de-referencing도 불가

	cout << *ptr << endl;
	return 0;
}
#include <iostream>
using namespace std;


int main()
{
	int value = 5;
	const int* ptr = &value;
	int value2 = 6;
	ptr = &value2;
	cout << *ptr << endl;
	//const pointer라도 다른 변수를 받는 건 되지만
	// *ptr = 100; //값 변경은 불가하다
	return 0;
}
#include <iostream>
using namespace std;


int main()
{
	int value = 5;
	int* const ptr = &value;
	*ptr = 10;
	cout << *ptr << endl;
	int value2 = 8;
	// ptr = &value2; //진정한 const 포인터를 만드는 행위
	return 0;
}
#include <iostream>
using namespace std;


int main()
{
	int value = 5;
	const int* const ptr = &value;
	//const가 두번 붙었다. 값 변경도 안되고 초기화와 동시에 선언해주어야 함.
	return 0;
}

함수 parameter로 넣을 때 가끔 사용한다(안전하게 코딩할 때)

 

6. 14 참조 변수 reference variable

 

#include <iostream>
using namespace std;


int main()
{
	int value = 5;
	int* ptr = nullptr;
	ptr = &value;

	int& ref = value; //같은 메모리인 것처럼 작동한다
	ref = 10;
	cout << value << " " << ref << endl;
	return 0;
}

 

참조는 별명, 또 다른 이름인 것처럼 사용할 수 있다.

#include <iostream>
using namespace std;


int main()
{
	int value = 5;
	int* ptr = nullptr;
	ptr = &value;

	int& ref = value;
	cout << &value << endl;
	cout << &ref << endl;
	cout << ptr << endl;
	cout << &ptr << endl;
	return 0;
}

 

참조 변수는 꼭 초기화가 되어야 한다.

//int &ref; //불가능

//int &ref = 4; //literal 불가

//const int y = 8;

//int &ref = y; //const는 그냥 reference 변수에 넣을 수 없다.

 

//const &ref = y; //이건 가능.

 

#include <iostream>
using namespace std;


int main()
{
	int value = 5;
	int value2 = 10;
	int& ref1 = value;
	cout << ref1 << endl;
	ref1 = value2;
	cout << ref1 << endl;
	return 0;
}

 

 

재할당도 가능.

 

#include <iostream>
using namespace std;

void doSomething(int n)
{
	n = 10;
	cout << "In doSomething " << n << endl;
}
int main()
{
	int n = 5;
	cout << n << endl;
	doSomething(n);
	cout << n << endl;
	return 0;
}

함수의 매개변수 안에 들어가는 변수는 복사되어 들어가는 값.

그래서 함수 안에서 값이 바뀌어도 실제 변수는 바뀌지 않는다.

 

#include <iostream>
using namespace std;

void doSomething(int &n)
{
	cout << &n << endl;
	n = 10;
	cout << "In doSomething " << n << endl;
}
int main()
{
	int n = 5;
	cout << n << endl;
	doSomething(n);
	cout << n << endl;
	cout << &n << endl;
	return 0;
}

 

포인터 변수는 주소가 달라지지만 참조 변수의 경우 주소도 같아진다.

아예 변수 자체가 넘어가는 것!

효율이 더 좋아진다.

 

#include <iostream>
using namespace std;

void doSomething(const int &n)
{
	cout << &n << endl;
	//n = 10; 수정 가능
	cout << "In doSomething " << n << endl;
}
int main()
{
	int n = 5;
	cout << n << endl;
	doSomething(n);
	cout << n << endl;
	cout << &n << endl;
	return 0;
}
#include <iostream>
using namespace std;

void printElements(int(&arr)[5])
{
	for (int i = 0; i < 5; ++i)
	{
		cout << arr[i] << " ";
	}
	cout << endl;
}
int main()
{
	const int length = 5;
	int arr[length] = { 1, 2, 3, 4, 5 };
	printElements(arr);
	return 0;
}

 

배열을 참조로 넘길 경우

배열 크기 명시해야 함

#include <iostream>
using namespace std;

struct Something
{
	int v1;
	float v2;
};

struct Other
{
	Something st;
};
int main()
{
	Other ot;
	ot.st.v1 = 1.0; //이런 식의 접근은 불편하다.

	int& v1 = ot.st.v1;
	v1 = 1; //이렇게 하는 방법도 가능!
	return 0;
}

 

구조체에서 변수의 접근자가 길어지는 경우에 참조 변수가 유용하다.

 

6.15 참조와 const

 

int main()
{
	int  x = 5;
	const int& ref_x = x;
	//이럴 경우 ref_x의 참조를 더 못 바꾼다.

	const int y = 5;
	const int& ref_y = y; //const끼리는 가능.
	return 0;
}

reference는 literal을 넣을 수 없지만

const reference는 literal이 가능하다.

 

const int &ref_x = 7; //가능

 

#include <iostream>
using namespace std;

void doSomething(const int &x)
{
	cout << x << endl;

}
int main()
{
	doSomething(1);
	return 0;
}

이렇게 함수 매개변수에 const 참조 변수를 넣으면 바로 상수를 넣는 것도 가능해진다!

 

6.16 포인터와 참조의 멤버 선택

#include <iostream>
using namespace std;

struct Person
{
	int age;
	double weight;
};
int main()
{
	Person person;
	person.age = 5;
	person.weight = 30;

	Person& ref = person;
	ref.age = 15; //이렇게 바꿀 수 있음

	Person* ptr = &person;
	ptr->age = 30; //포인터의 경우 이렇게!
	(*ptr).age = 20; //*를 하면 레퍼런스처럼 작용

	Person& ref2 = *ptr;
	ref2.age = 45;

	std::cout << &person << std::endl;
	std::cout << &ref2 << std::endl;

	return 0;
}

 

6.17 C++11 For-each 반복문

 

#include <iostream>
using namespace std;

int main()
{
	int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
	for (int number : fibonacci)
		cout << number << " ";
	cout << endl;

	//change array values;
	for (int number : fibonacci)
		number = 10; 
	//안된다.

	for (auto &number : fibonacci)
		number = 10;
	//멤버의 값이 바뀜(레퍼런스로 해야)
	// --내부에서 파라미터처럼 들어가기 떄문!

	//단순 출력 시에는 const를 붙여주는 것이 좋다.
	for (const auto  number : fibonacci)
		cout << number << " ";
	cout << endl;
	return 0;
}

#include <iostream>
#include <limits>
#include <algorithm>
using namespace std;

int main()
{
	int fibonacci[] = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
	int max_number = std::numeric_limits<int>::lowest();
	for (const auto& n : fibonacci)
		max_number = std::max(max_number, n);
	cout << max_number << endl;
	return 0;
}

#include <iostream>
#include <limits>
#include <algorithm>
#include <vector>
using namespace std;

int main()
{
	vector<int> fibonacci = { 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89 };
	int max_number = std::numeric_limits<int>::lowest();
	for (const auto& n : fibonacci)
		max_number = std::max(max_number, n);
	cout << max_number << endl;
	return 0;
}

 

6.18 void 포인터

 

 

#include <iostream>
using namespace std;

int main()
{
	//void pointer = generic pointer
	//모든 형의 포인터를 포괄적으로 다룰 수 있다.

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

	void* ptr = nullptr;
	ptr = &i;
	ptr = &f;
	ptr = &c;
	//보이드 포인터도 주소 자체는 가질 수 있다.

	//cout << ptr + 1 << endl; //불가
	//+1을 할 때 몇 bytes를 해야할지 모르는 것. 그래서 포인터 연산은 불가.
	return 0;
}

//de-referencing도 안됨.

//*ptr <--이런 식으로

//형을 모르기 때문에.

 

//강제로 가져올 땐 casting해야 한다.

*static_cast<float*>(ptr)

 

다형성 구현을 하면 이렇게 구현을 할 때가 있다.

 

#include <iostream>
using namespace std;

enum Type {
	INT,
	FLOAT,
	DOUBLE,
};
int main()
{
	Type type = FLOAT;
	if(type==FLOAT)
		//float 캐스팅
		//형 별로 캐스팅
	return 0;
}

 

6.19 다중 포인터와 동적 다차원 배열

#include <iostream>
using namespace std;

int main()
{
	int* ptr = nullptr;
	int** ptrptr = nullptr;

	int value = 5;
	ptr = &value;
	ptrptr = &ptr;

	cout << ptr << " " << *ptr << " " << &ptr << endl;
	cout << ptrptr << " " << *ptrptr << " " << &ptrptr << endl;
	cout << **ptrptr << endl;
	return 0;
}

#include <iostream>
using namespace std;

int main()
{
	const int row = 3;
	const int col = 5;
	const int s2da[row][col] =
	{
		{1, 2, 3, 4, 5},
		{6, 7, 8, 9, 10},
		{11, 12, 13, 14, 15}
	};

	int* r1 = new int[col] {1, 2, 3, 4, 5};
	int* r2 = new int[col] {6, 7, 8, 9, 10} ;
	int* r3 = new int[col] {11, 12, 13, 14, 15};

	int** rows = new int* [row] {r1, r2, r3};
	for (int r = 0; r < row; ++r)
	{
		for (int c = 0; c < col; ++c)
			cout << rows[r][c] << " ";
		cout << endl;
	}
	return 0;
}

 

 

 

#include <iostream>
using namespace std;

int main()
{
	const int row = 3;
	const int col = 5;
	const int s2da[row][col] =
	{
		{1, 2, 3, 4, 5},
		{6, 7, 8, 9, 10},
		{11, 12, 13, 14, 15}
	};
	int** matrix = new int* [row];

	for (int r = 0; r < row; ++r)
		matrix[r] = new int[col];

	for (int r = 0; r < row; ++r)
	{
		for (int c = 0; c < col; ++c)
			matrix[r][c] = s2da[r][c];
		cout << endl;
	}

	for (int r = 0; r < row; ++r)
	{
		for (int c = 0; c < col; ++c)
			cout << matrix[r][c] << " ";
		cout << endl;
	}

	for (int r = 0; r < row; ++r)
		delete[] matrix[r];

	delete[] matrix;
	return 0;
}

#include <iostream>
using namespace std;

int main()
{
	const int row = 3;
	const int col = 5;
	const int s2da[row][col] =
	{
		{1, 2, 3, 4, 5},
		{6, 7, 8, 9, 10},
		{11, 12, 13, 14, 15}
	};
	int* matrix = new int[row * col];
	//구부려서 사용하기

	for (int r = 0; r < row; ++r)
		for (int c = 0; c < col; ++c)
			matrix[c + col * r] = s2da[r][c];

	for (int r = 0; r < row; ++r)
	{
		for (int c = 0; c < col; ++c)
			cout << matrix[c + col * r] << " ";
		cout << endl;
	}

	delete[] matrix;
	return 0;
}

 

 

 

 

6.20 std::array 소개

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

void printLength(array<int, 5> my_arr)
{
	cout << my_arr.size() << endl;
	//그대로 들어온다.
}
int main()
{
	array<int, 5> my_arr = { 1, 2, 3, 4, 5 };
	cout << my_arr[0] << endl;
	cout << my_arr.at(0) << endl;
	//at은 인덱스를 넘어가는 거면 에러 처리를 할 수 있다.
	//약간 느릴 수는 있다.

	cout << my_arr.size() << endl; //사이즈
	printLength(my_arr);
	return 0;
}

 

 

 

#include <iostream>
#include <array>
#include <algorithm>
using namespace std;

void printLength(array<int, 5> my_arr)
{
	cout << my_arr.size() << endl;
	//그대로 들어온다.
}
int main()
{
	array<int, 5> my_arr = { 1 , 21, 3, 40, 5};
	for (auto &element : my_arr)
		cout << element << " ";
	cout << endl;
//참조 변수 형이면 복사하는 과정이 더 빠르다.

	//sorting도 라이브러리에 들어가 있음.
	std::sort(my_arr.begin(), my_arr.end());

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

 

 

// 여기서 역순 정렬

std::sort(my_arr.rbegin(), my_arr.rend());

위는 정적 배열을 대체할 수 있다.

 

6.21 std::vector 소개

이것은 동적 배열을 대체할 수 있다.

 

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

int main()
{
	vector<int> array;
	vector<int> array2 = { 1, 2, 3, 4, 5 };
	cout << array2.size() << endl;
	vector<int> array3 = { 1, 2, 3, };
	cout << array3.size() << endl;
	vector<int> array4{ 1, 2, 3, };
	cout << array4.size() << endl;
	return 0;
}

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

int main()
{
	vector<int> arr = { 1, 2,3  , 4, 5 };
	for (auto& iter : arr)
		cout << iter << " ";
	cout << endl;
	cout << arr[1] << endl;
	cout << arr.at(1) << endl;

	//vector는 알아서 delete가 된다.
	return 0;
}

arr.size()로 사이즈도 쉽게 알 수 있음.

arr.resize(10); //리사이징도 가능.