TIL_013 (포인터-배열, class, ifdef, virtual)

김펭귄·2025년 8월 13일

Today What I Learned (TIL)

목록 보기
13/93
post-thumbnail

포인터와 레퍼런스

포인터-배열 자료형

int* a[3]			// int*를 값으로 갖는 원소3개짜리 배열 a
int (*b)[3]			// int[3]인 배열을 가리키는 포인터 b
int arr[3]{ 1, 2, 3 };
int (*ptr)[3] = arr;  // 에러 발생
  • int (*ptr)는 3개의 int로 이루어진 배열을 가리키는 포인터

  • arr은 int 3개짜리 배열

    • arr은 배열의 첫 번째 요소를 가리키는 포인터(int*)
    • &arr은 int 3개짜리 배열 전체의 주소를 가리키는 포인터(int(*)[3])
    • 이 둘의 값은 같아도 의미하는 바가 다름
    • &arr로 배열의 주소값을 받았을 경우 당연히 *(&arr) 해야 배열로 사용 가능

int arr[3] {1, 2, 3};
int *p1 = arr;			// 올바른 방식
int (*p2)[3] = &arr;	// 올바른 방식
  • p1은 배열처럼 사용 가능 ex) p1[1], *(p1 + 1)

  • p2는 아래와 같이 사용 가능

int arr[3] {1, 2, 3};
int (*p2)[3] = &arr;	

// p2는 arr를 통으로 가리키는 주소이므로 역참조 해야 배열로 사용 가능
std::cout << (*p2)[0]; // 1
std::cout << (*p2)[1]; // 2
std::cout << (*p2)[2]; // 3


int data[2][3] = { {1,2,3}, {4,5,6} };
int (*p3)[3] = data;			// 그냥 data는 첫 번째 원소({1,2,3} 배열)의 주소값
std::cout << (*p3)[0];    	// 1
std::cout << (*(p3+1))[0]; 	// p가 {1,2,3} 배열이니 p+1은 그 다음 배열인 {4,5,6}
							// == p[1][0] == 4

int (*p4)[2][3] = &data;	// p4도 배열 통짜로 받아야하므로 &data로 주소값 받음
std::cout << (*p4)[1][2];	// 역시 역참조 해야 배열로 사용 가능

Class 개념

생성자 초기값

  • 기본 매개변수가 있는 클래스 생성자
class Person {
public:
    string name;
    int age;

    // 기본 매개변수가 있는 생성자
    Person(string n = "DefaultName", int a = 18) 
    {
        name = n;
        age = a;
    }
};

int main() {
    Person p1;              // 기본값 사용 (DefaultName, 18)
    Person p2("Bob", 30);   // 값을 
}
  • 생성자를 아예 정의하지 않았으면 Default 생성자가 사용되지만, 하나라도 있을 경우 Default 생성자는 사용되지 않음
class Person {
public:
    string name;
    int age;

    Person(string n, int a) 
    {
    	name = n;
        age = a;
    }
};

int main() {
    Person p("a", 30);	// ok
    Person p2;			// error. Defualt 생성자 사용되지 않음
}
  • 정의부와 선언부 분리했으면 선언부에만 default값 표현
class A
{
	int a;
public:
	A(int input = 10);			// 선언부에만 표현
};

A::A(int input) : a(input) {}	// 정의부에는 X
  • 여러 인자를 default로도 사용 가능
class Animal
{
	int a;
    int b;
public:
	Animal(int input1 = 10, int input2 = 5) : a(input1), b(input2) {}
};
  • Animal animal()로 생성 시 → a = 10, b = 5 저장
  • Animal animal(4)로 생성 시 → a = 4, b = 5 저장
  • Animal animal(4, 7)로 생성 시 → a = 4, b = 7 저장
  • Animal animal( , 7)은 잘못된 문법. 왼쪽 기본인자부터 교체된다

ifdef, ifndef

  • define, ifdef, ifndef는 모두 전처리기 지시문으로, 특정 매크로가 정의되어 있는지를 검사하여 그에 따라 코드의 일부를 컴파일할지 말지를 결정

  • 이들의 위치는 어디에 있어도 상관 없으나, define정의가 등장하기 전까지는 그 이름이 인식되지 않음

  • define 매크로명: 매크로명을 정의

  • ifdef 매크로명 ~ endif: 매크로명이 정의되어 있으면 ~ 부분을 컴파일

  • ifndef 매크로명 ~ endif: 매크로명이 정의되어 있지 않을 때 ~부분 컴파일

사용법

  1. 디버깅하고 싶을 때
int main() {

// 매크로가 정의되기 전에 사용하면 컴파일 안 됨
#ifdef DEBUG
  printf("Hello\n");		// 컴파일 안 됨
#endif

#define DEBUG				// 이제 정의
#ifdef DEBUG				// 정의 되었으니 컴파일 가능
  printf("Now Defined\n");	// 실행됨
#endif

}
  1. 헤더파일 중복 포함 방지
  • 헤더파일은 한 번만 포함되어야 하므로 ifndef를 이용
// Student.hpp

#ifndef STUDENT_H_		// 헤더파일이 처음 포함될 경우 컴파일
#define STUDENT_H_		// 컴파일 시작했으므로 다시 바로 정의
class Student
{
		...
};
#endif
  • 현대적인 #pragma once를 더 자주 사용하고 이게 더 좋긴 함

객체지향 프로그래밍

부모-자식 관계 파악 Tip

  • a는 b이다의 관계로 부모(b)-자식(a) 관계를 파악
  • 사자는 동물이다가 성립하므로 동물이 부모클래스, 사자가 자식 클래스

Virtual (가상함수 테이블)

  • class 내의 멤버변수를 virtual를 이용해 가상함수로 만들면 이 클래스를 상속한 자식 클래스에서 이 가상함수를 각자 입맛에 맞게 구현

  • virtual로 정의하면 가상 함수 테이블이 생성되어서 각 자식 클래스가 만든 함수가 이 가상 함수 테이블에 등록된다

  • 이러한 관계는 런타임에 결정되기 때문에 다른 멤버함수와는 다르게 가상 함수 테이블을 이용하여 동적으로 연결한다

추상 클래스

  • 순수 가상 함수를 하나 이상 포함하는 클래스
    • virtual <T> foo() = 0;처럼 가상함수를 정의하지 않으면 순수 가상 함수
  • 추상 클래스는 직접 인스턴스를 생성할 수 없고, 포인터 혹은 참조를 통해서만 사용할 수 있다
profile
반갑습니다

0개의 댓글