함수를 호출할 때 사용하는 규약
__cdecl
을 사용하는 편이다.__stdcall
을 사용하고, 일반 멤버함수는 __thiscall
(C++ 전용)을 사용한다.특정 메모리 영역의 값을 변경할 수 없다는 약속
void Damage(int& _Hp, int _Att)
{
_Hp -= _Att; // const가 없기 때문에 인자의 값 수정 가능
}
int StringCount(const char* _Ptr)
{
int Count = 0;
while (_Ptr[Count])
{
_Ptr[Count] = 'b' // const가 있기 때문에 인자의 값 수정 불가능
++Count;
}
return Count;
}
// 이 함수를 사용함으로써 인자로 들어간 문자열이 변경되어 나올 것이라고는 예상하지 않는다
// 따라서 인자에 const를 붙임으로써 사용자가 인자로 넣어준 값이 변경되지 않을 것이라고 명시한다 (1)
int main()
{
// const가 붙지 않은 지역변수는 스택영역에 생성된다 (수정 가능)
char Arr[10] = "aaaaa";
// const가 붙은 지역변수는 컴파일러에 따라 상수가 될 수도, 스택영역에 만들어질 수도 있다 (수정 불가능)
const char Arr1[10] = "aaaaa";
const char* CPtr = Arr; // const가 붙어있지 않은 참조형 자료형에 const 붙이기 => 가능
char* NPtr = Arr;
char* NPtr2 = CPtr; // const가 붙어있는 참조형 자료형에서 const 떼기 => 불가능 (2)
int Count = StringCount(Arr);
Count = StringCount("AAAAA"); // 함수의 인자에 const가 없으면 불가능 (3)
// "AAAAA"는 코드영역에 const char[6]의 형태로 존재하기 때문
// 위치 : 50번지(라고 하자)
// 크기 : 6byte
// 형태 : const char[6]
// 값 : AAAAA0
int MonsterHp = 50;
Damage(MonsterHp, 10);
}
const_cast
라는 방법을 사용해서 뗄 수는 있지만, 사용하지 말자) (2)#include <iostream>
int main()
{
{
const int CValue = 0;
int NValue = CValue; // 가능 => 새로운 int형 변수를 생성한 것
}
{
const int Value = 0;
const int* CValue = &Value;
int* NValue = CValue; // 불가능
/*
Value
위치 : 코드영역 100번지
크기 : 4byte
형태 : const int
값 : 0
CValue
위치 : 스택영역
크기 : 8byte
형태 : const int*
값 : 100번지
NValue
위치 : 스택영역
크기 : 8byte
형태 : int*
값 : 100번지
*/
*CValue = 1000; // 수정 불가능
*NValue = 30; // 수정 가능?!?!?
// 수정할 수 없는 값인 Value에 접근한 NValue가 수정할 수 있는 형태이기 때문에 안되는 것
}
{
const int Value = 2;
const int* CValue = &Value;
/*
Value
위치 : 코드영역 100번지
크기 : 4byte
형태 : const int
값 : 2
CValue
위치 : 스택영역 600번지
크기 : 8byte
형태 : const int*
값 : 100번지
*/
*CValue = 20; // 불가능 => 100번지에 있는 2라는 값을 20으로 수정하겠다
CValue = nullptr; // 가능 => 600번지에 있는 100번지라는 값을 0번지로 수정하겠다
const int* const CCValue = &Value;
// const int : 100번지의 값을 수정할 수 없다
// *const : 600번지의 값도 수정할 수 없다
// 크게 의미는 없다...
CCValue = nullptr; // 불가능
}
{
char Arr[100] = "Test Printf";
printf_s(Arr);
strcpy_s(Arr, 100, "aaaaa");
// 함수에 마우스를 대보면...
// const char* => 함수가 실행되어도 인자의 값은 변하지 않는다
// char* => 함수가 실행되면 인자의 값이 변할 수 있다
}
}
문자 표현 방식
char Ch = 'A'; // 65
// char를 int로 변경하는 방법
int iNumber = '1' - '0'; // 49 - 48 = 1
인자의 개수를 정하지 않고, 내가 인자를 넣어주는 순간 해당 인자 수를 가진 함수를 만드는 것
#include <iostream>
int StringCount(const char* _Ptr)
{
int Count = 0;
while (_Ptr[Count])
{
++Count;
}
return Count;
}
void MyPrintf(const char* const _Format)
{
int Number = StringCount(_Format); // 3
for (int i = 0; i < Number; i++)
{
char Ch = _Format[i];
putchar(Ch);
}
}
void VarTest(...)
{
}
void VarCountCheck(int Count, ...) // (1)
{
int* Ptr = &Count;
for (int i = 0; i < Count; i++)
{
// 포인터의 더하기 => 현재위치 + sizeof(자료형) * 넣어준 정수
Ptr += 2; // 8byte 이동 (다음 인자)
int Value = *Ptr;
}
}
int main()
{
char Arr[10] = { 'a', 'a', 'a', 0, 'a', 'a', 'a', 0 };
printf_s(Arr); // "aaa"
MyPrintf(Arr); // "aaa"
printf_s("aaa", 312, 312, 312, 312, 312, 312, 312, 312, 312);
// printf_s 함수는 인자를 엄청 많이 받을 수 있다!?
VarTest(10, 20);
// void VarTest(int Count0, int Count1) {}
// 호출 시에 위와 같은 함수를 만들어내는 것
VarCountCheck(4, 1, 2, 3, 4); // (1)
VarCountCheck(8, 1, 2, 3, 4); // (2)
// 이런식으로 실수할 가능성이 있기 때문에 위험하다
int* ptr = nullptr;
VarCountCheck(8, 1, ptr, 3, 4); // (2)
// 심지어 int가 아닌 값도 인자로 들어갈 수 있어 더 위험하다
printf_s("%d count %d", 999, 123); // "999 count 123"
// 하지만, printf_s 함수는 가변인자를 포함하고 있다
}
// Q) StringToNumber()과 NumberToString()을 완성시키자
#include <iostream>
int StringCount(const char* _Ptr)
{
int Count = 0;
while (_Ptr[Count])
{
++Count;
}
return Count;
}
int StringToNumber(const char* const _NumberString) // string을 int로 변경하는 함수
{
int Cnt = StringCount(_NumberString);
char Ch = _NumberString[0];
int Sum = 0;
int Dec = 1;
for (int i = Cnt; i > 0; i--)
{
Ch = _NumberString[i - 1];
Sum += (Ch - '0') * Dec;
Dec *= 10;
}
return Sum;
/*
int Count = StringCount(_NumberString);
int Value = 1;
// pow 함수를 대체하기 위해
for (int i = 0; i < Count - 1; i++)
{
Value *= 10;
}
int Result = 0;
for (int i = 0; i < Count; i++)
{
char Ch = _NumberString[i];
int Number = Ch - '0';
Result += Number * Value;
Value /= 10;
}
return Result;
*/
}
void NumberToString(int _Number, char* _Ptr) // int를 string으로 변경하는 함수
{
int Cnt = 0;
for (int num = _Number; num > 0; num /= 10)
{
Cnt += 1;
}
for (int i = Cnt; i > 0; i--)
{
_Ptr[i - 1] = (_Number % 10) + '0';
_Number /= 10;
}
/*
// 어떤 함수든 원본값을 보존해두는 것이 좋다!!!
int CalNumber = _Number;
const char* CPtr = _Ptr;
int NumberCount = 0;
while (CalNumber)
{
CalNumber /= 10;
++NumberCount;
}
int Mul = 1;
for (int i = 0; i < NumberCount - 1; i++)
{
Mul *= 10;
}
int Value = 0;
CalNumber = _Number;
for (int i = 0; i < NumberCount; i++)
{
Value = CalNumber / Mul;
_Ptr[i] = Value + '0';
CalNumber -= Value * Mul;
Mul /= 10;
}
*/
}
int main()
{
int Number = StringToNumber("32"); // 32
char Arr[100] = {};
NumberToString(123123, Arr); // "123123"
}
📢 코딩스탠다드
가급적 유지하자!
- 전역변수와 지역변수는 이름을 구분하여 작성하자
- 변수에는 반드시 초기값을 설정하자
- 함수의 인자 앞에는 언더바를 붙이자
- 포인터를 초기화할 때 절대 0을 사용하지 말고, nullptr을 사용하자
- 경로, 함수명, 변수명에 한글은 절대 쓰지 말자
- 프로젝트 생성 시 바탕화면은 절대 피하자
- if문을 사용할 때, 한 줄 코드일지라도 반드시 중괄호를 사용하자
- 함수의 리턴값을 꼭 변수로 받아서 확인해보자 (new!)
e.g. printf_s의 리턴값은 출력된 문자의 갯수(int)이다.