본문 바로가기
2CHAECHAE 학교생활/객체지향프로그래밍(C++)

[ 객체지향프로그래밍(C++) 4주차 ① ]

by 2CHAE._.EUN 2022. 3. 24.

[ 4주차 - 1 ]

 

포인터

 

포인터란 메모리 주소를 포함하는 변수로써 다른 변수의 위치 값(주소)을 저장하는데 사용한다.

 

// 포인터의기본형 *포인터변수의이름

int *p; // 정수에 대한 포인터

// p는 int형 변수의 주소를 저장한다는 것이다.
// p에는 다른 자료형의 변수의 주소를 저장하면 안된다.

 

& : 피연산자의 메모리 주소를 반환하는 단항 연산자

balptr = &balance

// balance 변수의 메모리 주소를 balptr에 넣는다.

 

* : 피연산자에 의해 명시된 주소에 위치한 변수의 값을 반환하는 단항 연산자

value = *balptr

// balance의 값을 value에 넣는다.
// *는 balptr이 가지고 있는 주소에 해당하는 변수의 값을 value에 넣어준다.

 

#include <iostream>
using namespace std;

int main() {

	int balance;
	int *balptr;
	int value;
	balance = 3200;
	balptr = &balance; // balptr에 balance의 주소를 저장
	value = *balptr; // value에 실제로 balance 주소에 해당하는 값을 넣어준다.

	cout << "balance 주소 : " << balptr << "\n";
	cout << "balance 값 : " << value << "\n";

	return 0;
}

 

포인터식

 

1. 포인터 산술식 : ++, --, +, - ex) p1 = p1 + 9, p++, p--

 

p1이라는 포인터 변수가 있을 때 p1++를 해주면 1이 더해지는 것이 아니라 p1 포인터 변수 자료형의 크기가 더해진다.

p1의 자료형이 int라면 4가 더해진다. p1 = p1 + 9의 경우도 9가 더해지는 것이 아니라 9x4=36이 더해진다. 

 

2. 포인터 비교식 : ==, <. >

 

==는 포인터 변수가 가리키고 있는 변수의 값이 같은지 사용하기 보다는 두 포인터 변수가 갖고 있는 주소값이 같은지 비교하는 것이다.  포인터 변수 2개를 비교할 때는 두 포인터 변수가 같은 값을 갖고 있는지 비교한다. 흔히 p1이라는

포인터가 null인지 아닌지를 비교할 때 많이 사용한다.

 

포인터 비교가 의미있는 결과가 되기 위해서 두개의 포인터는 서로서로 어떤 관계를 가져야한다.

→ 2개의 포인터가 같은 변수의 주소를 갖는지 혹은 null을 갖는지

 

 

포인터와 배열

 

포인터와 배열은 밀접한 관계를 가지고 있다. 

char str[80]; //str은 80개 바이트의 시작 주소를 갖는 포인터 변수이다. 
char *p1;
p1 = str; //str이 갖고 있는 주소를 p1에다가 저장

// 배열의 이름은 포인터 변수이다. 

str[4] == *(p1+4) == p1[4]
//p1에다가 배열의 시작주소를 저장한 다음부터는 *(p1+4) 형태로
//p1이 가리키고 있는 배열의 시작 주소에 4번째 index에 해당하는 문자를 가리킨다.

 

* 배열의 이름과 포인터 변수의 차이 :

배열의 이름도 일종의 포인터 변수이지만 str은 p1같은 다른 포인터 변수하고의 차이점은 p1에는 str이라는

배열의 시작주소를 저장했다가 나중에 또다른 변수의 주소를 저장할 수 있다. 반면에 str은 포인터 변수이긴 하지만

절대 다른 변수의 주소를 저장할 수 없다. 오직 배열의 시작 주소만 가지고 고정이 된다.

 

#include <iostream>
#include <cstdio>
using namespace std;

int main() {
	
	char str[80];
	char token[80]; //token : 문자열에 들어있는 각각의 단어
	char *p, *q;
	cout << "Enter a sentence : ";
	gets_s(str);
	p = str; //p가 str의 시작 주소를 가리킨다.

	// 문자열에서 토큰을 분리해서 저장하고 출력하기
	while (*p) { //한 단어씩 반복하게 됨
		q = token; //q가 token의 시작 주소를 가리킨다.

		while (*p != ' ' && *p) { //문자가 빈칸이나 널 종료 문자를 만날 때까지 문자열을 읽는다.
			*q = *p; //str의 문자가 token으로 복사가 된다.
			q++; p++;
		}

		if (*p) p++;
        //*p가 null이 아니라면 / 마지막 문자열의 끝에 도달하지 않았다면 str의 빈 칸은 건너뛰기
		*q = '\0'; //token의 끝에 널 문자를 넣어주기 ( 널문자는 반드시 넣어줘야함 )
		cout << token << '\n';
	}

	return 0;
}

 

* 포인터를 사용하지 않고 배열의 인덱스만을 사용했을 경우

#include <iostream>
#include <cstdio>
using namespace std;

int main() {

	char str[80];
	char token[80];
	cout << "Enter a sentence : ";
	gets_s(str);

	for (int i = 0; ; i++) {
		for (int j = 0; str[i] != ' ' && str[i]; j++, i++) {
			token[j] = str[i];
			token[j+1] = '\0'; //token의 끝에 널 문자를 넣는다.
		}
			cout << token << '\n';
			if (!str[i]) break;
	}

	return 0;
}

 

 

포인터에 첨자 사용하기

 

포인터 변수임에 불구하고 배열 이름처럼 사용하는 것이 가능하다.  배열의 이름은 일종의 포인터 변수이기 때문에

배열의 이름을 다른 포인터 변수에 저장했을 때 포인터 변수를 배열처럼 사용이 가능하다.

#include <iostream>
#include <cctype>
using namespace std;

int main() {
	char str[20] = "hello tom";
	char *p;
	p = str; // str의 시작 주소를 p에 넣는다.

	// p[i]가 null이 아닌 경우 무조건 참이다. 
	for(int i = 0; p[i]; i++) {
		p[i] = toupper(p[i]); // p[i]는 *(p+i)와 같음
	}

	//toupper(p[i]) : p[i]의 원소를 소문자인 경우 대문자로 변환해주는 시스템 함수

	cout << p; // p는 문자열을 나타냄
	return 0;

}

 // ▶ HELLO TOM

 

* toupper() : 인자 원소가 소문자인 경우 대문자로 변환해주는 시스템 함수

 

 

포인터와 문자열 리터럴

 

컴파일러가 문자열 리터럴을 문자열 테이블에 저장하고 그 문자열에 대한 포인터를 생성한다.

 

#include <iostream>
using namespace std;

int main() {
	char *s;
	s = "Pointers are fun to use.\n"; //에러남..?

	cout << s;

	return 0;
}

 

 

널 포인터

 

포인터에 값을 넣기 전에 사용하면 프로그램과 충돌할 뿐만 아니라 운영체제와도 충돌할 수 있다.

→ int *p; *p=10;을 해주면 기존 포인터 변수에 들어있는 쓰레기 값이 제거되고 10이 들어가게 됨

 

전통적으로 포인터가 널 값(0)을 가진다면 아무것도 가리키지 않는다고 가정한다.

→ 포인터 변수에 0이 저장되어있다는 뜻은 0번지에 해당하는 것이 아니라  null값을 가지고 있다는 의미이다.

 

사용되지 않은 포인터에 널 값을 갖고, 널 포인터의 사용을 피한다면, 초기화되지 않은 포인터를

사용하는 것을 피할 수 있다.

→ 포인터 변수를 선언할 때 초기값 0을 넣어주는 것이 부작용을 피할 수 있다.

 

포인터가 선언될 때 어떤 형의 포인터도 널로 초기화 될 수 있다.

 

 

다중 간접 접근

 

포인터에 대한 포인터는 일종의 다중 간접 접근, 또는 포인터들의 체인 (chain)이다.

1. 단일 간접 접근 : 어떤 변수의 주소를 포인터 변수에 넣어놓고 *를 사용해서 변수에 access할 수 있다.

 

2. 다중 간접 접근 : 어떤 변수의 주소를 포인터 변수 A에 넣어놓고 그 포인터 변수 A의 주소를 또 다른 포인터 변수 B에

넣어놓고 사용한다. 포인터 변수 B에 접근할 때는 *를 하나 붙여줘서 사용하면 된다. *의 내용은 포인터 변수 A의 내용이 된다. 하지만 포인터 변수 B에서 변수에 접근하고자 할 때는 포인터 연산자를 2개 사용해서 **를 사용해야 한다.

 

 

포인터와 관련된 문제

 

1. 초기화되지 않은 포인터

→ 포인터 변수에 어떠한 주소를 넣어주지 않고 바로 접근한다면 큰 문제를 야기할 수 있음

 

2. 무의미한 포인터 비교

→ 포인터 변수를 선언해놓고 각각의 포인터 변수를 비교할 때 *를 사용하지 않는다면 비교가 되지 않음

 

3. 포인터를 다시 설정하지 않는 실수

→ 포인터를 다른 목적으로 사용할 경우 포인터에 주소를 다시 설정해줘야한다.

 

 

실습문제

 

빈 라인이 입력될 때까지 문장들을 입력받아 토큰의 수를 계산하는 프로그램을 작성하기

 

* 전체를 하나의 문자열로 입력받기 위해서는 gets_s를 사용해야한다. 

 

#include <iostream>
using namespace std;

int main() {

	char str[80];
	int count = 0;

	char* p;

	while (true) {
		cout << "문자열 입력하기 : ";
		gets_s(str);

		if (!str[0]) { // 빈칸이면 끝내기
			break;
		}

		p = str; // p가 str의 시작 주소를 가리킨다.

		while (*p) { //한 단어씩 반복해서 문자열의 끝까지 진행


			while (*p != ' ' && *p) { // 문자가 빈칸이나 널문자를 만날 때까지 문자열을 읽는다.
				p++;
			}

			count++;

			if (*p) { // *p가 null이 아니거나 마지막 문자열의 끝에 도달하지 않았다면 str의 빈칸은 건너뛰기
				p++;
			}

		}

	}
	cout << "총 토큰의 수 : " << count;
	return 0;
}