배낀곳
https://velog.io/@dogfootbirdfoot/CPP-Module-01
https://velog.io/@zhy2on/CPP-01
(private)https://www.notion.so/donpark/CPP01-0d66d64924654e6d87385b18f606e539
using namespace
, friend
는 금지ClassName.hpp
, ClassName.cpp
로 작성c++
컴파일러를 사용Turn-in directory : ex00/
Files to turn in : Makefile, main.cpp, Zombie.{h, hpp}, Zombie.cpp,
newZombie.cpp, randomChump.cpp
Forbidden functions : None
단일 객체 생성
1.void announce(void)
,
2.Zombie* newZomibe(std::string name)
,
3.void randomChump(std::string name)
이 함수들을 구현하고 사용 방식에 따라 스택영역 힙역역에 할당하고 적절한때 소멸해야한다.
Zombie 함수는 New 연산자를 사용해서 동적할당해서 Zombie를 생성 하는것으로 구현
randomChump는 동적할당 하지 않고 Zombie를 생성 하는 함수로 구현
Zombie객체를 클래스의 외부에서 생성하는 경우 항상 new연산자를 사용해서 도적으로 생성 해야만 생성할 수 있도록 해두었고,동적할당 하지 않는 경우는 클래스 내부에서 생성하고 사라질 수 있도록 설계
이렇게 하면 Zombie 객체를 동적할당 하지 않을 경우 특정 함수가 종료 되면서 자동 소멸된다.
동적 할당한 경우는 직접 delete로 삭제 해줘야 한다.( delete 실행시 자동으로 소멸자 호출됨, 동적할당 안 할경우는 함수가 끝날때 자동으로 소멸자 호출됨)
그리고 추가적으로 randomChump 에서 생성되는 Zombie는 random한 6 글자 이름을 가질수 있도록 구현.?
stack
영역
.
연산자를 통해서 인스턴스에 접근 가능Zombie stack = Zombie("Zombie_in_stack"); // stack 할당 예시
heap
영역
Zombie *heap = new Zombie("Zomibe_in_heap") // heap 예시
Turn-in directory : ex01/ ;
Files to turn in : Makefile, main.cpp, Zombie.{h, hpp}, Zombie.cpp,
zombieHorde.cpp
Forbidden functions : None
많은 좀비를 한번에 할당 하고 첫번째 생성한 Zombie 의 주소값을 반환하기 위해서 배열을 사용
객체를 N게 저장할수 있는 배열 생성법 익히기!
객체 배열 생성시 생성자에 인자를 넘길수 없고, 디폴트 생성자만 호출된다.
방법 1.
Zombie arr[N];
방법2.(동적할당)
Zombie *arr = new Zombie[N];
delete[] arr
이렇게 해줘야함ex01 에선 N 만큼의 객체를 한 번에 할당해야하기 때문에 객체 배열을 선언해야 한다.
위에서 언급했듯이 객체 배열 생성시 생성자에 인자를 넘길수 없고, 디폴트 생성자만 호출되므로-> 생성자를 통한 이름 초기화는 불 가능하기 때문에
디폴트 생성자가 필요함.
그리고 생성후 첫번째 객체의 주소를 반환하라고 했으므로 -> 객체 배열이 동적할당 되어야 한다.
new연산자를 이용해 한 번에 여러 공간을 할당받고 , 반복문을 이요해 각각의 객체에 이름을 부여하는것이다.
다만 클래스의 이름 변수는 private
접근제어자이기 때문에 외부에서 접근할수 없으므로
클래스 외부에서private
으로 선언된 변수를 변경하기위해서는 내부에 private변수에 접근할수 있는 public 함수를 만들어야 한다
이러한 함수를 setter
함수라 부른다.
변수를 public
으로 선언하지 않고 굳이 private으로 선언한뒤, setter
함수를 통해 접근하는 이유는?
-> 외부에서 무분별하게 변수의 값을 설정하는것을 막기 위해서, 함수를 통해서 접근한다면 예외처리가 가능하지만, 변수에 바로 접근할수 있다면 예외처리가 불가능하기 때문에.
객체 배열을 동적할당했기 때문에 사용후 반드시 delete 연산자 !!
HI THIS IS BRAIN
Turn-in directory : ex02/
Files to turn in : Makefile, main.cpp
Forbidden functions : None
문자열 포인터와 문자열 참조자 를 비교해보는 문제
C에서는 변수 이름 앞에 &를 붙이면 변수의 주소 값 지칭을 의미하지만 C++ 에서는 타입에 대해서 &를 붙이게 되면 그 타입을 참조하는 참조자 선언을 의미한다.
참조는 선언시 초기화 되어야 하며, NULL 값을 가질수 없다
포인터는 선언시 꼭 초기화 할 필요는 없으며 , NULL값을 가질수 있다.
std::string *strPTR = &str;
std::string &strREF = str;
stringPTR 은 str 의 주소값을 저장한다.
stringREF 는 str 의 또다른 이름으로 stringREF 를 선언 한것. (근데 얘를 변경하면 원래 것도 변경된다.)
#include <iostream>
#include <string>
class Change {
public:
static void change_normal(std::string str, char c) {
str[0] = c;
}
static void change_ref(std::string &str, char c) {
str[0] = c;
}
};
int main(void)
{
std::string str = "ABCDEF";
std::string *stringPTR = &str;
std::string &stringREF = str;
Change::change_normal(str, 'x');
std::cout << "normal)\tstr: " << str
<< "\tstringREF: " << stringREF << std::endl;
Change::change_ref(stringREF, 'y');
std::cout << "ref1)\tstr: " << str
<< "\tstringREF: " << stringREF << std::endl;
Change::change_ref(str, 'z');
std::cout << "ref2)\tstr: " << str
<< "\tstringREF: " << stringREF << std::endl;
return (0);
}
>>normal) str: ABCDEF stringREF: ABCDEF
>>ref1) str: yBCDEF stringREF: yBCDEF
>>ref2) str: zBCDEF stringREF: zBCDEF
ref1)에서 확인할 부분은 주소값을 넘긴게 아니지만 참조값이기 때문에 수정 가능하다.
ref2)에서 확인할 부분은 참조값을 매개변수로 받으면 참조한 변수인 stringREF만이 아니라 일반 str도 받을 수 있다.
Turn-in directory : ex03/
Files to turn in : Makefile, main.cpp, Weapon.{h, hpp}, Weapon.cpp, HumanA.{h,
hpp}, HumanA.cpp, HumanB.{h, hpp}, HumanB.cpp
Forbidden functions : None
getter : private 으로 멤버 변수를 선언하게 되면 그 변수를 직접적으로 손 대지 못하니, getter 라는 public 멤버함수를 만들어서 private 멤버 변수를 읽어오는것, 즉 가져오는것.
setter : 이건 값을 직접 바꾸는 용도.
참조자를 함수 인자로 사용하게 되면 함수를 사용할때 &
기호를 붙이지 않아도 알아서 참조자로 전달.
이걸 함수안에서 다시 &
를 써서 메모리 주소를 포인터 변수에 연결시켜 놓으면 함수 안에서 값을 업데이트 시킨것이 밖에서도 적용됨
원래 C에선 포인터를 사용하지 않으면 무조건 변수가 복사 되어서 함수 안에서 지역변수로 사용이 되고 함수가 끝나면 사라져버렸음. 그래서 포인터 인자를 사용해서 애초에 함수에 &
기호를 붙여서 메모리 주소로 전달해 줘야 했었는데
cpp 에선 그냥 함수 인자를 참조자로 선언해 놓으면 &
기호 안 붙이고 전달해 줘도 참조자로 전달.
그대신 그걸 전달받을 멤버변수는 포인터로 선언해야함. 왜냐면 참조자는 선언과 동시에 초기화가 되어야하고, 업데이트 할수없기 때문
주어진 main.cpp
int main()
{
{
Weapon club = Weapon("crude spiked club");
HumanA bob("Bob", club);
bob.attack();
club.setType("some other type of club");
bob.attack();
}
{
Weapon club = Weapon("crude spiked club");
HumanB jim("Jim");
jim.setWeapon(club);
jim.attack();
club.setType("some other type of club");
jim.attack();
}
return 0;
}
HumanA 와 HumanB 클래스를 정의할때, Weapon 을 참조자로 정의하는것 혹은 포인터로 정의하는것중 어느것이 바람직할까?
문제에 따르면 HumanA 클래스는 항상 무장상태(객체 생성시 Weapon 클래스를 갖고 있는채로 생성) 이고
HumanB 클래스는 Weapon을 소지할 수도 있고 안 할수도 있다.
주어진 main.cpp 를 보면 HumanA 는 생성시 Weapon 을 인자로 받기 때문에 포인터 혹은 참조자 중 어느것을 사용해도 되므로 참조자로 선언하는것이 적절하다
HumanA는 무조건 Weapon을 가지고 있는 상태여야하기 때문에 객체 생성시 무조건 초기화를 진행해야할 참조값으로 선언하는것이 좋다.
HumanB 는 Weapon을 인자로 받지 않기 때문에 Weapon 이라는 멤버 변수가 참조자일수 없다. (컴파일 에러)따라서 HumanB 의 Weapon은 포인터로 선성하는것이 적절하다.
HumanB 는 Weapon을 가질수도 있고, 없을수도 있기 때문에 Weapon 을 포인터로값으로 받아서 없는경우 NULL을 가지고 있도록 하게 한다.
참조자와 포인터에 대해 더 생각해보면
club 은 포인터 변수가 아니고 , HumanA의 생성자의 매개변수로 객체 자체를 받고 있으므로 HumanA의 생성자의 매개변수는 참조자임을 알 수있다. 또한 HumanB의 setWeapon 멤버 함수의 매개변수도 객체 자체를 받고 있으므로 HumanB의 setWeapon 멤버함수의 매개벼눗도 참조자임을 알수있다.
HumanA의 생성자의 매개변수는 참조자로 선언되었기 때문에 참조자의 특성상 선언과 동시에 초기화를 해주어야한다.
따라서 HumanA 생성자는 Member Initializer를 통해 참조자를 초기화 시켜주어야 한다.
Weapon 클래스에 getType 이 상수참조를 반환하도록 해야한다고 되어있는데,
상수 참조를 반환하는 이유를 생각해보면 멤버 변수를 가져오긴 해야하지만 그 값을 변경할수는 없도록 하기 위함이다.
ex) void example(int a) const
함수 뒤에 const 키워드가 붙으면 함수 안에서는 어떤 변수도 바꿀수 없음(mutable은 제외) 를 뜻한다.
함수가 클래스 멤버인 경우에만 const 키워드를 함수 뒤에 삽입할 수 있으며 해당 함수가 속한 객체의 맴버 변수를 변경할 수 없다는 뜻이다.
또한 const 가 붙은 함수 내에서는 const 가 붙은 다른 함수를 제외한 일반 함수는 호출하지 못한다.
이런한 기능을 가지고 있어 getter, bool 반환값에서 많이 사용되면 이로 인 해 함수 내부의 변수 변경을 방지할수 있다.
Sed is for losers
Turn-in directory : ex04/
Files to turn in : Makefile, main.cpp, *.cpp, *.{h, hpp}
Forbidden functions : std::string::replace
./replace [filename][str1] [str2]
ex04 에서는 filename으로 파일을 열어서 파일에 작성된 str1이라는 문자열을 찾아서 str2로 바꾸고 , filename뒤에 .replace를 붙여서 새로운 파일을 만들도록!
std::ifstream 객체를 생성한다는 것은 파일의 데이터를 읽을 수 있도록 하는 객체를 하나 만들겠다는 의미이다.
std::ofstream 객체를 생성한다는 것은 파일에 데이터를 쓸수 있도록하는 객체를 하나 만들겠다는 의미이다.
C++ 은 스트림 관련 기능과 파일 입출력 함수와 같은 관련 함수들을 묶어서 클래스로 제공하고 있다. 즉, C 와 같이 FILE 구조체 를 복잡한 포인터로 사용하지 않고 정보를 객체에 저장한다.
또 기존 C++표준 입출력 (std::cout, std::cin)형식과 유사하게 시프트 연산 (<<, >>) 을 사용할수 있다.
헤더파일 :<fstream>
fstream
헤더 에는 총 3가지 클래스 존재.
파일 스트림에 연결할때는 std::ifstream 과 std::ofstream의 생성자 인자로 파일 이름을 전달하는 방법과 open() 함수를 사용하는 방법을 사용할수 있다.
스트림 작업이 끝나면 close()함수를 호출하면 된다.
std::ifstream과 std::ofstream으로 파일을 열 때는 ios_base::openmode 타입의 flag값을 설정할 수 있는데, std::ifstream은 std::ifstream::in, std::ofstream은 std::ofstream::out이 기본 값으로 설정되어 있다. 기본 값이 아닌 다른 값을 원한다면 별도의 flag들을 명시해야 한다.
ifstream_file.is_open(),
ofstream_file.is_open()
getline(std::ifstream in, std::string buf)
C++에는 문자열 관련 라이브러리가 두 가지 존재한다. 첫번째는 '\0' 로 끝나 char * 형식을 따르는 C방식의 문자열 라이브러리 cstring 이고
두번째는 std::string 을 따르는 string 라이브러리다.
따라서 getline() 함수도 두가지 라이브러리에 각기 다른 함수로 존재한다.
istream라이브러리에 속한 cin.getline() 함수와 string 라이브러리에 속한 getline()함수가 있다.
1) istream의 cin.getline()
istream& getline(char* str, streamsize n);
istream& getline(char* str, streamsize n, char delim);
2) string 의 getline()
istream& getline(istream& is, string str);
istream& getline(istream& is, string str, char delim);
1) str.find(str2)
2) str.erase()
3) str.insert()
4)std::ios::npos
std::ios::npos는 -1인 상수이다.
std::ios::npos는 자료형이 std::string::size_type 으로 unsigned int 값으로 음수값이 존재 하지 않고 size_type의 가장 큰 수를 의미하게 된다.
5)str.clear()
6)str1.append(str2)
7) str.substr(pos)
Harl 2.0 Turn-in directory : ex05/ Files to turn in : Makefile, main.cpp, Harl.{h, hpp}, Harl.cpp Forbidden functions : None
ex05 과제의 목표는 멤버 함수에 대한 포인터를 사용
해보는 것!!!!
void (Harl::*fptr)(void)
/* levels 멤버함수 포인터 배열 */
void (Harl::*fptr[])(void) = {
&Harl::debug,
&Harl::info,
&Harl::warging,
&Harl::error
};
/* error case */
for (int i = 0; i < 4; i++)
{
if (level == levels[i])
*fptr[i]();
}
/* fixed case */
for (int i =0; i< 4; i++)
{
if (level == levels[i])
(this->*fptr[i])();
}
명백한 호출의 괄호 앞에 오는 식에는 함수 (포인터)형식이 있어야 합니다.
function-pointer Expression preceding paretheses of apparent call must have(pointer-to-)function type
여기서 fptr은 멤버함수포인터 배열.
멤버함수는 객체가 있어야 사용할수 있다. 때문에 그냥 *fptr[i]로 사용할수 없고 객체 본인을 나타내는 this 포인터를 사용해서 접근해줘야 한다.
std::string levels[] = {
"debug",
"info",
"warging",
"error"
};
void (Harl::*fptr[])(void) = {
&Harl::debub,
&Harl::info,
&Harl::warning,
&Harl::error
};
프로그램에서 정의된 함수는 프로그램이 실행될때 모두 메인 메모리에 올라간다.
이때 함수의 이름은 메모리에 올라간 함수의 시작 주소를 가리키는 포인터상수(constant pointer) 가 된다. 이렇게 함수의 시작주소를 가리키는 포인터 상수를 함수 포인터(function pointer)라고 부른다.
즉, 함수포인터는 함수가 저장된 메모리 주소를 저장할 수있는 변수로 함수 호출할 수있는 또 다른 방법을 제시해준다.
포인터 상수란(constant pointer) 란 포인터 변수가 가리키고 있는 주소값을 변경할 수 없는 포인터를 의미하며,
상수 포인터(pointer to constant) 란 상수를 가리키는 포인터를 의미한다.
/* 함수 포인터 선언 */
[반환 타입] (*[함수 포인터명])([매개변수]);
/* 함수 포인터에 함수 할당 */
[함수 포인터명] = [함수명];
[함수 포인터명] = &[함수명];
/* 선언과 동시에 할당 */
[반환 타입] (*[함수 포인터명])([매개변수]) = [함수명];
[반환 타입] (*[함수 포인터명])([매개변수]) = &[함수명];
/* 함수 포인터로 함수 호출 */
[함수 포인터명]([매개변수]);
(*[함수 포인터명])([매개변수]);
#include <iostream>
int plus(int x, int y)
{
return (x+y);
}
int minus(int x, int y)
{
return (x-y);
}
int main()
{
int (*fn)(int,int); // 함수포인터 변수를 선언
fn = plus; // 함수포인터 초기화 방법1
std::cout << fn(5,2) << std::endl; // 함수포인터 호출 방법1
fn = − // 함수포인터 초기화 방법2
std::cout << (*fn)(5,2) << std::endl; // 함수포인터 호출 방법2
return 0;
}
전역함수에 대한 포인터 배열 사용법
함수 포인터 배열은 함수포인터에 []를 작성해서 사용할수 있다.
함수 포인터 배열 역시 암시적, 명시적 두 방식으로 함수를 호출, 초기화 할수있다.
#include <iostream>
int plus(int x, int y) {
return (x + y);
}
int minus(int x, int y) {
return (x - y);
}
int main() {
int (*fnarr[2])(int,int); // 함수포인터 배열 선언
fnarr[0] = plus; // 함수포인터 배열 초기화 방법1
fnarr[1] = − // 함수포인터 배열 초기화 방법2
std::cout << (fnarr[0])(9, 2) << std::endl; // 함수포인터 배열 호출 방법1
std::cout << (*fnarr[1])(9, 2) << std::endl; // 함수포인터 배열 호출 방법2
return 0;
}
static
을 제외한 클래스 멤버 함수는 앞에 this
가 생략 되어 있다.
this
는 자기자신의 메모리 주소를 가리키는 포인터로 생성된 인스턴스의 주소가 담겨있다.
그러므로 멤버함수를 일반 함수 포인터에 할당하면 형식이 맞지 않는다는 오류를 확인할 수 있다.
멤버 함수 포인터 는 원하는 함수 포인터 이름으로 바로 선언할 수 있었던 일반 함수 포인터와 달리 어떤 클래스에 속해 있는지 ::
연산자와 함께 명시하여 선언해야한다.
할당할 때도 마찬가지로 함수의 클래스를 명시하고 클래스명 앞에 &
연산자 또한 함께 써야한다.
/* 함수 포인터 선언 */
[반환 타입] ([클래스명]::*[멤버 함수 포인터명])([매개변수]);
/* 함수 포인터에 함수 할당 */
[멤버 함수 포인터명] = &[클래스명]::[멤버 함수명];
/* 선언과 동시에 할당 */
[반환 타입] ([클래스명]::*[멤버 함수 포인터명])([매개변수]) = &[클래스명]::[멤버 함수명];
/* 함수 포인터로 함수 호출 */
(this->*[함수 포인터명])([매개변수]);
/* 클래스 외부에서 함수 호출 */
[클래스명] [인스턴스명] = [생성자]; // 인스턴스 생성
([인스턴스명].*[멤버 함수 포인터명])([매개변수]); // 인스턴스를 활용하여 함수 호출
클래스에 선언된 멤버함수에 대한 포인터 사용법
전역함수의 경우 암시적으로 함수포인터를 초기화하고 호출 할수있었지만,
멤버함수에 대한 함수포인터를 사용할때에는 꼭 명시적
으로 함수포인터를 초기화 하고 호출해야 합니다.
그리고 추가된 점은 함수에 명시적으로 접근하기 위해서 함수를 클래스::함수
와 같은 형태로 접근해야 합니다.
#include <iostream>
class Cal
{
private:
int plus(int x, int y)
{
return (x+y);
}
int minus(int x, int y)
{
return (x-y);
}
public:
void calcul(const std::string &str, int x, int y)
{
int (Cal::*fn)(int,int);
if (str == "plus")
{
fn = &Cal::plus;
}
else if (str == "minus")
{
fn = &Cal::minus;
}
std::cout << (this->*fn)(x,y) << std::endl;
}
};
int main(void)
{
Cal cal;
cal.calcul("plus",5,2);
cal.calcul("minus",5,2);
return (0);
}
클래스에 선언된 멤버함수에 대한 함수 포인터배열 사용법
#include <iostream>
class Calculate
{
private:
int plus(int x, int y) {
return (x + y);
}
int minus(int x, int y) {
return (x - y);
}
public:
void calculate(int x, int y) {
int (Calculate::*fn[2])(int, int);
fn[0] = &Calculate::plus;
fn[1] = &Calculate::minus;
for (int i = 0; i < 2; i++)
std::cout << (this->*fn[i])(x, y) << std::endl;
}
};
int main(void) {
Calculate cal;
cal.calculate(5, 2);
return (0);
}
static const 멤버 변수를 클래스 안에서 초기화할 수있는 경우는
1. integral 타입(int , float 등..)
2. enum 타입
의 경우에만 가능.
그 외에는 class 밖에서 초기화해야 합니다.
class A
{
private:
static const int INT_NUM = 10;
static const float FLOAT_NUM = 10.2;
static const double COUBLE_NUM = 10.22;
static const std::string COMMENT;
static const std::string COMMENT[];
};
const std::string A:COMMENT = "karen";
const std::string A:COMMENT[4] = {
"DEBUG",
"INFO",
"WARNING",
"ERROR"
};
Harl filter Turn-in directory : ex06/ Files to turn in : Makefile, main.cpp, Harl.{h, hpp}, Harl.cpp Forbidden functions : None
ex05에서 filtering을 위한 요소만 추가하면 된다. main()의 인자로 요구가 들어오는데 해당 요구가 일치할 경우 그 요구 이상의 모든 로그를 출력해야 한다. 이를 구현하기 위한 가장 적절한 방법은 switch문을 사용하는 것이다.