C++ 포인터

NYH·2023년 11월 12일

C++

목록 보기
7/17

목차

  1. 포인터
  2. 상수 포인터
  3. void 포인터
  4. 구조체 포인터
  5. 함수 포인터
    ?. 문제풀이


1. 포인터

정의

자료형의 관점을 가지면서 주소를 담는 변수


  • 예시
int* _ptr = nullptr;

int 자료형의 주소 값을 가지는 변수 _ptr


  • 자료형의 관점을 가진다는 의미
float f = 5.f

// 강제로 int 타입 주소로 형변환하면?
int* _iptr = (int*)&f;

_iptr 포인터가 참조하는 값은 5가 나올 것 같지만 f의 주소가 가리키는 값에 들어 있는 값은 실수형 방식으로 표현된 5.f입니다.
_iptr 이 보는 관점은 int형 방식이므로 정수형 관점으로 값을 바라보기 때문에 실수형 방식으로 표현된 5.f 값을 정수형 방식으로 보게 됩니다.

f의 메모리 공간에 들어있는 값. 0x40a00000 형태의 값이 들어있는 것을 확인할 수 있습니다.


즉 해당 값을 int 자료형 관점의 값으로 바라보게 된다면 나오는 값은 다음과 같습니다.


2. 상수 포인터

상수를 가리키는 포인터 (Pointer to Const Value)

#include <iostream>

int main()
{
	int i = 100;
	const int* _iptr = &i;
}

여기서 const int _iptr을 해석해 보자면
const int : 상수 관점으로 int 자료형을 바라보는.
* : 포인터 입니다.


#include <iostream>

int main()
{
	int i = 100;
    const int* _iptr = &i;
    
    // 불가능!
    // *_iptr = 120;
    
    // 가능!
	i = 120;
    
    // 가능!
    int x = 150;
    _iptr = &x;
}

상수 관점으로 자료형을 바라보기에 안에 역참조시 안에 들어있는 데이터를 변경할 수는 없습니다.
그러나 주소를 상수 관점으로 바라보는 것은 아니기에 주소값은 변경이 가능합니다.


상수 포인터 (Pointer to Const Address)

#include <iostream>

int main()
{
	int i = 100;
    int* const _iptr = &i;
}

int* : int 자료형 관점으로 바라봅니다.
const _iptr : _iptr을 상수 관점으로 바라봅니다.
변수의 이름은 주소를 별명으로 부르는 것과 같습니다.
이를 해석하자면 int 자료형 관점으로 바라보고 주소를 상수 관점으로 바라보는 포인터입니다.


#include <iostream>

int main()
{
	int i = 100;
    int* const _iptr = &i;
    
    // 가능!
    *_iptr = 120;
    
    // 가능!
	i = 120;
    
    // 불가능!
    int x = 150;
    _iptr = &x;
}

안에 들어있는 데이터를 상수 관점으로 바라보는 것이 아닌 주소를 상수 관점으로 바라봅니다
해당 포인터는 안에 들어있는 데이터를 역참조해서 값을 변경할 수 는 있지만 자신이 가지고 있는 주소를 변경하는 행위는 불가능합니다.


상수를 가리키는 상수 포인터 (Const Pointer to a Const Value)

#include <iostream>

int main()
{
	int i = 100;
	const int* const _iptr = *i;
}

const int* : int 자료형을 상수관점으로 바라봅니다.
변수는 주소의 별칭입니다.
const _iptr : 변수명 _iptr에 해당하는 주소를 상수 관점으로 바라봅니다.
즉 안에 들어있는 자료형도 상수 관점으로 바라보고, 가지고 있는 주소값도 상수 관점으로 바라보는 것이 상수를 가리키는 상수 포인터입니다.


#include <iostream>

int main()
{
	int i = 100;
    int* const _iptr = &i;
    
    // 불가능!
    *_iptr = 120;
    
    // 가능!
	i = 120;
    
    // 불가능!
    int x = 150;
    _iptr = &x;
}

안에 들어있는 데이터를 역참조해서 값을 변경하는 것도, 자신이 가지고 있는 주소값을 변경하는 것도 불가능한 포인터입니다.


3. void pointer

#include <iostream>

int main()
{
	void* _vptr = nullptr;
}

c++ 에서 void 타입은 타입 없음 으로 정의됩니다.


#include <iostream>

int main()
{
	int i = 100;
	void* _vptr = &i;
}	

void 포인터는 다른 자료형의 주소를 가져올 수 있습니다.
타입이 다른 자료형인데 왜 void pointer는 다른 자료형을 가져올 수 있을까요?
이것을 이해하기 위해서는 pointer의 특성을 이해해야 합니다.


포인터의 크기

c++ 에서 각 포인터의 크기는 자료형과 관계 없이 모두 동일합니다.
32비트 운영체제 환경에서는 4byte, 64비트 운영체제 환경에서는 8byte 크기입니다.
각 자료형과 관계없이 포인터의 크기가 동일한 이유는 포인터가 메모리의 주소값만을 가지는 특성때문에 자료형과 관계없이 들어있는 값은 메모리의 주소값이기 때문입니다.


void pointer

#include <iostream>

int main()
{
	int i = 100;
	void* _vptr = &i;
}	

다시 돌아와서 해당 포인터의 특성을 이용하여 c++ 에서는 void 타입 pointer가 다른 자료형의 주소를 가질 수 있도록 구성되어있습니다.


#include <iostream>

int main()
{
	int i = 100;
	void* _vptr = &i;
    
    // 불가능!
    *_vptr;
}	

데이터를 타입없음 관점으로 바라보기 때문에 데이터에 대한 직접적인 참조가 불가능합니다.


#include<iostream>

void vFunc(void* _vptr)
{
	std::cout << "int 자료형 값?: " << *(int*)_vptr << std::endl;
}

int main()
{
	int i = 100;
	vFunc(&i);
    
    short sh[2] = {2, 2};
    vFunc(sh);
}

void pointer는 c++에서의 다형성과 일반성을 제공하기 위함입니다.
하지만 이러한 void 타입 포인터를 사용할 때는 주의할 점이 있습니다.
void 포인터는 타입에 대한 정보를 가지고 있지 않기 때문에 올바른 타입으로 캐스팅하지 않으면 잘못된 메모리 접근으로 인한 오류가 발생할 수 있습니다.
이러한 유연성은 적절한 타입 캐스팅과 주의깊은 사용이 필요합니다.


4. 구조체 포인터

멤버 변수 포인터 사용

구조체안의 멤버 변수가 포인터 타입인 경우


#inlcude <stdio.h>

typedef struct person {
    std::string name;
    int age;
    double* height; // 포인터 멤버 변수
}Person;

int main() {
    // 구조체 변수 생성
    Person person1;

    // 동적으로 double 변수 할당
    person1.height = new double;
    *(person1.height) = 175.5;

    // 데이터 출력
    std::cout << "Person1: " << person1.name << ", " << person1.age << " years old, Height: " << *(person1.height) << " cm" << std::endl;

    // 메모리 해제
    delete person1.height;

    return 0;
}


구조체 변수로 포인터가 사용


#include <iostream>

// 예제를 위한 구조체 정의
typedef struct point {
    int x;
    int y;
}Point;

int main() {
    // 구조체 포인터 변수 생성 및 동적 할당
    Point* pointPtr = new Point;

    // 포인터를 통한 멤버 변수 접근
    pointPtr->x = 5;
    pointPtr->y = 10;

    // 데이터 출력
    std::cout << "Point Coordinates: (" << pointPtr->x << ", " << pointPtr->y << ")" << std::endl;

    // 메모리 해제
    delete pointPtr;

    return 0;
}


구조체 간의 상호 참조가 적용되는 예제

#include <iostream>

// 전방 선언
struct B;

// 예제를 위한 구조체 정의
typedef struct A {
    int dataA;
    B* bPtr; // B 구조체를 가리키는 포인터
}A;

typedef struct B {
    int dataB;
    A* aPtr; // A 구조체를 가리키는 포인터
}B;

int main() {
    // 구조체 변수 생성
    A aInstance;
    B bInstance;

    // 포인터 설정
    aInstance.bPtr = &bInstance;
    bInstance.aPtr = &aInstance;

    // 데이터 출력
    std::cout << "A Data: " << aInstance.dataA << ", B Data: " << aInstance.bPtr->dataB << std::endl;
    std::cout << "B Data: " << bInstance.dataB << ", A Data: " << bInstance.aPtr->dataA << std::endl;

    return 0;
}


여러 링크가 존재하는 자기참조 구조


#include <stdio.h>

typedef struct node {
    int data;
    struct node* prev_link;
    struct node* next_link;
}Node;

int main()
{
    Node ob1; // Node1

    // 초기화
    ob1.prev_link = NULL;
    ob1.next_link = NULL;
    ob1.data = 10;

    Node ob2; // Node2

    ob2.prev_link = NULL;
    ob2.next_link = NULL;
    ob2.data = 20;

    Node ob3; // Node3

    ob3.prev_link = NULL;
    ob3.next_link = NULL;
    ob3.data = 30;

    // Forward links
    ob1.next_link = &ob2;
    ob2.next_link = &ob3;

    // Backward links
    ob2.prev_link = &ob1;
    ob3.prev_link = &ob2;

    printf("%d\t", ob1.data);
    printf("%d\t", ob1.next_link->data);
    printf("%d\n", ob1.next_link->next_link->data);

    printf("%d\t", ob2.prev_link->data);
    printf("%d\t", ob2.data);
    printf("%d\n", ob2.next_link->data);

    printf("%d\t", ob3.prev_link->prev_link->data);
    printf("%d\t", ob3.prev_link->data);
    printf("%d", ob3.data);
    return 0;
}


5. 함수 포인터

함수에 대한 포인터입니다

명시적 참조, 역참조

int (*funcPtr)();

int (*varPtr)(int, int);

int foo()
{
	return 10;
}

int add(int x, int y)
{
	return x + y;
}

int main()
{
	// foo 호출 -> 암시적 호출
	funcPtr();
    
    // foo 호출 -> 명시적 호출
    (*funcPtr)();
    
    // add(int x, int y) 호출
    varPtr(5,  5);
}

함수에 함수 포인터를 인자로 전달

void sort(int* array, int size, bool isAscending, void (*select_sort)(int*, int, bool)
{
	(*select_sort)(array, size, isAscending);
}

typedef, using

  • typedef
typedef void (*SortType)(int*, int, bool)

void sort(int* array, int size, bool isAscending, SortType sort_type)
{
	sort_type(array, size, isAscending);
}
  • using
using SortType void(*)(int*, int, bool);

void sort(int* array, int size, bool isAscending, SortType sort_type)
{
	sort_type(array, size, isAscending);
}

std::function

#include <functional>

void sort(int* array, int size, bool isAscending, std::function<void*(int*, int,bool)> sort_type)
{
	sort_type(array, size, isAscending);
}


?. 문제풀이

(문제를 푼뒤 코드로 확인해 주세요.)


문제 1.

다음 문제에서 출력될 숫자의 결과를 알려주세요.

#include <iostream>

int main()
{
	char c[2] = {2, 2};
	std::cout << *(short*)c << std::endl;
}


문제 2.

다음 문제에서 출력될 결과를 알려주세요.

#include <iostream>

int main()
{
	int i = 100;
    const int* _iptr = &i;
    
    i = 150;
    std::cout << *_iptr << std::endl;
}


문제 3.

다음 문제에서 int 자료형 i를 출력시키기 위한 구문을 ? 위치에 작성해주세요.

#include <iostream>

int main()
{
	int i = 100;
	void* _vptr = &i;
    
    std::cout << ? << std::endl;
}


문제 4.

다음 문제에서 출력될 결과를 알려주세요.

#include <iostream>

int main()
{
	char c[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    
    std::cout << (int)*(char*)((short*)c + 2) << std::endl;
}


문제 5.

std::function을 이용해서 다음 ? 빈칸을 채워주세요.

?

void sort(int* array, int size, ? sort_type)
{
		sort_type(array, size);
}

[현재 작성 중]

profile
그냥 해라

0개의 댓글