본문 바로가기
SW사관학교 정글/C언어와 친구들

[C언어와 친구들] 배열 .. 너 .. 포인터랑 뭐 있어?

by 대범하게 2022. 10. 22.
반응형

배열은 C언어가 제공하는 가장 기본적인 자료구조이면서, 몇 없는 ... 그냥 다(?) 없는 C언어의 자료구조 중 하나이다.

 

배열이란, 컴퓨터 메모리 상에 같은 타입의 변수를 연속적으로 여러 개를 한꺼번에 정의할 수 있는 방법이다.

 

배열의 장점은 크게 두 가지가 있다.

1) 공간 효율이 좋다.

구조가 단순하기 때문에 정보 자체를 기억하는 메모리 외에 추가로 소모하는 메모리가 전혀 없이 공간효율이 좋다. 정수형 변수 100개를 저장하는 int arr[100] 배열은 정확하게 정수 100개분만큼의 메모리만을 요구한다.

2) 검색 속도가 일정하다.

배열의 크기가 아무리 커지더라도 검색 속도가 일정한다. 배열의 첨자 연산은 포인터를 통해 시작 번지에 첨자 *요소 크기를 더하는 간단한 동작이므로 임의의 한 요소를 참조하는 시간이 상수이다. int arr[10]에서 arr[9]를 참조하는 시간과 int arr[1000]에서 arr[999]를 참조하는 시간이 똑같다는 얘기이다. 

 

1. 배열 선언

- 타입 이름 변수명[배열길이];  ex) int arr[5];

- 타입 => 배열 요소의 자료형

- 배열길이 => 배열 요소의 개수 

- 선언만 하고 초기화 하지 않으면, 각 요소에 쓰레기값들이 저장되어 있게 됨 

#include <stdio.h>
int main(){
	// 정수형이나 실수형 배열들을 중괄호를 써서 변수의 값을 정함.
    // 선언과 동시에 초기화를 진행할 시 초기화 리스트의 타입과 배열의 타입이 반드시 일치해야함.
    
    int a[5] = {10, 20, 30, 40, 50}; // 선언과 동시에 초기화
    int b[5] = {10}; // 0번째 값을 10으로 초기화하고 나머지는 모두 0으로 초기화
    int c[5] = {15, 25}; // 0번째 값을 15로 초기화하고, 1번째 값을 25로 초기화, 나머지는 모두 0으로 초기화
    int d[5] = {}; // 모두 0으로 초기화
    int e[5] ; // 초기화 하지 않음 => 각 요소에 trash value 저. 장.
    int f[] = {11, 22, 33, 44};
    
    // char 형 배열은 "~~"로 선언 가능. 문자는 0이라는게 NULL 의미
    char c[6] = {'H', 'E', 'L', 'L', 'O', NULL}; // c는 문자열 배열이고 값은 H, E, L, L, O
    char s[6] = "HELLO"; // s는 문자열 배열이고 값은 H, E, L, L, O입니다.
    return 0;
}
// 배열의 크기를 상수식으로 정할 수 있다. 
#define N 10
...
int a[N];

 

2. 배열이 차지하는 메모리의 크기

- sizeof 함수는 메모리 상에서 차지하고 있는 용량  알 수 있다.

- 이 함수를 이용하여 전체 배열 중 하나의 요소로 나누면 쉽게 배열의 크기를 알 수 있다.

#include <stdio.h>
int main(){
    int arr[] = {1,2 234, 34, 6634, 754};
    printf("%d\n", sizeof(arr)); // 배열이 메모리 상에서 차지하고 있는 용량
    // int는 4바이트이고, 6개가 있으므로 출력 결과는: 24

    printf("%d\n", sizeof(arr)/sizeof(arr[0])); // 배열의 크기를 구하는 방법 중 하나
    // 배열의 전체는 24바이트이지만 요소 하나는 4바이트
    // 전체 배열을 요소 하나로 나누면 길이를 구할 수 있다.
    // 출력 결과는 24 / 4 이므로 6

    printf("%d\n", sizeof(arr)/sizeof(int)); // 이렇게 해도 동일하게 나옴
    return 0;
}

 

3. 문자열 배열 

- C언어는 기본적으로 문자열 자료형을 제공하지 않는다. (문자열 쓰겠다고 string을 치고 앉아있던 나.. 드디어 깨달음)

- 문자열 배열 => 문자(Character)를 여러 개 묶어 놓는 형태로 문자열을 표현한다. (C++에서는 string 제공한다고 함.)

- 특이점은 문자열은 컴퓨터 메모리 구조상 마지막에 널(NULL)값을 포함한다. 문자열 마지막에 \0이 있으면 문자열의 끝으로 인식한다. 참고로, NULL은 ASCII 코드로 0이다. 어떠한 값도 의미 있지 않다는 말이다. 

 

- C언어에서는 하나의 문자1바이트만 담을 수 있기 때문에,

- 문자의 배열을 표현하기 위해 예를 들면 20자의 배열을 선언한 다음에, 해당 배열에 문자 배열을 할당한다. 

- 기본적으로 문자열을 선언할 때는 문자열의 크기보다 배열의 크기가 크도록 해야한다. 

// NULL과 띄어쓰기를 포함하여 총 13개의 char 크기가 필요
char str[13] = "Hello world!";

// 배열의 크기 미리 정하지 않고, 배열을 입력하면 알아서 사이즈가 잡히게 됨.
char str2[] = "hello world!";

 

4. 배열 .. 너 .. 포인터랑 뭐 있어? 

=> 배열과 포인터는 표기만 다를 뿐 문법 구조가 유사

=> 배열과 포인터는 표기법을 서로 바꿔 사용 가능

배열 표기법과 포인터 표기법의 관계
배열 포인터
char data[5]; char *p;
[ ] 연산자 사용 * 연산자 사용
배열이 사용하는 메모리 그룹의
시작 주소 기준
포인터 변수가 가리키는 메모리의
시작 주소 기준
char data[5];
data[1] = 5;
*(data+1) = 5;
char data;
char *p = &data;
*p = 3;
p[0] = 3;

 

 

5. 배열 변수의 이름은 배열의 시작 주소 ✨✨✨

char data[4];
char *p = &data[0]; 	// 배열의 첫 번째 항목의 주소가 배열 전체의 시작 주소와 같음
char *p = &*(data+0); 	// 배열 표기법을 포인터 표기법으로 변경
char *p = &*data;	// 0 생략
char *p = data; // 주소를 얻는 & 연산자와 주소를 지정하는 * 연산자는 서로 반대 개념의 연산자이기 때문에 서로 상쇄됨.

 

💡외워.

&a : a의 주소를 가져옴

*a : a를 주소로 하는 곳의 값

=> &(*data) == data

 

입력받은 문자를 그대로 출력하는 코드이다. 여기에서 scanf("%s", &a);를 쓰면  "warning: format specifies type 'char *' but the argument has type 'char (*)[20]' " 에러가 난다. 

 

Why?!?! 😶‍🌫️

갑자기 deep(?)하게 들어가자면 &는 해당 변수의 주소를 가리키는 역할을 한다. 

 

[scanf를 사용할 때 내부적으로 구동되는 방식]

1. 값을 입력받는다.

2. 그 값을 레지스터에 임시로 저장하고

3. 변수의 주소를 찾아가 그 주소를 가리키는 메모리에 레지스터에 있는 값(입력한 값)을 저장한다.

4. 레지스터에 있던 값은 지운다. 

즉, 값을 입력하려면 변수의 주소가 필요하기 때문에 &를 써야한다. 

 

만약 문자열(%s)을 입력받는 경우에

문자열 자체(포인터거나 배열일 때)는 주소이기 때문에 &를 쓰지 않고 scanf("%s", a);를 쓰면 된다. 

#include <stdio.h>

int main(void) {  
    char a[20];
    // 문자열 변수명 a는 그 자체가 주소값을 지칭하므로 &없이 사용. 즉, &a으로 사용하지 않음.
    scanf("%s", a);
    printf("%s\n", a);
}

 

6. 포인터로 배열의 주소를 저장하여 사용하기

 

1) 배열

- 배열은 배열의 시작 주소를 기준으로 색인 작업된 요소의 위치를 계산

- 배열의 같은 요소를 반복적으로 사용하는 경우 효율이 떨어짐

char data[5] = {1, 2, 3, 4, 5};
int sum = 0, select = 2;
// sum 변수에 data[select] (data[2]) 요소 값을 10번 더 함
for(int i=0; i<10; i++) sum = sum + data[select];

// 위와 same
for(int i=0; i<10; i++) sum = sum + *(data + select);

2) 포인터

- 지속적으로 사용되는 배열의 요소는 주소를 포인터 변수에 저장하여 사용

char data[5] = {1, 2, 3, 4, 5};
int sum = 0, select = 2;
char *p = data + select; // char *p = &data[select]; 와 동일한 표현
for(int i=0; i<10; i++) sum = sum + *p;

 

7. string.h

- strlen = string + length

: 문자열의 길이를 구하는 문자열 표준 함수

 

- strcpy = string + copy

: 문자열을 복사하는 문자열 표준 함수

 

- strcat = string + concatenation

: 문자열 뒤에 다른 문자열을 덧붙이는 문자열 표준 함수

 

From here..


배열과 관련된 BOJ 

// 2562: 최댓값
#include <stdio.h>
int main(){
    int max = 0;
    int idx;
    for(int i=1; i < 10; i++){ // 몇 번째... 인지 ...
        int b;
        scanf("%d", &b);

        if (max < b){
            max = b;
            idx = i;
        }
    }
    printf("%d\n%d\n", max, idx);

    return 0;
}
// 8958: ox퀴즈
#include <stdio.h>
#include <string.h>

int main(){
    int input;
    char test[80];

    scanf("%d", &input);

    for(int i = 0; i < input; i++){
        int sum = 0;
        int score = 1;
        scanf("%s", test);
        // strlen() 함수는 문자열의 길이를 반환하는 함수이며, <string.h> 헤더 파일을 필요로 한다.
        for(int j=0; j < strlen(test); j++){    
            if (test[j] == 'O'){
                sum += score;
                score++;
            }
            if (test[j] == 'X') score = 1;
        }
        printf("%d\n", sum);
    }
}

 

포인터 너...... 기다려라...

포인터 배열, 배열 포인터, 상수 포인터.....?  pointer......

반응형