C 이론(21)

Hi Beck·2023년 6월 18일

TIL-Language(C & C++)

목록 보기
26/34

구조체

1.구조체의 멤버로 구조체를 넣을 수 있다.(자기 자신 구조체도 멤버로 넣을 수 있음)

구조체 멤버 각 변수에 접근하고 싶을때
->
선언한 구조체 변수명.구조체 멤버의 변수명.구조체 멤버 안의 접근하고 싶은 변수명
=대입할 값;

  • 한글자로 변수 짜는거 절대 금지 -> 인덱스를 제외한 무조건 남이 봐도 기능을 알 수 있게끔(차라리 풀네임으로 쓰는게 나음)

2.두 점(x,y) 사이의 거리 구하는 코드를 보면서 공부한 점

  • 좌표 변수명은 보통 point로 한다.(point 하나당 좌표 상의 점 하나로 생각하면됨)

  • 좌표를 쓸 땐 단순 변수로 x,y를 안쓰고 구조체 변수로 쓰는 이유는?
    안되는건 아니지만 좌표의 성질상 묶여야되는 개념이기에 구조체 point로 묶어주는게 정석(기능 뿐만 아니라 유지 보수 그리고 누가 코드를 봐도 일할 수 있게 코드를 짜야하는게 중요함)

  • sqrt 함수 = 값에 제곱근을 씌워주는 함수(전처리기 math.h안에 있음)

  • 두 점 사이의 거리를 구할 땐 피타고라스의 정리 이용

  • 사각형 점4개가 아닌 점2개로 구현한다.
    (점2개가지고 나머지 점 2개의 좌표를 알 수 있기때문에)

  • 절댓값 씌우기 |x| -> abs 함수 쓰면된다.

3.같은 구조체 변수끼리 대입이 가능하지만 비교는 불가능하다.
-> 문법적으로 연산자를 써서 조건식을 풀어서 써줘야 허용해준다.

예시)

struct point {
int x;
int y;
};

int main(void)
{
struct point p1 = {10, 20};
struct point p2 = {30, 40};

p2 = p1; // 대입 가능

if( p1 == p2 ) // 비교 -> 컴파일 오류!!
printf("p1와 p2이 같습니다.")
if( (p1.x == p2.x) && (p1.y == p2.y) ) // 올바른 비교
printf("p1와 p2이 같습니다.")
}

4.구조체 배열
배열하고 똑같은 방식으로 쓰면되는것이고, 구조체가 인덱스만큼 모여있는것과 같다.

구조체 배열 선언
-> struct 구조체 태그명 변수명[i];

접근법
->구조체 변수[인덱스].구조체 멤버변수명 = ;

구조체 배열의 초기화
-> C언어는 구조체는 우리가 초기화를 안해줘도 자동으로 초기화해주는것이 지원이 된다.
(기본 자료형에 대해서만) 근데 객체지향언어인 C++에선 지원안하니깐 해줘야된다.(인간이 컴퓨터보다 더 꼼꼼해야되고 컴퓨터를 못믿는다.)

구조체 초기화할 때 포인터변수는 초기화 안해줘도된다.즉 포인터변수 포함한 4개의 변수를 가진 구조체여도 3개만 초기화해도 가능! 순서만 틀리지않으면된다.

예시)
struct student {
int number;
char name[20];
double height;
};

int main(void)
{
struct student list[100]; // 구조체의 배열 선언
list[2].number = 27;
strcpy(list[2].name, "홍길동");
list[2].height = 178.0;
}

struct student list[3] = { // 구조체 배열의 초기화
{ 1, "Park", 172.8 },
{ 2, "Kim", 179.2 },
{ 3, "LEE ", 180 3 }
}

5.구조체의 포인터
구조체도 포인터 변수가 있다.(자료형은 다 포인터가 있음)
-> 클래스 포인터도 있음(99프로 같은데, 1%가 매우 어려운 내용으로 다름)

struct student * p; 이 말은 즉 student라는 자료형 포인터 변수 P를 선언한 것

예시)
struct student * p;

struct student s = { 20070001, "홍길동", 180.2 };
struct student student * p;

p = &s;

printf("학번=%d 이름=%s 키=%f \n", s.number, s.name, s.height);
printf("학번=%d 이름=%s 키=%f \n", p->number, p->name, p->height);

  • -> 연산자
    = 구조체 포인터로 구조체 멤버를 참조할 때 사용.
    = 포인터가 가리키는 멤버변수의 값에 접근한다.

포인터변수가 그 가리키는 애의 멤버변수에 접근할때는 화살표를 써줘야한다.
(.을 안쓴이유는 멤버변수가 P가 가리키는애의 것이지 P의 것은 아니여서 그걸 구분해줄려고 위와 같이 쓴 것임)

p-> 은 (* p)와 같다.

printf("학번=%d 이름=%s 키=%f \n", ( p).number,( p).name,(* p).height);

6.포인터로 멤버를 가지는 구조체
구조체 안에는 변수들이 들어가니 당연히 포인터 변수도 들어갈 수 있는거고, 구조체 포인터 변수도 들어갈 수 있다.

예시)

struct date {
int month;
int day;
int year;
};

struct student {
int number;
char name[20];
double height;
struct date * dob;
};

int main(void)
{
struct date d = { 3, 20, 1980 };
struct student s = { 20070001, "Kim", 180.2 };

s.dob = &d;

printf("학번: %d\n", s number, s.number);
printf("이름: %s\n", s.name);
printf("신장: %f\n", s.height);
printf("생년월일: %d년 %d월 %d일\n", s.dob->year, s.dob->month, s.dob->day);

return 0;
}


student안의 struct date * dob;에 date의 변수들이 들어있을까?

-> 들어있을것같지만 들어있지않다.student 안에는 포인터 변수만 존재한다.
month,day,year는 멤버변수일뿐 아직 생겨나지않았다.포인터는 어떤 변수를 가리킬 수 있는애인데 포인터 변수는 무조건 가르키는것도 아니여서 안가리킬 수도 있다.

구조체 선언만 한 시점에서는 포인터변수가 가리킬지 아닐지 모른다.

그렇다면 언제 가리키냐

s.dob = &d;
이렇게 주소값을 넣어주는 코드를 넣어야 dob가 date.d를 가리키게되는것이다.
이때 그러면 dob의 데이터 크기는 늘어나냐? 아니다. 각자 다른 존재여서 date의변수들은 가족이 아니라 남의 존재이다.

s.dob->year, s.dob->month, s.dob->day
== dob가 가리키는 애(d)의 year, month, day를 의미

->연산자를 언제 써야되지?
무조건 포인터변수 쓰면 그때 화살표를 쓴다.

  • 포인터 변수 주의할 점
    포인터 변수를 쓸땐 항상 NULL 체크를 하여야된다.
    NULL, 즉 없는 공간으로 초기화되어있는데 -> 연산자로 쓰면 아무것도 없는데 뭔가를 가리키는 것이기 때문에 크래쉬가 일어난다.그래서 NULL로 값이 되어있는지를 체크해야된다. 포인터변수를 본다 -> NULL 체크해야지 이렇게 습관화 시키자!
    (프로그래머가 실수하는 순위 상위권 항목)
  • 포인터변수의 크기
    기본 변수들은 int형 변수면 4바이트, float형 변수는 4바이트 이렇게 자료형에 따라 변수의 크기가 달라지는 반면에 포인터변수의 크기는 int형 포인트변수나 float형 포인트변수나 double형 포인트변수나 다 주소값을 가리키는 변수여서 다 메모리크기가 똑같다.

만약 구조체 안의 int형 변수들이 100개라고 하면 구조체는 400바이트인데 그러면 그걸 가리키는 포인터변수도 400바이트여야되나? 그건 낭비다.가리키기만 역할 끝(배열도 인덱스가 몇 개든 배열이름 하나로 관리)

그런고로 모든 포인터 변수의 크기는 4byte다.
int형 포인트 변수 4바이트, double형 포인트 변수 4바이트, char형 포인트 변수 4바이트,구조체 date형 포인트변수, student형 포인트변수 다 4바이트!

단,
32비트(=X86)나 64비트 운영체제는 운영체제별로 사용하는 비트 단위수인데 64비트 환경에서는 포인터변수가 8 바이트다.(면접 질문 빈도 높음)

7.자체 참조 구조체
자기 자신 구조체 그대로 멤버로 넣을 수 있는 경우가 있는데 그건 포인터로만 된다.
== 즉 구조체 안에 구조체 포인터 변수가 들어간다는 소리와 같다.

가능 이유 : 자기를 가리킬 수 있는 포인터변수가 구조체 안에서 자리잡을 수 있기때문에
구조체 안에 구조체를 하나 더 담는다(x)
자기 구조체의 주소값을 가리키는 포인터변수를 넣는건 가능(0)
그리고 구조체를 가리킬 수 있다는것뿐이지 구조체와 무관한 존재임(같다고 생각X,그 구조체를 안가리킬 수도 있음) 4바이트짜리 student형 포인터 변수임.

  • 포인터변수 초기화 시 보통 NULL로 초기화한다.(==아무것도 안가리키겠다)

예시)

struct student { //구조체 student 선언
int number;
char name[10];
double height;
struct student * next; //포인터 변수 next가 들어있음
};

int main(void)
{
struct student s1 = { 30, "Kim", 167.2, NULL }; //구조체 변수선언및초기화
struct student s2 = { 31, "Park", 179.1, NULL }; //구조체 변수선언및초기화
struct student first = NULL; //구조체 포인터변수선언 및 초기화
struct student
current = NULL; //구조체 포인터변수선언 및 초기화

first = &s1; //포인터변수안에 S1 주소값을 넣어줌,얘는 S1을 가리킴
s1.next = &s2; //S1의 next에 S2의 주소값을 넣어줌,얘는 S2를 가리킴
s2.next = NULL; //S2의 next는 아무것도 안가리킴

current = first; //포인터변수에 &S1을 넣어줌 그러면 first하고 추가로 S1을 가
리키는애가 하나 더생김

while( current != NULL ) //while반복문 참이면 계속 거짓이면 탈출(널될때까지)
{
printf("학생의 번호=%d 이름=%s, 키=%f\n", current->number,current- >name, current->height);
current = current->next

//current는 현재 s1을 가리키고있기에 NULL이 아니여서 S1의 값들이 출력된다.
//그리고 가리키는 S1의 next가 가지고 있는 주소값을 current에 대입한다.
즉,S2를 가리키기때문에 S2의 주소값이 들어간다.
//current는 현재 s2을 가리키고있기에 NULL이 아니여서 S2의 값들이 출력된다.
//그리고 가리키는 S2의 next가 가지고 있는 값을 current에 대입한다.
S2의 next는 아무것도 안가리키기에 while문이 종료된다.

}

}

8.구조체를 함수에 적용하는법

똑같이 매개변수에 자료형과 변수를 쓰면되고 여기에 구조체 형식을 넣으면된다.다만 이런식으로 구조체 형식 그대로 인수를 넘겨받냐 구조체 자료형 포인트변수를 써서 주소값을 넘겨받는 방법이 있는데

전자의 경우 호출시 구조체의 복사본이 함수로 전달되게되고,(메인의 있는 구조체를 자체적으로 복사해서 하나하나를 함수로 넘겨준다.근데 이게 구조체가 많아지면 비효율적)

구조체의 크기만큼 시간이 걸린다.근데 후자의 경우는 시간과 공간을 절약할 수 있다.
다만 이때 다시한번 말하지만 NULL 체크는 꼭 하기

예시)

함수의 인수가 구조체 자체일때
int equal (struct student s1, struct student s2)
{
if( strcmp(s1.name, s2.name) == 0 )
return 1;
else
return 0;
}

함수의 인수가 구조체 포인터 변수일때
int equal(struct student student const p1 , struct student student const P2)
{
if( strcmp(p1->name, p2->name) == 0 )
return 1;
else
return 0;
}

9.구조체를 반환할 때(반환값이 구조체)

struct student make_student(void)
{
struct student s;

printf("나이:“);
scanf("%d", &s.age);
printf("이름:");
scanf("%s", s.name);
printf("키:");
scanf("%f",&s.height);

return s;
}

반환형이 struct로 되어있을때 구조체 변수 선언 후 반환값을 구조체 변수로 해주면
구조체s의 복사본이 반환된다.(이때 반환되는 변수 S는 함수내의 지역변수여서 main함수에서 쓸려면 return s의 복사본을 써야된다.)

10.열거형이라는 자료형

열거형(enumeration)이란 변수가 가질 수 있는 값들을 미리 열거해놓은 자료형
(예) 요일을 저장하고 있는 변수는 { 일요일, 월요일, 화요일, 수요일,
목요일, 금요일, 토요일 } 중의 하나의 값만 가질 수 있다.

열거형은 enum이라는 키워드를 사용하여 만들어진다.

enum 태그_이름 { 값1, 값2, ... };

enum days1 { MON, TUE, WED, THU, FRI, SAT, SUN };
enum days3 { MON=1, TUE=2, WED=3, THU=4, FRI=5, SAT=6, SUN=7 };

이건 int형 배열로 편의를 위해 만들어진 자료형인데, 이 안에는 정수들만 들어간다.
(문자열로 생각할 수 있는데 큰따옴표나 작은따옴표가 안들어감 enum은 들어가있는것들에 순서대로 0번부터 숫자를 부여하고 쭉 나열해놓는것과 같다.그리고 나열한 것들의 집합을 변수이름으로 부르는 것과 같음)

효과 : 문법적으로는 딱히 의미없지만
숫자를 문자처럼 보여줘서 사람입장에서 좀 더 보기 편하게 하기 위함임
숫자를 문자처럼 보여주는게 enum이다.

예시)

#include <stdio.h>

enum days { MON, TUE, WED, THU, FRI, SAT, SUN }; //enum 선언

char * days_name[] = {
"monday", "tuesday", "wednesday", "thursday", "friday",
"saturday", "sunday" };
//days_name이라는 배열

int main(void)
{
enum days d; //enum 변수 선언

for(d=MON; d<=SUN; d++) //enum을 인덱스처럼 사용(숫자처럼사용)
{
printf("%d번째 요일의 이름은 %s입니다\n", d, days_name[d]);
}
}

11.typedef

typedef은 새로운 자료형(type)을 정의(define),C의 기본 자료형을 확장시키는 역할
==자료형이라는 이름을 변경하거나 확장시켜준다.(#define의 자료형 버전)

쓰는 이유 : 단순 변수에서는 일괄적 자료형 변경 가능,구조체 변수에선 struct도 생략가능하게됨

  • 진짜 이름이 INT가 아니라 새 이름으로 바뀌는 것

예시)
typedef int INT32;
typedef unsigned int UINT32;

INT32 i; // int i;와 같다.
UINT32 k; // unsigned int k;와 같다.

방법
typedef + 자료형 + 내가 쓰고 싶은 이름

typedef struct complex {
double real;
double imag;
} COMPLEX;

COMPLEX X,Y;

방법
typedef + 자료형 + 내가 쓰고 싶은 이름
(자료형은 struct 중괄호를 의미, 이름은 중괄호 끝나고 뒤에)

profile
Emotional realizer

0개의 댓글