공부/C++

포인터, 주소 연산자 (&), 간접 참조 연산자 (*), 주소 연산, 배열과 포인터의 관계, 배열과 포인터 주소연산, 배열 포인터, 이차원 배열과 배열 포인터, 포인터 배열과 문자열, 함수, 함수화, 스코프, 블록 스코프, 함수 스코프, 변수의 종류, 함수의 반환

월러비 2025. 6. 9. 18:24

포인터

int a = 10;
int* ptr_a;
ptr_a = &a;

*ptr_a = 20;
printf("a = %d\\\\n", a);
printf("*ptr_a = %d\\\\n", *ptr_a);
  • 포인터 변수의 메모리는빌드하는 크기에 따라 고정된다.
    • 32비트로 빌드하면 32비트, 64비트 빌드면 64비트(8바이트)로 고정이다.
  • 포인터 변수 : 데이터가 저장되는 변수의 ‘메모리 주소’를 가리키는 변수다.
    • 저장된 메모리의 주인이 사라져도 포인터 변수에 저장된 주소는 계속 남아있는다.
      • 오류가 발생한다.
      • 초기화를 해줘야한다.
    • 메모리 주소는 프로그래머가 지정할 수 없다.
      • ptr_a = 20; ⇒ 불가능
  • 주소 연산자 (&)
    • 단항 연산자
    • 피연산자의 물리적인 메모리 주소를 반환한다.
    • %p로 출력한다.
  • 포인터 연산자 (*)
    • 단항 연산자
    • 해석하는 법 : 선언한 변수에 붙은 *을 지운 데이터형을 확인한다.
      • int* → *를 쓰면 int 값을 확인하는 것이다.
      • int** → 를 쓰면 int* 값을 확인하는 것이다.
    • 피 연산자는 ‘포인터 변수(주소값)’만 올 수 있다.
      • &(&a) ⇒ 불가능

주소 연산

int a = 10;
int* ptr_a = &a;

printf("&a = %d\\\\n", &a);
printf("(&a) + 1 = %d\\\\n", (&a) + 1);
printf("(&a) + 2 = %d\\\\n", (&a) + 2);

printf("ptr_a = %d\\\\n", ptr_a);
printf("ptr_a + 1 = %d\\\\n", ptr_a + 1);
printf("ptr_a + 2 = %d\\\\n", ptr_a + 2);
  • 주소에 1을 더하면 다음 변수 크기만큼 주소 증가
    • 산술 연산(+, -)은 좌항의 데이터 형의 메모리 크기만큼 증감하는 것이다.
  • int형의 경우 4바이트씩 증가
  • 이것은 ‘배열’에서 사용한다.
    • 인덱스마다 연속된 주소가 담기니까

배열과 포인터의 관계

  • 같은 자료형의 메모리가 연속된 주소로 이어진 구조
    • 주소값도 연속적이다.
  • 장점
  • 단점
    • 찾는 값을 검사하려면 비교를 나올때까지 반복해야한다..
int arr[10];

printf("%d\\\\n", &arr);
printf("%d\\\\n", arr);
printf("%d\\\\n", &arr[0]);
  • 세 값이 모두 동일한 주소로 출력된다.
    • 배열명은 첫 번째 요소의 주소를 나타냄
    • arr == &arr[0]
    • 배열명은 포인터 상수로 동작
      • 상수이기에 ‘쓰기’가 안된다.

배열과 포인터 주소연산 - 코드 추가

  • &arr[3], arr + 3, ptr + 3, &ptr[3] ⇒ 모두 값이 같다.
  • arr[3], *(arr + 3), *(ptr + 3), ptr[3] ⇒ 모두 값이 같다.
    • ptr[n] ⇒ *(ptr + n)
    • *ptr = arr ⇒ ptr이 arr과 같은 주소를 가리키게 되어 ptr[i] == arr[i]가 같은 의미가 된다.

배열 포인터

int arr[3] = { 1, 2, 3 };
//&를 빼도 같은 결과가 나온다. / 같은 값이니까
//컴파일이 안되는 이유 : [3]으로 제약을 두었기 때문이다.
//int* ptr = arr; => 제약이 없어서 더 넓은 범위의 선언이다.
int(*p_arr)[3] = &arr; 

for (int i = 0; i < 3; i++) {
    printf("%d\\\\n", (*p_arr)[i]);
}
  • int(*p_arr)[3]: 크기가 3인 정수 배열을 가리키는 포인터
  • &arr: 배열 전체의 주소
  • (*p_arr)[i]: 배열 포인터를 통한 요소 접근
    • *를 제거하면 배열 인덱스의 주소가 나온다.
  • 필요한 이유 : int*로 전환해서 넘겨도 되지만 ‘이차원’배열같은 경우에 사용된다고 한다.
    • 즉, 배열 특정 인덱스의 ‘주소’를 넘기는 것이다.

이차원 배열과 배열 포인터 - 코드 추가

  • &arr[0] == arr / &arr[1] == arr + 1 == &arr[0] + 1
int arr[3][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 } };
/*
	//단순한 포인트 변수와 이차원 배열 이름만 사용할 경우
 	//일차원 배열 형식으로 작성이 가능하지만 [] 요소를 잘 확인해야한다.
	int* ptr = (int*)arr;
	
	for(int i = 0; i < 3; ++i)
	{
		for(int j = 0; j < 4; ++j
		{
			printf("%4d", arr[i * 4 + j]);
		}
	}

*/

int(*p_row)[4] = &arr[1];

for (int i = 0; i < 4; i++) {
    printf("%d ", (*p_row)[i]);
}
  • int(*p_row)[4]: 크기가 4인 정수 배열을 가리키는 포인터
  • &arr[1]: 두 번째 행의 주소
    • &arr[i] + n ⇒ 다음 인덱스의 주소를 가리키게 된다.
  • 특정 행을 가리키는 포인터로 활용

포인터 배열과 문자열

char str[4][10] = { "hello", "world", "doodle", "google" };
char* ptr[4];

for (int i = 0; i < 4; i++) {
    ptr[i] = str[i];
}
for (int i = 0; i < 4; i++) {
    printf("%s\\\\n", ptr[i]);
}
  • char* ptr[4]: 문자열 포인터 4개로 구성된 배열
  • 각 포인터는 문자열의 시작 주소를 저장
  • 문자열 배열의 효율적인 관리 방법

함수

반환타입 함수이름(매개변수) 
{
    // 함수 본문
    return 반환값;  // 반환타입이 void가 아닌 경우
}
  • 특정 작업을 수행하는 코드의 집합
  • 반복되는 코드를 줄이고 프로그램을 모듈화
  • 코드의 재사용성과 가독성 향상
  • 프로그램의 구조화와 유지보수성 개선
  • 반환타입 : 데이터 형
  • 함수 이름 : 변수 이름 규칙과 동일하다
  • 매개 변수 : 데이터형과 변수 이름으로 선언된다. , n개의 변수가 들어간다. , ‘,’콤마로 구분한다.
  • return : 함수의 종료를 의미한다.
    • 함수의 데이터형을 확인하여 함수의 결과를 반환한다.

함수화

void buyItem(int* itemCnt, int* money) {
    (*itemCnt)++;
    (*money) -= 10;
    printf("아이템을 구매했습니다.\\\\n");
    printf("  아이템 개수: %d\\\\n", *itemCnt);
    printf("  잔액: %d\\\\n", *money);
}

int itemCnt = 0;
int money = 100;

buyItem(&itemCnt, &money);
  • void : 반환값이 없는 데이터형이다.

스코프 (=영역, 범위)

  • 변수가 유효한 범위
  • 변수에 접근할 수 있는 코드 영역
    • for에 선언한 반복 변수 등…
  • 프로그램의 메모리 관리와 직결
  • 반복문, if문, switch문 등이 있다.

블록 스코프

int a = 10;  // main 함수의 지역 변수

{
    int b = 20;  // 블록 내부의 지역 변수
    printf("a = %d, b = %d\\\\n", a, b);  // 둘 다 접근 가능
}

// printf("b = %d\\\\n", b);  // 오류! b는 블록을 벗어나면 접근 불가
printf("a = %d\\\\n", a);  // a는 여전히 접근 가능
  • 블록 : 중괄호 {}로 구분되는 영역
  • 블록 내부에서 선언된 변수는 블록 종료 시 소멸
  • 내부 블록에서 외부 블록의 변수 접근 가능
  • 외부 블록에서 내부 블록의 변수 접근 불가
  • 같은 이름을 중첩해서 사용할 경우 가장 가깝게 선언한 변수를 확인한다.

함수 스코프

  • 각 함수는 독립적인 스코프 영역
  • 함수 간 지역 변수는 서로 영향 없음
  • 전역 변수는 모든 함수에서 접근 가능
  • 같은 이름의 변수라도 스코프가 다르면 다른 변수

변수의 종류

  1. 지역 변수(Local Variable)
    • 함수나 블록 내부에서 선언
    • 해당 영역에서만 접근 가능
    • 포함되어있는 영역에서 만들어지고, 함수 종료 시 메모리에서 해제
  2. 전역 변수(Global Variable)
    • 함수 외부에서 선언
    • 프로그램 전체에서 접근 가능
    • 프로그램 종료 시까지 메모리에 유지

함수의 반환

  • 반환값
    • 함수가 작업을 완료한 후 호출한 곳으로 돌려주는 값
    • return 키워드를 사용하여 반환
    • 함수의 반환 타입과 일치해야 함
  • return 문 실행 시 함수 즉시 종료
  • 하나의 함수에서 여러 개의 return 문 가능
  • void 함수는 반환값 없음
  • 반환 타입과 실제 반환값의 타입 일치 필요

문자열 매개변수

void welcomePlayer(char playerName[])
{
	printf("환영합니다, %s님!", playerName);
}

int main()
{
	//연습문제 3_2 - 매개변수가 있는 함수
	char name[10];

	printf("플레이어 이름을 입력하세요: ");
	scanf("%s", name);

	welcomePlayer(name);

	return 0;
}
  • 문자열의 매개변수는 char playerName[]으로 받는다.
    • 매개변수에서만 쓸 수 있는 형식이다.
    • char* playerName도 같은 결과가 나온다.