week05 주가 시작되면서 C언어로 Red Black Tree 를 구현해야한다.
학부 때 배웠던 C언어를 다시 되새기는 마음으로 필요하고 중요하다고 생각 되는 부분을 정리해 두려고 한다.
해당 문법 정리는 씹어먹는 C 언어 를 기반으로 정리 할 것이다.
int a;
double b;
b = 2.4;
a = b;
printf("%d", a);
실행결과
2
위와 같이, double 형 타입의 값을 int 에 넣으면 당연하게도 손실이 되고, 경고창이 뜬다.
해당 경고창이 안뜨게 하려면 아래처럼 캐스팅 해주면 된다.
a = (int) b ;
어떠한 변수 형을 바꾸려면 (바꾸려는 형) 변수 이름 으로 캐스팅 하면 된다.
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}; # 위와 같이 선언하면 이렇게 됨.
어떠한 변수 앞에 const 를 선언하면, 해당 변수의 값은 절때 바뀌지 않는다.
const int a = 3;
a = 5; # 불가능
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
위에서 봤으면 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를 못바꾼다.
어떠한 포인터 변수에 배열을 할당하면 해당 포인터도 해당 배열을 접근 할 수 있다.
포인터 변수에 배열을 할당하기 전에, 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] 값을 가리킴
어렵다고 생각해서 어렵지, 똑같이 포인터의 주소값을 담는다고 생각하면 쉽다.
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);
같은 줄에 같은 값을 출력하는 것을 볼 수 있다.
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 와 간단히 표현이 가능하다
우리가 보통 배열의 크기를 정하고 사용하는 것은 정적메모리이다.
정적메모리는 한번 배열을 정하면 낭비되는 메모리 공간이 생기거나 부족한 공간이 생기곤 한다.
그러한 불편함을 동적메모리 할당을 통해 해결할 수 있다.
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);
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);
}