[C++] 03. 배열과 포인터 그리고 참조
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개가 필요하다면?
- 실행 중에 필요한 변수의 개수가 결정된다면?
-> 프로그램 실행 중 필요한 만큼의 메모리를 확보하는 방법
- 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
- eof-of-file 상수(EOF) 처리
Author And Source
이 문제에 관하여([C++] 03. 배열과 포인터 그리고 참조), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@jyj1055/C-03.-배열과-포인터-그리고-참조저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)