본문 바로가기

C++

[TIL 11장] 문자열, 함수 포인터

오늘의 TIL 목차 (22.08. 08)

 

  • 문자열
  • 함수 포인터

문자열


[ 문자열 ]

: 문자열을 배열 / 포인터 형태로 저장

[문자 배열]
자료형 변수명[크기] = {}; 
char	szName[6] = { 'j', 'u', 's', 'i', 'n'}; // 문자열은 끝에 널 공간도 줘야함 (남겨둬!)

[문자열 상수]
자료형 변수명[크기] = " ";
char szName[6] = "jusin"; 

[문자열 상수 배열]
자료형* 변수명[문자열 갯수] = {};
char* pArray[3] = {"jusin", "Game", "Academy"};

[포인터]
자료형* 포인터명 = " ";
char* pName = "jusin";

※ 포인터를 이용한 문자열 대입은 포인터가 임시 저장 장치에 "jusin"을 저장한 뒤, 주소를 가리키는 구조이므로 불안정함

→ 불안정한 형태를 보완하기 위해 strcpy_s를 통해 변수에 값을 넣어주는 형태로 많이 사용됨

※문자 배열로 선언 시 크기만큼 원소를 초기화해줘도([6]이면 원소 6개 초기화) 오류는 안 뜸, 그래도 널 값 남겨둬야 함

■ 기능 / 주의

  • 배열(문자열 배열 포함), 포인터(함수 포인터 포함)는 이름 자체가 주소이다.
  • 문자열은 이름 자체가 주소지만 출력 시 문자열 특성 상 널 전까지의 문자열 전체를 출력한다. (주소 출력 원하면 &)
  • 선언된 문자열은 변경 불가하지만 포인터로 선언한 경우 변경이 가능하다. ( 문자열 함수를 이용하여 변경 가능 )
  • 문자열만 큰 따옴표 (" ") 로 문자열을 선언할 수 있다. (큰 따옴표 사용 시 문자열 상수)
  • 바로 이름을 받아서 포인터로 사용할 수 있지만 문자 결합을 위해서 배열을 거의 사용한다. 
  • 문자열의 마지막은 꼭 '\0'이 들어가므로 배열 개수를 +1하여 '널' 자리를 포함해줘야 한다.
  • 문자열 상수 배열의 경우 []안에 문자열의 길이가 아닌 문자열의 개수가 들어간다.
char szName[6] = "dainn"; // 길이 6의 문자열 (널 포함)
char* szArray[3] = {"dainn", "siwoo", "mom"};
char* pName = "siwoo";

// szName = "mom"; 실행 오류, szName은 상수로 변경 불가능
szName[1] = 'D' // 일반 문자열의 경우 [n]번째 원소 '단문자'로 변경 가능 "문자열"은 불가능
szArray[1] = "family"; // 배열 1번째의 문자열 family로 변경 가능
pName = "daddy"; // 포인터로 문자열 입력 시, daddy로 변경 가능
cout << szName << "\t" << szArray[1] << "\t" << pName << endl;

출력 결과 :
Dainn 	family 	daddy

※ 주의 사항

- 선언된 문자열은 "문자열"로 변경 불가능, [n] 원소 지정해서 '단문자'로 변경 가능

- 포인터로 선언한 경우 변경 포인터명 = "문자열" 자체로 문자열 변경 가능

- cin으로 입력 시 일반 문자열, 포인터 상관없이 값 변경 가능

■ 문자 배열 & 문자열 상수의 차이점

  • 문자 배열 : 문자 하나 하나를 배열의 원소로 담고 있는 방식
  • 문자열 상수 : 데이터 영역(프로그램 종료 시 소멸)에 등록된 문자열의 시작 주소를 담고 있는 방식
  • 공통점 : 문자 배열도 문자열 상수도 문자열의 시작 주소를 갖고 있다.
char szName[3] = {'H' ,'i' ); 문자 배열은 char* const p 의 경우와 동일
char	szName[16]=""; 문자열 상수는 const char* p의 경우와 동일
char*	szName = "jusin"; // 포인터는 " " 로 대입

☞ 원리

 

■ 예시

char szName1[6] = { 'D', 'a', 'i', 'n', 'n' }; // 문자 배열
char szName2[6] = "Dainn"; // 문자열 상수
char* pName = "jusin"; // 포인터

문자 배열
cout << szName1 << endl; // 문자열 전체 출력 ; 문자열은 주소 출력 시 널 전까지의 문자열 출력
cout << *szName1 << endl; // szName1의 시작 주소에 있는 값 출력 = 0번째 값인 D 출력
cout << szName1[1] << endl; // 배열의 1번째 a 출력
cout << szName1[1] + 1 << endl; // 1번째 배열 a(97)에 1을 더한 값 98 출력
cout << &szName1 << endl; //  배열의 시작 주소 출력 (메모리 공간 5byte ?)
cout << szName1 + 2 << endl; // 2글자 뒤인 inn 출력
cout << &szName1 + 2 << endl; // 2글자 뒤인 i 메모리 주소 출력
cout << " --------" << endl;

char szString[6] = "okay";
cout << *szString << endl; // szString의 시작 주소에 있는 값 출력 = 0번째 값인 o 출력
cin >> szString; // notokay 입력 시 notokay로 값 변경됨
cout << szString << endl;; // notokay 출력
cin >> szString[3]; // yes 입력 시 3번째 원소 한 칸에 y 한 칸 들어감
cout << szString[3] << endl; // y 출력
cout << szString << endl; // notykay 출력
    
문자열 상수, 포인터도 문자 배열과 같음
cout << szName2[2] << endl; // 2번째 칸 i 출력
cout << &szName2 + 1<< endl; // a 주소
cout << &pName + 3 << endl; // i 주소

문자열 상수 배열
char* pArray[3] = { "dainn", "siwoo", "mom" }; // 문자열 배열
pArray[1] = "daddy";
cout << pArray << endl; // pArray의 주소 출력
cout << pArray[1] << endl; // 변경된 1번째 값 daddy 출력
cout << pArray[2] << endl; // 2번째 값 mom 출력
cout << *pArray + 1 << endl; // 0번째 배열의 값인 문자열 dainn + 1은 ainn 출력
cout << *(pArray+1) << endl; // pArray 주소 1칸 이동의 값은 siwoo 출력

→ 문자열 특징

변수명[n]은 해당 [n]번째의 단문자 출력

변수명[n] + x는 해당 n번째의 단문자 + x만큼 한 값 (아스키코드)

변수명은 해당 변수명의 문자열 전체 출력

변수명 + n은 n글자 뒤의 남은 문자열 출력

※ 문자배열의 배열명 + n은 n칸 이동한 주소 출력, 문자열 + n은 n칸 뒤의 문자열 출력

 

함수 포인터


[ 함수 포인터 ]

: 함수의 주소를 저장하는 용도의 포인터

타입(*변수명)(매개 변수의 형태)	
void(*ptr)(void) = Render; // 선언과 동시에 초기화
void(*p)(); // 매개변수가 없는 경우
int(*Func)(int*, int) = Add; // 매개변수가 여러 개인 경우
int(*Print[2])(int, int) = { Sum, Min }; // 함수 포인터 배열

사용
ptr(); // Render(); 처럼 함수처럼 사용
Print[1](iA, iB); // 1번째 배열의 Min함수를 실행

 

■ 기능 / 주의

  • 함수 포인터는 포인터와 마찬가지로 "32비트 기반 프로그래밍 기준" 4byte이다.
  • 함수 포인터는 변수의 특성을 지니기 때문에 대입에 따라서 함수는 바뀔 수 있다.
  • 함수 포인터  배열은 이름[배열 위치] 만 적으면 배열의 해당 위치에 있는 함수의 주소를 출력한다.
  • 함수 포인터  배열은 이름[배열 위치](매개변수)도 같이 적어야 해당 함수를 실행한다.

■ 예시

int Sum(int* _pA, int* _pB);
int Mul(int* _pA, int* _pB);
void Swap(int* _pA, int* _pB);
void Print(void);

void main(void)
{
	int iA = 5, iB = 3;
	int(*Cal)(int*, int*) = Sum; // 함수 포인터 선언 방식
	int(*Calcu[2])(int*, int*) = { Sum, Mul };
    void(*p)() = Print;

	cout << Cal(&iA, &iB) << endl; // Sum 함수 실행
    Cal = Mul; // 함수 포인터는 변수적 특성, Cal안의 Sum 주소 대신 Mul 주소로 변경 가능
    cout << Cal(&iA, &iB) << endl; // Mul 함수 실행
    
    // Calcu[0]은 0번째 배열의 주소(Sum의 주소) 출력, Calcu[0](&iA, &iB)은 해당 함수 실행
    cout << Calcu[0] << "\t" << Sum << "\t" << Calcu[0](&iA, &iB) << endl; 
    cout << iA << << "\t" << iB << endl; // Swap(&iA, *iB) 자체는 반환 값이 void이기 때문에 출력 불가능
	p(); // Print(); 와 동일
 }
 
int Sum(int* _pA, int* _pB) {	return *_pA + *_pB; }

int Mul(int* _pA, int* _pB){
	int Mul = (*_pA) * (*_pB);	
    return Mul; 
}

void Swap(int* _pA, int* _pB) {
	int iTemp = 0;
	iTemp = *_pA;
	*_pA = *_pB;
	*_pB = iTemp;
}

void Print(void) {	cout << "안녕" << endl; }
 
 출력 결과 : 
 8
 18
 14325A	1432A	8
 3	5
 안녕