728x90
728x90
:  는 초기화 리스트 initialize list 

객체를 초기화 할 때 사용
상수를 초기화 할 때, 상속 시 부모객체의 생성자를 호출하고 싶을 때 사용한다

public:
    Myclass(int pn) : n(pn){
	}

클래스의 각 멤버를 생성자에서 초기화 할 때 객체의 멤버를 초기화할 수 있다
클래스가 멤버로 다른 객체를 가질 때, 초기화 리스트를 사용한 초기화방법이 함수 호출 횟수가 적으므로 쾌적해진다

public:
    Myclass(int pn) : n(pn){
	}

 

 

::  는 더블콜론 연산자 (Scope Resolution Operator)

중괄호를 벗어난 범위 밖에서 참조 하고 싶을 때 사용 

class b
{
 public:
          static const int gn = 0;
          void fun(int);
 }
void b::fun(int n)
{
}
void main()
{
   int n =   b::gn;
}

 

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90

객체 배열 및 객체 포인터 배열은 C언어의 구조체 배열, 구조체 포인터 배열과 비슷하다.

 

객체 배열

객체 기반의 배열 선언하는 법을 본다.

SomSimple arr[10];
// SoSimple은 클래스 이름

 

동적 할당 객체 배열 선언

열 개의 SoSimple 객체가 모여서 배열을 구성하는 형태이다. 
구조체 배열과 형태가 같다.
배열을 선언하는 경우도 생성자는 호출된다

SoSimple * ptrArr=new SomSimple[10];

배열 선언과정에서는 호출할 생성자를 별도로 명시하지 못한다. (생성자에 인자를 전달하지 못한다)
위 형태로 배열이 생성되려면 SoSimple() { .... } 형태의 생성자가 반드시 있어야 한다.

SoSimple() { .... }

배열 선언 이후 각각의 요소를 원하는 값으로 초기화시키려면 일일히 초기화 과정을 거쳐야 한다.
이전 예제의 Person 클래스 기반의 예제로 객체 배열을 살펴본다.

 

객체 배열 예제

객체 배열 생성 시 void형 생성자가 호출되며, 배열 소멸시에도 그 배열을 구성하는 객체의 소멸자가 호출된다.

// ObjArr.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> 
#include<cstring> 

using namespace std;

class Person
{
private:
	char* name;
	int age;
public:
	/*Person(char * myname, int myage) 
	{
		int len = strlen(myname) + 1;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	}*/
	Person() //배열 생성 시 필요한 생성자
	{
		name = NULL;
		age = 0;
		cout << "called Person()" << endl;
	}
	void SetPersonInfo(char* myname, int myage) //원하는 데이터로의 초기화 목적
	{
		name = myname;
		age = myage;
	}
	void ShowPersonInfo() const
	{
		cout << "이름 : " << name<<", ";
		cout << "나이 : " << age << endl;
	}
	~Person()
	{
		delete[]name;
		cout << "called destructor!" << endl;
	}
};
int main(void)
{
	Person parr[3]; //Person 3번
	char namestr[100];
	char* strptr;
	int age;
	int len;

	for (int i = 0; i < 3; i++) { //정보를 입력받아 객체 초기화
		cout << "이름 : ";
		cin >> namestr;
		cout << "나이 : ";
		cin >> age;
		len = strlen(namestr) + 1;
		strptr = new char[len];
		strcpy(strptr, namestr);
		parr[i].SetPersonInfo(strptr, age);
	}
	parr[0].ShowPersonInfo();
	parr[1].ShowPersonInfo();
	parr[2].ShowPersonInfo();

	return 0;
}

 

 

 

객체 포인터 배열

객체 배열은 객체로 이뤄진 배열,
객체 포인터 배열은 객체의 주소값 저장이 가능한 포인터 변수로 이뤄진 배열이다.

객체 포인터 배열 예제
// ObjPtrArr.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> 
#include<cstring> 

using namespace std;

class Person
{
private:
	char* name;
	int age;
public:
	Person(char* myname, int myage) //포인터 배열 생성 시 필요한 생성자
	{
		int len = strlen(myname) + 1;
		name = new char[len];
		strcpy(name, myname);
		age = myage;
	} 
	Person() //배열 생성 시 필요한 생성자
	{
		name = NULL;
		age = 0;
		cout << "called Person()" << endl;
	}
	void SetPersonInfo(char* myname, int myage) //원하는 데이터로의 초기화 목적
	{
		name = myname;
		age = myage;
	}
	void ShowPersonInfo() const
	{
		cout << "이름 : " << name <<", ";
		cout << "나이 : " << age << endl;
	}
	~Person()
	{
		delete[]name;
		cout << "called destructor!" << endl;
	}
};
int main(void)
{
	Person* parr[3]; // 객체의 주소값 3개를 저장할 수 있는 포인터 배열 선언
	char namestr[100]; 
	int age; 

	for (int i = 0; i < 3; i++) { //정보를 입력받아 객체 초기화
		cout << "이름 : ";
		cin >> namestr;
		cout << "나이 : ";
		cin >> age; 
	
		parr[i] = new Person(namestr, age); //객체를 생성 후 객체의 주소값을 배열에 저장
	}
	parr[0]->ShowPersonInfo();
	parr[1]->ShowPersonInfo();
	parr[2]->ShowPersonInfo();
	delete parr[0];
	delete parr[1];
	delete parr[2];
	return 0;
}

 

parr[i]=new Person(namestr, age); 객체를 생성 후 객체의 주소값을 배열에 저장
객체를 저장할 때에는 두가지 중 하나로 저장한다. (객체를 저장 vs 객체의 주소값을 저장)

 

 

this 포인터

멤버함수 내에서는 this 포인터를 사용할 수 있다.
this 포인터는 객체 자신을 가리키는 용도로 사용되는 포인터이다.

this 포인터 이해 예제

this는 객체자신의 주소값을 의미한다.
this포인터는 주소값과 자료형이 정해지지 않은 포인터다.

// PointerThis.cpp
#define _CRT_SECURE_NO_WARNINGS
#include<iostream> 
#include<cstring> 
using namespace std;

class SoSimple
{
private:
	int num;
public:
	SoSimple(int n) : num(n)
	{
		cout << "num=" << num << ", ";
		cout << "address=" << this << endl;
	}
	void ShowSimpleData()
	{
		cout << num << endl;
	}
	SoSimple* GetThisPointer() //반환형이 포인터
	{
		return this; //문장을 실행하는 객체의 포인터 반환
	}
};
int main(void)
{
	SoSimple sim1(100);
	SoSimple* ptr1 = sim1.GetThisPointer(); //sim1 객체의 주소 값 저장
    // 객체 sim1에 의해 반환된 this는 SoSimple의 포인터이므로 SoSimple형 포인터변수에 저장
    
	cout << ptr1 << ", ";  //ptr1에 저장된 주소값 출력
	ptr1->ShowSimpleData();  
	
	SoSimple sim2(200);
	SoSimple* ptr2 = sim2.GetThisPointer(); //sim2 객체의 주소 값 저장
	cout << ptr2 << ", ";
	ptr2->ShowSimpleData();
	
	return 0;
}

 

 

this 포인터의 활용

같은 이름의 함수 매개변수, 객체 멤버변수를 this포인터를 활용하면 의도대로 사용할 수 있다.
객체를 참조하는 포인터 this의 특성 때문이다.

this->num=207;
// PointerThis.cpp
#include<iostream> 
using namespace std;

class TwoNumber
{
private:
	int num1;
	int num2;
public:
	TwoNumber(int num1, int num2) 
	{
	// 멤버변수 각각에 매개변수 각각을 저장한다.
		this->num1 = num1;
		this->num2 = num2;
	}
	// 멤버 이니셜라이저는 this 포인터를 사용할 수 없는 대신
	// 저장하는 변수는 멤버변수로, 저장되는 값은 매개변수로 인식해서
	// 이렇게도 사용할 수 있다. 
	/* TwoNumber(int num1, int num2)
	* : num1(num1), num2(num2)
	{ 
		//empty
	}*/
	void ShowTwoNumber()
	{
		cout << this->num1 << endl; //this포인터는 생략 가능
		cout << this->num2 << endl;
	}
};
int main(void)
{
	TwoNumber two(2, 4);
	two.ShowTwoNumber();
	return 0;
 }

 

점심먹고 여기부터

Self-Reference의 반환

Self-Reference는 객체 자신을 참조할 수 있는 참조자이다.
this포인터를 사용해서 객체가 자신의 참조에 사용할 수 있는 참조자의 반환문을 구성할 수 있다.

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

class SelfRef
{
private:
	int num;
public:
	SelfRef(int n) : num(n)
	{
		cout << "객체생성" << endl;
	}
	SelfRef& Adder(int n) 
	{
		num += n;
		return *this; //객체 자신의 포인터가 아닌 객체 자신을 반환
		// this가 객체를 가리키는 포인터니까
		// this가 가리키는 곳 (포인터의 포인터) 즉 객체자신.
	}
	SelfRef& ShowTwoNumber()
	{
		cout << num << endl;
		return *this;
	}
};

int main(void)
{
	SelfRef obj(3);
	SelfRef& ref = obj.Adder(2); //참조자므로 obj에 별명붙인거

	obj.ShowTwoNumber();
	ref.ShowTwoNumber(); //그래서 같은 값

	ref.Adder(1).ShowTwoNumber().Adder(2).ShowTwoNumber(); //객체의 참조값을 반환해서 함수를 호출해서 가능
	return 0;
}

 

 

참조의 정보(참조 값)에 대한 이해

변수 num을 참조할 수 있는 참조 값이 참조자 ref에 전달되어, ref가 변수 num 을 참조하게 된다.

int main(void)
{
	int num=7;
	int &ref=num; //변수 num을 참조할 수 있는 참조의 정보가 전달된다.
	...
}

 

728x90
728x90

'C, C++ > 열혈 C++ 프로그래밍' 카테고리의 다른 글

05.복사생성자(copy constructor) (책)  (0) 2021.09.29
05.복사생성자  (0) 2021.09.28
04-03. 생성자와 소멸자(책)  (0) 2021.09.24
04. 클래스의 완성 연습문제  (0) 2021.09.23
3. 클래스의 기본  (0) 2021.09.15
블로그 이미지

coding-restaurant

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

,
728x90
728x90

이제까지 객체 생성, 객체의 멤버변수 초기화를 목적으로 InitMembers라는 이름의 함수를 정의하고 호출했고, 정보은닉을 목적으로 멤버변수들은 private으로 선언해왔다. 생성자를 이용하면 생성과 동시에 초기화 할 수 있다. 

class SimpleClass
{
private:
    int num;
public:
    SimpelClass(int n) //constructor(생성자)
    { 
        num = n;
    }
    int GetNum() const {
        return num;
    }
}

위에서 클래스의 이름과 함수의 이름이 동일, 반환형이 선언되어 있지 않고 반환하지 않는 함수가 생성자이다

 

생성자는 이런 함수이다.

클래스의 이름과 함수의 이름이 동일하다.
반환형이 선언되어 있지 않고 반환하지 않는다.
객체 생성 시 딱 1번 호출된다. 
객체의 생성과 동시에 멤버변수를 초기화할 때 쓰인다.

 

생성자를 정의하지 않았을 때 아래처럼 객체를 생성했다.

SimpleClass sc; //전역, 지역, 매개변수의 형태
SimpleClass *ptr = new SimpleClass; //동적 할당의 형태

생성자가 정의되면 객체생성과정에서 자동으로 호출되는 생성자에게 전달할 인자의 정보를 다음과 같이 추가한다.

SimpleClass sc(20);  // 생성자에 20을 전달
SimpleClass *ptr = new SimpleClass(30); //생성자에 30을 전달

생성자도 함수의 일종이니 오버로딩이 가능하다.
생성자도 함수의 일종이니 매개변수에 디폴트값을 설정할 수 있다. 

 

매개변수에 디폴트값을 설정하는 예제
//Constructor1.cpp
#include <iostream>
using namespace std;

class SimpleClass
{
private:
    int num1;
    int num2;
    
public:
    SimpleClass()
    {
        num1=0;
        num2=0;
    }
    SimpleClass(int n)
    {
        num1=n;
        num2=0;
    }
    SimpleClass(int n1, int n2)
    {
        num1=n1;
        num2=n2;
    }
    // 오버로딩 가능
    
    // 매개변수의 디폴트 값 설정 가능
    // SimpleClass(int n1=0, int n2=0)
    // {
    //     num1=n1;
    //     num2=n2;
    // }
    
    void ShowData() const {
        cout<<num1<<' '<<num2<<endl;
    }
};

int main(void)
{
    SimpleClass sc1;
    sc1.ShowData();
    
    SimpleClass sc2(100);
    sc2.ShowData();
    
    SimpleClass sc3(100,200);
    sc3.ShowData();
    return 0;
}

 


코드에서 다른 생성자를 주석처리하지 않고 디폴트값 설정한 생성자를 주석 해제하면 어떤 생성자를 호출할지 애매하다는 오류 메시지가 출력된다. SimpleClass sc2(100); 처럼 객체를 생성하면 두 생성자 모두 호출이 가능하기 때문에 호출할 생성자를 결정하지 못해서다.

아래는 int형 데이터가 인자인 생성자를 이용해서 객체를 생성하는 예이다. 

SimpleClass sc1; (0)
SimpleClass sc1(); (X)
SimpleClass * ptr1=new SimpleClass; (0)
SimpleClass * ptr1=new SimpleClass(); (0)

또한 SimpleClass() 생성자를 이용해서 객체를 생성하려면 SimpleClass sc1(); 과 같이 구성하면 안되며
아래처럼 SimpleClass sc1; 로 작성해야 한다. 그 이유는 아래 코드에..

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

class SimpleClass
{
private:
    int num1;
    int num2;
    
public:
    SimpleClass(int n1=0, int n2=0)
    {
        num1=n1;
        num2=n2;
    }
    void ShowData() const 
    {
        cout<<num1<<' '<<num2<<endl;
    }
};

int main(void)
{
    // 함수의 원형이라고 이전에 전역적으로 (함수 밖에)
    // 선언했지만 지역적으로 함수 내에 원형을 선언했다
    // void형 (인자를 안받는)을 객체 생성문, 함수의 원형선언 둘 다 인정하지 않고
    // 함수의 원형선언에만 사용하기로 약속함
    SimpleClass sc1(); //지역적으로 함수의 원형 선언
    SimpleClass mysc=sc1(); //반환객체값으로 mysc객체를 초기화
    mysc.ShowData();
    
    return 0;
}
SimpleClass sc1()
{
    SimpleClass sc(20, 30);
    return sc;
}

함수의 원형은 함수 밖에 선언하지만, 함수 내 지역적으로도 선언 가능하다. SimpleClass sc1(); 는 함수의 원형 선언에 해당한다. void형 생성자의 호출문으로도 인정하면 객체생성문인지 함수원형선언인지 혼란을 주니 함수 원형선언으로만 인식한다.

 

생성자로 객체 생성하는 예제

03단원의 FruitSaleSim1.cpp 예제를 생성자를 적용해 변경했다.

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

//사과장수
class FruitSeller
{
private:
	int APPLE_PRICE; //사과가격 (상수)
	int numOfApples; //재고
	int myMoney; //사과장수의 지갑
public:
	// 초기화
	/*void InitMembers(int price, int num, int money)
	{
		APPLE_PRICE = price;
		numOfApples = num;
		myMoney = money;
	}*/
	// 생성자
	FruitSeller(int price, int num, int money) { 
		APPLE_PRICE = price;
		numOfApples = num;
		myMoney = money;
	}
	// 사과 판매
	int SaleApples(int money)
	{
		if (money < 0) {
			cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
			return 0;
		}
		int num = money / APPLE_PRICE; //팔린 갯수
		numOfApples -= num;
		myMoney += money; //사과장수의 지갑
		return num;
	}
	// 수익과 재고 출력
	// const : 이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다
	void ShowSalesResult() const  ////추가	
	{
		cout << "남은 사과 : " << numOfApples << endl;
		cout << "판매 수익: " << myMoney << endl << endl;
	}
};

// 손님
class FruitBuyer {
	//프라이빗
	int myMoney; // 손님지갑
	int numOfApples; //산 사과갯수
public:
	/*void InitMembers(int money)
	{
		myMoney = money;
		numOfApples = 0;
	}*/
	// 생성자
	FruitBuyer(int money) {
		myMoney = money;
		numOfApples = 0;
	}
	// 구매. 구매대상, 구매금액이 전달
	void BuyApples(FruitSeller& seller, int money) {
		// unsigned int로 하면 없어도 됨..
		if (money < 0) {
			cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
			return;
		}
		numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
		myMoney -= money;
	}
	void ShowBuyResult() const ////추가
	{
		cout << "현재 잔액 : " << myMoney << endl;
		cout << "사과 개수 : " << numOfApples << endl << endl;
	}
};

int main(void)
{
	//FruitSeller seller;
	//seller.InitMembers(1000, 20, 0);
	FruitSeller seller(1000, 20, 0);

	//FruitBuyer buyer;
	//buyer.InitMembers(5000);	 
	FruitBuyer buyer(5000);
	buyer.BuyApples(seller, 2000); //사과 구매

	cout << "과일판매자 현황" << endl;
	seller.ShowSalesResult();
	cout << "과일구매자 현황" << endl;
	buyer.ShowBuyResult();

	return 0;
}

 

 

생성자 내에서 멤버의 초기화를 목적으로 함수를 호출하기도 한다. 
즉 InitMembers 함수를 지우지 않고 생성자 내에서 함수를 호출하게끔 해도 된다.
아래 예제들에 생성자를 적용해본다.

Point 클래스의 선언 및 정의 - Point.h, Point.cpp
Rectangle 클래스의 선언 및 정의 - Rectangle.h, Rectangle.cpp
실행을 위한 main함수 정의 - RectangleFaultFind.cpp

 

Point 클래스에 생성자 적용
//Point.h
#ifndef __POINT_H_
#define __POINT_H_

class Point {	
	// 점을 표현
private:
	int x; //x좌표 범위는 0~100
	int y; //같음

public:
	// 함수 내에서 멤버변수에 저장되는 값을 제한
	// 변경 전
	// bool InitMembers(int xpos, int ypos);	
	// 변경 후
	Point(const int& xpos, const int& ypos); //생성자
	int GetX() const;
	int GetY() const;
	bool SetX(int xpos);
	bool SetY(int xpos);
};

#endif
//Point.cpp 
#include <iostream>
#include "Point.h"
using namespace std;

//변경 전
//bool Point::InitMembers(int xpos, int ypos)
//{
//	if (xpos < 0 || ypos < 0)
//	{
//
//		cout << "벗어난 범위의 값 전달" << endl;
//		return false;
//	}
//	x = xpos;
//	y = ypos;
//	return true;
//}

//변경 후
Point::Point(const int &xpos, const int &ypos)
{
	x = xpos; 
	y = ypos;
}

int Point::GetX() const { //const 함수 (나중에 설명볼것)
	return x;
}
int Point::GetY() const {
	return y;
}
bool Point::SetX(int xpos)
{
	if (0 > xpos || xpos > 100) {
		cout << "벗어난 범위의 값 전달" << endl;
		return false;
	}
	x = xpos;
	return true;
}
bool Point::SetY(int ypos)
{
	if (0 > ypos || ypos > 100) {
		cout << "벗어난 범위의 값 전달" << endl;
		return false;
	}
	y = ypos;
	return true;
}

 

Rectangle 클래스는 두 개의 Point 객체를 멤버로 갖고 있어서 Rectangle 객체가 생성되면 두 개의 Point 객체가 함께 생성된다. 생성자는 멤버변수 초기화를 목적으로 정의되니 객체생성과정에서의 생성자 호출은 객체초기화를 쉽게 한다. 

객체 생성과정에서 Point 클래스의 생성자를 통해 Point 객체를 초기화 하려면 멤버 이니셜라이저(member initializer)를 사용하면 된다.

 

멤버 이니셜라이저를 통한 멤버 초기화
// Rectangle.h 
#ifndef __RECTANGLE_H_
#define __RECTANGLE_H

#include "Point.h"

class Rectangle
{
private:
	Point upLeft;
	Point lowRight;
public:
	//변경 전
	//bool InitMembers(const Point& ul, const Point& lr);
	//변경 후
	Rectangle(const int& x1, const int& y1, const int& x2, const int& y2);
	void ShowRecInfo() const;
};

#endif

객체 upLeft의 생성과정에서 x1과 y1을 인자로 전달받는 생성자를 호출하라.
객체 lowRight의 생성과정에서 x2와 y2을 인자로 전달받는 생성자를 호출하라. 는 뜻

// Rectangle.cpp
#include <iostream>
#include "Rectangle.h"
using namespace std;

//변경 전
//bool Rectangle::InitMembers(const Point& ul, const Point& lr)
//{
//	if (ul.GetX() > lr.GetX() || ul.GetY() > lr.GetY()) {
//		cout << "잘못된 위치정보 전달" << endl;
//		return false;
//	}
//	upLeft = ul;
//	lowRight = lr;
//	return true;
//}

//변경 후
Rectangle::Rectangle(const int& x1, const int& y1, const int& x2, const int& y2)
	:upLeft(x1, y1), lowRight(x2, y2) //멤버이니셜라이저
{
	//empty
}

void Rectangle::ShowRecInfo() const
{
	cout << "좌 상단 : " << '[' << upLeft.GetX() << ", ";
	cout << upLeft.GetY() << ']' << endl;
	cout << "우 하단 : " << '[' <<  lowRight.GetX() << ", ";
	cout << lowRight.GetY() << ']' << endl <<endl;
}
// RectangleConstructor.cpp
#include <iostream>
#include "Point.h"
#include "Rectangle.h"
using namespace std;

//int main(void)
//{
//	Point pos1;
//	if (!pos1.InitMembers(-2, 4)) {
//		cout << "초기화 실패" << endl;
//	}
//	if (!pos1.InitMembers(2, 4)) {
//		cout << "초기화 실패" << endl;
//	}
//
//	Point pos2;
//	if (!pos2.InitMembers(5, 9)) {
//		cout << "초기화 실패" << endl;
//	}
//
//	Rectangle rec;
//	if (!rec.InitMembers(pos2, pos1))
//		cout << "직사각형 초기화 실패" << endl;
//	if(!rec.InitMembers(pos1, pos2))
//		cout << "직사각형 초기화 실패" << endl;
//
//	rec.ShowRecInfo();
//	return 0;
//}

//변경 후
//RectangleConstructor.cpp
int main(void)
{
	Rectangle rec(1, 1, 5, 5);
	rec.ShowRecInfo();
	return 0;
}

멤버이니셜라이저는 멤버변수로 선언된 객체의 생성자 호출에 활용된다.
멤버이니셜라이저를 사용하다보면, 생성자의 몸체 부분이 위처럼 비어있기도 하다.

 

 

객체 생성과정 3단계

C++의 모든 객체는 이 3단계를 순서대로 거쳐서 객체 생성이 완성된다.

1. 메모리 공간의 할당
2. 이니셜라이저를 이용한 멤버변수(객체)의 초기화
3. 생성자의 몸체부분 실행

이니셜라이저가 없다면 메모리 공간의 할당, 생성자의 몸체부분 실행으로 객체생성이 완료된다.
생성자를 정의하지 않으면 디폴트생성자가 자동 삽입되어 반드시 호출된다.

 

멤버 이니셜라이저를 이용한 변수 및 상수 초기화

멤버이니셜라이저는 객체말고 멤버의 초기화에도 사용할 수 있다.

class SoSimple
{
private:
	int num1;
	int num2;
public:
	// num1을 n1의 값으로 초기화
	// int num1=n1;과 같다
	SoSimple(int n1, int n2):num1(n1) 
	{
		num2=n2; 
	}
	.......
};

 

멤버변수 초기화 방법

1. 생성자의 몸체에서 초기화 
아래 문장과 같다.
int num2;
num2=n2;

2. 이니셜라이저를 이용하는 초기화 (일반적으로 좀 더 선호)
아래 문장과 같다.
int num1=n1;

 

멤버 이니셜라이저로 멤버를 초기화하는 이유 

1. 초기화의 대상 명확
2. 성능 약간 이점

선언과 동시에 초기화가 이뤄지는 형태로 바이너리 코드가 생성된다. 생성자의 몸체에서 대입연산으로 초기화를 하면 선언, 초기화를 별도 문장에서 진행하는 형태로 바이너리 코드가 생성된다. 

 

멤버 이니셜라이저로 const 멤버변수 초기화
// FruitSaleSim3.cpp
#include <iostream>
using namespace std;

//사과장수
class FruitSeller
{
private:
	const int APPLE_PRICE; //사과가격 (상수) ///const 추가
	int numOfApples; //재고
	int myMoney; //사과장수의 지갑
public:
	// 초기화
	/*void InitMembers(int price, int num, int money)
	{
		APPLE_PRICE = price;
		numOfApples = num;
		myMoney = money;
	}*/
	// 생성자
	FruitSeller(int price, int num, int money) 
		:APPLE_PRICE(price), numOfApples(num), myMoney(money)
	{ 
		/// 멤버 이니셜라이저를 사용
		/*APPLE_PRICE = price;
		numOfApples = num;
		myMoney = money;*/
	}
	// 사과 판매
	int SaleApples(int money)
	{
		if (money < 0) {
			cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
			return 0;
		}
		int num = money / APPLE_PRICE; //팔린 갯수
		numOfApples -= num;
		myMoney += money; //사과장수의 지갑
		return num;
	}
	// 수익과 재고 출력
	// const : 이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다
	void ShowSalesResult() const  
	{
		cout << "남은 사과 : " << numOfApples << endl;
		cout << "판매 수익: " << myMoney << endl << endl;
	}
};

// 손님
class FruitBuyer {
	//프라이빗
	int myMoney; // 손님지갑
	int numOfApples; //산 사과갯수
public:
	/*void InitMembers(int money)
	{
		myMoney = money;
		numOfApples = 0;
	}*/
	// 생성자
	FruitBuyer(int money) 
		: myMoney(money), numOfApples(0)
	{
		/// 멤버 이니셜라이저
		/*myMoney = money;
		numOfApples = 0;*/
	}
	// 구매. 구매대상, 구매금액이 전달
	void BuyApples(FruitSeller& seller, int money) {
		// unsigned int로 하면 없어도 됨..
		if (money < 0) {
			cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
			return;
		}
		numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
		myMoney -= money;
	}
	void ShowBuyResult() const ////추가
	{
		cout << "현재 잔액 : " << myMoney << endl;
		cout << "사과 개수 : " << numOfApples << endl << endl;
	}
};

int main(void)
{
	//FruitSeller seller;
	//seller.InitMembers(1000, 20, 0);
	FruitSeller seller(1000, 20, 0);

	//FruitBuyer buyer;
	//buyer.InitMembers(5000);	 
	FruitBuyer buyer(5000);
	buyer.BuyApples(seller, 2000); //사과 구매

	cout << "과일판매자 현황" << endl;
	seller.ShowSalesResult();
	cout << "과일구매자 현황" << endl;
	buyer.ShowBuyResult();

	return 0;
}

 

const변수(=const 상수)처럼 참조자도 선언하면서 초기화해야한다.
이니셜라이저로 참조자를 멤버변수로 선언할 수 있다.

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

class AAA
{
public:
    AAA()
    {
        cout << "empty object" << endl;
        // 객체생성 시 실제로 생성자가 호출된다.
    }
    void ShowYourName()
    {
        cout << "Im class AAA" << endl;
    }
};
class BBB
{
    AAA& ref;
    const int &num; //참조자가 멤버변수로 선언되어 이니셜라이저로 초기화해야한다.
public:
    BBB(AAA& r, const int& n) :ref(r), num(n) {
        //empty constructor body
     }
    void ShowYourName()
    {
        ref.ShowYourName();
        cout << "and" << endl;
        cout << "I ref num " << num << endl;
    }
};

int main()
{
    AAA obj1;
    BBB obj2(obj1, 20);
    obj2.ShowYourName();
    return 0;
}

 

 

디폴트 생성자 Default Constuctor

객체 : 메모리 공간 할당 후 생성자의 호출까지 완료되어야 한다.

모든 객체는 한 번의 생성자 호출을 동반한다. (new 연산자를 사용한 객체의 생성에도 해당)
그래서 생성자를 정의하지 않은 클래스에는 컴파일러에 의해 디폴트 생성자가 자동 삽입된다.
디폴트 생성자는 인자를 받지 않고, 내부적으로 아무런 일도 하지 않는다.

class AAA
{
private:
    int num;
public:
	// AAA(){} // 디폴트 생성자
    int GetNum
    {
        return num;
    }
};

new연산자를 이용한 객체 생성에도 생성자가 호출되나 
malloc함수를 사용하면 생성자가 호출되지 않는다. AAA클래스의 크기정보만 바이트 단위로 전달되서 그렇다.

AAA * ptr=new AAA;
// AAA * ptr=(AAA*)malloc(sizeof(AAA)); //xxxxx

 

 

private 생성자

객체의 생성이 클래스 외부에서 진행되면 생성자는 public으로 선언되야 한다.
클래스 내부에서 객체를 생성하면 당연히 생성자는 private으로 선언되어도 된다.

public과 private 선언에 따른 차이점을 확인하기 위한 예제이다.
주로 싱글톤 디자인 패턴을 사용할 때 private생성자를 사용한다.

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

class AAA
{
private:
    int num;
public:
    AAA() :num(0) {}
    AAA& CreateInitObj(int n) const
    {
        AAA* ptr = new AAA(n);
        return *ptr;
    }
    void ShowNum() const
    {
        cout << num << endl;
    }    
private:
    AAA(int n) :num(n) {}
    //private 생성자
}; 
int main(void)
{
    AAA base;
    base.ShowNum();
	
	// base 객체를 이용해서 하나의 객체를 또 생성한다.
    AAA& obj1 = base.CreateInitObj(3);
    obj1.ShowNum();

    AAA& obj2 = base.CreateInitObj(12);
    obj2.ShowNum();

    delete &obj1;
    delete &obj2;
    return 0;
}

힙영역에 생성된 객체를 참조의 형태로 반환하고 있다.
힙에 할당된 메모리 공간은 변수로 간주하여 참조자를 통해 참조가 가능하다는 걸 알 수 있다.

 

소멸자

생성자에서 할당한 리소스의 소멸에 사용된다.
객체소멸 시 반드시 호출
된다.
클래스 이름 앞에 ~
를 붙인다.
반환형이 선언되어 있지 않으며 실제로 반환하지 않는다.
매개변수는 void형으로 선언되어야 해서 오버로딩, 디폴트값 설정이 불가하다.
소멸자를 직접 정의하지 않으면 생성자처럼 디폴트소멸자가 자동 삽입된다.

class AAA
{
public:
	AAA() { .... }
	~AAA() { .... }
}


생성자 내에서 new 연산자를 이용해 할당한 메모리공간이 있으면
소멸자에서는 delete 연산자를 이용해 메모리공간을 소멸한다.

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

class Person
{
private:
	char* name;
	int age;
public:
	Person(char* myname, int myage) {
		int len = strlen(myname) + 1; //myname길이 + 널문자열
		name = new char[len];
		strcpy(name, myname); //저장
		age = myage;
	}
	void ShowPersonInfo() const
	{
		cout << "이름:" << name << endl;
		cout << "나이:" << age << endl;
	}
	//소멸자
	~Person()
	{
		delete []name;
		cout << "called destructor!" << endl;

	}
};
int main(void) {
	Person man1("Lee", 29);
	Person man2("Jang", 30);
	man1.ShowPersonInfo();
	man2.ShowPersonInfo();
	return 0;
}

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

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90
04-1 정보은닉과 const

chapter 03에서 제시한 과일장수 시뮬레이션 예제에서 정의한 두 클래스의 멤버변수는 private으로 선언이 되어있다.
그러나 다음 조건을 유지할 수 있는 장치는 없다.

사과의 구매를 목적으로 0보다 작은 수를 전달할 수 없다

위 제약사항을 항상 만족시킬 수 있도록 예제를 변경하고, 예제의 안전성을 높일 수 있도록 일부 함수를 const로 선언해보자.


chapter 03에서 제시한 과일장수 시뮬레이션 예제

#include <iostream>
using namespace std;

//사과장수
class FruitSeller
{
private:
	int APPLE_PRICE; //사과가격 (상수)
	int numOfApples; //재고
	int myMoney; //사과장수의 지갑
public:
	// 초기화
	void InitMembers(int price, int num, int money)
	{
		APPLE_PRICE = price;
		numOfApples = num;
		myMoney  = money;		
	}
	// 사과 판매
	int SaleApples(int money)
	{
		int num = money / APPLE_PRICE; //팔린 갯수
		numOfApples -= num;
		myMoney += money; //사과장수의 지갑
		return num;
	}
	// 수익과 재고 출력
	void ShowSalesResult() {
		cout << "남은 사과 : " << numOfApples << endl;
		cout << "판매 수익: " << myMoney<< endl <<endl;
	}
};

// 손님
class FruitBuyer {
	//프라이빗
	int myMoney; // 손님지갑
	int numOfApples; //산 사과갯수

public:
	void InitMembers(int money)
	{
		myMoney = money;
		numOfApples = 0;
	}
	// 구매기능
	// 구매대상, 구매금액이 전달
	void BuyApples(FruitSeller &seller, int money) {
		numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
		myMoney -= money;
	}
	void ShowBuyResult() 
	{
		cout << "현재 잔액 : " << myMoney << endl;
		cout << "사과 개수 : " << numOfApples << endl << endl;
	}
};

int main(void) 
{
	FruitSeller seller;
	seller.InitMembers(1000, 20, 0);

	FruitBuyer buyer;
	buyer.InitMembers(5000);
	buyer.BuyApples(seller, 2000); //사과 구매

	cout << "과일판매자 현황" << endl;
	seller.ShowSalesResult();
	cout << "과일구매자 현황" << endl;
	buyer.ShowBuyResult();
	
	return 0;
}

 

 

해설

#include <iostream>
using namespace std;

//사과장수
class FruitSeller
{
private:
	int APPLE_PRICE; //사과가격 (상수)
	int numOfApples; //재고
	int myMoney; //사과장수의 지갑
public:
	// 초기화
	void InitMembers(int price, int num, int money)
	{
		APPLE_PRICE = price;
		numOfApples = num;
		myMoney = money;
	}
	// 사과 판매
	int SaleApples(int money)
	{
		if (money < 0) {
			cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
			return 0;
		}
		int num = money / APPLE_PRICE; //팔린 갯수
		numOfApples -= num;
		myMoney += money; //사과장수의 지갑
		return num;
	}
	// 수익과 재고 출력
	// const : 이 함수 내에서는 멤버변수에 저장된 값을 변경하지 않겠다
	void ShowSalesResult() const  ////추가	
	{
		cout << "남은 사과 : " << numOfApples << endl;
		cout << "판매 수익: " << myMoney << endl << endl;
	}
};

// 손님
class FruitBuyer {
	//프라이빗
	int myMoney; // 손님지갑
	int numOfApples; //산 사과갯수
public:
	void InitMembers(int money)
	{
		myMoney = money;
		numOfApples = 0;
	}
	// 구매. 구매대상, 구매금액이 전달
	void BuyApples(FruitSeller& seller, int money) {
		// unsigned int로 하면 없어도 됨..
		if (money < 0) {
			cout << "잘못된 정보가 전달되어 구매를 취소합니다" << endl;
			return;
		}
		numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
		myMoney -= money;
	}
	void ShowBuyResult() const ////추가
	{
		cout << "현재 잔액 : " << myMoney << endl;
		cout << "사과 개수 : " << numOfApples << endl << endl;
	}
};

int main(void)
{
	FruitSeller seller;
	seller.InitMembers(1000, 20, 0);

	FruitBuyer buyer;
	buyer.InitMembers(5000);
	buyer.BuyApples(seller, 2000); //사과 구매

	cout << "과일판매자 현황" << endl;
	seller.ShowSalesResult();
	cout << "과일구매자 현황" << endl;
	buyer.ShowBuyResult();

	return 0;
}

 

 

 

 

04-2 다양한 클래스의 정의

다음의 Point 클래스를 기반으로 활용하여 원을 의미하는 Circle 클래스를 정의하자

class Point
{
private:
    int xpos, ypos;
public:
    void Init(int x, int y)
    {
       xpos=x;
       ypos=y;
    }
    void ShowPointInfo() const
    {
        cout<<"["<<xpos<<", "<<ypos<<"]"<<endl;
    }
};

 

Circle 객체에는 좌표상의 위치 정보(원의 중심좌표)와 반지름의 길이 정보를 저장, 출력할 수 있어야 한다. 그리고 정의한 Circle 클래스를 기반으로 Ring 클래스도 정의하자. 링은 두 개의 원으로 표현 가능하므로(바깥쪽, 안쪽 원), 두 개의 Circle 객체를 기반으로 정의가 가능하다. 참고로 안쪽 원과 바깥쪽 원의 중심좌표가 동일하다면 두께가 일정한 원을 표현하는 셈이 되며, 중심좌표가 동일하지 않다면 두께가 일정하지 않은 링을 표현하는 셈이 된다. 이렇게 해서 클래스의 정의가 완료되었다면, 다음 main 함수를 기반으로 실행을 시키자.

int main(void)
{
    Ring ring;
    ring.Init(1, 1, 4, 2, 2, 9);
    ring.ShowRingInfo();
    return 0;
}

 

Init의 함수호출을 통해서 전달된 1,1,4는 안쪽 원의 정보에 해당하며 (x좌표, y좌표, 반지름) 이어서 전달된 2,2,9는 바깥쪽 원의 정보에 해당한다. 그리고 실행결과는 다음과 유사해야 한다.

Inner Circle Info...
radius: 4
[1, 1]
Outter Circle Info...
radius: 9
[2, 2]

 

참고로 1개의 클래스를 정의하더라도, 항상 캡슐화를 고민하기 바란다. 이 문제의 답안도 캡슐화의 고민여부에 따라 차이를 보일 수 있다.

 

 

해설

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    void Init(int x, int y)
    {
        xpos = x;
        ypos = y;
    }
    void ShowPointInfo() const
    {
        cout << "[" << xpos << ", " << ypos << "]" << endl;
    }
};

// 원의 중심좌표와 반지름 길이를 저장, 출력
class Circle {
    int rad; //반지름
    Point center; //원의 중심좌표
public:
    void Init(int x, int y, int r) {
        rad = r;
        center.Init(x, y);
    }
    void ShowCircleInfo() const {
        cout << "radius : " << rad << endl;
        center.ShowPointInfo();
    }
};

class Ring
{
    Circle inCircle;
    Circle outCircle;
public:
    // 안쪽원 x좌표,y좌표,반지름
    // 바깥원 x좌표,y좌표,반지름
    void Init(int xX,int xY, int xR, int yX, int yY, int yR)
    {
        inCircle.Init(xX, xY, xR);
        outCircle.Init(yX, yY, yR);
    }
    /*void ShowRingInfo() {*/
    void ShowRingInfo() const {
        cout << "Inner Circle Info" << endl;
        inCircle.ShowCircleInfo();
        cout << "Outter Circle Info" << endl;
        outCircle.ShowCircleInfo();
    }
};

int main(void)
{
    Ring ring;
    ring.Init(1, 1, 4, 2, 2, 9);
    ring.ShowRingInfo();
    return 0;
}

 

 

 

 

04-3 C++ 기반의 데이터 입출력

1. 04-2는 생성자를 안 배웠기 때문에 별도의 초기화 함수를 정의, 호출하여 Point, Circle, Ring 클래스의 객체를 초기화하였다. 이 때 구현한 답에 대해서 모든 클래스에 생성자를 정의해보자.

 

해설

#include <iostream>
using namespace std;

class Point
{
private:
    int xpos, ypos;
public:
    Point(int x, int y) : xpos(x), ypos(y) { }
    /*void Init(int x, int y)
    {
        xpos = x;
        ypos = y;
    }*/
    void ShowPointInfo() const
    {
        cout << "[" << xpos << ", " << ypos << "]" << endl;
    }
};

// 원의 중심좌표와 반지름 길이를 저장, 출력
class Circle {
    int rad; //반지름
    Point center; //원의 중심좌표
public:
    Circle(int  x, int y, int r) : rad(r), center(x, y) {  }
    /*void Init(int x, int y, int r) {
        rad = r;
        center.Init(x, y);
    }*/
    void ShowCircleInfo() const {
        cout << "radius : " << rad << endl;
        center.ShowPointInfo();
    }
};

class Ring
{
    Circle inCircle;
    Circle outCircle;
public:
    // 안쪽원 x좌표,y좌표,반지름
    // 바깥원 x좌표,y좌표,반지름
    //void Init(int xX,int xY, int xR, int yX, int yY, int yR)
    //{
    //    inCircle.Init(xX, xY, xR);
    //    outCircle.Init(yX, yY, yR);
    //}
    Ring(int xX, int xY, int xR, int yX, int yY, int yR) :inCircle(xX, xY, xR), outCircle(yX, yY, yR){}

    /*void ShowRingInfo() {*/
    void ShowRingInfo() const {
        cout << "Inner Circle Info" << endl;
        inCircle.ShowCircleInfo();
        cout << "Outter Circle Info" << endl;
        outCircle.ShowCircleInfo();
    }
};

int main(void)
{
    /*Ring ring;
    ring.Init(1, 1, 4, 2, 2, 9);*/
    Ring ring(1, 1, 4, 2, 2, 9);
    ring.ShowRingInfo();
    return 0;
}

 

 

 

2. 명함을 의미하는 NameCard 클래스를 정의해보자. 이 클래스에는 다음의 정보가 저장되어야 한다.

성명, 회사이름, 전화번호, 직급

단, 직급 정보를 제외한 나머지는 문자열의 형태로 저장을 하되, 길이에 딱 맞는 메모리 공간을 할당 받는 형태로 정의하자(동적 할당을 하라는 뜻). 그리고 직급 정보는 int형 멤버변수를 선언해서 저장을 하되, 아래의 enum 선언을 활용해야 한다.

enum {CLERK, SENIOR, ASSIST, MANAGER};

 

위의 enum 선언에서 정의된 상수는 순서대로 사원, 주임, 대리, 과장을 뜻한다. 그럼 다음 main 함수와 실행의 예를 참조하여, 이 문제에서 원하는 형태대로 NameCard 클래스를 완성해보자.

int main(void)
{
    NameCard manClerk("Lee", "ABCEng", "010-1111-2222", COMP_POS::CLERK);    
    NameCard manSENIOR("Hong", "OrangeEng", "010-3333-4444", COMP_POS::SENIOR);
    NameCard manAssist("Kim", "SoGoodComp", "010-5555-6666", COMP_POS::ASSIST);
    manClerk.ShowNameCardInfo();
    manSENIOR.ShowNameCardInfo();
    manAssist.ShowNameCardInfo();
    return 0;
}
이름 : Lee
회사 : ABCEng
전화번호 : 010-1111-2222
직급 : 사원

이름 : Hong
회사 : OrangeEng
전화번호 : 010-3333-4444
직급 : 주임

이름 : Kim
회사 : SoGoodComp
전화번호 : 010-5555-6666
직급 : 대리

 

03의 예제 RacingCarEnum.cpp를 참고하면 수월하다. (더보기 클릭)

더보기
// 03장의 RacingCarEnum.cpp
#include <iostream>
using namespace std; 

// 이름 공간안에 구조체 Car에서 사용하는 상수를 모아놓음
namespace CAR_CONST {
    // 열거형 enum 상수 선언
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    };
}

struct Car
{      
    // 소유자ID, 연료량, 현재속도
    char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;

void ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= CAR_CONST::FUEL_STEP;
    }

    if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
        curSpeed = CAR_CONST::MAX_SPD;
        return;
    }
    curSpeed += CAR_CONST::ACC_STEP;
}

void Break() {
    if (curSpeed < CAR_CONST::BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= CAR_CONST::BRK_STEP;
}
};

int main(void)
{  
    // 구조체변수의 선언 및 초기화 진행
    Car run99 = { "run99", 100, 0 };
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    
    Car sped77= { "sped77", 100, 0 };
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();
    
    return 0;
}

 

 

해설

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
//#include <string.h>
#include <cstring>
using namespace std;

// 직급정보
namespace COMP_POS 
{
    // 익명이라도 충돌하지 않도록
    enum { CLERK, SENIOR, ASSIST, MANAGER };
    // 익명 열거형 : 열거형 이름, 변수가 없는 것

    void ShowPositionInfo(int pos) 
    {
        switch (pos) 
        {
        case CLERK:
            cout << "사원" << endl;
            break;
        case SENIOR:
            cout << "주임" << endl;
            break;
        case ASSIST:
            cout << "대리" << endl;
            break;
        case MANAGER:
            cout << "과장" << endl;
        }        
    }
}
// 명함 
class NameCard 
{
private:
    char* name; // 성명
    char* company; // 회사
    char* phone; // 전번
    int position; //직급
public:
    NameCard(const char* _name, const char* _company,  const char* _phone, int pos)
        : position(pos)
    {
        // 동적할당해 문자열의 형태로 저장
        name = new char[strlen(_name)+1]; //new는 생성된 것에 대해 포인터를 반환한다.
        company = new char[strlen(_company)+1];
        phone = new char[strlen(_phone)+1];
        strcpy(name, _name);
        strcpy(company, _company);
        strcpy(phone, _phone); 
    }
    void ShowNameCardInfo() const  {
        cout << "이름 : " << name << endl;
        cout << "회사 : " << company << endl;
        cout << "전화번호 : " << phone << endl;
        cout << "직급 : "; COMP_POS::ShowPositionInfo(position);
        cout << endl;
    }
    ~NameCard() {
        delete []name;
        delete []company;
        delete []phone;
    }
};

int main(void)
{    
    char lee[4] = {"Lee"};
    NameCard manClerk(lee, "ABCEng", "010-1111-2222", COMP_POS::CLERK);
    NameCard manSENIOR("Hong", "OrangeEng", "010-3333-4444", COMP_POS::SENIOR);
    NameCard manAssist("Kim", "SoGoodComp", "010-5555-6666", COMP_POS::ASSIST);
    manClerk.ShowNameCardInfo();
    manSENIOR.ShowNameCardInfo();
    manAssist.ShowNameCardInfo();
    return 0;
}

 

열거형 더보기

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90

구조체를 사용해야 되는 이유 , 클래스를 사용해야 되는 이유에 대해 정리해본다.


3.1 구조체와 클래스


구조체의 유용성 (WHY?)
struct Car basicCar; // 사용자정의자료형 구조체이름

구조체는 관련 있는 데이터를 하나의 자료형으로 묶을 수 있다. 
함께 움직이는 데이터들을 묶어주는 효과가 있다. -> 프로그램 구현, 관리가 쉬움

ex. 비디오대여가게에서 필요한 프로그램의 기능은?
- 고객관리, 비디오아이템관리(등록, 삭제, 대여 관련 정보), 회계관리
- 고객관리에서 회원은 각종 정보들과 함께 생성, 삭제되어 라이프사이클이 같다

소프트웨어 = 데이터의 표현 + 데이터의 처리 이며 표현해야 하는 데이터는 부류를 형성한다.
부류를 형성하는 데이터들은 함께 생성, 이동, 소멸되는 특성이 있다.
구조체는 이런 연관있는 데이터들을 묶을 수 있는 장치이다.

 

 

C언어의 구조체에 대한 불만

모든 사용자정의 자료형에 대한 불만, 기본 자료형으로 인식해주지 않는다. 
- 기본자료형 int a; 처럼 Person b;로 쓰면 에러 발생하여 struct를 앞에 붙여줘야 한다. -> 구분짓고 있다
- 기본자료형처럼 형태를 일치할 수 없는 지 의문

C++은 기본자료형과 사용자정의자료형을 동일한 자료형으로 인식한다.

또한 기본자료형은 각종 연산이 가능하다. C에서 사용자정의연산은 대입 외에 다른 연산이 되지 않는데,
C++은 사용자 정의 자료형도 기본자료형처럼 각종 연산이 가능하다.

// 사용자 정의 자료형 <-> 기본자료형
// 자료형이 Person이다. 
struct Person 
{
	int age;
	char name[10];
}

int main()
{
	int a = 10;
	Person p; //sturct Person p;
	return 0;
}
더보기
#include <iostream>
using namespace std; 

// 구조체 Car랑 관련된 정보의 상수화
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10

struct Car
{
    // 소유자ID, 연료량, 현재속도
    char gamerID[ID_LEN];
    int fuelGauge;
    int curSpeed;
};

// 차의 정보 출력 함수 
// 단순 정보 출력만 해서 const 참조자를 매개변수로 선언
// 참조자를 이용한 원본값의 변형은 하지 않겠다는 뜻
// https://gjghks.tistory.com/40
void ShowCarState(const Car &car) {
    cout << "소유자ID : " << car.gamerID << endl;
    cout << "연료량 : " << car.fuelGauge << "%" << endl;
    cout << "현재속도 : " << car.curSpeed << "km/s" << endl<< endl;
}
 
void Accel(Car &car) {
    if (car.fuelGauge <= 0) {
        return;
    }
    else {
        car.fuelGauge -= FUEL_STEP;
    }

    if (car.curSpeed + ACC_STEP >= MAX_SPD) {
        car.curSpeed = MAX_SPD;
        return;
    }
    car.curSpeed += ACC_STEP;
}

void Break(Car& car) {
    if (car.curSpeed < BRK_STEP) {
        car.curSpeed = 0;
        return;
    }
    car.curSpeed -= BRK_STEP;
}

int main(void)
{  
    // 구조체변수의 선언 및 초기화 진행
    Car run99 = { "run99", 100, 0 };
    Accel(run99);
    Accel(run99);
    ShowCarState(run99);
    Break(run99); 
    ShowCarState(run99);
    
    Car sped77= { "sped77", 100, 0 };
    Accel(sped77);
    Break(sped77);
    ShowCarState(sped77);
    
    return 0;
}

 

ShowCarState, Accel, Break 함수는 구조체 Car과 함께 부류를 형성해 데이터 처리를 담당하는 함수이다.
구조체 Car에 종속적인데도 전역함수로 선언되어 종속적임을 나타내지 못하고 있으며 다른데서 함수를 호출하는 실수가 생길 수도 있는 예이다.

 

 

함수를 넣으면 좋은 구조체

프로그램 = 데이터 + 데이터 조작 루틴(함수)
잘 구성된 프로그램은 데이터와 더불어 함수들도 부류를 형성한다.

 

더보기

구조체에 종속적인 함수들을 구조체 안에 넣어버리는 것..
데이터와 함수를 묶어벼러서 확실히 구분된다.
구조체 내에 선언된 변수에 직접접근이 가능하다

C++은 구조체 안에 함수를 넣는 것을 허용한다.

#include <iostream>
using namespace std; 

// 구조체 Car랑 관련된 정보의 상수화
#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10

struct Car
{
    // 소유자ID, 연료량, 현재속도
    char gamerID[ID_LEN];
    int fuelGauge;
    int curSpeed;

void ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= FUEL_STEP;
    }

    if (curSpeed + ACC_STEP >= MAX_SPD) {
        curSpeed = MAX_SPD;
        return;
    }
    curSpeed += ACC_STEP;
}

void Break() {
    if (curSpeed < BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= BRK_STEP;
}
};

int main(void)
{  
    // 구조체변수의 선언 및 초기화 진행
    Car run99 = { "run99", 100, 0 };
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    
    Car sped77= { "sped77", 100, 0 };
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();
    
    return 0;
}

 

 

 

ex. 고객정보 + 입출금함수

고객정보 

#include <iostream>
using namespace std;

struct Account{
    char accID[20];
    char secID[20];
    char name[20];
    int balance;
    // 계좌, 비번, 이름, 잔액
};

int main()
{
// struct 키워드를 붙이지 않았다. C와의 호환성을 위해 붙여도 된다.
    Account yoon={"1234", "2321", "yoon", 1000};
    cout<<yoon.accID<<endl;
    cout<<yoon.secID<<endl;
    cout<<yoon.name<<endl;
    cout<<yoon.balance<<endl;

    return 0;
}

 

 

고객정보와 입출금

#include <iostream>
using namespace std;

struct Account{
    char accID[20];
    char secID[20];
    char name[20];
    int balance;
    // 계좌, 비번, 이름, 잔액
};

// 입금
// C라면 포인터로 매개변수가 작성되었음도 생각해보자
void Deposit(Account &acc, int money) {
    acc.balance+=money;
}
// 출금
void Withdraw(Account &acc, int money) {
    acc.balance-=money;
}

int main()
{
    Account yoon={"1234", "2321", "yoon", 1000};
    
    cout<<yoon.accID<<endl;
    cout<<yoon.secID<<endl;
    cout<<yoon.name<<endl;
    cout<<yoon.balance<<endl;
    
    Deposit(yoon, 100);
    cout<<"입금 후"<<endl;
    cout<<yoon.balance<<endl;
    
    Withdraw(yoon, 200);
    cout<<"출금 후"<<endl;
    cout<<yoon.balance<<endl;
    

    return 0;
}

 

 

구조체 안에 멤버함수로 들어갔음

C는 에러가 발생하나, C++은 구조체 안에도 함수를 넣을 수 있다.
함수를 접근할 때 멤버변수로 접근한다. (.) 를 사용해서.

구조체 내부 멤버함수 안에 있는 변수는 멤버변수로 구조체 내에 있는 데이터들만 조작한다. 다른 용도로는 사용을 하지 않아 묶어주는 효과가 있다. 하나의 함수는 하나의 기능을 가지며, 함수에서 다루는 데이터들도 어떤 범주를 가져야 한다는 것도 생각해 볼 수 있겠다.

 

#include <iostream>
using namespace std;

struct Account{
    // 계좌, 비번, 이름, 잔액
    char accID[20];
    char secID[20];
    char name[20];
    int balance;

    // 구조체의 멤버 함수
    // 입금
    // C라면 포인터로 매개변수가 작성되었음도 생각해보자
    void Deposit(int money) {
        balance+=money;
    }
    // 출금
    void Withdraw(int money) {
      balance-=money;
    }
};

int main()
{
    Account yoon={"1234", "2321", "yoon", 1000};
    
    cout<<yoon.accID<<endl;
    cout<<yoon.secID<<endl;
    cout<<yoon.name<<endl;
    cout<<yoon.balance<<endl;
    
    yoon.Deposit(100);
    cout<<"입금 후"<<endl;
    cout<<yoon.balance<<endl;
    
    yoon.Withdraw(200);
    cout<<"출금 후"<<endl;
    cout<<yoon.balance<<endl;
    

    return 0;
}

 

 

 

구조체 안에 enum 상수의 선언

열거형 enum을 사용해서 구조체 내에서 유효한 상수를 정의한다.

더보기

앞 예제의 매크로 상수들 역시 구조체에 종속적이며 다른 곳에서 사용하도록 정의되지 않았으므로 구조체 내에 포함시키는 것이 낫다. 열거형 enum을 사용해서 구조체 내에서 유효한 상수를 정의한다.

#define ID_LEN 20
#define MAX_SPD 200
#define FUEL_STEP 2
#define ACC_STEP 10
#define BRK_STEP 10
#include <iostream>
using namespace std; 

struct Car
{
    // 열거형 enum 상수 선언
   enum
   {
    ID_LEN = 20,
    MAX_SPD = 200,
    FUEL_STEP = 2,
    ACC_STEP = 10,
    BRK_STEP = 10
   };

    // 소유자ID, 연료량, 현재속도
    char gamerID[ID_LEN];
    int fuelGauge;
    int curSpeed;

void ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= FUEL_STEP;
    }

    if (curSpeed + ACC_STEP >= MAX_SPD) {
        curSpeed = MAX_SPD;
        return;
    }
    curSpeed += ACC_STEP;
}

void Break() {
    if (curSpeed < BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= BRK_STEP;
}
};

int main(void)
{  
    // 구조체변수의 선언 및 초기화 진행
    Car run99 = { "run99", 100, 0 };
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    
    Car sped77= { "sped77", 100, 0 };
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();
    
    return 0;
}

 

 

구조체 안에 이름공간을 이용해서 상수를 명시

가독성이 더욱 좋아지며 구조체들 사이에서만 사용하는 사용하는 상수들을 선언할 때 좋다.

#include <iostream>
using namespace std; 

// 이름 공간안에 구조체 Car에서 사용하는 상수를 모아놓음
namespace CAR_CONST {
    // 열거형 enum 상수 선언
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    };
}

struct Car
{      
    // 소유자ID, 연료량, 현재속도
    char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;

void ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= CAR_CONST::FUEL_STEP;
    }

    if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
        curSpeed = CAR_CONST::MAX_SPD;
        return;
    }
    curSpeed += CAR_CONST::ACC_STEP;
}

void Break() {
    if (curSpeed < CAR_CONST::BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= CAR_CONST::BRK_STEP;
}
};

int main(void)
{  
    // 구조체변수의 선언 및 초기화 진행
    Car run99 = { "run99", 100, 0 };
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    
    Car sped77= { "sped77", 100, 0 };
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();
    
    return 0;
}

 

구조체를 보는 순간 정의되어있는 함수의 종류와 기능이 한 눈에 보게끔 코드를 작성하는 게 좋다.
그래서 구조체 내에 정의된 함수의 수가 많거나 길이가 길다면 다음과 같이 구조체 밖으로 함수를 빼낼 수 있다.

 

struct Car
{      
    ...
void ShowCarState();
void Accel();
void Break();
	...
};
void Car::ShowCarState()
{
	...
}
void Car::Accel()
{
	...
}
더보기
#include <iostream>
using namespace std; 

// 이름 공간안에 구조체 Car에서 사용하는 상수를 모아놓음
namespace CAR_CONST {
    // 열거형 enum 상수 선언
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    };
}

struct Car
{
    // 소유자ID, 연료량, 현재속도
    char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;
    void ShowCarState();
    void Accel();
    void Break();

};

void Car::ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Car::Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= CAR_CONST::FUEL_STEP;
    }

    if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
        curSpeed = CAR_CONST::MAX_SPD;
        return;
    }
    curSpeed += CAR_CONST::ACC_STEP;
}

void Car::Break() {
    if (curSpeed < CAR_CONST::BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= CAR_CONST::BRK_STEP;
} 

int main(void)
{  
    // 구조체변수의 선언 및 초기화 진행
    Car run99 = { "run99", 100, 0 };
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    
    Car sped77= { "sped77", 100, 0 };
    sped77.Accel();
    sped77.Break();
    sped77.ShowCarState();
    
    return 0;
}

 

구조체 안에 함수가 정의되어 있으면 함수를 인라인으로 처리해라는 의미가 들어있다. 예제처럼 함수를 구조체 외부로 빼면 이런 의미가 사라져서 인라인 처리하려면 키워드 inline 처리를 명시적으로 지시해야 한다.

 


2. 클래스와 객체


구조체가 아니고 클래스

C++의 사용자정의 자료형 클래스 : 변수, 함수까지 묶어서 자료형을 정의할 수 있다. 변수+함수
문법적으로 class를 붙여 사용
클래스는 기본적으로 private이다

구조체 : 기존자료형들로로 새로 자료형을 만든 것, 변수들로만 만들 수 있다.
문법적으로 struct를 붙여 사용
구조체는 기본적으로 public
그래서 어디서든 접근이 가능하다.

 

클래스 등장 이유 : -

클래스 = 멤버변수 + 멤버함수
클래스의 변수는 변수가 아니라 객체(object)라고 표현한다.

C++ 클래스 > 구조체 (범위)

 

 

사물 관찰 이후의 데이터 추상화

현실 세계의 사물을 데이터적 측면과 기능적인 측면을 통해 정의하는 것
데이터 (변수)+ 기능 (함수)

 

데이터 추상화 이후의 클래스화

추상화된 데이터를 갖고 사용자 정의 자료형을 정의하는 것 -> 클래스화

 

 

클래스화 이후의 인스턴스화 (객체화라고도 한다)

클래스 기반의 객체(Object) 생성

#include <iostream>
using namespace std;

class Account {
public:
    // 계좌, 비번, 이름, 잔액
    char accID[20];
    char secID[20];
    char name[20];
    int balance;

    // 구조체의 멤버 함수
    // 입금
    // C라면 포인터로 매개변수가 작성되었음도 생각해보자
    void Deposit(int money) {
        balance += money;
    }
    // 출금
    void Withdraw(int money) {
        balance -= money;
    }
};

int main()
{
    Account yoon = { "1234", "2321", "yoon", 1000 };

    cout << yoon.accID << endl;
    cout << yoon.secID << endl;
    cout << yoon.name << endl;
    cout << yoon.balance << endl;

    yoon.Deposit(100);
    cout << "입금 후" << endl;
    cout << yoon.balance << endl;

    yoon.Withdraw(200);
    cout << "출금 후" << endl;
    cout << yoon.balance << endl;


    return 0;
}

 

 

 


3-3. 클래스 멤버의 접근 제어


접근제어지시자(접근제어레이블)

public : 어디서든 접근허용
protected : 상속관계에 놓여있을 때 유도 클래스에서의 접근 허용 (상속)
private : 클래스 내 정의된 함수에서만 접근 허용 

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

using namespace std; 

// 이름 공간안에 구조체 Car에서 사용하는 상수를 모아놓음
namespace CAR_CONST {
    // 열거형 enum 상수 선언
    enum
    {
        ID_LEN = 20,
        MAX_SPD = 200,
        FUEL_STEP = 2,
        ACC_STEP = 10,
        BRK_STEP = 10
    };
}

class Car
{
private:
    /* 접근제어지시자(라벨):: ... */
    char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;
public:
    void InitMembers(const char *ID, int fuel);
    void ShowCarState();
    void Accel();
    void Break();
};

void Car::InitMembers(const  char *ID, int fuel) {
    strcpy(gamerID, ID);
    fuelGauge = fuel;
    curSpeed = 0;
}

void Car::ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Car::Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= CAR_CONST::FUEL_STEP;
    }

    if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
        curSpeed = CAR_CONST::MAX_SPD;
        return;
    }
    curSpeed += CAR_CONST::ACC_STEP;
}

void Car::Break() {
    if (curSpeed < CAR_CONST::BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= CAR_CONST::BRK_STEP;
} 

int main(void)
{  
    // run99는 객체
    Car run99;
    run99.InitMembers("run99", 100);
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();
    
    return 0;
}

 

 

멤버 변수 : 클래스를 구성하는 (클래스 내에 선언된) 변수
멤버 함수 : 클래스를 구성하는 (클래스 내에 정의된) 함수

 

 

C언어 파일분할 복습

헤더파일의 역할 : cpp파일로 만들어진 obj 파일에 있는 함수들의 내용을 다른 cpp파일에서 사용할 수 있게 한다.
A.cpp파일에서 B.cpp파일에 있는 함수나 클래스를 사용하려면 함수의 프로토타입이나 클래스선언 등의 정보가 필요하다. (그래야 어떤 함수나 메소드 호출 시 인자값이 필요한지, 리턴타입이 뭔지 알 수 있다). + 관리와 공유가 편하다

C언어를 대상으로 헤더파일에 들어가야 할 내용 구분

헤더파일의 중복포함을 막기 위해서 사용하는 매크로 #ifndef~#endif : https://sexycoder.tistory.com/4

둘 이상의 파일을 컴파일해서 하나의 실행파일을 만드는 법
링커(Linker)가 하는 일 : https://luckyyowu.tistory.com/8

 

 

C++에서의 파일분할

클래스를 대상으로 파일을 나눌 때
1) 헤더파일은 클래스의 선언,
2) 코드파일은 클래스의 정의(멤버함수의 정의)
를 담는다.

클래스의 선언(declaration)은 컴파일러가 Car 클래스와 관련된 문장의 오류를 잡아내는데 필요한 최소한의 정보로 클래스를 구성하는 외형적인 틀을 보여준다.

// 클래스의 선언
class Car
{
private: 
    char gamerID[CAR_CONST::ID_LEN];
    int fuelGauge;
    int curSpeed;
public:
    void InitMembers(const char *ID, int fuel);
    void ShowCarState();
    void Accel();
    void Break();
};

 

클래스의 정의(definition)는 다른 문장의 컴파일에 필요한 정보가 없고, 컴파일 된 후에 링커에 의해 하나의 실행파일로 묶일 코드이다.

void Car::InitMembers(const  char *ID, int fuel) { ... }

 

클래스와 관련된 문장의 컴파일 정보로 사용되는 클래스의 선언은 헤더파일에 저장해서 필요한 위치에 쉽게 포함되도록 하며 클래스의 정의는 소스파일에 저장해서 컴파일되게 한다. 

 

전에 사용했던 예제를 3개의 파일로 나눠본 예

Car.h

#ifndef __CAR_H__
#define __CAR_H__

namespace CAR_CONST
{
	enum
	{
		ID_LEN = 20,
		MAX_SPD = 200,
		FUEL_STEP = 2,
		ACC_STEP = 10,
		BRK_STEP = 10
	};
}

class Car
{
private:
	char gamerID[CAR_CONST::ID_LEN];
	int fuelGauge;
	int curSpeed;
public:
	void InitMembers(const char* ID, int fuel);
	void ShowCarState();
	void Accel();
	void Break();
};

#endif

 

Car.cpp

멤버함수의 정의부분을 컴파일하는데도 클래스의 선언 정보가 필요하다. 멤버함수에서 접근하는 변수의 존재유무를 확인해야 되고, 이름공간 CAR_CONST에 선언된 상수의 사용을 위해서도 헤더파일이 포함되어야 한다.

#define  _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>
#include "Car.h"
using namespace std; 


void Car::InitMembers(const  char *ID, int fuel) {
    strcpy(gamerID, ID);
    fuelGauge = fuel;
    curSpeed = 0;
}

void Car::ShowCarState() {
    cout << "소유자ID : " << gamerID << endl;
    cout << "연료량 : " << fuelGauge << "%" << endl;
    cout << "현재속도 : " << curSpeed << "km/s" << endl<< endl;
}
 
void Car::Accel() {
    if (fuelGauge <= 0) {
        return;
    }
    else {
        fuelGauge -= CAR_CONST::FUEL_STEP;
    }

    if (curSpeed + CAR_CONST::ACC_STEP >= CAR_CONST::MAX_SPD) {
        curSpeed = CAR_CONST::MAX_SPD;
        return;
    }
    curSpeed += CAR_CONST::ACC_STEP;
}

void Car::Break() {
    if (curSpeed < CAR_CONST::BRK_STEP) {
        curSpeed = 0;
        return;
    }
    curSpeed -= CAR_CONST::BRK_STEP;
}

 

RacingMain.cpp

#include "Car.h"

int main(void)
{
    // 구조체변수의 선언 및 초기화 진행 
    Car run99;
    run99.InitMembers("run99", 100);
    run99.Accel();
    run99.Accel();
    run99.ShowCarState();
    run99.Break();
    run99.ShowCarState();

    return 0;
}

 

 

인라인함수는 헤더파일에 같이 넣어야 한다.

Car.cpp에 정의된 ShowCarState, Break 등을 인라인화 할 경우 현재 위치에서는 컴파일에러 발생.
컴파일 과정에서 함수의 호출 문이 있는 곳에 함수의 몸체 부분이 삽입되어야 하므로
함수의 호출문장은 컴파일러에 의해서 함수의 몸체로 대체되어야 하기에 |

인라인함수는 클래스의 선언과 동일한 파일에 저장되어서 컴파일러가 동시에 참조할 수 있게 해야 한다.
즉 헤더파일에 같이 몸체를 넣어주면 됨.

컴파일러는 파일 단위로 컴파일을 하기 때문에
서로 참조되지 않기 때문에 클래스의 선언과 인라인 함수의 정의를 한 파일에 묶어두어야 하는 것이다.

 

 

클래스의 내부 접근과 외부 접근
#include <iostream>
using namespace std;

class Counter {
public:
    int val; //쓰레기값

    void Increment(void) {
        val++; //내부접근
    }
};

int main()
{
    Counter cnt; // 스택 공간 내에 지역 객체가 생성된다.
    // cnt라는 이름의 객체를 Counter라는 자료형으로 생성한다     

    cnt.val = 0; //외부접근
    // 쓰레기값에서 0이 된다

    cnt.Increment();
     
    cout << cnt.val << endl;
    return 0;
}

 

 

C++에서는 접근제어 키워드로 public, protected, private 3가지를 가진다. 접근허용 범위가 다른 것
public은 외부, 내부 접근 모두 허용하며(접근제어를 안한다) private 변수는 내부 접근만 허용한다.

protected는 상속 관련 내용이라 나중에 배우며, 클래스 멤버의 접근제어를 하는 이유는 다음 장에서 배운다. 

 

 

멤버 함수의 외부 정의
#include <iostream>
using namespace std;

const int OPEN = 1;
const int CLOSE = 2;

class Door {
    // struct, class의 차이점
    // 접근제어 키워드를 지우면 에러 발생 (private로 인식)
    // class 키워드를 struct로 바꿔도 에러는 없으나
    // private : 키워드를 넣으면 컴파일되나 
    // 접근제어 키워드를 생략하면 클래스는 프라이빗, 스트럭트는 퍼블릭으로 자동 붙음 (구조체와 유일한 차이점)
    // 클래스 사용을 지향한다 -- 인터페이스 기능을 작용된다 
private:
    int state;

public:
    void Open() {
        state = OPEN;
    }
    void Close() {
        state = CLOSE;
    }
    void ShowState() {
        cout << "현재 문의 상태 : ";
        cout << ((state == OPEN) ? "OPEN" : "CLOSE") << endl;
    }
};

int main()
{
    Door d;
    // d.state=OPEN; //private
    d.Open();
    d.ShowState();
    return 0;
}

 

 

#include <iostream>
using namespace std;

const int OPEN = 1;
const int CLOSE = 2;

class Door {
    // struct, class의 차이점
    // 접근제어 키워드를 지우면 에러 발생 (private로 인식)
    // class 키워드를 struct로 바꿔도 에러는 없으나
    // private : 키워드를 넣으면 컴파일되나 
    // 접근제어 키워드를 생략하면 클래스는 프라이빗, 스트럭트는 퍼블릭으로 자동 붙음 (구조체와 유일한 차이점)
    // 클래스 사용을 지향한다 -- 인터페이스 기능을 작용된다 
private:
    int state;

public:
    void Open() {
        state = OPEN;
    }
    void Close() {
        state = CLOSE;
    }
    void ShowState() {
        cout << "현재 문의 상태 : ";
        cout << ((state == OPEN) ? "OPEN" : "CLOSE") << endl;
    }
};

int main()
{
    Door d;
    // d.state=OPEN; //private
    d.Open();
    d.ShowState();
    return 0;
}

 

 

멤버 함수의 인라인화

C는 #define 매크로로 함수를 인라인시키고, C++는 inline 키워드로 인라인시킨다.

#include <iostream>
using namespace std;

const int OPEN = 1;
const int CLOSE = 2;

class Door {
    // struct, class의 차이점
    // 접근제어 키워드를 지우면 에러 발생 (private로 인식)
    // class 키워드를 struct로 바꿔도 에러는 없으나
    // private : 키워드를 넣으면 컴파일되나 
    // 접근제어 키워드를 생략하면 클래스는 프라이빗, 스트럭트는 퍼블릭으로 자동 붙음 (구조체와 유일한 차이점)
    // 클래스 사용을 지향한다 -- 인터페이스 기능을 작용된다 
private:
    int state;

public:
   /* void Open() {
        state = OPEN;
    }*/
    void Open();
    void Close() {
        state = CLOSE;
    }
    void ShowState() {
        cout << "현재 문의 상태 : ";
        cout << ((state == OPEN) ? "OPEN" : "CLOSE") << endl;
    }
};

// 전역함수가 아니라 클래스 내 멤버함수를 정의할 때 이렇게 쓴다
void Door::Open() {
    state = OPEN;
}

int main()
{
    Door d;
    // d.state=OPEN; //private
    d.Open();
    d.ShowState();
    return 0;
}

 

 

 


객체지향 프로그래밍의 이해


객체지향프로그래밍은 현실에 존재하는 사물, 대상, 그에 따른 행동을 있는 그대로 실체화시키는 형태의 프로그래밍

객체는 1개 이상의 상태정보(데이터)와 1개 이상의 행동(기능)으로 구성된다.
데이터는 변수로 표현이 되고, (상태정보저장) 행동은 함수로 표현이 된다.

객체 생성은 변수 선언, 함수 정의가 들어간 틀이 필요하다. 그게 클래스이다.
틀을 만들고 나서 정의한 클래스를 실체화를 시켜야 객체다. 아래는 클래스 기반의 객체 생성 방법 2가지이다.

ClassName objName; 
ClassName * ptrObj = new Classname; //힙 할당방식(동적할당)

 

예제

#include <iostream>
using namespace std;

//사과장수
class FruitSeller
{
private:
	int APPLE_PRICE; //사과가격 (상수)
	int numOfApples; //재고
	int myMoney; //사과장수의 지갑
public:
	// 초기화
	void InitMembers(int price, int num, int money)
	{
		APPLE_PRICE = price;
		numOfApples = num;
		myMoney  = money;		
	}
	// 사과 판매
	int SaleApples(int money)
	{
		int num = money / APPLE_PRICE; //팔린 갯수
		numOfApples -= num;
		myMoney += money; //사과장수의 지갑
		return num;
	}
	// 수익과 재고 출력
	void ShowSalesResult() {
		cout << "남은 사과 : " << numOfApples << endl;
		cout << "판매 수익: " << myMoney<< endl <<endl;
	}
};

// 손님
class FruitBuyer {
	//프라이빗
	int myMoney; // 손님지갑
	int numOfApples; //산 사과갯수

public:
	void InitMembers(int money)
	{
		myMoney = money;
		numOfApples = 0;
	}
	// 구매기능
	// 구매대상, 구매금액이 전달
	void BuyApples(FruitSeller &seller, int money) {
		numOfApples += seller.SaleApples(money); //사과 구매하려고 함수 호출
		myMoney -= money;
	}
	void ShowBuyResult() 
	{
		cout << "현재 잔액 : " << myMoney << endl;
		cout << "사과 개수 : " << numOfApples << endl << endl;
	}
};

int main(void) 
{
	FruitSeller seller;
	seller.InitMembers(1000, 20, 0);

	FruitBuyer buyer;
	buyer.InitMembers(5000);
	buyer.BuyApples(seller, 2000); //사과 구매

	cout << "과일판매자 현황" << endl;
	seller.ShowSalesResult();
	cout << "과일구매자 현황" << endl;
	buyer.ShowBuyResult();
	
	return 0;
}

 

 

 

message passing, 객체간의 대화 방법

어떤 동작의 요구를 위해 한 객체에 다른 객체에게 메세지를 전달하는 것은 함수호출을 기반으로 한다.
이것을 메세지전달이라고 한다.

 

 

반응형

 

 


연습문제


03-1 구조체 내에 함수정의하기
struct Point
{
	int xpos;
	int ypos;
}

2차원 평면상에서의 좌표를 표현할 수 있는 구조체를 정의했다. 이 구조체를 기반으로 다음의 함수를 정의하고자 한다.
단 함수들을 구조체 안에 정의해서 다음의 형태로 main 함수를 구성할 수 있어야 한다.

void MovePos(int x, int y); //점의 좌표이동
void AddPoint(const Point &pos); //점의 좌표증가
void ShowPosition(); //현재 x,y 좌표정보 출력

실행결과는 아래와 같게끔 정의한다.

[5, 14]
[25, 44]

 

 

푼 것

#include <iostream>
using namespace std;

// 2차원에서 좌표를 구현하는 구조체
struct Point
{
	int xpos;
	int ypos;

	//점의 좌표이동
	void MovePos(int x, int y) {
		xpos += x;
		ypos += y;
	}
	
	//점의 좌표증가
	void AddPoint(const Point& pos) {		
		/*cout << "[" << xpos << "," << ypos << "]" << endl;
		cout << "[" << pos.xpos << "," << pos.ypos << "]" << endl;*/

		xpos += pos.xpos;
		ypos += pos.ypos;
	}

	//현재 x,y 좌표정보 출력
	void ShowPosition() {
		cout << "[" << xpos  << "," << ypos  << "]" << endl;
	}
};

int main(void) 
{
	Point pos1 = { 12, 4 };
	Point pos2 = { 20, 30 };

	pos1.MovePos(-7, 10);
	pos1.ShowPosition(); //[5, 14] 출력

	pos1.AddPoint(pos2);
	pos1.ShowPosition(); //[25, 44]] 출력
	
	return 0;
}

 

 

03-2 클래스의 정의

1. 계산기 기능의 Calculator 클래스를 정의해보자. 기본적으로 지니는 기능은 덧셈, 뺄셈, 곱셈, 나눗셈이며 연산을 할 때마다 어떠한 연산을 몇 번 수행했는지 기록되어야 한다. 아래 main 함수와 실행의 예에 부합하는 Calculator 클래스를 정의하면 된다. 단 멤버변수는 private으로, 멤버함수는 public으로 선언하자. 

int main(void) 
{
	Calculator cal;
	cal.Init();
	
	cout << "3.2 + 2.4 = " << cal.Add(3.2, 2.4) << endl;
	cout << "3.5 / 1.7 = " << cal.Div(3.5, 1.7) << endl;
	cout << "2.2 - 1.5 = " << cal.Min(2.2, 1.5) << endl;
	cout << "4.9 / 1.2 = " << cal.Div(4.9, 1.2) << endl;
	cal.ShowOpCount();
	return 0;
}

 

 

푼 것

#include <iostream>
using namespace std;

class Calculator {
private: 
	int AddCount;
	int SubCount;
	int MulCount;
	int DivCount;
public:
	void Init() {
		AddCount = 0;
		SubCount = 0;
		MulCount = 0;
		DivCount = 0;
	}
	// 사칙연산
	double Add(double x, double y) {		
		AddCount++;  
		return x + y;
	}
	double Sub(double x, double y) {
		SubCount++;
		return x - y;
	}
	double Mul(double x, double y) {
		MulCount++;
		return x * y;
	}
	double Div(double x, double y) {
		DivCount++;
		return x / y;
	}
	// 연산 횟수 기록
	 void ShowOpCount() {
		cout << "덧셈: " << AddCount <<endl;
		cout << "뺄셈: " << SubCount << endl;
		cout << "곱셈: " << MulCount << endl;
		cout << "나눗셈: " << DivCount << endl;
	} 
 };

int main(void) 
{
	Calculator cal;
	cal.Init();
	
	cout << "3.2 + 2.4 = " << cal.Add(3.2, 2.4) << endl;
	cout << "3.5 / 1.7 = " << cal.Div(3.5, 1.7) << endl;
	cout << "2.2 - 1.5 = " << cal.Sub(2.2, 1.5) << endl;
	cout << "4.9 / 1.2 = " << cal.Div(4.9, 1.2) << endl;
	  
	cal.ShowOpCount();
	return 0;
}

 

 

답안

#include <iostream>
using namespace std;

class Calculator {
private: 
	int AddCount;
	int SubCount;
	int MulCount;
	int DivCount;
public:
	void Init();
	double Add(double num1, double num2);
	double Sub(double num1, double num2);
	double Mul(double num1, double num2);
	double Div(double num1, double num2);
	void ShowOpCount();
};

void Calculator::Init() {
	AddCount = 0;
	SubCount = 0;
	MulCount = 0;
	DivCount = 0;
}
// 사칙연산
double Calculator::Add(double x, double y) {
	AddCount++;  
	return x + y;
}
double Calculator::Sub(double x, double y) {
	SubCount++;
	return x - y;
}
double Calculator::Mul(double x, double y) {
	MulCount++;
	return x * y;
}
double Calculator::Div(double x, double y) {
	DivCount++;
	return x / y;
}
// 연산 횟수 기록
	void Calculator::ShowOpCount() {
	cout << "덧셈: " << AddCount <<endl;
	cout << "뺄셈: " << SubCount << endl;
	cout << "곱셈: " << MulCount << endl;
	cout << "나눗셈: " << DivCount << endl;
}  

int main(void) 
{
	Calculator cal;
	cal.Init();
	
	cout << "3.2 + 2.4 = " << cal.Add(3.2, 2.4) << endl;
	cout << "3.5 / 1.7 = " << cal.Div(3.5, 1.7) << endl;
	cout << "2.2 - 1.5 = " << cal.Sub(2.2, 1.5) << endl;
	cout << "4.9 / 1.2 = " << cal.Div(4.9, 1.2) << endl;
	  
	cal.ShowOpCount();
	return 0;
}

 

 

 

2. 문자열 정보를 내부에 저장하는 Printer라는 이름의 클래스를 디자인하자. 이 클래스의 두 가지 기능은 다음과 같다.
< 1)문자열 저장 2) 문자열 출력 >아래의 main 함수와 실행의 예에 부합하는 Printer 클래스를 정의하되 멤버변수는 private으로 멤버함수는 public으로 선언하자. 

int main(void) 
{
	Calculator cal;
	cal.Init();
	
	cout << "3.2 + 2.4 = " << cal.Add(3.2, 2.4) << endl;
	cout << "3.5 / 1.7 = " << cal.Div(3.5, 1.7) << endl;
	cout << "2.2 - 1.5 = " << cal.Min(2.2, 1.5) << endl;
	cout << "4.9 / 1.2 = " << cal.Div(4.9, 1.2) << endl;
	cal.ShowOpCount();
	return 0;
}

 

 

푼 것

#include <iostream>
using namespace std;

class Printer {
private:
	char pntString[30];
public:
	void SetString(const char *s1);
	void ShowString();
};

void Printer::SetString(const char *s1) {		
	//"const char *" 형식의 인수가 "char *" 형식의 매개 변수와 호환되지 않습니다.
	// 함수의 매개변수에 const를 붙여준다.
    
    // 문자열복사
	strcpy(pntString, s1);
}

void Printer::ShowString() {
	cout << pntString << endl;
}

int main(void) 
{
	Printer pnt;
	
	pnt.SetString("Hello World");
	pnt.ShowString();

	pnt.SetString("I Love C++");
	pnt.ShowString();

	return 0;
}
728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90
02-1 참조자 기반의 call-by-reference

1. 참조자를 이용해서 다음 요구사항에 부합하는 함수를 각각 정의하고, 호출하여 결과까지 확인하라.

- 인자로 전달된 int형 변수의 값을 1씩 증가시키는 함수
- 인자로 전달된 int형 변수의 부호를 바꾸는 함수

 

푼 것

#include <iostream>
using namespace std;

void addOne(int &num) {
    num++;
}

void chgSign(int& num) {
    num = num * -1;
}

int main(void)
{
    int num = 1;
    
    addOne(num);
    chgSign(num);

    cout << num << endl;
    
    return 0;
}

 

 

2. SwapByRef2를 아래처럼 호출하면 컴파일에러가 난다. 그 이유는?

void SwapByRef2(int &ref1, int &ref2) { ... } ;
SwapByRef2(23, 45);

 

푼 것 : 상수는 참조자에 대입할 수 없어서

 

 

3. ptr1과 ptr2를 대상으로 SwapPointer(ptr1, ptr2)를 호출하고 나면 ptr1과 ptr2가 서로 가리키는 대상이 바뀌도록 SwapPointer 함수를 정의해본다.

 

푼 것

#include <iostream> 
using namespace std;

void SwapPointer(int *(&a), int* (&b)) 
{
    int temp = *b;
    *b = *a;
    *a = temp;
}

int main(void)
{
    int num1 = 5;
    int* ptr1 = &num1; //주소
    int num2 = 10;
    int* ptr2 = &num2;
    
    //cout << *ptr1 << endl;
    SwapPointer(ptr1, ptr2); 

    cout << num1 << endl;
    cout << num2 << endl;
    
    return 0;
}

 

 

02-2 const 포인터와 const 참조자

포인터 변수를 선언해서 위 변수를 가리키게 한 뒤, 이 포인터 변수를 참조하는 참조자를 선언한다.
이렇게 선언된 포인터 변수와 참조자를 이용해서 num에 저장된 값을 출력하는 예제를 만든다.

const int num=12;

 

푼 것

int main(void)
{
    const int num = 12;
    const int *ptr = &num;
    const int& ref = *ptr;
    
    cout << ref << endl;
    
    return 0;
}

 

 

02-3 구조체에 대한 new & delete 연산

아래는 2차원 평면상에서의 좌표를 표현할 수 있는 구조체를 정의했다.

typedef struct __Point
{
    int xpos;
    int ypos;
} Point;

위의 구조체를 기반으로 두 점의 합을 계산하는 함수를 다음의 형태로 정의하며 덧셈결과는 함수의 반환을 통해 얻고

Point& PntAdder(const Point &p1, const Point &p2);

임의의 두 점을 선언하여 위 함수를 이용한 덧셈연산을 진행하는 main함수를 정의해보자. 단 구조체 Point 관련 변수의 선언은 무조건 new 연산자를 이용해서 진행하며, 할당된 메모리 공간의 소멸도 빼먹지 말자. 이 문제의 해결을 위해서는 다음 두 질문에 답을 할 수 있어야 한다.

1) 동적할당한 변수를 함수의 참조형 매개변수의 인자로 어떻게 전달해야 하나?
2) 함수 내에 선언된 변수를 참조형으로 반환하려면 해당 변수는 어떻게 선언해야 하나?

 

해설

#include <iostream>
using namespace std;

// 평면상 좌표를 구하는 구조체
typedef struct __Point
{
    int xpos;
    int ypos;
} Point;

Point& PntAdder(const Point& p1, const Point& p2) {
    // 참조형으로 지역변수를 전달
    Point* pptr = new Point;
    pptr->xpos = p1.xpos + p2.xpos;
    pptr->ypos = p1.ypos + p2.ypos;
    return *pptr;
}

int main(void)
{
    // 동적할당
    // ** 2차원 좌표는 두개가 필요하다
    Point* pptr1 = new Point;   
    pptr1->xpos = 10;
    pptr1->ypos = 20;
    
    Point* pptr2 = new Point;
    pptr2->xpos = 15;
    pptr2->ypos = 25;

    // 참조형 매개변수의 인자로 전달
    Point& pref = PntAdder(*pptr1, *pptr2);
    // 출력
    cout << pref.xpos <<", " <<pref.ypos<< endl;

    // 삭제
    delete pptr1;
    delete pptr2;
    delete &pref;
    
    return 0;
}

 

 

02-4 C++ 표준함수 호출

생략

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90
C에 없던 참조자

참조자(레퍼런스)란 C에서는 없던, C++에서 새로 생긴 개념.
포인터랑 의도하는 바는 같은데 포인터의 단점이 보완되어 출시된 것.
C++ 문서에서는 포인터보다 특정 경우가 아니라면 대부분 참조자를 사용하길 권장한다.

 

값으로 전달하는 방식의 한계

1. 큰 구조체나 클래스를 함수에 전달할 때 인수의 복사본을 매개변수로 만든다.

2. 함수의 호출자에 값을 전달하는 건 반환값을 사용하는 게 유일한 방법이나
함수에서 인수를 수정하는 게 확실하고 효율적이다.

→ 그래서 참조를 통해 문제를 해결한다.

 

변수를 참조로 전달하려면 매개변수를 참조로 선언한다.
함수가 호출되면 y는 인수에 대한 참조가 된다.

int x=5;
addOne(x);

//int &y=x; 이런 의미

void addOne(int& y)
{
	y=y+1;
}

 

 

참조자는 변수에 n개로 별명을 붙여준 것

참조자의 수에는 제한이 없다.
참조자를 대상으로 참조자를 선언할 수 있다.
한번 선언된 참조자의 대상은 바꿀 수 없다. (포인터는 가능)
선언과 동시에 무조건 할당되어야 하며 포인터처럼 null은 불가하다.

 

https://wonjayk.tistory.com/253 :: 포인터와 레퍼런스 비교

 

 

&연산자의 2가지 사용 : 변수의 주소값 vs 참조자 선언

같은 &지만, 이미 선언된 변수 앞에 &연산자가 오면 변수의 주소값을 반환하고
새로 선언한 변수의 이름 앞에 &연산자가 오면 참조자의 선언을 의미한다.
또한 이 경우 동시에 null 외의 값을 할당한다.

int num1=2020;
int *ptr=&num1; // num1 변수의 주소값을 반환해서 포인터 ptr에 저장
int &num2=num1; // num1 변수에 대한 참조자 num2를 선언

num2=3047;

cout<<num1<<endl; // 3047
cout<<num2<<endl; // 3047

cout<<&num1<<endl; // 주소값이 같다
cout<<&num2<<endl;

 

 

참조자(레퍼런스)와 변수

포인터와 참조자 둘 다 특정 메모리공간을 접근하기 위한 이름이다.
C언어는 특정 공간에 하나의 이름만, C++ 은 특정 공간에 여러 이름을 부여할 수 있다.
그래서 한 변수를 참조할 참조자의 수에는 제한이 없다.
그러나 포인터처럼 선언된 참조자의 대상은 바꿀 수 없다.
지역적 참조자는 지역 변수처럼 함수를 빠져나가면 소멸된다.

int val=20;
int &ref=val;
//val == &ref
int val=20;
int &ref1=val;

int &ref2=ref1;
int &ref2=val;
// 2개는 같다

 

레퍼런스 값 변경으로 원본 값 변경이 가능하다. (원본 값의 별칭이므로)

참조 변수의 범위에는 배열 요소도 들어간다. 
배열요소는 변수로 간주되어 참조자 선언이 가능하다.

#include <iostream>

using namespace std;

int main()
{
    int arr[3]={1,3,5};
    int &ref1=arr[0];
    int &ref2=arr[1];
    int &ref3=arr[2];
    
    cout<<ref1<<endl;
    cout<<ref2<<endl;
    cout<<ref3<<endl;

    return 0;
}

 

 

포인터 변수 참조자

포인터 변수도 참조자 선언이 가능하다.

#include <iostream>

using namespace std;

int main()
{
    int num=12;
    int *ptr=&num;
    int **dptr=&ptr;
    
    int &ref=num; //12
    int *(&pref)=ptr; //ptr의 참조자
    int **(&dpref)=dptr;
    
    cout<<ref<<endl; //12
    cout<<*pref<<endl; //12
    cout<<**dpref<<endl; //12

    return 0;
}

 

 

참조자의 장점

1. &, *, 등의 연산자를 안써서 깔끔한 코드가 완성된다.

2. 포인터의 경우 발생할 수 있는 엉뚱한 메모리 수정 사고를 예방할 수 있다.

3. (나중에 배워서 알고만 넘어갈) 벡터 관련 

4. 복사생성자가 발생되지 않아 메모리공간이 절약된다.

 

 

참조자(레퍼런스) 단점? 한계점

- 변수와 차이점

1. 이름이 없는 대상(상수, NULL 등)을 레퍼런스 할 수 없다.
1)매개변수에 NULL 포인터를 넘겨주는 것이나 2)리턴값으로 NULL 포인터 반환이 안된다.

2. 참조의 대상 변경도 불가하다. 

int &ref1; //초기화 안되서 에러
int &ref2=10; //상수가 올 수 없어서 에러
int &ref=NULL; //포인터변수 선언처럼 역시 안됨

 

 

 

 

참조자의 활용에는 함수가 큰 위치를 차지한다.

  • call-by-value : 값을 인자로 전달하는 함수의 호출방식
  • call-by-reference : 주소 값을 인자로 전달하는 함수의 호출방식

 

call-by-value기반의 함수는 아래와 같고, 2개의 정수를 인자로 요구한다.
함수 내에서는 함수 외부에 선언된 변수에 접근이 불가하며 두 변수에 저장된 값을 외부에서 바꿀 수 없다.
그래서 call-by-reference 기반의 함수가 필요하다. 

int Adder(int num1, int num2) {
	return num1+num2;
}

call-by-reference 기반의 함수는 변수의 주소값을 받아서 주소값이 참조하는 영역에 저장된 값을 직접 변경할 수 있다.

 

void SwapByRef(int* ptr1, int* ptr2)
{
	int temp=*ptr1;
    *ptr1=*ptr2;
    *ptr2=temp;
}

// ... 이런식으로 호출
// SwapByRef(&val1, &val2);

 

return 부분의 코드가 없었다면 둘 다 가능하겠지만 다음처럼 정의되면 Call-by-value 방식이다.
함수의 연산 주체가 주소값일 뿐 value다.
주소값을 이용해서 함수 외부에 선언된 변수에 접근하는 call-by-reference 방식과는 거리가 멀다. 

int * SimpleFunc(int * Ptr)
{
    return ptr+1; //주소값을 증가시켜서 반환    
}

아래는 주소 값을 이요해 함수 외부에 선언된 변수를 참조하니 call-by-reference 방식
다시 정리하면 call-by-reference는 "주소 값을 전달받아서 함수 외부에 선언된 변수에 접근하는 형태의 함수호출"
주소 값이 참조의 도구로 사용됐다는 것이 판단 기준

int * SimpleFunc(int * ptr)
{
    if(ptr==NULL)   {
        return NULL;
    }
    
    *ptr=20;
    return ptr;
}

 

call-by-reference 는 1)주소값을 이용한 call-by-reference2)참조자를 이용한 call-by-reference 두가지 방식이 있다.

 

 

레퍼런스를 이용한 call-by-reference

함수 외부에 선언된 변수의 접근 가능
포인터 연산을 할 필요가 없어 안정적
함수의 호출 형태 구분이 어렵다

 

함수 호출 시 전달되는 인자로 초기화를 한다.

 

메모리공간에 각각 대입. 레퍼런스로 받고 있다
val1이라는 이름이 붙은 메모리공간에 a라는 이름을 하나 더 붙여준 것이다.

 

예제

#include <iostream>

using namespace std;

void SwapByRef2(int &ref1, int &ref2) {
    int temp=ref1;
    ref1=ref2;
    ref2=temp;
}

int main()
{
   int val1=10;
   int val2=20;
   
   SwapByRef2(val1, val2);
   
   cout << "val1:" <<val1<< endl;
   cout << "val2:" <<val2<< endl;
   
   return 0;
}

 

 

포인터를 이용한 call-by-reference

함수 외부에 선언된 변수의 접근 가능
포인터 연산에 의해 가능하다
따라서 포인터 연산의 위험성 존재

 // int v1, v2 대입한다고 가정하자 
 // 주소값을 인자로 전달할 것이다
 // v1, v2의 값을 직접 바꾸는 예
 void swap(int *a, int *b) {
    int temp=*a; 
    *a=*b;
    *b=temp;
}

 

포인터 연산의 위험성 예 : 예전이라면 운영체제 자체가 망가질 수도 있었다. 지금은 OS에 의해 막아짐.

 void swap(int *a, int *b) {
    int temp=*a; 
    a++; // 잘못된 코드 (4바이트만큼 증가) --지금은 프로그램 중단
    *a=*b; // 줄줄이 잘못
    *b=temp;
}

 

 

Call-by-value vs call-by-reference
#include <iostream>
using std::cout;
using std::endl;
using std::cin;

struct _Person {
    int age;
    char name[20];
    char personalID[20];
}

typedef struct _Person Person;

// Person 구조체변수p를 인자로 받아서 출력해주는 기능.
void ShowData(Person p) {
    // void ShowData(Person &p) {
    // void ShowData(const Person &p) { //상수화로 안정적인 코드
    cout<< "****** 개인정보 출력 ******"<<endl;
    cout<< "이름 : "<<p.name<<endl;
    cout<< "주민번호 : "<<p.personalID<<endl;
    cout<< "나이 : "<<p.age<<endl;
    
    // p.age = 20; //const를 붙이면컴파일 시 에러 발생
}

int main()
{
    Person man;
    
    cout<<"이름: ";
    cin>>man.name;
    
    cout<<"나이: ";
    cin>>man.age;
    
    cout<<"주민번호: ";
    cin>>man.personalID;
    
    ShowData(man); //call by value
    // 많은 데이터 복사가 진행된다
    
    // 레퍼런스 방식의 장점은
    // &p 일때 레퍼런스 방식으로 변경, 데이터복사가 일어나지 않는다
    // 이미 있는 메모리공간에 이름만 붙여준거라 속도가 빨라진다.
    
    // 레퍼런스 방식의 단점은 
    // ShowData값을 바꾸면 원본 데이터가 변경될 수 있는데 (불안정적)
    // 출력만 하니 딱히 문제가 없지만 상수화시키면 더 좋다
    // 그래서 16줄에 const를 붙여준다. (상수화)
    
    
    return 0;
}

 

참조자의 단점 : 함수의 호출문장만 보고도 함수의 특성을 판단할 수 있어야 하는데
함수의 원형을 확인하고, 확인결과 참조자가 매개변수의 선언에 와있다면 
함수의 몸체까지 문장단위 확인이 필요하다.
이를 해결하려면 const 키워드를 사용한다. 


int num=24;
Ha....

 

 

반환형이 참조형인 경우

#include <iostream>

using namespace std;

// 매개변수가 참조자로 선언, 참조자를 반환
int& RefRetFuncOne(int &ref){
    ref++;
    return ref;
}

// 참조자를 반환해도 반환형은 참조형이 아님
// int RefRetFuncOne(int &ref){
//     ref++;
//     return ref;
// }

int main()
{
   int num1=1;
   //cout << "1:" <<num1<< endl;
   
   int &num2=RefRetFuncOne(num1); // 참조자를 반환, 다시 참조자에 저장
//   cout << "2:" <<num1<< endl;
//   cout << "2:" <<num2<< endl;
   
   num1++;
   num2++;
   
   cout << "num1:" <<num1<< endl;
   cout << "num2:" <<num2<< endl;
   
   cout << "주소:" << &num1 << &num2 << endl;  //같은델 가리킨다
   
   return 0;
}

 

 

레퍼런스를 리턴하는 함수의 정의 예제, 응용
#include <iostream>

int& increment(int &val)
// 2) int increment(int &val) //error
// 3) int increment(int &val) //error 
{
    // val은 지역변수라 값을 복사해서 리턴
    val++;
    return val;
    //int형 vs int형 레퍼런스로 리턴?
    //현재는 레퍼런스타입으로 리턴 
}

int main()
{
    int n = 10;
    int &ref=increment(n);
    // 레퍼런스로 받고 있다 
    // n, val, ref 이 같은 공간 가리키는 중)
    
    // 2) increment에서 int 형을 리턴하면    
    // int &ref = 11; //상수가 리턴되게 된다
    // 상수를 할당할 수 없으므로 컴파일에러
    
    // 3) int ref=increment(n);
    // int ref=11; //새로운 메모리공간
    
    std::cout<<"n : "<<n<<std::endl;
    std::cout<<"ref : "<<ref<<std::endl;
    
    return 0;
}

 

작성하면 안되는 예시 (컴파일에러는 없다)

출력결과가 나오고 작동은 되나 나중에 문제의 소지가 있어서 이렇게 작성하지 않는다.

#include <iostream>

int& function(void)
{
    int val = 10;
    return val;
    // 지역변수는 레퍼런스타입으로 리턴하면 안된다.
}

int main()
{
    int &ref=function();
    // val이 사라지면서 레퍼런스하던 대상이 사라진다
    std::cout<<"ref : "<<ref<<std::endl;
    
    return 0;
}

 

 


02.5. malloc&free를 대신하는 new&delete


추가적인 메모리공간을 실시간으로 확보해야할 예 : 채팅 대화방에 2명이 있는데 3명이 됐다던가, 프로그램에 자료를 추가 입력해 저장하는 등의 예가 있다.

 

C의 malloc / free : 힙의 메모리 할당 및 소멸에 필요한 함수

힙 영역에 동적으로 메모리를 할당하는 함수로 함수 호출 시 할당하고자 하는 메모리의 크기를 바이트 단위로 전달하면 그 크기만큼 메모리 할당 후 할당한 메모리의 주소 즉 첫 번째 바이트의 주소를 리턴한다. 메모리 할당에 실패하면 NULL 리턴. 반환값이 주소값이어서 포인터로 받아야 한다.

C의 동적할당 예시로, 길이정보를 인자로 받아 해당 길이의 문자열 배열을 생성하고, 그 배열의 주소값을 반환한다.

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
#include <stdlib.h>

using namespace std;

char* MakeStrAdr(int len) {
    char* str = (char*)malloc(sizeof(char)*len); 
    //문자열 저장을 위한 배열을 힙에 할당중
 
    return str;
}

int main(void)
{
    char* str = MakeStrAdr(20);
    strcpy(str, "I am so");
    cout << str << endl;
    free(str);//힙에 할당한 메모리공간 소멸
    
    return 0;
}

 

단점 : 1) 할당할 대상의 정보를 바이트 단위로 전달해야 하며 2) 반환형이 void형 포인트라 적절한 형변환을 거쳐야 한다 → 보완된 게 C++ new, delete 이다.

 

C calloc

C malloc : 할당 메모리를 초기화하지 않아 쓰레기값이 들어있음 
C calloc : 할당된 메모리공간을 모두 0으로 초기화시킴

void* calloc(size_t n, size_t size); // 메모리의 단위 갯수, 하나당 크기 (예 : 4개 * 4바이트)
char* p1 = (char*)malloc(sizeof(char)*10));
char* p2 = (char*)calloc(10, sizeof(char));



void포인터 (void*)

어떤 변수의 주소값을 담을 것인지 지정된 타입이 없는 것
추후 casting을 통해 타입을 바꾸어 줄 수 있다.
메모리의 주소만 있고 데이터형이 없는 것

 

 

C realloc()

malloc이나 calloc으로 메모리를 할당한 뒤 이 공간의 크기를 조절하기 위해 사용한다.

void* realloc(void*p, size_t size);
char* p = (char*)malloc(sizeof(char)*10); 
p = (char*)realloc(p, 12); // 메모리 공간 확장

 

 

new와 delete 연산자

malloc, free함수에 비해 사이즈 계산, 포인터형 변환의 필요가 줄었다.

new 연산자 : 데이터형 저장하기 위한 메모리공간을 힙에 할당
필요 메모리공간만큼 자동계산해서 힙에 할당 후 알아서 맞는 데이터형 포인터로 주소값 리턴한다.

* 주의 : C에서 malloc 해지는 단순히 free()를 썼지만 C++에서는 할당한 메모리공간 해지로 delete 를 쓸 때 변수의 경우는 같지만, 배열인 경우 명시적으로 인덱스 연산자를 붙인다.

 

new 사용방법

할당할 대상의 정보를 직접 명시한다. 

int * ptr1=new int;
int * arr1=new int[3];

 

delete 사용방법

new 연산 시 반환된 주소 값을 대상으로 delete 연산을 진행한다.

delete ptr1;
delete []arr1;

 

C++ 동적할당 예시
#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
#include <string.h>
using namespace std;

char* MakeStrAdr(int len) {
    char* str = new char[len];
    return str;
}

int main(void)
{
    char* str = MakeStrAdr(20);
    strcpy(str, "I am so");
    cout << str << endl;
    delete[]str;
    return 0;
}

 

 

C++에서는 malloc 과 free 함수의 호출이 문제가 될 수 있다

new와 malloc 함수의 동작방식에는 차이가 있다...만 기억하고
나머지는 클래스, 객체, 생성자에 대해 알고나면 정확히 이해할 수 있다

//#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdlib.h>
using namespace std;

class Simple 
{
    public : 
        Simple()
        {
            cout << "am simple consturctor" << endl;
        }
};

int main(void)
{
    cout << "case 1";
    Simple* sp1 = new Simple;  //힙 영역에 변수 할당

    cout << "case 2";
    Simple* sp2 = (Simple*)malloc(sizeof(Simple) * 1); //힙 영역에 변수 할당
    
    cout << endl << "end of main" << endl;
    
    delete sp1; //소멸
    free(sp2); //소멸

    return 0;
}
//#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdlib.h>
using namespace std;

class Simple 
{
    private:
        string m_strmyname;
        int m_myage;
        int* m_p;

    public : 
        Simple()        // 생성자.
            : m_strmyname("햄버거")
            , m_myage(12)
            , m_p(nullptr)
        {
            m_strmyname = "롯데리아";
            m_p = new int();
            cout << "am simple consturctor" << endl;
        }

        ~Simple()        // 소멸자
        {
            if (m_p)
            {
                delete m_p;
                m_p = nullptr;
            }
            cout << "am simple disturctor" << endl;
        }

        void myfunc()
        {

        }
};

int main(void)
{
    cout << "case 1";
    Simple* sp1 = new Simple;  //힙 영역에 변수 할당

    cout << "case 2";
    Simple* sp2 = (Simple*)malloc(sizeof(Simple) * 1); //힙  영역에 변수 할당
    
    cout << endl << "end of main" << endl;
    
    delete sp1; //소멸
    free(sp2); //소멸

    return 0;
}

 

 

malloc을 잘못 사용하게 되면 오류나 메모리 낭비를 하게 될 가능성이 높아진다. (출처)

 

더보기 (malloc/free, new/delete)

 

[c++] malloc/free, new/delete

malloc / free - malloc 은 "stdlib.h" 헤더파일에 포함되어 있다. - malloc 함수는 입력받은 바이트 크기만큼 메모리 공간을 Heap Memory에 할당해준다. 그리고 void *를 리턴하므로 sizeof 연산자와 캐스트 연산..

armful-log.tistory.com

 

 

 

힙에 할당된 변수 포인터 없이 접근해서 값 변경하기

참조자의 선언은 상수아닌 변수를 대상으로만 가능하다. (const참조자 제외)
new 연산자를 이용해 할당된 메모리 공간에도 참조자의 선언이 가능하다. 변수와 같은 특징을 지녔으므로
(메모리 공간할당, 이름 존재)

int *ptr=new int;
int &ref=*ptr; //힙 영역에 할당된 변수에 대한 참조자 선언
ref=20;
cout<<*ptr<<endl; //20

 

 

null포인터 리턴하는 new연산자

원하는 메모리 공간을 할당하지 못하는 경우(메모리 부족) NULL 포인터를 리턴한다.
새로운 표준에 관한 내용은 예외처리를 통해 다시 언급한다

// OS가 좋아져서 이 방식으로는 잘 안한다
int* arr=new int[size]; //배열의 동적할당
if(arr=NULL) { //동적할당검사
    cout<<"메모리할당실패"<<endl;
    // 0이면 메모리가 꽉 참
    return -1; //프로그램 종료
}
#include <iostream>

// 이걸로 조절
#define DEBUG 1;
// #define DEBUG 0;

//조건부컴파일
int main()
{
    int size;
    std::cout<<"할당하고자 하는 배열의 크기: ";
    std::cin>>size;
    
    int* arr=new int[size]; //배열의 동적할당
        
#if DEBUG==1
    cout<<"디버그 모드 입니다"<<endl;
    if(arr=NULL) 
    {
        cout<<"메모리할당 실패"<<endl;
        return  -1; //프로그램 종료
    }
    
#endif
    for(int i=0; i<size; i++)
        arr[i]=i+10;
        
        ....
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
 
void main()
{
    int* pPoint;
 
    int nCount = 0;
 
    printf("malloc size ?? ");
    scanf("%d", &nCount);
 
    pPoint = (int*)malloc(sizeof(int) * nCount);
 
    int i = 0;
    for (i = 0; i < nCount; i++)
    {
        printf("input pPoint[%d] : ", i);
        scanf("%d", &pPoint[i]);
    }
 
    for (i = 0; i < nCount; i++)
        printf("Output pPoint[%d] : %d\n", i, pPoint[i]);
 
    free(pPoint);
}

 

 

 

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90

함수 포인터 : 윈도우 프로그래밍 API ... 이벤트 드리븐과 관련
void 포인터 : 메모리의 동적 할당과 관련

 

함수 포인터

함수의 이름은 포인터다.
함수가 실행될 경우 그 메모리공간을 가리킨다.

사실 fct(a,b) 같은건 fct함수가 가리키는 메모리공간 안에 존재하는 fct함수를 가리키면서 인자를 전달한다는 뜻이었다.

 

함수 포인터 타입

함수포인터는 주소(함수가 존재하는 위치를 가리키는) 와 자료형정보가 있을 것
함수포인터의 타입을 결정 짓는 요소는 리턴타입 + 매개변수타입 이다.

// fct1의 포인터 타입은 리턴타입int, 매개변수타입 int형 데이터를 
// 하나 받을 수 있는 함수를 가리킬 수 있다.
// 포인터  int (*fPtr1)(int); 얘는 변수
// fct1은 상수
int fct1(int a) {
    a++;
    return a;
}

// 리턴타입 더블이고 인자값으로 더블형 데이터 2개를 받을 수 있는
// 함수를 가리킬 수 있는 함수 포인터다.
// 포인터는 double (*fPtr2)(double, double);
// *fPtr2과 fct2는 타입이 같으나 변수, 상수인지만 다르다.
// *fPtr2과 fct2는 타입이 같아 대입이 가능하다.
double fct2 (double a, double b) {
   double add=a+b;
   return add;
}

 

예제

#include <stdio.h> 

void Add(int a, int b);
void SPrint(char* str);

int main()
{
    char* string = "Function Pointer";
    int a=10, b=20;
    
    void (*fPtr1)(int, int)=Add;
    void (*fPtr2)(char*)=SPrint;
    
    printf("%d, %d\n", Add, fPtr1); // 같은 주소값 
    
    // 함수 포인터에 의한 호출
    fPtr1(a, b);
    fPtr2(string);
    
    // 함수 이름으로 호출
    Add(a,b);
    SPrint(string);
    
    return 0;
}

void Add(int a, int b){
    printf("덧셈 결과 : %d\n", a+b);
}

void SPrint(char * str){
    printf("입력 문자열 : %s\n", str);
}

 

 

void형 포인터

자료형에 대한 정보가 제외된 주소 정보를 담을 수 있는 형태의 변수

char c='a';
int n = 10;
void* vp; //void 포인터 선언
vp=&c;
vp=&n;

 

주소값을 저장할 수 있는 메모리공간(변수)이나 일반적 포인터와 달리
void형 포인터는 저장할 수 있는 저장할 수 있는 주소값의 형태가 제한되어 있지 않다 

int* a, char* b 등은 자료형이 제한적이나
void* c는 int/char 등 변수의 주소값 int형 포인터의 주소값까지 전부 저장이 가능하다.

 

오히려, 만능이 아니라 제한된 형태의 기능을 지닌다
타입 정보가 없어 포인터 연산, 메모리 참조와 관련된 일에 활용할 수 없다.

 

int n = 10;
void* vp = &n; //몇 바이트를 참조할 지 애매해진다. 주소값만 갖고 있다
*vp=20; //Error
vp++; //Error

 

malloc 함수, 동적할당 시 void형 포인터가 활용될 것이다.

 

 

main 함수의 인자 전달

argv로는 배열요소의 주소값을 전달한다

 

 

#include <stdio.h> 

 
int main(int argc, char **argv)
{
    int i=10;
    printf("전달된 문자열의 수 : %d\n", argc);
    
    for(i=0; i<argc; i++){
        printf("%d번째 문자열 : %s\n", i+1, argv[i]);
    }
    
    return 0;
}
728x90
728x90
블로그 이미지

coding-restaurant

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

,

C 구조체

C, C++ 2021. 9. 12. 02:48
728x90
728x90
TCP 스쿨 11.40 11.41. 11.42. 11.43 보고 필기

 

구조체(structure type)

구조체란 사용자가 C언어의 기본 타입을 가지고 새롭게 정의할 수 있는 사용자 정의 타입 (사용자정의 자료형)
배열 : 같은 타입의 변수 집합 vs 구조체 : 다양한 타입의 변수 집합을 하나의 타입으로 나타낸 것

 

구조체를 구성하는 변수

 구조체의 멤버 또는 멤버변수(member variable) 라 한다.

 

구조체 정의
struct 구조체이름
{
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
}; // 세미콜론에 주의

 

구조체 선언
struct 구조체이름 구조체변수이름;

 

동시에 정의와 선언하기

struct 구조체이름
{
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
} 구조체변수이름;

 

 

typedef  : 이미 존재하는 타입에 새로운 이름을 붙일 때 쓴다

구조체 변수를 선언하거나 사용할 때에는 매번 struct 키워드를 사용하여 구조체임을 명시해야 하는데
typedef 키워드를 사용하여 구조체에 새로운 이름을 선언하면 매번 struct 키워드를 사용하지 않아도 된다.
구조체의 정의와 typedef 선언을 동시에 할 시 구조체 이름 생략 가능하다.

 

선언하기

typedef struct 구조체이름 구조체의 새로운이름;

 

동시에 정의와 선언하기

typedef struct (구조체이름)
{
    멤버변수1의타입 멤버변수1의이름;
    멤버변수2의타입 멤버변수2의이름;
    ...
} 구조체의새로운이름;

 

 

 

구조체 멤버 접근과 초기화

구조체에서 구조체 멤버로의 접근은 멤버 연산자 (.)를 사용한다.
구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다.

구조체변수이름.멤버변수이름

 

 

구조체 멤버 초기화

구조체 변수 초기화는 멤버 연산자(.) 와 중괄호 { } 를 사용한다.

 

원하는 멤버 변수만 초기화

원하는 멤버 변수만 초기화 가능하며, 초기화하지 않은 멤버변수는 0으로 초기화된다.

// 원하는 멤버 변수만 초기화
// 순서 상관 없음
구조체변수이름 = {.멤버변수1이름 = 초깃값, .멤버변수2이름 = 초깃값, ...};

 

배열의 초기화 방법으로 초기화

구조체 정의에서 멤버 변수가 정의된 순서에 따라 차례로 초기값 설정, 나머지 멤버변수는 0으로 초기화

// 배열의 초기화와 같은 방법으로 구조체 변수 초기화
// 정의된 순서에 따라 차례대로 초깃값이 설정되며, 나머지 멤버 변수는 0으로 초기화
구조체변수이름 = {멤버변수1의초깃값, 멤버변수2의초깃값, ...};

 

구조체 변수 초기화 예제

#include <stdio.h>  

struct book.
{
    char title[30];
    char author[30];
    int price;
};  

int main(void)
{
    struct my_book = {"HTML과 CSS", "홍길동", 28000};
    struct java_book = {.title = "Java language", .price = 30000};  

    printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        my_book.title, my_book.author, my_book.price);
        
    printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        java_book.title, java_book.author, java_book.price);

    return 0;
}
#include <stdio.h>  

// typedef 키워드 사용으로 새 이름 선언 후 사용하는 예제
typedef struct
{
    char title[30];
    char author[30];
    int price;
}  TEXTBOOK;  

int main(void)
{
    TEXTBOOK my_book = {"HTML과 CSS", "홍길동", 28000};
    TEXTBOOK java_book = {.title = "Java language", .price = 30000};  

    printf("첫 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        my_book.title, my_book.author, my_book.price);
        
    printf("두 번째 책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n",
        java_book.title, java_book.author, java_book.price);

    return 0;
}

 

 

 

 

구조체 배열 선언

C 언어에서 배열의 요소가 될 수 있는 타입에는 제한이 없다. 즉 구조체도 배열의 한 요소가 될 수 있다.

구조체 배열 선언은 다른 타입의 배열을 선언하는 방법과 같고,
구조체 배열에서 각 배열 요소로 접근하는 방법도 일반 배열의 접근 방법과 같고,
멤버 연산자(.)를 사용하여 각 배열 요소의 멤버에 접근한다.

#include <stdio.h>

struct book
{
	char title[30];
	char author[30];
	int price;
};

int main(void)
{
	struct book text_book[3] = 
	{
		{"국어", "홍길동", 15000},
		{"영어", "이순신", 18000},
		{"수학1", "강감찬", 10000}
	};
	
	puts("각 교과서의 이름은 다음과 같습니다.");
	printf("%s, %s, %s\n", text_book[0].title, text_book[1].title, text_book[2].title);
	return 0;
}

구조체 배열 text_book의 메모리 상태

 

 

구조체를 가리키는 포인터

구조체 변수를 가리키는 포인터는 다음처럼 선언한다.

// struct 구조체이름* 구조체포인터이름;
struct book* ptr_my_book;

 

구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다. 그러나
배열의 경우와 달리 구조체의 이름은 구조체를 가리키는 주소가 아니다.
즉 포인터에 할당할 때에는 구조체는 반드시 주소 연산자(&)를 사용해야 한다.

 

 

구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법

구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법은 아래 두 가지이다.
화살표 연산자가 좀 더 많이 사용된다.

1. 참조 연산자(*) 이용
참조 연산자(*)는 멤버 연산자(.)보다 연산자 우선순위가 낮으므로 반드시 괄호(())를 사용

// (*구조체포인터).멤버변수이름
(*ptr_my_book).author


2. 화살표 연산자(->) 이용

// 구조체포인터 -> 멤버변수이름
ptr_my_book -> author

 

예시

#include <stdio.h>
#include <string.h>

struct book
{
	char title[30];
	char author[30];
	int price;
};

int main(void)
{
	struct book my_book = {"C언어 완전 해부", "홍길동", 35000}; //title, author, price
	struct book* ptr_my_book;	// 구조체 포인트 선언 
	
	ptr_my_book = &my_book; 
	
	strcpy((*ptr_my_book).title, "C언어 마스터");	// 참조 연산자(*)를 이용하는 방법
	strcpy(ptr_my_book->author, "이순신");			// 화살표 연산자(->)를 이용하는 방법
	my_book.price = 32000;							// 구조체 변수를 이용한 직접 수정 
	
	printf("책의 제목은 %s이고, 저자는 %s이며, 가격은 %d원입니다.\n", my_book.title, my_book.author, my_book.price);
	return 0;
}

 

 

함수와 구조체

C에서는 함수를 호출할 때 전달되는 인수나, 함수가 종료될 때 반환되는 값으로 구조체를 사용할 수 있고
기본 타입과 똑같은 방식으로 사용한다.
구조체를 가리키는 포인터나 구조체의 한 멤버변수만을 사용할 수도 있다.

 

구조체를 인수로 전달하는 예제

구조체를 인수로 전달하면 장점 : 함수가 원본 구조체의 복사본을 갖고 작업하여 안전하다.

#include <stdio.h>
#include <typeinfo>    
#include <iostream>    

typedef struct
{
	int savings;
	int loan;	
} PROP;

int calcProperty(int, int);

int main(void)
{
	int hong_prop;
	PROP hong =	{10000000, 4000000};
	
	hong_prop = calcProperty(hong.savings, hong.loan);	// 구조체의 멤버 변수를 함수의 인수로 전달함 
	
	printf("홍길동의 재산은 적금에 %d원 대출 %d원을 제외한 총 %d원입니다.\n", hong.savings, hong.loan, hong_prop);
	
	std::cout << typeid(hong).name() << std::endl;
	
 	printf("%s\n", typeid(hong_prop).name());
 	printf("%s\n", typeid(calcProperty).name());
	
	return 0;
}

int calcProperty(int s, int l)
{
	return (s - l);
}

 

 

함수의 인수로 구조체의 주소를 직접 전달하는 예제

구조체 포인터를 인수로 전달하는 방식의 장점 : 구조체의 복사본이 아닌 주소 하나만 전달해로 처리가 빠르다. 
하지만 호출된 함수에서 원본 구조체에 직접 접근하므로 원본 데이터의 보호 측면에서는 매우 위험

 

#include <stdio.h>

typedef struct
{
	int savings;
	int loan;	
} PROP;

int calcProperty(PROP*);

int main(void)
{
	int hong_prop;
	PROP hong =	{10000000, 4000000};
	
	hong_prop = calcProperty(&hong);	// 구조체의 주소를 함수의 인수로 전달함. 
	
	printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", hong.savings, hong.loan, hong_prop);
	return 0;
}

int calcProperty(PROP* money)
{
	money->savings = 100;	// 호출된 함수에서 원본 구조체의 데이터를 변경
	return (money->savings - money->loan);
}

 

 

보완 방법 : const를 사용하여 전달된 인수를 함수 내에서 직접 수정 못하게 한다.

#include <stdio.h>

typedef struct
{
	int savings;
	int loan;	
} PROP;

PROP initProperty(void);
int calcProperty(const PROP*);

int main(void)
{
	PROP prop;
	int hong_prop;
	
	prop = initProperty();
	hong_prop = calcProperty(&prop);
	
	printf("홍길동의 재산은 적금 %d원에 대출 %d원을 제외한 총 %d원입니다.\n", prop.savings, prop.loan, hong_prop);
	return 0;
}

PROP initProperty(void)
{
	PROP hong =	{10000000, 4000000};
	return hong;	// 구조체를 함수의 반환값으로 반환함.
}

// const 키워드를 사용하여 구조체의 데이터를 직접 수정하는 것을 방지
int calcProperty(const PROP* money)	
{
	return (money->savings - money->loan);
}

 

initProperty() 함수는 반환값으로 구조체를 직접 반환한다.
기본적으로 C언어의 함수는 한 번에 하나의 데이터만을 반환할 수 있으나
구조체를 사용하면 여러 개의 데이터를 한 번에 반환할 수 있다.

 

 

중첩 구조체

C에서는 구조체를 정의할 때 멤버 변수로 다른 구조체를 포함할 수 있다.

#include <stdio.h>

struct name
{
	char first[30];
	char last[30];
};

struct friends
{
	struct name friend_name;
	char address[30];
	char job[30];
};

int main(void)
{
	struct friends hong = 
	{
		{ "길동", "홍" },
		"서울시 강남구 역삼동",
		"학생"
	};
	
	printf("%s\n\n", hong.address);
	printf("%s%s에게,\n", hong.friend_name.last, hong.friend_name.first);
	printf("그동안 잘 지냈니? 아직 %s이지?\n", hong.job); 
	puts("공부 잘 하고, 다음에 꼭 한번 보자.\n잘 지내^^");
	return 0;
}

 

 

구조체의 크기

구조체의 크기는 멤버 변수들의 크기에 따라 결정되나 언제나 멤버 변수들의 크기 총합과 같지는 않다. 
구조체 크기 변수 크기 총합과 다른 이유 : 흰트 (패딩)

더보기

c는 구조체의 멤버 중 가장 크기가 큰 걸 기준으로 그 배수로 구조체의 사이즈를 늘려나간다.
그리고 기준보다 작은 멤버들은 패딩을 더해 기준과 같게 만든다.
패딩이 생기는 이유는 CPU가 메모리에 접근하는 걸 최소화해 속도를 빠르게 하기 위해서다. 
그래서 멤버의 위치만 바껴도 구조체의 크기가 달라지기도 한다. 
구조체의 최적화를 위해서 멤버의 위치도 고려해야 한다.

 

예시

#include <stdio.h>

typedef struct
{
	char a;
	int b;
	double c;
} TYPESIZE;

int main(void)
{
	puts("구조체 TYPESIZE의 각 멤버의 크기는 다음과 같습니다.");
	printf("%d %d %d\n", sizeof(char), sizeof(int), sizeof(double));
	
	puts("구조체 TYPESIZE의 크기는 다음과 같습니다.");
	printf("%d\n", sizeof(TYPESIZE));
	return 0;
}

 패딩 바이트(padding byte)는 3바이트

 

 

바이트 패딩 (byte padding)

구조체를 메모리에 할당할 때 컴파일러는 프로그램 속도 향상을 위해 바이트 패딩 규칙을 사용한다.
구조체는 다양한 크기의 타입을 멤버변수로 가지는 타입이며, 컴파일러는 메모리 접근을 쉽게 하려고 크기가 가장 큰 멤버 변수를 기준으로 모든 멤버변수의 메모리 크기를 맞춘다. 이게 바이트 패딩이며 아까도 더보기에 설명한 부분이다.
이 때 추가되는 바이트를 padding byte라 한다.

 

 

공용체 (union)

거의 구조체와 같은데, 모든 멤버 변수가 하나의 메모리 공간을 공유한다는 점이 구조체와 차이점이다.
모든 멤버변수가 같은 메모리를 공유해 공용체는 한 번에 하나의 멤버 변수밖에 사용할 수 없다.

 

순서가 불규칙하며 미리 알 수 없는 다양한 타입의 데이터 저장용으로 나온 타입이다.
가장 큰 멤버 변수의 자료형의 공간(크기)를 공유한다.

 

union A {
	int x; //가장 큰 int 자료형의 공간 공유
	char y;
}
// 공용체 정의
union 공용체이름 {
	자료형 멤버이름;
};

// 공용체 변수 선언
union 공용체이름 변수이름;

// 동시에 정의, 선언
union 공용체이름{
	자료형 멤버이름;
} 변수;

// 동시에 정의, 별칭
typedef union 공용체이름{
	자료형 멤버이름;
} 공용체별칭;

// 익명 공동체
typedef union {
	자료형 멤버이름;
} 공용체별칭;


공용체 멤버 접근은 (.) 사용
공용체 배열을 사용하면 같은 크기로 구성된 배열 요소에 대한 다양한 크기의 데이터를 저장할 수 있다.

 

공용체 멤버 변수를 하나만 초기화했는데 나머지 멤버 변수들이 같은 데이터를 공유하는 걸 알 수 있는 예제

#include <stdio.h>

typedef union
{
	unsigned char a;
	unsigned short b;
	unsigned int c;
} SHAREDATA;

int main(void)
{
	SHAREDATA var;
	var.c = 0x12345678;
	
	printf("%x\n", var.a);
	printf("%x\n", var.b);
	printf("%x\n", var.c);
	return 0;
}

 

 

 

열거체 (enumerated types)

새로운 타입을 선언하면서, 동시에 해당 타입이 가질 수 있는 정수형 상수값도 같이 명시할 수 있는 타입.
열거체 사용의 장점 : 프로그램의 가독성 좋아짐, 변수가 지니는 값에 의미 부여 가능

enum 키워드를 사용해서 선언한다.
사용자가 별도로 멤버에 해당하는 상수값을 명시할 수 있다.
명시하지 않으면 0부터 시작되며, 그 다음 멤버 값은 바로 앞 멤버값보다 1만큼 증가하여 정의된다.

enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};

 

예제

#include <stdio.h>

enum Weather {SUNNY = 0, CLOUD = 10, RAIN = 20, SNOW = 30};

int main(void)
{
	enum Weather wt;
	
	wt = SUNNY;
		
	switch (wt)
	{
		case SUNNY:
			puts("오늘은 햇볕이 쨍쨍!");
			break;
		case CLOUD:
			puts("비가 올락말락하네요!");
			break;
		case RAIN:
			puts("비가 내려요.. 우산 챙기세요!");
			break;
		case SNOW:
			puts("오늘은 눈싸움하는 날!");
			break;
		default:
			puts("도대체 무슨 날씨인건가요!!!");
	}
	
	puts("각각의 열거체 멤버에 해당하는 정수값은 다음과 같습니다.");
	printf("%d %d %d %d\n", SUNNY, CLOUD, RAIN, SNOW);
	return 0;
}

 

#include <stdio.h>

enum Days {MON, TUE, WED, THU, FRI, SAT, SUN};

int main(void)
{
	enum Days today;
	
	today = SAT;
	
	if (today >= SAT && today <= SUN)
	{
		puts("오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!");
	}
	else
	{
		printf("주말까지 %d일 남았어요~ 조금만 더 힘내자구요!", 5 - today);
	}

	puts("각각의 열거체 멤버에 해당하는 정수값은 다음과 같습니다.");
	printf("%d %d %d %d %d %d %d\n", MON, TUE, WED, THU, FRI, SAT, SUN);
	return 0;
}

 

 

 

구조체와 비트 

지금까지 구조체의 멤버는 각 자료형 크기만큼 공간을 차지했는데
구조체 비트 필드를 사용하면 구조체 멤버를 비트 단위로 저장할 수 있다.

CPU나 기타 칩의 플래그를 다루는 저수준(low level) 프로그래밍을 할 때 기본 자료형보다 더 작은 비트 단위로 값을 가져오거나 저장하는 경우가 많으므로 구조체 비트 필드가 유용하게 사용

모든 정수 자료형을 사용할 수 있고 보통 부호 없는(unsigned) 자료형을 주로 사용한다.
실수 자료형은 비트 필드로 사용할 수 없다. 멤버를 선언할 때 : (콜론) 뒤에 비트 수를 지정해준다.

struct 구조체이름 {
    정수자료형 멤버이름 : 비트수;
};

 

사용 예제

#include <stdio.h>

struct Flags {
    unsigned int a : 1;     // a는 1비트 크기
    unsigned int b : 3;     // b는 3비트 크기
    unsigned int c : 7;     // c는 7비트 크기
};

int main()
{
    struct Flags f1;    // 구조체 변수 선언

    f1.a = 1;      //   1: 0000 0001, 비트 1개
    f1.b = 15;     //  15: 0000 1111, 비트 4개
	// 비트 필드에 지정한 비트수보다 할당한 비트 수가 많다
    f1.c = 255;    // 255: 1111 1111, 비트 8개

    printf("%u\n", f1.a);    //   1:        1, 비트 1개만 저장됨
    printf("%u\n", f1.b);    //   7:      111, 비트 3개만 저장됨
    printf("%u\n", f1.c);    // 127: 111 1111, 비트 7개만 저장됨
    
    printf("%d", sizeof(struct Flags));    // 4: 멤버를 unsigned int로 선언했으므로 4

    return 0;
}

모자라서 할당한 값과 다른 값이 나온다.

 

 

비트 필드 구조체의 크기는 예제에서 멤버를 unsigned int로 선언하여 4가 나온다.
비트필드의 멤버를 선언하는 자료형보다 큰 비트 수는 지정할 수 없다. 

unsigned int a : 37; // error

32비트까지 가능.

 


비트 필드에는 지정 비트 수만큼만 저장되고 나머지 비트는 버려진다. 
비트 필드의 각 멤버는 최하위비트 (Least Significant Bit, LSB)부터 차례로 배치된다.
예제에서는 a가 최하위비트에 온다.

 

https://dojang.io/mod/page/view.php?id=472

 

 

 

비트 필드와 공용체 함께 사용

코드에서 값을 지정 시 비트 필드를 사용하지만 CPU나 칩에 값 설정 시 모든 비트를 묶어서 한번에 저장한다.
한번에 저장하는 방법은 공용체를 비트 필드와 함께 사용하는 것.
비트 필드로 사용할 멤버는 익명 구조체로 감싼다.

 

 

#include <stdio.h>

struct Flags {
    union {    // 익명 공용체
        struct {    // 익명 구조체
        // 비트 수 지정
            unsigned short a : 3;    // a는 3비트 크기
            unsigned short b : 2;    // b는 2비트 크기
            unsigned short c : 7;    // c는 7비트 크기
            unsigned short d : 4;    // d는 4비트 크기
        };                           // 합계 16비트
        unsigned short e;    // 2바이트(16비트)
    };
};

int main()
{
    struct Flags f1 = { 0, };    // 모든 멤버를 0으로 초기화

	// 비트 필드에 값 할당 
    f1.a = 4;     //  4: 0000 0100  
    f1.b = 2;     //  2: 0000 0010 
    f1.c = 80;    // 80: 0101 0000  
    f1.d = 15;    // 15: 0000 1111  
 
    printf("%u\n", f1.e);    // 64020: 1111 1010000 10 100

    return 0;
}

 

 

 

반응형

 

 

 

문제 : Flags 안을 채워서 3, 1, 63이 출력되게 하시오.
#include <stdio.h>

struct Flags {
    
};

int main()
{
    struct Flags f1;

    f1.a = 0xffffffff; 
    f1.b = 0xffffffff; 
    f1.c = 0xffffffff; 

    printf("%u %u %u\n", f1.a, f1.b, f1.c);

    return 0;
}

 

해설 : 비트 수를 조절하여 값을 도출한다.
0xffffffff 는 1이 32개므로 모든 비트가 1로 채워진다. 각 값이 1이 몇 개인지 판단하면 된다.
3, 1, 63을 표현하려면 3은 1이 2개, 1은 1이 1개, 63은 1이 6개 필요하다.

#include <stdio.h>

struct Flags {
    unsigned int a : 2;
    unsigned int b : 1;
    unsigned int c : 6;
};

int main()
{
    struct Flags f1;

    f1.a = 0xffffffff; 
    f1.b = 0xffffffff; 
    f1.c = 0xffffffff; 

    printf("%u %u %u\n", f1.a, f1.b, f1.c);

    return 0;
}

 

 


 

 

참고한 곳

TCP 스쿨 : http://tcpschool.com/c/c_struct_intro

코딩공장 : https://coding-factory.tistory.com/639

모두의 코드 : https://modoocode.com/55

 

 

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90
미래유망기술세미나 온라인 참여 후 내용정리

✔ 일시: 2021. 09. 10 (금) 13:00~18:00
✔ 온라인 생중계: 2021 미래유망기술세미나 홈페이지

 

http://www.2021emergingtech.re.kr/program.asp

 

 

뉴노멀시대, 시장의 대응 방법 (야놀자, 야놀자 클라우드)

01. 김종윤 대표이사_발표자료.pdf
3.20MB

 

 

1) "ESG" 트랜드 , 그리고 여행 산업과의 관계


2)

 

seamless - 지역과 지역간의 경계가 없는 맵 방식.  

 

DIDs - 개인정보를 하나하나 다 쪼갠 블록체인 기술..해킹에 좀 안전하다
새로운 시장을 만든다

소품종 대량생산 -> 다품종 적량생산

메타플랫폼은 전부 6가지 돌이 있어야 하며, 운영하는 사람에 따라서도 큰 차이가 난다.
자동화는 쓸데 없는 걸 하지 않게 하는 것. 이다.

이미 Sass 방식의 ERP 솔루션을 해외에도 도입한 글로벌 기업이다.
업그레이드가 언제든지 자동으로 되어있다. 

앱서비스 의 B2C 관점 말고도 글로벌 메타 플랫폼  (레저, 주거, 레스토랑 등)이 솔루션 안에서 데이터가 돌게 할 거다.
그리고 디지털 트랜스포메이션과 ESG에 대비되어 있다.

 

 

메타버스 시대의 새로운 고객 경험과 삶의 변화 (SK 텔레콤)

강의자 요청으로 ppt 자료 비공개.

 

메타버스 : 1992 소설에 나온 것으로 시공간을 초월하는 미래 공간이자 현실과 가상의 경계가 사라진 세계를 의미

주요 요소 : 캐릭터적 아바타, 크리에이트, 물리적 한계를 넘은 가상공간, 경제활동, 액티비티
+ 현실과의 경계가 자유스러워야 한다

메타버스 확산 핵심 트리거 : 5g 통신기술, 기술진화, 비대면생활, 고객 수용도 증가
진화방향 : 현실증강, 현실복제(미러링)-순천향대 입학식, 가상세계창조

 

 

다방향, 서비스가 종료되어도 가치를 보존하며 개방적이다.

(개인적 생각) 비슷하게 보이는 ... -- sk ifland vs 페이스북 vs 제페토

이프 인플루언서 이프루언서 육성 프로그램도 진행중이다
점프 스튜디오 (ar로 스타들을 집에서, 자이언트 시원 등)

(개인적 생각) 골프강습, 요가 등 운동, 캠핑 온라인 AS 집에서
결론은 트리거 요소를(전기차 처럼) 찾는 거네... 기술력은 충분하다
경제적 지원(바운더리낮추기), 별탈없음, 혹은 간소화, 편리함, 입소문(이미 마련된 사용자그룹)

 

기업의 DT 전환을 자극하는 구독 경제 (카카오)

03. 안진혁 부사장_발표자료.pdf
2.04MB

 

제품 판매 마진보다 구독 서비스 형태로 바뀔 것이다
보수적인 자동차도 직접판매보다 서비스 판매로 바꾼다는 얘기도 나오고 있다
HILTI. 칩을 넣어서 전동 공구를 판매했다. 
소모적 고가 장비 렌탈

(개인적 생각) 비슷한 걸로 명품의류 렌트시장, 다른구독 서비스로는 어도비 제품군, 카톡 이모티콘, 네이버쇼핑도 장보기에서 음식, 세제 등 구독 섭스 시도중

 

 

1인가구의 증가, 가구 수 증가 & 인구는 감소

(개인적 생각) 카톡 대여?

국내 렌탈비즈니스 시장은 해외에 비해 규모가 크다
제조, 서비스인력의 퀄리티, 선진적인 금융플랫폼 3박자를 다 갖춰야 한다

학습지, 고가 가발

(개인적 생각) 모발이식?

 

 

초전도 큐비트를 활용한 양자컴퓨팅 과제와 방향 (IBM Qunatum)

04. 백한희 박사_발표자료.pdf
0.79MB

 

해외 강연으로 질문 생략
내 능력으로는 어려운 내용이라 패스..

 

 

디지털 헬스케어 미래 전략 (셀트리온)

05. 이수영 전무_발표자료.pdf
1.30MB

 

트룩시마 (림프구성 백혈병 ) 등...

헬스케어에 다양한 디바이스가 많은데, 표준화에 대한 질문 - 데이터가 활용되지 못한 부분... 아직은 통합이 어렵다
그러나 정부 등에서 표준화에 대한 니즈가 있어서 차차 바뀔 것이며 시간이 필요한 부분이다.

 

 

탄소중립을 위한 전략 및 기술 (한화솔루션)

06. 손인완 상무_발표자료.pdf
1.55MB

 

환경에 대한 이슈로 나온 탄소중립.

- 오염 감축 : 각국에서 노력중. 유럽과 미국은 탄소 국경세 검토중.
기술개발 방향성 : 신재생에너지 대체, 리사이클링 기술, 미활용 바이오매스(목재, 풀)로 만들어지는 바이오플라스틱 확대, ccus 카본 챕쳐 앤 유틸리제이션 (경제성 아직 없음) CO2순환기술에 대한 디지털 평가시스템 등

태양광 : Tandem셀이 효율이 좋다
풍력, 
수소 : 전기에너지 (AEC, AEM, ..) -> 수소 + 탄소결합 화학물질

친환경 물질 분야
1 PTC : 폐플라스틱을 오일화해 연료로 사용 (나프타)
2 바이오매스 : 사탕수수, 밀, 옥수수 등 1세대 바이오화학원료에서 2세대는 목재, 풀, 폐유기물을 정제해서 플라스틱 생산. 재배나 식량확보의 어려움에서 탈출. 에탄올, 아세테이트 등으로 분해해서 씀. 
나중에는 대기중의 CO2까지 잡는..

탄소중립 위한 디지털 기술 (LCA+TEA)
- LCA : 원료부터 생산, 활용, 폐기, 수거까지 모든 과정 CO2 생성과 에너지사용 평가 기술 (친환경성, 탄소중립)
- TEA : LCA 최선 기술을 선택, 적용하는 시스템. 광범위 평가, 분석 시스템 (경제적 실현가능성 판단)

한화솔루션은 eco-dehch 가소제/인체친화적 석유수지 제품을 에너지 및 공정 최적화 기술로 개발하고 있다.

질문 : 기후변화위기에 따라 탄소중립에 대한 각국의 목표가 다른데 (...중략) 한화솔루션은 어떤 핵심 가치를 두고 사업을 추진하는지? - 제품 설계, 생산, 소비까지 적용하고 있다. ESG 위원회를 두고 있다. 또 탄소중립 위원회도 있다.

 

 

데이터에서 발견한 미래기술 : 위크시그널 (한국과학기술정보연구원)

07. 양혜영 박사_발표자료.pdf
4.34MB

 

2020 데이터 분석 시작 전에
- 2020 데이터와 2021 데이터는 같을까? : 데이터는 코로나 등의 이슈로 불연속적이기도 하다.
- 장기간 데이터 학습기반 예측은 언제나 유용성을 보장하는가? 

위크 시그널 : 경험적 외삽으로 설명 어렵고 약하고 중요성 정도를 모르며 미래 일어날 징후를 담는다. 노이즈와 구별 어렵다. 기초데이터 수집 후 전문가그룹에서 토론을 거쳐 위크시그널이 판별된다. 그래서 시간이 많이 소요되고 특정분야로 쏠릴 수 있다.

빅데이터, 인공지능기반 위크시그널 탐색법 : 아직은 전문가 의존적임

시의성 높이고 전분야로 확장시키고 전문가 비의존적인 걸 개발했다.

 

10대 위크시그널 포커스영역
1. 딥러닝, 그 다음
2. 기생컴퓨팅 : cryptojacking - 몰래 침입해 채굴 (랜섬웨어의 일종). 개인용컴퓨터가 아니라 기관을 노린다. 전기자동차도 타겟이다.
3. 플랫폼기반 커뮤니티
4. 에너지 클라우드 : 어디에서든 생산하고, 어디로든 전송, 무엇으로든 저장
5. 유연한 기업 : 빠른 기술흡수와 디지털전환, 시대적요구에 적극 대응
6. 새로운 탄소물질
7. 인류와 지구의 공생 : 탄소중립과 탄소거래, 새로운 생태시스템, 미세플라스틱 문제, 친환경농업
8. 온오프 정신건강 : 정신건강의 중요성, 가상공간에서의 정신적고통, 코로나포비아
9. DNA에서 RNA로 확대 : RNA 기술의 발전
10. covid-19, 위드코로나 : 치료, 라이프스타일의 변화, 포스트코로나를 위한 산업패러다임 변화(타 바이러스 발생 예방의 노력)

질문 : 위크시그널이 유망 기술, 트렌드로 갈 지 판단 방법과 이에 대해 연구를 하고 있나요?
- 먼저 위크시그널에 대한 탐지에 집중해 개발했고 이후 성장 판단 기술도 개발 계획중이다

 

 

정부 알엔디 투자 유망분야 (한국과학기술정보연구원)
- 탄소중립기반 혁신성장 R&D 패키지 투자분야를 중심으로

08. 원동규 센터장_발표자료.pdf
4.35MB

 

역설적으로 데이터센터에서의 에너지소비량도 무시못한다
... ppt 보는게 더 나을 듯 해서 정리 안함

 

 

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90
책 흐름 집중 포인트 : 예제 단위로 끊어본다. 핵심이 얕고 넓게 퍼져 있어서 사실만 축약한다.
질질질..글 스타일이라 코드를 치면서 집중도를 높이고 음악 들으며 봐도 되는 책이다. 오히려 그래야 좀 진도가 나간다. 옆에 비고?란에 의외로 중요한게 가끔 나온다.

 

 

4-3 

% 연산자의 피연사는 모두 정수형이어야 한다.

 

4-4 증감연산자

증감 연산자의 피연산자는 상수나 일반 수식을 사용할 수 없다
++ -- 사이에 공백은 넣을 수 없다
증감연산자는 산술연산자, 관계연산자, 논리연산자보다 우선순위가 높다

#include <iostream>
using namespace std;

int main() {
	int x, y, p;

	x = 3;
	y = x++;
	cout << "x=" << x << " " << " y=" << y << endl;

	x = 10;
	y = ++x;
	cout << "x=" << x << " " << " y=" << y << endl;

	p = ++x + y++;
	cout << "x=" << x << " " << " y=" << y;
	cout << "   p=" <<  p << endl;

	return 0;
}

 

4-5 감소연산자
(비슷해서 생략)

 

4-6 관계연산자

두 개의 피연산자 크기를 비교하는 연산자
< > <= => == != 가 있다.

관계연산자 우선순위는 사칙연산자보다 낮다.
조건이 일치하면 참/true/0 외의 정수가 되고 불일치하면 거짓/false/0 이 된다.
각 기호 사이에 공백을 넣으면 오류가 발생한다.

#include <iostream>
using namespace std;

int main() {
	int x;

	cout << "0이나 1을 입력하세요 : ";
	cin >> x;

	if (x == 0) {
		cout << "10/3=" << 10 / 3 << endl;
	}
	else {
		cout << "10%3=" << 10 % 3 << endl;
	}

	return 0;
}

 

 

4-7 논리 연산자

두 개의 데이터만 비교 가능한 관계연산자의 한계를 극복한다
논리연산자는 두 개 이상의 조건을 조합하여 표현한다.
연산 결과는 true, false 값만 가진다.

NOT(!) 연산 - 전체 조건 부정. 곱셈연산자보다 우선순위가 높고, 증가연산자와 같다
AND(&&) - 모든 조건 만족. || 연산자보다 우선순위가 높다
OR(||) - 일부 조건 만족. 

#include <iostream>
using namespace std;

int main() {
	int x;

	cout << "하나의 정수를 입력하세요 : ";
	cin >> x;

	if (x >=5 && x<10) {
		cout << "5보다 크거나 같고 10미만" << endl;
	}
	else {
		cout << "다른 정수" << endl;
	}

	return 0;
}

 

4-8 비트 단위 논리 연산자

임베디드시스템, 암호, 하드웨어 디바이스 제어를 위해 사용된다.
각 비트에서 비트단위논리연산자는 해당 비트에만 적용되고 다른 비트에는 적용되지 않는다

~x
x&y
x|y

비트 단위 보수 (~) - 1의 보수 (0이면 1, 1이면 0으로 변경된 것이 연산결과)
비트 단위 (&) - x와 y의 비트 단위 곱 (AND), 모두 1이면 1, 그 외에는 0가 연산 결과 
비트 단위 (|) - x와 y의 비트 단위 합(OR), 연산결과 모두 0이면 0, 그 외 1
배타적 비트 단위 합 (^) - 비트 XOR. 다르면1, 모두 같으면 0

~1100=0011
a=1100
b=0110
a&b=0100
0&0=0
0&1=0
1&1=1
1&0=0
// & - 모두 1일때만 1이고 그 외 0
a=1100
b=0110
a|b=1110
0|0=0
0|1=1
1|1=1
1|0=1
// | - 모두 0일때만 0, 그 외에는 모두 1
a=1100
b=0110
a^b=1010
// 0^0=0
// 0^1=1
// 1^0=1
// 1^1=0
// 입력값이 같지 않으면 1을 출력

 

n개 비트들의 xor 결과는 일반적으로
1의 개수가 홀수 개면 1, 짝수 개면 0이다

#include <iostream>
using namespace std;

int main() 
{
	int a, b, c;

	a = 25;
	b = 17;

	c = a & b;
	cout << "25(00011001) & 17(00010001) ==> " << c << '\n';

	c = a | b;
	cout << "25(00011001) | 17(00010001) ==> " << c << '\n';

	c = a ^ b;
	cout << "25(00011001) ^ 17(00010001) ==> " << c << '\n';

	c = ~a;
	cout << "~00011001 => 11100110 ==> -(0011001+1) 25에 대한 1의 보수";
	cout << c << endl;

	return 0;
}
// 각각 2진수로 변환한 후 각 비트끼리 연산한다

 

 

4-9 shift 연산자

D/A 변환기(디지털/아날로그 전환 회로) 와 같은 외부 장치로부터 입력한 내용을 디코딩(인코딩 반대로, 컴퓨터 언어를 사람용으로 변환)하거나 상태 정보 등을 읽어올 때 주로 사용. 

x<<n // x의 값을 2진수로 표시 후 n비트만큼 왼쪽으로 이동하고 이동 위치에 0을 채움
x>>n // // 우측으로 //

원래 있던 것은 절단

#include <iostream>
using namespace std;

int main() 
{
	int x, y;
	x = 9; //1001

	y = x << 1; // 10010 (18)
	cout << "1비트 왼쪽으로 이동하고 우측 남는 빈칸은 0으로 채움" << y << endl;

	y = x << 2; // 100100 (36)
	cout << "2비트 왼쪽으로 이동하고 우측 남는 빈칸은 0으로 채움" << y << endl;

	return 0;
}

 

 

주소 연산자 &
포인터 *

변수 x가 위치한 메모리 시작 주소
포인터 변수 x가 가리키는 곳의 데이터

&x // &주소 변수 
*x // *포인터

 

4-10 조건 연산자

if~then~else 명령문을 간단하게 하여 ?: 이렇게 쓴다
3개의 피연산자를 갖는 삼항 연산자

조건식?식2:식3;
//true면 2, flase 면 3

 

키보드에서 두 개의 정수값을 입력하고 최대값을 구하는 예

#include <iostream>
using namespace std;

int main() 
{
	int x, y, max;

	cout << "두 개의 정수값을 입력하고 Enter 키 ㄱㄱ";
	cin >> x >> y;

	max = (x > y) ? x : y;
	cout << "최대값: " << max << endl;

	return 0;
}

 

4-11 sizeof 연산자

프로그램에서 사용하는 데이터타입이 현재 사용중인 컴에서 얼마만큼의 메모리를 확보하고 사용되는지 조회

sizeof(data type);
#include <iostream>
using namespace std;

int main() 
{
	 
	cout << sizeof(int) << endl;
	cout << sizeof(char) << endl;
	cout << sizeof(double) << endl;

	return 0;

}

 

 

4-12 형 변환 연산자(캐스트연산자)

묵시적 타입 변환 (자동 타입 변환, implicit)
형 변화는 모두 암시적

명시적 타입 변환 
특별한 경우가 아니면 사용하지 않는다

캐스트 연산 후에는 원래의 정수 데이터형을 가진다.

 

#include <iostream>
using namespace std;

int x = 100;

int main() 
{
	int x = 10;
	int y = 3;
	double div;

	cout << x/y << endl; //정수/정수는 정수
	
	
	div = (double)x / y; //캐스트연산자 
	cout << div << endl;

	return 0;

}

 

4-13 단항 스코프 식별 연산자(::) 
::전역변수명

 

전역변수에 접근하기

#include <iostream>
using namespace std;

int x = 100;

int main() 
{
	int x = 10;
	int y = 3; //지역변수로 스코프 안에서 유효하다

	cout << x << endl;
	cout << y << endl;
	cout << ::x << endl; // 전역

	x = y + ::x;
	cout << x << endl;

	return 0;

}

 

함수/클래스/네임스페이스 내에서 정의하지 않고 외부에 선언된 이름은 선언 위치부터 파일의 끝까지가 유효 범위
숨겨진 전역변수 이름은 범위지정연산자(::)를 사용해서 참조한다.

 

 

 

연습문제

1. 데이터 2, 3, 5를 정수 변수에 저장하고, 곱셈한 결과를 변수 result에 저장한 후 화면에 출력

#include <iostream>
using namespace std;


int main() 
{
	int x = 2;
	int y = 3;
	int z = 5;

	int result = x * y * z;
	cout << result;
	
	return 0;
}

 

2. 키보드에서 두 개의 정수를 입력한 후 두 수의 합, 뺄셈, 곱, 몫, 나머지를 구

#include <iostream>
using namespace std;


int main() 
{
	int x, y;

	cout << "두 정수 입력 ";
	cin >> x >> y;
	
	cout << x + y <<endl;
	cout << x - y << endl;
	cout << x * y << endl;
	cout << x / y << endl;
	cout << x % y << endl;

	return 0;
}

 

3. 키보드에서 세 정수 입력 후 가장 큰 수, 가장 작은 수 출력

#include <iostream>
using namespace std;


int main() 
{
	int x, y, z;
	int min, max;

	cout << "3 정수 입력 ";
	cin >> x >> y >> z;	

	// 최소값
	min = x;	
	if (min > y) {
		min = y;
	}
	if (min > z) {
		min = z;
	} 

	// 최대값 
	max = x;
	if (max < y) {
		max = y;
	}
	if (max < z) {
		max = z;
	}
	
	cout << min <<endl;
	cout << max << endl;
	
	return 0;

}

 

4. 가로 4, 세로 7m인 직사각형의 면적(가*세)을 구하는 코드 

#include <iostream>
using namespace std;


int main() 
{
	int width = 4;
	int height = 7;
	int area = width * height;
	
	cout << area << endl;
	
	return 0;

}

 

5. 키보드에서 원금, 이율, 일 수를 입력 후 변수에 저장하고,
다음 공식으로 이자를 산출해서 결과를 출력하는 코드

#include <iostream>
using namespace std;


int main() 
{
	int principal;
	int annualInterest;
	int days;
	int rate;

	cin >> principal >> annualInterest >> days;

	rate = principal * annualInterest * days / 365;
	cout << rate << endl;
	
	return 0;

}

 

 

6. 사각형의 가로와 세로의 개수를 입력 후 별로 된 사각형을 출력하는 코드를 작성해보자. 
꽉 채운 사각형, 테두리 한 줄 외 속이 빈 사각형 모양으로 출력

#include <iostream>
using namespace std;


int main() 
{
	int x, y;	 
	
	cout << "사각형의 가로 세로 개수 입력 : ";
	cin >> x >> y;

	// 1. 꽉찬
	// 세로
	for (int j=0; j< y; j++) {
		// 가로
		for (int i=0; i< x; i++) {
			cout << "*";
		}			
		cout << '\n';
	}

	cout << '\n';
	 
	// 2. 빈
	for (int j = 0; j < y; j++) {
		// 가로
		for (int i = 0; i < x; i++) {			
			if (i == 0 || i == x - 1 || j == 0 || j == y-1) {
				// 첫번째나 마지막 가로 전부 찍기
				// 그 외 첫번째, 마지막 별만 찍기
				cout << "*";
			}
			else {				
				cout << " ";
			}		
		}		
		cout << '\n';
	}
	
	return 0;

}

 

7. 키보드에서 두 수를 입력 후 범위가 -5<x<10이면 참, 그 외의 수이면 거짓으로 출력

#include <iostream>
using namespace std;


int main() 
{
	int x, y;	 
	
	cout << "두 정수 입력 (음~양수) : ";
	cin >> x >> y;
	
	((x > -5 && x < 10) && (y > -5 && y < 10)) ? cout << "참" : cout << "거짓";

	return 0;

}

 

8. 키보드에서 두 수 입력 후 범위가 -10<=x<=95면 참, 그 외의 수면 거짓으로 출력

#include <iostream>
using namespace std;


int main() 
{
	int x, y;	 
	
	cout << "두 정수 입력 (음~양수) : ";
	cin >> x >> y;
	
	((x >= -10 && x <= 95) && (y >= -10 && y <= 95)) ? cout << "참" : cout << "거짓";

	return 0;

}

 

9. x>0이거나 y가 100보다 크거나 같으면 정상 출력

#include <iostream>
using namespace std;


int main() 
{
	int x, y;	 
	
	cout << "두 정수 입력 (음~양수) : ";
	cin >> x >> y;
	
	if(x > 0 || y >= 100) cout << "정상";

	return 0;

}

 

10. 비트단위 연산자를 사용해 65를 2, 8, 16진수로 출력 **

#include <iostream>
using namespace std;

int n = 65;

int ten_to_two(int n);
int ten_to_eight(int n);
int ten_to_sixteen(int n);

int main() 
{
	// 10진수를 2,8,16진수로 변환
	ten_to_two(n);
	ten_to_eight(n);
	ten_to_sixteen(n);

	return 0;
}

int ten_to_two(int n) {	
	// 재귀함수
	cout << "2진수 변환 결과 :";

	if (n < 2) { // 0이나 1로 이진법으로 변환할 필요가 없다
		cout << n;		
	}
	else {
		ten_to_two(n/2);
		cout << n%2;
	}
	cout << '\n';
}

int ten_to_eight(int n) {
	cout << "8진수 변환 결과 :";
	// 비트연산???? 이따 할거
	
}

int ten_to_sixteen(int n) {
	cout << "16진수 변환 결과 :";	
}

https://shacoding.com/2020/01/10/c%EC%96%B8%EC%96%B4-10%EC%A7%84%EC%88%98%EB%A5%BC-2%EC%A7%84%EC%88%98%EB%A1%9C-%EB%B3%80%ED%99%98%ED%95%98%EC%9E%90-%EC%84%B8-%EA%B0%80%EC%A7%80-%EB%B0%A9%EB%B2%95-%EC%A0%9C%EA%B3%B5/ 

 

11. 13과 9를 2진수로 표현 후 비트 단위의 곱과 합의 결과 출력 **

 

 

12. 컴퓨터에서는 RGB로 색상을 표현하고, 16비트의 경우 각각 5비트씩 할당한다.
색상값을 16진수로 입력하고, 초록색에 해당하는 부분만 AND 연산해 마스킹 후 해당 부분의 값만 추출해 출력한다.

 

 

13. 비트 이동 연산자를 사용해 16을 곱하고 4로 나누어보자

 

 

14. 0~127까지의 문자 코드 중에서 촐력이 가능한 문자 출력

 

 

15. 다음과 같은 식을 계산해보자 (x값 입력은 cin 사용, 결과는 화면에 출력)

 

 

16. 키보드에서 화씨 온도 입력 후 섭씨 온도로 변환해보자

 

 

17. 키보드에서 두 정수 입력 후 합, 차이, 곱, 나누기 구해

 

 

18. 다섯자리 정수 입력 후 각 숫자를 하나씩 구분해서 출력
흰트 > 정수 나눗셈과 나머지 연산 사용(24781-> 2 4 7 8 1)

 

 

728x90
728x90
블로그 이미지

coding-restaurant

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

,
728x90
728x90

3장 본문 예제들 중

문자열 뒤집어서 출력
#include <stdio.h>
#include <string.h>

int main()
{
    int i, n;
    char a[] = "DOG";
    char b[10];
    
    n = strlen(a); //문자열길이
    for(i = n-1; i>=0; i--) {
        b[n-1-i] = a[i];
    }
    b[n]='\0'; //문자열의 끝났음을 알리는 표시 추가
    
    printf("%s를 거꾸로 읽으면 %s\n", a, b);

    return 0;
}

 

* strlen() : 헤더파일 string.h 필요. 공백을 포함한 널문자인 \0 전까지의 문자 개수


 

파일입출력
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>

main()
{
    FILE* fp;
    char s[256];
    int i = 1;

    fp = fopen("abc.txt", "r");
    if (fp == NULL) {
        return;
    }

    while (feof(fp) == 0) {
        fgets(s, 255, fp);
        printf("%04d: %s", i, s);
        i++;
    }
    fclose(fp);
}

 

* fopen , feof , fgets , fclose : https://coding-restaurant.tistory.com/

* %4d : 필드 폭을 4칸 확보하고 우측 정렬해서 출력
* -%4d : 필드 폭을 4칸 확보하고 좌측 정렬해서 출력 
* %04d : 필드 폭을 4칸 확보하고 우측 정렬해서 출력하고 0으로 채움

 

 

4장 본문 예제들 중 

a부터 b까지의 합
#include <stdio.h> 

int calc(int, int);

main()
{
	int c;
	c = calc(1, 5);
	printf("%d\n", c);
}

int calc(int a, int b) {
	int i, n = 0;
	for (i = a; i <= b; i++) {
		n += i;
	}
	return n;
}
#include <stdio.h>

void swap(int*, int*);
void sum(int, int);

main()
{
	sum(1, 5);
	sum(10, 5);
	sum(1, 10);
	sum(2, 2);
}

void swap(int* a, int* b) {
	int temp;
	temp = *a;
	*a = *b;
	*b = temp;
}

void sum(int min, int max) {
	int i, n;

	if (min > max) {
		swap(&min, &max);
	}
	printf("%d", min);

	n = min;	
	for (i = min + 1; i <= max; i++) {
		printf("+%d", i);
		n += i;
	}
	printf("=%d\n", n);
	printf("%d에서 %d까지의 합은 %d\n", min, max, n);
}

 

 

 

재귀 호출 (팩토리얼)
#include <stdio.h>

int factorial(int);

main()
{
	printf("%d! = %d\n", 5, factorial(5));
}

int factorial(int n) {
	if (n == 0) {
		return 1;
	}
	else {
		return (n * factorial(n - 1));
	}
}

 

 

 

 

3-1. 1부터 n까지의 정수 중 홀수의 값을 구하는 프로그램 작성 (n은 키보드로 입력받기)

#include <stdio.h>

int main()
{
    int num; //입력값 
    int total=0;
    
    printf("홀수의 합을 어디까지 더해볼 지 숫자 입력 : \n");
    scanf("%d", &num);
    
    //홀수 고르기
    for(int i=1; i<=num; i++) {
        if(i%2 == 1) {
            total += i;
        }
    }
    
    printf("홀수의 합은 : %d", total);
    
    return 0;
}

* scanf 오류 체크 

 

3-2. 사용자와 컴퓨터가 가위바위보를 5번 하는 프로그램 작성*

1. 컴퓨터가 낼 것을 난수를 사용해서 임의로 결정
2. 사용자가 낼 것을 번호로 입력
3. 누가 이겼는지 결과 표시
4. 5번 동안에 많이 이긴 사람을 승자로 표시
#include <stdio.h>
#include <stdlib.h> 

int main()
{
    //각각 낸거
    int com; int you;
    //이긴 횟수
    int youWin = 0;
    int comWin = 0;

    for (int i = 0; i < 5; i++)
    {
        //컴
        com = rand() % 3; //0,1,2

        //사람
        printf("가위 1 바위 2 보 0 입력하시오 : \n");
        //fflush(stdin);
        scanf_s("%d", &you);

        // 개행문자가 있으면 다시 입력
        //if(you == '\n') scanf("%d", &you);        

        //printf("컴:%d, 사람:%d\n", com, you);

        //승자 판별
        // 사용자
        if ((com == 0 && you == 1) || (com == 1 && you == 2) || (com == 2 && you == 0)) {
            youWin++;
            printf("사용자 \n");
        }
        // 무승부
        else if (com == you) {
            printf("무승부 \n");
        }
        // 컴
        else {
            comWin++;
            printf("컴 \n");
        }
    }

    //최종 승자
    if (comWin < youWin) {
        printf("사용자가 승자");
    }
    else if (comWin == youWin) {
        printf("무승부");
    }
    else {
        printf("컴이 승자");
    }

    return 0;
}

 

답안지

#include <stdio.h>
#include <stdlib.h> 

int main()
{
    int userson, userwin = 0;
    int comson, comwin = 0;
    int i = 1, result;

    while (1) {
        // 컴퓨터가 낼 손
        comson = rand() % 3;

        // 사용자가 낼 손
        printf("낼 손의 번호 입력 (보=0, 가위=1, 바위=2)\n");
        scanf("%d", &userson);

        //0~2외 수 입력 예외처리
        if (userson < 0 || userson>2) {
            continue;
        }
        else {
            i++;
        }
    
        //판정결과
        printf("사용자: %d, 컴퓨터 :%d", userson, comson);

        if (userson == comson) {
            printf("무승부");
        }
        else if (comson == (userson + 1) % 3) {
            printf("컴퓨터 승");
            comwin += 1;
        }
        else {
        	printf("사용자 승");
            userwin += 1; //사용자 승리 횟수 추가            
        }

        // 5번이 넘었으면 종료
        if (i > 5) {
            break;
        }
    }

    // 최종결과
    printf("사용자 vs 컴 : %d vs %d", userwin, comwin);
    result = userwin - comwin;

    if (result > 0) {
        printf("이겼다");
    }
    else if (result < 0) {
        printf("졌다");
    }
    else {
        printf("비겼다");
    }

    return 0;
}

 

키워드

(a+1) % 3

0, 1 ,2 을 1, 2, 3으로 변경, 3으로 나누면 1,2,0으로 나머지가 떨어진다.
* 반복되는 규칙을 찾아 정리된 식인 듯

rand() : 0부터 RAND_MAX 범위 내에서 int형 난수를 반환한다. 난수를 생성하기 위한 시드를 설정하려면 srand()를 사용한다. % 나머지 연산자를 사용하여 난수의 범위를 한정시킬 수 있다. rand() %10 는 0~9까지의 랜덤한 난수를 만든다.

rand() % n

 

scanf 오류, scanf_s로 수정

비주얼스튜디오 최신버전에서는 함수 취약 에러가 나면서 컴파일이 안된다.
#define _CRT_SECURE_NO_WARNING을 추가해 줄 수도 있지만 되도록 안쓸 수 있으면 안쓸 것을 권장한다.

scanf는 scanf("입력받을 데이터형", &변수이름); 이런 형태로 사용되며
scanf_s는 입력받은 크기를 추가해준다. scanf_s("입력받을 데이터형태", &변수이름, sizeof(a)); 이런 식으로 쓰인다.

 

 

반응형

 

 

4-1. 인수를 값으로 전달하는 것과 참조로 전달하는 것의 차이점은 무엇인가요

인수를 값으로 전달하는 방식(call-by-value)은 값을 복사하여 전달하기 때문에 복사 대상을 직접적으로 외부에서 바꿀 수 없다. 인수를 참조로 전달하는 방식은 주소값을 전달하기 때문에 외부에서 접근하여 값을 변경할 수 있다

 

4-2. 다음 프로그램의 출력 결과를 예상해보세요

#swap으로 1번 내용을 비교하는 예제라 생략

 

4-3. 다음 프로그램에서 오류가 발생하니, 잘못된 곳을 수정하세요

#include <stdio.h>

void main()
{
	calculate(2, 3);
}

void calculate(int a, int b)
{
	printf("두 수의 합은 %d입니다\n", a+b);
}

함수를 사용하려면 호출 전 함수를 미리 정의해야한다. main 위에 프로토타입을 선언하던가 위에 정의해야한다.

 

4-4. 세 개의 정수를 전달 인수로 하고, 세 값 중에서 가장 작은 수를 반환하는 함수 작성 *

int printMin(int a, int b, int c) 
{
    int min = a;
    
    if(b<min) {
        min=b;
    }
    if(b<min) {
        min=c;
    }
    return min;
}
728x90
728x90

'C, C++' 카테고리의 다른 글

콜론연산자(:), 더블콜론연산자(::)  (1) 2021.09.25
C 구조체  (0) 2021.09.12
c 난수 생성 함수  (0) 2021.09.08
보안 경고 #define _CRT_SECURE_NO_WARNINGS  (0) 2021.09.08
메인 함수(엔트리포인트)  (0) 2021.09.08
블로그 이미지

coding-restaurant

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

,

v