경일 메타버스 20220503 5주차 2일 수업내용. C언어 - 복습, 문자열 리터럴, 열거형, 구조체와 패딩, 비트필드, 공용체, 함수와 가변 인자
Type : 데이터를 해석하는 방법
Memory : 데이터를 저장하는 공간
Object : 데이터를 저장하는 영역 -> Memory
int a ;
a = 10;
연산자는 피연산자를 동반한다.
a + b 등 : 할당 연산자는 피연산자가 2개 필요하다.
피연산자의 조건 => 타입.
모든 연산자는 피연산자의 조건(타입)이 있다.
int* p;
p = &a;
10 + 20;
a[b] = *(a + b);
왜 a + b는 주소 연산일까? => 피연산자의 타입 때문에 주소 연산이란 걸 알게 됨
=> a의 타입은 pointer이고, b의 타입은 int
*연산자는 오직 pointer 타입에만 적용할 수 있음 => (a + b)의 타입은 pointer
역참조한 값의 타입은? pointer가 가리키고 있는 타입.
(p + 2); //
(arr + 2); // arr(value) + sizeof(type) * int
// &arr[0] + sizeof(int) * 2;
//
int arr2[2][3]; // 2차원 배열의 주소 계산 순서 (*(arr2 + 2) + 3); arr2 + 2; // arr2 : int(*)[3]; // 주소 연산 // &arr2[0] + sizeof(int[3]) * 2; // 첫 번째 원소로부터 24바이트 뒤에 있는 것을 참조. // arr2 + 2 : int(*)[3]; - (arr2 + 2); // int[3] - (int* + 3); // *(int*) int; arr[2][3];
char* str1 = "Hello"; str1[2] = 'o'; // printf("%s", str1); => Hello char str4[] = "Hello"; str4[3] = 'o'; // printf("%s", str4); => Heloo const char* str2 = "World"; const char* str3 = "Metaverse";
- C++에서 추가되는 내용들
- C++에서의 캐스팅(명시적 변환)
- static_cast
- const_cast
- reinterpret_cast
- dynamic_cast
- OOP 등등
- C++은 공부할 게 진짜 많다!
열거형 : 연관 있는 기호 상수를 묶어서 다룰 수 있는 타입
열거형은 아래와 같이 사용한다.
enum [identifier] { enumerator-list }
// 열거형은 아래와 같이 사용한다.
// enum [identifier] { enumerator-list }
// 식별자는 생략 가능하다.
enum DIR
{
// 열거형에 나열한 기호 상수는 모두 int 타입의 값을 가진다.
DIR_LEFT, // 첫 번째 값을 지정해주지 않을 시 0이다.
DIR_RIGHT, // 두 번째부터는 값을 지정하지 않을 시
// 이전 값의 + 1 이다.
DIR_UP = 3, // 명시적으로 값을 지정할 수도 있다.
DIR_DOWN,
DIR_UP_LEFT,
DIR_UP_RIGHT,
DIR_DOWN_LEFT,
DIR_DOWN_RIGHT
};
// 식별자만 사용하면 C언어가 알아듣지 못한다.
// 꼭 앞에 enum을 명시해야 한다.
enum DIR playerDirection = DIR_LEFT; // 값은 열거형에 있는 기호 상수를
// 이용해도 되고
playerDirection = 3; // 정수를 직접 지정할 수도 있다.
// 열거형과 int 타입 간에는 변환이 일어난다.
playerDirection = -1; // 열거형에 아예 없는 값을 지정할 수도 있다.
구조체(Structure) : 여러 타입으로 구성된 새로운 타입을 만드는 데 사용한다. 이를 사용자 정의 타입(User-defined Type)이라고 한다.
// 구조체의 정의는 아래와 같다.
// struct [identifier] { struct-declaration-list }
struct Student
{
int Age; // 구조체를 구성하는 타입을 멤버(Member)라고 한다.
enum { A, B, O, AB } BloodType;
char Name[24];
struct Student* BestFriend; // 자신의 타입도 멤버로 사용할 수 있다.
};
// 열거형과 마찬가지로 앞에 struct를 붙여야
// Student라는 식별자를 인식할 수 있다.
struct Student seonmun = { 20, A, "최선문", NULL };
// 멤버가 정의된 순서대로 값을 넣어주게 된다.
// Age = 20, BloodType = B, Name = “최선문”, BestFriend = NULL
메모리에는 순서에 맞춰 일직선으로 표현된다.
객체를 만들고 초기화 하는 방법은 위와 같다. 레퍼런스는 아래대로이다.
https://en.cppreference.com/w/c/language/struct_initialization
멤버에 접근하는 것은 연산자를 사용하며, 포인터를 이용해 접근할 때는 -> 연산자를 사용한다.
// 당연한 얘기지만 주소 연산을 활용한다.
seonmun.Age; // &seonmun에 접근하면 값을 얻을 수 있다. 값은 20
seonmun.BloodType; // &seonmun + sizeof(int)에 접근하면
// 값을 얻을 수 있다. 값은 B(1)
seonmun.Name; // &seonmun + sizeof(int) + sizeof(enum)에 접근하면
// 값을 얻을 수 있다. 값은 "최선문"
seonmun.BestFriend; // &seonmun + sizeof(int) + sizeof(enum) +
// sizeof(Name)에 접근하면 값을 얻을 수 있다.
// 값은 NULL(0)
struct Student* p = &seonmun;
// 각 멤버에 접근하려면 아래와 같이 한다.
(*p).Age;
// 하지만 매번 위처럼 쓰는 것이 귀찮으므로
// 이를 축약한 형태가 -> 연산자이다.
p->Age;
구조체 크기는 일반적으로 멤버의 크기를 전부 더하면 된다. 다만, 패딩 바이트가 추가되는 경우가 있다.
패딩 바이트 : 멤버 접근 빈도를 줄이기 위해 추가된다. 메모리 주소에 접근하려면 주소가 정렬되어 있어야 한다. 그렇지 않으면 데이터가 나눠져 저장될 수 있으므로 접근을 2번 해야 한다. 그래서 C 프로그램에서는 접근을 한 번에 하기 위해 패딩 바이트를 넣는다. ⇒ 메모리 낭비
패딩은 멤버의 정렬 요건 중 가장 큰 값으로 맞추게 된다.
#include <stdio.h>
int main()
{
struct S { char ch; double d; float f; } s; // 패딩 발생
sizeof(struct
{
char ch; // 1바이트 + 7바이트(패딩)
double d; // 8바이트
float f; // 4바이트 + 4바이트(패딩)
}); // 24바이트
alignof(s); // 24바이트
sizeof(struct { char ch; float d; float f; }); // 패딩 해결
sizeof(struct { char ch; float f; double d; }); // 패딩 해결
}
union Color
{
struct
{
unsigned char R; // 1바이트
unsigned char G; // 1바이트
unsigned char B; // 1바이트
unsigned char A; // 1바이트
};
unsigned int Value; // 4바이트
};
union Color color;
color.R = 0;
color.G = 47;
color.B = 167;
color.B = 255;
// 클라인 블루
color.Value = 0xFFAB4700;
// 멤버 중 가장 큰 크기의 데이터를 가지고 나눠쓰는 구조체
#include <stdio.h>
#include <stdarg.h>
// myprintf(3, 1, 2, 3) => My Variadic Function : 1, 2, 3
// "My Variadic Function : ~"
void myprintf(int count, ...)
{
va_list args; // <stdarg.h> // 가변 인자를 다룰 객체를 만든다.
va_start(args, count); // <stdarg.h> // 가변 인자의 시작 위치를 알려준다.
printf("My Variadic Function :");
for (int i = 0; i < count; ++i)
{
int number = va_arg(args, int); // <stdarg.h> // 인자를 빼서 사용한다.
printf(" %d,", number);
}
va_end(args); // <stdarg.h> // 끝낸다.
}
int main(void)
{
myprintf(5, 1, 2, 3, 4, 5);
return 0;
}
#include <stdio.h>
#include <stdarg.h>
// <summary>
// printf와 유사하게 동작하나 기능이 제한됨.
// %d / %c / %f / %s
// </summary>
// <param name="format">형식 문자열</param>
// <param name="">가변 인자값</param>
void myprintf(const char* format, ...);
int main(void)
{
return 0;
}
void myprintf(const char* format, ...)
{
va_list args; // 가변 인자를 다룰 객체를 만든다.
va_start(args, format); // 가변 인자의 시작 위치를 알려준다.
while (*format != '\0')
{
if (*format == '%')
{
//putchar(' ');
if (*(format + 1) == 'd')
{
int i = va_arg(args, int); // 인자를 빼서 사용한다.
// 데이터 타입의 크기만큼 주소값을 받아 사용
if (i == 0)
{
putchar(i + '0');
}
else
{
int digit[256] = { 0 };
// int* Digit = digit; // 동적 할당 malloc
int count = 0;
if (i < 0)
{
putchar('-');
i *= -1;
}
while (i != 0)
{
digit[count] = i % 10;
// *(Digit + count) = i % 10;
// Digit++;
count++;
i /= 10;
}
for (int k = count - 1; k >= 0; k--)
{
putchar(digit[k] + '0');
}
//for (int k = count - 1; k >= 0; k--)
//{
// putchar(*(Digit + k) + '0');
//}
/*
int tendigit = i / 10;
int onedigit = i % 10;
putchar(tendigit + '0');
putchar(onedigit + '0');
*/
}
}
else if (*(format + 1) == 'c')
{
char c = va_arg(args, char);
putchar(c);
}
else if (*(format + 1) == 's') // 오답노트 안건 : 문자열 리터럴("Hello")은 데이터 영역 (변경 불가)에 저장되며, ""은 그 주소값을 보낸다
{
char* s = va_arg(args, char*);
//s = "Hello";
for (int k = 0; *(s + k) != '\0'; k++)
{
putchar(*(s + k));
}
}
/*if (*(format + 2) != '\0')
{
putchar(',');
}*/
++format;
}
else
{
putchar(*format);
}
++format;
}
va_end(args); // 끝낸다.
}