[C++] 03. 배열과 포인터 그리고 참조

83759 단어 cppcpp

3.1 1차원 배열과 다차원 배열

- 1차원 배열을 이용한 100명의 점수 저장

  • 배열
    - 같은 타입의 변수 여러 개를 묶어 하나의 변수 이름으로 처리
    - int ary[100];
    - 배열 선언 시 원소의 개수로는 상수만 허용 -> int ary[count]; (x)
    - 배열 사용 시에는 원소 인덱스로 상수, 변수 모두 가능
    • ary[i] = 5; or ary[2] = 5;
int main()
{
	int scores[100];	// 초기화하지 않을 경우 쓰레기값 저장
    
    for i< 100; i+)
    	scores[i] = i + 1;	// score[0] => 첫 번째 원소 변수
        
    for(int i = 0; i < 100; i++)
    	cout << "scores[" << i << "]" <<scores[i] << "\t";
        
    cout << endl;
    
    return 0;
}

- 배열 선언 시 초기화 및 2차원 배열

  • 배열 선언 시 각 원소에 대한 초기화
    1) int scores[5] = {1, 2, 3, 4, 5}; // 첫 번째 원소부터 1, 2, 3, 4, 5
    2) int scores[5] = {1, 2}; // scores[0] = 1, scores[1] = 2, 나머지는 0
    3) int scores[5] = {0}; // 모든 원소의 값을 0으로 초기화
    4) int scores[] = {1, 2, 3}; // 3개의 원소를 갖는 배열
    5) int scores[]; // 에러, 크기를 결정할 수 없음
  • 2차원 배열 : int ary[3][4]; -> 3행 4열의 2차원 배열
    - 4개의 원소를 갖는 1차원 배열 3개가 연속해서 나열됨
int main()
{
	 int scores[100][3];
     
     for(int i = 0; i < 100; i++)
     {
     	for(int j = 0; j < 3; j++)
        {
        	scores[i][j] = 3 * i + (j + 1);
            // i, j 값이 변함에 따라 1~300의 값이 됨
        }
     }
     
     for(int i = 0; i < 100; i++)
     {
     	for(int j = 0; j < 3; j++)
        {
        	cout << "scores9" << i << "][' << j << "]" << scores[i][j] << "\t";
        }
        cout << endl;
    }
    return 0;
}

- 2차원 배열의 선언 시 초기화

  • 2차원 배열의 선언 및 초기화 예
    1) int scores[2][3] = {{10, 20, 30}, {40, 50, 60}};
    : scores[0][0] = 10, scores[0][1] = 20, ..., csores[1][2] = 60으로 초기화
    2) int scores[2][3] = {10, 20, 30, 40, 50, 60};
    : 1)과 동일
    3) int scores[2][3] = {{10}, {40, 50}};
    : scores[0][0] = 10, scores[1][0] = 40, scores[1][1] = 50, 나머지는 0;
    4) int scores[2][3] = {10, 40, 50};
    : scores[0][0] = 10, scores[0][1] = 40, scores[0][2] = 50, 나머지는 0;
    5) int scores[2][3] = {0};
    : 모든 원소의 값을 0으로 초기화
    6) int scores[][3] = {{1, 2, 3}, {4, 5, 6}}
    : 2행 3열의 2차원 배열
    7) int scores[2][] = {{1, 2, 3}, {4, 5, 6}} // 에러
    8) int scores[][] = {{1, 2, 3}, {4, 5, 6}} // 에러

3.2 포인터

- 주소 연산자(&)의 사용

  • 주소 연산자
    - 특정 변수의 시작 주소 반환 : &var
    - 주소값은 주로 16진수로 표현 : 2진수를 낮은 자리부터 4비트 단위로 끊어 변환
  • 배열의 저장
    - 연속 저장
int main()
{
	int var1 = 1;
    int var2[3] = {1, 2, 3};
    char var3[3] = {'A', 'B', 'C'};
    double var4[3] = {1.1, 2.2, 3.3};
    
    cout << "var1	: " << &var1 << ", " << (int)&var1 >> endl;
    for(int i = 0; i < 3; i++)	// var2
    {
    	cout << "var2[" << i << "] : " << (int)&var2 << endl;
    	cout << endl;
    }
    
    for(int i = 0; i < 3; i++)	// var3
    {
    	cout << "var3[" << i << "] : " << (int)&var3 << endl;
    	cout << endl;
    }
    
    for(int i = 0; i < 3; i++)	// var4
    {
    	cout << "var4[" << i << "] : " << (int)&var4 << endl;
    	cout << endl;
    }
    
    return 0;
}
[결과]
 각 배열의 주소값 출력

- 메모리 주소를 저장하는 포인터 변수 선언

  • 포인터
    - 다른 변수(또는 함수)의 메모리 주소
    - 포인터 변수 : 포인터를 저장하기 위한 변수, 항상 4바이트 (주소 체계)
  • 포인터의 타입
    - 저장하고자 하는 다른 변수(또는 함수)의 타입에 따라 달라짐
    - int num;
    - int *ptr = &num // ptr은 int 변수의주소를 저장하는 포인터
int main()
{
	int i_num = 3;
    int *i_ptr;
    double d_num = 1.1;
    double *d_ptr;
    
    i_ptr = &i_num;	// i_ptr 포인터 변수에 i_num의 주소 대입
    d_ptr = &d_num;	// d_ptr 포인터 변수에 d_num의 주소 대입
    
    cout << "i_num의 주소 : " << (int)&i_num << endl;
    cout << "i_ptr의 값	: " << (int)i_ptr << endl;
    cout << "d_num의 주소 : " << (int)&d_num << endl;
    cout << "d_ptr의 값	: " << (int)d_ptr << endl;
    
    return 0;
}
[결과]
i_num의 주소	: 6093296
i_ptr의 값	: 6093296
d_num의 주소 : 6093298
d_ptr의 값	: 6093298

- 역참조 연산자를 이용한 포인터 변수의 활용

  • 역참조 연산자
    - 포인터 변수를 통해 현재 가리키는 변수의 값을 읽거나 변경할 수 있음
    - 역참조 연산자 : *ptr = 5; //num = 5;와 동일
int main()
{
	int num = 3;
    int *ptr = &num
    
    cout << "num의 주소			: " << &num << endl;
    cout << "num의 값			: " << num << endl;
    cout << "ptr의 주소			: " << &ptr << endl;
    cout << "ptr의 값			: " << ptr << endl;
    cout << "ptr가 가리키는 변수	: " << &num << endl;
    
    *ptr = 5;	// num의 값을 5로 변경
    
    cout << "num의 주소	: " << &num << endl;
    cout << "ptr가 가리키는 변수	: " << *ptr << endl;
    
    return 0;
}

- 널(null) 포인터와 void 포인터

  • void 포인터 변수
    - 다른 변수(또는 함수)의 메모리 주소를 저장하기 위해 사용
    - 그러나, 역참조 연산자 사용 불가
    - non-void 포인터 -> void 포인터로 묵시적 형변환 가능, 역은 불가
  • 널 포인터
    - NULL : 0의 값을 가진 특수한 포인터 상수 (#define NULL 0)
    - if(ptr == NULL) {......} // 아직 유효한 주소가 아님을 인식할 수 있음
int *ptr1;	// 전역 int 포인터 변수

int main()
{
	int *ptr2 = NULL;	// 널 포인터 상수 NULL 대입
    int *ptr3;
    void *ptr4 = (void *)ptr2;
    // 묵시작 형변환 가능 => 명시적 형변환 (void *) 생략 가능
    
    cout << "ptr1 : " << ptr1 << endl;
    cout << "ptr2 : " << ptr2 << endl;
    cout << "ptr4 : " << ptr4 << endl;	// ptr4의 값을 먼저 출력
    cout << "ptr3 : " << ptr3 << endl;	// 오류 발생 예정
    
    return 0;
}
[결과]
ptr1 : 00000000
ptr2 : 00000000
ptr4 : 00000000
ptr3 : CCCCCCCC

3.3 포인터 연산

- 포인터 변수에 대한 덧셈과 뺄셈

  • 포인터 연산
    - 덧셈, 뺄셈만 가능 : +, -, ++, --, +=, -=
  • 포인터 변수에 대한 +1, -1의 의미
    // 다음 주소를 가리켜라, 이전 주소를 가리켜라
    - int ptr = NULL; ptr++; -> ptr의 값은? 4
    - double
    ptr = NULL; ptr++; -> ptr의 값은? 8
    - +1 : 가리키는 대상의 바이트 수만큼 증가
    - -1 : 가리키는 대상의 바이트 수만큼 감소

int main()
{
	int *iptr = NULL;
    double *dptr =NULL;
    
    cout << "초기값	: itpr = " << ", dptr = " << dptr << endl;
    
    iptr++, dptr++;	// 1 더하기: iptr = iptr + 1, dptr = dptr + 1
    cout << "1 더하기	: iptr = " << iptr << ", dptr = " << dptr << endl;
    
    iptr--, dptr--;	// 1 빼기: iptr = iptr - 1, dptr = dptr - 1
    cout << "1 더하기	: iptr = " << iptr << ", dptr = " << dptr << endl;
    
    iptr++, dptr++;	// 2 더하기: iptr = iptr + 2, dptr = dptr + 2
    cout << "1 더하기	: iptr = " << iptr << ", dptr = " << dptr << endl;
    
    iptr--, dptr--;	// 2 빼기: iptr = iptr - 2, dptr = dptr - 2
    cout << "1 더하기	: iptr = " << iptr << ", dptr = " << dptr << endl;
    
    iptr -= 2, dptr -= 2;	// 2 빼기
    cout << "2 빼기	: iptr = " << iptr << ", dptr = " << dptr << endl;
    
    iptr = 100 + iptr, dptr = 100 + dptr;	// 100 더히기
    cout << "100 더하기 : iptr = " << iptr << ", dptr = " << dptr << endl; 
}

3.4 배열과 포인터

- 포인터 변수로 배열에 접근

  • 배열의 원소를 가리키는 포인터
    - 일반 포인터와 동일
    - int *ptr = &ary[0];
    - ptr++는? 다음 원소를 가리킴
    - ptr--는? 이전 원소를 가리킴
int main()
{
	int ary[5] = {1, 2, 3, 4, 5};
    int *ptr;
    
    ptr = &ary[0];	// 첫 번째 원소를 가리킴
    
    for(int i = 0; i < 5; i++)
    {
    	cout << "ary[" << i << "]" << *ptr << endl;	
        ptr++;	// 다음 원소를 가리킴
    }
    
    return 0;
}
[결과]
ary[0] 1
ary[1] 2
ary[2] 3
ary[3] 4
ary[4] 5

- 배열 이름과 배열 첨자 연산자의 정체

  • 배열 이름
    - int ary[5];
    - 배열 이름 ary -> 첫 번째 원소의 주소로 묵시적 형변환
    - 형변환 결과는 rvalue, 즉, 상수이므로 값 변경 불가 : ary = 2000; x
    - int *ptr = &ary;는 동일한 의미

- 배열은 포인터처럼, 포인터는 배열처럼 사용

  • 배열은 포인터처럼 사용 가능
    - *(ary + 1) = 5;
  • 포인터는 배열처럼 사용 가능
    - ptr[1] = 5;
int main()
{
	int ary[5] = { 1,2,3,4,5 };
	int* ptr;

	ptr = ary;	// 배열 이름은 첫 번째 원소에 대한 포인터를 의미

	for (int i = 0; i < 5; i++)	//배열을 포인터처럼 사용
	{
		*(ary + i) = *(ary + i) * 100;	// ary[i] = ary[i] * 100	
	}

	for (int i = 0; i < 5; i++)	// 포인터를 배열처럼 사용
	{
		cout << "ptr[" << i << "] : " << ptr[i] << endl;	// *(ptr + i)
	}

	return 0;
}

3.5 참조

- 참조의 개념(1)

  • 참조
    - 기존 변수(또는 함수)에 대한 또 다른 이름(주소가 아님)
    - int var1 = 3;
    - int &ref = var1; // var1과 ref는 100% 동일한 변수, 이름만 서로 다름
    - 참조 변수 선언 시 초기화 필수
  • 잘못된 예
    - int &ref = var1 + var2;
    - int &ref = 3;
int main()
{
	int var1 = 3;
	int var2 = 5;
	int &ref = var1;	//ref와 var1은 동일한 변수

	ref = 7;
	cout << "var1 : " << var1 << ", var2 : " << var2 << ", ref : " << ref << endl;

	ref = var2;	// var1에 var2의 값을 대입하는 것과 동일
	cout << "var1 : " << var1 << ", var2 : " << var2 << ", ref : " << ref << endl;

	return 0;
}

- 참조의 개념(2)

  • 참조 변수 선언 이후에는 참조 변수 또한 일반 변수와 동일함
    - cf) 포인터 : 가리키는 대상이 변경될 수 있음
int main()
{
	int var = 2;
	int& ref1 = var;	// ref1과 var는 동일한 변수
	int& ref2 = ref1;	// ref2와 ref1은 동일한 변수, 모두 동일한 변수

	ref1 = 5;

	cout << "var : " << var << endl;
	cout << "ref : " << ref1 << endl;
	cout << "ref2 : " << ref2 << endl;

	return 0;
}

3.6 함수와 배열, 포인터, 참조

- 함수 포인터와 함수 참조

  • 함수 포인터
    - 함수의 주소를 저장하는 포인터 변수
    - int(*ptr)(int, int)
    - 함수명은 함수의 주소로 묵시적 변환
  • 함수 참조
    - 해당 함수에 대한 또 다른 이름
    - int(&ref)(int, int) = Sum;
    배열이나 함수의 이름은 주소를 가리킨다
int Sum(int num1, int num2)
{
	return(num1 + num2);
}

int main()
{
	int(*ptr)(int, int) = Sum;
	int(&ref)(int, int) = Sum;

	cout << "Sum : 3 + 4 = " << Sum(3, 4) << endl;
	cout << "ptr : 3 + 4 = " << ptr(3, 4) << endl;	//(*ptr)(3,4)와 동일
	cout << "ref : 3 + 4 = " << ref(3, 4) << endl;

	return 0;
}
[결과]
Sum : 3 + 4 = 7
ptr : 3 + 4 = 7
ref : 3 + 4 = 7

- 값에 의한 전달 Vs. 참조에 의한 전달(1)

  • 값을 교환하는 Swap 함수의 문제점 (값에 의한 전달)
    - 실매개변수의 값 교환 실패
void Swap(int x, int y)	// 값에 의한 전달
{
	int temp = x;	// x와 y의 값 교환
	x = y;
	y = temp;
}

int main()
{
	int num1 = 3, num2 = 4;
	cout << "교환 전 : num1 = " << num1 << ", num2 = " << endl;

	Swap(num1, num2)	//교환
	cout << "교환 후 : num1 = " << num1 << ", num2 = " << num2 << endl;

	return 0;
}

- 값에 의한 전달 Vs. 참조에 의한 전달(2)

  • Swap 함수의 문제점 해결
    - 방법 1 : 주소값의 전달
    - 방법 2 : 참조에 의한 전달
// 주소값의 전달
void Swap(int* x, int* y)
{
	int temp = *x
		* x = *y
		* y = temp;
}
//호출
int num1, num2;
Swap(&num1, &num2);
// 참조에 의한 전달
Void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}
// 호출
int num1, num2;
Swap(num1, num2);
// 주소값의 전달
void Swap(int* x, int* y)
{
	int temp = *x;
	*x = *y;
	*y = temp;
}

int main()
{
	int num1 = 3, num2 = 4;
	cout << "교환 전 : num1 = " << num1 << ", num2 = " << num2 << endl;

	Swap(&num1, &num2);	// 교환
	cout << " 교환 후 : num1 = " << num1 << ", num2 = " << num2 << endl;

	return 0;
}

- 배열에 대한 참조 (피피티에 없다)

  • 함수 이름의 참조나 함수 주소의 포인터 변수와 비슷한 형식
    ex) int a[3] = {1, 2, 3};
    int(&b)[3] = a; // 크기 생략 불가능
void func(int(&arr)[3])
{
	arr[0] = 10;
	arr[1] = 20;
	arr[2] = 30;
}
int main()
{
	int a[3] = { 1,2,3 };
	func(a);
	for (int i : a)
	{
		cout << i << endl;
	}
	return 0;
}
[결과]
10
20
30

- 1차원 배열의 매개변수 전달

  • 1차원 배열의 매개변수 전달
    - 값에 의한 전달 불가
    - 첫 번째 원소의 주소를 전달 -> 포인터를 이용해 접근
    - 원소의 개수 전달 필요
int Sum(int* ptr, int count)	// int Sum(int ptr[], int count)과 동일
{
	int result = 0;
	for (int i = 0; i < count; i++)
	{
		result += ptr[i];	// 포인터는 배열처럼 사용
	}
	return result;
}

int main()
{
	int ary[5] = { 1,2,3,4,5 };	// int 배열
	int result = Sum(ary, 5);	// 배열의 첫 번째 주소와 원소의 개수 전달

	cout << "합 : " << result << endl;

	return 0;
}

- 2차원 배열의 매개변수 전달

  • 2차원 배열의 매개변수 전달
    - 1차원 배열과 동일 : 펏 번째 원소의 주소 전달 -> 포인터로 접근
    - 원소의 개수(행의 개수)전달 필요 // 가장 큰 차원의 크기
    - 열의 개수([4]) 생략 불가 : 열의 개수가 4인 2차원 배열만 처리가능
    - 임의의 행, 열의 개수를 가진 2차원 배열 처리 -> 메모리 동적 할당 필요
int Sum(int(*ptr)[4], int count)
{
	int result = 0;
	for (int i; i < count; i++)
	{
		for (int j = 0; j < 4; j++)
		{
			result += ptr[i][j];	// 포인터는 배열처럼 사용
		}
	}
	return result;
}

int main()
{
	// ary : 2차원 배열
	int ary[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	int result = Sum(ary, 3);	// ary는 ary[0]의 주소

	cout << "합 : " << result << endl;

	return 0;
}

- 정리 : 다차원 배열의 매개 변수 전달

- 함수의 매개변수 전달

  • 함수를 매개변수로 전달하는 방법
    - 함수의 주소 전달
    -> 함수 포인터로 전달받아 함수와 동일하게 사용 가능

    - void ExecuteFunc(int (*Func)(int, int), int x, int y)
    - void ExecuteFunc(int (*Func)(int, int), int x, int y)
int Sum(int num1, int num2)
{
	return(num1 + num2);
}

void ExecuteFunc(int(*Func)(int, int), int x, int y)
{
	cout << "Func(Sum) 함수 실행 결과 : " << Func(x, y) << endl;
}

int main()
{
	ExecuteFunc(Sum, 3, 4);

	return 0;
}

- 값의 반환

  • return z;에 의한 값의 반환 원리
    1) 4라인 : z에 대한 임시변수 생성
    2) 4라인 : 지역 변수 z 메모리 해제
    3) 12라인 : 임시변수 전달
    4) 12라인 : 임시변수 대입 후 임시변수 메모리 해제
int Sum(int x, int y)
{
	int z = x + y;
	return z;	// 임시변수 생성
}

int main()
{
	int num1 = 3, num2 = 4;
	int result;

	result = Sum(num1, num2);	// 임시변수 사용(대입) 후 소멸
	cout << result << endl;

	return 0;
}

- 포인터(주소)의 반환

  • 변수의 주소값 반환 가능
    - 반환형이 포인터 타입이 됨
    - 지역 변수의 주소를 반환하지 않도록 주의
int* Sum(int& z, int x. int y)
{
	z = x + y;
	return &z;	// z의 주소 = num1의 주소
}

int main()
{
	int num1;
	int* result1 = Sum(num1, 1, 1);
	return 0;
}
#include <iostream>
using namespace std;

int* Sum(int x, int y)	// 반환형 : int *
{
	int z = x + y;
	return &z;	// z 변수의 주소 반환
}

int main()
{
	int* result1 = Sum(1, 1);	// 결과는 2?
	int* result2 = Sum(2, 2);	// 결과는 4?

	cout << "result1 : " << *result1 << endl;	// 2?
	cout << "result2 : " << *result2 << endl;	// 4?

	return 0;
}
[결과]
result1 : -858993460
result2 : -858993460

- 참조의 반환

  • 참조의 반환 = 변수 그 자체를 반환
    - 반환형이 참조 타입이 됨
    - 지역 변수의 주소를 반환하지 않도록 주의 -> 아래 프로그램의 문제점은?
#include <iostream>
using namespace std;

int &Sum(int x, int y)	// 반환형 : int & 참조
{
	int z = x + y;
	return z;	// z 변수 그 자체 반환
}

int main()
{
	int& result1 = Sum(1, 1);	// 결과는 2?
	int& result2 = Sum(2, 2);	// 결과는 4?

	cout << "result1 : " << result1 << endl;	// 2?
	cout << "result2 : " << result2 << endl;	// 4?

	return 0;
}
[결과]
result1 : -858993460
result2 : -858993460
#include <iostream>
using namespace std;

int &Min(int &x, int &y)	// 매개변수 참조 전달 및 참조 변환
{
	return ((x < y) ? x : y);
}

int main()
{
	int num1 = 3, num2 = 4;
	Min(num1, num2) = 5;

	cout << "num1 = " << num1 << endl;
	cout << "num2 = " << num2 << endl;

	return 0;
}
[결과]
num1 = 5
num2 = 4

- 참조 반환의 예

  • GetArray 함수
    - 배열 원소 그 자체를 반환
    - 참조를 반환함에 따라 GarArray 함수 호출 결과가 변수가 되고, 이에 따라 대입문의 왼족에 올 수 있음
#include <iostream>
using namespace std;

int& GetArray(int* ary, int index)	// 참조 반환
{
	return ary[index];	// index에 해당하는 변수 자체 반환
}

int main()
{
	int i;
	int ary[5];

	for (i = 0; i < 5; i++)
	{
		GetArray(ary, i) = i;	// ary[i] = i;와 동일
	}

	for (i = 0; i < 5; i++)
	{
		cout << "ary[" << GetArray(ary, i)<<"]" << endl;
	}

	return 0;
}
[결과]
ary[0]
ary[1]
ary[2]
ary[3]
ary[4]

3.8 메모리 동적 할당

- 메모리 동적 할당의 의미와 방법

  • 메모리 동적 할당
    - 메모리 동적 할당
    • int 변수 하나가 필요하다면? 10개가 필요하다면?
    • 실행 중에 필요한 변수의 개수가 결정된다면?
      -> 프로그램 실행 중 필요한 만큼의 메모리를 확보하는 방법
  • 메모리 동적 할당 방법
    - C언어 : malloc, free 함수
    - C++ 언어 : new, delete 연산자
  • 메모리 동적 할당 예
    1) int *ptr = (int *) malloc(4);
    2) int *ptr = (int *) malloc(sizeof(int)*1);
    3) int *ptr = (int *) malloc(sizeof(int)*10);
    4) int *ptr = new int;
    5) int *ptr = new int[10];
    6) int *ptr = new int(100); // 메모리 할당과 동시에 초기화

- 1개의 int 변수 동적 할당

  • 포인터 변수 ptr과 동적 생성 변수의 관계
    - int *ptr = new int(100);

- 5개의 int 변수 동적 할당

  • 포인터 변수 ptr과 동적 생성 배열의 관계
    - int *ptr = new line[5];

3.9 문자와 문자열 저장하고 출력하기

- 문자와 문자열 저장하고 출력하기

  • 문자 저장 : char 변수
  • 문자열 저장 : char 배열
  • 문자열 상수 "HI! C++"의 구조 : 문자열은 널문자(\0)로 종료
  • char str[10] = "HI! C++";의 메모리 구조
    - 한글 1 글자 : 2바이트 사용

- 문자열을 표현하기 위한 char 배열과 char*의 차이

  • 다음 프로그램의 문제점은?
    - str1 : 변수 개념
    - str2 : 변수를 가리키는
    - str3 : 상수를 가리키는

- 문자 입력 및 출력 함수

  • 문자 입력 : cin 객체, getchar 함수
    - getchar 함수의 반환형은 int :
    • eof-of-file 상수(EOF) 처리
      - 1바이트 (char)로 처리시 데이터와 EOF 값을 구붆하기 어려움
      - int형 처리 시 EOF와 데이터(최하위 바이트만 1s) 구분
    • ZLQHEM DLQF

좋은 웹페이지 즐겨찾기