-
C++ Primer CH2. Variables and Basic Typesc++ 2024. 1. 21. 16:00
c++는 정적 타입 언어이다.
정적 타입 언어란, 컴파일러가 컴파일할 때, 모든 식별자의 타입을 알아야 한다는 것이다.
c++은 class에 들어갈 data와 class가 수행할 연산도 커스텀할 수 있다.
Type(타입)은 데이터와 연산자의 의미를 결정한다.
char = same size as machine basic character set = single Byte
char 8
wchar_t 16
char16_t 16
char32_t 32
short: 16bits
int: 16bits(2Bytes)
long 32
long long 64
float32
double 64
long double 96
메모리는 워드 단위로 읽힌다.
워드 = 32bit(4B) or 64bit(8)
int main() { char c2 = 60; int a = c2 + 20; cout << "result: "<< c2 << endl; return 0; } // 결과: result: < // 60이 안나온 이유: c2의 타입이 char이고, 아스키코드상 60은 <와 매칭되기 때문이다. // c2대신 a 넣으면 80 나옴. 이유: c2가 더 큰 타입인 int로 바뀌기 때문.
undefined behavior: 컴파일은 성공하는데 런타임시에 발생할 수도 있는 에러
implementation-defined behavior: 머신A에서는 잘 돌아갔던 프로그램이 머신B로 갔을 때 에러 발생하는 것
예: int 타입 크기를 고정시켜 놓은 경우.(변수할당을 통해)
signed와 unsigned value를 섞어 사용하지 말라(표현식에서)
signed → unsigned로 implicit type casting되므로 위험하다.(예기치 못한 동작)
literal(값 그자체, self-evident자명함)
literal 종류들
- 숫자 literal
- integer literal: 1,2,3…(decimal) 024(octal), 0x14(hexadecimal)
- char
- ‘a’
- char string
- “Hello World”,
- H, e, l, l,… → 각각 const char이다.
- 컴파일러가 string 맨끝에 ‘\0’ 붙임.
escape sequence
- ‘\n’ 이런식으로 표현하는데 문자 1개로 취급함
literal 쓸 때 명시하는 법
prefix(접두사, 앞에붙음)
u → unicode 16 character, char16_t
U → unicode 32 character, char32_t
L → wide character, wchar_t
u8 → utf-8(string literals only), char
suffix(접미사, 뒤에붙음)
u,U → unsigned
l,L → long
ll,LL → long long
floating point literal에 붙이면,
f,F → float
l,L → long double
unsigned를 나타내는 u,U는 number literals에 붙을 수 있다.
ex)UL: unsigned long
boolean and pointer literals
boolean: true, false
pointer: nullptr
initialization과 assignment는 c++에서 동작과정이 다르다
초기화(initialization): 변수가 생성될 때 변수 선언 및 값 할당 등의 과정
할당(assignment): 변수의 현재 값을 지우고 다가올 값으로 교체하는 것
List initialization
{} 써서 초기화하는 것.
ex)
long double ld = 3.1415926536; int a{ld}, b = {ld}; // error: narrowing conversion required int c(ld), d = ld; // ok: but value will be truncated
여기서 a{ld} → 이게 direct-list-initialization인데, {}쓰려면 narrowing conversion 일어나면 안됨.
narrowing conversion은 변환 후 precision이 감소하는 변환을 말함.
여기서 long double → int 되면서 소수점은 날라가므로 precision이 줄어듦 → narrowing conversion → {}는 narrowing conversion 허용 안함 → error
함수 밖에 선언된 변수들은 초기값이 0으로 initialize 된다.
함수 안에 선언된 변수들은 uninitialize 된다 → 값이 undefined임.
Objects 종류
built-in type(int나 char 등)
class type(우리가 만든것, stl 포함)
built-in type에 대해 initialize 하는게 좋다. → 컴파일러는 warning을 띄우지만 안띄우는 경우도 있고 런타임시에도 됐다 안됐다 하는 경우 있어서 어디서 버그 발생하는지 찾기 어렵기 때문
Declaration and Definition
변수 선언, 정의, 초기화
선언: 프로그램에게 해당 이름을 가진 변수가 있다 라고 알려주는 것
정의: 실제 개체(entity)를 만들어 이름과 매칭시키는 것
선언만: extern int i;
선언+정의: int j;
정의는 한번만 되어야 하지만, 선언은 여러곳에 할 수 있다.
c++에서 scope → curly braces가 기준 → block scope
변수가 사용되는 곳에다가 정의해라. 미리 다해놓지 말고
→ 초기화시 값 넣을 때 편함.
전역 스코프에서 정의된 변수를 사용하고 싶을 때 → ::변수명
Compound types
References
- lvalue reference(지금 배울것)
- rvalue reference(이후에 배움)
일반적인 변수 초기화(int a = 10)와 참조(int &b = a)의 다른 점
전자: 초기자(initializer)를 복사해서 생성될 object에 넣음
후자: 초기자와 참조를 bind(묶음) → 복사 과정 X
int main() { int ival = 1024; int &refVal = ival; // int &refVal2; // reference는 무조건 initialized 되어야 함. ival = 10; cout << refVal << endl; int &test = 10; // 안됨. object만 bind 가능 }
refVal과 ival binding 함. binding이란? → 초기자 object와 묶이는 것
ival object의 값을 1024→10으로 바꿈
출력결과 10
같은 reference(참조) 변수에 대해 rebind 하는 법 없음.
왜 rebind되지 않도록 만든 것인가?
Reference는 기존 변수의 또다른 이름이라고 보면 된다.
Reference는 object가 아니다.
Pointers
reference와의 차이점
- pointer는 정의시 initialize 안해줘도 됨.
- 재할당 가능
- 복사 가능
- 실제로 object임
포인터에 포인터를 할당할 땐 타입이 같아야 함.
포인터의 역참조*
int *p = &val;
cout << *p;
or
int *p;
*p = 10;
cout << *p // 10
*p를 써서 할당할 때, p가 가리키고 있는 object에 할당하고 있다라고 해석하면 편하다.
따라서,
int main() { int val; int *p = &val; int *q; *p = 10; *q = 100; cout << *p; cout << *q; // error -> q가 가리키는 object가 없으므로. }
Null Pointers
nullptr
int *p1 = nullptr == int *p1 = 0 == int *p1 = NULL;
NULL은 preprocessor에서 처리되어 컴파일러가 컴파일하기 전에 0으로 바꿔줌 → 0과 똑같음 → 안씀
아무 object도 가리키고 있지 않다는 것을 명시해줌으로써 undefined behavior를 방지함(runtime crash)
int zero = 0;
pi = zero
→ 이거는 int타입을 int*타입 변수 p1에 할당해주는거라서 error
void*
object를 가리키고 있지만 그것이 어떤 타입인지는 정해지지 않았을 때
아무 타입의 포인터든 할당될 수 있다.
사용처: 함수 인자로, 반환타입으로. 메모리 주소 저장할 때.
reference는 pointer를 가리킬 수도 있다.
int *p;
int *&r = p;
*r = 10;
cout << *p; // 10
2.4 const Qualifier
변하지 않을 변수를 정의하고 싶을 때 사용
재할당시 error 발생하므로, 정의시점에 초기화까지 해줘야 한다.
const 변수를 다른 변수에 할당하는건 아무런 특이사항이 없다.
const 변수가 정의(정의는 곧 초기화)된 file에 대해 local성을 띈다.
만약, 해당 const 변수를 사용하고 싶다면, 해당 파일 내에 initializer가 있어야 함.
이러면 같은 const 변수를 여러개의 file에 동시에 정의해놓아야 함.
→ extern const
extern이 붙으면 다른 파일에서 해당 변수를 접근할 수 있게 됨.
extern이 const 변수에 붙고 해당 변수가 초기화되어있으면,
이후, 해당 변수를 사용하고 싶은 파일에서 똑같이 extern const로 선언만 해주면, 컴파일러가 해당 변수의 초기자를 찾으려고 할 때,
초기자 탐색 범위를 파일 내로 한정하지 않고해당 변수는 local 변수가 아니고 외부 어딘가에 있음을 명시함. extern 까지 확인함으로써 초기자를 찾을 수 있게됨.→ 정의를 한번만 내려도 다른 파일에서 사용할 수 있게됨.
extern은 선언(declaration)만 해주는 것임.
선언 + 정의 + 초기화 까지 해줘야 변수를 사용 가능
References to const
const int ci = 1024;
const int &r1 = ci; OK, not object
r1 = 42; Error bcs r1 is const’s reference
int &r2 = ci; Error bcs r2 is int’sreference
근데,
const int a = 10;
int b = a; → 이건 됨. 값을 할당하는거라서.
double dval = 3.14; 1. const int &ri = dval; // 컴파일됨. ri = 3 2. int &ri = dval; // 컴파일에러. 1. int &ri = 42; // 컴파일에러. 2. const int &ri = 42; // 컴파일됨.
const 붙은 reference 변수는 초기자가 literal이든 다른 타입이든 다 컴파일됨
int i = 42; int &r1 = i; const int &r2 = i; r1 = 0; // legal. r2 = 10; // illegal because r2 is const.
Pointer const
const double pi = 3.14; // double *ptr = π // ptr은 const object를 가리키지 않고 그냥 생짜 double을 가리켜서 컴파일 안됨. const double *cptr; // const 붙이면 됨. // *cptr = 33; // 당연히 const니까 재할당 불가. // double dval = 3.14; // cptr = &dval; // 근데 const포인터가 일반 포인터 할당받을 수 있음. // *cptr = 1111; // 당연히 const라서 컴파일 에러.
const 변수를 가리키는 포인터는 변하지 않아야 하는게 가리키는 오브젝트다.
const 변수처럼 const 포인터 변수도 정의될 때 초기화도 되어야 함.→ const포인터 변수는 초기화 안해줘도 됨. 재할당 가능const 포인터에 일반변수주소를 할당할 수는 있는데 이유 → 어차피 변경하려고 시도할 때 const 포인터 변수 선에서 막히기 때문
일반 포인터에 const 변수주소 컴파일 안됨 → 일반포인터라서 변경이 되기 때문에 const인데 변경되니까 막음.
const Pointers(변하면 안되는 포인터)
포인터가 가리키는 오브젝트(주소값)이 바뀌면 안될 때 사용
int main() { int errNumb = 0; int *const p1 = &errNumb; const double pi = 3.14; const double *const cp1 = π cout << *p1 << *cp1 << endl; }
Top-Level Const vs Low-Level Const
top-level const: 나 자체가 const
low-level const: 내가 가리키는 object가 const
할당자가 top-level이면 컴파일 되는데 low-level이면 할당받는 변수도 low-level이어야 함.
할당받는 변수가 const이면 허용 범위가 커짐. 일반변수면 const 할당 못받음(이건 참조인 경우에 해당. 바꾸려고 하므로)
#include <iostream> using namespace std; int main() { int i = 0; int *const p1 = &i; // top-level const int ci = 42; // top-level const int *p2 = &ci; // low-level const int *const p3 = p2; // top and low level const int &r = ci; // top-level i = ci; // ci is top-level so const in ci is ignored p2 = p3; // same // int *p = p3; // p3 has low-level so error p2 = p3; // p3 has low-level but p2 also has low-level so OK // p2 = &i; // converted int* to const int* // int &r = ci; // error. ci is const and r is ordinary // const int &r2 = i; // OK // p2 = &i; cout << *p2; }
constexpr and Constant Expressions
constexpr(const expression)
- const 변수가 const한 expresison으로 초기화되는걸 보장하고 싶을 때 사용
constexpr int mf = 20;// OK constexpr int limit = mf + 1;// OK constexpr int sz = size() -> // size()함수가 constexpr 함수일 때만 가능. define시 추가해줘야함.
Pointers and constexpr
constexpr int *p = &val; // expression을 const화 시키기 때문에 top-level const constexpr int *constp = &val; // object와 pointer object 둘다 const, top,low level const
Type Aliases
기존 타입을 좀 더 해석하기 쉬운 이름으로 바꾸는 것
typedef double wages; typedef wages base, *p; p val = &val; // val은 double pointer 변수다. const p val = &val2; // const pointer 생성.
const 붙으면 좀 어렵다.
auto Type Specifier
컴파일러가 initializer 결과를 보고 변수 타입을 추론하도록 하는 것.
auto item = item1 + item2; auto i = 1, *p = &i; // OK 둘다 int auto sz = 1, df = 3.14 // error. 두 변수의 추론타입이 다름(int, float) auto &n = i, *p2 = &ci; // error. n:int, p2: const int const int ci = i, &cr = ci; auto b = ci; // ci: top-level -> auto=int auto c = cr; // cr = ci = top-level -> auto=int auto d = &i; // int* auto e = &ci; // const int * = low-level -> const int* 명시적으로 const 넣어줄 수 있음. const auto f = ci; // const int
헤더파일을 한번만 include 되도록 하는 법 → preprocessor 사용
directives
- #define: preprocessor variable
- #ifdef: true if the variable has been defined
- #ifndef: true if the variable has not been defined
그래서, Sales.item.h 맨 위에
#ifndef SALESITEM_H #define SALESITEM_H 이렇게 있는데, 아직 해당 SALES_item.h 파일이 전처리기가 안 읽었으면 SALESITEM_H not define이므로 #ifndef는 true라서 다음으로 넘어가고, 이후에 #define을 통해 SALESITEM_H가 define 된다. 이후, 다른 파일에서 SALES_item.h가 include 되었을 때, #ifndef가 false가 되면서 전처리기가 해당 파일 을 건너뛰게된다. true면 #endif 만날 때까지 코드 읽으면서 메모리에 로딩함. false면 #endif 사이 코드들 다 무시함. SALESITEM_H같은 변수를 header guard 라고 부름
헤더 가드는 프로그램 내에서 유니크한 이름이어야 함. 따라서 클래스 이름을 대문자화_H 한 이름으로 주로함.
헤더가드 설치하는걸 습관화해야함.
'c++' 카테고리의 다른 글
C++ Primer CH7. Classes (0) 2024.01.21 C++ Primer CH6. Functions (0) 2024.01.21 C++ Primer CH4. Expressions (0) 2024.01.21 C++ Primer CH3. Strings, Vectors and Arrays (0) 2024.01.21 C++ Primer CH1. Getting Started (0) 2024.01.21