열 개의 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을 참조할 수 있는 참조의 정보가 전달된다.
...
}
이제까지 객체 생성, 객체의 멤버변수 초기화를 목적으로 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); 처럼 객체를 생성하면 두 생성자 모두 호출이 가능하기 때문에 호출할 생성자를 결정하지 못해서다.
또한 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
객체의 생성이 클래스 외부에서 진행되면 생성자는 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형으로 선언되어야 해서 오버로딩, 디폴트값 설정이 불가하다. 소멸자를 직접 정의하지 않으면 생성자처럼 디폴트소멸자가 자동 삽입된다.
#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 클래스를 완성해보자.
이름 : Lee
회사 : ABCEng
전화번호 : 010-1111-2222
직급 : 사원
이름 : Hong
회사 : OrangeEng
전화번호 : 010-3333-4444
직급 : 주임
이름 : Kim
회사 : SoGoodComp
전화번호 : 010-5555-6666
직급 : 대리
멤버 변수 : 클래스를 구성하는 (클래스 내에 선언된) 변수 멤버 함수 : 클래스를 구성하는 (클래스 내에 정의된) 함수
C언어 파일분할 복습
헤더파일의 역할 : cpp파일로 만들어진 obj 파일에 있는 함수들의 내용을 다른 cpp파일에서 사용할 수 있게 한다. A.cpp파일에서 B.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; //힙 할당방식(동적할당)
1. 계산기 기능의 Calculator 클래스를 정의해보자. 기본적으로 지니는 기능은 덧셈, 뺄셈, 곱셈, 나눗셈이며 연산을 할 때마다 어떠한 연산을 몇 번 수행했는지 기록되어야 한다. 아래 main 함수와 실행의 예에 부합하는 Calculator 클래스를 정의하면 된다. 단 멤버변수는 private으로, 멤버함수는 public으로 선언하자.
2. 문자열 정보를 내부에 저장하는 Printer라는 이름의 클래스를 디자인하자. 이 클래스의 두 가지 기능은 다음과 같다. < 1)문자열 저장 2) 문자열 출력 >아래의 main 함수와 실행의 예에 부합하는 Printer 클래스를 정의하되 멤버변수는 private으로 멤버함수는 public으로 선언하자.
포인터 변수를 선언해서 위 변수를 가리키게 한 뒤, 이 포인터 변수를 참조하는 참조자를 선언한다. 이렇게 선언된 포인터 변수와 참조자를 이용해서 num에 저장된 값을 출력하는 예제를 만든다.
const int num=12;
푼 것
int main(void)
{
const int num = 12;
const int *ptr = #
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;
}
참조자(레퍼런스)란 C에서는 없던, C++에서 새로 생긴 개념. 포인터랑 의도하는 바는 같은데 포인터의 단점이 보완되어 출시된 것. C++ 문서에서는 포인터보다 특정 경우가 아니라면 대부분 참조자를 사용하길 권장한다.
값으로 전달하는 방식의 한계
1. 큰 구조체나 클래스를 함수에 전달할 때 인수의 복사본을 매개변수로 만든다.
2. 함수의 호출자에 값을 전달하는 건 반환값을 사용하는 게 유일한 방법이나 함수에서 인수를 수정하는 게 확실하고 효율적이다.
→ 그래서 참조를 통해 문제를 해결한다.
변수를 참조로 전달하려면 매개변수를 참조로 선언한다. 함수가 호출되면 y는 인수에 대한 참조가 된다.
int x=5;
addOne(x);
//int &y=x; 이런 의미
void addOne(int& y)
{
y=y+1;
}
참조자는 변수에 n개로 별명을 붙여준 것
참조자의 수에는 제한이 없다. 참조자를 대상으로 참조자를 선언할 수 있다. 한번 선언된 참조자의 대상은 바꿀 수 없다. (포인터는 가능) 선언과 동시에 무조건 할당되어야 하며 포인터처럼 null은 불가하다.
&연산자의 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=#
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 기반의 함수는 변수의 주소값을 받아서 주소값이 참조하는 영역에 저장된 값을 직접 변경할 수 있다.
아래는 주소 값을 이요해 함수 외부에 선언된 변수를 참조하니 call-by-reference 방식 다시 정리하면 call-by-reference는 "주소 값을 전달받아서 함수 외부에 선언된 변수에 접근하는 형태의 함수호출" 주소 값이 참조의 도구로 사용됐다는 것이 판단 기준
call-by-reference 는 1)주소값을 이용한 call-by-reference , 2)참조자를 이용한 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 키워드를 사용한다.
#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의 동적할당 예시로, 길이정보를 인자로 받아 해당 길이의 문자열 배열을 생성하고, 그 배열의 주소값을 반환한다.
함수 포인터 : 윈도우 프로그래밍 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 함수의 인자 전달
#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;
}
구조체 변수를 선언하거나 사용할 때에는 매번 struct 키워드를 사용하여 구조체임을 명시해야 하는데 typedef 키워드를 사용하여 구조체에 새로운 이름을 선언하면 매번 struct 키워드를 사용하지 않아도 된다. 구조체의 정의와 typedef 선언을 동시에 할 시 구조체 이름 생략 가능하다.
구조체에서 구조체 멤버로의 접근은 멤버 연산자 (.)를 사용한다. 구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다.
구조체변수이름.멤버변수이름
구조체 멤버 초기화
구조체 변수 초기화는 멤버 연산자(.) 와 중괄호 { } 를 사용한다.
원하는 멤버 변수만 초기화
원하는 멤버 변수만 초기화 가능하며, 초기화하지 않은 멤버변수는 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;
}
구조체의 주소값과 구조체의 첫 번째 멤버 변수의 주소값은 언제나 같다. 그러나 배열의 경우와 달리 구조체의 이름은 구조체를 가리키는 주소가 아니다. 즉 포인터에 할당할 때에는 구조체는 반드시 주소 연산자(&)를 사용해야 한다.
구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법
구조체 포인터를 이용하여 구조체의 멤버에 접근하는 방법은 아래 두 가지이다. 화살표 연산자가 좀 더 많이 사용된다.
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;
}
바이트 패딩 (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만큼 증가하여 정의된다.
#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
비트 필드에는 지정 비트 수만큼만 저장되고 나머지 비트는 버려진다. 비트 필드의 각 멤버는 최하위비트 (Least Significant Bit, LSB)부터 차례로 배치된다. 예제에서는 a가 최하위비트에 온다.
비트 필드와 공용체 함께 사용
코드에서 값을 지정 시 비트 필드를 사용하지만 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;
}
DIDs - 개인정보를 하나하나 다 쪼갠 블록체인 기술..해킹에 좀 안전하다 새로운 시장을 만든다
소품종 대량생산 -> 다품종 적량생산
메타플랫폼은 전부 6가지 돌이 있어야 하며, 운영하는 사람에 따라서도 큰 차이가 난다. 자동화는 쓸데 없는 걸 하지 않게 하는 것. 이다.
이미 Sass 방식의 ERP 솔루션을 해외에도 도입한 글로벌 기업이다. 업그레이드가 언제든지 자동으로 되어있다.
앱서비스 의 B2C 관점 말고도 글로벌 메타 플랫폼 (레저, 주거, 레스토랑 등)이 솔루션 안에서 데이터가 돌게 할 거다. 그리고 디지털 트랜스포메이션과 ESG에 대비되어 있다.
메타버스 시대의 새로운 고객 경험과 삶의 변화 (SK 텔레콤)
강의자 요청으로 ppt 자료 비공개.
메타버스 : 1992 소설에 나온 것으로 시공간을 초월하는 미래 공간이자 현실과 가상의 경계가 사라진 세계를 의미
주요 요소 : 캐릭터적 아바타, 크리에이트, 물리적 한계를 넘은 가상공간, 경제활동, 액티비티 + 현실과의 경계가 자유스러워야 한다
메타버스 확산 핵심 트리거 : 5g 통신기술, 기술진화, 비대면생활, 고객 수용도 증가 진화방향 : 현실증강, 현실복제(미러링)-순천향대 입학식, 가상세계창조
다방향, 서비스가 종료되어도 가치를 보존하며 개방적이다.
(개인적 생각) 비슷하게 보이는 ... -- sk ifland vs 페이스북 vs 제페토
이프 인플루언서 이프루언서 육성 프로그램도 진행중이다 점프 스튜디오 (ar로 스타들을 집에서, 자이언트 시원 등)
(개인적 생각) 골프강습, 요가 등 운동, 캠핑 온라인 AS 집에서 결론은 트리거 요소를(전기차 처럼) 찾는 거네... 기술력은 충분하다 경제적 지원(바운더리낮추기), 별탈없음, 혹은 간소화, 편리함, 입소문(이미 마련된 사용자그룹)
기업의 DT 전환을 자극하는 구독 경제 (카카오)
제품 판매 마진보다 구독 서비스 형태로 바뀔 것이다 보수적인 자동차도 직접판매보다 서비스 판매로 바꾼다는 얘기도 나오고 있다 HILTI. 칩을 넣어서 전동 공구를 판매했다. 소모적 고가 장비 렌탈
(개인적 생각) 비슷한 걸로 명품의류 렌트시장, 다른구독 서비스로는 어도비 제품군, 카톡 이모티콘, 네이버쇼핑도 장보기에서 음식, 세제 등 구독 섭스 시도중
1인가구의 증가, 가구 수 증가 & 인구는 감소
(개인적 생각) 카톡 대여?
국내 렌탈비즈니스 시장은 해외에 비해 규모가 크다 제조, 서비스인력의 퀄리티, 선진적인 금융플랫폼 3박자를 다 갖춰야 한다
학습지, 고가 가발
(개인적 생각) 모발이식?
초전도 큐비트를 활용한 양자컴퓨팅 과제와 방향 (IBM Qunatum)
해외 강연으로 질문 생략 내 능력으로는 어려운 내용이라 패스..
디지털 헬스케어 미래 전략 (셀트리온)
트룩시마 (림프구성 백혈병 ) 등...
헬스케어에 다양한 디바이스가 많은데, 표준화에 대한 질문 - 데이터가 활용되지 못한 부분... 아직은 통합이 어렵다 그러나 정부 등에서 표준화에 대한 니즈가 있어서 차차 바뀔 것이며 시간이 필요한 부분이다.
탄소중립을 위한 전략 및 기술 (한화솔루션)
환경에 대한 이슈로 나온 탄소중립.
- 오염 감축 : 각국에서 노력중. 유럽과 미국은 탄소 국경세 검토중. 기술개발 방향성 : 신재생에너지 대체, 리사이클링 기술, 미활용 바이오매스(목재, 풀)로 만들어지는 바이오플라스틱 확대, ccus 카본 챕쳐 앤 유틸리제이션 (경제성 아직 없음) CO2순환기술에 대한 디지털 평가시스템 등
태양광 : Tandem셀이 효율이 좋다 풍력, 수소 : 전기에너지 (AEC, AEM, ..) -> 수소 + 탄소결합 화학물질
친환경 물질 분야 1 PTC : 폐플라스틱을 오일화해 연료로 사용 (나프타) 2 바이오매스 : 사탕수수, 밀, 옥수수 등 1세대 바이오화학원료에서 2세대는 목재, 풀, 폐유기물을 정제해서 플라스틱 생산. 재배나 식량확보의 어려움에서 탈출. 에탄올, 아세테이트 등으로 분해해서 씀. 나중에는 대기중의 CO2까지 잡는..
탄소중립 위한 디지털 기술 (LCA+TEA) - LCA : 원료부터 생산, 활용, 폐기, 수거까지 모든 과정 CO2 생성과 에너지사용 평가 기술 (친환경성, 탄소중립) - TEA : LCA 최선 기술을 선택, 적용하는 시스템. 광범위 평가, 분석 시스템 (경제적 실현가능성 판단)
한화솔루션은 eco-dehch 가소제/인체친화적 석유수지 제품을 에너지 및 공정 최적화 기술로 개발하고 있다.
질문 : 기후변화위기에 따라 탄소중립에 대한 각국의 목표가 다른데 (...중략) 한화솔루션은 어떤 핵심 가치를 두고 사업을 추진하는지? - 제품 설계, 생산, 소비까지 적용하고 있다. ESG 위원회를 두고 있다. 또 탄소중립 위원회도 있다.
데이터에서 발견한 미래기술 : 위크시그널 (한국과학기술정보연구원)
2020 데이터 분석 시작 전에 - 2020 데이터와 2021 데이터는 같을까? : 데이터는 코로나 등의 이슈로 불연속적이기도 하다. - 장기간 데이터 학습기반 예측은 언제나 유용성을 보장하는가?
위크 시그널 : 경험적 외삽으로 설명 어렵고 약하고 중요성 정도를 모르며 미래 일어날 징후를 담는다. 노이즈와 구별 어렵다. 기초데이터 수집 후 전문가그룹에서 토론을 거쳐 위크시그널이 판별된다. 그래서 시간이 많이 소요되고 특정분야로 쏠릴 수 있다.
빅데이터, 인공지능기반 위크시그널 탐색법 : 아직은 전문가 의존적임
시의성 높이고 전분야로 확장시키고 전문가 비의존적인 걸 개발했다.
10대 위크시그널 포커스영역 1. 딥러닝, 그 다음 2. 기생컴퓨팅 : cryptojacking - 몰래 침입해 채굴 (랜섬웨어의 일종). 개인용컴퓨터가 아니라 기관을 노린다. 전기자동차도 타겟이다. 3. 플랫폼기반 커뮤니티 4. 에너지 클라우드 : 어디에서든 생산하고, 어디로든 전송, 무엇으로든 저장 5. 유연한 기업 : 빠른 기술흡수와 디지털전환, 시대적요구에 적극 대응 6. 새로운 탄소물질 7. 인류와 지구의 공생 : 탄소중립과 탄소거래, 새로운 생태시스템, 미세플라스틱 문제, 친환경농업 8. 온오프 정신건강 : 정신건강의 중요성, 가상공간에서의 정신적고통, 코로나포비아 9. DNA에서 RNA로 확대 : RNA 기술의 발전 10. covid-19, 위드코로나 : 치료, 라이프스타일의 변화, 포스트코로나를 위한 산업패러다임 변화(타 바이러스 발생 예방의 노력)
질문 : 위크시그널이 유망 기술, 트렌드로 갈 지 판단 방법과 이에 대해 연구를 하고 있나요? - 먼저 위크시그널에 대한 탐지에 집중해 개발했고 이후 성장 판단 기술도 개발 계획중이다
정부 알엔디 투자 유망분야 (한국과학기술정보연구원) - 탄소중립기반 혁신성장 R&D 패키지 투자분야를 중심으로
역설적으로 데이터센터에서의 에너지소비량도 무시못한다 ... ppt 보는게 더 나을 듯 해서 정리 안함
임베디드시스템, 암호, 하드웨어 디바이스 제어를 위해 사용된다. 각 비트에서 비트단위논리연산자는 해당 비트에만 적용되고 다른 비트에는 적용되지 않는다
~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 연산자
프로그램에서 사용하는 데이터타입이 현재 사용중인 컴에서 얼마만큼의 메모리를 확보하고 사용되는지 조회
#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진수 변환 결과 :";
}
#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);
}
* %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);
}