#함수의 설명서
def cheerup(name):
"""
이 함수는 응원의 메세지를 출력하는 함수입니다
인수로는 응원할 사람의 이름을 입력하면 됩니다
"""
print("{}님 힘내세요".format(name))
help(cheerup) #설명서 출력
cheerup("홍길동")
범위에 따른 값
def func():
num=10
print("함수 호출 시 변수 값은", num)
num=20
func()
print("함수 호출 이후 변수 값은", num)
#함수 호출 시 변수 값은 10
#함수 호출 이후 변수 값은 20
list_=["홍", "윤", "이", "임"]
print(len(list_)) #리스트 길이 측정
print(type(list_)) #데이터 타입 확인
print(list_[1:2]) #시작부터 끝 인덱스 직전까지 출력
print(list_[:2]) #처음부터 인덱스 직전까지 출력
#4
#<class 'list'>
#['윤']
#['홍', '윤']
#include <iostream>
using namespace std;
void Counter()
{
static int cnt; //초기화하지 않으면 0으로 초기화되며 딱 한번 실행된다
cnt++;
cout<<"Current cnt: "<<cnt<<endl;
}
int main(void){
for(int i=0; i<10; i++){
Counter();
}
return 0;
}
static 멤버변수가 private 선언되면 클래스 내부에서만 접근되고, public으로 선언되면 클래스의 이름이나 객체의 이름을 통해서 어디든 접근 가능하지만, privte으로 유지하고 싶다면 이렇게 처리하면 밖에서도 쓸 수 있다. (외부 출처)
....
int GetPrivateVal(){
return simObjCnt;
}
}; //클래스 내부
static 멤버변수의 초기화 문법
int SoSimple::simObjCnt=0;
// SoSimple클래스의 멤버변수simObjCnt가 메모리공간에 저장될 때 0으로 초기화해라
static 변수를 생성자에서 초기화하면 안 되는 이유
객체가 생성될때마다 0으로 초기화된다 객체가 생성될 때 동시에 생성되는 변수가 아니고 이미 메모리 공간에 할당이 이뤄진 변수다
static 멤버변수는 어디서든 접근이 가능하기 때문에, 클래스 내부에서 private 선언되면 해당 클래스 객체만 접근이 가능하지만 public 선언되면 클래스 이름/객체의 이름을 통해 어디서든 접근이 가능하다. 아래 두 문장은 sim1 sim2의 멤버변수에 접근하는 듯한 혼동 여지가 있어 첫번째 문장처럼 클래스의 이름을 사용해서 접근하는 것을 추천한다.
선언된 클래스의 모든 객체가 공유한다 public으로 선언되면 클래스 이름을 사용해 호출이 가능하다 객체의 멤버로 존재하는 것이 아니다
컴파일 에러 발생 예
class SoSimple{
private:
int num1;
static int num2;
public:
SoSimple(int n): num1(n)
{ }
static void Adder(int n)
{
num1+=n; //error
num2+=n;
}
};
int SoSimple::num2=0;
객체의 멤버가 아니니까 멤버변수에 접근이 안된다. 객체생성 이전에도 호출이 가능하다 (이미 메모리공간에 할당됐다) ..멤버변수는 아직 안만들어졌을 것이다
static 멤버함수 내에서는 static 멤버변수와 static 멤버함수만 호출이 가능하다
전역함수를 대체해서 쓰인다.
const static 멤버
클래스 내에 선언된 const 멤버변수(상수)의 초기화는 이니셜라이저를 통해야만 한다. const static으로 선언되는 멤버변수(상수)는 선언과 동시에 초기화가 가능하다. const static 멤버변수는 클래스가 정의될 때 지정된 값이 유지된다 그래서 초기화가 가능하게 문법으로 정의하고 있다.
#include <iostream>
using namespace std;
class CountryArea
{
public:
const static int RUSSIA = 1707540;
const static int CANADA = 998467;
const static int CHINA = 957290;
const static int SOUTH_KOREA = 9922;
// const static 상수는 하나의 클래스에 둘 이상 모이는 게 일반적
};
int main(void)
{
cout<<"러시아 면적: "<<CountryArea::RUSSIA<<"km"<<endl;
cout<<"캐나다 면적: "<<CountryArea::CANADA<<"km"<<endl;
cout<<"중국 면적: "<<CountryArea::CHINA<<"km"<<endl;
cout<<"한국 면적: "<<CountryArea::SOUTH_KOREA<<"km"<<endl;
// 클래스의 이름을 통해 접근하는게 편할 뿐만 아니라
// 점근하는 대상에 대한 정보를 잘 담는다.
}
mutable
const 함수 내에서의 값의 변경을 예외적으로 허용하는 키워드이다. mutable의 과도한 사용은 const 선언의 의미를 퇴색시키기에 가급적 사용 빈도를 낮춰야 한다.
#include <iostream>
using namespace std;
class SoSimple
{
private:
int num1;
mutable int num2; //const 함수에 대해 예외 - const 함수 내에서의 변경이 허용된다
public:
SoSimple(int n1, int n2)
:num1(n1), num2(n2)
{ }
void ShowSimpleData() const
{
cout<<num1<<", "<<num2<<endl;
}
void CopyToNum2() const
{
num2=num1;
}
};
int main(void)
{
SoSimple sm(1, 2);
sm.ShowSimpleData();
sm.CopyToNum2();
sm.ShowSimpleData();
return 0;
}
//C스타일
int num=20;
int &ref=num;
//C++스타일
int num(20);
int &ref(num);
객체의 생성
class SomSimple
{
private:
int num1;
int num2;
public:
SoSimple(int n1, int n2):num1(n1), num(n2){}
void ShowSimpleData()
{
cout<<num1<<endl;
cout<<num2<<endl;
}
};
int main(void)
{
SoSimple sim1(15, 20);
SoSimple sim2=sim1;
//sim2 객체를 새로 생성하여 객체 sim1과 sim2간의 멤버 대 멤버 복사가 일어난다.
sim2.ShowSimpleData();
return 0;
}
C++의 모든 객체는 생성자의 호출을 동반한다.
SoSimple sim2(sim1);
SomSimple형 객체를 생성 -> 객체의 이름을 sim2로 지정 -> sim1을 인자로 받을 수 있는 생성자의 호출을 통해서 객체 생성을 완료
복사 생성자 예제
복사생성자의 호출시점을 잘 이해해야 한다. 멤버 대 멤버 복사에 사용되는 원본의 변형 방지를 위해 키워드 const를 삽입한다.
위 생성자를 얕은복사 예제에 추가해준다. 멤버변수 age의 멤버 대 멤버 복사,메모리 공간 할당 후 문자열 복사, 할당된 메모리의 주소값을 멤버 name에 저장한다.
복사생성자를 별도로 정의하는 경우 (깊은 복사)
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include <memory>
using namespace std;
class Person
{
private:
char* name;
int age;
public:
Person(const char* myname, int myage)
: name(NULL)
, age(0)
{
int len = strlen(myname) + 1;
name = new char[len];
memset(name,len,NULL);
strcpy(name, myname);
age = myage;
}
// deep copy
Person(const Person& copy) :age(copy.age)
{
name = new char[strlen(copy.name) + 1];
strcpy(name, copy.name);
}
void ShowPersonInfo() const {
cout << "이름 : " << name << endl;
cout << "나이 : " << age<< endl;
}
~Person() {
delete []name;
name = nullptr;
cout << "소멸자 호출!" << age << endl;
}
};
int main(void) {
Person man1("Lee", 29);
Person man2 = man1;
man1.ShowPersonInfo();
man2.ShowPersonInfo();
return 0;
}
복사 생성자의 호출시점 (3가지)
1. 기존에 생성된 객체를 이용해서 새로운 객체를 초기화 2. Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우 3. 객체를 반환하되, 참조형으로 반환하지 않는 경우
* 공통점 : 객체를 새로 생성하면서 동시에 동일한 자료형의 객체로 초기화해야한다.
복사 생성자의 호출횟수는 프로그램의 성능과도 관련있다.
1. 기존에 생성된 객체를 이용해서 새로운 객체를 초기화
메모리공간이 할당과 동시에 초기화되는 대표적인 상황은 다음과 같다.
int num1=num2 //num1라는 이름의 메모리공간을 할당과 동시에 num2에 저장된 값으로 초기화
2. Call-by-value 방식의 함수호출 과정에서 객체를 인자로 전달하는 경우
다음에서도 할당과 동시에 초기화가 일어난다.
int SimpleFunc(int n)
{
.....
}
int main(void)
{
int num=10;
SimpleFunc(num); //호출되는 순간 매개변수 n이 할당과 동시에 초기화
}
SimpleFunc 함수가 호출되는 순간에 매개변수 n이 할당과 동시에 변수 num에 저장되어 있는 값으로 초기화된다. 매개변수도 함수가 호출되는 순간에 할당되므로 메모리공간의 할당과 초기화가 동시에 일어나는 상황이다.
3. 객체를 반환하되, 참조형으로 반환하지 않는 경우
다음의 경우에도 메모리 공간이 할당되면서 동시에 초기화가 이뤄진다. 값을 반환하면 반환된 값은 별도의 메모리 공간이 할당되어서 저장된다.
int SimpleFunc(int n)
{
.....
return n; //반환하는 순간 메모리공간이 할당되면서 동시에 초기화
}
int main(void)
{
int num=10;
cout<<SimpleFunc(num)<<endl; //반환되는 값을 메모리 공간에 저장해뒀기에 출력도 가능하다.
.....
}
출력을 위해서는 값을 참조할 수 있어야 하고, 참조가 가능하려면 메모리 공간에 저장되어있어야 된다. 함수가 값을 반환하면 별도의 메모리 공간이 할당되고, 이 공간에 반환값이 저장된다. (반환값으로 초기화된다.)
* 변수 말고 객체, 함수도 마찬가지
객체도 마찬가지로 객체가 생성되면서 (메모리공간이 할당되면서)초기화가 이뤄진다. 함수호출도 마찬가지로 객체가 생성되면서 전달되는 인자로 초기화된다.
SimpleFuncObjc 함수가 호출되는 순간, 매개변수로 선언된 ob 객체가 생성되고(메모리공간이 할당) 인자로 전달된 obj 객체로 초기화된다.(메모리 공간이 할당되면서 동시에 초기화)
SoSimple SimpleFuncObj(SoSimple ob)
{
.....
return ob; //반환하는 순간 메모리 공간이 할당되면서 동시에 초기화
// return문이 실행되는 순간 SoSimple 객체를 위한 메모리공간이 할당되고,
// 이 공간에 할당된 객체는 반환되는 객체 ob의 내용으로 초기화
}
할당 이후, 복사생성자를 통한 초기화
객체가 생성 및 초기화될 때 초기화는 멤버 대 멤버가 복사되는 형태로 이뤄져야 하므로 복사생성자를 호출하는 방식으로 초기화를 진행한다.
# 임시 변수 (출처) c++에서는 리턴값이 있을때 리턴값을 저장하기 위해 메모리 상에 임시 변수가 생성된다. 단! 리턴값이 없다면 임시변수는 생성되지 않는다. 또한! 함수 선언이 끝나게 된다면 임시변수 역시 메모리 상에서 사라진다.
반환할 때 만들어진 객체가 사라지는 시점
임시객체도 임시변수처럼 임시로 생성됐다 소멸되는 객체이다. 임시객체는 임의로 만들 수 있다.
#include <iostream>
using namespace std;
class Temporary
{
private:
int num;
public:
Temporary(int n)
:num(n){
cout<<"create obj:"<<num<<endl;
}
~Temporary()
{
cout<<"destroy obj:"<<num<<endl;
}
void ShowTempInfo()
{
cout<<"My num is:"<<num<<endl;
}
};
int main()
{
Temporary(100);//임시객체를 직접 생성
// 100으로 초기화된 Temporary 임시객체가 생성된다.
cout << "********* after make!" << endl << endl;
Temporary(200).ShowTempInfo();// 임시객체를 생성하고, 임시객체를 대상으로 ShowTempInfo 함수를 호출한다
// 객체가 생성 및 반환되면 생성 및 반환된 위치에 객체를 참조할 수 있는 참조 값이 반환되기 때문에 이런 문장이 가능 (후에 이야기)
cout << "********* after make2!" << endl << endl;
const Temporary& ref = Temporary(300); // 임시객체를 생성
// 참조자 ref로 임시객체를 참조하고 있다
cout << "********* end of main!" << endl << endl;
return 0;
}
클래스 외부에서 객체의 멤버함수를 호출하기 위해 필요한 것은 3가지 중 하나이다.
1) 객체에 붙여진 이름 2) 객체의 참조 값 (객체 참조에 사용되는 정보) 3) 객체의 주소 값
임시객체가 생성된 위치에는 임시객체의 참조 값이 반환된다. Temporary(200).ShowTempInfo(); 는 (임시객체의 참조값).ShowTempInfo(); 로 변환되는 것이다. 참조값이 반환되기 때문에 멤버함수의 호출이 가능하며 다음 문장도 가능하다.
const Temporary& ref = Temporary(300); // 임시객체를 생성
임시객체 생성 시 반환되는 참조값이 참조자 ref에 전달되어 ref가 임시객체를 참조하게된다 임시객체는 메모리 공간에 있고, 임시객체의 참조값이 반환되어 일을 처리한다.
(접근이 불가능하게 된)임시객체는 다음 행으로 넘어가면 바로 소멸된다. 소멸자를 호출한다. 참조자에 참조되는 임시객체는 바로 소멸되지 않는다.
반환할 때 만들어지는 임시객체의 소멸시기 확인 예제
(분리컴파일을 익숙하게 하려고 예제를 분리해서 작업하고 있습니다.)
// SoSimple.h
#pragma once
#include <iostream>
using namespace std;
class SoSimple
{
public:
SoSimple(int n);
SoSimple(const SoSimple& copy);
~SoSimple();
private:
int num;
};
// SoSimple.cpp
#include "SoSImple.h"
SoSimple::SoSimple(int n) : num(n)
{
cout << "New OBject: " << this << endl;
}
SoSimple::SoSimple(const SoSimple& copy) : num(copy.num)
{
cout << "New Copy obj: " << this << endl;
}
SoSimple::~SoSimple()
{
cout << "Destoy obj: " << this << endl;
}
// C 스타일의 초기화 문장
int main(void)
{
int val1=20;
AAA al=10;
// C++ 스타일의 초기화 문장
int main(void)
{
int val1(20);
AAA a1(10);
5-2 복사 생성자의 형태
생성자가 오버로딩된 형태의 클래스 예제
class AAA
{
public :
AAA(){
cout<<"AAA() 호출"<<endl;
}
AAA(int i){
cout<<"AAA(int i) 호출"<<endl;
}
AAA(const AAA& a){ //복사생성자
//생성자의 이름에 해당하는 클래스의 객체를 인자로 받을 수 있는
//AAA클래스 객체를 인자로 받을 수 있는
cout<<"AAA(const AAA& a) 호출"<<endl;
}
};
int main(void)
{
AAA obj1; //메모리공간 할당 후 void형 생성자 호출되면서 객체 생성 완료
AAA obj2(10);
AAA obj3(obj2);
// 3번 생성자 호출, 1번 복사생성자 호출
return 0;
}
복사생성자 정의
복사를 하기 위한 용도로 사용될 수 있는 생성자
5-3 디폴트 복사 생성자
복사생성자의 의미와 기능에 대해 알아본다.
※ 배운 것 중 디폴트로 제공되는 것 : 생성자, 소멸자, 복사생성자 생성자소멸자는 순서 때문이나 복사생성자는 역할이 있다.
디폴트 복사 생성자
사용자 정의 복사 생성자가 없을 때 자동 삽입 멤버변수 대 멤버변수의 복사를 진행
#include <iostream>
using namespace std;
class Point
{
int x, y;
public:
Point(int _x, int _y){
x=_x;
y=_y;
}
Point(const Point& p){ // 복사생성자
x=p.x; //p1 객체에 직접접근
y=p.y;
}
void ShowData(){
cout<<x<<' '<<y<<endl;
}
};
int main()
{
Point p1(10, 20);
Point p2(p1);
p1.ShowData();
p2.ShowData();
return 0;
}
컴파일러에 의해 자동 제공되어 에러가 없다. 쓰레기값도 들어가지 않아 멤버 대 멤버복사가 일어남을 알 수 있다. 복사생성자는 멤버변수의 갯수와 타입에 따라 정의되는 형태가 달라진다
#include <iostream>
using namespace std;
class Point
{
int x, y;
public:
Point(int _x, int _y){
x=_x;
y=_y;
}
// 생략해도 문제 없음
// Point(const Point& p){ // 복사생성자
// x=p.x; //p1 객체에 직접접근
// y=p.y;
// }
void ShowData(){
cout<<x<<' '<<y<<endl;
}
};
int main()
{
Point p1(10, 20);
Point p2(p1);
p1.ShowData();
p2.ShowData();
return 0;
}
쓰레기값이 출력되지 않습니당.
5-4 Deep Copy
디폴트 복사 생성자 복사 형태
얕은 복사 (Shallow Copy)
디폴트 복사 생성자의 문제점
얕은 복사에 의한 메모리 참조 오류
(좌)얕은복사. "KIM" 은 힙에 저장, (우)깊은복사
컴파일은 잘 되는데 런타임 에러가 발생한다.
#include <iostream>
#include <string.h>
using namespace std;
class Person
{
char* name; //name이 멤버지, 포인터가 가리키는 문자열이 멤버가 아니다
char* phone;
int age;
// Person 멤버는 두개의 포인터와 1개의 변수
public:
Person(const char* _name, const char* _phone, int _age);
// Person(const Person& p){
// // 디폴트 복사생성자, 멤버 대 멤버 복사
// 생략
// name=p.name;
// phone=p.phone;
// age=p.age;
// }
~Person();
void ShowData();
};
Person::Person(const char* _name, const char* _phone, int _age)
{
name=new char[strlen(_name)+1];
strcpy(name, _name);
phone=new char[strlen(_name)+1];
strcpy(phone, _phone);
age=_age;
}
Person::~Person(){
delete []name;
delete []phone;
}
void Person::ShowData()
{
cout<<"name:"<<name<<endl;
cout<<"phone:"<<phone<<endl;
cout<<"age:"<<age<<endl;
}
int main()
{
Person p1("KIM", "010-000-000", 22);
Person p2=p1; //Person p2(p1); 묵시적 변환, 복사생성자 호출
return 0;
}
p1, p2는 name이 가리키는 값을 동일하게 가리킨다.
그러나 메인함수가 끝나는 순간 p2, p1 객체가 사라지고 (객체가 생성되는 순서랑 반대로 사라진다, 스택형태의 성질) 소멸자가 호출되어 "KIM", "010...이 사라진다.
소멸자에서는 문자열을 해제하는데 name 포인터가 가리키는 문자열을 두 번 소멸하려고 해서 문제가 생긴 것. (이미 사라지고 없음) 하나의 메모리공간을 두 번 소멸하려 해서 문제 즉 p1이 완전히 복사된 게 아니다.
깊은 복사를 하는 복사생성자 예제
런타임에러가 발생하지 않는다. 내용은 같지만 다 복사가 된 것.
#include <iostream>
#include <string.h>
using namespace std;
class Person
{
char* name;
char* phone;
int age;
// Person 멤버는 두개의 포인터와 1개의 변수
public:
Person(const char* _name, const char* _phone, int _age);
// 깊은 복사를 하는 복사생성자
Person(const Person& p){
// 문자열 공간 할당
name = new char[strlen(p.name)+1]; //동적할당을 하니 소멸자의 명시적 제공이 필요할 것
strcpy(name, p.name);
phone = new char[strlen(p.phone)+1];
strcpy(phone, p.phone);
age=p.age;
}
~Person();
void ShowData();
};
Person::Person(const char* _name, const char* _phone, int _age)
{
name=new char[strlen(_name)+1];
strcpy(name, _name);
phone=new char[strlen(_name)+1];
strcpy(phone, _phone);
age=_age;
}
Person::~Person(){
delete []name;
delete []phone;
}
void Person::ShowData()
{
cout<<"name:"<<name<<endl;
cout<<"phone:"<<phone<<endl;
cout<<"age:"<<age<<endl;
}
int main()
{
Person p1("KIM", "010-000-000", 22);
Person p2=p1; //Person p2(p1); 묵시적 변환, 복사생성자 호출
p2.ShowData();
return 0;
}
5-5 복사 생성자의 호출 시기
복사 생성자 호출 형태 3가지
case 1. 기존에 생성된 객체로 새로운 객체 초기화 case 2. 함수 호출 시 객체를 값에 의해 전달 case 3. 함수 내에서 객체를 값에 의해 리턴
...생성자 내에서 동적할당 시 복사생성자 정의가 필요하다고 배웠다.
Q. 클래스 내에서 동적할당 시 복사생성자 정의가 필요할까? A. 프로그램 관점에서는 아니지만, 클래스 내에서 확장성 등의 이유로 복사생성자 정의가 필요하다. 프로그램이 아니라 클래스를 기준으로 복사생성자 여부를 결정짓게 된다.
case1. 기존에 생성된 객체로 새로운 객체 초기화
#include <iostream>
class AAA
{
int val;
public:
AAA(int i){ //i에 10이 전달되어 val=10
val=j;
}
AAA(const AAA& a){
cout<<"AAA(const A &a) 호출"<<endl;
val=a.val;
}
void ShowData(){
cout<<"val: "<<val<<endl;
}
};
int main()
{
AAA obj1(10);
AAA obj2=obj1; //복사생성자 호출
// 묵시적 변환됨 AAA obj2(obj1);
// obj1기존의 객체, obj2새객체
// 기존에 생성된 객체로 새로운 객체 초기화
return 0;
}
Case2. 함수 호출 시객체를 값에 의해 전달
기본자료형 샘플
int fct(int a)
{
// 인자가 전달되면서 a가 초기화된다
// b값을 받기 위해서는 a라는 메모리공간 할당
// b값이 전달되면서 a가 10으로 초기화
}
int main(){
int b=10;
fct(b);
}
// 1. 메모리공간 a 할당
// 2. b값이 전달되면서 초기화 (a는 10)
객체
// 매개변수 a는 메모리공간 할당, 생성자가 호출되어서 객체이다.
int function(AAA a) // 참조가 아니라 값에 의해 전달.
{
// a라는 이름으로 메모리공간 할당
// 전달값으로 초기화
// obj가 지닌 값 val이 다이렉트로 전달되어 30으로 채워지는 게 아님
// 객체의 경우 값의 초기화는 obj객체가 a객체의 생성자의 매개변수로 전달된다.
// a객체의 복사생성자를 호출하면서 obj가 전달되는 것.
a.ShowData();
}
int main(){
AAA obj(30);
function(obj);
return 0;
}
// 객체의 경우에는 a를 채우는 게 아니고
// 객체의 복사생성자를 호출하면서 복사하려는 대상 객체(obj)가 전달된다.
// 값의 대입은 복사생성자 호출을 통해 이루어짐...
case 3. 함수 내에서 객체를 값에 의해 리턴
AAA function(void)
{
AAA a(10);
return a;
// a라는 이름으로 객체 생성
// val은 10으로 초기화
// 메인함수 영역으로 값에 의해 a객체 리턴
}
int main()
{
function(); // a객체 생성
function().ShowData(); // a객체의 복사본이 메인함수 영역으로 리턴
// 클래스 객체의 경우 다이렉트로 집어넣는게 아니다.
// a객체의 복사본도 객체이다
// 1. a클래스 참조를 해서 a객체와 같은 형태로 메모리 공간 할당,
// 복사본 객체의 멤버변수 val은 쓰레기값으로 초기화된 상태.
// 2. a 객체의 복사본 객체의 복사생성자를 호출하면서 복사의 대상인 a 객체를 인자로 전달한다
// 복사생성자 내에서 멤버 대 멤버 복사가 이뤄짐
// 3. a 객체 멤버변수 val의 값으로 복사본객체의 val 값이 초기화
// 4. 메인함수로 리턴
reuturn 0;
}