220905 C언어#12

김혜진·2022년 9월 5일
0

C언어

목록 보기
12/13

C언어 #12

%d 10진수
%x 16진수
%p 포인터 값을 16진수로 출력

순차정렬 과제


내 코드

#include<stdio.h>

int main(void)
{
	int arr[] = {3,5,2,11,10};
	int i = 0;
	int imax = arr[0];

	for (i = 0; i < 4; i++)
	{
		if (arr[i] < arr[i + 1])
		{
			imax = arr[i + 1];
		}
	}
	printf("%d", imax);
}

구조체

구조체란 무엇인가

  • 구조체의 정의
    하나 이상의 서로 다른 종류의 변수들을 묶어서 새로운 자료형을 정의하는 것

구조체를 사용하는 이유

  • 연관된 변수들을 하나로 묶어서 관리함으로써 데이터 관리에 유용하다.
  • 데이터의 양(변수의 개수)이 많아지면 구조체가 유리하다.
  • 예를 들어 학생 정보 관리 변수를 보자.
    • 이름, 나이, 성별 등의 정보들은 모두 변수로 선언.
    • 각각의 변수를 별도로 관리하면 연관성을 알 수 없음.
    • 학생 수가 10명 이내이면 괜찮지만, 100, 200명으로 늘어나면 변수의 관리가 힘들어짐. 구조체가 대안.

구조체 정의하기

  • 구조체의 기본 형태(학생 정보)
struct student
{
	char name[10];
    int age;
    int height;
}

struct 키워드는 구조체라는 자료형을 의미
student는 내가 만든 구조체의 이름
name, age, height는 구조체 멤버

  • 구조체는 사용자가 정의한 새로운 자료형이다. ⇒ 추상적이다

모든 사람이 아는 개념 ⇒ 구체적
나만 알고 있는 개념 ⇒ 추상적

  • student는 사용자인 내가 정의한 새로운 자료형이다.

strcpy(대상문자열, 원본문자열);
strcpy 함수는 문자열을 다른 곳으로 복사하며 함수 이름은 string copy에서 따왔다. (string.h 헤더 파일에 선언되어 있음).

구조체 선언하기

struct student
{
	char name[10];
    int age;
    int height;
};

int main(void)
{
	student str1;
    student str2;
    ...
}

struct(구조체) student(자료형) str1(변수 이름);

struct student
{
	char name[10];
    int age;
    int height;
}str1,str2;

구조체를 간단하게 선언하는 방법

str1과 str2는 각 18바이트로, 구조체 멤버들의 크기 값의 합이다.

구조체 멤버에 접근하기

  • 구조체 변수를 통해 구조체 멤버의 값을 참조해야 한다.
    [구조체 변수명].[구조체 멤버]
    ex) st1.name, st1.age, st1.height

  • 멤버에 접근 시 .(점)을 사용하는데, 이를 직접 접근이라고 한다.

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

struct student
{
    char name[10];
    int age;
    int height;
}st1;

int main(void)
{
    strcpy_s(st1.name, "김혜진");
    st1.age = 2;
    st1.height = 15;
    printf("이름 = %s, 나이 = %d, 키 = %d\n", st1.name, st1.age, st1.height);
    
    return 0;
}

출력결과
이름 = 김혜진, 나이 = 2, 키 = 15

  • 포인터 변수의 구조체 멤버 접근
    구조체 변수를 포인터로 사용하게 되면 포인터가 가리켜야 할 대상이 있어야 한다.
int a, *pA;
pA = &a;
#include <stdio.h>
#include <string.h>

struct student
{
    char name[10];
    int age;
    int height;
}st, * pSt;

int main(void)
{
    pSt = &st;
    strcpy_s(pSt->name, "김혜진");
    pSt->age = 2;
    pSt->height = 15;
    printf("이름 = %s, 나이 = %d, 키 = %d\n", pSt->name, pSt->age, pSt->height);

    return 0;
}

*pSt와 st의 타입이 동일하므로 값에 접근 가능함

포인터 변수에서 구조체 멤버를 참조할 시 ->를 사용하였는데 이는 포인터의 간접 참조 연산자를 의미한다.
-> Arrow(애로우)라고 부르기도 함.

● 사용자로부터 입력받은 값을 구조체를 이용하여 출력

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

struct object
{
	char name[10];
	int height;
	int weight;
}obj;

int main(void)
{
	printf("물건의 이름 : ");
	fgets(obj.name, 9, stdin);
    obj.name[strlen(obj.name)-1] = '\0';

	printf("물건의 높이(cm) : ");
	scanf_s("%d", &obj.height);

	printf("물건의 무게(kg) : ");
	scanf_s("%d", &obj.weight);

	printf("\n");
	printf("보낼 물건의 정보 : %s, %dcm, %dkg", obj.name, obj.height, obj.weight);
}

출력결과
물건의 이름 : 책
물건의 높이(cm) : 20
물건의 무게(kg) : 30

보낼 물건의 정보 : 책, 20cm, 30kg

gets(obj.name)
입력받는 문자보다 더 많은 문자를 받을 수 있는 문제점이 있음.

fgets(obj.name, 9 ,stdin)
입력하는 타겟 지정, 입력받을 수 (그 이상으로 입력받으면 뒤를 자름)
fgets() 함수는 scanf()나 gets()와는 달리 뒤에 개행문자가 붙는다.
str[strlen(str) - 1] = '\0'; 개행문자 제거

fgets 함수는 실행 후 마지막 인덱스에 \n을 삽입하기 때문에 자동 개행이 된다.

구조체 변수 초기화 하기

  • 일반적 초기화 방법
    [구조체 변수].[구조체 멤버] = 데이터 값

  • 선언과 동시에 초기화 방법

struct student
{
    char name[10];
    int age;
    int height;
}st = {"김혜진", 2, 15};

또는

student st = {"김혜진", 2, 15};

출력결과
이름 = 김혜진, 나이 = 2, 키 = 15

구조체와 배열

구조체는 자료형이므로 배열형으로 선언할 수 있다.
여러 사람의 정보를 관리하고 싶다면 구조체 변수를 배열로 선언하면 된다.

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

struct student
{
    char name[10];
    int age;
    int height;
}st[5];

또는

student st[5];
struct student
{
    char name[10];
    int age;
    double height;
}st[5] = { {"김혜진", 2, 15},
        {"이주성", 3, 125.8},
        {"송유흠", 4, 15.0},
        {"김민성", 5, 145.6},
        {"김민준", 6, 152.7}
};

int main(void)
{
    int i;
    printf("st[i] 구조체 멤버의 초기값 출력\n");
    for (i = 0; i < sizeof(st) / sizeof(st[0]); i++)
    {
       printf("이름 = %s, 나이 = %d, 키 = %f\n", st[i].name, st[i].age, st[i].height);
    }
    st[2].height = 146;
    printf("\nst[2] 구조체 멤버의 데이터 변경\n");
    printf("이름 = %s, 나이 = %d, 키 = %f\n", st[2].name, st[2].age, st[2].height);
    return 0;

}

출력결과
st[i] 구조체 멤버의 초기값 출력
이름 = 김혜진, 나이 = 2, 키 = 15.000000
이름 = 이주성, 나이 = 3, 키 = 125.800000
이름 = 송유흠, 나이 = 4, 키 = 15.000000
이름 = 김민성, 나이 = 5, 키 = 145.600000
이름 = 김민준, 나이 = 6, 키 = 152.700000

st[2] 구조체 멤버의 데이터 변경
이름 = 송유흠, 나이 = 4, 키 = 146.000000

  • 구조체 배열 구조

구조체와 포인터

구조체는 자료형이므로 포인터형으로 선언할 수 있다.
앞서 사용했던 student 구조체를 다음과 같이 포인터형으로 선언할 수 있다.

struct student *pSt;

예제

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

struct student
{
    char name[10];
    int age;
    double height;
}st[5] = { {"김혜진", 2, 15},
        {"이주성", 3, 125.8},
        {"송유흠", 4, 15.0},
        {"김민성", 5, 145.6},
        {"김민준", 6, 152.7}
};

int main(void)
{
    int i;
    struct student* pSt;
    pSt = st;
    printf("st[i] 구조체 멤버의 초기값 출력\n");
    for (i = 0; i < 5; i++)
    {
        printf("이름 = %s, 나이 = %d, 키 = %.2f\n", st[i].name, st[i].age, st[i].height);
    }
    printf("\nst[2] 구조체 포인터를 이용한 출력\n");
    for (i = 0; i < 5; i++)
    {
        printf("이름 = %s, 나이 = %d, 키 = %.2f\n", (pSt+i)->name, (pSt + i)->age, (pSt + i)->height);
    }
    return 0;

}

출력결과
st[i] 구조체 멤버의 초기값 출력
이름 = 김혜진, 나이 = 2, 키 = 15.00
이름 = 이주성, 나이 = 3, 키 = 125.80
이름 = 송유흠, 나이 = 4, 키 = 15.00
이름 = 김민성, 나이 = 5, 키 = 145.60
이름 = 김민준, 나이 = 6, 키 = 152.70

st[2] 구조체 포인터를 이용한 출력
이름 = 김혜진, 나이 = 2, 키 = 15.00
이름 = 이주성, 나이 = 3, 키 = 125.80
이름 = 송유흠, 나이 = 4, 키 = 15.00
이름 = 김민성, 나이 = 5, 키 = 145.60
이름 = 김민준, 나이 = 6, 키 = 152.70


열거형

열거형이란

열거형은 enumeration의 약자로, enum(이넘)이라고 읽는다.
데이터들을 열거한 집합이다.
컴파일러는 열거형 멤버들을 정수형 상수로 취급한다.

열거형의 사용법

키워드는 enum 을 사용하여 정의한다.

enum Week
{
	sun = 0;
    mon
    tue
    wed
    thu
    fri
    sat
}

열거형의 멤버들은 각 요일을 나타낸다.
첫 번째 멤버 sun을 0으로 설정하면 다음 멤버 mon은 1씩 증가하여 1, tue는 2...

enum Week
{
	sun = 1,
	mon,
	tue,
	wed,
	thu,
	fri,
	sat
};

int main(void)
{
	enum Week day;
	printf("요일을 입력하세요(1.일 2.월 3.화 4.수 5.목 6.금 7. 토 : ");
	scanf_s("%d", &day);

	switch (day)
	{
	case sun:
		printf("일요일엔 짜파게티\n");
		break;
	case mon:
		printf("월요일엔 스트레스\n");
		break;
	case tue:
		printf("화요일에 만나요\n");
		break;
	case wed:
		printf("수요일엔 빨간 장미를\n");
		break;
	case thu:
		printf("목요일엔 뭐하지?\n");
		break;
	case fri:
		printf("금요일엔 달걀 후라이\n");
		break;
	case sat:
		printf("토요일엔 나들이\n");
		break;
	default:
		printf("잘못 입력하셨습니다.\n");
		break;
	}
	return 0;
}

열거형으로 사용자 정의 타입 구현 가능
실무에 자주 적용됨!


메모리의 구조

메모리 영역

메모리는 크게 코드영역, 스택영역, 힙영역, 데이터영역 총 4가지로 구분

힙 영역이 동적 영역에 해당한다.

  • 코드(code)영역
    소스코드가 저자오디는 영역으로, 실행할 명령어들이 순서대로 쌓인다.
    CPU가 이 영역에서 명령어들을 하나씩 가져다가 처리한다.

  • 스택(stack)영역
    스택이란 모든 원소들의 삽입 삭제를 한쪽 방향에서만 수행하도록 하는 선형 자료구조이다.
    이를 후입 선출방식(LIFO)이라고 한다. 나중에 들어온 녀석이 먼저 빠져나간다는 뜻

    우리가 지금껏 사용한 지역변수 및 매개변수 등은 모두 스택 메모리를 사용

void Test(int a)
{
	char b = 'A',
    int c = 1;
    double d = 3.14;
}

이러한 매개 변수 및 지역 변수가 스택 메모리에 어떠한 구조로 저장되는지 보자.

  • 힙(heap)영역
    힙은 컴퓨터 메모리의 일부가 할당되었다가 회수되는 일들의 반복을 의미
    힙은 컴파일 시가 아닌 실행(런타임) 시 사용자로부터 할당 메모리를 입력 받음

  • 데이터 영역
    전역변수와 static 변수가 저장되는 메모리 영역
    이 메모리는 프로그램 종료 시 소멸


❗ 동적 메모리 할당

객체지향에서는 동적메모리를 꼭 사용함. (중요)

동적으로 메모리를 할당하는 이유

  • 일반 변수 선언 형태
int a;
double b;

메모리 할당은 컴파일 타임(compile-time)에 이루어진다.

  • 전교생이 10명인 학교의 학생 수를 배열로 선언한다면?
int studnet[10];
  • 학생수가 늘어나게 되면?
int student[100];

이는 미봉책에 불과하다. 학생 수가 어떻게 변할 지 모른다. 어떤 학교는 1000명 이상이 될 수도 있다.

  • 결국 학생 수는 유동적이므로 고정하지 말고 실행 시 결정하자.
int num;
fputs("학생 수를 입력하세요: , stdout);
scanf_s("%d", &num);
int student[num];
  • 문제점
    scanf는 런타임(run-time)에 실행
    int student[num]은 컴파일타임(compile-time)에 실행
    런타임에 입력받은 변수를 컴파일 타임에 대입하는 형태는 논리에 맞지 않는다.

    소스코드⇒컴파일(obj)(컴파일 타임)⇒링크(lib)⇒실행(exe)(0과 1)(런타임)
    scanf는 런타임에 입력을 받아야 하는데, 배열의 길이는 컴파일 타임에 결정이 되어야하므로 시점이 맞지 않아 오류가 남.

  • 해결방법
    실행 중에 학생 수를 알아야하는 경우, 동적 메모리 할당 기법을 통해 문제 해결이 가능하다.

메모리 할당 및 해제

  • 동적 메모리 할당 함수의 원형
void* malloc(size_t size);

엠얼록 또는 말록이라고 읽는다.
전달인자 size는 바이트 단위로 입력
메모리 할당이 되면 메모리의 주소값을 리턴
메모리 부족 시 NULL 포인터 리턴
리턴형이 void*인데, 타입이 지정되어 있지 않는 포인터를 리턴 ( 난 당신이 원하는 메모리 크기만큼 할당해줄테니, 메모리는 당신이 원하는 형태로 정해서 사용하세요.)

  • 동적 메모리 해제 함수의 원형
void free(void* memblock);

메모리 사용 후 반드시 해제해주어야 한다.
전달인자로 메모리를 가리키는 포인터를 대입한다. (이 메모리는 자유예요)
free를 안해주면 메모리 누수(memory leak)가 일어난다.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int num;
	int* student; // 스택메모리 할당 (4바이트) 컴파일 시점 할당

	fputs("학생 수를 입력하세요 : ", stdout);
	scanf_s("%d", &num);

	student = (int*)malloc(sizeof(int) * num); // 런타임 시점 할당

	if (student == NULL)
	{
		printf("메모리가 부족하여 메모리를 할당할 수 없습니다.\n");
		return 0;
	}
	
	printf("할당된 메모리의 크기는 %d 입니다\n", sizeof(int) * num);
	
	free(student);

	return 0;
}

정적메모리 <-> 동적메모리 차이
사용자로부터 입력받는 값이 있음.

#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int num, i, total = 0;
	int* student;

	fputs("학생 수를 입력하세요 : ", stdout);
	scanf_s("%d", &num);

	student = (int*)malloc(sizeof(int) * num);

	if (student == NULL)
	{
		printf("메모리가 부족하여 메모리를 할당할 수 없습니다.\n");
		return 0;
	}

	for (i = 0; i < num; i++)
	{
		printf("%d번째 학생의 성적 입력 : ", i + 1);
		scanf_s("%d", &student[i]);
	}

	for (i = 0; i < num; i++)
	{
		total += student[i];
	}

	printf("총점: %d, 평균 : %d \n", total, total / num);
	
	free(student);

	return 0;
}

메모리 재할당

실시간 메모리를 할당하여 사용한다 해도 사용중에 메모리 크기를 더 늘려야 하는 경우가 발생할 수 있다.
malloc 함수로 할당된 메모리를 다시 동적으로 재할당해주는 함수가 realloc이다.

void *realloc(void* memblock, size_t size);

기존 동적 메모리의 값을 모두 가지면서 크기만 변경된 동적 메모리를 만들어낸다.
기존 메모리와 새로 확장할 메모리를 모두 포함한 크기로 설정한다.

realloc을 통해 힙메모리를 재할당하되, malloc을 통해 할당한 메모리 arr에 메모리를 덧붙여 할당한다.

  • 연속적으로 할당할 수 있는 메모리 공간이 부족한 경우
    기존 메모리에 새로 할당될 메모리 공간이 연속적으로 할당될 수 있을 거라는 보장은 없다.

    1과 같이 32바이트만 재할당되는 경우에는 새로운 공간의 메모리 위치인 2로 옮겨서 연속된 메모리 공간을 할당할 수 있다.
profile
알고 쓰자!

0개의 댓글