Algorithms/C, C++

C++ 자주 쓰이는 기능 요약

esmJK 2021. 1. 6. 10:05

복습한다는 개념으로 볼 수 있게 필요한 개념을 정리하였습니다. 

Integer Types 

char 

short int 

int 

long int 

long long int 

 

Pointers

변수 이름은 특정 데이터 타입을 가진 주소로 가기 위한 인덱스로 사용됩니다.

포인터는 값을 직접 가지지는 않으나 값을 지칭합니다. 또한, 포인터는 strongly typed 로서 컴파일러가 포인터를 int, char 등의 자료형과 연관시킵니다. 

 

임베디드 개발 시 struct data를 넘겨줄 때 데이터 전체를 넘기기에 부담이 되니 포인터를 넘겨주는 작업을 자주 하곤 합니다. 

#include <cstdio>


int main()
{
    
    int y = 42;
    int *ip = &y;
    
    printf("The value of y is %d\n", y);
    printf("The value of *ip is %d\n", *ip);
    
    
    return 0;
    
}

 

Function Pointer

Function을 가리키는 포인터입니다. 문법은 기존 포인터와 조금 달라 익숙치 않을 수 있습니다. 

#include <cstdio>



void func()
{
    puts("this is func()");
}

int main()
{
    void (*pfunc)() = func;
    puts("This is main()");
    pfunc();
    return 0;
}

Function pointer는 주로 하나의 카테고리로 묶을 수 있는 함수들에 대해 사용하면 편리합니다. 다음 예제에서는 입력에 맞는 함수를 호출해야 할 때 function pointer array를 선언해 switch문보다 간편하고 읽기 쉬운 방법을 설명합니다. 

#include <cstdio>

const char * prompt();
int jump(const char *);

void fa() {puts("this is fa()");}
void fb() {puts("this is fb()");}
void fc() {puts("this is fc()");}
void fd() {puts("this is fd()");}
void fe() {puts("this is fe()");}

void (*funcs[])() = {fa, fb, fc, fd, fe};

int main()
{
    while(jump(prompt()));
    puts("\nDone");
    return 0;
}

const char * prompt()
{
    puts("Choose an option");
    puts("1. Function fa()");
    puts("2. Function fb()");
    puts("3. Function fc()");
    puts("4. Function fd()");
    puts("5. Function fe()");
    puts("Q. Quit.");
    printf(">> ");
    fflush(stdout);     // flush after prompt
    
    const int buffsz = 16;
    static char response[buffsz];
    fgets(response, buffsz, stdin);
    
    return response;
}

int jump(const char * rs)
{
    char code = rs[0];
    if (code == 'q' || code == 'Q') return 0;
    
    //get the length of the funcs array
    int func_length = sizeof(funcs) / sizeof(funcs[0]);
    
    int i = (int) code - '0';  // convert ASCII numeral to int
    
    if (i < 1 || i > func_length)
    {
        puts("invalid choice");
        return 1;
    } else {
        funcs[i - 1]();     // array is zero based
        return 1;
    }
}

References (참조자)

C에는 존재하지 않고 C++에만 존재하는 기능입니다. 

 

1. 포인터와 비슷하지만 dereferencing 같은 과정이 필요 없습니다.

2. Reference를 refer 할 수 없습니다. 한 번 선언되면 다른 variable을 refer 할 수도 없습니다.

 

전통적으로 C++에서는 Reference가 변수에 별명을 하나 붙여주는 것과 같은 기능을 한다고 했는데요. Function signature를 보지 않는 이상 코드가 side effect를 가지는지 모르기 때문에 위험할 수 있습니다. 

#include <cstdio>


int main()
{
    int x = 7;
    int *ip = &x;
    int &y = x;
    
    printf("The value of *ip is %d\n", *ip);
    printf("The value of x is %d\n", x);
    printf("The value of y is %d\n", y);
    
    printf("\n----------\n");
    
    y = 42;
    printf("The value of *ip is %d\n", *ip);
    printf("The value of x is %d\n", x);
    printf("The value of y is %d\n", y);
    
    return 0;
}

/*
The value of *ip is 7
The value of x is 7
The value of y is 7

----------
The value of *ip is 42
The value of x is 42
The value of y is 42
Program ended with exit code: 0
*/

 

using namespace std;


const int & f(int & i)
{
    int _i = i;
    return ++_i;
}


int main()
{
    int i = 5;
    int & ir = i;
    ir = 10;
    printf("i is %d\n", i);
    printf("f() is %d\n", f(i));
    printf("i is %d\n", i);
    
    return 0;
}

// i is 10
// f() is 11
// i is 10

 

 

auto type

#include <stdio.h>
#include <string>
using namespace std;

string func()
{
    return string("this is a string");
}


int main()
{
    auto x = func();
    // string x = func(); 와 같다 
    printf("x is %s\n", x.c_str());
    if(typeid(x) == typeid(string)) puts ("x is string");
    return 0;
}

 

#include <cstdio>
#include <vector>
using namespace std;


int main()
{
    vector<int> vi = {1, 2, 3, 4, 5};
    
    //for(auto it = vi.being(); it != vi.end(); ++it)
    for(vector<int>::iterator it = vi.begin(); it != vi.end(); ++it)
    {
        printf("int is %d\n", it);
    }
    return 0;
    
}

Structure

struct Employee 
{
    int id;
    const char * name;
    const char * role;
};


int main()
{
    // using initializer list : new feature in c++11 
    Employee joe = {42, "Joe", "Boss"}; 
    
    printf("%s is the %s and has id %d\n", joe.name, joe.role, joe.id);
    
    
    Employee * e = &joe;
    
    // using pointer member
    printf("%s is the %s and has id %d\n", e->name, e->role, e->id); 
    
    return 0;
}

 

Bit Fields

구조체와 비슷하지만 바이트 수를 지정해 정해진 크기 내에서 데이터를 포함하게끔 할 수 있습니다. 메모리 절약에 효과적입니다.

Mac에서는 homer 가 32비트를 차지하는데 Windows에서는 64비트를 차지할 수 있습니다. 데이터가 어떻게 꾸려지는지는 target architecture에 의해 좌우됩니다. 

 

Bit field는 read/write 가 동시에 이루어지기 때문에 Concurrent / Threaded programming에서 문제의 원인이 되기도 합니다. 

#include <stdio.h>

struct preferences
{
    // number after colon : bytes set aside
    bool likesMusic : 1;
    bool hasHair : 1;
    bool hasInternet : 1;
    bool hasDinosaur : 1;
    unsigned int numberOfChildren : 4;
};

int main()
{
    struct preferences homer;
    homer.likesMusic = true;
    homer.hasHair = false;
    homer.hasInternet = true;
    homer.hasDinosaur = false;
    homer.numberOfChildren = 3;
    
    
    printf("sizeof int: %ld bits\n", sizeof(int) * 8);
    //sizeof int: 32 bits
    printf("sizeof homer %ld bits \n", sizeof(homer) * 8);
    //sizeof homer: 32 bits
    
    
    
    if(homer.likesMusic) printf("homer likes music\n");
    printf("homer has %d children \n", homer.numberOfChildren);
    
    return 0;
}

 

Typedef 

alias를 제공함으로써 더 편리하게 코드를 작성할 수 있습니다. Architecture Independent Type, 예를 들어 uint8_t 도 이러한 방식으로 만들어졌습니다. 복잡한 template type를 다룰 때도 유용하게 쓰일 수 있습니다. 

#include <stdio.h>
#include <cstdint>
using namespace std;

// bit field to save space
typedef unsigned char points_t;
typedef unsigned char rank_t;

struct score
{
    points_t p;
    rank_t r;
};


int main()
{
    score s = {5, 1};
    printf("score s has %d points and rank of %d\n", s.p, s.r);
    // score s has 5 points and rank of 1 
    return 0;
}

 

 

 

Enumerator

카드게임의 경우 숫자를 글자로 대체하는 경우가 있는데요, Ace = 1, Deuce = 2, Jack = 11 과 같이 표현하곤 합니다. 공통 역할을 하는 정수를 묶어 표현할 때 Enum을 사용합니다. Preprocessor constant의 좋은 대안이 될 수 있습니다.

Enumerator는 신택스만 보면 Type 처럼 보이지만 Integer Type에 기반을 두고 있으며 constant 와 더 유사합니다. Case statement 비교에 매우 편리합니다. 

#include <stdio.h>
#include <cstdint>

using namespace std;

enum card_suit : uint8_t {SPD, HRT, DIA, CLB};
enum card_rank : uint8_t {ACE = 1, DEUCE = 2, JACK = 11, QUEEN, KING};
// Queen = 12, King = 13
constexpr const char * aceString = "Ace";
constexpr const char * jckString = "Jack";
constexpr const char * queString = "Queen";
constexpr const char * kngString = "King";
constexpr const char * deuString = "Deuce";
constexpr const char * spdString = "Spades";
constexpr const char * hrtString = "Hearts";
constexpr const char * diaString = "Diamonds";
constexpr const char * clbString = "Clubs";

// bit field to save space
struct card
{
    uint8_t rank : 4;
    uint8_t suit : 4;
};


card deck[52] =
{
    {ACE, SPD}, {DEUCE, SPD}, {3, SPD}, {4, SPD}, {5, SPD}, {6, SPD}, {7, SPD},
    {8, SPD}, {9, SPD}, {10, SPD}, {JACK, SPD}, {QUEEN, SPD}, {KING, SPD},
    {1, HRT}, {2, HRT}, {3, HRT}, {4, HRT}, {5, HRT}, {6, HRT}, {7, HRT},
    {8, HRT}, {9, HRT}, {10, HRT}, {11, HRT}, {12, HRT}, {13, HRT},
    {1, DIA}, {2, DIA}, {3, DIA}, {4, DIA}, {5, DIA}, {6, DIA}, {7, DIA}, {8, DIA},
    {9, DIA}, {10, DIA}, {11, DIA}, {12, DIA}, {13, DIA},
    {1, CLB}, {2, CLB}, {3, CLB}, {4, CLB}, {5, CLB}, {6, CLB}, {7, CLB},
    {8, CLB}, {9, CLB}, {10, CLB}, {11, CLB}, {12, CLB}, {13, CLB}
};

void print_card(const card & c)
{
    if(c.rank > DEUCE && c.rank < JACK){
        printf("%d of ", c.rank);
    } else {
        switch(c.rank)
        {
            case ACE:
                printf("%s of ", aceString);
                break;
            case JACK:
                printf("%s of ", jckString);
                break;
            case QUEEN:
                printf("%s of ", queString);
                break;
            case KING:
                printf("%s of ", kngString);
                break;
            case DEUCE:
                printf("%s of ", deuString);
                break;
        }
    }
    switch (c.suit) {
        case SPD:
            puts(spdString);
            break;
        case HRT:
            puts(hrtString);
            break;
        case DIA:
            puts(diaString);
            break;
        case CLB:
            puts(clbString);
            break;
    }
}


int main()
{
    long int count = sizeof(deck) / sizeof(card);
    printf("count: %ld cards\n", count);
    
    for(card c : deck)
    {
        print_card(c);
    }
    
    return 0;
}

 

Qualifiers 

CV(constant and volatile) Qualifiers Storage Duration 
const : cannot be changed once defined static : life beyond execution of block 
volatile : may be changed another process. used in threaded  register : stored in processor registers 
mutable : used on a data member to make it writable from a const qualified member function  extern : defined in separate translation unit. linked with your code in the compilation process 

- Variables have a automatic lifetime in a block by default 

 

examples) 

- const static int i = 42;  -> variable is immutable 

- static value in a member function of a class = same value even when instantiated separately 

 

 

const 의 의미를 조금 더 자세히 살펴보기 위해 다음 예시를 보겠습니다 

//변수 num을 상수화 
const int num = 10;

// 포인터 ptr1을 이용해서 val1의 값을 변경할 수 없음
const int * ptr1 = &val1;

//포인터 ptr2가 상수화 됨
int* const ptr2 = &val2;

//포인터 ptr3가 상수화, ptr3 이용해 ptr3 값 변경할 수 없음 
const int * const ptr3 = &val3;

 

string 은 const char 자료형과 같다고 볼 수 있습니다. 

 

using namespace std;
int main()
{
    
    // pointer to array you cannot change
    const char * cstring = "string";
    
    printf("The string is : %s\n", cstring);
    
    for (unsigned int i = 0; cstring[i]; i++)
    {
        printf("%02d: %c\n", i, cstring[i]);
    }
    
    return 0;
}

 

 

 

new, delete operator 

C에는 힙메모리 접근을 위해 malloc, free 함수가 존재했었는데 C++에서는 이를 더 간결하게 new, delete 로 수행할 수 있습니다. 그러나 try {} catch {} 블록을 이용해 메모리가 할당될 수 없는 상황인지를 알아야 합니다. 

#include <cstdio>
#include <new>

using namespace std;

constexpr size_t count = 1024;

int main()
{
    printf("allocate space for %lu long int at *ip with new\n", count);
    
    // allocate array
    long int * ip;
    
    try{
        ip = new long int [count];
    } catch (std::bad_alloc & ba) {
        fprintf(stderr, "Cannot allocate memory (%s)\n", ba.what());
        return -1;
    }
    
    
    // initialize array
    for (long int i = 0; i < count; ++i)
    {
        ip[i] = i;
    }
    
    // print array
    for (long int i = 0; i < count; ++i)
    {
        printf("%ld", ip[i]);
    }
    
    // deallocate array
    delete [] ip;
    puts("space at *ip deleted\n");
    
    
    return 0;
}

 

Class 

Member value 와 member function

class C1 {
    int i = 0;
    
//separate implementation from interface
public:
    void setvalue(int value);
    int getvalue();
};


void C1::setvalue(int value)
{
    i = value;
}

int C1::getvalue()
{
    return i;
}

int main()
{
    int i = 47;
    C1 o1;
    
    o1.setvalue(i);
    printf("value is %d\n", o1.getvalue());
    return 0;
}

 

Class의 Constructor와 Destructor는 각각 인스턴스 생성 시와 소멸 시에 불려집니다. 

 

#include "string"


using namespace std;

const string unk = "unknwon";
const string clone_prefix = "clone-";
    
//-- interface --
class Animal {
string _type = unk;
string _name = unk;
string _sound = unk;

public:
Animal(); // default constructor
Animal(const string & type, const string & name, const string & sound);
Animal(const Animal &); // copy constructor
~Animal();
void print() const;
};

//implementation
Animal::Animal(){
    puts("default constructor");
}

Animal::Animal(const string & type, const string & name, const string & sound)
: _type(type), _name(name), _sound(sound){ // class initializers
    puts("constructor with arguments");
}

Animal::Animal(const Animal & a){
    puts("copy constructor");
    _name = clone_prefix + a._name;
    _type = a._type;
    _sound = a._sound;
}

Animal::~Animal()
{
    printf("destructor: %s the %s\n", _name.c_str(), _type.c_str());
}

void Animal::print () const
{
    printf("%s the %s says %s\n", _name.c_str(), _type.c_str(), _sound.c_str());
}



int main()
{
    Animal a;
    
    a.print();
    
    const Animal b ("goat", "bob", "baah");
    b.print();
    
    const Animal c = b;
    c.print();
    
    puts("end of main");
    return 0;
}

/*
default constructor
unknwon the unknwon says unknwon
constructor with arguments
bob the goat says baah
copy constructor
clone-bob the goat says baah
end of main
destructor: clone-bob the goat
destructor: bob the goat
destructor: unknwon the unknwon
Program ended with exit code: 0
*/

 

Exception Handling : Try Catch 

예외를 처리하는 방법 중 하나는 Try Catch를 사용하는 것입니다. 

#include <iostream>
#include <string>
#include <exception>

using namespace std;

class E : public exception {
	const char* msg; 
	E();
public:
	E(const char * s) throw() : msg(s) {}
	const char* what() const  throw() { return msg; }
};

void broken()
{
	cout << "this is a broken function" << endl;
	throw E("Ouch!");
}

int main()
{
	cout << "let's have an emergency!" << endl;
	try {
		broken();
	}
	catch (E& e) {
		cout << e.what()<< endl;
	}
}