10-01. 배열과 메모리
자료형의 크기만큼 메모리가 계산된다!
10-02. 배열의 기본적인 사용방법
#define MONTHS 12 //symbolic constant, macro
int main()
{
int high[MONTHS] = {2,5,11,18,23,27,29,30,26,20,12,4};
// Address
printf("%p %p \n", high, &high[0]);
for (int i =0; i < MONTHS; ++i)
printf("%lld \n", (long long)&high[i]); // 10진수로 출력하여 4씩 증가하는지 확인(int이므로)
printf("\n");
///* complier doesn't check whether indices are valid!! */
// high[12] = 4; //compiler는 에러를 출력하지 않는다!! -> 나중에 Runtime Error를 발생시킴
/* const and array */
const int low[12] = { -7, -5, 1, 7, 13, 18, 22, 22, 16, 9, 2, -5};
// low[]를 바꾸지 못하게 할 수 있다!!
/* omitting size */
const int power_of_tons[] = {1,2,4,8,16,32,64}; // 다음과 같이, []내에 수를 지정하지 않더라도 컴파일러가 이를 count해준다.
printf("%d \n", sizeof(power_of_tons)); // 하지만 동적할당에서는 이렇게 하면 안된다!
printf("%d \n", sizeof(int));
printf("%d \n", sizeof(power_of_tons[0]));
for (int i =0; i< sizeof power_of_tons / sizeof power_of_tons[0]; ++i)
printf("%d ", power_of_tons[i]);
printf("\n");
/* Designated initalizers */
int days[MONTHS] = {31,28, [4] = 31,30,31, [1] =29}; // array의 첫번째 값을 29로 지정해줄 수 있다! // 지정되지 않은 값들은 0으로 채워진다.
for (int i = 0; i<MONTHS; ++i)
printf("%d ", days[i]);
}
10-03. 포인터의 산술 연산(Pointer Arithmetic)
int main()
{
int* ptr= 0; // try double*, long long*, char*, void*
// 원래는 NULL을 넣는 것이 맞지만, 이번 강의에서 포인터의 이해를 돕기 위해 0으로 지정함. 추후에 와닿을 것
// 절대 평소에는 0을 넣지 말것
printf("%p %lld \n", ptr, (long long)ptr);
ptr ++; //try -=, ++, --, -, +
// 1이 아니라! 자료형의 사이즈만큼 더해주게 된다!!
// 즉, 주소값에 어떤 값을 더해주는 게 아니라 자료형에 맞춰서 건너뛰어간다는 의미가 크다
printf("%p %lld \n", ptr, (long long)ptr);
// 단항연산자는 작동하지 않는다!!
//ptr = -ptr; 주소의 값을 음수로 바꾼다는 것 자체가 의미가 없다
//ptr = +ptr;
/*substraction*/
double arr[10];
double* ptr1= &arr[3], * ptr2 = &arr[5];
// ptr2 = ptr1 + ptr2; //not working: cannot add two pointer
int i = ptr2 - ptr1; //working -> meaning: 두 메모리 주소 차이가 두 칸이라는 것!!
// e.g. 아파트 301호와 303호 비교하기
printf("%lld %lld %d \n", (long long)ptr1, (long long)ptr2, i);
// 메모리 주소간 차이는 16 -> double은 8 -> 16 / 8 = 2라는 결과가 나옴(주어진 주소값에 인덱스 차이가 2)
return 0;
}
10-04. 포인터와 배열
앞 강의에서 포인터의 덧셈/뺄셈은 자료형에 맞는 인덱스 칸을 움직이는 것이라고 하였다.
ptr을 더해주고, 이들의 인덱스가 출력하는 값이 같은지를 살펴보자.
int main()
{
int arr[10];
int num = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < num ; ++i)
arr[i] = (i+1)*100;
int* ptr = arr;
printf("%p %p %p \n", ptr, arr, &arr[0]); // 세 값이 같음!
ptr += 2;
printf("%p %p %p \n", ptr, arr+2, &arr[2]); // arr+2 = &arr[2] 값 동일!
// Note : arr += 2; // invalid
printf("%d %d %d\n", *(ptr+1), *(arr+3), arr[3]); // All the same
/// warning
//printf("%d %d %d \n", *ptr + 1, *arr + 3, arr[3]); // 301, 103, 400 // they are all different!!
///
for (int i = 0, *ptr = arr; i<num; ++i)
{
printf("%d %d \n", *ptr++, arr[i]);
// printf("%d %d \n", *(ptr + i), arr[i]); // 위 식과 동일함
}
}
마무리: 행복으로 가는 한 쪽 문이 닫히면 다른 쪽 문이 열린다. 그러나 우리는 때때로 닫힌 문 만을 쳐다보느라 열린 쪽을 오랫동안 못 보기도 한다.
Helen Keller
10-05. 2차원 배열과 메모리
- Tip: "3개 짜리가 2개 있다"로 이해하면 외우기 편하다!
또한, 메모리는 어차피 1차원 배열이다!!
int main()
{
int arr[2][3] = {{1,2,3},
{4,5,6}};
for (int j =0; j < 2; ++j)
{
for (int i = 0; i<3; ++i)
printf("%d ", arr[j][i]);
printf("\n");
}
printf("\n");
//Note : inner loop uses i. why?
int *ptr = &arr[0][0];
for (int k =0; k< 6; ++k)
printf("%d ", ptr[k]);
printf("\n\n");
printf("%zd %zd \n", sizeof(arr), sizeof(arr[0]));
printf("\n");
/* 3D array */
int arr_3d[2][3][4] = {
{
{000, 001, 002, 003},
{010, 011, 012, 013},
{020, 021, 022, 023},
},
{
{100, 101, 102, 103},
{110, 111, 112, 113},
{120, 121, 122, 123},
},
};
for (int k=0; k<2; ++k)
{
for (int j = 0; j<3; ++j)
{
for (int i =0; i<4; ++i)
printf("%d ", arr_3d[k][j][i]);
printf("\n");
}
printf("\n");
}
return 0;
}
10-06. 2차원 배열 연습문제
#define MONTHS 12
#define YEARS 3
int main()
{
double year2016[MONTHS] = {1,2,3,4,5,6,7,8,9,10,11,12};
double year2017[MONTHS] = {13,14,15,16,17,18,19,20,21,22,23,24};
double year2018[MONTHS] = {25,26,27,28,29,30,31,32,33,34,35,36};
/* Your code */
// 1. Use 2D array
int year_all[YEARS][MONTHS] = {
{1,2,3,4,5,6,7,8,9,10,11,12},
{13,14,15,16,17,18,19,20,21,22,23,24},
{25,26,27,28,29,30,31,32,33,34,35,36},
};
// 2. Print array
printf("[Temperature Data]\n");
printf("Year Index: ");
for (int i = 1 ; i <= MONTHS; ++i)
{
printf("\t%d ", i);
}
printf("\n");
for (int y = 0; y < YEARS; ++y)
{
printf("Year %d : ", y);
for (int m= 0; m < MONTHS; ++m)
{
printf("\t%.1d ", year_all[y][m]);
}
printf("\n");
}
// 3. yearly avg temperatures of 3 years
printf("[Yearly average temperatures of 3 years] \n");
for (int y=0; y<YEARS; ++y)
{
double avg_temp = 0.0;
for (int m= 0; m < MONTHS; ++m)
avg_temp += year_all[y][m];
avg_temp /= (double)MONTHS;
}
printf("\n");
// 4. Monthly avg temperatures for 3 years
printf("[Monthly average temperatures for 3 years]\n");
printf("motth index : ");
for (int m = 0; m < MONTHS; ++m)
{
printf("\t%d ", m + 1);
}
printf("\n");
printf("avg temps : ");
for (int m = 0; m<MONTHS; ++m)
{
double avg_temp = 0;
for (int y=0; y < YEARS; ++y)
{
avg_temp += year_all[y][m];
}
avg_temp /= (double)YEARS;
printf("\t%.1f ", avg_temp);
}
printf("\n");
}
10-07. 배열을 함수에게 전달해주는 방법
double average(double *arr1, int n) // 함수에서는 배열로 받는 게 아니라 포인터로 받는다!! // arr size도 함께 받아야 함!
{
printf("size = %zd in function average\n", sizeof(arr1));
double avg = 0.0;
for (int i =0; i < n; ++i) // arr의 수만큼 돌림!
{
avg += arr1[i];
}
avg /= (double)n;
return avg;
}
int main()
{
double arr1[5] = {10, 13, 12, 7, 8};
double arr2[3] = {11, 12, 12};
printf("Address = %p\n", arr1);
printf("Size = %zd\n", sizeof(arr1));
printf("Address = %p\n", arr2);
printf("Size = %zd\n", sizeof(arr2));
printf("Avg = %.1f\n", average(arr1, 5));
printf("Avg = %.1f\n", average(arr2, 3));
}
이렇게 배열을 함수로 받아올 때는 배열전체를 복사해서 함수에 인자로 넘겨주는 것이 아니라, 배열의 포인터 값을 넘겨주게 된다.(배열의 크기가 큰 경우 메모리 오버가 발생하기 때문)
따라서, 배열의 포인터 값과 크기를 함께 함수에 넣어주어서 처리가 되도록 한다.
10-08. 두 개의 포인터로 배열을 함수에게 전달하는 방법
double average(double* start, double* end)
{
int count = end - start; //앞서 배웠듯이, end와 start 차이는 인덱스가 몇 칸 차이인지를 나타낼 수 있다.
double avg = 0.0;
while (start < end)
{
avg += *start++;
//count++;
}
avg /= (double)count;
return avg;
}
10-09. 포인터 연산 총정리
지금까지 배운 내용들을 총정리하여 보자.
Pointer Differencing은 %td를 통해 printf할 수 있다.
e.g. printf("%td\n", ptr3 - ptr1);
- 포인터에 증가/감소 연산자를 사용할 수 있다.
- 포인터끼리 비교도 가능하다.
- 타입이 같은 경우 비교가 명확하다.
- 타입이 다른 경우 비교를 하려면 할 수는 있는데, 대부분의 경우 프로그래머의 실수일 것이다. (Type warning을 띄워줌)
-> 굳이 하고 싶다면 타입캐스팅을 해서 하면 된다. but, 실무에서는 이렇게 할 일이 전혀 없을 것이다.
10-10. const와 배열과 포인터
const는 기본적으로 변수를 상수로 바꿔주는 역할을 한다. 특히 const는 포인터와 사용을 할 때 주의해야 할 점이 있기 때문에 이에 대해 자세히 알아보는 시간을 가지도록 하자.
다만, const로 지정해준 array를 포인터로 불러와서 수정하게 되면 수정이 가능하게 된다. 이를 막기 위해서 포인터를 선언할 때도 const를 선언하여 지정해줘야 실수를 방지할 수 있다.
int main()
{
// type qualifiers: const, volatile
const double PI = 3.14159;
//PI = 2.14159; // Error because of const qualifier
const int arr[5] = {1, 2, 3, 4, 5}; // 배열 "각각의 원소"를 바꿀 수 없게 된다.
//arr[1] = 123;
const double arr2[3] = {1.0, 2.0, 3.0};
//arr2[0] = 100.0;
const double * pd = arr2;
//*pd = 3.0; //same with : pd[0] = 3.0 ; arr2[0] = 3.0;
// 다음과 같이 derefencing을 하고 3을 집어넣게 된다면, pd의 주소 자체는 arr2의 첫 번째 주소를 가리키기 때문에 수정이 된다.
//pd[2] = 1024.0; //same with : arr2[2] = 4.0;
// 여기서 에러가 날 것이다. 왜냐하면 *pd가 const이기 때문에 값을 바꿔주지 못하기 때문이다.
printf("%f %f \n", pd[2], arr2[2]);
pd++; // allowed: 변수 내의 원소값을 바꿀 수는 없지만, 주소값은 바꿀 수 있다.
printf("%f %f \n", pd[2], arr2[2]); // 참조할 원소 인덱스가 없기 때문에 0.00000이라는 이상한 값이 나온다!
return 0;
}
10-11. 배열 매개변수와 const
void print_array(const int arr[], const int n)
{
for (int i =0; i < n; ++i)
printf("%d ", arr[i]);
printf("\n");
}
void add_value(int arr[], const int n, const int val)
{
int i;
for (i =0; i < n; i++)
arr[i] += val;
}
int sum(const int ar[], const int n) // 여기 배열에 const를 붙여주게 되면 아래에서 배열값을 바꾸는 실수를 잡아줄 수 있다!
{
int i;
int total = 0;
for ( i= 0; i<n; i++)
total += ar[i];
return total;
}
int main()
{
int arr[] = {1,2,3,4,5};
const int n = sizeof(arr)/sizeof(arr[0]);
print_array(arr, 5);
add_value(arr, 5, 100);
print_array(arr, 5);
int s = sum(arr, n);
printf("sum is %d \n", s);
print_array(arr, 5);
return 0;
}
가진 게 하나도 없다면 모든 것을 다 가진 것처럼 행동해야 해 - 알라딘
10-12. 포인터에 대한 포인터(2중 포인터)의 작동원리
이중포인터는 포인터 변수의 "주소"를 저장한다. 포인터 변수 또한 변수
이기 때문에, 앞에 주소 연산자를 붙이면 주솟값에 대한 변수를 가져올 수 있게 된다. double indirection!
10-13. 포인터의 배열과 2차원 배열
포인터를 이용해 배열의 배열
을 만들 수 있다.
포인터의 배열을 가지고 이중포인터처럼 사용할 수 있다.
int main()
{
/* Two of 1D arrays */
int arr0[3] = {1,2,3};
int arr1[3] = {4,5,6};
int* parr[2] = {arr0, arr1}; // an array of pointers
for (int j=0; j<2; ++j)
{
for (int i = 0; i<3; ++i)
printf("%d(==%d, %d) ", parr[j][i], *(parr[j] + i), (*(parr + j))[i] ); // all same
printf("\n");
}
printf("\n");
/* 2D arrays are arrays of 1D arrays */
int arr[2][3] = { {1,2,3}, {4,5,6}};
int *parr0 = arr[0];
int *parr1 = arr[1];
for (int i =0; i<3; ++i)
printf("%d ", parr0[i]);
printf("\n");
for (int i =0; i<3; ++i)
printf("%d ", parr1[i]);
printf("\n");
/* arrays of pointers works like a 2D array */
//int* parr[2] = { arr[0], arr[1]};
for (int j=0; j<2; ++j)
{
for (int i = 0; i<3; ++i)
printf("%d %d %d %d\n",
arr[j][i], parr[j][i], *(parr[j] + i), *(*(parr + j)+i) );
printf("\n");
}
printf("\n");
///* Notes
// - parr[0] and parr[1] do not point valid memory by default
// - &parr[0] != &arr[0]
// - &parr[0] != parr[0] but &arr[0] = arr[0]
//*/
int arr[2][3] = { {1,2,3}, {4,5,6}};
int* parr[2];
parr[0] = arr[0];
parr[1] = arr[1];
printf("%p \n", &parr[0]); //different! //point array 자체 주소
printf("\n");
// 아래 5개의 printf의 결과값은 동일하다
printf("%p \n", parr[0]);
printf("%p \n", arr); // 배열의 이름은 첫번째 원소의 주소인 것처럼 사용할 수 있다.
//하지만, 주소를 저장하는 별도의 메모리를 가지지는 않는다.
printf("%p \n", &arr[0]);
printf("%p \n", arr[0]);
printf("%p \n", &arr[0][0]);
/* array of string of diverse lengths example */
char* name[] = {"Aladdin", "Jasmine", "Magic carpet", "Genie"};
const int n = sizeof(name) / sizeof(char*); // pointer의 변수에 4개의 원소가 담겨있고, 그 4개가 각 문자열의 첫번째 주소를 가리킨다.
for (int i=0; i<n; ++i)
printf("%s at %llu \n", name[i], (unsigned long long)name[i] ); //Use ull in x64 build
printf("\n");
char aname[][15] = {"Aladdin", "Jasmine", "Magic carpet", "Genie", "Jafar"};
const int an = sizeof(aname)/sizeof(char[15]); //앞과 다르게 포인터가 아니며, 메모리 사이즈가 이미 정해져 있는 경우
for (int i=0; i< an; ++i)
printf("%s at %llu \n", aname[i], (unsigned long long)aname[i] ); //Use ull in x64 build
// char 15만큼 메모리르 할당하여 사용한다.
printf("\n");
}
10-14. 2차원 배열과 포인터
int main()
{
float arr2d[2][4] = { {1.0f, 2.0f, 3.0f, 4.0f}, {5.0f, 6.0f, 7.0f, 8.0f} };
printf("%llu \n", (unsigned long long)arr2d); // use ull in x64
printf("%llu \n", (unsigned long long)arr2d[0]);
printf("\n");
// arr2d points to arr2d[0] (not arr2d[0][0])
// 배열의 이름이 주소를 가리킨다는 것은 알고 있을 것이다. arr2d[0](주소)와 arr2d[0][0](값)의 주소가 같다
// 하지만 개념,문법상 arr2d가 가리키는 것은 "arr2d[0]"이라는 것을 짚고 넘어가자!
printf("%llu \n", (unsigned long long)* arr2d);
printf("%llu \n", (unsigned long long)& arr2d[0]);
printf("%llu \n", (unsigned long long)& arr2d[0][0]);
printf("%f %f \n", arr2d[0][0], **arr2d);
printf("\n");
printf("%llu \n", (unsigned long long)(arr2d+ 1) );
printf("%llu \n", (unsigned long long)(&arr2d[1]) );
printf("%llu \n", (unsigned long long)(arr2d[1]) );
printf("%llu \n", (unsigned long long)(*(arr2d+ 1)) );
printf("%llu \n", (unsigned long long)( &arr2d[0]+ 1) );
printf("%llu \n", (unsigned long long)( &arr2d[1][0]) );
printf("\n");
printf("%f \n", *(*(arr2d+1) + 2) ); //7.0000
printf("\n");
for (int j = 0; j<2; ++j)
{
printf("[%d] = %llu, %llu \n", j, (unsigned long long)(arr2d[j]), (unsigned long long) * (arr2d+j));
for (int i = 0; i< 4; ++i)
{
printf("[%d][%d] = %f, %f \n", j,i, arr2d[j][i], *(*(arr2d+j)+i));
*(*(arr2d+j)+i) += 1.0f; // arr2d[j][i] += 1.0f;
}
}
printf("\n");
}
이번엔 다차원 배열에 대한 포인터 문법을 살펴보자.
float (*pa)[4]는 네 개의 int를 가진 배열
대한 포인터
이다.
float* ap[2]는 원소가 두 개인 포인터
의 배열
이 된다.
이들의 사이즈를 출력해보면, pa의 사이즈는 4바이트가 나오고(포인터 하나가 4바이트), ap의 사이즈는 8바이트가 나온다.(포인터 원소 2개인 배열 1개)
문법적으로 설명하자면, ap[2] 연산자 우선순위가 *(indirection)보다 높다.
pa는 단일 포인터이다. 따라서, 이를 배열로 취급하여 사용할 수는 없다. 하지만 첫 번째 주소를 대입할 수는 있다.
반대로 ap는 다음과 같이 사용해야 한다.
대부분의 경우 용법은 비슷하지만, 결과가 63, 73번째 라인을 사용할 때가 다르다. pa는 주소값을 사용했기 때문에 arr2d[0]와 pa가 같지만, ap는 포인터를 받는 배열을 생성한 것이기 때문에 arr2d[0]와 결과값이 다르다.
10-15. 포인터의 호환성 Compatibility
int main()
{
/* pointer Compatibility */
int n =5;
double x;
x = n; //no error
int* p1 = &n;
double* pd = &x;
//pd = p1; // Warning!! -> Type이 맞지 않음! 절대 권장하지 않음
int* pt;
int(*pa)[3];
int ar1[2][3] = {3,};
int ar2[3][2] = {7,};
int** p2; // a pointer to a pointer
pt = &ar1[0][0]; //pointer to int
pt = ar1[0]; // pointer to int
//pt = ar1; // warning(error)
p2 = &pt; // pointer to int * // 이중포인터이기 때문에, "포인터의" 주소를 담을 수 있다.
*p2 = ar2[0]; //pointer to int
//p2 = ar2; // warning(error)
// Notes
// - p2: pointer to pointer to int
// - ar2: a pointer to array-of-two-ints
/* pointer and const */
int x = 20;
const int y = 23;
int* p1 = &x;
const int* p2 = &y;
const int** pp2 = &p1;
//p1 = p2; // warning error!
//*p2 = 123; // Error: 이는 const인 y의 값을 바꾸려는 행위와 동일하기 때문!
//p2 = p1;
int x2 = 30;
int* p3 = &x2;
*pp2 = p3;
pp2 = &p1;
/* another example */
const int** pp2;
int* p1;
const int n = 13;
pp2 = &p1; // const?
*pp2 = &n; // sets p1 to point at n
*p1 = 10; //change n
}
# 10-16. 다차원 배열을 함수에게 전달해주는 방법
---
```c
#define ROWS 3
#define COLS 4
int sum2d_1(int ar[ROWS][COLS]);
int sum2d_2(int ar[][COLS], int row);
int sum2d_3(int* ar,int row, int col);
int main()
{
int data[ROWS][COLS] = {
{1,2,3,4},
{5,6,7,8},
{9,0,1,2}
};
printf("%d\n", data[2][3]);
int* ptr = &data[0][0];
printf("%d\n", *(ptr+3+ COLS*2));
printf("Sum of all elements = %d\n", sum2d_1(data));
printf("Sum of all elements = %d\n", sum2d_2(data, ROWS));
printf("Sum of all elements = %d\n", sum2d_3(&data[0][0], ROWS, COLS));
}
int sum2d_1(int ar[ROWS][COLS])
{
int r, c, tot = 0;
for (r=0; r< ROWS; r++)
for (c =0; c<COLS; c++)
tot += ar[r][c];
return tot;
}
int sum2d_2(int ar[][COLS], int row)
{
int r, c, tot = 0;
for (r=0; r< row; r++)
for (c =0; c<COLS; c++)
tot += ar[r][c];
return tot;
}
int sum2d_3(int* ar,int row, int col) // row, col을 변수로 넘겨주고 있기 때문에, 심볼릭 필요없음
{
int r, c, tot = 0;
for (r=0; r< row; r++)
for (c =0; c<col; c++)
tot += *(ar + c + col*r); // ar[c + col*r]
return tot;
}
/*
In higher dimensions,
You may omit the value in the left-most breacket
int my_function(int ar[][3][5][8], int rows);
int my_function(int (*ar)[][3][5][8], int rows);
// 고차원 배열의 경우 다음과 같이 넣어준다. 가장 높은 차원은 주로 비워둔다.
*/
10-17. 변수로 길이를 정할 수 있는 배열(Variable-Length Arrays)
VLA은 visual studio에서 지원을 해주지 않는다.
물론, 이후 강의에서 배울 동적할당을 더욱 많이 사용하게 될 테고, VLA은 컴파일러 마다 안정적이지 않은 경우도 있기 때문에 비추천한다.
10-18. 복합 리터럴과 배열 (Compound Literals)
line 15를 보면 알 수 있듯이, compound literal은 이름이 없다!
'Computer Science > 따라하며 배우는 C' 카테고리의 다른 글
12 Memory (0) | 2022.06.02 |
---|---|
11 String (0) | 2022.04.25 |
09 function, pointer (0) | 2022.03.31 |
08 Input Output (0) | 2022.03.31 |
07 If문 (0) | 2022.03.31 |