CPP Module 00

hhkim·2022년 5월 8일
0

42cursus

목록 보기
9/20
post-thumbnail

네임스페이스, 클래스, 멤버 함수, stdio 스트림, 초기화 목록, static, const 등

개념

namespace

내부 식별자의 유효 범위를 정해주는 것

  • 프로그램이 복잡해지고 여러 라이브러리가 포함되면 내부 식별자(변수명, 함수명, 구조체명 등) 간 충돌이 발생할 수 있기 때문에 사용

정의

namespace 키워드를 사용하여 정의

  • 블록 내에서는 정의할 수 없음
  • 일반적으로 헤더 파일에 정의
  • 네임스페이스를 명시하지 않으면 전역 네임스페이스에 자동으로 포함됨
  • C++ 표준 라이브러리 타입과 함수들은 std 네임스페이스 내부에 선언되어 있음
// namespace.h
namespace bono
{
    int count;
}
namespace hello
{
	int count;
}

접근

범위 지정 연산자(scope resolution operator) ::를 사용하여 접근

// namespace.cpp
#include "namespace.h"

int main(void)
{
	bono::count = 1;
    hello::count = 2;
}

class, member function

https://blog.hexabrain.net/167
구조체의 상위 호환

  • 멤버로 함수를 포함할 수 있음
  • 클래스의 멤버 변수를 프로퍼티(property), 멤버 함수를 메소드(method)라고도 함
  • 기본적으로 구조체는 기본 접근 제한자가 public이지만 클래스는 private
    👉 객체 지향 프로그래밍

인스턴스(instance)

메모리에 대입된 클래스 타입의 객체

  • 클래스는 구조체와 같이 사용자가 정의하는 일종의 타입이므로, 클래스를 사용하기 위해서는 해당 클래스 타입의 객체인 인스턴스를 선언해야 함
  • 인스턴스는 독립된 메모리 공간에 저장된 자신만의 멤버 변수를 가짐
  • 멤버 함수는 모든 인스턴스가 공유

객체 지향 프로그래밍(OOP, Object-Oriented Programming)

실생활의 물체에 대해 그 행동(behavior)과 상태(state)를 구체화하는 프로그래밍

  • 모든 데이터를 객체로 취급
  • 객체를 만들어내는 틀과 같은 개념이 클래스
  • 재사용이 용이하고 유지보수 및 업그레이드가 쉬움
    (다른 사람이 구현한 클래스를 가져와서 사용할 수 있고, 상속으로 확장도 되고, 오류가 나면 그 클래스만 찾아서 수정하면 되고...)

객체 지향 프로그래밍의 특징

https://hwan-shell.tistory.com/226

  1. 추상화(abstraction)
    어떤 객체가 가진 공통된 특징, 속성, 기능 등을 한 곳에 모아 이름을 붙여 작성하는 것
    (객체의 멤버 변수와 함수를 정의하는 것)
  2. 캡슐화(encapsulation)
    공통의 속성이나 기능, 행동 등을 하나의 클래스로 묶는 것
    (재사용성 up)
  3. 정보 은닉(data hiding)
    캡슐화의 효과. 객체의 로직과 외부에 보일 필요 없는 변수와 특징 등은 감추고 동작하는 기능만 외부에 공개
    (외부 노출을 최소화하여 오류를 최소화하고 안전성 up)
  4. 상속성(inheritance)
    자식 클래스가 부모 클래스의 속성, 특징 등을 물려받아 재사용하거나 확장시키는 것
    (재사용성 up)
  5. 다형성(polymorphism)
    동일한 함수명을 상황에 따라 다른 의미를 지니도록 하는 것
    (오버로딩, 오버라이딩, ...)

stdio stream

  • iostream: C++에서 표준 입출력에 필요한 기능들을 포함하는 표준 라이브러리
std::cout << 출력할데이터;
std::cin >> 저장할변수;
  • cin 객체는 사용자가 입력한 데이터를 오른쪽에 위치한 변수 타입과 동일하게 변환시켜줌
  • <<: 삽입 연산자
  • >>: 추출 연산자
  • 삽입 연산자와 추출 연산자가 데이터의 흐름을 나타내어 직관적
  • 입출력 데이터의 타입을 자동으로 변환시켜주어 편리하고 안전

initialization lists

초기화 목록
생성자에서 필드를 간단하게 초기화하는 방법

  • 클래스의 멤버를 초기화할 때 사용

변수 초기화

  1. 복사 초기화(copy initialization)
int value = 5;
  1. 직접 초기화(direct initialization)
    일부 데이터 타입에서 복사 초기화보다 성능이 뛰어나기 때문에 직접 초기화를 사용하는 것이 좋음
int value(5);

클래스 초기화

생성자 초기화 목록

class Test
{
	int m_a;
    int m_b;
	public:
		Test(int a, int b): m_a(a), m_b(b) {}    	
}
  • const(상수)를 초기화하거나 레퍼런스 변수를 초기화하는 경우 반드시 사용

static

  • 클래스의 모든 멤버 변수와 멤버 함수는 정적(static) 멤버로 지정 가능
  • 인스턴스가 생성될 때마다 독립적으로 생성되는 게 아니라, 해당 클래스에 하나만 생성됨
  • 인스턴스 이름으로 접근할 수도 있지만, 일반적으로 클래스 이름으로 접근
  • 정적 멤버 함수는 일반 멤버에 접근할 수 없고, 정적 멤버에만 접근 가능

const

상수(값을 변경할 수 없는 변수)

const 변수

  • 선언과 동시에 초기화해야 하며, 할당을 통해 값을 변경할 수 없음
  • 일반 변수 값을 이용해 초기화할 수 있음
  • 클래스의 멤버 변수인 경우 초기화 리스트를 사용하여 초기화해야 함
  • 포인터 변수의 경우
    • const int* ptr: 변수가 가리키는 값에 대하여 상수화
      (가리키는 값을 직접 변경할 수는 있지만 포인터를 통해 접근하여 변경은 불가능)
    • int* const ptr: 포인터 자체를 상수화
      (주소값 변경 불가능)
    • 둘을 합쳐서 const int* const ptr로 사용할 수도 있음

const 멤버 함수

  • 클래스의 멤버 함수에만 적용 가능
  • 함수 선언문 맨 뒤에 const를 붙임
    예) int getNum(void) const {}
  • 함수 내에서 객체의 멤버 변수를 상수화 (함수 내의 불변성 유지)
  • 지역 변수는 변경 가능

서브젝트

  • c++-Wall -Wextra -Werror 플래그로 컴파일
  • -std=c++98 플래그를 추가해도 컴파일되어야 함
  • 클래스 이름은 카멜 케이스로 작성
  • 파일이 클래스를 포함하는 경우 파일명은 항상 클래스명으로 작성
  • 특별한 언급이 없으면 출력은 줄바꿈과 함께 표준 출력으로 함
  • *printf(), *alloc(), free() 사용 금지
  • using namespace <ns_name>, friend 사용 금지
  • STL은 모듈 08에서만 사용 가능
  • new 키워드 사용 후 메모리 누수 잘 체크하기
  • 모듈 02~08에서 클래스는 Orthodox Canonical Form을 따라야 함
  • 헤더에 함수 구현 금지 (함수 템플릿은 예외)
  • 헤더를 독립적으로 작성하고 필요한 의존성을 추가할 것. 단, 중복으로 포함 금지
  • 필수 파일을 제출한다면 추가적으로 다른 파일을 제출해도 됨 (코드가 길어졌거나 할 때)
  • 가이드라인에는 나와 있지 않지만 예시에 나와 있는 부분이 있다면 구현해야 함

ex00: Megaphone

iostream cout 사용해보기

  • 인자를 대문자로 변환하여 출력
    인자가 없으면 * LOUD AND UNBEARABLE FEEDBACK NOISE * 출력
  • 줄바꿈문자(\n) 대신 std::endl 사용
    • \n은 버퍼를 사용하기 때문에 바로 출력이 되지 않을 수도 있음
  • 대문자 변환cctypetoupper 사용
    결과를 int로 받기 때문에 타입 캐스팅을 해주어야 함
    👉 static_cast<타입> 연산자 사용
    (char) 처럼 변환하는 것과 달리 컴파일 타임에 오류를 잡아줌
$>./megaphone "shhhhh... I think the students are asleep..."
SHHHHH... I THINK THE STUDENTS ARE ASLEEP...
$>./megaphone Damnit " ! " "Sorry students, I thought this thing was off."
DAMNIT ! SORRY STUDENTS, I THOUGHT THIS THING WAS OFF.
$>./megaphone
* LOUD AND UNBEARABLE FEEDBACK NOISE *
$>

ex01: My Awesome PhoneBook

동적 할당 없이(new 쓰지 않고) 클래스 만들고 사용해보기
string, iomanip 사용해보기

  • 각 연락처와 전화번호부는 클래스를 통해 인스턴스화해야 함
  • 클래스 내부에서만 사용하는 멤버들은 private, 밖에서도 쓰는 멤버들은 public으로 선언

클래스

  • PhoneBook
    • 연락처 배열을 가짐
    • 최대 8개의 연락처 저장 가능. 9번째 연락처를 저장하려면 가장 오래된 연락처를 교체함
    • 동적 할당 금지
  • Contact: 연락처 클래스

명령어

프로그램 시작 시 전화번호부는 비어 있고, 사용자에게 아래의 명령어 중 하나를 입력받음
하나의 명령어를 수행한 후 다시 명령어 입력받기

  • ADD: 새로운 연락처 저장
    • 한 번에 한 필드씩 입력받음
    • 모든 필드 입력을 받았으면 전화번호부에 저장
    • 저장된 연락처는 빈 필드를 가질 수 없음
    • 필드: first name, last name, nickname, phone number, darkest secret
  • SEARCH: 특정 연락처 출력
    • 저장된 연락처를 네 가지 컬럼으로 출력: index, first name, last name, nickname
    • 각 컬럼은 10글자 너비, |로 구분, 오른쪽 정렬
    • 텍스트가 10글자보다 길면 10번째 글자를 .으로 치환해서 자르기
    • 사용자에게 인덱스를 입력받아서 해당 연락처 출력 (한 필드 당 한 줄)
    • 입력받은 인덱스가 범위를 벗어나거나 틀린 경우 적절하게 처리
      (범위 내로 입력하라고 알려주고 다시 입력받으면 될 듯)
  • EXIT: 프로그램 종료
    • 동적 할당을 하지 않았기 때문에 메모리를 해제할 필요 없음
  • 이외의 명령어는 무시

http://www.cplusplus.com/reference/string/string/
http://www.cplusplus.com/reference/iomanip/

구현

  • c++에서는 헤더파일의 확장자가 hpp (참고)
  • hpp 파일에 클래스를 선언하고 cpp 파일에 클래스를 정의(멤버 함수 구현)
  • 글자 출력할 너비 지정은 iomanip의 setw()
  • 문자열 길이는 string의 size()
  • 문자열 자르기는 string의 substr()

cin에 int가 아닌 문자가 들어올 때 제대로 동작하지 않는 오류

https://literate-t.tistory.com/69

int	num;
std::cin >> num;

위와 같은 코드가 있을 때 유효하지 않은 입력값이 들어오면 cin에 failbit가 켜지고 더이상 cin에 입력을 받을 수 없게 된다고 함
이후 입력을 위해서는 플래그를 초기화하고 버퍼의 내용을 모두 삭제하여 스트림을 복구해야 함

string 메모리 관리

https://smoothiecoding.kr/cpp-dynamical-string/
https://ha-young.github.io/2020/cpp/2020-08-05-C++-String-%EB%AC%B8%EC%9E%90%EC%97%B4/
C++에서 string은 클래스로 관리되는데 자동으로 newdelete를 실행해준다고 함
👉 따로 메모리를 관리할 필요가 없음

cin에 공백 입력이 들어오면 다음 입력을 건너뛰는 오류

https://kyu9341.github.io/C-C/2020/01/17/C++getline()/
<string>getline() 사용
주의! 이전에 cin으로 입력을 받았다면 개행 문자가 버퍼에 남아 있어서 입력으로 간주하기 때문에 cin.ignore()를 호출하여 버퍼에 남아 있는 내용을 무시해야 함


ex02: The Job Of Your Dreams

헤더와 로그 파일을 참고하여 Accout.cpp 작성

개념

STL(Standard Template Library)

뒤에서 사용하는 과제가 있으니까 여기서 굳이 깊게 살펴볼 필요는 없을 듯

컨테이너(container) - vector

임의 타입의 객체 보관
(vector, list, deque가 있는데 이번 과제에서는 vector 사용)

  • 가변길이 배열
  • 원소들이 메모리상에서 순차적으로 저장되어 있기 때문에 접근이 빠름
  • 보통 저장된 메모리보다 많은 공간을 미리 할당해두고, 공간이 더 필요하면 재할당됨
  • 원소 접근과 맨 뒤 추가 및 제거는 빠르지만, 임의 위치에 추가 및 제거의 경우 느림
  • begin()end()를 사용하여 반복자를 얻을 수 있음
    • begin(): 첫 번째 원소를 가리킴
    • end(): 마지막 원소 하나 뒤를 가리킴 (비어 있는 벡터를 표현하기 위해 이렇게 설계됨)

반복자(iterator)

컨테이너 원소에 접근할 수 있는 포인터와 같은 객체

  • 컨테이너에 iterator 멤버 타입으로 정의되어 있음

std::pair

첫 번째로 들어온 값을 first, 두 번째로 들어온 값을 second 멤버 변수로 갖는 클래스

std::for_each

시작 지점과 끝 지점을 가리키는 반복자를 받아서 인자로 주어진 함수를 순차적으로 실행

Function for_each (InputIterator first, InputIterator last, Function fn);

std::for_each( acc_begin, acc_end, std::mem_fun_ref( &Account::displayStatus ) );
  • fnvoid(T&)
  • std::mem_fun_ref: 특정 객체의 멤버 함수를 래핑할 때 사용?

구현

타임스탬프

ctime 사용

  • time()으로 현재 시간 받아오기
    • time_t: 밀리세컨드 단위
time_t time (time_t* timer);
struct tm * localtime (const time_t * timer);
  • strftime()으로 포맷 지정 가능
    • ptr: 문자열을 저장할 버퍼
    • maxsize: ptr에 복사될 최대 문자 수 (널문자 포함)
    • format: 형식 지정 문자열
    • timeptr: struct tm 포인터
size_t strftime (char* ptr, size_t maxsize, const char* format, const struct tm* timeptr );

amounts 초기화

[19920104_091532] index:0;amount:42;created
[19920104_091532] index:1;amount:54;created
[19920104_091532] index:2;amount:957;created
[19920104_091532] index:3;amount:432;created
[19920104_091532] index:4;amount:1234;created
[19920104_091532] index:5;amount:0;created
[19920104_091532] index:6;amount:754;created
[19920104_091532] index:7;amount:16576;created
typedef std::vector<Account::t>							  accounts_t;

int	const				amounts[]	= { 42, 54, 957, 432, 1234, 0, 754, 16576 };
size_t const			amounts_size( sizeof(amounts) / sizeof(int) );
accounts_t				accounts( amounts, amounts + amounts_size );
  • 배열을 가지고 Account 생성자를 호출한 뒤 각 인스턴스를 accounts_t vector에 저장
  • Account의 생성자가 int를 받도록 되어 있음
Account( int initial_deposit );

displayAccountsInfos()

[19920104_091532] accounts:8;total:20049;deposits:0;withdrawals:0
static void	displayAccountsInfos( void );
  • Account의 static 멤버 변수들 출력

displayStatus()

[19920104_091532] index:0;amount:42;deposits:0;withdrawals:0
[19920104_091532] index:1;amount:54;deposits:0;withdrawals:0
[19920104_091532] index:2;amount:957;deposits:0;withdrawals:0
[19920104_091532] index:3;amount:432;deposits:0;withdrawals:0
[19920104_091532] index:4;amount:1234;deposits:0;withdrawals:0
[19920104_091532] index:5;amount:0;deposits:0;withdrawals:0
[19920104_091532] index:6;amount:754;deposits:0;withdrawals:0
[19920104_091532] index:7;amount:16576;deposits:0;withdrawals:0
std::for_each( acc_begin, acc_end, std::mem_fun_ref( &Account::displayStatus ) );
  • Account 인스턴스의 멤버 변수 출력

makeDeposit(), makeWithdrawal()

[19920104_091532] index:0;p_amount:42;deposit:5;amount:47;nb_deposits:1
[19920104_091532] index:1;p_amount:54;deposit:765;amount:819;nb_deposits:1
[19920104_091532] index:2;p_amount:957;deposit:564;amount:1521;nb_deposits:1
[19920104_091532] index:3;p_amount:432;deposit:2;amount:434;nb_deposits:1
[19920104_091532] index:4;p_amount:1234;deposit:87;amount:1321;nb_deposits:1
[19920104_091532] index:5;p_amount:0;deposit:23;amount:23;nb_deposits:1
[19920104_091532] index:6;p_amount:754;deposit:9;amount:763;nb_deposits:1
[19920104_091532] index:7;p_amount:16576;deposit:20;amount:16596;nb_deposits:1
...
[19920104_091532] index:0;p_amount:47;withdrawal:refused
[19920104_091532] index:1;p_amount:819;withdrawal:34;amount:785;nb_withdrawals:1
[19920104_091532] index:2;p_amount:1521;withdrawal:657;amount:864;nb_withdrawals:1
[19920104_091532] index:3;p_amount:434;withdrawal:4;amount:430;nb_withdrawals:1
[19920104_091532] index:4;p_amount:1321;withdrawal:76;amount:1245;nb_withdrawals:1
[19920104_091532] index:5;p_amount:23;withdrawal:refused
[19920104_091532] index:6;p_amount:763;withdrawal:657;amount:106;nb_withdrawals:1
[19920104_091532] index:7;p_amount:16596;withdrawal:7654;amount:8942;nb_withdrawals:1
for ( acc_int_t it( acc_begin, dep_begin );
	  it.first != acc_end && it.second != dep_end;
	  ++(it.first), ++(it.second) ) {

	(*(it.first)).makeDeposit( *(it.second) );
}
...
for ( acc_int_t it( acc_begin, wit_begin );
	  it.first != acc_end && it.second != wit_end;
	  ++(it.first), ++(it.second) ) {

	(*(it.first)).makeWithdrawal( *(it.second) );
}
  • accountsdeposits 둘 다 마지막이 아닐 때까지 하나씩 순회
  • Account 인스턴스의 멤버 함수인 makeDeposit의 인자로 현재 가리키는deposit을 순서대로 전달하여 호출
  • withdrawals도 동일
    • 계좌 잔액보다 인출할 금액이 더 크면 refused
      👉 int checkAmount( void ) const; 사용

소멸자(destructor)

프로그램이 종료될 때 자동으로 호출됨

[19920104_091532] index:0;amount:47;closed
[19920104_091532] index:1;amount:785;closed
[19920104_091532] index:2;amount:864;closed
[19920104_091532] index:3;amount:430;closed
[19920104_091532] index:4;amount:1245;closed
[19920104_091532] index:5;amount:23;closed
[19920104_091532] index:6;amount:106;closed
[19920104_091532] index:7;amount:8942;closed
~Account( void );
  • 과제의 로그는 우분투 환경에서 생성되었고, 맥에서는 소멸자가 역순으로 호출되기 때문에 로그도 역순으로 찍힌다고 함

private에 있는 기본 생성자

C++98에서 기본 생성자, 대입 연산자, 복사 생성자, 소멸자를 작성하지 않으면 자동으로 정의되어 호출할 수 있게 됨
👉 이를 막으려면 외부에서 접근하지 못하도록 private으로 빼야 함
(이번 서브젝트에서 기본 생성자를 호출하지 않기 때문에 안에 구현할 내용은 없음)

static 멤버 변수를 인식하지 못하는 오류

https://stackoverflow.com/questions/9282354/static-variable-link-error

Undefined symbols for architecture arm64:
  "Account::_totalAmount", referenced from:
      Account::getTotalAmount() in Account.o
      Account::displayAccountsInfos() in Account.o
      Account::Account(int) in Account.o
      Account::makeDeposit(int) in Account.o
...

static 멤버 변수는 전역에서 정의를 하고 사용해야 한다고 함!


참고

Makefile
https://skm1104.tistory.com/m/86
https://bigpel66.oopy.io/library/42/inner-circle/11
https://velog.io/@appti/CPP-Module-00-ex01

0개의 댓글