2.1 포인터
변수의 주소
포인터(pointer)
- 변수의 주소를 나타냄
변수의 주소
- 변수가 포함하고 있는 제일 첫 번째 바이트의 주소
- 변수의 주소는 변수 이름 앞에 &를 붙여줌. ex) a의 주소는 &a
포인터 변수
- 주소값을 저장하는 변수
- 포인터라고 약칭
포인터 변수의 선언 및 사용
간접 참조 연산자
- 포인터 변수가 가리키는 실제 변수의 값을 참조
#include <iostream>
using namespace std;
int main()
{
int n = 10, m;
char c = 'A';
double d;
int* p = &n; // p는 n의 주소값을 가짐
char* q = &c; // q는 c의 주소값을 가짐
double* r = &d; // r은 d의 주소값을 가짐
*p = 25; // n에 25가 저장됨
*q = 'A'; // c에 문자 'A'가 저장됨
*r = 3.14; // d에 3.14가 저장됨
m = *p + 10; // p가 가리키는 값(n 변수값)+10을 m에 저장
cout << n << ' ' << *p << "\n"; // 둘 다 25가 출력됨
cout << c << ' ' << *q << "\n"; // 둘 다 'A'가 출력됨
cout << d << ' ' << *r << "\n"; // 둘 다 3.14가 출력됨
cout << m << "\n"; // m 값 35 출력
}
#include <iostream>
using namespace std;
int main()
{
int arr[6] = { 1,2,3,4,5,6 };
int* chr_ptr;
// chr_ptr이 arr 배열이 저장되어 있는 메모리 주소값을 갖도록 초기화
chr_ptr = arr;
chr_ptr++;
cout << chr_ptr << "\n"; // 4 출력
cout << *chr_ptr << "\n"; // 2 출력
cout << arr << "\n"; // 0 출력
cout << arr + 4 << "\n"; // 16 출력
cout << &arr[3] << "\n"; // 12 출력
cout << arr[4] << "\n"; // 5 출력
// arr[3] 값을 chr_ptr을 이용해 출력
cout << *(chr_ptr + 2) << "\n";
cout << chr_ptr[2] << "\n";
}
배열의 원소를 가리키는 포인터
int arrays[10];
int* p = &arrays[5]; // int* p = arrays+5
배열과 포인터
배열 이름은 배열 메모리의 시작 주소로 다룸
1차원 배열의 이름
1차원 배열의 이름
- 1차원 배열의 첫 번째 원소의 주소값
- 포인터 상수(constant): 바뀔 수 없는 값 -> 배열의 메모리 주소가 바뀌지 않음
int nArray[10];
if(nArray == &nArray[0])
{
// 항상 이곳 실행
}
nArray = &nArray[1]; // 에러
nArray++; // 에러
원소의 주소
- 주소 연산자 사용
&nArray[2]
- 배열이름 사용
nArray+2 == &nArray[2]
1차원 배열의 이름을 사용해서 배열 탐색
주소를 이용한 원소 참조
int nArray[10];
for(int i=0; i<10; i++)
*(nArray + i) = i; // nArray[i] = i와 동일
2차원 배열과 포인터
2차원 배열의 이름
int M[3][3];
- 배열의 시작 주소
M = &M[0]
*M == M[0]
M+1 == &M[1]
*(M+1) == &M[1][0]
**(M) == M[0][0]
**(M+1) == M[1][0]
부분 배열의 이름
- 부분 배열의 시작 주소
M[0] == *M // M[0]은 0행의 시작주소
m[1] == *(M + 1) // M[1]은 2행의 시작주소
- 포인터 자료형
*(M[1]) == M[1][0] // 값
*(M[1]+1) == M[1][1] // 값
*(M[1]+2) == M[1][2] // 값
포인터 배열
포인터 배열은 포인터 변수들을 배열 요소로 갖는 배열
2차원 배열과 포인터 배열
1차원 배열의 배열명을 포인터 배열에 저장하면 포인터 배열을 2차원 배열처럼 사용할 수 있다.
int ary1[4] = {1,2,3,4};
int ary2[4] = {11,12,13,14};
int ary3[4] = {21,22,23,24};
int* ptr_ary[3] = {ary1, ary2, ary3}; // 각 배열명을 포인터 배열에 초기화
문자열 포인터 배열
문자열 포인터 배열
- 많이 사용되는 포인터 배열
- 문자열들을 효율적으로 저장
- 문자열 상수는 문자열의 시작주소
문자열 포인터 배열과 2차원 문자 배열
초기화
- 2차원 문자 배열: 모든 문자열이 배열에 복사
- 문자열 포인터 배열: 주소값만 복사
char s[10] = "dog";
char *p = "dog";
cout<<(s=="dog") // 0 출력
cout<<(p=="dog") // 1 출력
// "dog" 자체가 상수를 관리하는 메모리 영역 (Text 영역)에 있어서
// 한 번 선언되면 중복해서 선언되지 않는다.
// 따라서 s에 담긴 "dog"을 제외한 나머지 "dog"은 동일한 "dog"이다.
원소의 접근 및 변경
- 2차원 문자 배열
- 문자열 포인터 배열
// 원소 접근
cout<<(animal[0][1] == 'o'); // 1
cout<<(animal[0] == "dog"); // 1
cout<<(ptr_ary[0][1] == 'o'); // 1
cout<<(ptr_ary[0] == "dog"); // 1
// 원소 변경
animal[0][1] = 'i'; // "dog"이 "dig"으로 바뀜
ptr_ary[0] = "cat"; // 교체는 가능하다. 주소값을 바꾸는 것이기 때문이다.
ptr_ary[0][1] = 'i'; // 에러, 상수이기 때문에 수정불가
이중 포인터
이중 포인터
- 포인터 변수를 가리키는 포인터 변수
참고) 포인터는 메모리 주소 단위로 결정된다. ex) 32 bit OS에서는 4 byte, 64 bit OS에서는 8 byte
배열 포인터
- 배열을 가리키는 포인터
int arr[2][3] = { {11,12,13}, {21,22,23} };
int(*parr)[3]; // 배열 포인터
int* p; int** pt;
parr = arr;
cout << parr << arr;
cout << parr + 1 << arr + 1; // 주소, 3 x 4byte 만큼 늘어남, parr+1 == arr+1
cout << *(parr + 1) << parr[1] << *(arr + 1) << arr[1]; // 주소
cout << **(parr + 1) << **(arr + 1) << *arr[1] << arr[1][0]; // 21
p = arr[0];
cout << p << arr[0] << *arr; // p == arr[0] == *arr 각 원소의 시작주소
cout << *(p + 1) << *(arr[0] + 1) << *(*arr + 1); // 12
pt = &p; // pt = arr; (X)
cout << *pt << p; // *pt == p
cout << **pt << *p; // **pt == *p
- 2차원 배열을 포인터로 사용하기 위해서는 배열 포인터로 사용하는 것이 좋다.
const와 포인터
포인터 변수에 const 속성을 부여할 때의 고려사항
- 포인터 변수 자체에 const 속성 부여
- 포인터 변수가 가리키는 변수에 const 속성 부여
포인터 변수에 const 속성을 적용한 3가지 경우를 비교
- 포인터 변수가 가리키는 변수가 const인 경우 (포인터로 변수 변경할 수는 있지만 값을 변경할 수는 없음)
int i1 = 10;
int i2 = 20;
const int* p = &i1;
p = &i2; // 가능
*p = 30; // 에러
- 포인터 변수 자신이 const인 경우 (포인터 변수를 변경하지 못지만, 값을 변경할 수는 있음)
int i1 = 10;
int i2 = 20;
int* const p = &i1;
p = &i2; // 에러
*p = 30; // 가능
두 변수 모두 const인 경우 (포인터 변수, 값 모두 변경 못함)
int i1 = 10;
int i2 = 20;
const int* const p = &i1;
p = &i2; // 에러
*p = 30; // 에러
2.2 레퍼런스
레퍼런스의 선언 및 사용
레퍼런스(Reference) 선언
참조자 & 도입
이미 존재하는 변수에 대한 다른 이름 선언
- 참조 변수는 이름만 생김
- 참조 변수에 새로운 공간을 할당하지 않음
- 초기화로 지정된 기존 변수 공유
const 레퍼런스
const 레퍼런스
- 레퍼런스를 선언할 때 const 키워드를 함께 사용하면 레퍼런스가 참조하는 변수의 값을 변경할 수 없다.
- 레퍼런스는 자신이 참조하는 변수에 읽기 전용의 접근(read-only access)만 가능하다.
int data = 100;
const int &ref = data;
cout << ref; // cout << data;와 같다
ref = 200; // const 레퍼런스는 변경할 수 없으므로 컴파일 에러
data = 200; // data를 직접 변경하는 것은 가능
참조에 의한 호출
레퍼런스(참조) 변수를 가장 많이 활용하는 사례 : call by reference
함수의 매개 변수를 레퍼런스 타입으로 선언
- 참조 매개 변수(reference parameter)라고 부름
- 참조 매개 변수의 이름만 생기고 공간이 생기지 않음
- 참조 매개 변수는 실인자 변수 공간 공유
- 참조 매개 변수에 대한 조작은 실인자 변수 조작 효과
int main(){
int x = 10;
foo(x);
cout<<"x = "<<x<<"\n;
}
void foo(int &ref){
ref++; // ref는 x의 별명이므로, main 함수의 x를 증가시킨다.
}
2.3 함수에서의 인자 전달
함수의 인자 전달
함수의 인자 전달 방법을 결정하는 기준
- 함수를 호출할 때 넘겨준 인자를 함수 안에서 사용만 하고 변경하지 않을 때 -> 값에 의한 전달 (call by value)
- 함수를 호출할 때 넘겨준 인자를 함수 안에서 변경해야 할 때-> 포인터에 의한 전달이나 레퍼런스에 의한 전달 (call by reference)
const 레퍼런스의 전달
함수의 인자로 사용할 때의 const 레퍼런스
- 구조체나 클래스 형을 넘길 때 유용하게 사용된다.
- 구조체를 인자로 전달할 때 레퍼런스로 전달하면 구조체를 복사하지 않고 별명으로만 전달한다.
#include <string>
using namespace std;
typedef struct STUDENNT {
string name;
int grade[4];
}STUDENT;
void Print(const STUDENT& s)
{
s.grade[0] = 0; // 함수 안에서 s를 변경할 수 없으므로 컴파일 에러
}
int main()
{
STUDENT s1 = { "엄준식", 100, 90, 80, 99 };
Print(s1);
}
함수의 인자전달 방법 - 포인터
포인터의 타입을 인자로 전달하는 방법의 정리
- 함수의 매개 변수는 포인터 타입으로 정의한다.
- 인자를 넘겨줄 때는 결과 값을 담고 싶은 변수의 주소를 넘겨준다.
- 함수 안에서 결과를 넘겨줄 때는 매개변수가 가리키는 곳에 값을 넣어준다.
void GCD_LCM(int a, int b, int* pgcd, int* plcm)
{
// ...
*pgcd = 5; // pgcd는 gcd를 가리키고 있으므로 gcd의 실제 값이 바뀜
}
int main()
{
// ...
GCD_LCM(28, 35, &gcd, &lcm);
}
함수의 인자전달 방법 - 레퍼런스
레퍼런스 타입을 인자로 전달하는 방법의 정리
- 함수의 매개 변수는 레퍼런스 타입으로 정의한다.
- 인자를 넘겨줄 때는 결과 값을 담고 싶은 변수를 그대로 넘겨준다.
- 함수 안에서 결과를 넘겨줄 때는 매개 변수에 값을 넣어준다.
void GCD_LCM(int a, int b, int& pgcd, int& plcm)
{
// ...
pgcd = 5; // pgcd는 gcd의 별명이므로 gcd의 실제 값이 바뀜
}
int main()
{
// ...
GCD_LCM(28, 35, gcd, lcm);
}
함수의 인자전달 방법 - 1차원 배열
배열을 인자로 전달하는 방법의 원리
- 배열 전체를 한 번에 보낼 수 없으므로, 배열의 시작 주소를 보냄
- 참조에 의한 호출(call-by-reference)를 사용하여 전달
1차원 배열의 전달
- 배열의 주소를 넘겨준다
- 인자로 넘어온 배열을 사용할 때는 그냥 평범한 배열을 사용하듯이 하면 된다.
void UsingArray(int arr[])
{ // int arr[3], int *arr 와 같다. 사람에 따라 다르게 의미를 받아들임
// ...
cout<<arr[0]<<*(arr+1)<<"\n";
}
int main()
{
int arr1[3] = {1,2,3};
UsingArray(arr1);
UsingArray(arr1 + 1);
UsingArray(&arr1[2]);
}
함수의 인자전달 방법 - 2차원 배열
2차원 배열의 전달
- 1차원 배열의 경우와 같이 배열의 주소 전달
- 호출된 함수에서는 몇 개의 원소가 있는지 알 수 있도록 함
- 1차원 원소의 개수는 절대로 생략하지 않는다. -> offset을 뛸 때 사용하기 때문이다.
참고) 2차원 배열의 경우는 이중 포인터로 받아도 됨
void Using2DArray(int arr[][3]){
// int arr[5][3];
// ...
}
int main()
{
int array2[5][3] = { {1,2,3}, {4,5,6}, {7,8,9}, {10,11,12}, {13,14,15} };
Using2DArray(array2);
}
참조 리턴
C 언어의 함수 리턴
함수는 반드시 값만 리턴
- 기본 타입 값: int, char, double 등
- 포인터 값
C++의 함수 리턴
함수는 값 외에 참조 리턴 가능
- 참조 리턴
- 변수 등과 같이 현존하는 공간에 대한 참조 리턴
- 변수의 값을 리턴하는 것이 아님
값을 리턴하는 함수 vs. 참조를 리턴하는 함수
'공부 > C++' 카테고리의 다른 글
4. 클래스와 객체의 기본 (0) | 2024.04.09 |
---|---|
3. 개선된 함수 기능 (0) | 2024.03.31 |
1. C++ 프로그래밍의 기본 (2) | 2024.03.26 |
0. C++ 시작 (0) | 2024.03.24 |
C++ 공부 (0) | 2024.03.14 |