쉽게 풀어쓴 C언어 Express(이승재, 이기정)-5주차

Jaylee
Quantum Ant
Published in
23 min readAug 10, 2019

포인터

포인터란?

포인터는 메모리의 주소를 가지고 있는 변수이다. 컴퓨터에서 메모리는 바이트 단위로 주소가 매겨져 있다. 컴퓨터 메모리에서 주소로 접근할 수도 있고 주소를 이용하여 값을 저장, 읽기도 한다.

변수와 메모리

변수는 메모리에서 만들어진다. 메모리에서는 각 바이트마다 고유한 주소가 매겨진다. 이들 주소를 이용하여 우리는 메모리 바이트에 접근할 수 있다. 프로그램에서 변수를 만들면 이들 변수는 컴파일러에 의하여 메모리 공간의 비어있는 위치를 차지한다. 변수의 크기에 따라서 차지하는 메모리의 공간의 크기가 달라진다.

주소연산자 &

C언어에서는 변수의 주소를 계산하는 연산자 &가 있다. 주소연산자 &는 변수의 이름을 받아서 변수의 주소를 반환한다. 예를 들어 int i;라고 변수 i를 선언하면 &i로 i의 주소를 알 수 있다.

포인터 선언

포인터는 변수의 주소를 가지고 있는 변수이다. 포인터는 변수이지만 저장하고 있는 것이 보통의 변수처럼 데이터가 아닌 메모리의 주소이다. 하지만 포인터 역시 변수이므로 사용하기 전에 선언을 해주어야 한다.

-문법

자료형 * 변수이름;

ex) int *p;

포인터를 선언하려면 포인터가 가리키게 되는 데이터의 자료형을 먼저 쓰고 *를 붙인다음 포인터의 이름을 쓴다. 변수이름 옆에 *를 붙이는 것 빼고는 일반적인 변수 선언과 같지만 ,를 써서 여러 개의 변수를 선언할 때는 int *p1,p2,p3;이 아닌 int *p1,*p2,*p3;이 되어야 한다. 만약 포인터의 자료형과 포인터가 가리키는 변수의 자료형이 맞지 않으면 포인터가 저장된 변수와 맞지 않는 바이트를 읽을 수 있으므로 꼭 맞춰주어야만 한다.

포인터 초기화

아두이노와 같은 엠베디드 장치에는 하드웨어(LED, 모터, 센서)를 메모리의 일부로 인식하기 때문에 우리가 메모리의 특정 번지에 값을 쓰면 LED를 키고 끄는 것이 가능하다.

ex)

int *p=(int*)10000;

하지만 PC에서는 운영체제가 메모리를 관리하므로 위와 같이 주소값을 대입하는 것은 피해야 한다. 포인터에는 주소가 저장되어야 하므로 PC에서는 & 연산자를 이용하여 변수의 주소를 계산하여 포인터에 대입할 수 있다.

ex)

int i=10;

int * p;

p=&i;

Fig1. 변수와 포인터

간접참조 연산자 *

포인터가 유용한 이유는 포인터를 통해서 값을 읽어오거나 값을 변환할 수 있기 때문이다. 포인터가 가리키는 주소에 저장된 내용을 읽으려면 포인터 이름 앞에 * 기호를 붙여 *포인터이름 하면 된다. 이것을 포인터를 통하여 메모리를 간접 참조한다고 한다. ex)의 예제 코드에서 포인터 p는 i를 가리킨다. 그러면 *p는 i와 동일하다. 따라서 *p를 출력하면 변수 i의 값이 출력되고 *p에 어떤 값을 저장하면 변수 i에 그 값이 저장된다.

주소 연산자 &와 간접참조 연산자*

&연산자는 변수의 주소를 구하여 포인터에 대입할 때 사용하는 연산자이다. *연산자는 포인터를 통하여 변수를 간접 참조할 때 사용하는 연산자이다.

Fig2. *와 &

포인터 사용 시의 주의점

① 초기화하지 않고 사용 금지

만약 포인터가 선언만 되고 초기화되지 않았다면 포인터는 쓰레기값을 가지게 된다. 따라서 이런 상태에서 포인터를 사용하여 메모리의 내용을 변경한다면 문제가 생길 수 있다. 만약 우연히 포인터가 중요한 값을 가리키고 있었다면 중요한 정보를 덮어쓸 수도 있으며 따라서 전체 시스템을 다운시킬 수도 있다.

② NULL 포인터의 사용

포인터가 아무것도 가리키지 않을 때는 NULL로 설정하는 것이 바람직하다. NULL은 헤더 파일 stdio.h에 0으로 정의되어 있다. 만약 주소 0은 CPU가 사용하는 영역이어서 일반 프로그램은 주소 0에 접근할 수 없다.

③ 포인터 자료형과 변수의 자료형 일치

만약 포인터의 자료형이 변수의 자료형보다 크다면, 포인터를 통해 변수의 주소를 통해 쓰게 되면 변수의 범위를 넘어가서 이웃 바이트를 덮어쓰게 된다.

④ 절대 주소 사용 금지

절대 주소는 아두이노와 같은 엠베디드 시스템에서만 사용한다. 왜냐하면 PC는 윈도우와 같은 운영체제가 프로그램을 관리하기 때문에 프로그래머가 사용하고자 하는 주소가 어떠한 용도로 사용되는 지 모르기 때문이다.

포인터 연산

포인터 또한 변수이므로 값을 더하거나 뺄 수 있다. 다만 곱셈이나 나눗셈은 불가능하다.

-포인터의 덧셈

포인터 변수에 대한 연산은 일반적인 변수에 대한 연산과는 다르다. 포인터에 증가연산인 ++을 적용하였을 경우, 증가되는 값은 포인터가 가리키는 객체의 크기이다. 즉 포인터와 변수의 자료형의 바이트 크기만큼 증가된다. 만약 char 형일 때는 1씩 증가, short형이면 2씩 증가, int, float 형이면 4씩 증가, double 형이면 8씩 증가한다. — 연산자를 이용한 감소연산도 마찬가지이다. 덧셈과 뺄셈도 이와 유사하다. 포인터에 정수를 더하거나 뺄 때도 마찬가지이다. 포인터에 정수를 더하면 포인터의 값이 단순히 그만큼 증가하는 것이 아니라 포인터가 가리키는 자료형의 크기만큼 증가한다. 즉 포인터의 자료형의 크기가 n일때 포인터에 정수 m을 더하면 포인터 값은 n*m만큼 증가한다.

-간접참조 연산자 *와 증감연산자 — ,==

증감연산자는 간접 참조 연산자와 같이 사용될 수 있다. 여기서 주의해야 할 점은 증감연산자를 포인터에 적용할 수도 있고 포인터에 가리키는 대상에 적용할 수도 있다. 증감연산자와 간접참조 연산자와의 계산 순서는 Fig3에 나와있다.

Fig3. 포인터 연산 순서

포인터의 형변환

C언어에서는 필요한 경우에 명시적으로 포인터의 타입을 변경할 수 있다. 예를 들어서 double형 포인터를 int형 포인터로 변경할 수 있다. 다만 이 경우에는 형변환 연산자를 앞에 써주어야 한다. 만약 형변환 연산자를 앞에 써주지 않으면 경고가 발생한다.

포인터와 함수

함수가 외부로부터 매개 변수를 통하여 데이터를 받는 방법은 크게 2가지가 있다.

값에 의한 호출(call-by-value): 복사본이 전달된다.

참조에 의한 호출(call-by-reference): 원본이 전달된다.

함수가 호출될 때 인수의 복사본이 함수로 전달되면 값에 의한 호출이다, 만약 함수가 호출될 때 인수들의 원본이 함수로 전달되면 참조에 의한 초출이다. C에서는 값에 의한 호출만 지원한다. 참조에 의한 호출은 포인터를 이용해 구현이 가능하다.

-값에 의한 호출

ex)

#include <stdio.h>

void swap(int x,int y);

int main(){

int a=100, b=200;

printf(“a=%d b=%d\n”,a,b);

swap(a,b);

printf(“a=%d b=%d\n”,a,b);

return 0;

}

void swap(int x,int y){

int tmp;

printf(“x=%d y=%d\n”,x,y);

tmp=x;

x=y;

y=tmp;

printf(“x=%d y=%d\n”,x,y);

}

swap()은 주어진 변수의 값을 교환하는 함수이다. 위의 예제의 실행결과를 보면 swap()안에서는 변수 x,y의 값이 교환되었지만 main()에서는 변수 a와 b의 값이 교환되지 않았다. swap() 함수 안에서는 변수 x,y,의 값이 서로 바뀌었지만 왜 이것이 main()으로 전달되지 않았을까? 이유는 함수 로출이 기본적으로 값에 의한 호출이기 때문이다. 함수의 인수로 변수의 값만 전달되기 때문에 원본함수 자체를 변경할 수는 없다. 복사본을 가진 사람이 원본을 변경할 수 없는 것과 마찬가지다. 즉 swap()의 매개변수인 x,y(복사본)는 main()의 a,b(원본)과는 다른 변수이다.

Fig4. swap()함수의 작동(값에 의한 호출)

-참조에 의한 호출

참조에 의한 호출은 변수의 복사본이 함수로 전달되는 것이 아니라 원본이 직접 전달되는 것이다. 변수의 원본이 전달되기 때문에 호출된 함수 안에서 매개 변수의 값을 수정하면 원래의 변수가 그대로 수정된다.

C언어에서 참조에 의한 호출이 필요한 경우, 포인터를 이용하여 참조에 의한 호출을 구현할 수 있다. 변수의 주소를 함수에 넘겨주면 호출된 함수에서는 이 포인터를 이용해 원본 변수의 값을 수정할 수 있다.

Fig5. swap()함수의 작동(참조에 의한 호출)

ex)

#include <stdio.h>

void swap(int *px,int *py);

int main(){

int a=100, b=200;

printf(“a=%d b=%d\n”,a,b);

swap(&a,&b); //함수 호출: 변수의 주소가 전달된다.

printf(“a=%d b=%d\n”,a,b);

return 0;

}

void swap(int *px,int *py){ //함수 선언

int tmp;

tmp=*px; // 값 교환1: 결과적으로 tmp=a; 랑 같은 의미

*px=*py; // 값 교환2: 결과적으로 a=b; 랑 같은 의미

*py=tmp; // 값 교환3: 결과적으로 b=tmp; 랑 같은 의미

}

위의 예제를 실행해 보면 두 개의 변수값이 제대로 교환된 것을 알 수 있다.

⑴ 함수 호출: swap(&a,&b);

인수가 a,b가 아닌 &a,&b이다. 즉 변수의 값을 전달하는 것이 아닌 변수의 주소를 전달한다. swap()에서는 매개변수 px,py가 전달된 변수의 주소를 받아야한다. 즉 매개변수를 포인터로 만들어야한다.

⑵ 값 교환1: tmp=*px;

tmp라는 임시변수에 a의 값을 저장한다. a의 값은 포인터 px을 통하여 얻을 수 있다. px의 값은 &a의 값을 가지고 있으므로 *px는 a의 값이 된다.

⑶ 값 교환2: *px=*py;

포인터를 통하여 b의 값을 a에 저장한다. px와 py가 a와 b를 가리키고 있으므로 위 문장이 b의 값을 a에 대입한다.

⑷ 값 교환3: *py= tmp;

tmp에 저장되었던 값을 b에 대입한다.

-scanf() 함수

scanf()는 항상 변수들의 주소를 요구했는데, scanf()가 참조에 의한 호출을 사용하는 함수이기 때문이다.

포인터를 사용하는 반환값

함수의 return 값으로도 포인터를 사용할 수 있다. 다만 한 가지 주의해야할 점은 함수가 끝나도 남아 있는 기억 장소의 변수를 반환해야 한다는 점이다.

포인터와 배열

배열과 포인터는 밀접한 관계를 가지고 있다. 배열 이름이 포인터이기 때문이다.

ex)

#include <stdio.h>

int main(){

int a[]={10,20,30,40,50};

printf(“&a[0]=%u\n”,&a[0]);

printf(“&a[1]=%u\n”,&a[1]);

printf(“&a[2]=%u\n”,&a[2]);

printf(“&a[0]=%u\n”,&a);

return 0;

}

예제에서 알 수 있는 사실은 배열의 요소들이 메모리에서 연속된 공간을 차지하고 있음을 알수 있다. 또한 a가 int형 배열이므로 각 요소들이 차지하는 메모리 공간은 4바이트이다. 또 하나는 배열의 이름을 정수 형식으로 출력하면 배열의 첫 번째 요소의 주소와 같다는 사실이다. 배열의 이름은 포인터이므로 여기에 여러 가지 포인터 연산을 적용하여 보자.

ex)

#include <stdio.h>

int main(){

int a[]={10,20,30,40,50};

printf(“&a=%u\n”,a);

printf(“&a+1=%u\n”,a+1);

printf(“&*a=%d\n”,*a);

printf(“*(a+1)=%d\n”,*(a+1));

return 0;

}

배열이름을 포인터라 생각하고 *a를 출력하여보면 첫 번째 요소 a[0]이 출력된다. 또한 a+i는 a가 포인터이므로 배열 시작 주소에 (i*배열요소의 크기)이 더해진다. 따라서 a+i는 &a[i]와 같다. 또한 *(a+i)는 a[i]와 완전히 동일하다.

Fig6. 배열과 포인터

배열에 이름에다 다른 변수의 주소를 대입할 수는 없다. 배열의 이름은 포인터 상수이기 때문이다.

포인터를 배열처럼 사용

포인터도 배열 이름처럼 간주될 수 있고 배열과 똑같이 사용할 수 있다. 배열의 값을 포인터를 이용해서 변경할 수 있다.

ex)

#include <stdio.h>

int main(){

int a[]={10,20,30,40,50};

int *p;

p=a

printf(“a[0]=%d a[1]=%d a[2]=%d”,a[0],a[1],a[2]);

printf(“p[0]=%d p[1]=%d p[2]=%d”,p[0],p[1],p[2]);

p[0]=60;

p[1]=70;

p[2]=80;

printf(“a[0]=%d a[1]=%d a[2]=%d”,a[0],a[1],a[2]);

printf(“p[0]=%d p[1]=%d p[2]=%d”,p[0],p[1],p[2]);

return 0;

}

Fig7. 포인터로 배열 값 변경

배열 매개 변수

배열은 함수의 매개변수로 받을 수 있다. 만약 일반적인 매개 변수라면 메모리가 할당된다. 하지만 매개변수로 배열을 선언하게 되면 배열 매개 변수에서는 실제로 배열이 생성되지 않는다. 외부에서 전달되는 배열의 주소를 저장하는 포인터로 생성된다. 함수를 호출할 때 배열을 전달하면 자동으로 배열의 주소가 전달된다. 앞에서 배열의 이름은 배열의 주소와 같다고 했다. 만약 함수 안에서 배열 매개 변수를 통해 배열 원소에 접근하면 배열 원소가 변경된다.

포인터의 장점

1.연결 리스트나 이진 트리 등의 향상된 자료 구조 제작

2.메모리 매핑 하드웨어

3.참조에 의한 호출

4.동적 메모리 할당

문자열

문자와 문자열

C에서는 문자와 문자열을 구분한다. 문자는 1개의 글자이며 작은 따옴표(‘a’)로 표기한다. 반면 문자열은 일련의 문자들의 모임이다. 문자열은 큰 따옴표(“a”,”abc”)를 이용하여 표기한다.

문자열의 저장 위치

C에서는 문자를 저장하기 위하여 만들어진 char형이 있다. char형의 변수를 이용하면 하나의 문자를 저장할 수 있다. 문자열은 여러 개의 문자가 모인 것이다. 즉 char 배열을 이용하면 문자열을 저장할 수 있다.

NULL 문자

널 문자는 아스키 코드 값이 0인 문자이다. 문자열은 반드시 널 문자로 끝나야 한다. 그 이유는 문자열의 끝을 널 문자로 끝내지 않으면 컴퓨터가 문자열의 끝을 알지 못하기 때문이다. 만약 10바이트 문자열에 “seoul”을 저장했다면 “seoul”을 제외한 나머지 배열에는 쓰레기 값이 들어있다.

Fig8. 문자열과 쓰레기 값

따라서 이 경우 컴퓨터는 어디까지가 의미있는 문자열인지 알지 못한다. 따라서 컴퓨터가 어디까지가 의미있는 문자열인지 알려주기 위해 널 문자(0)을 사용한다. 따라서 사용자가 “seoul”을 저장하려고 하면 맨 마지막 널 문자를 추가하기 위해 문자열보다 1개 더 많은 배열을 만들어야 한다.

문자 배열의 초기화 방법

① 배열을 초기화하듯 각 배열 원소들을 중괄호 안에 넣어서 초기화할 수 있다.

char str[4];

str[0]=’a’;

str[3]=’\0';

② 각 배열 원소들의 값을 중괄호 안에 넣어서 초기화할 수 있다. 이 경우 배열의 마지막 원소값은 널 문자가 되어야 한다.

char str[4]={‘a’,’b’,’c’,’\0'};

③ 문자열 상수를 사용하여 초기화할 수 있다. 이 경우 컴파일러가 자동으로 문자열 끝에 널 문자를 추가한다. 단 배열 크기는 문자열 길이보다 커야한다. 만약 배열 크기가 충분하지 않다면 컴파일러가 경고를 한다. 일부 문자 또한 저장되지 않는다.

char str[4]=”abc”;

④ 만약 널 문자로 문자열을 초기화하려면 다음 문장을 이용한다.

char str[4]=””;

⑤ 만약 배열의 크기를 지정하지 않으면 컴파일러가 자동으로 배열의 크기를 초기화 문자열에 맞추어 설정한다.

char str[]=”abc”; //이 경우 배열의 크기는 4

문자열의 출력

문자열의 출력은 printf()의 “%s” 서식지정자를 사용하면 된다. “%s”를 사용할 때는 문자 배열의 이름을 적어주면 된다. 또는 형식 지정자 없이 그냥 문자 배열을 전달하여도 문자열은 출력된다.

printf(“%s”,str);

printf(str);

문자열의 변경

① 각각의 문자 배열 원소에 원하는 문자를 개별적으로 대입하는 방법이다. 이 경우 프로그래머가 끝에 널 문자를 넣어줘야 한다.

char str[10]=”abcde”;

char str[0]=’A’;

char str[5]=’\0';

② 라이브러리 함수 strcpy()를 이용하면 문자열을 문자 배열에 복사할 수 있다. 문자 배열을 원하는 문자열로 변경할 수 있으므로 편리하다.

char str[10]=”abcde”;

strcpy(str,”ABCDE”);

문자열 상수와 포인터

문자열 상수는 “abcd”와 같이 프로그램 소스에 포함된 문자열을 의미한다. 문자열 상수는 프로그램이 사용하는 메모리 영역 중 텍스트 세그먼트라고 불리는 특수한 메모리 영역에 저장된다. 텍스트 세그먼트는 읽기는 가능하지만 우리가 변경하기(쓰기)는 불가능한 영역이다. 변수는 데이터 세그먼트에 저장된다.

char *p=”helloworld”;

위 문장에서는 먼저 포인터 변수가 생성된다. 포인터 변수 역시 데이터 세그먼트에 저장된다. 즉 문자열이 저장된 텍스트 세그먼트 주소가 포인터 변수가 저장된 데이터 세그먼트에 저장된다.

strcpy(p,”goodbye”);

위 문장은 컴파일은 되지만 실행은 되지 않는다. 문자열 상수가 텍스트 세그먼트에 저장되므로 우리가 변경할 수 없기 때문이다.

p=”goodbye”;

하지만 위의 문장은 실행되는데, 위 문장은 단순히 포인터 변수의 p에 저장된 주소를 “helloworld”에서 “goodbye”로 바꾸는 것이기 때문이다.

char p[]=”helloworld”;

strcpy(p,”goodbye”);

위의 문장은 잘 실행된다. 배열 p가 텍스트 세그먼트가 아닌 데이터 세그먼트에 저장되기 때문이다.

문자 입출력 라이브러리

문자를 입출력하는 함수들은 다음과 같다.

Fig9. 문자 입출력 라이브러리

-getchar()와 putchar()

=문법

int 변수;

변수=getchar(); //하나의 문자를 읽는다.

putchar(변수); //하나의 문자를 쓴다.

ex)

#include <stdio.h>

int main(){

int ch;

while((ch=getchar())!=EOF)

putchar(ch);

return 0;

}

위 프로그램을 실행해보면 엔터를 치기 전까진 입력하였던 문자를 출력하지 않는다. 이것은 getchar()가 버퍼를 사용하고 있기 때문이다. 사용자가 키보드를 입력하여 문자를 입력하면 문자는 바로 프로그램으로 가는 것이 아니고 버퍼라는 저장공간을 거친다. 엔터키가 눌러야 버퍼에 있던 문자들이 프로그램으로 전달된다. 만약 버퍼를 사용하고 싶지 않다면 getchar() 대신 _getche()를 사용하여야 한다.

_getch()와 _putch()

문자를 받거나 출력할 때 _getch()나 _putch()를 사용할 수 도 있다. 이들 함수를 사용하려면 <conio.h>를 포함시켜야 한다. 이들 함수는 에코가 없으며 버처를 사용하지 않는다. 보통의 입출력 함수들은 입력할 때도 문자를 출력하지만 에코가 없는 프로그램들은 입력할 때 출력을 하지 않는다.

Fig10. getchar(), _getch(), _getche()함수의 비교

문자열 입출력 라이브러리

Fig11. 문자열 입출력 라이브러리

-scanf()와 printf()

“%s” 형식지정자를 이용해 입출력하면 된다. 다만 scanf()는 하나의 단어까지 밖에 입력받지 못한다. 따라서 여러개의 단어로 이루어진 한 줄을 입력받으려면 gets_s()를 사용하여야 한다.

-gets_s()와 puts()

=문법

char 배열[인덱스];

gets_s(배열,인덱스);

puts(배열);

get_s()는 표준 입력으로부터 엔터키(\n)을 입력할 때 까지 한 줄의 라인을 문자열로 입력받는다. 문자열에 줄바꿈문자는 포함되지 않으며 대신에 자동으로 널 문자를 추가한다.(더 정확히 말하면 \n을 널문자로 바꾼다.) 만약 성공적으로 입력받았으면 인수(배열)이 그대로 반환된다. 만약 실패하였으면 NULL 값이 반환된다.

puts()는 인수가 가리키는 문자열을 받아서 화면에 출력하는 함수이다. 이때 문자열의 마지막의 널문자는 \n으로 바뀐다. 만약 출력 작업이 성공적이면 음수 아닌 값을 반환한다. 만약 실패했으면 EOF(-1)이 반환된다.

문자 처리 라이브러리

문자 처리 함수들은 헤더파일 ctype.h에 정의된다. 문자에 대한 검사는 대부분 영문 알파벳인지, 대문자인지, 소문자인지와 같은 것들을 검사하게 된다. 만약 검사 결과가 참이면 0이 아닌 값이 반환되고 거짓이면 0이 반환된다.

Fig12. 문자 처리 라이브러리-1

문자에 대한 변환도 가능하다.

Fig13. 문자 처리 라이브러리-2

문자열 처리 라이브러리

문자열 함수들은 string.h에 선언되어있다.

Fig14. 문자열 처리 라이브러리

-문자열 길이

strlen()를 이용해 문자열 길이를 계산할 수 있다.

=문법

int 변수=strlen(“문자열”);

-문자열 복사

=문법

char 배열1[인덱스];

char 배열2[인덱스]=”복사할 문자열”;

strcpy(배열1,배열2);

strcpy()는 배열2가 가리키는 문자열을 배열1로 복사한다. 배열1이 가지고 있는 문자열은 덮어씌워져서 없어진다. 문자열의 복사는 한 문자씩 이루어지면 널 문자가 나올 때까지 복사를 계속하게 된다. strcpy()를 사용하게 되면 간단하게 복사할 수 있다. 다만 배열1의 문자열 길이가 배열2의 문자열 길이보다 길거나 같아야 한다.

만약 복사할 문자의 개수를 제한하려면 strncpy()를 사용하면 된다. strncpy()는 복사되는 문자열의 개수가 인수로 주어지는 n을 넘을 수 없다.

-문자열 연결

=문법

char 배열1[인덱스]=”문자열1"; //붙여질 배열

char 배열2[인덱스]=”문자열2"; //붙여질 문자열

strcat(배열1,배열2);

strcat()는 배열1의 문자열1 뒤에 배열2의 문자열2을 연결한다. 연결한 뒤 널 문자로 문자열을 종료한다. 만약 붙여질 배열에 충분한 공간이 없다면 문제가 발생한다. 이러한 문제를 방지하려면 strncat()함수를 사용하여야 한다.

-문자열 비교

두 개의 문자열을 비교하는데는 strcmp()함수를 사용하면 된다. strcmp()는 사전적인 순서로 두 개의 문자열을 비교한다.

=문법

int 변수=strcmp(“문자열1”,”문자열2");

strcmp()는 문자열 2개를 비교하여 문자열1이 앞에 있으면 음수를 반환하고 같으면 0, 문자열 2이 더 크면 양수가 반환된다. 기호를 비교할 때는 아스키 코드값을 차례대로 비교한다.

-문자 검색

주어진 문자열에 특정한 문자가 있는 지 검색하려면 strchr()를 이용한다.

=문법

char *p=strchr(“문자열”,’찾는 문자’) //찾는 문자의 주소를 반환

strchr()은 문자열 첫 번째 문자에서 차례대로 검색해나가다가 문자를 찾으면 그 위치의 주소를 반환한다. 문자를 찾지 못하면 NULL 값을 반환한다.

ex)

#include <string.h>

#include <stdio.h>

int main(){

char[]=”language”;

char c=’g’;

char *p;

int loc;

p=strchr(s,c);

loc=(int)(p-s);

if(p!=NULL)

printf(“%s에서 첫 번째 %c가 %d에서 발견되었음\n”,s,c,loc);

else

printf(“%c가 발견되지 않았음\n”,c);

return 0;

}

-문자열 검색

주어진 문자열 안에 특정한 문자열이 있는지를 검색하려면 strstr()을 사용한다.

=문법

char *변수 = strstr(“문자열1”,”문자열2");

문자열1에서 문자열2를 찾는다. 사용방법은 strchr()와 동일하다.

-문자열 토큰 검색

토큰은 더 이상 분리할 수 없는 단어들을 말한다. 공백 문자로 분리된 한 단어라고 할 수 있다. strtok()에서 토큰을 분리할 수 있다. 분리하는 기준 또한 strtok()에서 지정할 수 있다.

=문법

char 변수1[]=”문자열1";

char 변수2[]=”기준 문자열”;

char *변수=strtok(변수1, 변수2);

strtok()함수는 문자열1에서 다음 토큰을 찾는다. 변수2가 가리키는 문자들은 토큰을 분리하는 분리자를 나타낸다. 첫 번째 토큰을 만들려면 strtok(변수1,“ ”)와 같이 호출한다. strtok은 첫 번째 토큰에 대한 포인터를 반환한다. 다음토큰을 계속 찾으려면 변수1 대신에 NULL을 넣으면 된다. 즉 나머지 토큰들은 strtok(NULL,“ ”); 호출에 의해 추출된다. 만약 지정자를 여러 개 하고 싶다면 “”안에 지정자를 여러 개 써주기만 하면 된다.

문자열 수치 변환

문자열을 수치로 변환할 수 있다.

-sscanf()와 sprintf()

Fig15. sscanf()와 sprintf()

scanf()와 printf()에 s를 붙인 것이다. sscanf()는 키보드에서 입력받는 대신 문자열에서 입력받는다. sprintf()는 모니터로 출력하는 것 대신 문자열을 출력한다. 이들 함수의 첫 번째 배개 변수는 항상 문자열이고, 나머지 매개 변수는 printf()나 scanf()와 동일하다.

ex)

#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>

int main() {

char s[] = “100”;

int value;

sscanf(s, “%d”, &value); //문자열을 수치로 바꾼다, 수치는 value에 저장된다.

printf(“%d\n”, value);

value++;

sprintf(s, “%d”, value);

printf(“%s\n”, s);

return 0;

}

-atoi()와 atof()

Fig16. atoi()함수와 atof()함수

이들 함수는 각각 int와 double형을 반환한다.

문자열 여러 개 저장하기

-2차원 배열

문자열의 배열, 즉 2차원 문자 배열을 이용하면 문자열을 여러 개 저장할 수 있다.

char s[3][6]={“init”,”open”,”close”};

각각의 문자열은 s[0],s[1],s[2]로 접근할 수 있다.

-문자 포인터 배열

2차원 배열은 문자열의 크기가 서로 다르면 낭비되는 공간이 많아진다. 메모리의 낭비를 막으려면 포인터의 개념을 사용해야 한다.

char *s[3]={“init”,”open”,”close”}

위 배열은 3개의 포인터를 요소로 가지는 배열이다. 각 포인터는 메모리에 저장된 문자열 상수의 주소를 가지고 있다. 문자열 상수는 문자열의 크기만큼 텍스트 세그먼트에 저장공간을 차지한다. 따라서 공간을 절약할 수 있다. 다만 한번 문자열이 결정되면 문자열의 내용을 변경할 수 없다.

--

--