[C] ppap

장세민·2022년 10월 8일
0

📝 TIL

목록 보기
21/40
post-thumbnail

몇 년전 유행했던 밈이 있다.

이 아저씨를 기억하는가?

난데 없는 "pen", "pineapple", "apple", "pen"을 합쳐 춤추는 영상을 모두 다 기억할 것이다.

오늘은 이 아저씨처럼 문자열의 대입, 붙이기 등을 수행하는 방법에 대해 이야기를 해보려 한다.



📌 문자열 연산 함수

컴퓨터에 10+20 식을 넣으면 무심하게 '30' 하고 던질 것이다.

그럼
"pen" + "pineapple" 식 역시 "pen pineapple"하고 던져줄까?

# include <stdio.h>
 
int main(void)
{
	char a[10] = "pen";
	char b[10] = "pineapple";
 
	printf("%s\n", a + b);
 
	return 0;
}

아마 이처럼 실패할 것이다.

문자열로 연산할 때는 문자열 연산에 사용하는 함수를 따로 사용해야 한다.


📖 문자열을 대입하는 함수

char 배열은 문자열을 저장하는 변수의 역할을 하며 문자열로 초기화할 수 있다.

예를 들어, "pineapple" 가 저장된 배열을 "applepen" 로 바꾸려면

char str1[80] = "pineapple";
str1[0] = 'a';
str1[1] = 'p';
str1[2] = 'p';
str1[3] = 'l';
str1[4] = 'e';
str1[5] = 'p';
str1[6] = 'e';
str1[7] = 'n';
str1[8] = '\0';

다음과 같이 문자를 하나씩 대입해야 한다.

그런데 이때 마지막에 널 문자도 저장해야 한다.
널 문자가 빠진 채로 대입하고 끝내면 applepene이 된다.

간단한 문자열이면 괜찮겠지만,
길이가 긴 문자열을 이렇게 대입하는 건 매우 번거롭지 않을까?

라는 질문에
strcpy 함수가 문자열을 한 번에 대입하는 방법으로 제공한다.


strcpy(str1, str2)

str1 : 복사 받을 곳
str2 : 복사할 내용

이런 함수를 문자열 연산 함수라고 하며 사용하려면 string.h 헤더 파일을 인클루드 해야 한다.

# include <stdio.h>
# include <string.h>
 
int main(void)
{
	char str1[10] = "pineapple";
	char str2[10] = "applepen";
 
	strcpy(str1, str2);
 
	printf("%s\n", str1);
 
	return 0;
}

쉽다.

strcpy 함수는 복사 받을 곳의 배열명(str1)을 첫 번째 인수로 주고
복사할 문자열(str2)을 두 번째 인수로 준다.

첫 번째 문자부터 널 문자가 나올 때까지 문자를 하나씩 배열에 옮겨 저장하는 방식이다.

  1. # include <stdio.h>
  2. # include <string.h>
  3.  
  4. int main(void)
  5. {
  6. char str1[10] = "pineapple";
  7. char str2[10] = "applepen";
  8. char *ps1 = str2;
  9.  
  10. strcpy(str1, ps1);
  11.  
  12. printf("%s\n", str1);
  13.  
  14. return 0;
  15. }

10행처럼 ps1은 str2 배열의 값을 저장한 포인터이므로 문자열의 시작 위치를 알 수 있다.
이처럼 복사할 문자열의 시작 위치를 알 수 있다면 모두 두 번째 인수로 사용할 수 있다.

두 번째 인수는 다양한 값을 사용할 수 있지만 첫 번째 인수로 사용할 수 있는 값은 제한적이다.

첫 번째 인수는 char 배열이나 그 배열명을 저장한 포인터만 가능하다.

strcpy("banana", "apple");
strcpy(ps1, "apple");

다음과 같이 문자열 상수는 값을 바꿀 수 없으므로 첫 번째 인수로 사용하면 에러가 발생한다.
문자열 상수를 연결하고 있는 포인터를 사용하는 것도 마찬가지!


🔔 정리

첫 번째 인수는 char 배열이나 배열명을 저장한 포인터만 사용할 수 있다.

두 번째 인수는 문자열의 시작 위치를 알 수 있다면 어떤 것이든 사용할 수 있다.



📖 원하는 개수만큼 복사하는 함수

strncpy 함수는 strcpy 에서 'n'만 추가된 것이다.
'n' = number

즉, 문자열을 복사할 때 문자의 수를 지정할 수 있는 것이다.

strncpy(str, "applepen", 5);

str : 복사 받을 배열명
applepen : 복사할 문자열
5 : 복사할 문자 수

  1. # include <stdio.h>
  2. # include <string.h>
  3.  
  4. int main(void)
  5. {
  6. char str1[20] = "pine apple";
  7.  
  8. strncpy(str1, "apple-pen", 5);
  9.  
  10. printf("%s\n", str1);
  11.  
  12. return 0;
  13. }

결과처럼 strncpy 함수는 복사할 문자열에서 지정한 개수만큼 문자를 복사하고

널 문자는 저장하지 않는다.

만약 str 배열이 깔끔하게 문자열 "apple"로만 쓰이도록 하려면
9행에

str1[5] = '\0';

다음 문장을 적어서 널 문자를 추가해야 한다.

깔끔.



📖 문자열을 붙이는 함수

이제 "pen", "pineapple", "apple", "pen" 을 붙일 때가 왔다.

배열에 있는 문자열 뒤에 이어 붙일 때는 strcat 또는 strncat 함수를 사용한다.

앞에서 설명한 것처럼 중간 'n'의 차이는 지정한 문자의 개수이다.

strcat 함수는 먼저 붙여넣을 배열에서 널 문자의 위치를 찾고
붙여넣기가 끝난 후에는 널 문자를 저장하여 마무리한다.

그럼 이제 붙여보자.

  1. # include <stdio.h>
  2. # include <string.h>
  3.  
  4. int main(void)
  5. {
  6. char str1[50] = "pen";
  7.  
  8. strcat(str1, "pineapple ");
  9. printf("1단계: %s\n", str1);
  10.  
  11. strncat(str1, "apple-pie", 5);
  12. printf("2단계: %s\n", str1);
  13.  
  14. strcat(str1, "pen");
  15. printf("최종: %s\n", str1);
  16.  
  17. return 0;
  18. }

🔔 11행의 strncat 함수는 strncpy 함수와 달리 붙여넣은 후
널 문자를 저장하여 문자열을 완성한다는 것 챙겨가자!



🚨 strcat 함수 사용 시 주의사항

1. strcat 함수는 메모리를 침범할 수 있다.

문자열을 덧붙이는 것이므로 붙여넣기가 되는 배열의 크기가 충분히 커야 한다.
붙여넣을 공간의 주소를 증가시키므로 공간이 부족한 경우
할당되지 않은 다른 메모리 영역을 침범할 수 있다.

2. strcat 함수를 사용할 때는 배열을 초기화해야 한다.

붙여넣기 전에 먼저 널 문자의 위치를 찾으므로 초기화되지 않으면
쓰레기 값 중간부터 붙여넣을 가능성이 크다.

따라서, 6행처럼 특별한 문자열로 초기화하거나
다음과 같이 최소한 첫 번째 문자가 널 문자가 되도록 초기화한다.

  • 명시적으로 널 문자를 초기화

    char str[80] = {'\0'};
  • 널 문자의 아스키 코드 값으로 초기화

    char str[80] = {0};
  • 큰따옴표 안에 아무것도 없으므로 널 문자만 초기화

    char str[80] = "";
  • 첫 번째 배열 요소만 별도로 초기화

    str[0] = '\0';

📖 문자열 길이를 계산하는 함수

char 배열은 다양한 길이의 문자열을 저장할 수 있도록 충분히 크게 선언해서 사용한다.
따라서 배열에 저장된 문자열의 길이는 배열의 크기와 다를 수 있는데,

배열에 저장된 문자열의 실제 길이를 알고 싶으면 strlen 함수를 사용한다.

strlen(str)

str : 크기를 확인할 배열명

  1. # include <stdio.h>
  2. # include <string.h>
  3.  
  4. int main(void)
  5. {
  6. char str1[80], str2[80];
  7. char *resp; // 문자열이 긴 배열을 선택할 포인터
  8.  
  9. printf("2개의 과일 이름 입력: ");
  10. scanf("%s%s", str1, str2);
  11. if (strlen(str1) > strlen(str2))
  12. resp = str1;
  13. else
  14. resp = str2;
  15. printf("이름이 긴 과일은: %s\n", resp);
  16.  
  17. return 0;
  18. }

strlen 함수는 널 문자가 나올 때까지 문자 수를 세어 반환한다.
문자열의 길이를 반환하므로 11행처럼 반환값을 바로 비교하거나 수식의 일부로 사용한다.

그런데 이런 궁금증이 들 수 있다.

Q. sizeof 연산자랑 strlen 함수는 뭐가 다른거지?

A. sizeof 연산자는 배열에 저장된 문자열 길이와는 상관없이 배열 전체의 크기를 계산한다.

# include <stdio.h>
# include <string.h>
 
int main(void)
{
	char str[80] = "apple";
	printf("sizeof연산자 출력 결과: %d\n", sizeof(str));
	printf("strlen 함수 출력 결과: %d\n", strlen(str));
 
	return 0;
}

따라서 11행의 strlen을 sizeof로 바꾸면 어떤 문자열을 입력하더라도 조건식은 항상 거짓!



📖 문자열을 비교하는 함수

오늘 함수가 많이 나온다고 당황하지 말고, 포인트 중심으로 기억해보자.

strcmp 함수는 두 문자열의 사전 순서(알파벳 순서)를 판단하여 그 결과를 반환한다.

strcmp(str1, str2);    // str1이 str2보다 사전에 나중에 나오면 1 반환
                       // str1이 str2보다 사전에 먼저 나오면 -1 반환
                       // str1과 str2가 같은 문자열이면 0 반환
  1. # include <stdio.h>
  2. # include <string.h>
  3.  
  4. int main(void)
  5. {
  6. char str1[80] = "pear";
  7. char str2[80] = "peach";
  8.  
  9. printf("사전에 나중에 나오는 과일 이름: ");
  10. if (strcmp(str1, str2) > 0)
  11. printf("%s\n", str1);
  12. else
  13. printf("%s\n", str2);
  14.  
  15. return 0;
  16. }

strcmp 함수는 두 문자열에서 우선 첫 문자의 아스키 코드 값을 비교한다.
아스키 코드 값이 크면 사전의 뒤에 나오는 문자열이 된다.

strcmp 함수는 문자의 아스키 코드 값을 비교하므로

대소문자, 숫자 특수문자가 섞인 경우는 반환값이 사전 순서와 다를 수 있다.

그럼 strncmp는 뭘까?

비교할 문자 수를 지정할 수 있다는 것!

profile
분석하는 남자 💻

0개의 댓글