본문 바로가기
2️⃣ 개발 지식 B+/OS

[C] 문법 빠르게 훑기

by ddubbu 2024. 8. 27.

해당 링크를 토대로 주요 문법들을 복습했다. 5년 전 기억을 더듬고 있는데 꽤 기억에 남았다는 사실이 재밌다. 이번 만큼은 그 당시 힘들었던 포인터를 극복하고 RedBlack트리까지 멋있게 구현해보고 싶다.


 

반복적으로 실수했던 부분


1. 모든 변수는 자료형 선언해주기

2. printf 변수를 위해서는 자료형 지정 필요함
3. 세미콜론(;) 필수

4. char s 작은 따옴표로 할당하기

 

주제별로 학습하기

 

// C언어

#define _CRT_SECURE_NO_WARNINGS 
#include<stdio.h>
#include<math.h>


void practice_data_type() {
	/* 모든 자료형 - 형식 지정자 연결 짓기 */

	// 정수형
	int a = 123;
	long b = pow(10, 6);
	printf("a=%d, int자료형 크기: %llu Byte\n", a, sizeof(a));
	printf("b=%d, long자료형 크기: %llu Byte\n", b, sizeof(b));

	// 문자, 문자열
	char c = 'A'; // 쌍따옴표이면 %c로 출력 안됨
	char* s = "Hello World!";
	printf("c=%c, char 자료형 크기: %llu Byte\n", c, sizeof(c));
	printf("ascii code of c =%d\n", c);
	printf("s=%s\n", s);

	// 실수형
	float f = 1234.5678;
	double df = 1234.5678;
	printf("f=%f, float 자료형 크기: %llu Byte\n", f, sizeof(f));
	printf("f(절삭)=%.2f\n", f);
	printf("df=%f\n", df);

	// 그 외
	printf("5칸 띄고 출력:%5c\n", '*');
}

void practice_operator() {

	// 산술 연산자
	int a = 4, b = 2;
	printf("a=%d, b=%d\n", a, b);
	printf("덧셈: %d\n", a + b);
	printf("뺄셈: %d\n", a - b);
	printf("곱셈: %d\n", a * b);
	printf("나눗셈: %d\n", a / b);
	printf("나머지: %d\n", a % b);


	// 단항 연산자
	int x = 4;
	int y = --x;
	printf("x=%d, y=%d\n", x, y); // 3, 3

	int z = x++;
	printf("x=%d, z=%d\n", x, z); // 4, 3

	// 관계 연산자 결과는 int (0 or 1)
	// 주의: false, true 와 같은 bool 자료형 없음
	int c = 10, d = 2;
	printf("c <= d = %d\n", c <= d);
	printf("c <= d = %d\n", c != d);

	// 논리 연산자
	printf("&& : %d\n", 1 && 2);
	printf("|| : %d\n", 1 || 0);

	// 조건 연산자
	printf("한줄 조건문 가능; %s\n", 1 == 1 ? "YES" : "NO");

	// 비트 연산자
	printf("\n[비트단위]\n");
	printf("AND %d\n", 1 & 0);
	printf("OR %d\n", 1 | 0);
	printf("XOR %d\n", 1 ^ 0);
	printf("NOT %d\n", ~1); // 보수값 이해 필요

	// 쉬프트 연산자
	printf("\n[쉬프트 연산자]\n");
	printf("곱하기 2^n%d\n", 1 << 2);
	printf("나누기 2^n%d\n", 8 >> 1);
}

void practice_overflow() {
	/*
		TMI: C언어에서는 char 형식도 정수 형식으로 취급함.
		char = 1Byte (8bit, 2^8 표현 가능) (2^7 = 128)
		표현 가능한 수의 범위 -128 ~ + 127
	*/

	char num1 = -129; // 언더플로우
	char num2 = 128;  // 오버플로우

	printf("%d \n", num1); // 127
	printf("%d \n", num2); // -128
}

void practice_scanf() {
	char a;
	int b;

	printf("문자 입력: ");
	scanf("%c", &a);
	printf("%c 의 ASCII 코드 값: %d", a, a); // a 97
}

void practice_loop() {
	int i = 1;
	while (i <= 5) {
		if (i == 3) {
			continue;
		}

		printf("i=%d\n", i++);
	}

	printf("\n");
	for (int i = 1; i <= 5; i+=2) { // step up
		printf("i=%d\n", i);
	}

	printf("\n");
	int j= 0;
	do {
		printf("우선 출력 %d\n", j);
	} while (j > 1);
}

void practice_switch() {
	int i = 1;

	switch (i) {
		printf("이곳은 미출력됩니다.");

		case 1:
			printf("숫자 1입니다.\n");
		case 2:
			printf("break를 써주지 않으면 다음 구문까지 진행돼요\n");
			break;
		case 3:
			printf("여기까지 출력이 될까요?\n");
		default:
			printf("case 중에 없다면 이곳이 출력됩니다.");
	}
}

void practice_array() {
	#define ARRAY_SIZE 5 // 상수 선언 주의) 세미콜론, 등호 없어야함
	
	/* 배열을 통해 포인터 개념 살짝 맛보기 */

	int a = 123;
	int* p = &a; // 포인터 변수 선언 (*), 등호 좌측에 있을때는 포인터 변수 및 주소값

	printf("a=%d\n", *p); // 역참조 (*), 등호 오른쪽에 있을 경우 혹은 값 조회 위치
	printf("a주소=%p\n", p); // 

	*p = 456; // 메모리 주소 접근해서 값 재할당
	printf("a=%d\n", *p);
	
	// 주의) 안되는 문법; 주소에 접근해서 재할당될 것 같은데 안됨
	// *a = 789;
	// &a = 789;

	int arr[ARRAY_SIZE] = { 1 }; // 요소 생략 시 데이터 0으로 초기화

	printf("arr[0]의 주소 = 배열 시작 주소: %p\n", &arr[0]);
	printf("배열의 이름 = 배열 시작 주소: %p\n", arr);
	printf("arr[1] 주소: %p\n", (arr + 1));
	printf("arr[1] 주소: %p\n", &arr[1]);
	printf("배열의 길이(ARRAY_SIZE): %d\n", sizeof(arr) / sizeof(arr[0]));

	printf("arr[0]=%d\n", arr[0]);
	printf("arr[0]=%d\n", *arr);
	printf("arr[0]=%d\n", *&*arr); // 값을 가지는 주소의 값
	

	for (int i = 0; i < ARRAY_SIZE; i++) {
		printf("i=%d\n", arr[i]);
	}
}

void practice_pointer_basic() {
	/* 2차원 포인터 */
	char a = 'A';
	char* p = NULL;
	char** pp = NULL;

	p = &a;
	pp = &p;

	printf("%c %p %p\n", a, p, pp);
	printf("%p %p %p\n", &a, &p, &pp);
	printf("%c %c %c\n", a, *p, **pp);

	/* 함수의 이름은 함수의 시작 주소 */
	printf("%p %p\n", printf, scanf);

	void (*fp); // 함수 포인터 선언 시 괄호 필요함
	fp = printf;
	printf("%p\n", fp);

	/* 문자열 (배열 vs 포인터) */
	char array1[] = { 'A', 'B', 'C', 'D', '\0' };
	char array2[] = { 'A', 'B', 'C', 'D' };

	printf("%s\n", array1);
	printf("%s\n", array2);  // 주의) 원하지 않는 결과 생성

	char sentence_by_array[] = "ABC";
	char* sentence_by_pointer = "DEF"; // 문자열 상수 DEF\0

	printf("%c\n", sentence_by_array[1]);
	printf("%c\n", sentence_by_pointer[1]);

	/* 포인터의 상수화 */
	char aa = 'A';
	char bb = 'B';
	const char* const p_aa = &aa;  // 포인터와 데이터 모두 상수화 

	printf("%c %c %c\n", aa, bb, *p_aa);

	//p = &bb; // 에러 발생한다는데 안함
	//*p = bb;

	/* void형 포인터 */
	// 모든 자료형의 주소 저장 가능, 내용 변경 불가, 강제 형변환만 가능
	void* vx = NULL;
}

// pointer advanced
int call_by_value(int i) {
	i = i + 1;
	return i;
}

int call_by_reference(int* i) {
	*i = *i + 1;
	return *i;
}

void practice_call_func() {
	int a = 1;

	printf("%d (after a=%d)\n", call_by_value(a), a);
	printf("%d (after a=%d)\n", call_by_reference(&a), a);
}


int main(void) {

	practice_call_func();

	return 0;
}

 

 

더 알아볼 것

1. define vs const

2. 자료형 크기 및 부동 소수점

3. 실수형 오차는 어떻게 대응하면 될까?

4. malloc, struct

5. lib vs header file