본문 바로가기
휴지통/C 언어

1차원 배열, 포인터

by 신재권 2021. 1. 23.

배열이란? 

다수의 데이터를 저장하고 처리하는 경우에 유용하게 사용할 수 있는 것이 배열이다.

 

배열의 선언 방법

자료형 배열이름 길이정보 순이다

즉 

int arr[20];  //int형 변수 20개로 이뤄진 배열, 이름은 arr이다. 4 * 20개 byte

double arr2[10]; // 길이가 10인 double형 1차원 배열 8*10개 byte

이렇게 배열을 선언 가능하다 . 

각 배열마다 각 자료형의 크기만큼 바이트가 할당되어 있다.

 

배열의 접근 방법

int arr[3]; //길이가 3인 int형 1차원 배열

위의 배열로 각 배열에 접근을 하는 방법은 ..

arr[0]= 1; // 배열 arr에 첫 번째 요소에 1을 저장

arr[1]= 2; // 배열 arr에 두 번째 요소에 2을 저장

arr[3]= 3; // 배열 arr에 세번째 요소에 3을 저장 

arr[idx] = 20;  //배열 arr에 idx+1 번째 요소에 20을 저장

배열의 위치 정보를 명시하는 인덱스 값(idx)은 0부터 시작한다.

배열의 모든 요소에 접근할 때는 반복문을 이용하여 순차적으로 접근하는 것이 가능하다.

배열은 선언과 동시에 초기화가 가능하다.

즉.

int arr1[5]= {1,2,3,4,5};  //순차적으로 1,2,3,4,5로 초기화 

오른쪽 중괄호로 묶인 부분을 '초기화 리스트'라 한다.

인덱스 값이 공백이어도 초기화 리스트가 존재하면 그에 맞게 알아서 길이정보가 삽입이 된다.

또한

int arr[3] = {1, 2} ;  //나머지 배열요소는 0으로 초기화 된다.

 

sizeof(배열이름) : 바이트 단위의 배열 크기 반환

sizeof(배열이름)/ sizeof(동일한자료형) : 배열의 길이 반환 

 

char형 배열로 문자열의 저장뿐만 아니라 문자열의 변경도 가능하다. 

C언어는 큰 따옴표를 이용해  문자열을 표현한다. 

char str[14] = "Good morning!"; 

위의 선언으로 메모리 공간에 char 배열이 할당이 가능하다.

문자열의 끝에는 '\0'문자가 자동으로 삽입된다. 이를 가리켜 널(NULL)문자라 한다.

널 문자의 아스키 코드 값은 0이다. 그리고 이를 출력할 경우 아무런 출력이 발생하지 않는다.

널과 공백문자는 다르다. 공백문자의 아스키 코드는 32이다.

 

scanf함수를 이용해 배열에 문자열을 입력할 수 있다. 

char str[30];

scanf("%s", str);  문자열을 입력 받아서 str에 저장

 

scanf함수를 통해서 입력받은 문자열의 끝에도 널문자가 삽입된다.

C언어에서 표현하는 모든 문자열의 끝에는 널 문자가 자동으로 삽입된다.

 

널문자가 필요한 이유는 컴퓨터는 널문자를 기준으로 문자열을 구분하기 때문이다.

scanf함수의 입력은 공백을 포함하지 않는다.


포인터 : 주소 값의 저장을 목적으로 선언된다.

int num=7;
int *pnum;    //포인터 변수 pnum선언
pnum =#   //num의 주소 값을 포인터 변수 pnum에 저장

&기호는 'the address of'라고 읽을 수 있다. 변수를 대신해 메모리 주소를 되돌려 준다. 

즉 위의 코드에서는 num의 메모리 주소를 pnum에 저장하겠다(대입하겠다)라는 의미이다. 

pnum에는 num의 주소가 들어있는 것이다.

 

우리가 num의 주소를 참조해서 값을 읽거나 변경하기를 원하면 pnum을 이용해 변경할 수 있다.

pnum은 num의 주소를 가리키고 있기 떄문이다. 

메모리에 저장된 값을 참조하기 원한다면 *pnum을 사용하면 된다.

즉..

type *ptr; 

"type형 변수의 주소 값을 저장하는 포인터 변수 ptr의 선언"

 

int형, double형과 같이  int*도 int형 포인터, double*은 double형 포인터라고 읽는다.

즉 '포인터 형' 도 '자료형'의 범주에 포함시킨다.

 

*는 포인터가 가리키는 메모리를 참조하는 연산자이다.

*pnum= 20;  //포인터 변수 pnum이 가리키는 메모리 공간인 변수 num에 20을 저장
printf("%d", *pnum);  //포인터 변수 pnum이 가리키는 메모리 공간인 변수 num을 출력
int *ptr;  //포인터 변수 ptr은 쓰레기값으로 초기화
*ptr =200;  //ptr이 가리키는 주소를 모르기 때문에 함부로 변경 하면 안됨
========================================================================
int *ptr1= 15;  //15번지가 어디인지 모르기때문에 함부로 값을 변경하면 안됨
*ptr1=10;  
=========================================================================
 int *ptr=0; 	//선언만 하고 나중에 주소값을 채울라면 이렇게 초기화를 해야함
 int *ptr =NULL;  //NULL == 0   //이것을 가리켜 '널포인터'라 함.
 /* 널 포인터는 "어느 곳도 가리키지 않음"의 뜻을 지닌다. */

배열의 이름은 포인터라 할 수 있다.  값이 변경 불가한 '상수 형태의 포인터''이다.

즉..

int arr[3]  = {1, 2, 3}

printf("%p", arr); 

printf("%p", &arr[0]);

위의 예제는 같은 주소 값을 나타낸다. 

즉 배열의 이름과 배열의 첫번째 요소의 주소값은 같다.

"int형 배열요소간 주소 값의 차이는 4 바이트이다." 

 

즉 배열의 이름을 상수 형태의 포인터 라 한다 . '포인터 상수'라고도 한다.

배열의 이름도 포인터 이기 때문에 배열의 이름을 피연산자로 하는 *연산이 가능하다.

 

int arr1[5];  //arr1은 int형 포인터 상수

double arr2[3]; //arr2는 double형 포인터 상수

*arr1 += 100 ;  //이 코드를 대입할 시 arr1의 배열의 첫번째 요소 값에 100을 증가 시키는 것이다. 

 

int arr[3] = {10 , 11, 12};
int *ptr =&arr[0];   // int *ptr = arr; 와 동일한 문장

 

위의 코드로 인해 ptr[0~3] == arr[0~3]의 값은 같다.

 

포인터를 대상으로 메모리의 접근을 위한 *연산 이외에 증가 및 감소 연산도 가능하다. 

int *ptr = ... ; //적절한 값으로 초기화 할시

ptr+1; //4증가(즉 포인터 형에 따라 값이 증가한다)

ptr++; //4증가

tpye형 포인터를 대상으로 n의 크기만큼 값을 증가 및 감소시키면, n x sizeof(type)의 크기만큼 주소 값이 증가 및 감소한다.

 

*(++ptr) = 20;   //ptr에 저장된 값 자체를 변경

*(ptr+1) = 20;  //ptr에 저장된 값은 변경되지 않음

 

즉 arr[i] == *(arr+i)로 나타낼 수 있다.

 

포인터는 문자열을 가리킬 수 도 있다.

char str1[] = "Simple" ;  //배열의 길이는 자동 계산 마지막 널문자도 삽입

char *str2 = "Simple"; 

이렇게 선언시 차이점은 ..

첫번째는 배열에 저장했기 때문에 문자열 전체를 저장하는 배열이고 

두번째는  메모리 공간에 "Simple"이 저장되고, 문자열의 첫 번째 문자(S)의 주소값이 반환된다. 그리고 그 반환 값이 포인터 변수 str2에 저장이 된다. 

첫번째도 배열의 이름 str1이 의미하는 것도 첫번째 문자인 S의 주소값이다.

 

하지만 둘이 차이점이 있다면, 배열로 선언할시 계속 문자 S가 저장된 위치를 가리켜야 하지만 , 

포인터 변수는 다른 위치를 가리킬 수  있다.

또 다른 차이점은 

str1은 배열안의 문자열의 내용을 변경할 수 있다. 즉 '변수 형태의 문자열' 이라 하고

str2는 가리키는 문자열을 내용을 변경할 수 없기 때문에 '상수 형태의 문자열'이라 한다.

 

포인터 변수로 이루어진, 즉 주소 값의 저장이 가능한 배열을 가리켜 '포인터 배열'이라 한다.

int *arr[20];  //길이가 20인 int형 포인터 배열 arr

dobule *arr[30]; //길이가 30인 double형 포인터 배열 arr

int num1=10, num2 =20, num3=30;
int *arr[3] = {&num1, &num2, &num3};

for(int i=0;i<3; i++)
{
	printf("%d \n", *arr[i]);
}
=================================실행 결과 ================================
10
20
30

char *strArr[3];  //길이가 3인 char형 포인터 배열 

char형 포인터 배열은 문자열의 주소 값을 저장할 수 있는 배열이다.

char *strArr={"Simple", "ABC", "DEF"}; 이렇게 값을 초기화 할 수 있다.

"큰따옴표로 묶여서 포연되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소값이 반환된다"

반환된 주소 값은 문자열의 첫 번째 문자의 주소 값이다.

 


인자전달의 기본방식은 값의 복사이다. 함수가 호출되고 나면 전달인자와 매개변수는 별개이고 값만 복사되는 것이다.

배열을 매개변수로 선언할 수 없으므로 배열을 전달하고 싶다면 포인터를 사용해야한다.

int arr[3]={..}; 

void function(int *param){ ...};

function(arr); .//  포인터변수로 배열의 형태로 접근이 가능하다 .

함수 안에서 배열을 접근할라면 

param[1], param[2] .. 이런식으로 접근하면 된다.

배열의 주소값만 안다면 어디서든 배열에 접근하여 저장된 값을 참조하거나 변경가능하다.

int *param 대신 int param[]으로 선언이 가능하다.(오직 매개변수의 선언에서만 같은 것이다)

 

Call-by-value : 단순히 값만 전달하는 형태의 함수 호출

Call-by-reference : 메모리의 접근에 사용되는 주소값을 전달하는 형태의 함수 호출

#include <stdio.h>

void Swap_value(int n1, int n2)
{
 int temp = n1;
 n1= n2;
 n2 = temp; 
 }
 
 void Swap_reference(int *n1, int *n2)
 {
 int temp =*n1;
 *n1 = *n2;
 *n2 = temp;
 }
 
 int main()
 {
 int num1=10;
 int num2=20;
 printf("num1 num2 : %d %d \n", num1,  num2);
 
 Swap_value(num1, num2);
 printf("num1 num2 : %d %d \n", num1,  num2);
 
 Swap_reference(&num1, &num2);
 printf("reference> num1 num2 : %d %d \n", num1,  num2);
 
 return 0;
 }
 =============실행결과=========
 num1 num2 : 10 20
 num1 num2 : 10 20
 reference> num1 num2 : 20 10

위 예제에서 보이듯이 call-by-value형식의 함수 호출은 매개변수와 전달인자가 별개이므로 값의 변화가 일어나지 않는다 .하지만 call-by-reference 형식의 함수호출은 매개변수의 주소값을 전달해주기 때문에 매개변수의 값이 바뀐다.

위의 예제를 이해하면 call-by-reference 와 call-by-value를 쉽게 이해할 것이다.

 

포인터를 대상으로 const 선언이 가능하다.

int num =20;

const int *ptr = &num;

*ptr= 30;  //에러 

num = 30; //성공

 

const를 맨 앞에선언 하면 포인터 변수 ptr을 이용해 ptr이 가리키는 변수의 값을 변경하는것을 허용하지 않게 하는것이다.

 

int num=20;

int num= 10;

int *const ptr = &num;

ptr =&num2;  //컴파일 에러

*ptr =40; //컴파일 성공

 

이렇게 선언되면 포인터 변수 ptr은 상수가 된다. 포인터 변수 ptr이 상수라는 것은 주소값이 저장되면 이제 그 값의 변경이 불가능 하다는 것이다. 가리키는 변수를 계속 가리켜야 한다는 뜻이다.

 

즉 값은 변경 가능하지만 , ptr변수가 가리키는 변수를 변경을 못한다.

 

const int * const ptr 이렇게도 선언이 가능한데

위의 두방식이 모두 적용된 것이다 . 즉 주소값도 변경 불가하고, 변수를 이용해 값을 변경도 불가하다.

 

const는 안정성을 위해 붙인다.

 

 

'휴지통 > C 언어' 카테고리의 다른 글

구조체  (0) 2021.02.04
문자열 입력 함수  (0) 2021.02.02
문자와 문자열  (0) 2021.02.01
함수 포인터, void 포인터  (0) 2021.01.31
다차원 배열과 포인터, 다중 포인터  (0) 2021.01.30