C 언어의 제어문은 프로그램의 흐름을 제어하며, 주요 제어문으로 조건문과 반복문, 흐름을 조정하는 break와 continue가 있습니다.
1. 조건문
1-1. if문
용도: 조건에 따라 코드 실행.
형식:
if (조건) { ... } else { ... }
1-2. switch문
용도: 여러 값에 따라 실행 분기.
형식:
switch (표현식) {
case 값1: ... break;
default: ...
}
2. 반복문
2-1. for문
특징: 초기값, 조건, 증감 모두 포함.
형식:
for (초기값; 조건; 증감) { ... }
2-2. while문
특징: 조건이 참일 때만 실행.
형식:
while (조건) { ... }
2-3. do-while문
특징: 최소 한 번 실행 후 조건 검사.
형식:
do { ... } while (조건);
3. break와 continue
3-1. break
특징: 반복문이나 switch문을 즉시 종료.
형식:
if (조건) break;
3-2. continue
특징: 반복문의 현재 반복 건너뛰기.
형식:
if (조건) continue;
예제
짝수 출력 (break, continue 사용):
#include <stdio.h>
int main() {
for (int i = 1; i <= 10; i++) {
if (i % 2 != 0) continue; // 홀수 건너뜀
if (i > 6) break; // 6 초과 시 종료
printf("%d\n", i);
}
return 0;
}
조건문과 반복문은 프로그램의 기본 흐름을 관리하고, break와 continue로 유연하게 제어할 수 있습니다.
프로그래밍에서 변수는 데이터를 저장하는 상자이며, 자료형은 이 상자가 어떤 종류의 데이터를 담을지 정의합니다. 자료형은 시스템 아키텍처(32비트, 64비트)와 운영체제(Linux, Windows)에 따라 크기와 범위가 다를 수 있습니다. 또한, **오버플로우(Overflow)**와 **언더플로우(Underflow)**와 같은 특수 상황도 이해해야 합니다.
1. 자료형의 정의와 변수 선언
변수: 데이터를 저장하기 위한 이름이 있는 공간.
자료형: 저장할 데이터의 유형(숫자, 문자 등)과 크기를 정의.
자료형 예제
int age = 25; // 정수형
float pi = 3.14; // 실수형
char grade = 'A'; // 문자형
2. 기본 자료형과 운영체제 및 아키텍처별 차이
자료형
32bit Linux
64bit Linux
32bit Windows
64bit Windows
범위(signed)
범위(unsigned)
char
1 byte
1 byte
1 byte
1byte
-128 ~ 127
0 ~ 255
short
2 byte
2 byte
2 byte
2 byte
-32,768 ~ 32,767
0 ~ 65,535
int
4 byte
4 byte
4 byte
4 byte
-2,147,483,648 ~ 2,147,483,647
0 ~ 4,294,967,295
long
4 byte
8 byte
4 byte
4 byte
-2,147,483,648 ~ 2,147,483,647 (32비트 기준)
0 ~ 4,294,967,295
long long
8 byte
8 byte
8 byte
8 byte
-9,223,372,036,854,775,808 ~ ...
0 ~ 18,446,744,073,709,551,615
float
4 byte
4 byte
4 byte
4 byte
1.2E-38 ~ 3.4E+38
N/A
double
8 byte
8 byte
8 byte
8 byte
2.3E-308 ~ 1.7E+308
long double
12~16 byte
12~16byte
8 byte
8 byte
환경에 따라 다름
운영체제와 아키텍처 차이
Linux vs Windows:
Linux: long은 64비트에서 8바이트, Windows에서는 4바이트.
long double은 Linux에서 더 높은 정밀도(12~16바이트)를 가짐.
32비트 vs 64비트:
64비트 환경에서는 주소 크기가 증가하므로 일부 자료형(long)이 더 큰 크기를 가질 수 있음.
3. signed와 unsigned의 차이
signed: 양수와 음수를 모두 표현.
unsigned: 음수를 제외하고 양수 범위를 2배로 확장.
예제 코드
signed int s = -10; // 양수와 음수 모두 가능
unsigned int u = 300; // 음수 불가능, 양수 범위 확장
4. 오버플로우와 언더플로우
오버플로우(Overflow)
변수에 저장 가능한 최대값을 초과했을 때 다시 최소값으로 돌아가는 현상. 예: unsigned char에서 255를 초과하면 0으로 돌아감.
언더플로우(Underflow)
변수에 저장 가능한 최소값 아래로 내려갔을 때 최대값으로 돌아가는 현상. 예: unsigned char에서 0 아래로 가면 255로 돌아감.
예제 코드
#include <stdio.h>
int main() {
unsigned char max = 255;
unsigned char min = 0;
max += 1; // 255를 넘어서 0으로 돌아감
min -= 1; // 0 아래로 가서 255로 돌아감
printf("Overflow: %u\n", max);
printf("Underflow: %u\n", min);
return 0;
}
5. 자료형 안전하게 사용하기
최대값과 최소값 확인 <limits.h>를 사용하여 자료형의 최대값과 최소값을 확인할 수 있습니다.
초기화 습관 선언된 변수는 항상 초기화하세요. 초기화하지 않으면 쓰레기 값(Random Value)을 가질 수 있습니다.
최근 C 언어에서 객체지향 프로그래밍(OOP)을 구현하려고 시도하면서, C++에서 많이 사용되는 벡터(Vector)를 구현하기 전에 먼저 스택(Stack)을 구현해보기로 했습니다. 자료구조를 공부하면서 Stack의 push 연산부터 구현해보기로 했고, 이를 OOP 스타일로 어떻게 구현할 수 있는지 살펴보았습니다.
이번에 구현한 스택은 C 언어에서 객체지향 프로그래밍의 개념을 적용하여, 스택에 데이터를 push, pop, clear, delete하는 메서드를 갖는 구조체를 작성하는 방식입니다.
#include "main.h"
#ifndef __ALGORITHM_H
#define __ALGORITHM_H
#pragma pack(push, 1)
typedef struct STACK {
void** data; // 스택에 저장될 데이터
size_t top; // 스택의 탑(가장 최근에 저장된 데이터)
size_t size; // 스택이 차지하는 메모리 크기
size_t byteSize; // 각 데이터 항목의 크기
void (*push)(struct STACK*, void*); // push 함수 포인터
void* (*pop)(struct STACK*); // pop 함수 포인터
void (*clear)(struct STACK*); // clear 함수 포인터
void (*delete)(struct STACK*); // delete 함수 포인터
} Stack;
#pragma pack(pop)
Stack new_stack(size_t type); // 새로운 스택 생성 함수
#define new_Stack(type) new_stack(sizeof(type)) // 매크로를 사용하여 타입에 맞는 스택 생성
#endif
이 헤더 파일에서는 Stack 구조체를 정의하고, 스택을 조작하는 메서드들을 함수 포인터로 연결했습니다. 각 메서드는 스택에 데이터를 추가하거나 제거하고, 스택을 초기화하거나 삭제하는 기능을 합니다. 또한, new_stack() 함수는 새로운 스택을 생성하는 함수로, 각 데이터 항목의 크기를 인자로 받습니다.
algorithm.c (구현 파일)
#include "algorithm.h"
// stackPush: 스택에 데이터를 추가하는 함수
// stack: 스택 객체, data: 스택에 넣을 데이터
static void stackPush(Stack* stack, void* data) {
// 스택에 저장된 데이터의 크기를 확장하기 위해 realloc 호출
// 현재 스택의 크기보다 하나 더 큰 크기로 재할당
stack->data = (void**)realloc(stack->data, (stack->top + 1) * sizeof(void*));
// 새 공간에 데이터를 할당하고, 해당 위치에 data 복사
*(stack->data + stack->top) = malloc(stack->byteSize); // 새 데이터를 위한 메모리 할당
memcpy(*(stack->data + stack->top), data, stack->byteSize); // 실제 데이터 복사
// 스택의 top을 증가시키고, 스택의 크기를 다시 계산
stack->top++;
stack->size = stack->top * sizeof(void*);
}
// stackPop: 스택에서 데이터를 꺼내는 함수
// stack: 스택 객체
static void* stackPop(Stack* stack) {
// 스택의 맨 위에서 데이터를 제거하고, 그 데이터를 반환
void* removedData = *(stack->data + stack->top - 1);
// top을 하나 줄이고, 스택 크기 재계산
stack->top--;
stack->size = stack->top * sizeof(void*);
// 스택 메모리를 재할당하여 크기 줄이기
stack->data = (void**)realloc(stack->data, stack->size);
// 꺼낸 데이터 반환
return removedData;
}
// stackClear: 스택에 저장된 모든 데이터를 해제하고, 스택을 비우는 함수
// stack: 스택 객체
static void stackClear(Stack* stack) {
// 스택에 있는 각 데이터를 해제
for (size_t i = 0; i < stack->top; i++) {
free(*(stack->data + i)); // 각 데이터 메모리 해제
}
// 스택 정보를 초기화
stack->top = 0; // 스택의 top을 0으로 초기화
stack->size = 0; // 스택의 크기도 0으로 초기화
free(stack->data); // 스택 데이터 메모리 해제
stack->data = NULL; // 데이터 포인터 초기화
}
// stackDelete: 스택을 삭제하는 함수
// stack: 스택 객체
static void stackDelete(Stack* stack) {
// 스택을 비운 후
stackClear(stack);
// 스택 관련 정보를 초기화
stack->byteSize = 0; // 각 데이터 항목의 크기 초기화
stack->data = NULL; // 데이터 포인터 초기화
}
// new_stack: 새로운 스택을 생성하는 함수
// type: 스택에 저장될 데이터 타입의 크기
Stack new_stack(size_t type) {
Stack stack;
stack.data = NULL; // 초기 데이터 포인터는 NULL
stack.push = stackPush; // push 함수 포인터 연결
stack.pop = stackPop; // pop 함수 포인터 연결
stack.clear = stackClear; // clear 함수 포인터 연결
stack.delete = stackDelete; // delete 함수 포인터 연결
stack.top = 0; // 초기 top 값은 0
stack.size = 0; // 초기 크기는 0
stack.byteSize = type; // 데이터 항목 크기 설정 (type)
return stack; // 초기화된 스택 객체 반환
}
각 함수 설명:
stackPush:
스택에 데이터를 추가하는 함수입니다.
realloc을 사용해 기존의 data 배열을 확장하고, 새 데이터를 할당하여 복사합니다.
stackPop:
스택에서 데이터를 제거하는 함수입니다.
top을 감소시키고, 스택의 크기를 다시 계산하여 realloc을 사용해 메모리를 조정합니다.
stackClear:
스택에 저장된 모든 데이터를 해제하고 스택을 초기화합니다.
stackDelete:
stackClear를 호출하여 스택을 비운 후, 스택 구조체 내의 추가적인 메모리 해제를 수행합니다.
new_stack:
새로운 스택을 생성하는 함수입니다. 타입 크기를 인자로 받아 스택 구조체를 초기화하고 반환합니다.
매크로 new_Stack:
매크로 new_Stack(type)은 new_stack(sizeof(type))을 호출하는 방식으로, 스택을 쉽게 생성할 수 있게 도와줍니다. 예를 들어, new_Stack(int)를 사용하면 int 타입에 맞는 스택을 생성할 수 있습니다.
예시 사용법:
int main() {
Stack intStack = new_Stack(int); // int 타입의 스택 생성
int value = 10;
intStack.push(&intStack, &value); // 값 10을 스택에 push
int* poppedValue = (int*)intStack.pop(&intStack); // 값을 pop하고 출력
printf("Popped value: %d\n", *poppedValue); // 출력: Popped value: 10
intStack.delete(&intStack); // 스택 삭제
return 0;
}
결론
C 언어에서 객체지향적인 방식으로 스택을 구현하면, 코드의 재사용성과 유지보수성이 높아지며, 다양한 데이터 타입을 처리할 수 있는 유연성을 갖게 됩니다. void* 포인터를 활용해 어떤 데이터 타입도 저장할 수 있도록 구현한 것이 큰 특징입니다. 이를 통해 C 언어에서도 객체지향 프로그래밍의 핵심 개념을 적용할 수 있다는 점에서 유용한 학습이 되었습니다.
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#pragma once
#ifndef __MAIN_H
#define __MAIN_H
#define import extern
#define final const
#define null NULL
#define false 0
#define true 1
typedef char *String;
typedef char byte;
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char ubyte;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
typedef uint8_t boolean;
#pragma warning(pop)
#endif
그리고 전에 만들었던 Scanner-with-C 를 활용하고 싶어 Scanner와 System 클래스를 구현했다.
System.h
#include "main.h"
#pragma once
#ifndef __SYSTEM_H
#define __SYSTEM_H
struct __stdin_t;
#pragma pack(push, 1)
/**
* The {@code System} class contains several useful class fields
* and methods. It cannot be instantiated.
*
* Among the facilities provided by the {@code System} class
* are standard input, standard output, and error output streams;
* access to externally defined properties and environment
* variables; a means of loading files and libraries; and a utility
* method for quickly copying a portion of an array.
*
* @since 1.0
*/
typedef struct _System{
struct __stdout_t{
/**
* @brief System out print function
* @param format
* @return void
*/
void (*print)(const String, ...);
/**
* @brief System out println function
* @param format
* @return void
*/
void (*println)(const String, ...);
}out;
struct __stdin_t{
int (*read)();
} in;
}SYSTEM;
#pragma pack(pop)
#pragma warning(pop)
#endif
Scanner.h
#include "main.h"
#pragma once
#ifndef __Scanner_H
#define __Scanner_H
struct __stdin_t;
#pragma pack(push, 1)
/**
* @brief Scanner class
*/
typedef struct Scanner{
/**
* @brief User input char
* @param void
* @return char
*/
char (*nextChar)(void);
/**
* @brief User input Byte
* @param void
* @return byte
*/
byte (*nextByte)(void);
/**
* @brief User input Short
* @param void
* @return int16_t
*/
int16_t (*nextShort)(void);
/**
* @brief User input Int
* @param void
* @return int32_t
*/
int32_t (*nextInt)(void);
/**
* @brief User input Long
* @param void
* @return int64_t
*/
int64_t (*nextLong)(void);
/**
* @brief User input Unsigned Byte
* @param void
* @return ubyte
*/
ubyte (*nextUByte)(void);
/**
* @brief User input Unsigned Short
* @param void
* @return uint16_t
*/
uint16_t (*nextUShort)(void);
/**
* @brief User input Unsigned Int
* @param void
* @return uint32_t
*/
uint32_t (*nextUInt)(void);
/**
* @brief User input Unsigned Long
* @param void
* @return uint64_t
*/
uint64_t (*nextULong)(void);
/**
* @brief User input boolean
* @param void
* @return boolean
*/
boolean (*nextBoolean)(void);
/**
* @brief User input Float
* @param void
* @return float
*/
float (*nextFloat)(void);
/**
* @brief User input Double
* @param void
* @return double
*/
double (*nextDouble)(void);
/**
* @brief User input Long Double
* @param void
* @return long double
*/
long double (*nextLDouble)(void);
/**
* @brief User input String no Spaces
* @param void
* @return String
*/
String (*next)(void);
/**
* @brief User input String with Spaces
* @param void
* @return String
*/
String (*nextLine)(void);
}Scanner;
#pragma pack(pop)
Scanner new_Scanner(struct __stdin_t);
#pragma warning(pop)
#endif
Systemout.c
#include "main.h"
#include "System.h"
void print(const String format, ...) {va_list ap;char buf[4096];va_start(ap, format);vsprintf(buf, format, ap);va_end(ap);fprintf(stdout, "%s", buf);}
void println(const String format, ...) {va_list ap;char buf[4096];va_start(ap, format);vsprintf(buf, format, ap);va_end(ap);fprintf(stdout, "%s\n", buf);}
SYSTEM System = {print, println};