배움 저장소

[홍정모의 따라하며 배우는 C++] 4.변수 범위와 더 다양한 변수형 본문

Programming Language/C++

[홍정모의 따라하며 배우는 C++] 4.변수 범위와 더 다양한 변수형

시옷지읏 2021. 12. 15. 00:43

4.1 지역 변수, 범위, 지속기간


범위(영역)   Scope 변수를 사용할 수 있는 범위

지속기간 Duration : 메모리를 할당받고 반환하기 전까지의 기간

 

Compound Statement(복합문)

- { } 내부에 있는 코드의 모음이다. 컴파일러는 { } 내부에 있는 코드를 하나의 statement로 취급한다.

 

외부에서 사용된 변수명을 Compund Statement 내에 다시 정의할 수 있다

- 객체지향 프로그램에서 변수명을 object와 함께 사용하기 때문에 같은 변수명을 여러번 활용할 수 있다.

- namespace를 활용해도 된다.

int main()
{
    int x = 1;
    cout << x << endl;
    {
        int x = 2;
    	cout << x << endl;
    }
    cout << x << endl;
}

 

4.2 전역 변수, 정적 변수, 내부 연결, 외부 연결


전역 변수, 정적변수, 내부연결/외부연결

- Compiler가 obj파일을 만들면 linker가 특정 변수를 다른 파일에 있는 변수와 연결시킬지(메모리 공유) 선택한다

- 위 과정에서 전역/정적 변수를 결정한다. 이에 따라 내부연결/외부연결이 된다.

int g_x;          // external linkage
static int g_y;   // internal linkage
const int g_z;    // Error!, You must init global const 

extern int g_a;       // extern
extern const int g_b; // OK, but must init on the other

const int g_i(-1)  // internal linkage, impossible to share memory with other 
extern int g_j(1); // init only a time
extern const int g_k(10) // const, share memory, inited

참고) 홍정모의 따라하며 배우는 C언어 12. Storage Classes, Linkage and Memory Management

 

홍정모의 따라하며 배우는 C언어 12. Storage Classes, Linkage and Memory Management

12.1 메모리 레이아웃 훑어보기 Read Only Memory(Text Segment) 프로그램이 시작되면 해당 프로그램을 실행하기 위한 코드가 Read Only Memory에 저장된다. 이 Memory는 프로그램이 끝날 때까지 변경되지 않..

hoplite.tistory.com

 

지역변수(local variable)은 linkage가 없다

- 다른 파일에서 사용하지 않으므로 연결 or 공유할 필요가 없다.

 

전역변수는 internal linkage로 쓸 수 있다

- 프로그램 내부 어디에서나 접근 가능하다. static keyword를 추가하면 다른 파일에서 접근할 수 없다.

 

함수가 다른 .cpp 파일에 정의 되어있을 때 사용하는 방법

- 사용하려는 함수를 forward declaratation(전방선언) 해준다.

- build가 되면 linker가 다른 파일에 있는 definition과 연결한다. 이 때 #include "testFunct.cpp" 없이도 작동한다.

이는 모든 함수가 기본값으로 extern keyword를 가지기 때문이다. 함수는 전역으로 사용된다.

// main.cpp
void testFunct();

int main()
{
    testFunct();
}

// testFuct.cpp
void testFunct()
{
    cout << "Executing testFunction!" << endl;
}

 

C++에서 전역변수가 지역변수에 의해 invisible되었을 때 전역변수를 사용하기

- scope resolution operator( :: )를 사용해주자. 다만 객체지향 프로그램에서 전역변수를 사용하지 않음이 좋다.

int x = 0;
int main()
{
    cout << x << endl;   // >> 0
    int x = 1;
    cout << x << endl;   // >> 1
    cout << ::x << endl; // >> 0
}

 

Header에서 변수나 함수를 정의했을 때 문제가 생긴다

- Header에 따로 자료를 저장하여 저장공간을 아끼고 여러 변수를 쉽게 관리할 수 있다.

- 이 때 Header에 변수나 함수의 정의를 입력하면 해당 header를 호출할 때마다 별개의 저장공간을 할당받아 저장공간을 낭비한다.

 

아래 코드를 보자. 의도하지 않게, testFuct.cpp와 main.cpp은 서로 다른 Constants 변수를 사용하고 있다.

// Constants.h
#pragma once
namespace Constants
{
    const double pi = 3.14;
    const double gravity = 9.8;
}
// testFunct.cpp
#include <iostream>
#include "Constants.h"

void testFunct()
{
    using namespace std;
    cout << "Executing test Function Pi=" << Constants::pi <<" add=" << &Constants::pi << endl;
}
// main.cpp
#include <iostream>
#include "Constants.h"
using namespace std;

void testFunct();

int main()
{
    cout << "Executing in main       Pi=" << Constants::pi << " add=" << &Constants::pi << endl;
    testFunct();
}

address 주소가 다름을 주목하자

Executing in main       Pi=3.14 add=004D9B30
Executing test Function Pi=3.14 add=004D9B70

 

 이를 해결하기 위하여 Header에서 변수를 선언하고 extern keyword를 넣어준다. declarartion(선언)은 몇 번 중복되어도 된다. 이를 정의하는 파일을 따로 만들어주자. 아래와 같이 header 파일을 고치고 cpp파일을 추가하면 된다.

// Constants.h
#pragma once
namespace Constants
{
    extern const double pi;
    extern const double gravity;
}
// Constants.cpp
#pragma once
namespace Constants
{
    extern const double pi = 3.141592;
    extern const double gravity = 9.8;
}
>> Executing in main       Pi=3.14159 add=00749B30
>> Executing test Function Pi=3.14159 add=00749B30

주소 값이 동일하게 출력됨을 알 수 있다. constants.cpp파일은 linker가 header 내부 선언과 연결시켜 준다

 

4.3 Using문과 모호성


- 객체지향 프로그램에서 변수나 함수 이름이 자주 겹친다. 중복되는 이름이 있을 때 어떤 변수나 함수를 사용할 지 모를 때 생기는 문제를 모호성이라 한다.

- using keyword를 사용해 namespace 지정하자. 모호성을 제거할 수 있다.

using namespace std;  // every varaible skip 'std::'

using namespace std::cout; // only cout skip 'std::'

 

 헤더파일에 using namespace {name}을 사용할 경우, 이를 불러오는 모든 파일에 using namespace가 적용된다. 되도록이면 compound statements 내부에서 using namespace를 사용하거나 cpp파일에서 사용함이 현명하다.

namespace A
{ int test(1); }
namespace B
{ int test(-1); }

int main()
{
    using namespace A;
    cout << test << endl;

    using namespace B;
    cout << test << endl; // Error!
}

Compound statment를 사용하자

int main()
{
    {
        using namespace A;
        cout << test << endl;
    }
    {
        using namespace B;
        cout << test << endl;
    }
}

 

4.4 auto 키워드와 자료형 추론


변수를 초기화할 때 자료형을 컴퓨터가 자동으로 결정해준다. 함수의 반환값에도 사용가능하다.

auto add(int x, int y) -> int; 

auto add(int x, int y) -> int // inform return type to programmer
{
    return x + y;
}

int main()
{
    auto a = 123;
    auto b = 1.23;
    auto c = 1.23f;

    auto result = add(1,2);
}

 

4.5 형변환 Type conversion


자료형을 확인해보자

- <typeinfo>를 포함하고 typeid를 사용해보자. 자료형을 출력할 수 있다.

#include <iostream>
#include <typeinfo>
using namespace std;

int main()
{
    /* typeid print the type of literal */
    cout << typeid(4).name() << endl;    // int
    cout << typeid(4.0f).name() << endl; // float
    cout << typeid(4.0).name() << endl;  // double
}

 

Implicit Type Conversion(coercion)  암시적 형변환

ㄴNumeric Conversion: 다른 자료형의 값을 가져올 때 해당 자료형에 맞게 변환한다.

-  암시적 형변환해서 float->int가 되면 소수점 아래는 버린다.

double d = 3; // integer to double
short s = 2;  // integer to short

// trancation
char c = 1000; // maximum of char : 127

double d2 = 0.123456789;
float f = d2;

cout << setprecision(15);
cout << d2 << endl; // >> 0.123456789
cout << f << endl;  // >> 0.123456791043282

 

 암시적 형변환에서 자료형의 우선순위

 long double > double > float > unsinged long long >
 long long > unsigned long > long > unsigned int > int

unsigned는 음수 값을 가질 수 없다

cout << 5u - 10u << endl; // >> 4294967291

 

ㄴ Promotion: Numeric Conversion중 작은 자료형 값이 큰 자료형에 저장될 때 큰자료형으로 변환된다.

float f = 1.0f; // float: 4byte, double: 8byte
double d = f; // promotion

 

Explicit Type Conversion(casting)  명시적 형변환

int i = (int)4.0; // c
int i = int(4.0); // c++ instancing
int i = static_cast<int>(4.0) // modern c++

 

4.6 문자열 std string 소개


standard library에서 포함된 string을 사용해보자. <string>을 포함하자.

const std::string sentence = "Hello, World";
const std::string sentence1("Hello, World");
const std::string sentence2{ "Hello, World" };

 

아래 코드를 보자. 코드 실행 이후 "이름(space)이름"을 입력하면 두 번째 cin 입력을 받지않고 buffer에 저장된 이름을 바로 가져간다. cin 함수는 space를 기준으로 입력값을 구분하여 받는다. 한문장씩 받으려면 getline함수를 사용하자

cout << "input your name" << endl;
string name; cin >> name;

cout << "input your age" << endl;
string age;  cin >> age;

cout << "name = " << name << " and age = " << age << endl;
>>input your name
<< jack jack
>> input your age
>> name = jackand age = jack

 

getline함수로 한 줄씩(Enter, '\n'까지) 입력받기

using namespace std;
string name;
cout << "input your name" << endl;
std::getline(std::cin, name);

string age;
cout << "input your age" << endl;
std::getline(std::cin, age);

cout << "name = " << name << " and age = " << age << endl;
>> input your name
<< Jackson Hwang
>> input your age
<< 1
>> name = Jackson Hwang and age = 1

 

서로 다른 자료형 입력받기(int와 string)

- 다음 코드에 나이를 입력하면 string을 입력받지 않고 끝이난다. 다음 줄을 의미하는 문자 '\n'가 buffer에 남아입력되었기 때문이다.

using namespace std;
int age;
cout << "input your age" << endl;
cin >> age;

// std::cin.ignore(32767, 'n');

string name;
cout << "input your name" << endl;
std::getline(std::cin, name);
>> input your age
<< 1
>> input your name      // skip getting input
>> name =  and age = 1

 이를 해결하기 위하여 buffer에 있는 입력값을 모두 지워주자. cin.ignore(int count, char end)함수를 이용하면 해당 int 개수만큼 혹은 end character를 만날 때까지 buffer를 비워준다. commenting 되어있는 코드를 풀어주자.

 

 위에서 임의로 사용한 magic number 32767는 SHRT_MAX이다. 이 값 대신 streamsize의 최대값을 사용해도 된다. magic number를 사용하지 않고 프로그래머가 이해할 수 있는 변수명을 사용함이 더 현명하다.

#include <limits>
constexpr long long stream_max = std::numeric_limits<std::streamsize>::max();

 

Append : string 자료형끼리의 덧셈연산

- string 라이브러리 안에 '+' 연산자가 오버로딩되어있어 아래와 같이 사용할 수 있다.

string a("Hello, ");
string b("World");

cout << a + b << endl; // >> Hello, World

 

4.7 열거형 enumerated types


참고) 14.18 열거형(Enumerated Types) - [홍정모의 따라하며 배우는 C언어] 14. 구조체

enum Color // user-defined data types
{
    COLOR_BLACK,
    COLOR_RED,
    COLOR_BLUE,
    COLOR_GREEN,
};

enum TEST
{
    COLOR_BLACK, // Error!
};

 

열거형 변수에 각 값을 할당해줄 수 있다. 중복된 값도 가능하다

enum Color // user-defined data types
{
    COLOR_BLACK,
    COLOR_RED  = 5,
    COLOR_BLUE = 5,
    COLOR_GREEN,
};

 

정수와 호환되는 범위

- 열거형 변수를 정수 대신 사용할 수 있다.

- 열거형 변수에 정수 값을 넣을 수 없다. casting(명시적 형변환)이후 가능하다

int i = COLOR_RED;
Color col = 2; // Error!
Color col = static_cast<Color>(2); // OK

 

cin 함수로 열거형 변수에 값을 바로 할당할 수 없다. 임시 값을 만들어 구현할 수 있다. 권장하지 않는다

int i;
cin >> i;

Color col = static_cast<Color>(i);
if (col == COLOR_BLACK)
cout << "Color is BLACK" << endl;

 

여러 파일에서 사용하는 enum 열거형은 header에 구현하여 사용함을 권장한다.

 

4.8 영역 제한 열거형 (열거형 클래스)


열거형enum의 문제점

- 관계연산자를 사용해 다른 열거형 변수와 비교할 수 있다. 이 때의 계산은 의미가 없다.

- if(expression) 내부에 열거형 변수는 integer로 변환된다.

enum Color
{
    COLOR_BLACK,
    COLOR_RED,
};

enum Fruit
{
    APPLE,
    BANANA,
};

int main()
{
    if (COLOR_BLACK == APPLE)
        cout << "COLOR_BLACK is APPLE" << endl;
}
>> COLOR_BLACK is APPLE

 

enum class를 사용하자

- enum class는 해당 변수의 영역이 제한되어있다. 다른 열거형 변수와 비교가 불가능하다.

- 열거형 + namespace이라 생각하자.

enum class Color
{
    COLOR_BLACK,
    COLOR_RED,
};

enum class Fruit
{
    APPLE,
    BANANA,
};

int main()
{
	if (Color::COLOR_BLACK == Fruit::APPLE); // Error!
}

형변환을 하면 관계연산자를 사용할 수 있다. 추천하지 않는다.

if(static_cast<int>(Color::COLOR_BLACK) == static_cast<int>(Fruit::APPLE));

 같은 열거형 자료는 비교가 가능하다.

Color cur_color = Color::COLOR_BLACK;
if (Color::COLOR_BLACK == cur_color)
    cout << "current color is BLACK" << endl;

 

4.9 자료형에게 가명 붙여주기(Type aliases)


Typedef를 사용하면 자료형을 편리하게 사용할 수 있다

- typedef를 사용하면 자료형을 어떤 의미로 사용할지 변수명으로 정할 수 있다.

- 해당 typedef 자료형을 사용하는 모든 변수는 쉽게 자료형을 바꿀 수 있다. 유지-보수에 유리하다.

typedef double distance_t; // _t for indicating typedef
distance_t home_to_work;

 - 복잡한 자료형을 간단하게 나타낼 수 있다.

// Complecate and messy
vector<pair<string, int>> pairlist_1;
vector<pair<string, int>> pairlist_2;

// Simple and easy to write down
typedef vector<pair<string, int>> pairlist_t;
pairlist_t pairlist_3;

 

참고) using을 사용하여 복잡한 자료형 간단하게 나타내기

using을 사용하여 복잡한 자료형을 간단하게 부를 수 있다. 아래 코드는 using_pairlist_t에 값을 할당한 것이 아니다

    using using_pairlist_t = vector<pair<string, int>>;
    using_pairlist_t parilist_4;

 

 

typedef 자료형 끝에 _t를 붙여 typedef 임을 알리자

// ex) fixed-width integer
std::int8_t i; // _t for indicating typedef

 

4.10 구조체 struct


구조체 struct를 사용하면 여러 자료형 변수를 하나로 묶어 사용할 수 있다.

 

[C]홍정모의 따라하며 배우는 C언어 14 구조체

14.1 구조체가 필요한 이유 1. 서로 다른 자료형 배열을 연관지어 관리하기 어렵다 2. 구조체를 이용하여 Tag 하나로 관리할 수 있다 1. 구조체 내부 member variable에 접근하기 위하여 Dot(.) Operator를 사

hoplite.tistory.com

 

struct 구조체 예제이다

- uniform initialization을 사용했다.

- 구조체 각 멤버변수에 접근하기위해 Dot Operator( . )을 사용하면 된다.

struct Person
{
    double height;
    float  weight;
    int       age;
    string name;
};

void printPersnInfo(Person p)
{
    cout << p.height << " " << p.weight << " " << p.age << " " << p.name;
    cout << endl;
}

int main()
{
    Person dad{1.78, 90.0f, 42, "John Father" };
    Person me{ 1.82, 70.0f, 22, "John Trash" };

    printPersnInfo(dad);
    printPersnInfo(me);
}
>> 1.78 90 42 John Father
>> 1.82 70 22 John Trash

 

C++는 구조체 내부에 멤버함수를 선언/정의할 수 있다. printPersonInfo를 구조체 멤버함수로 만들어보자

struct Person
{
    double height;
    float  weight;
    int       age;
    string name;

    void printInfo()
    {
        cout << height << " " << weight << " " << age << " " << name;
        cout << endl;
    }
};

int main()
{
    Person dad{1.78, 90.0f, 42, "John Father" };
    Person me{ 1.82, 70.0f, 22, "John Trash" };

    dad.printInfo();
    me.printInfo();
}

 

 C에서 다음과 같이 designated initalization이 가능했다. C++에선 이를 금지하며 C++20부터 허용된다.

// C version designated initialization
struct Person me = { 
    .height = 1.8, 
    .weight = 80.0f, 
    .age = 22, 
    .name = "John Trash" 
};

https://stackoverflow.com/questions/18731707/why-does-c11-not-support-designated-initializer-lists-as-c99

 

Why does C++11 not support designated initializer lists as C99?

Consider: struct Person { int height; int weight; int age; }; int main() { Person p { .age = 18 }; } The code above is legal in C99, but not legal in C++11. What was the c++11

stackoverflow.com

 

구조체 내부에 구조체를 담을 수 있다

struct Family
{
    Person me, mom, dad;
};

 

주의) 구조체 변수끼리 assignment operator(대입연산자) 사용하기

- 기본 자료형을 대입할 때는 문제가 없다.

- 구조체 내부에 사용자 정의 자료형(구조체, 클래스)이 있을 때 의도하지 않은 결과가 나타날 수 있다. 나중에 배운다.

 

구조체 메모리 할당에서 나타나는 Padding

- 14.3 구조체의 메모리 할당 (Memory Allocation) - [홍정모의 따라하며 배우는 C언어] 14. 구조체

 

[C]홍정모의 따라하며 배우는 C언어 14 구조체

14.1 구조체가 필요한 이유 1. 서로 다른 자료형 배열을 연관지어 관리하기 어렵다 2. 구조체를 이용하여 Tag 하나로 관리할 수 있다 1. 구조체 내부 member variable에 접근하기 위하여 Dot(.) Operator를 사

hoplite.tistory.com

 
 

 

 

Comments