printf
#include <stdio.h>
지정자 쓰는 타입 예시 출력
| %d | int | printf("%d", 42) | 42 |
| %ld | long | printf("%ld", 100L) | 100 |
| %lld | long long | printf("%lld", 10000000000LL) | 10000000000 |
| %u | unsigned int | printf("%u", 7u) | 7 |
| %f | float, double | printf("%f", 3.14) | 3.140000 |
| %.2f | 소수점 자리 지정 | printf("%.2f", 3.14159) | 3.14 |
| %c | char (문자 1개) | printf("%c", 'A') | A |
| %s | 문자열 | printf("%s", "hi") | hi |
| %p | 포인터(주소) | printf("%p", (void *)&x) | 0x7ffd... |
| %zu | sizeof 결과 | printf("%zu", sizeof(int)) | 4 |
| %x | 16진수 | printf("%x", 255) | ff |
| %% | % 기호 자체 | printf("100%%") | 100% |
scanf
키보드 입력을 받는 함수
성공적으로 읽은 변수 개수를 돌려준다.
int x;
int a = scanf("%d", &x);
변수
타입 지정이 필요하다.
int : 정수
char : 문자 1개
float, double : 실수
void : 값이 없음
C 언어에서는 타입 이름이 같다고 해서 항상 크기까지 완전히 고정되는 것이 아니다. 그래서 항상 sizeof()를 사용하여 크기를 구하는 습관을 들여야 한다.
C 언어에서는 단순 대입은 값 복사이다. 이는 포인터 개념과 대비되며 매우 중요하다.
Boolean 참, 거짓
0이면 거짓, 0이 아니면 참이다. C 언어 버전에 따라 C23 버전 이전에는 True, False 를 쓰려면 아래 헤더가 필요했다.
#inclue <stdbool.h>
⭐️ 함수
C는 함수에 값을 넘길 때 값을 복사해서 넘긴다.
#include <stdio.h>
void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
int main(void){
int x = 1, y = 2;
swap(x,y);
printf("%d %d\n", x, y); // 1, 2 출력
return 0;
}
위와 같은 케이스에서 swap 안의 a,b 는 복사본이라 복사본을 바꿔도 원본은 그대로이다. → 포인터로 해결
배열과 문자열
C언어의 배열은 크기를 못 늘리고, len() 도 없고, 범위를 넘어가도 막아주지 않는다. 문자열은 "문자의 배열 + 끝 표시 \0" 이다. printf는 \0을 만날 때까지 글자를 추출한다.
int arr[5] = {10,20,30,40,50};
int len = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < len; i++){
printf("%d ", arr[i]);
}
arr[10] = 99; // 범위 초과인데 에러가 안 날 수 있다.
#include <string.h>
char name[] = "Kim";
printf("%s\n", name);
printf("%zu\n", strlen(name));
if (strcmp(name, "Kim") == 0) {
printf("same\n");
}
포인터
값 대신 주소를 넘기면, 함수가 원본 자체에 접근해 값을 바꿀 수 있다.
int *p = &n // int *변수명 자체가 주소값을 담는 포인터 변수라는 의미이다.
p = &n // &n 은 n 의 주소값
*p = n // *p 는 역참조라 그 주소에 할당된 변수의 값을 가져옴
void swap (int *a, int *b){
int temp = *a;
*a = *b;
*b = temp;
}
int main(void){
int x = 1, y = 2;
swap(&x, &y);
printf("%d %d\n", x, y); // 2 1
return 0;
}
배열 이름은 첫번째 칸의 주소이다.
이중 포인터
int * // int를 가리키는 포인터
int ** // int *를 가리키는 포인터
int a = 5;
int *p = &a;
int *p_p = &p;
int **p_p = &p
*p_p = p가 담고 있는 값
p_p → p가 놓인 주소
*p_p → p가 담고 있는 값 (a 의 주소)
**p_p → a의 값
이때, →와 . 는 * 보다 우선순위가 높다. () 를 잘 활용할 것.
구조체
여러 데이터를 하나로 묶는 것. Python 클래스에서 메서드를 뺀 버전이다. struct 는 이름 없는 익명 구조체이다.
struct {
...
}
typedef : struct 를 호출할 때 별칭을 붙여 뒤에 있는 Student 만으로 타입을 사용할 수 있다.
typedef struct {
char name[20];
int score;
}Student;
int main(void) {
Student s = {"Kim", 90};
printf("%s %d\n", s.same, s.score);
}
구조체 안에서 자기 자신을 가리키는 포인터로 써야할 때 태그 이름이 필요하다. 이때는 아래와 같은 구조로 struct 태그이름 { ... } 과 같이 사용할 수 있다.
typedef struct _listnode{
int item;
struct _listnode *next;
} ListNode;
typedef struct _linkedlist{
int size;
ListNode *head;
} LinkedList;
동적 메모리 malloc, free
#include <stdio.h>
#include <stdlib.h>
int main(void) {
int n = 5;
int *arr = malloc(sizeof(int) * n);
if (arr == NULL) {
return 1;
}
for (int i = 0; i < n; i++) arr[i] = i * i;
for (int i = 0; i < n; i++) printf("%d ", arr[i]);
free(arr);
arr = NULL;
return 0;
}