따라하며 배우는 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); //리사이징도 가능.
'개발 공부 > C++' 카테고리의 다른 글
따라하며 배우는 C++ 8. 객체지향의 기초 (0) | 2020.07.01 |
---|---|
따라하며 배우는 C++ 7. 함수 (0) | 2020.07.01 |
따라하며 배우는 C++ 5. 흐름제어 (0) | 2020.06.30 |
따라하며 배우는 C++ 4. 변수 범위와 더 다양한 변수형 (0) | 2020.06.29 |
따라하며 배우는 C++ 3. 연산자들 (0) | 2020.06.29 |