[ 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;
}
'2CHAECHAE 학교생활 > 객체지향프로그래밍(C++)' 카테고리의 다른 글
[ 객체지향프로그래밍(C++) 5주차 ① ] (0) | 2022.04.01 |
---|---|
[ 객체지향프로그래밍(C++) 4주차 ② ] (0) | 2022.03.28 |
[ 객체지향프로그래밍(C++) 3주차 ② ] (0) | 2022.03.21 |
[ 객체지향프로그래밍(C++) 3주차 ① ] (0) | 2022.03.21 |
[ 객체지향프로그래밍(C++) 2주차 ] (0) | 2022.03.18 |