프롤로그
ELITE HACKER Bootcamp 3rd 3주차 수업 공부 내용
점수 처참해서;; 빡세게 공부해보자.
aws 우분투 서버 하나 파서 연습
배열, 배열 선언 방법 , 배열의 인덱스 참조 방법, 배열의 메모리 구조, 2차원 배열 , n차원 배열 (n > 2)
배열
같은 형태의 데이터, 동일한 자료형의 데이터를 한데 묶어 연속된 메모리 공간에 저장하는 데이터 구조이다. 미리 크기를 정의하고, 인덱스를 통해서 각 요소에 접근할 수 있다.
배열의 특징이 있다면 다음과 같은데
- 고정된 크기: 배열의 크기는 선언할 때 정해지며, 프로그램 실행 중에는 변경할 수 없다.
- 인덱스: 배열의 인덱스는 0부터 시작한다. 첫 번째 요소는 인덱스 0에, 두 번째 요소는 인덱스 1에 위치한다.
- 연속된 메모리: 배열 요소는 메모리상에 연속적으로 배치된다.
//자료형 배열이름[배열의 크기] = {데이터1, 데이터2, 데이터3...}
int numbers[3] = {1, 2, 3}
이런식으로 사용한다.
int 대신에 char, float, double 전부 다 가능하다.
선언 방법은 조금 뒤에 알아보도록 하고,
이게 왜 굳이 배열을 사용하느냐? 를 알아야하는데.. 사용해야할 데이터가 너무 많은 경우이다.
숫자 10개를 저장해야한다고 했을 때
int a = 1;
int b = 2;
int c = 3;...
이걸 언제 다하고 있는가.... 10개 동일한 자료형 즉 자료형이 같은 애들을 한 번에 정의하기 위해서 만들어진 것이 배열이다.
인덱스는 왜 0번부터 시작하는가? 우리는 숫자를 셀 때 1부터 세는데 왜 여기서는 0번부터 쓰는가? 배열의 인덱스에 접근하기 쉽게 하기 위해서이다. 왜냐?
메모리 주소를 가리킬 때, 첫번째를 가리키기 위해서는 본인 자신이라는 것을 알려주기 위해서이다. 그리고 다음 인덱스로 넘어갈려고 할 때, 정수형이라면 +4바이트를 더해주는 방식으로 진행하면 된다. 연산에 더 쉽게 해주기 위해서, 4*0, 4*1, 4*2 이런식으로 더해주면 되기 때문에 0번부터 시작하는 것이다.
배열 선언 방법
아까 위에있던 거 대로
//자료형 배열이름[배열의 크기] = {데이터1, 데이터2, 데이터3...}
int numbers[3] = {1, 2, 3}
이게 가장 기본적인 방법이다.
그리고 이미 저렇게 시작부터 값이 정해져 있을 경우에는 크기는 생략할 수 있다.
int numbers[] = {1, 2, 3}
밑에는 예시코드.
#include <stdio.h>
int main(){
int numbers[3] = {1, 2, 3};
printf("%d\n", numbers[0]);
return 0;
}
근데 궁금하지 않나? 만약에 3개 선언해 넣고 2개만 넣은 경우? 그래서 해봤다.
#include <stdio.h>
int main(){
int numbers[3] = {1, 2};
printf("%d %d %d\n", numbers[0], numbers[1], numbers[2]);
return 0;
}
실행시키니까
1, 2는 정상적으로 1, 2 로 초기화를 시켜주고, 나머지 값은 0으로 초기화를 시켜준다.
전부 다 0으로 초기화 시켜주고 싶다면?
int numbers[3]= {0,} //{0}으로 해도됨
이렇게 하면 된다.
진짜 원초적인 방법도 있는데,
int numbers[3];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
이렇게 사용해도 된다.
#include <stdio.h>
int main(){
int numbers[3];
//int numbers[3];
numbers[0] = 1;
numbers[1] = 2;
numbers[2] = 3;
printf("%d %d %d\n", numbers[0], numbers[1], numbers[2]);
return 0;
}
배열 선언 시 주의해야할 점
배열의 크기에 변수가 오면 안 된다!
#include <stdio.h>
int main(){
int a = 3;
int b[a] = {1, 2, 3};
printf("%d %d %d\n", b[0] ,b[1], b[2]);
return 0;
}
그리고
int a[3];
a = {1, 2, 3}
이런 느낌도 안 되더라.
인덱스 참조 방법
아까 위에서 하드코딩 느낌으로 일일이 대괄호를 이용해서 사용한다고 했다.
총 10개짜리 배열을 만들었다면 인덱스는 0번부터 9번까지 만들어지고, 5번째 있는 배열에 접근하려면 (5-1)인 4번 인덱스로 접근하면 해당 위치로 접근할 수 있다.
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// 배열 요소 출력
printf("첫 번째 요소: %d\n", numbers[0]); // 10 출력
printf("세 번째 요소: %d\n", numbers[2]); // 30 출력
// 배열 요소 변경
numbers[1] = 25; // 두 번째 요소를 25로 변경
printf("두 번째 요소: %d\n", numbers[1]); // 25 출력
return 0;
}
한 번 초기화 된 거에 값을 계속 바꿔줄 수도 있다.
근데 여기서 주의해야할 건
#include <stdio.h>
int main() {
int numbers[5] = {10, 20, 30, 40, 50};
// 배열 요소 출력
printf("첫 번째 요소: %d\n", numbers[0]); // 10 출력
printf("세 번째 요소: %d\n", numbers[2]); // 30 출력
// 배열 요소 변경
numbers[1] = 25; // 두 번째 요소를 25로 변경
printf("두 번째 요소: %d\n", numbers[1]); // 25 출력
numbers[5] = 3;
printf("이게 되나 ? %d\n", numbers[5]);
return 0;
}
맨 마지막에 numbers[5] = 3; 인덱스는 [5]만큼의 크기를 만들었다면 0~4까지나와야한다.
근데 뭐냐? 실행이 왜 됨?
전지전능하신 GPT 형님께서도, 이건 정상적인 작동으로 보이지만 사실은 아니라고한다. 매우 치명적인 방법이다. 만약에 다음 메모리에 매우 중요한 정보가 있다고 했을 떄, 배열인덱스를 넘어버린 것이 중요한 정보를 덮어버릴 수도 있기 떄문이다. 유효한 범위를 벗어난 인덱스를 사용하면 예기치 않은 동작이나 프로그램 충돌이 발생할 수 있다. 그 이유를 알기 위해 메모리 구조에 대해서 바로 알아보자.
배열의 메모리 구조
C언어에서 메모리 구조는 연속된 메모리 블록에 동일한 자료형의 데이터를 저장하는 방식이다. 그니까 int형으로
int a[5];
이렇게 생성한다면, int 자료형은 4바이트이니까 4바이트 * 5개 = 20바이트의 공간을 할당받는다.
만약에
char str[6];
이라면 1바이트 * 6 = 6바이트의 공간을 할당받는다.
표를 이용해서 다시 확인해보자.
int a[5] = {1, 2, 3, 4, 5}
라고 했을 떄 1000번째 주소부터 시작을 했다면
메모리주소 | 0x1000 | 0x1004 | 0x1008 | 0x1012 | 0x1016 |
인덱스 | 0 | 1 | 2 | 3 | 4 |
값 | 1 | 2 | 3 | 4 | 5 |
이렇게 될 것이다. 즉, 각 인덱스의 메모리 시작주소는 는
시작주소 + sizeof(int) * 인덱스
으로 찾아갈 수 있다. 만약에 인덱스가 1이라면 시작 주소를 찾는 계산 과정이 복잡해서 인덱스를 0이라고 두는 것이다.
포인터를 이용하는 방법도 있는데, 나중에 포인터 나오면 좀 더 자세히 알아보도록 하자.
2차원배열
말은 2차원이긴한데, 그건 알아야한다. 컴퓨터 메모리는 일차원이다. ㅋㅋ 이게 뭔소리냐? 0번부퉈 뭐 메모리 끝주소가 1000번이라고 가정하면 0, 1, 2, 3....999, 1000이 일렬로 쭉 이어져 있다고 하는 거다. 근데 이제 자료구조니 뭐니 하면서 메모리를 2차원으로 우리가 임의대로 정해서 표현한 것.
일단은, 아까 까지만 해도 1*n해서 1차원 배열을 만들어냈다.
정수형 int a[6]이라고 하면 1*n 짜리 1차원 배열을 만들어낸 것이다. 크기는 정수기 때문에 4바이트 * 6 = 24
이번에는 2*3배열을 만든다고 하면
// 2x3 배열 선언
int matrix[2][3];
이렇게 선언을 해주면 된다. 값을 넣어줄때는
// 배열 선언과 동시에 초기화
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 또는
int matrix[2][3] = {1, 2, 3, 4, 5, 6}; // 동일하게 초기화됨
이본적으로 아까랑 거의 비슷하다.
2차원 배열은 행 우선 방식(row-major order)이기 떄문에, [0][0], [0][1], [0][2], [1][0], [1][1], [1][2] 순으로 데이터가 들어간다
그니까
1, 2, 3, 4, 5, 6저렇게 해버린다면
1 | 2 | 3 |
4 | 5 | 6 |
이렇게 데이터가 들어간다는 것이다.
메모리는 1000부터 시작한다면, 그대로 1~6까지 차례대로 올라간다. 그니까 메모리에 쌓이는 건 행 순서대로 차례대로 쌓이는데 우리가 임의적으로 그려낸 것 뿐이다.
n차원 배열
다 차원 배열도 마찬가지. 메모리는 1차원이기 떄문에 일렬로 적혀있고, 행 우선 방식으로 인하여 순서만 잘 생각한다면 문제없다. 근데
거의 쓸일이 있나 싶다. 저거에 접근하려면 for문으로 잘 사용해보면 된다.
도전
(1) 랜덤한 값을 가지는 10개의 배열 만들기
#include <stdio.h>
#include <stdlib.h>
int main(){
int a[10];
for (int i=0;i<sizeof(a) / sizeof(a[0]);i++){
int tmp = 0;
tmp = rand() % 100;
a[i] = tmp;
}
for (int i=0;i<10;i++){
printf("%d\n", a[i]);
}
}
처음에 이렇게 코드를 짰다.
rand() % 100을 하면 0~99까지의 수를 랜덤으로 생성해준다고 하고, 그것을 위하여 stdlib.h를 사용한다고 했다.
근데
값이 다 똑같다. 이를 해결하기 위해선
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
int a[10];
int len = sizeof(a) / sizeof(a[0]);
for (int i=0;i<len;i++){
int tmp = 0;
tmp = rand() % 100;
a[i] = tmp;
}
for (int i=0;i<len;i++){
printf("%d\n", a[i]);
}
}
time.h 와 함께 srand(time(0))으로 난수 생성할 수 있게 바꿔주어야한다.
(2) 해당 배열의 최댓값 출력하기
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
int a[10];
int len = sizeof(a) / sizeof(a[0]);
for (int i=0;i<len;i++){
int tmp = 0;
tmp = rand() % 100;
a[i] = tmp;
}
for (int i=0;i<len;i++){
printf("%d\t", a[i]);
}
printf("\n");
int max_value = -1;
int max_index = -1;
for (int i=0;i<len;i++){
if(max_value < a[i]){
max_value = a[i];
max_index = i;
}
}
printf("최대값은 %d번째 인덱스에 있는 %d입니다\n", max_index, max_value);
}
for문으로 하나씩 돌면서 큰값이 나오면 넣어주는 방식으로 진행
(3) 해당 배열의 최솟값 구하기
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
int a[10];
int len = sizeof(a) / sizeof(a[0]);
for (int i=0;i<len;i++){
int tmp = 0;
tmp = rand() % 100;
a[i] = tmp;
}
for (int i=0;i<len;i++){
printf("%d\t", a[i]);
}
printf("\n");
int min_value = 99999999;
int min_index = -1;
for (int i=0;i<len;i++){
if(min_value > a[i]){
min_value = a[i];
min_index = i;
}
}
printf("최솟값은 %d번째 인덱스에 있는 %d입니다\n", min_index, min_value);
}
방금 문제랑 비슷하다. 그러니까 min_value를 최대로 설정해놓은 다음에 작은 수가 나올때마다 바꿔주는 것이다.
(4) 해당 배열을 오름차순으로 정렬하기
이건 일단 버블정렬이라는 것을 이용해서 해보자.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
int a[10];
int len = sizeof(a) / sizeof(a[0]);
for (int i=0;i<len;i++){
int tmp = 0;
tmp = rand() % 100;
a[i] = tmp;
}
for (int i=0;i<len;i++){
printf("%d\t", a[i]);
}
printf("\n");
int min_value = 99999999;
int min_index = -1;
for (int i=0;i<len;i++){
if(min_value > a[i]){
min_value = a[i];
min_index = i;
}
}
printf("최솟값은 %d번째 인덱스에 있는 %d입니다\n", min_index, min_value);
for (int i=0;i<len;i++){
for (int j=0;j<len-1-i;j++){
if(a[j] > a[j+1]){
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
for (int i=0;i<len;i++){
printf("%d\t", a[i]);
}
printf("\n");
}
(5) 해당 배열을 내림차순으로 정렬하기
아까랑 비슷한데, <>만 바꿔주면 된다.
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(){
srand(time(0));
int a[10];
int len = sizeof(a) / sizeof(a[0]);
for (int i=0;i<len;i++){
int tmp = 0;
tmp = rand() % 100;
a[i] = tmp;
}
for (int i=0;i<len;i++){
printf("%d\t", a[i]);
}
printf("\n");
int min_value = 99999999;
int min_index = -1;
for (int i=0;i<len;i++){
if(min_value > a[i]){
min_value = a[i];
min_index = i;
}
}
printf("최솟값은 %d번째 인덱스에 있는 %d입니다\n", min_index, min_value);
for (int i=0;i<len;i++){
for (int j=0;j<len-1-i;j++){
if(a[j] < a[j+1]){
int tmp = a[j];
a[j] = a[j+1];
a[j+1] = tmp;
}
}
}
for (int i=0;i<len;i++){
printf("%d\t", a[i]);
}
printf("\n");
}
에필로그
슬슬 백준 문제 풀어봐야겠다.
'KnockOn' 카테고리의 다른 글
[KnockOn] Linux/Ubuntu C언어 함수 -2 (0) | 2024.11.20 |
---|---|
[KnockOn] Linux/Ubuntu C언어 함수 -1 (2) | 2024.11.19 |
[KnockOn] Linux/Ubuntu C언어 조건문, 반복문 (2) | 2024.11.15 |
[KnockOn] Linux/Ubuntu C언어 입출력함수 (1) | 2024.11.14 |
[KnockOn] Linux/Ubuntu C언어 연산자 (0) | 2024.11.13 |