
참고 자료: 인프런
[하루 10분|C++] 누구나 쉽게 배우는 C++ 프로그래밍 입문
#include <iostream> // <= 전처리 지시자
int a;//선언
a = 7;//대입
int b = 6;//초기화
cout << &a << &b << endl; // 주소 출력
// ""랑 '' 뭐가 달라?
char g = 'g';
// null: '\0'
"": 명시적으로 null 문자 포함 => String
#define PIE 3.141592 // C에선 요래 함
// c++은?
const float PIE = 3.141592;
int a = 3.141592; // -> 3
char c = 'M';
cout << int(c) << endl;
cout << static_cast<int>(c) << endl;
auto n = 100;
short month[12] = {1,2,3};
cout << month[0] << endl;
cin >> name1; // <- 공백 입력 X
// -> get, getline: 공백 입력 가능
배열은 다른 배열에 통째로 대입할 수 없음 -> string은 가능하다~
// ex) 축구선수
struct SoccerPlayer {
// 멤버 요소
string name;
string position;
int height;
int weight;
} S; // 구조체 이름
SoccerPlayer A; // 구조체형 변수
A.height = 160.0;
A.weight = 70;
A.name = "Sonny";
A.position = "Striker";
SoccerPlayer B = { // 구조체형 변수 선언
"Son",
"Striker",
180,
80
};
SoccerPlayer C { };
SoccerPlayer P[2] = {
{ "A", "B", 1, 1 },
{ "C", "D", 2, 2 },
};
union MyUnion {
int intValue;
long longValue;
float floatValue;
};
MyUnion test;
test.longValue = 10.0; // 10
test.floatValue = 5.0; // 5
cout << test.floatValue << endl; // 5
cout << test.longValue << endl; // 1084227584
한번에 한가지 데이터만 보관해.
int 형이면 int형만! 여러가지 데이터형 쓰긴 하는데, 동시에 쓸 수 없어.
메모리 절약할 수 있음 -> 코딩할땐 잘 안씀
enum rainbow {
redd = 0,
orangee = 1,
yelloww = 3,
greenn, // 4
bluee // 5
};
c++: 객체 지향 프로그래밍임
포인터: 사용할 주소에 이름을 붙인다
포인터는 포인터의 이름 == 주소
* (간접값 연산자, 간접 참조 연산자)활용
/// 표현 방식쓰 ///
int *a; // c
int* b; // c++
int* c, d; // c: 포인터 변수, d: int 변수
int a = 6;
int *b; // 포인터 변수
b = &a; // a의 주소를 가리킴
a == b // b: 포인터의 값(a의 값)
&a == b // 얘가 가리키는 주소의 값(a의 주소값)
*b = *b + 1;
이러면 a의 값은 이제 +1된거
포인터의 진정한 면모는 이름이 없는 메모리(아직 결정되지 않은 메모리)와 쓸 때!
어떤 데이터형을 원하는지 new 연산자에게 알려주면,
new 연산자는 그에 알맞은 크기의 메모리 블록을 찾아내고, 그 블록의 주소를 리턴한다.
int* pointer = new int;
int형 데이터를 지정할 수 있는 새로운 메모리가 필요해 -> 메모리 블록 찾아 -> pointer에 반환
이게 뭔말이냐
기존 방법은 요랬지
int a;
int* b = &a; -> b, &a 둘다 주소에 접근할 수 있음
근데 new 쓰면,
int* pointer = new int;
int에 접근할 수 있는 유일한 방법이 pointer 변수임
pointer가 가리키는 건 데이터의 객체
메모리 제어권을 사용자에게 줄 수 있다는 장점이 있음
pss = new char[strlen(animal) + 1]; // 실행 시간에 메모리 크기를 결정!! 유리쓰
strcpy(pss, animal); // animal 값을 pss로 복사
사용한 메모리를 다시 메모리 폴로 환수
-> 환수된 메모리는 프로그램의 다른 부분이 다시 사용
int* ps = new int;
// 메모리 사용
delete ps;
// MARK: 👩🏻💻 delete 연산자 사용
// 1. new로 대입하지 않은 메모리는 delete로 해제할 수 없다.
// 2. 같은 메모리 블록을 연달아 두 번 delete로 해제할 수 없다.
// 3. new[]로 메모리를 대입할 경우 delete[]로 해제한다.
// 4. new로 선언(대괄호 사용하지 않았다면) -> delete도 [] 사용X
struct MyStruct {
char name[20];
int age;
};
멤버 부를때 원래 .찍고 접근하는데, 얘는 -> 씀
MyStruct* temp = new MyStruct;
갖다 쓸땐
temp->name;
(*temp).age;
요렇게 접근쓰
기본적인건 안쓸거임
do {
cout << "while\n"; //실행부터 하고
j++; // 반복
} while(0); // 조건 검사
이렇게 해도 한번은 출력됨
1) 함수 정의하고, 2) 원형 제공하고, 3) 호출해서 쓰면됨
함수에 있는 애는 파라미터(매개변수), 여기에 전달해서 넣는게 argument(인자)
Time sum(Time* t1, Time* t2) {
Time total;
// .: 값=>멤버 접근 ->: 주소=>멤버 접근
total.hours = t1->hours + t2->hours + (t1->mins + t2->mins) / minsPerHr;
total.mins = (t1->mins + t2->mins) % minsPerHr;
return total;
}
어떤 함수에 <- 함수의 주소를 매개변수로 넘겨주는 경우 유용하게 사용됨!
int (*pf)(int);
pf = func;
cout << (*pf)(3) << endl;
// 참조로 전달하는 방식
void swapA(int& a, int& b) {
int temp;
temp = a;
a = b;
b = temp;
}
// 포인터로 전달하는 방식
void swapB(int* a, int* b) {
int temp;
temp = *a;
*a = *b;
*b = temp;
}
// 값으로 전달하는 방식 -> 복사본을 갖고 동작해 원본 안건드려 원본 건드리려면 참조해야됨!
void swapC(int a, int b) {
int temp;
temp = a;
a = b;
b = temp;
}
inline float square(float x) { return x * x; }
// MARK: 인라인 함수
일반적으로 함수의 호출은? 함수의 주소로 점프하는 과정
인라인 함수는 컴파일된 함수 코드가 프로그램의 다른 코드에 삽입
컴파일러의 인라인 함수 호출 == 점프가 아닌! 그에 대응하는 함수 코드로 대체
float b = square(a);
int sumArr(int*, int n = 1);
// default는 젤 오른쪽에 배치해야됨
값이 없을 때에는 1이 들어감 == 디폴트 매개변수
int arr[5] = {1,2,3,4};
cout << sumArr(arr) << endl;
함수는 요래
int sumArr(int* arr, int n) {
int total = 0;
for(int i=0; i<n; i++) {
total += arr[i];
}
return total;
}
int c;
int &d = c; // c에 대한 참조 변수
&: 주소 연산자이자, 참조 연산자임
다형: 다양한 형태를 지닌 함수의 오버로딩
여러개의 함수를 같은 이름으로 연결한다. => 매개변수 리스트
왜?? 매개변수의 데이터형이 서로 달라도, 파라미터로 선언된 데이터 형의 값에 따라서 동일한 함수 이름으로 동일한 연산을 수행하도록 하자~
void print(char, int);
void print(int, int);
void print(char);
// 다 똑같은 이름이어도~
print('a', 3);
print(3, 2);
print('a');
// 각각 매칭됨
❌ 함수의 오버로딩 쓰면 안돼 경우
total = day1.operator+(day2);
operator+ -> 요렇게 써도됨 헐
total2 = day1 + day2;
구체적인 데이터형을 포괄할 수 있는 일반형으로 함수를 정의한다. 범용적으로 만드는 것 == 일반화
일반화 프로그래밍
template <class Any> <- class or type name
Any sum(Any, Any);
// Any: 데이터형의 일반화
// class or type name
template <class Any> // Any: 데이터형의 일반화
Any sum(Any, Any);
// =========== 선언 후
template <class Any>
Any sum(Any a, Any b) {
return a + b;
}
헤더부분 + main 함수 + func.cpp 요렇게 나눔
#include "struct.h"
// 헤더 파일을 여러 파일에 포함시킬 대, 반드시 단 한번만 포함시켜야 한다.
// #inclue "new.h" <- 새 헤더 파일이 있을 때, 이 안에 struct.h 가 또 있으면 안된다는 뜻
// 이거 방지해서 #ifndef STRUCT ~ #endif 사용
함수 원형, #define, const 기호 상수, 구조체,클래스,템플릿 선언, 인라인 함수이 들어감
필요한건?
-> 클래스를 선언하는 부분
-> 클래스 메서드를 정의하는 부분
c++의 지향점: 클래스 객체를 표준 데이터형 사용하듯이 사용할 수 있게 만들어야 한다!
=> 생성자 활용
#ifndef Stock_h
using namespace std;
class Stock { // < = 새로운 데이터 형을 만든 것
private:
string name;
int shares;
float share_val; // 얘는 public의 update를 활용해서 값 변경하겠지
double total_val;
void set_total() { total_val = shares * share_val; }
public:
// void acquire(string, int, float); <- 생성자의 역할
void buy(int, float);
void sell(int, float);
void update(float);
void show();
// 클래스 2개 받아서 비교하는거 만들어보자
Stock &topVal(Stock&);
Stock(string, int, float); // <- 파라미터 받는 생성자 꾸며줌
Stock();// default 생성자
~Stock();
};
#define Stock_h
#endif /* Stock_h */
private: 데이터 은닉 <- 추상화
public을 통해 private 값 변경해야됨
// class::선언
Stock::Stock(string co, int n, float pr) {
name = co;
shares = n;
share_val = pr;
set_total();
}
// 함수의 오버로딩을 통해 매개변수 없는 생성자도 만들어줌
Stock::Stock() {
name = "";
shares = 0;
share_val = 0;
set_total();
}
// 파괴자
// 매개변수를 가질 수 없음
Stock::~Stock() {
cout << name << " class is 소멸\n";
}
acquire 함수가 하고 있던 것: 파라미터 받기! => 생성자로 꾸밀 수 있다
Stock temp = Stock("Panda", 100, 100);
Stock temp2("Panda", 100, 100);
Stock temp; // 사용자 정의형, c++에서 default 생성자를 지원해줘
Stock &Stock::topVal(Stock& s) {
if(s.share_val > share_val) {
return s;
} else {
return *this; // self임 포인터로 자기 가리키기
}
}
Stock s3[4];
Stock s4[4] = {
Stock("AA", 10, 1000),
Stock("BB", 10, 2000),
Stock("CC", 10, 3000),
Stock("DD", 10, 4000)
};
default 생성자에 대입 -> 4개의 배열
클래스를 배열로 선언하기 위해서는 default 생성자가 반드시 정의되어 있어야 한다!!!!!!!!!!
왜냐면 s4[4] <-여기부터 공간 4개 default로 만들고 각 원소에 아래 애들 넣어야 하기 때문!!!!!!!!!
Stock first = s4[0];
for(int i=1; i<4; i++) {
first = first.topVal(s4[i]);
}
first.show();
Stock *first = &s4[0];
for(int i=1; i<4; i++) {
first = &first->topVal(s4[i]);
}
first->show();
class는 private 부분에 접근할 수 있는 유일한 통로가 public에 있는 메소드들이었는데, 하나 더있음 ==> Friend!!!!!!!!!👯
Time a, b;
Time c;
c = a + b; <- 이렇게는 했는데,
a = b * 3; <- 요런건 Time * Int 니깐 안됐음
a = b.operator*(3); <- 이렇게 해도 안됨
=> 깐부 맺어라 friend 함수로 해결!
클래스에
friend Time operator*(int n, Time& t) {
return t * n;
}
요런식으로 정의해두고 씀
// 함수는 요렇게
// friend
Time operator*(int n, Time& t) {
Time result;
long resultMin = t.hours * n * 60 + t.mins * n;
result.hours = resultMin / 60;
result.mins = resultMin % 60;
return result;
}
그러면 이제 t2 = 3 * t1; 이런거 할 수 잇어
기초 클래스 -> 상속 -> 파생 클래스
두 클래스에 출력하는 함수로 print, show 두 가지를 쓰고 있었는데, 이걸 하나로 합치면 좋겠다. 속해있는 클래스는 달라도, 기능이 같으니깐
이것도 오버로딩 해보자 friend를 활용해서!!!!!
Time의 show, NewTime의 print는 같은 기능을 수행하고 있지?
그러면 show라는 함수가 Time일때는 h, m를, NewTime일때는 d를 출력하면 좋겠따
상황에 따라 동작을 달리하는 것 == 다형! == 오버로딩!!
friend std::ostream& operator<<(std::ostream&, Time&);
요렇게 ostream의 cout << 이 부분을 위에서 '*'에 대해 오버로딩했던 것처럼 해보자
//friend
std::ostream& operator<<(std::ostream& os, Time& t) {
os << t.hours << "시간 " << t.mins << "분\n";
return os;
}
print대신에 show를 상속받아서 써보자 -> print 없애고, show를 재정의해 & 기존 show를 virtual 키워드 붙여
프로그램에게 서로 독립된 두 개의 메서드 정의가 있다!를 알랴줘
show() 호출했을 때 객체를 따져 -> 대응하는 함수를 선택해
1) 기초 클래스에서 가상 메서드를 선언하면, 그 함수는 기초 클래스 및 파생되는 클래스에서 모두 가상이 된다!
2) 객체에 대한 참조를 사용하여, 객체를 지시하는 포인터를 사용하여 가상 메서드가 호출되면, 참조나 포인터를 위해 정의된 메서드를 사용하지 않고,
객체형을 위해 정의된 메서드를 사용한다. -> 동적 결합!!!!!!!
3) 상속을 위해 기초 클래스로 사용할 클래스를 정의할 때,
파생 클래스에서 다시 정의해야 하는 클래스 메서드들은 가상 함수로 선언해야 한다.
-> show 재정의 하려면 기초 클래스에서 virtual 써야됨
// class Time 내에
virtual void show(); // 재정의하기 위함
class NewTime: public Time {
/*
1. 파생 클래스형의 객체 안에는 기초 클래스형의 데이터 멤버들이 저장된다.
2. 파생 클래스형의 객체는 기초 클래스형의 메서드들을 사용할 수 있다.
3. 파생 클래스는 자기 자신의 생성자를 필요로 한다.
4. 파생 클래스는 부가적인 데이터 멤버들과 멤버 함수들을 임의로 추가할 수 있다.
*/
private:
int day;
public:
NewTime();
NewTime(int, int, int);
// void print(); 이제 필요없다
void show();
};
// print -> show로 재정의
void NewTime::show() {
cout << "일: " << day << endl;
// show();
cout << "시간: " << getHour() << endl;
cout << "분: " << getMin() << endl;
}
Time t5;
NewTime t6;
위에 있는 클래스들임.
타입이 다르니깐 배열에 넣을 수도 없지.
근데 이걸 가리키는 포인터들의 배열은 만들 수 있지
왜냐면 time, newtime은 public 상속 모델이 적용되어 있고,
time을 지시하는 포인터가 time 객체를 지시할 수도 있고, new time 객체를 지시할 수도 있어
그래서 포인터들의 배열을 만들어..
Time* times[MAX];
int day;
int hour;
int min;
cf. 응용 -> 파괴자도 virtual
for(int i=0; i<MAX; i++) {
cout << i+1 << "번째 정보 ";
times[i]->show(); // <- 여기 안에서 time, newtime 알아서 대응해서show()
}
for(int i=0; i<MAX; i++) {
delete times[i]; // <- 얘는 항상 time 객체 안에 있는 파괴자임
// ~Times도 virtual로 만들어주면 됨
}
virtual ~Time();