기억해야 하는 C언어 문법

민픽minpic·2023년 5월 5일
0

[TIL] Today I Learned

목록 보기
11/25

week05 주가 시작되면서 C언어로 Red Black Tree 를 구현해야한다.
학부 때 배웠던 C언어를 다시 되새기는 마음으로 필요하고 중요하다고 생각 되는 부분을 정리해 두려고 한다.

해당 문법 정리는 씹어먹는 C 언어 를 기반으로 정리 할 것이다.

1. 캐스팅

int a;
double b;

b = 2.4;
a = b;

printf("%d", a); 

실행결과
2

위와 같이, double 형 타입의 값을 int 에 넣으면 당연하게도 손실이 되고, 경고창이 뜬다.

해당 경고창이 안뜨게 하려면 아래처럼 캐스팅 해주면 된다.

a = (int) b ;

어떠한 변수 형을 바꾸려면 (바꾸려는 형) 변수 이름 으로 캐스팅 하면 된다.

2. 배열

C언어는 기본적으로 배열을 만들 때, 배열의 크기를 미리 할당해준다.

int arr[3] = {1,2,3};  # 가능

int arr[] = {1,2,3};  # 가능

arr = {4, 5, 6}; # 불가능

배열 초기화는 아래와 같이 사이즈 3의 배열을 선언하고 1만 넣었을 때, 나머지는 0으로 채워진다.

int arr[3] = {1}; 

int arr[3] = {1, 0, 0}; # 위와 같이 선언하면 이렇게 됨.

3. 상수

어떠한 변수 앞에 const 를 선언하면, 해당 변수의 값은 절때 바뀌지 않는다.

const int a = 3;

a = 5; # 불가능

4. 포인터

C언어에서 가장 많이 헷갈릴 수 있는 부분 포인터.

int *p;

위와 같이 변수명 앞에 * 를 붙여주어 포인터를 선언할 수 있음.
포인터는 주소값을 담는 변수라 기억하면 조금 이해하기 쉽다.

예를 들어서

int a = 1;

위와 같이 a라는 int 형 변수를 만들었다. 그런데 a라는 변수는 어딘가 메모리에 저장이 되어있을 것이다.
a 변수에 담긴 값은 1이지만, a가 지정된 메모리의 주소를 알고 싶다.
그러면 변수 앞에 & 를 붙여서 a라는 변수의 메모리 주소를 얻을 수 있다.

printf("%d", &a);

그런데 해당 메모리 주소를 어떠한 변수에 담고 싶을 수 있다.
그럴때 포인터를 쓰는 것이다.
포인터는 주소값을 담는 변수다.

int a = 1;
int *p = &a;

이렇게 하면 포인터 p의 값은 a변수의 메모리 주소인 것이다.

그렇다면 p의 값은 a변수이라면 어떻게 출력하면 될까? 다음과 같이 출력하면 된다. 그리고 &a 를 출력한 값과 같을 것이다.

printf("%p", p)

printf("%p", &a)

실행결과 
0x7fff67ef32fc

0x7fff67ef32fc

그러면 만약 p 를 가지고 a의 주소가 아닌, 값인 1을 출력하고 싶다면 어떻게 할까? 다음과 같이 하면 된다.

printf("%d", *p); 

실행결과 
1

그렇다면 p를 가지고 a의 값을 바꾸고 싶으면 어떻게 하면 될까?

*p = 345 ;

printf("%d", a);

실행결과
345

5. 상수 포인터

위에서 봤으면 const 는 변하지 않는 값을 지정할 때 쓴다.
그렇다면 아래 두개의 차이는 무엇일까?

const int* pa = &a; 
int* const pa = &a;

int 앞에 const를 붙이면, 포인터가 가리키는 메모리 주소의 실제 값을 바꿀 수 없다는 의미이다. 즉 메모리 주소를 바꿀 수 없는게 아니라, 메모리 주소가 가리키는 값을 변경할 수 없다.

int a = 1;
const int* pa = &a; 

a = 4 ; # 불가능
pa = &b # 가능

아래는 pa 앞에 const 가 붙었다. 이 의미는 pa가 가리키는 메모리주소가 변경되면 안된다는 의미이다.

int a = 1;
int* const pa = &a;

pa = &b; # 불가능
a = 4; # 가능

이걸 안헷갈릴 수 있는 방법을 생각해봤는데,
const 앞에 어떠한 변수가 있는지 생각해보면 조금 기억하기 쉽다.

const int* pa = &a;
const 앞에 int 이니까, a의 값인 1 을 못바꾼다.

int* const pa = &a;
const 앞에 pa 변수가 있으니까, 변수의 값 &a를 못바꾼다.

6. 포인터 변수로 배열 가리키기

어떠한 포인터 변수에 배열을 할당하면 해당 포인터도 해당 배열을 접근 할 수 있다.

포인터 변수에 배열을 할당하기 전에, arr[0]을 접근하면, 실제로 컴파일러는 *(arr+1) 로 변환된다.
그래서 아래와 같이 포인터 변수에 배열을 할당하면 원래 배열처럼 사용이 가능하다.

int arr[3] = {1,2,3};
int *parr;

parr = arr;

printf("%d", arr[0]);
printf("%d", parr[0]);

실행결과
1
1

그러면 왜 굳이 포인터 변수에 배열을 할당해서 쓰나?
만약 for 루프처럼 인덱스를 증가시켜서 배열의 인덱스를 접근할 수 없을 때,
포인터 배열을 활용해서 ++ 연산자로 다음 인덱스를 접근 할 수 있기 때문이다.

int arr[3] = {1,2,3};
int *parr;
parr = arr;

arr++; # 불가능
parr++ # 가능 arr[1] 값을 가리킴

7. 포인터의 포인터

어렵다고 생각해서 어렵지, 똑같이 포인터의 주소값을 담는다고 생각하면 쉽다.

int a;
int *pa;
int **ppa;

pa = &a;
ppa = &pa;

a = 3;

printf("a : %d // *pa : %d // **ppa : %d \n", a, *pa, **ppa);
printf("&a : %p // pa : %p // *ppa : %p \n", &a, pa, *ppa);
printf("&pa : %p // ppa : %p \n", &pa, ppa);


같은 줄에 같은 값을 출력하는 것을 볼 수 있다.

8. 구조체 포인터

struct test{
int a, b;
};

int main() {
	struct test st;
    struct test *ptr;
    
    (*ptr).a = 1;
    (*ptr).b = 2;

	return 0;
}

해당 접근은 잘된다.
(*ptr).a 는 st.a 와 같은 의미이다.
그대신 괄호를 꼭 사용해야 에러가 안난다.

이유는 . 은 보다 우선순위가 높기 떄문에, ptr.a 를 사용하면
*(ptr.a) 와 동일한 뜻이 되고, ptr은 단순히 포인터일 뿐, 구조체가 아니기 때문에 구조체가 아닌 것에서 구조체 a로 접근하려고 하니까 오류가 난다.

그리고 (*ptr).a 는 ptr->a 와 간단히 표현이 가능하다

9. 동적메모리

우리가 보통 배열의 크기를 정하고 사용하는 것은 정적메모리이다.
정적메모리는 한번 배열을 정하면 낭비되는 메모리 공간이 생기거나 부족한 공간이 생기곤 한다.
그러한 불편함을 동적메모리 할당을 통해 해결할 수 있다.
malloc을 사용하여 가능하다.
만약 학생수를 사용자로 만큼 입력을 받고, 입력 받은 만큼의 배열의 크기를 할당 받고자 한다면 다음과 같이 코드를 쓸 수 있다.

int student;

scanf("%d", &student);

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

이렇게 score 에 사용자의 입력만큼 배열을 생성하고, 사용 할 수 있다.

하지만 가장 중요한 부분은 동적메모리를 할당하고 난 뒤에는 동적메모리 해제를 해주어야 한다.

free(score);

이차원 배열 malloc 할당

int width, height;
printf("배열 행 크기 : ");
scanf("%d", &width);
printf("배열 열 크기 : ");
scanf("%d", &height);

int(*arr)[width] = (int(*)[width])malloc(height * width * sizeof(int));
}

struct의 크기를 할당할 때도, 직접 계산보다는 sizeof 를 사용하여 크기를 할당하는게 안정적이다.

struct Something {
  int a, b;
};

arr = (struct Something *)malloc(sizeof(struct Something) * size);

10. Node 구현

struct Node {
    int data;
    struct Node* nextNode;
};

struct Node* CreateNode(int data){
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

    newNode->data = data;
    newNode->nextNode = NULL;

    return newNode;
}

struct Node* InsertNode(struct Node* current, int data) {
    struct Node* after = current->nextNode;
    struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));

    newNode->data = data;
    newNode->nextNode = after;
    current->nextNode = newNode;

    return newNode;
}

void DeleteNode(struct Node* delete, struct Node* head) {
    struct Node *next = head;
    if (delete == head) {
        free(delete);
        return;
    }

    while (next) {
        if(next->nextNode == delete){
            next ->nextNode = delete->nextNode;
        }
        next = next->nextNode;
    }
    free(delete);
}

profile
사진찍는 개발자 / 한 가지 개념이라도 깊이있게

0개의 댓글