[c++] c++ 문법 정리

Madeline👩🏻‍💻·2024년 5월 9일
0

개발 지식

목록 보기
10/12
post-thumbnail

참고 자료: 인프런
[하루 10분|C++] 누구나 쉽게 배우는 C++ 프로그래밍 입문

1. 🛠️ 개요, 변수 & 상수

#include <iostream> // <= 전처리 지시자

변수 중요한 건

  • 자료형,
  • 이름,
  • 어디에 저장되는가?(메모리 영역)이다.
  • 변수는 사용되기 이전에 정의되어야 한다

자료형

  • 정수형: short, int, long, long long
  • 실수형: float
  • char(작은 문자형), bool
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;
    

연산자

  • 새로웠던 것
    '>>': 두 피연산자가 모두 정수이면, 몫을 반환
    '%': 실수에서는 사용X

🚗 auto

  • auto: 초기화 시 데이터 형 추론
auto n = 100;

2. 복합 데이터

  • 사용자 정의대로 새로운 데이터형을 만들 수 있다.
  • 복합데이터형: 기본 정수형 + 부동소수점의 조합

array(배열)

  • 같은 데이터형의 집합
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)

  • 서로 다른 데이터형을 한번에 한가지만 보관할 수 있음
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)

  • 기호 상수를 만드는 것에 대한 또다른 방법
  • 원소끼리 연산 X, 연산 시, int형으로
  • 초기화도 정수만 가능
  • 초기화 안되어있는 일부는 이전 원소 + 1
enum rainbow {
        redd = 0,
        orangee = 1,
        yelloww = 3,
        greenn, // 4
        bluee // 5
    };

3. 포인터

c++: 객체 지향 프로그래밍임

  • 컴파일이 아닌, "실행 시간"에 어떤 결정을 내릴 수 있음
    ex) 배열을 하나 생성하고자 하면,
    -> 재래적(절차적) 프로그래밍: 배열의 크기가 미리 결정됨
    -> 객체 지향 프로그래밍: 배열의 크기를 실행 시간에 결정할 수 있음
    => 변수로 배열의 크기를 결정할 수 있음

포인터: 사용할 주소에 이름을 붙인다
포인터는 포인터의 이름 == 주소
* (간접값 연산자, 간접 참조 연산자)활용

/// 표현 방식쓰 ///
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 연산자에게 알려주면,
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로 복사

🗑️ delete 연산자

사용한 메모리를 다시 메모리 폴로 환수
-> 환수된 메모리는 프로그램의 다른 부분이 다시 사용

int* ps = new int;
// 메모리 사용
delete ps;

// MARK: 👩🏻‍💻 delete 연산자 사용
// 1. new로 대입하지 않은 메모리는 delete로 해제할 수 없다.
// 2. 같은 메모리 블록을 연달아 두 번 delete로 해제할 수 없다.
// 3. new[]로 메모리를 대입할 경우 delete[]로 해제한다.
// 4. new로 선언(대괄호 사용하지 않았다면) -> delete도 [] 사용X

4. 동적 구조체

struct MyStruct {
    char name[20];
    int age;
};

멤버 부를때 원래 .찍고 접근하는데, 얘는 -> 씀

MyStruct* temp = new MyStruct;

갖다 쓸땐

temp->name;
(*temp).age;

요렇게 접근쓰

5. 반복문

기본적인건 안쓸거임

do while 문

do {
    cout << "while\n"; //실행부터 하고
    j++; // 반복
} while(0); // 조건 검사

이렇게 해도 한번은 출력됨

6. 함수

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

함수 포인터

어떤 함수에 <- 함수의 주소를 매개변수로 넘겨주는 경우 유용하게 사용됨!

  1. 함수의 주소를 얻는다.
  2. 함수를 지시하는 포인터를 선언한다.
  3. 함수를 지시하는 포인터를 사용하여 그 함수를 호출한다.
int (*pf)(int);
pf = func;

cout << (*pf)(3) << endl;

swap 함수 구현의 다양한 방법


// 참조로 전달하는 방식
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 함수

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에 대한 참조 변수

&: 주소 연산자이자, 참조 연산자임

7. 함수의 오버로딩

함수의 다형 == 함수의 오버로딩

다형: 다양한 형태를 지닌 함수의 오버로딩
여러개의 함수를 같은 이름으로 연결한다. => 매개변수 리스트
왜?? 매개변수의 데이터형이 서로 달라도, 파라미터로 선언된 데이터 형의 값에 따라서 동일한 함수 이름으로 동일한 연산을 수행하도록 하자~

void print(char, int);
void print(int, int);
void print(char);
     
// 다 똑같은 이름이어도~
     
print('a', 3);
print(3, 2);
print('a');
     
// 각각 매칭됨

❌ 함수의 오버로딩 쓰면 안돼 경우

  • 파라미터는 같은데, 리턴형만 다른 경우 오버로딩할 수 없음
  • 함수를 사용할 때, 두 개 이상의 함수에 대응되는 경우

연산자 오버로딩

  • 연산자에게 다중 의미를 부여함
    ex) * -> 1) 주소에 쓰면 그 주소에 저장되어있는 값을 산출 2) 곱
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;
}

8. 분할 컴파일

헤더부분 + main 함수 + func.cpp 요렇게 나눔

header 부분엔

#include "struct.h"

// 헤더 파일을 여러 파일에 포함시킬 대, 반드시 단 한번만 포함시켜야 한다.
// #inclue "new.h" <- 새 헤더 파일이 있을 때, 이 안에 struct.h 가 또 있으면 안된다는 뜻
// 이거 방지해서 #ifndef STRUCT ~ #endif 사용

함수 원형, #define, const 기호 상수, 구조체,클래스,템플릿 선언, 인라인 함수이 들어감

9. 추상화와 클래스

  • 클래스: 추상화를 사용자 정의 데이터형으로 변환해주는 수단
  • 추상화: 어떤 객체를 사실적으로 표현하는 것이 아니라, 공통된 특징을 간결한 방식으로 이해하기 쉽게 표현하는 것

필요한건?
-> 클래스를 선언하는 부분
-> 클래스 메서드를 정의하는 부분

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 생성자를 지원해줘

this 포인터

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로 만들고 각 원소에 아래 애들 넣어야 하기 때문!!!!!!!!!

  1. first에 값을 직접 전달하는 방법
Stock first = s4[0];
    for(int i=1; i<4; i++) {
        first = first.topVal(s4[i]);
    }
first.show();
  1. 포인터(주소, 첫번쨰 원소)로 지정하는 방법
Stock *first = &s4[0];
for(int i=1; i<4; i++) {
    first = &first->topVal(s4[i]);
}
first->show();

Friend

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; 이런거 할 수 잇어

상속

  1. 기존 클래스에 새로운 기능을 추가할 수 있다.
  2. 클래스가 나타내고 있는 데이터에 다른 것을 더 추가할 수 있다.
  3. 클래스 메서드가 동작하는 방식을 변경할 수 있다.

    기초 클래스 -> 상속 -> 파생 클래스

연산자 오버로딩

두 클래스에 출력하는 함수로 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 키워드 붙여

🥽 가상 메서드 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();
profile
🍎 Apple Developer Academy@POSTECH 2기, 🍀 SeSAC iOS 4기

0개의 댓글