카테고리 없음

13일 차 : 복습 ~ C++ 문법 Class 개념

maypawn 2025. 5. 29. 20:42

코딩 테스트 - 짝수와 홀수

더보기

#include <string>
#include <vector>

using namespace std;

string solution(int num) {
    string answer = "";
    if(num * 2) {
        cout << "Even";
        
        else {
            cout << "Odd";
        }
    }
    return answer;
}


#include <string>
#include <vector>

using namespace std;

string solution(int num) {
    string answer = "";
    if(num % 2 ==0) {
        answer = "Even";
    }
    else {
            answer = "Odd";
        }
    return answer;
}

어제 공부했던 if문 문제가 나왔다. 정확하게 짝수와 홀수를 구분하는 문제라 생각부터 직접 풀이를 도전했다.

  1. num 가 2의 배수인 경우 Even 출력
  2. 아닌 경우 Odd 출력

위에 string answer = ""; 는 뭔지 잘 모르겠지만, 문자열 답이 공란이다, 뭐 그런식으로 느껴져서 별거 아닌 거라고 판단하고 그 아래 if문 넣고 생각해봤다. 

짝수 홀수를 구분하는게 2의 배수로 고민하니까 if 특성상 참 거짓 두개로 나와야 하고, =, <, >, == 같은지, 더 큰지, 비교하는 연산자를 써야하는데 2에다가는 비교가 안되더라.. 아는 대로 다 적어보고 어쩔수 없이 어제 풀이했던 내용이 블로그에 있으니 참고하는 수밖에 없었다.

  • if(num % 2 == 0) {answer = "Even";} - 만약 num을 2로 나누고 나머지가 0과 같다면, answer에 텍스트 Even을 넣어라.

정답을 보니 어떻게 써야하는지  알았다. 그리고 텍스트 경우 [] 를 써야 되는걸로 기억하는데, 이미 string을 include로 불러왔을 때는 "" 큰 따옴표로 해도 되는 것 같다. 그래서 위에 string answer = " "; 이 있었나 보다. 

 

복습 & 정리 

  • % : 나머지를 구한다 (정수끼리만 사용 가능)
  • / : 정수끼리 나누면 몫을 구하고 소수점 이하는 버리고, 소수점이 있는 수와 나누면 정확한 소수점 값을 구한다. 
  • std::string : 변수에 문자열을 할당할 때 " " 큰따옴표를 사용한다.
  • [] : 배열 선언, 초기화 혹은 크기, 포인터와 함께 동적 배열 할당 시 사용.
  • 변수 : 데이터를 저장하는 메모리 공간에 붙여진 이름. (숫자, 텍스트, 참/거짓 값 등 다양한 종류의 데이터 보관.
  • 함수 : 특정 작업을 수행하기 위한 코드들의 묶음. (코드 재사용성을 높인다)
  • = : 대입 연산자. 오른쪽에 있는 값 혹은 결과를 왼쪽에 있는 변수에 저장한다. ("같다" 가 아니라 "오른쪽 값을 왼쪽에 넣어라.")
  • == : 같음 비교 연산자. 왼쪽과 오른쪽 값이 서로 같은지 비교한다. (같은지, 동일한가? 묻는 질문.)
  • != : 다름 비교 연산자. 왼쪽과 오른쪽 값이 서로 다른지 비교한다. (같지 않은지, 다른지? 묻는 질문.)

for 문

조 : 초기화 - 종료 조건 확인 - 동작 - 사후 동작 - 종료 조건 확인

문법 : 초기화, 종료조건, 사후 동

 

더보기

1 부터 10까지 합 계산

- 초기화 (int i = 1) 는 반복문의 시작 지점이자 반복이 어디서부터 시작될지를 정의하는 과정. 

 

#include <iostream>
using namespace std;

int main() {
int sum = 0;
for (int i = 1; i <= 10; i++) {

sum += i;
}
cout << "sum :" << sum << endl;
return 0;
}


5부터 1까지 출력

- i 자체가 반복의 제어와 출력 값을 제공하는 역할을 수행한다. (위 코드의 변수 sum 이 필요없음)

 

#include <iostream>
using namespace std;

int main() {
for (int i = 5; i >= 1; i--)
{
cout << i << " "; 
}
cout << endl;

return 0;
}


1부터 20까지 3의 배수 출력

 - 3으로 나누었을 때 0이 되는 수만 출력하여, 20 이전의 3의 배수를 구한다.

 

#include <iostream>
using namespace std;

int main() {
for (int i = 1; i <= 20; i++) {
if (i % 3 == 0) {
cout << i << " ";
}
}
cout << endl; 
return 0;
}


 오른쪽 정렬된 삼각별 출력

 

#include <iostream>
using namespace std;

int main() {
int n = 5;
for (int i = 1; i <= n; i++) {

for (int j = 1; j <= n - i; j++) {

cout << " "; 
}
for (int j = 1; j <= i; j++) {
cout << "*";
}
cout << endl;
}
return 0;

}

while 문

순서 : 종료 조건 <-> 동작

 

더보기

사용자 입력 종료

 

 

#include <iostream>
using namespace std;

int main() {
int number;

cout << "번호를 입력하세요 (음수 입력 시 종료) : ";
cin >> number;

while (number >= 0) {
cout << "You entered : " << number << endl;
cin >> number;
}
cout << "프로그램 종료.\n";
return 0;
}


게임 루프

- <cstdlib> : 난수 생성 함수 { rand(), srand() }

- <ctime> : 시간 관련 함수 { time() }

- rand() % 100 : rand MAX(0 ~ 32767) / rand 값 중 100으로 나눈 나머지 = 0~99 사이의 값 

- +1 : 1부터 ~ 시작한다는 뜻

 

#include <iostream>
#include <cstdlib>
#include <ctime>
using namespace std;

int main() {
srand(time(0));
int secretNumber = rand() % 100 + 1;
int guess;

cout << "Guess the number (1 to 100): ";
cin >> guess;

while (guess != secretNumber) {
if (guess < secretNumber) {
cout << "Too low! Try again: ";
}
else {
cout << "Too High! Try again: ";
}
cin >> guess;
}

cout << "축하드립니다! 당신의 guessd the number!\n";
return 0;
}


포인터와 레퍼런스

  • 변수에 특정한 값을 담음. (int는 정수, double은 부동 소수점을 담음.)
  • 포인터도 변수이다. 포인터에는 값을 담는 대신 변수의 조수를 담는다.
  • 메모리 주소값을 알면 해당 공간을 직접 제어할 수 있다.
  • int* : 포인터 변수임을 나타냄. 그리고 포인터가 가리키는 데이터의 타입을 의미.
  • * (에스테르크) : 역참조 연산자.
더보기

일반 변수의 대입 

- 변경 후 b의 값은 b = 20의 값대로 불러온다. (덮어쓰기)

 

#include <iostream>
using namespace std;

int main() {
int a = 10; 
int b = a;

cout << "초기값 - a : " << a << ", b: " << b << endl;

b = 20;

cout << "변경 후 - a : " << a << ", b : " << b << endl;
return 0;
}


 배열의 대입

- 배열은 한번에 전체 대입이 불가능, 원소 하나씩 값 넣어줘야 한다.

 

#include <iostream>
using namespace std;

int main() {
int arr1[3] = { 1, 2, 3 };
int arr2[3];

for (int i = 0; i < 3; i++) {
arr2[i] = arr1[i];
}
arr2[0] = 100;

cout << "arr1[0]: " << arr1[0] << ", arr2[0] : " << arr2[0] << endl;
return 0;
}

포인터 변수의 연산

 

값을 복사하면 비용이 든다. 이러한 복사 비용 때문에 C++ 에서는 값을 직접 복사하는 대신, 포인터를 활용해 변수의 주소를 가리켜서 동일한 데이터에 접근할 수 있도록 할 수 있다. 

 

정리 

& : 변수의 주소값을 제공하는 연산자.

배열의 이름 : 배열의 이름은 주소값을 가지고 있다. ( arr[3] )

더보기

 값 복사 비용 (기본 변수)

 

#include <iostream>
using namespace std;

int main() {
int a = 10;
int b = a;

cout << "a: " << a << ", b: " << b << endl;

b = 20; 

cout << "변경 후 a: " << a << ", b: " << b << endl;

return 0;
}


값 복사 비용 (배열)

- int 타입은 보통 4byte 메모리를 차지하는데 arr1, arr2 배열이 8MB 메모리를 사용해 스택 메모리에서 스택 오버플로우 오류가 발생한다. 

 

#include <iostream>
using namespace std;

int main() {
const int SIZE = 1000000;
int arr1[SIZE];
int arr2[SIZE];

for (int i = 0; i < SIZE; i++) {
arr2[i] = arr1[i];
}

cout << "배열 복사 완료" << endl;
return 0;
}

 


변수의 주소값 저장

- 변수 a 의 메모리 위치를 포인터 변수 p 에 기록하여 해당 메모리 위치를 통해 값에 접근하거나 조작할 수 있다.

 

#include <iostream>
using namespace std;

int main() {
int a = 10;
int* p = &a;

cout << "변수 a 값 : " << a << endl;
cout << "변수 a의 주소 : " << &a << endl;
cout << "포인터 p의 값(저장된 주소) : " << p << endl;

return 0;
}


포인터를 이용한 배열 접근

- 주소값을 가져오려면 & 연산자를 써야하지만, 배열[] 의 경우 이름에 주소값이 포함되어 있어서 & 연산자를 사용하지 않아도 된다.

 

#include <iostream>
using namespace std;

int main() {
int arr[3] = { 10, 20, 30 };
int* p = arr;

cout << "p가 가리키는 값 : " << *p << endl;
cout << "p+1 이 가리키는 값 : " << *(p + 1) << endl;
cout << "p+2 이 가리키는 값 : " << *(p + 2) << endl;

return 0;
}


 포인터 변수의 타입과 크기

- 포인터로 주소를 가져오려면 해당 변수의 타입과 같아야 가리킬 수 있다.

 

#include <iostream>
using namespace std;

int main() {
int x = 3;
char y = 'A';

int* ptr1 = &x;
char* ptr2 = &y;

cout << "ptr1이 가리키는 값 : " << *ptr1 << endl;
cout << "ptr2가 가리키는 값 : " << *ptr2 << endl;

return 0;
}


 포인터를 이용한 주소 확인

- cout 은 char* 타입의 포인터를 만나면 문자열로 간주하여 화면에 출력하려고 시도하기 때문에 (void*) 로 어떤 타입도 가리키지 않는 일반적인 포인터 타입 void* 로 캐스팅(강제 변환) 한다. 주소를 출력하고 싶을 때는 (void*)를 사용해야한다. 

 

#include <iostream>
using namespace std;

int main() {
int x = 3;
char y = 'A';

int* ptr1 = &x;
char* ptr2 = &y;

cout << "x의 주소 : " << &x << ", ptr1 : " << ptr1 << endl;
cout << "y의 주소 : " << (void*)&y << ", ptr2 : " << (void*)ptr2 << endl;

return 0;
}


포인터 연산과 데이터 크기 

- int 정수는 4 증가, char 변수는 1 크기가 증가함을 확인할 수 있다.

 

#include <iostream>
using namespace std;

int main() {
int x = 3; 
char y = 'A';

int* ptr1 = &x;
char* ptr2 = &y;

cout << "ptr1 : " << ptr1 << ", ptr1 + 1 : " << ptr1 + 1 << endl;
cout << "ptr2 : " << (void*)ptr2 << ", ptr2 + 1 : " << (void*)(ptr2 + 1) << endl;

return 0;
}

 

결과 

ptr1 : 0000005C5EBEF674, ptr1 + 1 : 0000005C5EBEF678
ptr2 : 0000005C5EBEF694, ptr2 + 1 : 0000005C5EBEF695


포인터를 이용한 값 읽기

 

#include <iostream>
using namespace std;

int main() {
int a = 10;
int* p = &a; 

cout << "포인터 p가 가리키는 값 : " << *p << endl; 

return 0;
}


 포인터를 이용한 값 변경 

 

#include <iostream>
using namespace std;

int main() {
int a = 10;
int* p = &a; 

cout << "변경 전 a : " << a << endl;

*p = 20;

cout << "변경 후 a : " << a << endl;

return 0;
}

ptr : 변수에 저장된 값을 복사 시 처리량이 많아 메모리에 부하를 줄 수 있다. 그래서 복사하지 않고 해당 좌표만 따와 값을 불러오거나 수정할 수 있도록 하는 변수다.

(void*) : void 포인터 어떤 타입의 데이터라도 가리킬 수 있는 일반적인 포인터. 

 

char 를 ptr로 가리킬 때, void* 를 쓰지 않으면 알 수 없는 메모리 내용이 잘못 해석 된다. 

  • ptr1 : 000000208B37FAA4, ptr1 + 1 : 000000208B37FAA8
    ptr2 : A儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆劫??, ptr2 + 1 : 儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆劫??

*ptr 를 출력하면 ptr이 가리키는 주소의 값을 가져온다. 값을 가져오려면 앞에 * 역참조를 붙인다. 

 

포인터와 변수의 관계 

  • #include <iostream>
    using namespace std;

    int main() {
    int x = 3;
    int* ptr = &x;

    cout << "x의 값 : " << x << endl;
    cout << "x의 주소 : " << &x << endl;
    cout << "ptr의 값(저장된 주소) : " << ptr << endl;
    cout << "ptr이 가리키는 값 : " << *ptr << endl;
    return 0;
    }

결과

  • x의 값 : 3
    x의 주소 : 000000F8E14FF674
    ptr의 값(저장된 주소) : 000000F8E14FF674
    ptr이 가리키는 값 : 3

복습 (포인터는 헷갈릴 수 있으니 복습)

더보기

올바른 포인터 타입을 사용하는 포인터

- ptr 변수를 구분하기 위해 이름을 임의로 지어 동작하게 한다. 

 

#include <iostream>
using namespace std;

int main() {
int x = 3;
char y = 'A';

int* intPtr = &x; 
char* charPtr = &y;

cout << "intPtr : " << *intPtr << endl;
cout << "charPtr : " << *charPtr << endl;

return 0;
}


포인터 연산

 

#include <iostream>
using namespace std;

int main() {
int x = 3;
char y = 'A';

int* ptr1 = &x; 
char* ptr2 = &y;

cout << "ptr1 : " << ptr1 << ", ptr1 + 1 : " << ptr1 + 1 << endl;
cout << "ptr2 : " << (void*)ptr2 << ", ptr2 + 1 : " << (void*)(ptr2 + 1) << endl;

return 0;
}


nullptr 을 이용한 안전한 포인터 사용

 

#include <iostream>
using namespace std;

int main() {

int* p = nullptr;

if (p == nullptr) {
cout << "포인터가 아직 어떤 변수도 가리키지 않습니다. " << endl;

}
return 0;
}

 

알아두면 자주쓰는 코드

- 다른 사람들의 코드를 가져오거나 할 때 사용.

buf(k) = *(buf + k) 

실행 결과는 같지만 표현 방식과 내부의 담긴 의미가 다른 포인터

  1. arr / &arr(0)
  2. *(ptr+2) / ptr[2]

1. 배열의 시작 주소. 배열 이름 자체가 0부터 시작하는 주소를 포함, 배열 내 0번째 주소를 가리키는 것. 

2. 포인터 연산 / 배열 인덱싱 - 포인터가 가리키는 메모리 주소에서 +2 칸 떨어진 값을 가져온 것, 배열 인덱싱은 ptr 시작점에서 두번째 인덱스 값을 가져오는 것. 

 

포인터 배열 : 포인터를 원소로 갖는 배열. ( int* ptrArr[4] -> 4칸짜리 배열의 원소는 포인터int로 되어있다.)

 

포인터 배열 : int* ptrArr[0]

배열 포인터 : int (*ptr)[0]

더보기

포인터 배열

 

#include <iostream>
using namespace std;

// 배열의 선언, 초기화 및 인덱스를 통한 접근 예제
int main() {
int a = 10, b = 20, c = 30;
int* ptrArr[3] = { &a, &b, &c };

cout << *ptrArr[0] << endl;
cout << *ptrArr[1] << endl;
cout << *ptrArr[2] << endl;

return 0;
}


배열 포인터

 

#include <iostream>
using namespace std;

int main() {
int arr[3] = { 100, 200, 300 };
int (*ptr)[3] = &arr;

cout << (*ptr)[0] << endl;
cout << (*ptr)[1] << endl;
cout << (*ptr)[2] << endl;

return 0;

}

 


포인터 배열 vs 배열 포인터

 

#include <iostream>
using namespace std;

int main() {
int x = 1, y = 2, z = 3;
int* ptrArr[3] = { &x, &y, &z };

int arr[3] = { 10, 20, 30 };
int (*ptr)[3] = &arr;

cout << "ptrArr[0] : " << *ptrArr[0] << endl;
cout << "ptrArr[1] : " << *ptrArr[1] << endl;
cout << "ptrArr[2] : " << *ptrArr[2] << endl;

cout << "(*ptr)[0] : " << (*ptr)[0] << endl;
cout << "(*ptr)[1] : " << (*ptr)[1] << endl;
cout << "(*ptr)[2] : " << (*ptr)[2] << endl;

return 0;
}

 

레퍼런스 문법

- 역참조 없이도 변수를 직접 가리켜 주는 역할. 특정 변수에 별명을 부여한다.

 

더보기

기본적인 레퍼런스 사용

- 레퍼런스의 값을 변경하면, 기존 변수 값도 변경됨. 

 

#include <iostream>
using namespace std;

int main() {
int var = 10;
int& ref = var;

cout << "초기 값 : " << endl;
cout << "var : " << var << endl;
cout << "ref : " << ref << endl;

ref = 20;

cout << "ref 변경 후 값 : " << endl;
cout << "var : " << var << endl;
cout << "ref : " << ref << endl;

return 0;
}


클래스 

- 객체를 만들기 위한 설계도. 모듈화, 재사용성을 높이기 위해 사용.

  • 멤버 변수 - 객체가 가질 데이터를 정의한다. (예: Person class - name, age)
  • 멤버 함수 - 객체가 수행할 수 있는 동작을 정의한다. (예: Person class - display, getAvg() )

 

정리

  • 접근제어 : 클래스 멤버에 대해 외부에서 얼마나 접근할 수 있는지 제한하는 메커니즘.  
  • 접근 연산자 : ( ' . ' , ' ->' ) 객체 멤버에 접근하기 위해 사용되는 연산자. (예 : this-> : 객체 멤버에 접근하기 위한 연산자)
  • 접근 지정자 : public(공개), private(비공개) +@ protected(보호)
  • 객체 인스턴스 : 클래스 내 객체를 실제로 만들어 구체화 시킨 것. (예: Person p;)
  • getter 와 setter : 접근자와 설정자. 캡슐화 원칙을 구현하는데 사용. 값을 외부에서 읽게 하거나 외부에서 값을 변경하도록 하는 함수.
더보기

학생 클래스 정의

 

class Student
{
double getAvg();
int getMaxNum();

int kor[3];
int eng[3];
int math[3];
};

 

 

class 내부 구현

- 클래스 함수와 계산하는 함수를 구분한다.

 

#include <iostream>
#include <algorithm>
#include <string>
using namespace std;

class Student
{
double getAvg();
int getMaxNum();

int kor;
int eng;
int math;
};

double Student::getAvg()
{
return (kor + eng + math) / 3.0;
}
int Student::getMaxNum()
{
return max(max(kor, eng), math);
}


getter 와 setter 를 적용한 class

- this-> 를 사용해 외부의 math 값을 가져와 적용한다.

 

 

#include <iostream>
#include <algorithm> //max 함수 사용
#include <string>

using namespace std;

class Student
{
public:
    //동작 정의(이를 멤버함수라고 합니다)
    double getAvg();
    int getMaxScore();

    void setMathScore(int math)
    {
        this->math = math;
    }
    void setEngScore(int eng)
    {
        this->eng = eng;

    }
    void setKorScore(int kor)
    {
        this->kor = kor;
    }

    int  getMathScore() { return math; }
    int  getEngScore() { return eng; }
    int  getKorScore() { return kor; }

private:
    //데이터 정의(이를 멤버변수라고 합니다.)
    int kor;
    int eng;
    int math;
};

double Student::getAvg()
{
    return (kor + eng + math) / 3.0;
}

int Student::getMaxScore()
{
    return max(max(kor, eng), math);
}

int main()
{
    Student s;

    s.setEngScore(32);
    s.setKorScore(52);
    s.setMathScore(74);

    //평균 최대점수 출력
    cout << s.getAvg() << endl;
    cout << s.getMaxScore() << endl;

    return 0;
}

생성자 

- 객체를 생성할 때 마다 한번씩 자동으로 호출되는 특별한 함수.

 

더보기

기본 생성자 

- 클래스의 객체를 생성 할 때 (Person p;), 클래스의 생성자 (Constructor) 가 자동으로 호출된다. 

- Person p; 는 display() 함수를 호출하는 코드 이전에, p 객체를 탄생 시키고, 초기화 하는 과정에서 기본 생성자를 먼저 호출한다.

 

#include <iostream>
using namespace std;

class Person {
public:
string name;
int age;

Person() {
name = "Unknown";
age = 0;
}

void display() {
cout << "Name : " << name << ", Age : " << age << endl;
}
};

int main() {
Person p;
p.display();
return 0;
}


매개변수가 있는 생성자

- p 객체 생성과 동시에 "Alice" , 25 라는 초기값 전달.

 

#include <iostream>
using namespace std;

class Person {
public:
string name;
int age;

Person(string n, int a) {
name = n;
age = a;
}

void display() {
cout << "Name : " << name << ", Age : " << age << endl;
}
};

int main() {
Person p("Alice", 25);
p.display();
return 0;


기본 매개변수가 있는 생성자

- 기본 매개변수가 있는 생성자에 Person p 객체 생성과 동시에 초기값을 전달하고 값이 지정되지 않은 경우 기본값을 보여준다.

- 값을 전달하지 않아도 기본값을 출력하고, 동시에 값을 지정 시 지정한 값도 출력할 수 있다.

 

#include <iostream>
using namespace std;

class Person {
public:
string name;
int age;

Person(string n = "DefaultName", int  a = 18) {
name = n;
age = a;
}

void display() {
cout << "Name : " << name << ", Age : " << age << endl;
}
};

int main() {
Person p1;
Person p2("Bob", 30);
p1.display();
p2.display();
return 0;
}

헤더 가드 (Include Guard) - 중복 선언 방지

 

예시 코드 

  • #ifndef PERSON_H // 1. 만약 PERSON_H가 아직 정의되지 않았다면 (if not defined)
  • #define PERSON_H // 2. PERSON_H를 정의합니다. (이제부터 PERSON_H는 정의된 상태)
  • #endif // 3. #ifndef 블록의 끝을 나타냅니다.

 

코드 나누기

- 헤더 파일과 소스 파일로 나눠 동작.