728x90
728x90

 

 

abs() : 절대값
num=999
pnum=abs(num)
mnum=abs(-num)
print(pnum==mnum) #True

 

divmod() : 두 수를 나눈 몫과 나머지를 튜플형으로 반환
print(type(divmod(9,2)))
#<class 'tuple'>
print(divmod(9,2))
#(4, 1)

 

enumerate()  : 순서가 있는 컬렉션데이터를 입력받아 인덱스값을 포함해서 반환
topten=["애플", "구글", "마소", "아마존"]

for i, enterprise in enumerate(topten):
    print(i, enterprise)

 

map() : 함수와 컬렉션데이터를 입력받아 처리하는 함수. 처리한 결과를 반환
topten=["애플", "구글", "마소", "아마존"]

def company(x):return x+"기업"

mapresult=list(map(company,topten))
print(mapresult)

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

 

함수 정의

def magician_hat(a):
    if a=="동전":
        return "토끼"
    elif a=="손수건":
        return "비둘기"
        
print(magician_hat("동전"))
print(magician_hat("손수건"))

 

호출

def hello(a):
    while(a>0):
        print("hi")
        a-=1
hello(3) #hi hi hi
 def hello(a):
   for x in range(a):
       print("hi")
hello(3) #hi hi hi
def big(a, b);
    if(a>b) :
        return a 
    elif(a<b):
        return b 
    else:
        return "두 수는 같습니다"

print(big(5,5))

 

return은 함수의 값

def add(a,b):
    return a+b 
c= add(150,130)
    
print(add(150,180))
print(c)
print(type(c))

# 330
# 280
# <class 'int'>
def add_all(*args): #여러 값을 매개변수로 가진다
    total=0
    for i in args:
        total=total+i 
    return total

print(add_all(1,6,7)) #14

args 키워드는 튜플로 사용되서 인덱스 사용도 가능하다

def phone(*args):
    print("회원님의 전화번호는 "+args[2]+"입니다")
phone("나래", "강남구", "010-1111")
#회원의 전화는 010-1111입니다

 

함수의 설명서
#함수의 설명서
def cheerup(name):
    """
    이 함수는 응원의 메세지를 출력하는 함수입니다
    인수로는 응원할 사람의 이름을 입력하면 됩니다
    """
    print("{}님 힘내세요".format(name))
    
help(cheerup) #설명서 출력
cheerup("홍길동")

 

 

범위에 따른 값

def func():
    num=10
    print("함수 호출 시 변수 값은", num)

num=20
func()

print("함수 호출 이후 변수 값은", num)

#함수 호출 시 변수 값은  10
#함수 호출 이후 변수 값은 20

전역변수

age=29

def birthday():
    print(age, "번째 생일을 축하")

birthday()
print(age, "번째 생일을 축하")

지역변수

def birthday():
    age=29 #지역
    print(age, "번째 생일을 축하")

birthday()
print(age, "번째 생일을 축하") #에러

 

글로벌

age=29 #전역
def birthday():
    global age
    age=30
    print(age, "번째 생일을 축하")

birthday() 
print(age, "번째 생일을 축하")

#30 번째 생일을 축하
#30 번째 생일을 축하

 

함수 디폴트 값 지정

def chice_menu(beverage="coke", food="burger"):
    print("주문한 음식은 {}과 {}입니다".format(beverage,food))

chice_menu()
chice_menu("milk","toast")

 

키워드로 인수를 입력

def my_family(c3,c2,c1):
    print("우리집 막내는", c3, "입니다")

my_family(c1="찬희", c2="혜지", c3="혜원")
#우리집 막내는 혜원 입니다
def my_family(**child):
    print("우리집 첫째는", child["c1"], "입니다")

my_family(c1="찬희", c2="혜지", c3="혜원")

 

재귀함수

#선착순 n명
#인덱스 0, 1, ... n-1
num=int(input())
print("선착순", num,"명")

def first_come(num):
    person=["구준", "잔디", "우빈", "지후"]
    if num==0:
        print("끝")
    else:
        print(person[num-1])
        first_come(num-1)
        
first_come(num)

 

 

람다(익명함수)

함수의 이름을 가지지 않고 인수와 실행 기능만 넣는다.

def add(a,b):
    return a+b 
print(add(4,5)) #9

#람다
print((lambda a,b:a+b)(4,5)) #9

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

while() - 지정 조건이 참일 때까지 블록 구문을 불러온다

 

i=1
while i<5:
    print("{}번 반복".format(i))
    i=i+1
    #i++
i=1

while i<10:
    print("{} x {} = {}".format(2,i,2*i))
    i+=1
i=1

while True:
    print(i, "번째 출력")
    if i==10:
        break
    i+=1
i=0

while i<10:
    i+=1
    if i==3:
        continue
    print(i, "번쨰 출력")

 

 

컬렉션데이터 반복

Attendance=["김순희", "김영철", "최민규"]
index=0

while index<len(Attendance):
    print(Attendance[index])
    index += 1

 

 

for while() : ~하는 동안

Attendance=["김순희", "김영철", "최민규"]

for name in Attendance:
    print(name)

문자열은 반복문을 통해서 한글자씩 출력할 수 있다

for x in "python":
    print(x)
#p
#y
#t
#h
#o
#n

break : 반복문을 중단, continue : 한단계만 생략하고 다음 반복문 진행

Attendance=["김순희", "김영철", "최민규", "민지"]

for name in Attendance:
    print(name)
    if name=="최민규":
        break

Attendance=["김순희", "김영철", "최민규", "민지"]

for name in Attendance:
    
    if name=="최민규":
        continue #제외한 나머지가 출력
    print(name)

 

range

for x in range(2,6):
    print(x)
for x in range(1, 15, 2):
    print(x) #공차설정

 

for x in range(3):
    print(x) 
else :
    print("반복문 종료")

for x in range(3):
    if x==2:
        break
    print(x) 

else :
    print("반복문 종료") #반복문 루프가 마지막까지 확인하는 용도로도 쓴다

 

2중 for문

for x in range(2, 10):
    print("%d단" %x)
    for y in range(1,10):
        print("{}x{}={}".format(x,y,y*x))
728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

 실행코드는 블록(들여쓰기)로 표현한다.

 

if
switch=True
if switch:
    print("형광등 켜짐")
my_age = 20

if my_age < 20 :
     print("미성년")
if my_age >= 20 :
    print("성인")
a=5
b=3

if a>b:
    print("a가 b보다 크다")
    print("크다")
    #실행코드가 크면 블록 형태로 만든다
my_age = 6

if my_age < 20 :
     print("미성년")
     if my_age < 8:
        print("어린이")

 

 

if elif else
my_age = 70

if my_age < 20 :
     print("미성년")
elif 20<=my_age<70:
    print("성인이나 노인은 아님")
else:
    print("노인")
"""
160~   165 s사이즈
166~   175 m사이즈
175~   L사이즈 추천
"""
height=175
if 160<=height<=165:
    print("s사이즈")
elif 166<=height<=175 
    print("M사이즈")
else:
    print("L")

 

 

 and or
a=140
b=3
c=100
if a>b and a>c:
    print("a가 가장 크다")

lens = True
glasses=False
if lens or glasses:
    print("시력이 좋지 않다")

 

 

in, not in
animal_list=["개", "고양이", "토끼"]
animal="독수리"
print("개" not in animal_list) #False

if animal in animal_list:
    print("{}는 리스트에 있습니다".format(animal))

 

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
dictionary(사전)

특정 키에 맞는 value를 가지는 것들이 딕셔너리의 요소들

 

기본형

sd={
    "name":"윤",
    "직업":"편집자",
    "나이":20
}
print(sd)
print(type(sd))

#{'name': '윤', '직업': '편집자', '나이': 20}
#<class 'dict'>

 

person={
    "이름":"김",
    "나이":10,
    "집":False,
    "취미":{"잠","음악감상"}
}
print(person) #{'이름': '김', '나이': 10, '집': False, '취미': {'음악감상', '잠'}}
print(person["나이"])  #10
print(person[0])  #error

 

 

키, 값 출력

person={
    "이름":"김",
    "나이":10,
    "집":False,
    "취미":{"잠","음악감상"}
}

print(person.keys())
print(person.values())
#dict_keys(['이름', '나이', '집', '취미'])
#dict_values(['김', 10, False, {'잠', '음악감상'}])

 

 

추가

person={
    "이름":"김",
    "나이":10,
    "집":False,
    "취미":{"잠","음악감상"}
}

person["신장"]=170
person["주소"]="서울시"

print(person) 
#{'이름': '김', '나이': 10, '집': False, '취미': {'잠', '음악감상'}, '신장': 170, '주소': '서울시'}

 

 

항목을 튜플로 변환

person={
    "이름":"김",
    "나이":10,
    "집":False,
    "취미":{"잠","음악감상"}
}

print(person.items()) 
#dict_items([('이름', '김'), ('나이', 10), ('집', False), ('취미', {'음악감상', '잠'})])

 

 

중첩 딕셔너리

person={
    "김" : {"결혼":False, "나이":29},
    "박" : {"결혼":True, "나이":25}
}

print(person)  #{'김': {'결혼': False, '나이': 29}, '박': {'결혼': True, '나이': 25}}
person1={
        "이름":"김박",
        "나이":29
}
person2={
        "이름":"김민",
        "나이":28
}

person={
    "첫째" : person1,
    "둘째" : person2
}

print(person) #{'첫째': {'이름': '김박', '나이': 29}, '둘째': {'이름': '김민', '나이': 28}}

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

중복 요소가 없고 순서가 없고, 인덱스 관련 키워드는 사용하지 않으나 길이 등은 사용한다.
중괄호를 사용한다. (딕셔너리 데이터도 사용한다)

set1={1,2,3,4}
print(set1)
print(type(set1))
#{1, 2, 3, 4}
#<class 'set'>

 

공집합을 사용하는 방법

set1={}
print(set1)
print(type(set1)) #공집합은 딕셔너리라고 출력

s2=set()
print(type(s2)) 

#{}
#<class 'dict'>
#<class 'set'>

 

선별해 출력

 

set1={1,2,3}
print(1 in set1) #True

 

add() - 추가

set1={1,2,3}
set1.add(4)
print(set1)#{1, 2, 3, 4}

 

update() - 추가, 결합

set1={1,2,3}
set1.update([4,5,6])
print(set1) #{1, 2, 3, 4, 5, 6}
set1={1,2,3}
set2={4,5,6}
set1.update(set2)
print(set1)

 

union() - 새로운 곳에 결합

set1={1,2,3}
set2={4,5,6}
set3=set1.union(set2)
print(set3)

 

remove(), discard() - 집합의 항목 제거

차이점 : 삭제할 데이터가 있으면 에러 vs 에러 발생 x

set1={1,2,3,4} 
set1.discard(3)
set1.discard(5)
#set1.remove(5)  #error
print(set1)

 

clear() - 모든 값 지우기

set1={1,2,3,4} 
set1.clear()
print(set1) #set()

 

교집합 - 공통적인 항목을 모아놓은 집합

x={1,2,4,8}
y={1,2,4,8,16}
z=x.intersection(y)
print(z) #{8, 1, 2, 4}

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

turple은 생성하면서 값을 정해버리는 컬렉션 데이터 타입
수정이 불가능한 데이터 (사용 불가능 : sort(), append() 등...)

turple1=("홍길동", "이순신", "세종대왕", "유관순")
print(turple1)
print(type(turple1))

#('홍길동', '이순신', '세종대왕', '유관순')
#<class 'tuple'>
numtuple=(1,2,3,4,5)
nametuple=("임윤희", "장미영")
mixtuple=(1,"text",3.14,True)
print(numtuple)
print(nametuple)
print(mixtuple)

 



리스트 말고 튜플을 쓰는 이유

메모리의 효율성 (데이터사이즈) 때문이다.

 

import sys

numtuple = (1,2,3,4,5)
numlist = [1,2,3,4,5]
print(len(numtuple), len(numlist))
print("리스트 용량", sys.getsizeof(numlist))
print("튜플 용량", sys.getsizeof(numtuple))

#5 5
#리스트 용량 96
#튜플 용량 80

 

 

튜플과 리스트의 공통점

rainbow=("빨", "주", "노")

print(rainbow[1]) #주
print(rainbow[-1]) #노
print(rainbow[0:5]) #('빨', '주', '노')
print("빨" in rainbow) #True 
print(type("빨" in rainbow)) #class 'bool'

 

튜플을 리스트로 형변환시켜 수정한 후 다시 튜플로 형변환시키면 수정도 가능하다.

rainbow=("빨", "주", "검")

list_rainbow=list(rainbow)
list_rainbow[2]="노"
rainbow=tuple(list_rainbow)

print(rainbow) #('빨', '주', '노')

 

튜플의 연산

t1=(1,2,3)

t2=("하나", "둘", "셋")

print(t1+t2)
print(t1*2)

#(1, 2, 3, '하나', '둘', '셋')
#(1, 2, 3, 1, 2, 3)

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

1. 리스트

데이터들을 하나의 묶음으로 나열한 것 (점심메뉴 리스트)

 

기본형

lunch=["샤브샤브", "물냉면", "케밥", "불고기", "연어덮밥", "돈가스"]
# 인덱스 0 , 1, ...

 

예제

attendance=["김", "이", "박", 1, 1, 2, 2, True, 1==5, 2+2]
print(attendance);
print(attendance[2]);
print(attendance[-1]); 

#['김', '이', '박', 1, 1, 2, 2, True, False, 4]
#박
#4 뒤에서부터 센다

 

리스트 길이 측정 / 데이터 타입 확인 / 범위 지정으로 특정 인덱스 추출

list_=["홍", "윤", "이", "임"]
print(len(list_)) #리스트 길이 측정
print(type(list_)) #데이터 타입 확인
print(list_[1:2]) #시작부터 끝 인덱스 직전까지 출력
print(list_[:2]) #처음부터 인덱스 직전까지 출력

#4
#<class 'list'>
#['윤']
#['홍', '윤']

 

리스트 값 변경

bird_list=["딱따구리", "까치", "부엉이"]
bird_list[1]="참새"
print(bird_list) #['딱따구리', '참새', '부엉이']

 

리스트 값 삽입, 추가 (insert, append)

 

animal=["dog", "cat", "turtle", "lion"]
animal.insert(0, "bear")
print(animal) #['bear', 'dog', 'cat', 'turtle', 'lion']
animal.append("tiger")
print(animal) #['bear', 'dog', 'cat', 'turtle', 'lion', 'tiger']

 

extend() : 리스트에 배열 추가
animal=["dog", "cat", "turtle", "lion"]
bird=["독수리", "부엉이", "참새"]
animal.extend(bird) #넓히다, 확장하다
print(animal) #['dog', 'cat', 'turtle', 'lion', '독수리', '부엉이', '참새']

 

extend와 append 차이점 예제

animal=["dog", "cat", "turtle", "lion"]
bird=["독수리", "부엉이", "참새"]
animal.append(bird) #리스트 자체가 4번째 요소로 들어감
print(animal) #['dog', 'cat', 'turtle', 'lion', ['독수리', '부엉이', '참새']]

 

리스트 연산

animal=["dog", "cat", "turtle", "lion"]
bird=["독수리", "부엉이", "참새"]
 
print(animal+bird) #['dog', 'cat', 'turtle', 'lion', '독수리', '부엉이', '참새']

 

리스트 제거

animal=["dog", "cat", "turtle", "lion"]
bird=["독수리", "부엉이", "참새"]

animal.remove("lion")
animal.pop(0)
animal.pop() #가장 마지막 인덱스 요소가 사라진다.
print(animal)
animal=["dog", "cat", "turtle", "lion"]
bird=["독수리", "부엉이", "참새"]
del animal[2]
#del animal #전체가 삭제되어 error
print(animal) #['dog', 'cat', 'lion']

 

clear() : 리스트 비우기
animal=["dog", "cat", "turtle", "lion"]
bird=["독수리", "부엉이", "참새"]
animal.clear()
print(animal) #[]

 

sort() : 리스트 정렬
eng=["b", "a", "d", "c", "e"]
num=[4,5,600,-4,0]

eng.sort()
#eng.sort(reverse=True) 오름차순으로 정렬
#eng.sort(reverse=False) 내림차순으로 정렬
num.sort()

print(eng)
print(num)

#['a', 'b', 'c', 'd', 'e']
#[-4, 0, 4, 5, 600]

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

01 const

const 객체
const int num=10; //변수의 상수화
const SoSimple sim(20); //객체의 상수화

변수를 상수화하듯 객체도 상수화 할 수 있다.
객체에 const 선언이 붙으면 이 객체의 대상으로 const 멤버함수만 호출이 가능하다.
객체의 데이터 변경을 허용하지 않기 때문이다.  아예 호출부터 허용을 막아 안전하게 코드를 구성하는 것.

const 객체의 특성 확인 예제
#include <iostream>
using namespace std;

class SoSimple
{
private:
    int num;
public:
    SoSimple(int n):num(n)
    {   }
    SoSimple& AddNum(int n){
        num+=n;
        return *this;
    }
    void ShowData() const { //내부에서 출력만 할 경우 무조건 const
        cout<<"num: "<<num<<endl;
    }
};

int main()
{
    const SoSimple obj(7); //const 객체를 생성
    // obj.AddNum(20); // const 함수가 아니어서 호출이 불가
    obj.ShowData(); //const 함수여서 const 객체를 대상으로 호출이 가능

    return 0;
}

멤버변수에 저장된 값을 수정하지 않는 함수는 가급적 const로 선언해서 const 객체에서도 호출이 가능하도록 한다.

 

const와 함수 오버로딩

오버로딩 조건은 매개변수의 수나 자료형이 다른 것 + const의 선언유무

void SimpleFunc() { ... }
void SimpleFunc() const { ... }

 

오버로딩된 const 함수가 호출되는 예

#include <iostream>
using namespace std;

class SoSimple
{
private:
    int num;
public:
    SoSimple(int n):num(n)
    {   }
    SoSimple& AddNum(int n){
        num+=n;
        return *this;
    }
    void SimpleFunc()
    {
        cout<<"SimpleFunc: "<<num<<endl;
    }
    void SimpleFunc() const
    {
        cout<<"const SimpleFunc: "<<num<<endl;
    }
};

void YourFunc(const SoSimple &obj){
    // 전달 인자를 const 참조자로 받는다
    // 참조자를 이용한 함수호출의 결과로 const 멤버함수가 호출된다
    obj.SimpleFunc();
}

int main()
{
    SoSimple obj1(2);
    const SoSimple obj2(7);
    
    obj1.SimpleFunc();
    obj2.SimpleFunc();
    
    YourFunc(obj1);
    YourFunc(obj2);
    
    return 0;
}

 

 

 

02 클래스와 함수에 대한 friend 선언

클래스의 friend 선언

A클래스가 B클래스를 대상으로 friend 선언을 하면 B클래스는 A클래스의 private 멤버에 직접 접근이 가능하며
그 반대도 전제조건에 접근하려는 클래스를 대상으로 friend 선언을 해줘야 된다. 

friend 선언은 클래스 내 어디든 있을 수 있다 - private, public 영역 어디든..

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

class Girl;
class Boy
{
private:
    int height;
    friend class Girl;
public:
    Boy(int len) : height(len)
    { }
    void ShowYourFriendInfo(Girl &frn);
};

class Girl
{
private:
    char phNum[20];
public:
    Girl(const char* num){
        strcpy(phNum, num);
    }
    void ShowYourFriendInfo(Boy &frn);
    friend class Boy; //Boy클래스에 대한 friend 선언
};

void Boy::ShowYourFriendInfo(Girl &frn){
    cout<<"Her phone num: "<<frn.phNum<<endl;
}
void Girl::ShowYourFriendInfo(Boy &frn){
    cout<<"His height: "<<frn.height<<endl;
}

int main()
{
    Boy boy(170);
    Girl girl("010-1234");
    boy.ShowYourFriendInfo(girl);
    girl.ShowYourFriendInfo(boy);
    return 0;
}

 

* 단점 : 정보은닉을 무너뜨리는 문법이다. 오로지 연산자 오버로딩 시 사용한다.

 

함수의 friend 선언 

전역 함수를 대상으로도, 클래스의 멤버함수를 대상으로도 friend 선언이 가능하다.
자신이 선언된 클래스의 private 영역에 접근이 가능하다.

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

class Point;

class PointOP
{
private:
    int opcnt;
public:
    PointOP(): opcnt(0)
    {   }
    
    Point PointAdd(const Point&, const Point&);
    Point PointSub(const Point&, const Point&);
    ~PointOP(){
        cout<<"Operation times: "<<opcnt<<endl;
    }
};

class Point{
private:
    int x;
    int y;
public:
    Point(const int &xpos, const int &ypos): x(xpos), y(ypos)
    {   }
    friend Point PointOP::PointAdd(const Point&, const Point&);
    friend Point PointOP::PointSub(const Point&, const Point&);
    friend void ShowPointPos(const Point&);
};

Point PointOP::PointAdd(const Point& pnt1, const Point& pnt2)
{
    opcnt++;
    return Point(pnt1.x+pnt2.x, pnt1.y+pnt2.y); //friend선언으로 private멤버에 접근이 가능
}

Point PointOP::PointSub(const Point& pnt1, const Point& pnt2)
{
    opcnt++;
    return Point(pnt1.x-pnt2.x, pnt1.y-pnt2.y);
}

int main()
{
    Point pos1(1,2);
    Point pos2(2,4);
    PointOP op;
    
    ShowPointPos(op.PointAdd(pos1, pos2));
    ShowPointPos(op.PointSub(pos2, pos1));
    
    return 0;
}

void ShowPointPos(const Point& pos)
{
    cout<<"x: "<<pos.x<<", ";
    cout<<"y: "<<pos.y<<endl;
}

 

 

 

 

03. C++에서의 static

C++에서는 멤버변수와 멤버함수에 static 선언을 추가할 수 있다.

* C언어 복습
전역변수에 선언된 static의 의미 : 선언된 파일 내에서만 참조를 허용하겠다는 의미
함수 내에 선언된 static의 의미 : 한번만 초기화되고 지역변수와 달리 함수를 빠져나가도 소멸되지 않는다

static변수 : visible한 범위는 블록 내이지만 메모리는 프로그램이 종료가 될 때 까지 해제가 되지 않는다
static함수 : https://dojang.io/mod/page/view.php?id=691

 

#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;
}

 

 

전역변수가 필요한 상황
#include <iostream>
using namespace std;

int simObjCnt=0;
int cmxObjCnt=0;

class SoSimple{
public:
    SoSimple(){
        simObjCnt++;
        cout<<simObjCnt<<"번째 SoSimple 객체"<<endl;
    }
 };
 
class SoComplex
{
public:
    SoComplex(){
        cmxObjCnt++;
        cout<<cmxObjCnt<<"번째 SoComplex 객체"<<endl;
    }
    SoComplex(SoComplex &copy){
        cmxObjCnt++;
        cout<<cmxObjCnt<<"번째 SoComplex 객체"<<endl;
    }
};

int main()
{
    SoSimple sim1;
    SoSimple sim2;
    
    SoComplex com1;
    SoComplex com2=com1;
    SoComplex();
    return 0;
}

 

전역변수는 어디에서든 접근이 가능하여 문제를 일으킬 소지가 있다. simObjCnt와 cmxObjCnt를 SoSimple, SoCimplex 클래스의 static 멤버로 두면 문제 발생을 예방할 수 있다.

 

static멤버변수 (클래스 변수)

일반 멤버변수와 다르게 클래스당 하나씩만 생성된다.
일반 지역변수처럼 객체가 생성될 때마다 함께 생성되어 객체별로 유지되는 변수가 아니다.
객체 생성여부와 별개로 메모리 공간에 하나만 할당되어 공유되는 변수

위의 코드를 static 변수로 바꾸었다.

#include <iostream>
using namespace std;

class SoSimple{
private:
    static int simObjCnt; //클래스 변수
public:
    SoSimple(){
        simObjCnt++;
        cout<<simObjCnt<<"번째 SoSimple 객체"<<endl;
    }
 };
 
class SoComplex
{
private:
    static int cmxObjCnt; //클래스 변수
public:
    SoComplex(){
        cmxObjCnt++;
        cout<<cmxObjCnt<<"번째 SoComplex 객체"<<endl;
    }
    SoComplex(SoComplex &copy){
        cmxObjCnt++;
        cout<<cmxObjCnt<<"번째 SoComplex 객체"<<endl;
    }
};

int SoSimple::simObjCnt=0; //static 멤버변수의 초기화
int SoComplex::cmxObjCnt=0; //static 멤버변수의 초기화

int main()
{
    SoSimple sim1;
    SoSimple sim2;
    
    SoComplex com1;
    SoComplex com2=com1;
    SoComplex();
    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의 멤버변수에 접근하는 듯한 혼동 여지가 있어 첫번째 문장처럼 클래스의 이름을 사용해서 접근하는 것을 추천한다. 

SoSimple::simObjCont;

// 헷갈린다
sim1.simObjCnt;
sim2.simObjCnt;

 

static 멤버함수

선언된 클래스의 모든 객체가 공유한다
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;
}

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
결과적으로 같은 초기화 방식
//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를 삽입한다.

// ClassInit.cpp
#include <iostream>
using namespace std;

class SoSimple{
private:
    int num1;
    int num2;
public:
    SoSimple(int n1, int n2)
        :num1(n1), num2(n2)
    {
        // empty
    }
    
    // 복사생성자
    SoSimple(SoSimple &copy) 
        :num1(copy.num1), num2(copy.num2) 
    {
        // SoSimple객체를 인자로 받는 생성자
        // 이니셜라이저를 이용해 멤버 대 멤버 복사 
        cout<<"Called SomSimple(SoSimple &copy)"<<endl;
    }
    void ShowSimpleData(){
        cout<<num1<<endl;
        cout<<num2<<endl;
    }
};

int main()
{
    SoSimple sim1(15, 30);
    cout<<"생성 및 초기화 직전"<<endl;
    SoSimple sim2=sim1; //SoSimple sim2(sim1);으로 변환
    cout<<"생성 및 초기화 직후"<<endl;
    sim2.ShowSimpleData(); 
    return 0;
}

 

 

디폴트 복사 생성자

복사 생성자 없이도 오류가 일어나지 않는다. 
복사 생성자를 정의하지 않으면 멤버 대 멤버의 복사를 진행하는 디폴트 복사 생성자가 자동으로 들어간다.

아래 두 클래스 정의는 동일하다.

class SoSimple{
private:
    int num1;
    int num2;
public:
    SoSimple(int n1, int n2)
        :num1(n1), num2(n2)
    {     }
};
class SoSimple{
private:
    int num1;
    int num2;
public:
    SoSimple(int n1, int n2)
        :num1(n1), num2(n2)
    {     }
};    
     
    SoSimple(SoSimple &copy) 
        :num1(copy.num1), num2(copy.num2) 
    {     }
};

 

반드시 복사생성자를 정의해야 하는 경우도 있다.(나중에 설명)

 

 

변환에 의한 초기화 키워드 explicit로 막기

묵시적변환이 일어나서 복사생성자가 호출되는데, 복사생성자의 묵시적 호출을 막는 방법이다.
연산자를 이용한 객체의 생성 및 초기화가 불가능하다.

 explicit SoSimple(const SoSimple &copy)
 	:num1(copy.num1), num2(copy.num2)
{     }

묵시적변환이 많이 발생하면 코드의 결과를 예측하기 어렵기 때문에 explicit 키워드로 명확하게 코드를 하기 위해 자주 사용한다.

복사생성자 외에도 전달 인자가 하나인 생성자가 있어도 묵시적변환이 발생한다.

class A{
private:
	int num;
public:
	AAA(int n):num(n){    }
	. . . . . .
};

explicit 키워드가 생성자에 선언되면, 다음의 형태로 객체를 생성할 수 밖에 없다.

AAA obj(3);

복사생성자의 매개변수 선언에 const는 필수가 아니지만 참조형의 선언을 의미하는 &는 반드시 삽입해야 한다.
& 선언이 없다면 복사생성자 호출은 무한루프에 빠져버려 컴파일러가 미리 에러를 출력한다.

 

 

깊은 복사, 얕은 복사

디폴트 복사생성자에 의한 멤버 대 멤버 복사는 얕은 복사(Shallow copy)로 멤버변수가 힙에 메모리 공간을 참조하는 경우 문제가 된다.

디폴트 복사 생성자의 문제점
// ShallowCopyError.cpp
// 디버그 모드로 컴파일, 실행해야 함
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

using namespace std;
class Person
{
private:
	char* name;
	int age;
public:
	Person(const char* myname, int myage) {
		int len = strlen(myname) + 1;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	}
	void ShowPersonInfo() const {
		cout << "이름 : " << name << endl;
		cout << "나이 : " << age<< endl;
	}
	~Person() {
		delete []name;
		cout << "소멸자 호출!" << age << endl;
	}
};
int main(void) {
	Person man1("Lee", 29);
	Person man2 = man1;
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();
	return 0;
}

(좌) 경고문 (우) 얕은복사의 결과

 

man2가 생성되면서 디폴트 복사 생성자 호출되고, 디폴트 복사 생성자는 멤버 대 멤버의 단순 복사를 하여 하나의 문자열을 두 객체가 참조하는 형태이기 때문에 객체 소멸과정에서 문제 발생

delete []name;

man1객체의 소멸자가 실행되어 소멸되어야 하는데 참조문자열이 man2 소멸자가 호출되며 소멸되어 문제가 된다.

 

깊은 복사(deep copy)를 위한 복사생성자의 정의

멤버뿐만 아니라 포인터로 참조하는 대상까지 복사

Person(const Person& copy):age(copy.age)
{
	name=new char[strlen(copy.name)+1];
	strcpy(name, copy.name);
}

위 생성자를 얕은복사 예제에 추가해준다.
멤버변수 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; //반환되는 값을 메모리 공간에 저장해뒀기에 출력도 가능하다.
	.....
}

출력을 위해서는 값을 참조할 수 있어야 하고, 참조가 가능하려면 메모리 공간에 저장되어있어야 된다.
함수가 값을 반환하면 별도의 메모리 공간이 할당되고, 이 공간에 반환값이 저장된다. (반환값으로 초기화된다.)

 

* 변수 말고 객체, 함수도 마찬가지

객체도 마찬가지로 객체가 생성되면서 (메모리공간이 할당되면서)초기화가 이뤄진다.
함수호출도 마찬가지로 객체가 생성되면서 전달되는 인자로 초기화된다.

SoSimple obj2=obj1;
SoSimple SimpleFuncObj(SoSimple ob)
{
 .....
}
int main(void)
{
	SoSimple obj;
	SimpleFuncObj(obj);
	.....
}

SimpleFuncObjc 함수가 호출되는 순간, 매개변수로 선언된 ob 객체가 생성되고(메모리공간이 할당) 
인자로 전달된 obj 객체로 초기화된다.(메모리 공간이 할당되면서 동시에 초기화)

SoSimple SimpleFuncObj(SoSimple ob)
{
 	.....
	return ob; //반환하는 순간 메모리 공간이 할당되면서 동시에 초기화
	// return문이 실행되는 순간 SoSimple 객체를 위한 메모리공간이 할당되고,
	// 이 공간에 할당된 객체는 반환되는 객체 ob의 내용으로 초기화
}

 

 

할당 이후, 복사생성자를 통한 초기화

객체가 생성 및 초기화될 때 초기화는 멤버 대 멤버가 복사되는 형태로 이뤄져야 하므로 복사생성자를 호출하는 방식으로 초기화를 진행한다.

#include <iostream>
using namespace std;

class SoSimple
{
private:
    int num;
public:
    SoSimple(int n)
        :num(n)
    { }
    SoSimple(const SoSimple& copy) 
        : num(copy.num)
    { 
        cout<<"Called SoSimple(const SoSimple& copy)"<<endl; 
    }
    void ShowData()
    {
        cout<<"num: "<<num<<endl;
    }
};
void SimpleFuncObj(SoSimple ob)
{
    ob.ShowData();
}
int main()
{
    SoSimple obj(7);
    cout<<"함수 호출 전"<<endl;
    SimpleFuncObj(obj); //함수를 호출하면서 객체 obj를 인자로 전달
    // 매개변수 ob의 복사 생성자가 호출되면서 인자로 obj가 전달된다
    
    cout<<"함수 호출 후"<<endl;
    return 0;
}

 

Q. 복사 생성자의 호출주체는 어느쪽일까?
obj 객체의 복사 생성자 호출 vs ob 객체의 복사 생성자 호출
A. ob객체는 obj 객체로 초기화된다. ob객체의 복사 생성자가 호출되면서 obj 객체가 인자로 전달되어야 한다.

 

 

복사생성자가 호출되는 세 번째 경우

* Point : 임시객체의 존재 알아보기

#include <iostream>
using namespace std;

class SoSimple
{
private:
    int num;
public:
    SoSimple(int n)
        :num(n)
    { }
    SoSimple(const SoSimple& copy) 
        : num(copy.num)
    { 
        cout<<"Called SoSimple(const SoSimple& copy)"<<endl; 
    }
    SoSimple& AddNum(int n) //참조형을 반환하는 함수
    {
        num+=n;
        return *this; //문장을 실행하는 객체 자신을 반환, 참조값을 반환
    }
    void ShowData()
    {
        cout<<"num: "<<num<<endl;
    }
};

SoSimple SimpleFuncObj(SoSimple ob) 
{ 
	cout<<"return 이전"<<endl;
	return ob; // 반환형이 참조형이 아니어서 객체의 복사본이 만들어지면서 반환이 진행	
	
	// 지역적으로 선언된 객체 ob는 소멸되고 obj객체와 임시객체만 남는다
	// obj객체가 전달되며 ob의 복사생성자가 호출되어 ob객체가 생기고
	// ob객체가 전달되며 임시객체의 복사생성자가 호출된다
} 

/** 
* @mainpage 메인페이지
* @brief 232쪽 프로그램 실행
* @details 임시객체의 존재 확인하기
*/
int main() {
	SoSimple obj(7); 
	SimpleFuncObj(obj).AddNum(30).ShowData(); //임시객체에 저장한 값 
	obj.ShowData(); //obj 객체에 저장된 값

	return 0; 
}

 

반환형이 참조형이 아니어서 객체의 복사본이 만들어지면서 반환이 진행되는데
지역적으로 선언된 객체 ob는 소멸되고 obj객체와 임시객체만 남는다.

obj객체가 전달되며 ob의 복사생성자가 호출되어 ob객체가 생기고
ob객체가 전달되며 임시객체의 복사생성자가 호출된다

호출결과로 반환된 임시객체를 대상으로 AddNum함수를 호출하고, (임시객체의 num은 37이 된다)
ShowData함수를 호출한다.

 

 

# 임시 변수 (출처)
 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;
}
// ReturnObjDeadTime.cpp 
#include <iostream>
#include "SoSImple.h"
using namespace std;

SoSimple SimpleFuncObj(SoSimple ob)
{
	cout << "Param ADR: " << &ob << endl;
	return ob;
}

int main()
{
	SoSimple obj(7);
	SimpleFuncObj(obj);

	cout << endl;
	SoSimple tempRef = SimpleFuncObj(obj); 
	//추가로 새 객체를 생성하지 않고
	//반환되는 임시객체에 tempRef이름을 할당

	cout << "Return obj " << &tempRef << endl; 
	return 0;
}

 

 

obj 생성
함수호출로 매개변수 ob 생성
Param ADR 출력 - ob의 주소
반환으로 인한 임시객체 생성
매개변수 ob 소멸
반환으로 생성된 임시객체 소멸

함수호출로 매개변수 ob 생성
Param ADR 출력 - ob의 주소
반환으로 인한 임시객체 생성
매개변수 ob 소멸
새 객체가 생성되지 않고 임시객체에 할당됨을 알 수 있는 결과. 임시객체의 주소값과 같음
임시객체소멸
obj 소멸

cout << "Return obj " << &tempRef << endl; //임시객체의 주소값이 반환된다

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
복사생성자의 의미, 필요성 , 정의

 

5-1 C++ & C 스타일 초기화

 

두 가지 형태의 초기화 :: InitStyle.cpp, InitStyle2.cpp

C++ 에서는 아래로 묵시적변환이 되서 둘은 같은 것이다.

// 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;
}
728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v