[C] C언어 스터디 6주차

Review

  • 포인터 == 주소
  • 포인터 변수 : 주소를 담는 변수
int num = 10;
int* ptr = # // & : 앰퍼샌드. 주소.


image-20210725204905894

  • 포인터 변수 ptr이 num을 가리킨다 라고 표현하기도 함.
    • num은 10
    • ptr은 &num, 즉 0x1154
    • *ptr은 num, 즉 10
  • 역참조 연산자 * : 주소값에 접근하여 해당 주소에 저장되어 있는 값을 참조함


image-20210719155018683


  • 포인터 변수의 크기는 컴파일러의 bit 방식에 따라서 달라진다.
    • 우리는 32bit 형식으로 코딩하다 보니까 4byte.


  • 포인터의 사용
int *ptr = NULL; // 처음에 초기화를 안해줄 땐, NULL로 초기화 하는게 일반적
int num = 10;
ptr = #		// 포인터 변수 ptr에 num의 주소를 대입


  • Call by value : 이 복사되어 넘겨지는 것
void changeValue(int n);

void main() {
	int num = 10;
	
	printf("%d", num); // 10
	changeValue(num);  // 값이 복사되어 넘겨지는 것
	printf("%d", num); // 10
}

void changeValue(int n) {
	n = 20;
}


  • Call by reference : 주소가 복사되어 넘겨지는 것
void changeValue(int* n);

void main() {
	int num = 10;
	
	printf("%d", num); // 10
	changeValue(&num); // 주소가 복사되어 넘겨지는 것
	printf("%d", num); // 20
}

void changeValue(int* n) {
    *n = 20;
}
  • 주소를 넘겨주면, 해당 메모리의 주소에 직접 접근 할 수 있는 것!!
  • 배열의 이름은 배열의 첫 번째 주소 == 배열의 이름은 배열의 포인터


  • 배열을 함수에 넘겨, 포인터 접근 방식으로 순차적으로 출력해보는 코드
#include <stdio.h>

void printArray(int*, int);

void main() {
	int nums[5] = {1,2,3,4,5};

	printArray(nums, sizeof(nums)/sizeof(nums[0])); // (&nums) 또는 (&nums[0])
}

void printArray(int* arr, int size) {
    
    // 인덱스 접근 방식
    for (int i=0; i<size; i++) {
        printf("arr[%d] = %d\n", i, arr[i]);
    }

    // 주소 접근 방식
    for (int i=0; i<size; i++) {
        printf("*(arr+%d) = %d\n", i, *(arr+i);
    }
}



지나가는 깜짝 Quiz


다음 빈칸의 코드를 완성하시오.

  • 배열 arr의 값들 중, 짝수만 모두 더해서 출력하는 프로그램
    • 인덱스 접근 방식으로 배열에 접근 및 사용 할 수 있는지?
#include <stdio.h>
#define SIZE 5

int main() {
    int arr[5] = {90,97,99,98,100};
    int sum = 0;
    
    for (int i=0; i<SIZE; i++) {
        if ( arr[i] % 2 == 0 ) {	// arr의 요소가 짝수이면 (인덱스 접근 방식으로 할 것)
			 sum += arr[i];			// sum에 해당 요소를 더하라 (인덱스 접근 방식으로 할 것)
        }
        printf("arr[%d] = %d\n", i, arr[i]) // 인덱스 접근 방식 요소 출력
    }
    
    printf("%d\n", sum);
    return 0;
}


다음 빈칸의 코드를 완성하시오.

  • 배열 arr의 값들 중, 짝수만 모두 더해서 출력하는 프로그램
    • arr[i] (인덱스 접근 방식) 대신 포인터 접근 방식으로 주소 개념을 잘 이해하고 있는지?
#include <stdio.h>
#define SIZE 5

// 

int main() {
    int arr[5] = {90,97,99,98,100};
    int sum = 0;
    
    for (int i=0; i<SIZE; i++) {
        if ( *(arr+i) % 2 == 0 ) {	// arr의 요소가 짝수이면 (포인터 접근 방식으로 할 것)
			 sum += *(arr+i);	// sum에 해당 요소를 더하라 (포인터 접근 방식으로 할 것)
        }
        printf("*(arr+%d) = %d\n", i, *(arr+i)); // 포인터 접근 방식 요소 출력
    }
    
    printf("%d\n", sum);
    return 0;
}


다음의 출력 결과를 쓰시오.

  • 주소 개념을 잘 이해하고 있는지?
#include <stdio.h>

int main() {
    int arr[5] = {90,97,99,98,100};
    int* ptr = arr; 
    
    printf("%p\n", &arr[0]);	// 0x1110 라고 한다면

    printf("%p\n", arr);		// 1) 0x1110
    printf("%p\n", &arr);		// 2) 0x1110
    printf("%p\n", ptr);		// 3) 0x1110
    printf("%p\n", &ptr);		// 4) 주어진 정보로는 알 수 없다. 운영체제가 알아서 할당
    printf("%p\n", ptr+1);		// 5) 0x1114
        
    printf("%d\n", ptr[3]);		// 6) 98
    printf("%d\n", *ptr);		// 7) 90
	printf("%d\n", (*ptr)++);	// 8) 90 // 출력 후 *ptr == 91이 된 상태
    printf("%d\n", ++(*ptr));	// 9) 92
	printf("%d\n", *(ptr++));	// 10) 92
    printf("%d\n", *(++ptr));	// 11) 99

    return 0;
}



이차원 포인터

이차원 포인터의 이해

  • 이차원 포인터는, 포인터의 포인터. 주소를 담는 변수인 포인터 변수의 주소를 담는 변수
  • 즉, 이차원 포인터 변수가 담고 있는 변수의 값은 주소
    • 그 주소값을 참조(*)하면 해당 메모리에는 또다시 다른 변수의 주소를 값으로 담고 있는 것!

image-20210801140834982

int num = 10;
int *ptr = &num;
int **ptr2 = &ptr;
  • 메모리를 그려서 구체적인 주소를 보면 아래와 같다.

image-20210801140917301

  • ptr2 == &ptr
  • *ptr2 == ptr == &num
  • **ptr2 == *ptr == num == 10



이차원 포인터의 사용

#include <stdio.h>

void main() {
	int num = 10;
	int* ptr = &num;
	int** ptr2 = &ptr;

	printf("%d == %d == %d \n", num, *ptr, **ptr2);
	printf("%p == %p == %p \n", &num, ptr, *ptr2);
	printf("%p == %p \n", &ptr, ptr2);

	*ptr = 20;
	printf("%d\n", num);

	**ptr2 = 30;
	printf("%d\n", num);
}



이차원 포인터와 이차원 배열

  • 일차원 포인터와 일차원 배열의 경우, 앞서 Quiz에서 아주 적나라하게 알아보았다.
  • 이차원 포인터와 이차원 배열 역시 마찬가지!
    • (포인터와 배열의 차원이 같다고 생각하면 됨)
int twoArr[2][2] = { {1,2},	{3,4} };
int** ptrptr = twoArr; // == &twoArr == twoArr[0] == &twoArr[0] == &twoArr[0][0]

printf("twoArr \t\t= %p\n", twoArr);
printf("&twoArr \t= %p\n", &twoArr);
printf("twoArr[0] \t= %p\n", twoArr[0]);
printf("&twoArr[0] \t= %p\n", &twoAr[0]);
printf("&twoArr[0][0] \t= %p\n", &twoArr[0][0]);


  • 이차원 배열이란, 일차원 배열의 이름들을 담고있는 배열
    • 즉, 배열의 주소들을 담고있는 배열
    • 즉, 배열의 주소들을 담은 배열(의 이름)
    • 즉, 주소를 담은 변수의 주소
    • 포인터의 포인터, 이차원 포인터

image-20210802141540429



이차원 포인터 응용

// 이차원 배열의 값들을 두 가지 접근 방식을 이용해 순차적으로 출력하는 코드
// 함수 응용
#include <stdio.h>
#define ROW 4
#define COL 4

void printArrayWay(int [][COL]);	// 인덱스 접근
void printPointerWay(int **);		// 포인터 접근

void main() {
	int arr[ROW][COL] = {
		{00,01,02,03},
		{10,11,12,13},
		{20,21,22,23},
		{30,31,32,33}
	};

	printArrayWay(arr); // arr 대신 쓸 수 있는 것은?
	printArrayWay(arr); // arr[0], &arr[0], &arr, &arr[0][0]
}

void printArrayWay(int arr[][COL]) {
	for (int i = 0; i < ROW; i++) {
		for (int j = 0; j < COL; j++) {
			printf("arr[%d][%d] = %d | ", i, j, arr[i][j]);
            if (j == 3) {
                printf("\n");
            }
		}
	}
	printf("\n");
}

void printPointerWay(int **arr) {
	for (int i = 0; i < ROW; i++) {
		for (int j = 0; j < COL; j++) {
			//printf("**(arr + j + (i * ROW)) = %d | ", i, j, arr[i][j]);
            printf("*(*(arr + j) + i) = %d | ", i, j, arr[i][j]);
            if (j == 3) {
                printf("\n");
            }
        }
	}
}



문자열

문자열의 이해


문자열이란?

  • C언어에서 ‘문자열’은 따로 String 자료형이 없다.
    • char형 배열을 의미하기도 하고
    • 문자열 상수를 의미하기도 한다.
      • const int num = 100;
char charStr[20] = "캐릭터배열";
char *constStr = "문자열상수"
char str[10] = "Hello!";



문자열 사용


선언 및 초기화

// 선언과 동시에 초기화 하는 방법
char str[10] = "Hello!";
char *constStr = "문자열상수";

// 선언 후 초기화 하는 방법
char str[10];
// str = "Hello!"; // Error!
str[0] = 'H';
str[1] = 'e';
str[2] = 'l';
str[3] = 'l';
str[4] = 'o';
str[5] = '!';
str[6] = '\0';

char *constStr;
constStr = "문자열상수";
  • char형 배열의 경우, 선언과 동시에 초기화 할 때만 “큰따옴표”를 이용해서 초기화 할 수 있다.
  • 이후에는 배열의 각 요소에 문자를 하나하나 대입해주어야 한다.


메모리적 관점

image-20210802145244130

  • 문자열에서는, 널문자 ‘\0’를 기준으로 문자열의 끝을 판별한다.
    • 대입 연산자를 이용하거나 scanf 등의 format(%s)으로 초기화를 할 경우에는 알아서 들어가므로, 문자열의 크기를 설정할 때 고려해야 한다.
    • 또, 인코딩 형식(ANSI, UTF-8, UTF-16…)에 따라 한글의 byte 수가 달라지므로 마찬가지로 문자열의 크기를 설정할 때 고려해야 한다.
      • 보통 UTF-8을 주로 쓰는데, 한글은 글자당 2byte이다.


문자열 응용

  • 초기화 및 출력하기
#include <stdio.h>

void main() {
    char str[20] = "Hello, String!";
    char *strPtr = "Hello, String Pointer!";
    
    printf("%s\n", str);
    printf("%s\n", strPtr);
}


  • 문자열 입력 받아 출력하기
    • 형식 제어문자 %s를 이용해 문자열을 입력받을 수 있다.
#include <stdio.h>

void main() {
    char str[20];
    
    scanf("%s", str); // hello
    printf("%s\n", str); // hello\0
}


  • gets() 함수를 이용한 입력, puts를 이용한 출력하기
#include <stdio.h>

void main() {
    char str[20];
    
    gets(str);
    puts(str);
}



NULL 값

  • char 형에서 널값은 \0, 0 둘다 NULL을 의미한다.
  • int 형에서 0은 NULL이 아닌 ‘값’이다. Zero.
  • 포인터 형에서 NULL // 다른 언어에서도 이런식으로 함
    • char *ptr = NULL; // 아무것도 가리키지 않는다.


stack error

  • 스택 메모리가 손상되었다는 에러 메시지! 왜 났을까?

image-20201110142128321

  • 우선, 지역변수는 stack 메모리에서 할당된다.
  • name1, name2는 각각 크기 4로 할당되었다. 즉, 문자 4개를 넣을 수 있다.
    • String의 마지막에는 ‘\0’ 널문자가 반드시 포함되어야 한다. (string의 끝을 나타내는것이 널문자)
    • string 관련 함수에서는 기본적으로 문자열의 마지막에 알아서 \0을 넣는 기능을 수행
    • (만약 함수를 직접 구현한다면, 마찬가지로 반드시 \0 또는 0을 넣어주어야 한다)
  • 인코딩 방식에 따라.. 한글의 경우 한 글자에 2byte. 따라서 박상민 넣으려면 널문자까지 각각 최소 7byte씩 필요하다.
  • 4byte 밖에 할당 안해줬으므로 스택 메모리가 손상되었다는 에러 메시지가 발생하는 것.
    • int arr[3];
      • arr[10] = 10; // Error



배열 형식의 문자열 / 문자열 상수 형식의 문자열의 차이?

  • “문자열 상수” 는 변경할 수 없는 상수, 저장된 시작 주소값 (읽기 전용. 포인터로 다룰 수 있음)
    • const int num = 10; // 상수는 한번 선언하면 변경할 수 없다!
char *pstr = "string";

printf("%c", pstr[0]); // 출력은 가능
pstr[0] = 'c' // 수정은 불가.. 에러!
  • Read_Only_Memory로 운영체제가 알아서 적당한 위치에 할당한다.
  • 따라서 문자열 상수의 값은 수정이 불가!!

image-20210802154751748

char *pstr = "string";
pstr = "string2";
  • 위와 같은 상황의 경우, 상수의 값이 수정되는 것이 아니라, 아예 새로운 “string2”라는 상수를 메모리에 할당해서 그 주소를 바꿔 넣는 것이다.


char str[10] = "string";
char *pstr = "string2";

str[0] = 'k';
puts(str); // "ktring";

pstr[0] = 'k'; // Error!
  • 따라서 배열의 경우엔 요소 수정이 가능하지만, 문자열 상수의 경우엔 요소 수정이 불가능하다.
    • ‘허용되지 않는 접근’이라고 에러가 뜰 것. 전에 Array out of range 처럼..
  • 다만 접근하여 ‘읽는’ 것은 둘 다 가능하다.



1. 선언과 동시에 초기화가 되느냐??

char str[15] = "캐릭터 배열";		// 가능!
char *pstr = "문자열 상수";			// 가능!


2. 선언 이후에 초기화가 되느냐??

// char형 배열
char str[10] = "캐릭터 배열";
str = "수정된 배열";				// 불가!
str[0] = 's';					// 요소 하나하나 초기화 해야 함

// 문자열 상수
char *pstr = "문자열 상수";
pstr = "String_Const";			// 가능!


3. 선언 이후에 수정이 되느냐??

// char형 배열
char str[10] = "charactor";
printf("%c", str[0])		// 접근도 가능!
str[0] = 'k';				// 수정도 가능!

/*  결과 : "kharactor"  */


// 문자열 상수
char *pstr = "stringConst";
printf("%c", pstr[0])		// 접근은 가능!
pstr[0] = 'x'  				// 수정은 불가!

/*  결과 : "error" */


4. 문자열을 입력해서 넣을 수 있느냐?

  • // char형 배열
    char str[10];		// 선언과 동시에 메모리 10byte만큼 할당 (길이 10) \0 포함!
    scanf("%s", str)    // 길이 10까지 입력 가능 (\0 문자포함)		가능!
    
  • // 문자열 상수
    char *pstr = NULL; 		// 선언 시, char형 포인터 크기 4byte만큼 할당 (주소 대입 가능)
    scanf("%s", pstr);  	// 가리키는 주소가 없습니다!				불가!
      
    /*
    =>  나중에 배울 malloc 동적 메모리 할당을 통해 해결 가능!
    	pstr = (char*) malloc (sizeof(char) * 10); // 반환된다.
    	heap 메모리에 char형*10 크기의 메모리가 할당되고, 그 첫번째 주소가 pstr에 대입된다.
      	
    	scanf("%s", pstr);
    */
    



메모리 (복습)

Memory Structure1

Memory Structure2

(꼭 시간내서 읽어보는 것 추천)

image-20201110140010069

  • code

    • 실행할 프로그램의 코드가 들어가는 부분
    • 작성한 소스 코드가 저장되는 영역이다.


  • data

    • 프로그램이 시작과 동시에 할당 되고, 종료될 때 사라지는 영역
    • 그렇기 때문에 한번 초기화 하면 다시 초기화 할 수 없다
  • 정적변수(static), 전역변수


  • heap
    • 프로그래머가 관리할 수 있는 유일한 영역
    • 프로그래머에 의해 메모리를 할당 / 해제 할 수 있는 메모리 공간이다.
    • 동적 메모리 할당 (Dynamic Memory Allocation)
      • malloc(), realloc(), … free() 등…


  • stack
    • 프로그램 수행 도중 호출될 때 할당 / 수행 끝나면 사라지는 영역
    • 함수, 매개변수, 지역변수 등…
    • Last In First Out 형태의 자료구조 의미도 가지고 있다.



함수들

  • : printf, scanf, ....
  • ** 헤더파일**에 미리 정의된 문자열 관련 함수가 많이 존재한다.
    • strcpy(), strcat(), strcmp(), strstr(), strlen(), strtok() …
  • int strlen(char*) : 문자열 길이 구해 반환하는 함수
  • void strcpy(char*, char*) : 문자열을 복사하는 함수
  • void strcat(char*, char*) : 문자열을 이어 붙이는 함수
  • int strcmp(char*, char*) : 문자열이 같은지 비교하는 함수
  • int strstr() : 문자열 속에 문자열이 존재하는지 찾는 함수
  • void strtok() : 문자열을 token으로 분리하는 함수

등등… 많~다!

string 레퍼런스


함수를 공부할 때 생각하며 볼 부분

  • 무슨 기능을 수행하나?
  • 매개변수로 주는 인자가 무엇인가?
    • 타입은 뭔지
    • 개수는 몇개인지
  • 반환값은 있는가?
    • 타입은 뭔지
  • 어떤 원리인가?


문자 관련 함수

문자 하나 입력 받는 함수

  • ch = getchar();
    • 버퍼 있음 (Enter 눌러야 입력 완료)
    • 콘솔에 출력함
  • ch = getche();
    • 버퍼 없음 (Enter 안눌러도 입력 완료)
    • 콘솔에 출력함
  • ch = getch();
    • 버퍼 없음 (Enter 안눌러도 입력 완료)
    • 콘솔에 출력 안함


버퍼 개념 찾아보기



다음시간에 계속…



과제

문자의 빈도 출력하기

  • 문자를 하나 입력 받아 int ch에 저장한다.
  • 문자열에서 ch와 같은 문자를 찾아 그 개수를 반환하는 함수 numberOfChar() 함수를 구현한다.
#include <stdio.h>

int numberOfChar(char*, char ch); // or char []

void main() {
	int count;
	char ch;
	char str[1000] = "English is a West";

	printf("찾을 문자를 하나 입력하세요 : ");
	ch = getche();
	
	count = numberOfChar(str, ch);
	printf("가 %d개 들어있습니다.\n", count);
}

int numberOfChar(char *str, char ch) {
	// \0 널문자는 문자열의 끝을 의미한다.
	int count = 0;

	// for (int i = 0; str[i] != '\0'; i++);

	while (*str !='\0') {
		if (*str == ch) {
			count++;
		}
		str++;
	}

	return count;
}



문자열의 길이 구하기

  • 문자열을 입력 받아 str 배열에 저장한다.
  • char형 배열에서 문자의 개수(공백 포함, 널문자 포함)를 반환하는 함수 myStrLen()을 구현한다.
#include <stdio.h>

int myStrLen(char *);

void main() {
    char str[100];
    
    // str에 문자열 입력받기
    
    // str를 인자로 넘겨 myStrLen() 호출 및 반환값 출력
}

int myStrLen(char *arr) {
    // 기능 구현
}




© 2020.07. by Sanggoe

Powered by Sanggoe