6 분 소요

문자와 문자열 출력

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main() {
    //문자 선언과 출력
    char ch = 'A';
    printf("%c %d\n", ch, ch);

    //문자열 선언 방법1
    char java[] = { 'J', 'A', 'V', 'A', '\0' };
    printf("%s\n", java);

    //문자열 선언 방법2
    char py[] = "Python";
    printf("%s\n", py);

    //문자열 선언 방법3
    char csharp[5] = "C#";
    printf("%s\n", csharp);

    //문자 배열에서 문자 출력
    printf("%c %c\n", csharp[0], csharp[1]);

    return 0;
}

output>

A 65

JAVA

Python

C#

C #

cf. 다양한 문자열 선언 방법

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main() {
    char st1[] = { 'H','E','L','L','O',' ', 'W','O','R','L' ,'D','\0' };
    printf("st1: %s\n", st1);
    char st2[] = "HELLO WORLD";
    int i = 0;
    while (st2[i] != '\0') {
        printf("st2: %c", st2[i]);
        i++;
    }

    

    return 0;
}

문자열 중간 ‘\0’을 삽입해 문자열 분리

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main() {
    char c[] = "C C++ Java";
    printf("%s\n", c);
    c[5] = '\0';
    printf("%s\n%s\n", c, (c + 6));

    //문자 배열의 각 원소를 하나하나 출력하는 방법
    c[5] = ' '; //널 문자를 빈 문자로 바꾸어 문자열 복원
    char* p = c;
    while (*p) //(*p != '\0')도 가능
        printf("%c", *p++);
    printf("\n");
    

    return 0;
}

문자 입출력 함수에 대해서

“내가 아무리 값을 입력해도 실제로 엔터를 쳤을때 프로그램에 값이 이식되는 것이다. 이를 라인버퍼링이라 부른다.”

1.getchar()

2.putchar()

3._getche()

4._getch()

함수 scanf()와 printf(), gets()와 puts()의 문자열 입출력

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>

int main() {
    char name[20], dept[30]; //char *name, *dept; 실행 오류 발생
    int snum;

    printf("학번  이름  학과  입력  >>\n");
    scanf("%d %s %s", &snum, dept, name);
    printf("출력: %d %s %s \n", snum, dept, name);

    char line[101]; // char *line으로는 오류발생
    printf("한 행에 학번 이름 학과 입력한 후 Enter 누르고 ");
    printf("새로운 행에서 [ctrl + z]를 누르세요\n");
    while (gets(line))
        puts(line);
    printf("\n");

    
    return 0;
}

코드리뷰: 예제와 달리 get_s()사용하는 구문은 여기서 사용하지 못했다. 왜냐하면 컴파일러 버전상 해당 기능이 제대로 동작하지 않았기 때문이다. 솔루션이 있기는 하겠으나 현재는 찾지 못한 상황이다.

scanf()대신 get()활용해보기

Q. 이둘의 차이가 뭘까?

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <conio.h>

int main() {
    char s[100];
    //문자배열 s에 표준입력한 한 행을 저장
    gets(s);

    //문자배열에 저장된 한 행을 출력
    char* p = s;
    while (*p)
        printf("%c", *p++);
    printf("\n");

    return 0;
}

문자열 함수 strlen()과 memcpy(), strcmp()의 활용

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main() {
    char src[20] = "C Python";
    char dst[20];

    printf("%s\n", src);   
    printf("%zu\n", strlen(src)); 
    memcpy(dst, src, strlen(src) + 1);
    printf("%s\n", dst);
    memcpy(dst, "안녕하세요!", strlen("안녕하세요!") + 1);
    printf("%s\n\n", dst);

    char* s1 = "C lang";
    char* s2 = "C lang";
    printf("strcmp(%s, %s) = %d\n", s1, s2, strcmp(s1, s2));
    s1 = "C lang";
    s2 = "C lang";
    printf("strcmp(%s, %s) = %d\n", s1, s2, strcmp(s1, s2));
    printf("strcmp(%s, %s) = %d\n", s2, s1, strcmp(s2, s1));
    printf("strcmp(%s, %s, %d) = %d\n", s1, s2, 2, strcmp(s1, s2, 2));

    return 0;
}

코드리뷰:

(1~6)

-“C Python”을 출력합니다.

-문자열의 길이를 출력합니다.

-src를 dst로 복사합니다.

-복사된 문자열을 출력합니다.

-“안녕하세요!”를 dst에 복사합니다.

-새로운 문자열을 출력합니다.

(7~10)

-두 문자열을 비교합니다.

-동일한 문자열을 다시 비교합니다.

-이번에는 순서를 바꾸어 비교합니다.

-처음 두글자만 비교합니다.

고찰: memcpy()와 strcmp()를 썼다는 것에 주목할만 하다.

memcpy()는 문자열을 복사해올때 쓰고, strcmp()는 두 문자열을 비교할때 사용한다.

중요! 함수 strcp()와 strcat()를 사용시 주의사항

두 함수 모두 첫번째 인자인 dest는 복사 결과나 연산결과가 뒤탈없이? 저장될 수 있도록 충분한 공간을 확보해야 한다. 우리가 주차장에 들어갔는데, 모닝정도만 들어갈수있는 공간에 대형 버스가 들어간다고 해보자.

그러면 다 못들어가고 잘려버릴 것이다. 마찬가지로 이것도 값이 들어갈 수는 있으나, 다 들어가지는 못하는 이슈가 발생한다. 어려운 말로 무결성이 깨진다고 볼수도 있을 것 같다.

또한 문자열 관련 함수에서 단순히 문자열 포인터를 수정이 가능한 문자열의 인자로 사용할 수 없다. 즉 함수 strcpy()와 strcat()에서 첫 인자로 문자열 포인터 변수는 사용할 수 없다.

문자열 복사와 연결함수 strcpy()와 strcat()활용

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main() {
    char dest[20] = "Java";
    char source[80] = "C is a language.";

    printf("%s\n", strcpy(dest, source));
    
    printf("%s\n", strncpy(dest, "C#", 2));
    dest[2] = '\0';

    printf("%s\n\n", strncpy(dest, "C#", 3));
    dest[3] = '\0';

    char data[80] = "C";

    printf("%s\n", strcat(data, " is "));

    printf("%.*s\n", 2, strncat(data, "a java", 2));  // data 배열 크기를 초과하지 않도록 수정
    data[16] = '\0';  // 배열 경계 초과 수정

    printf("%s\n", strcat(data, "procedural"));

    printf("%s\n", strcat(data, " language."));
    return 0;
}

문자열에서 지정한 분리자(델리미터)를 사용해 문자열 토큰 분리

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "C and C++\\t languages are best!";
    char* delimiter = " !\\t";
    // char *next_token;

    printf("문자열 \"%s\"을 >>\n", str);
    printf("구분자[%s]를 이용하여 토큰을 추출 >>\n", delimiter);
    char* ptoken = strtok(str, delimiter);
    while (ptoken)
    {
        printf("%s\n", ptoken);
        ptoken = strtok(NULL, delimiter);
    }

    return 0;
}

다양한 문자열 관련 함수의 이해

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "JAVA 2022 Python C";
    printf("strlen(""python""): %zu\n", strlen("python"));
    printf("%s, \n", _strlwr(str));
    printf("%s\n\n", _strupr(str));

    printf("%s, \n", strstr(str, "VA"));
    printf("%s\n", strchr(str, 'A'));

    return 0;
}

strlen(), _strlwr(), _strupr(), strstr(),strchr()에 대해 알아보았다.

한가지 알게 된 게, strstr()은 문자열을 인자로 받고, strchr()은 문자를 인자로 받는다.

문자열을 역순을 저장하는 함수 reverse() 구현

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

void reverse(char str[]);

int main() {
    char s[50];
    const char* str = "Python C";  // 포인터를 상수 포인터로 수정
    memcpy(s, str, strlen(str) + 1);
    printf("%s\n", s);

    reverse(s);
    printf("%s\n", s);

    return 0;
}

void reverse(char str[])
{
    int j = (int)strlen(str) - 1;  // j를 초기화
    for (int i = 0; i < j; i++, j--)  // 조건식 수정
    {
        char c = str[i];
        str[i] = str[j];
        str[j] = c;
    }
}

코드리뷰:

const char* str = "Python C";와 같이 문자열은 상수포인터로 선언해주는게 일반적이라고 하는 것 같다.

memcpy(s, str, strlen(str) + 1);를 넣지 않으면 콘솔에 출력은 되지만 모두 깨져서 출력되는 이슈가 있다.

이것의 기능이 아직은 잘 와닿지 않는다.

여러 개의 문자열을 선언과 동시에 저장하고 처리하는 방법

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main() {
    char* pa[] = { "JAVA", "C#", "C++" };
    char ca[][5] = { "JAVA", "C#", "C++" };

    printf("%s ", pa[0]); printf("%s ", pa[1]); printf("%s\n", pa[2]);
    printf("%s ", ca[0]); printf("%s ", ca[1]); printf("%s\n", ca[2]);

    //문자 출력
    printf("%c %c %c\n", pa[0][1], pa[1][1], pa[2][1]);
    printf("%c %c %c\n", ca[0][1], ca[1][1], ca[2][1]);

    return 0;
}

명령행 인자 출력

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(int argc, char* argv[]) {
    int i = 0;

    printf("실행 명령해 인자(command line arguments) >> \n");
    printf("argc = %d\n", argc);
    for (i = 0; i < argc; i++)
        printf("argv[%d] = %s\n", i, argv[i]);
   
    return 0;
}

결과>

image-20240111111754508

image-20240111111932630

LAB 여러 문자열 처리

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

int main(int argc, char* argv[]) {
    char str1[] = "Python";
    char str2[] = "Kotlin";
    char str3[] = "Tensorflow";

    char* pstr[] = { str1, str2, str3 };

    //각각의 3개 문자열 출력
    printf("%s ", pstr[0]);
    printf("%s ", pstr[1]);
    printf("%s \n", pstr[2]);

    //문자 출력
    printf("%c %c %c\n", str1[0], str2[1], str3[2]);
    printf("%c %c %c\n", pstr[0][1], pstr[1][1], pstr[2][1]);

    return 0;
}

pstr에 배열ㅇㅇ이름 나열하고 데이터타입 문자열 포인터 맞는지? 배열이름은 뭐다 캐릭터 포인트!

만약 저기 str이 아니고 숫자배열이면?

pstr 앞에 int*이 들어가야지!

이 코드가 그대로 주어지고 이를 동적할당방식으로 바꿔서 표현하라는 문제 나옴!

메모리 길이만큼 strcpy로 붙여넣자.

이 배열 이름이 s다.

s[0], s[1], s[2]이렇게 해서 접근하도록 하자

포인터 배열에 대해서

stringOps라는 함수포인터 배열을 사용하여, 포인터로 이루어진 배열을 생성, 각 요소가 포인트로서 함수를 가리키도록 설정함. 첫번째 포인터는 toUpperCase()를, 두번째 포인터는 toLowerCase()를 가리키도록 함.

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>


void toUpperCase(char* str) {
    while (*str) {
        *str = toupper((unsigned char)*str);
        str++;
    }
}

void toLowerCase(char* str) {
    while (*str) {
        *str = tolower((unsigned char)*str);
        str++;
    }
}

int main(int argc, char* argv[]) {
    char text[] = "Hello World!";
    void (*stringOps[2])(char*) = { toUpperCase, toLowerCase };  // 올바른 배열 초기화 문법 사용

    stringOps[0](text);
    printf("Uppercase: %s\n", text);

    stringOps[1](text);
    printf("Lowercase: %s\n", text);

    return 0;   
}

위 함수-포인터-배열을 이용해서 좀더 확장시켜보았다.

※함수 포인터 배열 응용

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <ctype.h>

void foo(char* str) {
    int length = strlen(str); //format: strlen({포인터 이름})
    for (int i = 0, j = length - 1; i < j; i++, j--) {
        char temp = str[i];
        str[i] = str[j];
        str[j] = temp;
    }
}


void bar(char* str) {
    //1. 한번더 뒤집기(복원하기)
    int length = strlen(str); //format: strlen({포인터 이름})
    for (int i = 0, j = length - 1; i < j; i++, j--) {
        char temp = str[i];
        str[i] = str[j];
        str[j] = temp;
    }

    //2. 대문자로 바꾸기
    while (*str) { //*str이 가져온 값이 마지막값인 널이 되면 탈출함
        *str = toupper((unsigned char)*str);
        str++;
    }
}

void whatFuck(char* str) {
    //1. 소문자로 바꾸기
    while (*str) { //*str이 가져온 값이 마지막값인 널이 되면 탈출함
        *str = tolower((unsigned char)*str);
        str++;
    }
}

int main() {
    char text[] = "Hello World!";
    void(*funcCollection[])(char*) = {foo, bar, whatFuck};
    //문자열을 반전시키고 출력
    funcCollection[0](text);  //<-foo(text);
    printf("%s\n", text);

    //문자열을 대문자로 바꾸고 출력
    funcCollection[1](text);
    printf("%s\n", text);

    //문자열을 소문자로 바꾸고 출력
    funcCollection[2](text);
    printf("%s\n", text);

    return 0;   
}

output>

!dlroW olleH HELLO WORLD! hello world!

void형 포인터에 대해

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

// 주어진 데이터 타입에 따라 값을 출력하는 함수
void printValue(void* value, char type) {
    switch (type) {
    case 'i':
        printf("%d\n", *(int*)value);  // 정수형 데이터 출력
        break;
    case 'f':
        printf("%f\n", *(float*)value);  // 부동소수점형 데이터 출력
        break;
    case 'c':
        printf("%c\n", *(char*)value);  // 문자형 데이터 출력
        break;
    }
}

int main() {
    int i = 10;
    float f = 23.5;
    char c = 'x';

    // 각 데이터 타입에 대한 값을 출력
    printValue(&i, 'i');
    printValue(&f, 'f');
    printValue(&c, 'c');

    return 0;   
}
  1. #define _CRT_SECURE_NO_WARNINGS: visual studio에서 발생할 수 있는 경고 메시지를 무시하도록 하는 pragma입니다.
  2. printValue: void형 포인터를 사용하여 임의의 데이터 유형을 받아들이고 ‘type’매개변수를 통해 어떤 유형의 데이터인지 지정을 받아 출력하게 됩니다.
  3. main함수에서는 정수형, 부동소수점형 , 문자형의 변수를 만들어서 ‘printValue’함수를 통해 각각의 값을 출력하고 있습니다.

실습3번문제 응용)

3-2)사용자로부터 아이디와 비밀번호를 입력받아 유효성 검증을 수행하는 프로그램을 작성하세요. 다음과 같은 조건을 따라야 합니다.

-아이디와 비밀번호는 모두 5자 이상 15자 이하이어야 합니다.

-아이디와 비밀번호는 동일해서는 안됩니다.

-비밀번호에는 최소한 한 개 이상의 숫자가 포함되어야 합니다.

-프로그램은 유효한 아이디와 비밀번호가 입력될 때까지 계속해서 입력을 받아야 합니다.

-유효한 아이디와 비밀번호가 입력되면 “Registration Complete”를 출력하고 프로그램을 종료하세요.

정답>

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

//비밀번호 유효성 검증 함수
int validatePassword(char* username, char* password) {
	//info값 길이 검증
	if (strlen(username) < 5 || strlen(username) > 15) {
		printf("Retry\n");
		return 0;	// 비밀번호 길이가 유효하지 않음
	}
	if (strlen(password) < 5 || strlen(password) > 15) {
		printf("Retry\n");
		return 0;	// 비밀번호 길이가 유효하지 않음
	}

	// 두 info값이 같은지 확인 (같으면 strcmp()에서 0 반환)
	if (strcmp(username, password) == 0) {
		printf("Retry\n");
		return 0; //두 info값이 같음
	}

	// password에 숫자가 하나도 없는지 확인
	printf("password에 숫자가 하나도 없는지 확인");
	int flag = 0;
	while (*password) {
		printf("*password: %c\n", *password);
		if (isdigit(*password)) {
			flag = 1; //숫자가 하나도 없음
			break;
		}
		password++;
	}
	if (flag) {
		return 1;
	}
	
	return 0; //info값 유효성 통과
}

int main() {
	char username[20]; //비밀번호를 저장할 배열, 충분한 크기로 지정
	char password[20]; 
	
	do {
		//사용자로부터 비밀번호 입력받기
		printf("Username: ");
		scanf("%s", username);


		//두번째 비밀번호 입력받기
		printf("password: ");
		scanf("%s", password);
	} while (!validatePassword(username, password));

	printf("Registration Complete\n");

	return 0;
}

댓글남기기