참고 자료: 인프런
[하루 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();