[TIL]20210813

박창현·2021년 8월 13일
0

TODAY I LEARNED

목록 보기
31/53

현타가 온다. 공부좀 하자.

C

구름ide의 c언어 무료강좌로 일부 복습, 포인터 구조체 학습.

참조연산자 미세한 주의사항

(*p)++*p++는 둘 다 똑같은 증감 연산자인데 무슨 차이가 있길래 (p)++는 정상적으로 값이 증가되었고, p++는 이상한 값이 출력된 걸까요?연산자의 우선순위를 때문이다. 증감연산자(++, --)가 참조 연산자()보다 우선순위가 높습니다. 따라서 마지막 문단에 있는 p++의 경우, 주소를 먼저 찾아가지 않고 주소값이 들어있는 변수 p를 먼저 증가시키게 됩니다. 그러면 포인터 변수에 들어있는 주소값이 증가하는 것인데, 증가한 그 주소에는 아무것도 선언되어 있지 않으므로 쓰레기값이 들어있습니다. 따라서 포인터 p는 쓰레기값이 들어있는 주소를 가리키게 되는 것이지요.

포인터는 함수를 사용할 때 진가를 발휘한다. 함수에서는 인자를 전달할 때 복사해서 지역변수를 새롭게 만들어 사용한다. 즉, 전달해주는 원래 변수는 함수에서 수정할 수 없다는 뜻. 하지만 포인터는 다르다. 포인터로 메모리의 주소를 넘겨주면 함수에서도 메모리에 직접적으로 참조할 수 있기 때문에, 변수의 값을 바로 수정하는 것이 가능.

Call by value & Call by reference

c언어에서 엄밀히 따지자면 참조값을 보내도 call by value의 일종인 call by address라고한다. 근데 의미로 보나 결과로 보나 reference를 보낸 것임으로 거의 동일시 취급한다.

배열과 포인터 연산

배열에 포인터 연산을 해준다면 +1 해준다고 포인터주소값이 1 증가하는 것이 아니라, int형이면 4칸 float이면 8칸 등 자료형에 맞춰서 크 메모리 칸수만큼 증가한다.
이를 통해 포인터를 배열처럼 사용할 수 있다. *(arr+i) == arr[i]이기 때문이다. 포인터의 이름은 배열의 첫번째 원소의 주소를 가리키므로, i x 자료형의 크기만큼 더해지면 결국 배열 i와 같아진다. 괄호 위치 중요.

상수 포인터

포인터가 가리키는 변수를 상수화

const int *ptr 과 같이 const를 가장 앞에 사용하게 되면, 이 포인터를 이용해 변수의 값을 변경하는 것을 막습니다. 즉, 메모리 주소를 가지고있는 다른 포인터를 이용하면 해당 값을 수정할 수 있다.

#include <stdio.h>

int main()
{
	int num = 10;
	int *ptr1 = &num;
	const int *ptr2 = &num;

	*ptr1 = 20;
	num = 30;

	*ptr2 = 40;

	return 0;
}

이처럼 ptr1, ptr2 모두 같은 num의 메모리 주소가 할당되었다. 하지만 ptr1을 통해 값을 수정하면 ptr1, num 둘다 20으로 또는 30으로 변경된다. 그러나 ptr2를 이용해 num값을 수정하려면 오류가 발생한다.

포인터 상수화

int const ptr과 같이 자료형 다음에 const를 선언하게 되면 포인터 변수 자체가 상수화 됩니다. 주소값을 변경할 수 없다는 뜻이죠.여기서 주의할 점은, 포인터를 상수화 시킬때에는 const 전에 연산자를 써주어야 합니다. 만약 int const *ptr 처럼 사용한다면 const를 제일 앞에 써준것과 같은 효과임으로 주의.
위의 포인터가 가리키는 변수를 상수화 와 다르게 \*ptr2 = 40 이런식으로 주소로 접근해서 값을 수정하는 건 가능하지만 주소 자체를 변경시킬 수 없다.!

위와 같이 const를 자료형 다음에 써주면, 포인터를 이용해서 값을 변경하는 것은 가능하지만 포인터가 가리키고 있는 주소값을 변경하는 것은 불가능합니다. 따라서 "이 포인터가 오로지 num1 변수만을 가리키며, 절대 다른 변수를 가리키지 않겠다" 라고 생각한다면 위와 같이 써주면 되는 것입니다.또한 포인터를 통해 값을 변경하는 것도, 다른 변수를 가리키는 것도 불가능하게 하고 싶다면, const를 두번 써주는 것도 가능합니다.

#include <stdio.h>

int main()
{
	int num = 10, num2 = 20;
	int *ptr1 = &num;
	const int* const ptr2 = &num;

	ptr1 = &num2;

	*ptr2 = 30;
	ptr2 = &num2;

	return 0;
}

이렇게 하면 포인터 주소를 변경하는 것도, 주소에 접근해 값을 수정하는 것도 불가능하다.

이중 포인터

#include <stdio.h>

int main()
{
	int num = 10;
	int *ptr;
	int **pptr;

	ptr = &num;
	pptr = &ptr;

	printf("num : %d, *ptr : %d, **ptr : %d\n", num, *ptr, **pptr);
	printf("num 주소 : %d, ptr 값 : %d, **ptr 값 : %d\n", &num, ptr, *pptr);
	printf("ptr 주소 : %d, pptr 값 : %d", &ptr, pptr);

	return 0;
}
->num : 10, *ptr : 10, **ptr : 10
  num 주소 : 2125285624, ptr 값 : 2125285624, **ptr 값 : 2125285624
  ptr 주소 : 2125285616, pptr 값 : 2125285616

이 코드를 보면 이중 포인터가 쉽게 이해된다. 비유를 하자면 --->ㅁ (네모를 가리키는 화살표)에서 화살표가 주소 이고 네모가 값이다.

포인터 배열

포인터로 배열을 선언했다.

#include <stdio.h>

int main()
{
		int num1 = 10, num2 = 20, num3 = 30;
		int *parr[3];

		parr[0] = &num1;
		parr[1] = &num2;
		parr[2] = &num3;

		for(int i=0; i<3; i++)
		{
			printf("parr[%d] : %d\n", i, *parr[i]);
		}

		return 0;
}

이 코드처럼 포인터 배열에 10, 20 같은 값을 대입하려면 &을 이용하고 출력할땐 값에 접근해야함으로 * 연산자를 사용한다.

포인터 관련 문제인데 한번 이해하기 좋아 가져왔다.

#include <stdio.h>
int main() 
{
    int arr[5] = { 1, 3, 5, 7, 9 };
    double arr2[5] = { 1.1, 3.2, 5.3, 7.4, 9.5 };
    int *arrPtr = arr;
    double *arrPtr2 = arr2;
    printf("%d %lf %d %d\n", *arrPtr, *arrPtr2, arrPtr, arrPtr2);
    (*arrPtr)++;
    *arrPtr2++;
    printf("%d %lf %d %d\n", *arrPtr, *arrPtr2, arrPtr, arrPtr2);
    return 0;
}
-> 1 1.100000 292486928 292486880
   2 3.200000 292486928 292486888

보면 arrPtr은 그대로이고 arrPtr는 값이 변했다.참조연산자 미세한 주의사항 에 적은 내용이 적용된 것이다. (*arrPtr)++;는 메모리 값에 접근해 1이라는 숫자에 +1을 해준 것이고, *arrPtr2++;는 우선순위에 따라 증감 연산자가 먼저 적용되어 메모리 주소를 가져오고 double 자료형임으로 자료형에서 +1 인 8비트만큼을 더 간 값이 저장된다.

구조체

struct 구조체 이름 { 구조체 멤버들 };

새로운 자료형을 만든다고 생각하면 편하다.

#include <stdio.h>

struct student
{
	char name[15];
	int s_id;
	int age;
	char phone_number[14];
};

int main()
{
	struct student goorm;
	
	printf("이름 : ");
	scanf("%s", goorm.name);
	printf("학번 : ");
	scanf("%d", &goorm.s_id);
	printf("나이 : ");
	scanf("%d", &goorm.age);
	printf("번호 : ");
	scanf("%s", goorm.phone_number);
	
	printf("이름 : %s, 학번 : %d, 나이 : %d, 번호 : %s\n", goorm.name, goorm.s_id, goorm.age, goorm.phone_number);
	
	return 0;
}

위와 같은데, 중괄호 뒤에 ; 를 붙여주어야 하므로 주의. 사용할 변수들은 안에 묶어서 적어주는데, 이 변수들이 구조체 멤버이다.
선언한 것만으로는 구조체를 바로 사용할 수 없고, main 함수 안에서 따로 선언을 해주어야 합니다. struct로 선언했던 구조체 의 이름과 앞으로 사용할 변수 이름을 써주면 되는데, struct [구조체 이름] 까지가 int 와 같은 자료형이라고 생각하면 된다. struct student goorm 으로 선언한다면 goorm 의 자료형은 student 구조체가 되는 것.
사용할 때에는 변수이름.구조체 멤버의 이름 과 같은 형태로 작성하면 됩니다. goorm.name 이런식으로. 이렇게 구조체를 이용하면 묶어진 구조체 단위로 처리할 수 있기 때문에 가독성도 높아지고 관리하기 편하다.

typedef를 이용한 구조체 선언

typedef를 이용하면 구조체 선언을 조금 더 편리하게 할수 있다.
이를 이용하면 main 함수에서 구조체를 선언할 때 매번 struct를 써줄 필요가 없습니다. 이 typedef를 사용할 때에는 구조체 별칭이 필요한데, 구조체 별칭은 구조체를 정의할 때 중괄호 뒤에 쓴다. 일반적으로 구조체 이름과 별칭은 이름에 _ 차이 하나를 둔다.

#include <stdio.h>

typedef struct _Student {
	int age;
	char phone_number[14];
} Student;

int main(){
	Student goorm;
	
	printf("나이 : ");
	scanf("%d", &goorm.age);
	printf("번호 : ");
	scanf("%s", goorm.phone_number);
	
	printf("----\n나이 : %d\n번호 : %s\n----", goorm.age, goorm.phone_number);
	
	return 0;
}

이처럼 struct _Student 대신 typedef struct _Student 으로 선언하고 main함수에서 struct Student goorm 대신 Student goorm으로 선언하면 된다.

익명 구조체

실질적으로 typedef를 이용할땐 구조체 별칭을 이용하는데 굳이 구조체 이름이 없어도 된다. 이를 익명 구조체라고 한다.

구조체 배열

#include <stdio.h>

typedef struct {
	char name[30];
	int age;
} Student;

int main(){
	Student goorm[3] = { {.name = "해리 포터"}, {.name = "헤르미온느 그레인저"}, {.name = "론 위즐리"} };
	
	goorm[0].age = 10;
	goorm[1].age = 10;
	goorm[2].age = 10;
	
	printf("이름 : %s / 나이 : %d\n", goorm[0].name, goorm[0].age);
	printf("이름 : %s / 나이 : %d\n", goorm[1].name, goorm[1].age);
	printf("이름 : %s / 나이 : %d\n", goorm[2].name, goorm[2].age);
	
	return 0;
}

배열을 선언한 즉시 문자열로 초기화해야 한다는 부분때문에 배열을 미리 선언해놓고 문자열을 나중에 할당할 수는 없다. 그럼으로 name은 선언과 동시에 할당했다. 물론 일반적인 문자열처럼 선언하고 scanf로 할당해주는 건 된다.

구조체 포인터

구조체를 가리키는 포인터를 구조체 포인터라고 합니다. 하지만 구조체 포인터에서는 기억해야 할 점이 있습니다. int형 포인터는 int ptr; 형식으로 선언했었지요. 구조체는 struct [구조체 이름]이 자료형이나 마찬가지입니다. 따라서 struct student ptr; 과 같이 선언해야합니다. 이렇게 되면 앞에 struct가 있으니 ptr이 구조체라고 착각하시는 분들이 많은데, 이 ptr은 구조체를 가리키는 포인터일뿐 절대 구조체가 아니다!

#include <stdio.h>

typedef struct {
	int s_id;
	int age;
} Student;

int main(){
	Student goorm;
	Student *ptr;
	
	ptr = &goorm;
	
	(*ptr).s_id = 1004;
	(*ptr).age = 20;
	
	printf("goorm의 학번 : %d, 나이: %d\n", goorm.s_id, goorm.age);
}

(*ptr).s_id 는 연산자의 우선순위가 * 보다 . 이기때문에 괄호를 넣어준것이다. 이 방법대신 -> 기호를 사용해도 된다.
(\*ptr).s_id 대신 ptr->s_id를 이용할 수 있다는 것.

중첩 구조체

#include <stdio.h>

typedef struct {
	char name[15];
	int age;
} Teacher;

typedef struct {
	char name[15];
	int age;
	Teacher teacher; 
} Student;

int main(){
	Student Student;
	Teacher Teacher;
	
	Student.teacher.age = 30;
	Teacher.age = 40;
	
	return 0;
}

Teacher라는 별칭을 가진 구조체를 Student라는 별칭을 가진 구조체안에 중첩시킨 코드이다. Teacher teacher; 코드가 구조체를 중첩시키게 만드는 코드이다. Teacher은 하나의 자료형이고 이 자료형을 기반으로 teacher이라는 변수를 만들었다. int 자료형의 age 변수처럼. 이렇기에 Student.teacher.age 와같은 중첩된 구조체에 접근이 가능하다.

자기 참조 구조체

typedef struct {
	char name[15];
	int age;
	struct Student *ptr; 
} Student;

구조체는 자기 자신을 참조하도록 자기와 똑같은 타입의 구조체를 멤버로 가질 수 있는데, 이는 연결 리스트와 트리를 만들때 사용된다고 한다.

구조체 전달

구조체를 인자로 전달할 때에는 두가지 방법이 있습니다. 포인터로 전달하는 것과, 구조체 그대로 전달하는 것.
포인터를 사용하지 않고 구조체 그대로를 전달하게 되면 매개변수에 복사되는 것이므로 원본 값에는 영향을 끼치지 않습니다. 하지만 구조체의 경우는 여러가지 자료형을 묶어서 새로운 자료형으로 만든 것이기 때문에, 구조체 크기가 커질 수록 복사할 공간이 더 필요하게 됩니다. 따라서 공간이 낭비되어 비효율적이기 때문에, 매개변수로 구조체를 전달할 때에는 보통 포인터를 사용합니다. 값을 바꿀 필요가 없는 경우에도요.

#include <stdio.h>

typedef struct {
	int s_id;
	int age;
} Student;

void print_student(Student s){
	s.s_id = 2000;
	s.age = 25;
	
	printf("학번 : %d, 나이 : %d\n", s.s_id, s.age);
}

int main(){
	Student s;

	s.s_id = 1000;
	s.age = 20;
	
	print_student(s);
    
	printf("학번 : %d, 나이: %d\n", s.s_id, s.age);
}

위 코드는 구조체를 복사하는 공간낭비가 되는 코드이다. 또한, printf마다 출력되는 값이 다르다.

#include <stdio.h>

typedef struct {
	int s_id;
	int age;
} Student;

void print_student(Student *s){
	s->s_id = 2000;
	s->age = 25;
	
	printf("학번 : %d, 나이 : %d\n", s->s_id, s->age);
}

int main(){
	Student s;

	s.s_id = 1000;
	s.age = 20;
	
	print_student(&s);
    
	printf("학번 : %d, 나이: %d\n", s.s_id, s.age);
}

위 코드는 포인터를이용했기때문에 printf마다 출력되는 값이 동일하다.

알고리즘

1312 (백준)

런타임 에러.

A, B, N = (map(int, input().split()))
arr = A / B
arr = str(arr)
arr1, arr2 = arr.split(".")
print(arr2[N - 1])

17219 (백준)

A, B = (map(int, input().split()))
dic={}
for i in range(A):
    site, pw = map(str, input().split())
    dic[site]=pw

for _ in range(B):
    b=input()
    print(dic[b])

10826 (백준)

inp=int(input())
fn, fn_1, fn_2=0, 0, 1
if inp>=2:
    for _ in range(inp):
        fn=fn_1+fn_2
        fn_2=fn_1
        fn_1=fn
    print(fn)


else:
    print(inp)
inp=int(input())
fn=[0,1,1]
for i in range(3,inp+1):
    fn.append(fn[i-1]+fn[i-2])
print(fn[inp])
profile
개강했기에 가끔씩 업로드.

0개의 댓글