[C] C언어 스터디 6주차
Review
- 포인터 == 주소
- 포인터 변수 : 주소를 담는 변수
int num = 10;
int* ptr = # // & : 앰퍼샌드. 주소.
- 포인터 변수 ptr이 num을 가리킨다 라고 표현하기도 함.
- num은 10
- ptr은 &num, 즉 0x1154
- *ptr은 num, 즉 10
- 역참조 연산자 * : 주소값에 접근하여 해당 주소에 저장되어 있는 값을 참조함
- 포인터 변수의 크기는 컴파일러의 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;
}
이차원 포인터
이차원 포인터의 이해
- 이차원 포인터는, 포인터의 포인터. 주소를 담는 변수인 포인터 변수의 주소를 담는 변수
- 즉, 이차원 포인터 변수가 담고 있는 변수의 값은 주소
- 그 주소값을 참조(*)하면 해당 메모리에는 또다시 다른 변수의 주소를 값으로 담고 있는 것!
int num = 10;
int *ptr = #
int **ptr2 = &ptr;
- 메모리를 그려서 구체적인 주소를 보면 아래와 같다.
- ptr2 == &ptr
- *ptr2 == ptr == &num
- **ptr2 == *ptr == num == 10
이차원 포인터의 사용
#include <stdio.h>
void main() {
int num = 10;
int* ptr = #
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]);
- 이차원 배열이란, 일차원 배열의 이름들을 담고있는 배열
- 즉, 배열의 주소들을 담고있는 배열
- 즉, 배열의 주소들을 담은 배열(의 이름)
- 즉, 주소를 담은 변수의 주소
- 포인터의 포인터, 이차원 포인터
이차원 포인터 응용
// 이차원 배열의 값들을 두 가지 접근 방식을 이용해 순차적으로 출력하는 코드
// 함수 응용
#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형 배열의 경우, 선언과 동시에 초기화 할 때만 “큰따옴표”를 이용해서 초기화 할 수 있다.
- 이후에는 배열의 각 요소에 문자를 하나하나 대입해주어야 한다.
메모리적 관점
- 문자열에서는, 널문자 ‘\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
- 스택 메모리가 손상되었다는 에러 메시지! 왜 났을까?
- 우선, 지역변수는 stack 메모리에서 할당된다.
- name1, name2는 각각 크기 4로 할당되었다. 즉, 문자 4개를 넣을 수 있다.
- String의 마지막에는 ‘\0’ 널문자가 반드시 포함되어야 한다. (string의 끝을 나타내는것이 널문자)
- string 관련 함수에서는 기본적으로 문자열의 마지막에 알아서 \0을 넣는 기능을 수행
- (만약 함수를 직접 구현한다면, 마찬가지로 반드시 \0 또는 0을 넣어주어야 한다)
- 인코딩 방식에 따라.. 한글의 경우 한 글자에 2byte. 따라서 박상민 넣으려면 널문자까지 각각 최소 7byte씩 필요하다.
- 4byte 밖에 할당 안해줬으므로 스택 메모리가 손상되었다는 에러 메시지가 발생하는 것.
- int arr[3];
- arr[10] = 10; // Error
- int arr[3];
배열 형식의 문자열 / 문자열 상수 형식의 문자열의 차이?
- “문자열 상수” 는 변경할 수 없는 상수, 저장된 시작 주소값 (읽기 전용. 포인터로 다룰 수 있음)
- const int num = 10; // 상수는 한번 선언하면 변경할 수 없다!
char *pstr = "string";
printf("%c", pstr[0]); // 출력은 가능
pstr[0] = 'c' // 수정은 불가.. 에러!
- Read_Only_Memory로 운영체제가 알아서 적당한 위치에 할당한다.
- 따라서 문자열 상수의 값은 수정이 불가!!
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); */
메모리 (복습)
(꼭 시간내서 읽어보는 것 추천)
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으로 분리하는 함수
등등… 많~다!
함수를 공부할 때 생각하며 볼 부분
- 무슨 기능을 수행하나?
- 매개변수로 주는 인자가 무엇인가?
- 타입은 뭔지
- 개수는 몇개인지
- 반환값은 있는가?
- 타입은 뭔지
- 어떤 원리인가?
문자 관련 함수
문자 하나 입력 받는 함수
- 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) {
// 기능 구현
}