c++ 수업 내용 및 수업 후 과제

2024. 11. 5. 11:51C++프로그래밍/수업 내용 및 수업 후 과제

반응형

여러 프로그래밍 언어에서 new 연산자의 사용을 비교한 표

언어 new 연산자 사용 예시 설명
C++ MyClass* obj = new MyClass(); 동적으로 메모리를 할당하고 객체를 생성합니다. 수동으로 delete를 사용해 메모리를 해제해야 합니다.
Java MyClass obj = new MyClass(); 객체를 생성하고 생성자를 호출합니다. 가비지 컬렉터가 자동으로 메모리를 관리합니다.
C# MyClass obj = new MyClass(); 객체를 인스턴스화하고 생성자를 호출합니다. 가비지 컬렉터가 메모리를 관리합니다.
1
JavaScript let obj = new MyClass(); 새 객체 인스턴스를 생성하고 생성자 함수를 호출합니다.
7
TypeScript let obj: MyClass = new MyClass(); JavaScript와 유사하지만 타입 지정이 가능합니다.
Python 사용하지 않음 (obj = MyClass()) new 키워드 없이 직접 클래스를 호출하여 객체를 생성합니다.
8
Go 사용하지 않음 (obj := MyStruct{}) 구조체 리터럴을 사용하여 객체를 생성합니다.

C++, Java, C#, JavaScript, TypeScript 등의 언어에서는 new 연산자를 사용하여 객체를 생성 

반면 Python이나 Go 같은 언어에서는 new 키워드를 사용하지 않고 다른 방식으로 객체를 생성

new 연산자의 주요 목적은 동적으로 메모리를 할당하고 객체를 생성

 

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string> //strcpy에서 오류나면 string.h로 변경
using std::cout;
class Cat {
private: //생략가능
	int age;
	char name[20];
	// const char* name; //A
public:
	Cat(int age, const char* n) {
		this->age = age;
		strcpy(name, n); // name=n; //A
		cout << name << "고양이 객체가 만들어졌어요.\n";
	}
	~Cat() { cout << name << "객체 바이\n"; };
	int getAge();
	const char* getName();
	void setAge(int age);
	void setName(const char* pName);
	void meow();
};
int Cat::getAge() {
	return age;
}
void Cat::setAge(int age) {
	this->age = age;
}
void Cat::setName(const char* pName) {
	strcpy(name, pName);
	//strcpy(대상주소, 원본주소);
	//strcpy_s(대상주소, 대상의길이, 원본주소);
	//name=pName; //A
}
const char* Cat::getName() {
	return name;
}
void Cat::meow() {
	cout << name << "고양이가 울어요\n";
}
int main() {
	Cat nabi(1, "나비"), yaong(1, "야옹"), * pNabi;
	cout << nabi.getName() << " 출생 나이는 " << nabi.getAge() << "살이다.\n";
	cout << yaong.getName() << " 출생 나이는 " << yaong.getAge() << "살이다.\n";
	pNabi = &nabi;
	cout << pNabi->getName() << " 출생 나이는 " << pNabi->getAge() << "살이다.\n";
	nabi.setName("Nabi");
	nabi.setAge(3);
	cout << nabi.getName() << " 나이는 " << nabi.getAge() << "살이다.\n";
	yaong.meow();
	nabi.meow();
	return 0;
}

 

 

ctrl + f 찾기 + 일괄 바꾸기

 

배열 없애고 결과를 string형을 바꾸기 8번째 줄 확인 12번째줄 29번째 줄

const char*을 std::string로 변경

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string> //strcpy에서 오류나면 string.h로 변경
using std::cout;
class Cat {
private: //생략가능
	int age;
	std::string name;
public:
	Cat(int age, std::string n) {
		this->age = age;
		name, n; // name=n; //A
		cout << name << "고양이 객체가 만들어졌어요.\n";
	}
	~Cat() { cout << name << "객체 바이\n"; };
	int getAge();
	std::string getName();
	void setAge(int age);
	void setName(std::string pName);
	void meow();
};
int Cat::getAge() {
	return age;
}
void Cat::setAge(int age) {
	this->age = age;
}
void Cat::setName(std::string pName) {
	name, pName;
}
std::string Cat::getName() {
	return name;
}
void Cat::meow() {
	cout << name << "고양이가 울어요\n";
}
int main() {
	Cat nabi(1, "나비"), yaong(1, "야옹"), * pNabi;
	cout << nabi.getName() << " 출생 나이는 " << nabi.getAge() << "살이다.\n";
	cout << yaong.getName() << " 출생 나이는 " << yaong.getAge() << "살이다.\n";
	pNabi = &nabi;
	cout << pNabi->getName() << " 출생 나이는 " << pNabi->getAge() << "살이다.\n";
	nabi.setName("Nabi");
	nabi.setAge(3);
	cout << nabi.getName() << " 나이는 " << nabi.getAge() << "살이다.\n";
	yaong.meow();
	nabi.meow();
	return 0;
}

 

○ const 변수
 변수에 const라는 키워드를 사용하여 상수(constant)의 의미를 갖게 하여 그 내용을 변경할 수 없게 함
 #define IN 10  // 전처리기로 컴파일 전에 IN을 10로 변경

 

const int x=1; // 변수 x는 항상 초깃값 1, 

// const를 쓰면 뒤에 x=2 이렇게 써도 초기값이 변경이 되지 않음
  반드시 초깃값 필요함
 int const y=1; // 비추, const는 자료형 앞에 씀
 const int z{1}; // Uniform initialization, C++11
    z{}라고 쓰면 자동으로 0으로 초기화

//위의 3개는 실행할 때 상수로 만들지만 아래는 ↓ 컴파일 할때 상수로 만듬
 constexpr int a = 5; // C++11부터 가능, compile-time constant

 

c++에서 변수를 상수로 만드는 방법

 

실습 5-1: const형 멤버함수
오류 수정

#include <iostream>
class Dog {
	int age; //private 생략함
public:
	int getAge() const;
	void setAge(int a) { age = a; }
	void view() { std::cout << "나는 view"; }
};
int Dog::getAge() const
{
	view(); // 오류 ①
	return (++age); // 오류 ②
}
int main()
{
	Dog happy;
	happy.setAge(5);
	std::cout << happy.getAge();
	return 0;
}

① const 멤버함수는 const 함수만 호출할 수 있고, 일반 함수를 호출할 수 없다. 
  여기서 view함수가 const 함수라면 가능하다. 
② const 멤버함수에서는 멤버변수 age를 변경(++age)할 수 없다.

① Dog::view: 'this' 포인터를'const Dog'에서'Dog &'(으)로변환할수없습니다.
② l-value가 const 개체를지정합니다.
오류 수정 완료

#include <iostream>
class Dog {
	int age; //private 생략함
public:
	int getAge() const;
	void setAge(int a) { age = a; }
	void view() const { std::cout << "나는 view"; }
};
int Dog::getAge() const
{
	view(); // 오류 ①
	return age; // 오류 ②
}
int main()
{
	Dog happy;
	happy.setAge(5);
	std::cout << happy.getAge();
	return 0;
}

const 멤버
 const형 멤버함수는 해당 멤버변수를 변경하는 치환 (replacement)연산을 수행할 수 없다.
 const로 지정된 함수에서는 멤버변수의 값을 변경할 수 없다. 
   즉, 멤버를 참조만 하는 읽기 전용 함수(앞에서 get으로 시작하는 함수)가 된다. 
 const함수는 const함수만 호출할 수 있으며 일반 멤버함수에는 접근할 수 없다. 
   일반 멤버함수에 접근하여 간접적으로 멤버변수를 변경하는 것을 막기 위해  생성자와 소멸자에서는 const를 사용할 수

없다. 
 const형을 선언하고자 하면 멤버변수는 형 앞에 const를, 멤버함수는 함수의 괄호 다음에 const를 추가한다. 
const int age; //멤버변수는 형 앞에
int getAge() const; //멤버함수는 괄호 다음에

 

   int getAge()
   {
   return age;
   }

 

실습 5-2: const 객체 예(오류 수정)


#include <iostream>
class Dog {
	int age;
public:
	Dog(int a) { age = a; }
	int getAge() const;
	void setAge(int a) { age = a; }
	void view() const
	{
		std::cout << "나는 view\n";
	}
}; 
int Dog::getAge() const
{
	view();
	return (age);
}
int main()
{
	const Dog happy(5); //const 객체
	happy.setAge(7); // 오류
	std::cout << happy.getAge();
	return 0;
}


• Happy.setAge(7); 생략
• const 객체 happy에는 const로 지정된 멤버함수만 호출할 수 있다. 
• 해당 클래스의 어떠한 멤버변수도 바꾸지 않는 멤버함수(get으로 시작하는 함수)는 const형으로 선언하는 것이 좋다. 
• 이렇게 하면 컴파일러가 오류를 찾는데 도움을 줄 수 있다.

 

위의 코드 오류 수정

#include <iostream>

class Dog {
    int age;
public:
    Dog(int a) : age(a) {} // 초기화 리스트 사용
    int getAge() const { return age; }
    void setAge(int a) { age = a; }
    void view() const
    {
        std::cout << "나는 view\n";
    }
}; 

int main()
{
    const Dog happy(5); // const 객체
    // happy.setAge(7); // 오류 - 주석 처리 또는 제거
    std::cout << happy.getAge();
    return 0;
}

오류 설명 및 수정사항
주요 오류: happy.setAge(7);
문제: happy는 const 객체로 선언되었기 때문에, 비-const 멤버 함수인 setAge()를 호출할 수 없다.
해결: 이 라인을 주석 처리하거나 제거했습니다. const 객체의 값은 변경할 수 없습니다.

추가 개선사항
생성자 초기화 리스트 사용:

cpp
Dog(int a) : age(a) {}

이는 멤버 변수를 초기화하는 더 효율적인 방법
getAge() 함수 인라인화:
cpp
int getAge() const { return age; }

작은 함수의 경우 인라인으로 정의하는 것이 일반적입니다.
Dog::getAge() 함수의 별도 정의 제거:클래스 내부에서 직접 정의했으므로,클래스 외부의 정의는 불필요

이러한 수정을 통해 코드는 이제 C++ 컴파일러에서 오류 없이 컴파일되며,const 정확성(const correctness)을 준수.
const 객체는 오직 const 멤버 함수만 호출할 수 있으며,이는 객체의 상태가 변경되지 않음을 보장

 

포인터 개요
 포인터(pointer)는 C/C++ 언어를 다른 언어와 차별화시키는 가장 큰 특징
 포인터를 이용하게 되면 기계어나 어셈블리 언어처럼 메모리의 주소를 이용해 메모리의 내용을 직접 접근할 수 있음

 

 포인터가 사용되는 경우
- call by reference로 함수로부터 한 개 이상의 값을 리턴할 때
- 함수들 간에 배열이나 문자열을 전달할 때
- 배열 조작을 쉽게 할 때
- 연결 리스트(linked list)나 이진 트리(binary tree) 등 복잡한 자료 구조를 만들 때
- 메모리를 동적으로 할당할 때
   int *pi=(int*)malloc(100*sizeof(int)); //C
   int *pi=new int[100]; //C++

 

x와 x의 주소

#include <iostream>
int main()
{
	int x = 1;
	std::cout << x << " " <<&x;
	return 0;
}

결과
1 0000009E990FF8B4(주소는 스택의 주소라 매번 다르게 나옴)

 

포인터와 주소
 메모리에는 위치를 구분하기 위해 순서대로 번호가 붙어있는데 이것을 메모리의 주소, 번지, address라 함
 int sum=0;
  - 변수를 초기화하면서 선언하면, int형이므로 4바이트 메모리 공간이 확보되고 초기값으로 0이 할당됨
  - sum변수가 실제로 할당된 메모리의 주소를 알고 싶다면 &연산자를 사용하여
    &sum이라고 하면 변수가 기억되어 있는 메모리 번지를 알 수 있음
 메모리의 주소를 저장하려면 일반 변수가 아닌 포인터를 사용
포인터라고만 해도 되지만 주소를 저장하는 변수이므로 포인터 변수라고도 함

 

#include <iostream>
int main()
{
	int x = 1;
	int *px = &x;  // 주소는 포인터 변수로 저장해야함
	// 따라서 px 앞에 *을 써서 포인터 변수로 선언
	// 포인터 변수는 주소만 넣을 수 있음
	std::cout << x << " " << &x << " " << *px; 
	return 0;
}

------------------------
코드 설명
`#include <iostream>`: 표준 입출력 라이브러리 포함.
`int main()`: 프로그램 진입점.
`int x = 1;`: 정수형 변수 x 선언 및 초기화.
`int *px = &x;`: 포인터 변수 선언. x의 주소 저장.
`std::cout << x << " " << &x << " " << *px;`: 출력문.
- x: 변수 값 출력.
- &x: 변수 주소 출력.
- *px: 포인터가 가리키는 값 출력.
`return 0;`: 프로그램 정상 종료 표시.

포인터 개념 설명:
- 변수: 메모리 상 주소 보유.
- &: 주소 연산자.
- 포인터: 주소 저장 변수.
- *: 역참조 연산자.

프로그램 기능: 변수 값, 주소, 포인터 통한 값 접근 예시.

 

포인터 변수의 자료형?
 자료형 *포인터변수명;
 int *pi;
- 변수 pi가 int형 데이터를 참조하기 위한 포인터 변수
- pi가 지시하는 곳의 자료형이 int형
 char *pc;
- 변수 pc가 char형 데이터를 참조하기 위한 포인터 변수
- pc가 지시하는 곳의 자료형이 char형

 

기말고사에 나오는 문제

 

정적 vs. 동적 메모리 할당
 정적 메모리 할당

- 컴파일시 필요한 메모리 할당
- 배열 : int array[32]; // 항상128byte 공간 확보
- 사용하지 않는 메모리까지 충분히 잡아 낭비
- 너무 큰 메모리는 할당 불가
- 보통 수 kB까지는 스택 사용, 그 이상은 heap에 할당
- Windows(Visual Studio)에서 기본 스택 크기는 1MB
- Linux와 macOS에서 기본 스택의 크기는 8MB

 

 

 동적 메모리 할당
- 실행시 필요한 메모리(heap) 할당
- 필요한 만큼만 잡음
- 메모리의 주소를 사용(포인터 이용)하여 접근
- malloc()/free() 사용 // C
- new/delete사용 // C++
- new : 메모리 할당, 할당된 메모리 시작주소 리턴됨
- delete : 할당 받은 메모리 해제

 

 

 

실습 5-4: 동적메모리 할당(필요한 만큼의 메모리만 실행시 할당)
기말고사에 나올 확률이 있음

#include <iostream>
#include <stdlib.h> // exit() 함수를 사용하기 위한 헤더

int main()
{
    int i, n;
    int* num;  // num에는 주소만 저장,
    // 포인터 변수 선언. 동적 할당된 배열의 시작 주소를 저장할 예정
	// 배열로 쓰고 있는데 배열이 아닌 포인터로 선언되어 있음
    std::cout << "몇 개의 숫자를 입력하시겠습니까==";
    std::cin >> i;

    num = new int[i]; // 동적으로 int 배열 할당. num은 배열의 첫 번째 요소 주소를 가리킴
    if (num == NULL) exit(1); // 메모리 할당 실패 시 프로그램 종료
		// 대괄호가 나온 것은 배열
		// 배열로 쓰고 있는데 배열이 아닌 포인터로 선언되어 있음
		// 배열의 이름은 그 배열의 시작 주소이다.
    // 사용자로부터 숫자 입력 받기
    for (n = 0; n < i; n++)
    {
        std::cout << "숫자를 입력하십시오 : ";
        std::cin >> num[n]; // 포인터를 배열처럼 사용. num[n]은 *(num+n)과 동일
    }

    // 입력받은 숫자 출력
    std::cout << "당신이 입력한 숫자는: ";
    for (n = 0; n < i; n++)
        std::cout << num[n] << ", ";

    delete[] num; // 동적 할당된 배열 메모리 해제. []를 생략하면 첫 번째 요소만 해제됨
    return 0;
}

이 코드의 동작 과정

1. 프로그램 시작:
   main() 함수 실행 시작.

2. 사용자 입력 요청:
   "몇 개의 숫자를 입력하시겠습니까==" 메시지 출력.
   사용자로부터 정수 i 입력 받음.

3. 동적 메모리 할당:
   new int[i]로 i개의 정수를 저장할 수 있는 배열 동적 생성.
   생성된 배열의 시작 주소를 num 포인터에 저장.

4. 메모리 할당 확인:
   할당 실패 시(num == NULL) 프로그램 종료.

5. 숫자 입력 루프:
   for 루프로 i번 반복.
   각 반복마다 사용자에게 숫자 입력 요청.
   입력된 숫자를 num[n]에 저장 (n은 0부터 i-1까지).

6. 입력된 숫자 출력:
   "당신이 입력한 숫자는: " 메시지 출력.
   for 루프로 num 배열의 모든 요소 출력.
   각 숫자 뒤에 쉼표와 공백 추가.

7. 메모리 해제:
   delete[] num으로 동적 할당된 메모리 해제.

8. 프로그램 종료:
   return 0으로 main() 함수 종료.

주요 특징:
- 동적 메모리 할당으로 런타임에 배열 크기 결정.
- 포인터를 배열처럼 사용.
- 사용자 입력에 따라 유동적으로 메모리 사용.

이 코드는 사용자가 지정한 개수만큼의 숫자를 입력받고 출력하는 간단한 프로그램. 
동적 메모리 할당의 기본적인 사용법 예시.
실습 5-5: 객체 동적할당

#include <iostream>
class Dog {
private:
	int age;
public:
	int getAge() const;
	void setAge(int a);
};
int Dog::getAge() const
{
	return age;
}
void Dog::setAge(int a)
{
	age = a;
}
int main()
{
	Dog* dp;
	dp = new Dog; 
	// Dog *dp=new Dog
	if (!dp) {
		std::cout << "메모리할당 불가!";
		return 1;
	}
	dp->setAge(5);
	std::cout << "메모리에 할당된 값은 "
		<< dp->getAge() << "입니다."; 
	// 포인터 객체는 .이 아닌 ->로 접근한다
	delete dp; //delet 쓰는것 매우 중요
	return 0;
}

 

 

실습 5-6: 배열객체 동적 할당

```cpp
#include <iostream>

class Dog {
private:
    int age;
public:
    int getAge() const; // const 추가: 객체의 상태를 변경하지 않음을 명시
    void setAge(int a);
};

int Dog::getAge() const // const 추가: 선언과 일치
{
    return age;
}

void Dog::setAge(int a)
{
    age = a;
}

int main()
{
    Dog* dp;
    dp = new Dog[10]; // 10개의 Dog 객체 배열 동적 할당
    // dp를 배열처럼 씀
    
    if (!dp) {
        std::cout << "메모리할당이 되지 않았습니다.";
        return 1; // 메모리 할당 실패 시 프로그램 종료
    }
    
    // 각 Dog 객체의 나이 설정
    for (int i = 0; i < 10; i++)
        dp[i].setAge(i);
    
    // 각 Dog 객체의 나이 출력
    for (int i = 0; i < 10; i++)
        std::cout << i << "번째 객체의 나이는 " 
                  << dp[i].getAge() << " 입니다. " << std::endl;
    
    delete[] dp; // 배열 형태로 할당된 메모리 해제
    return 0;
}
```

주요 수정 및 설명:

1. `getAge()` 함수에 `const` 추가:
   - 클래스 선언과 정의 모두에 `const` 추가.
   - 이는 함수가 객체의 상태를 변경하지 않음을 보장.

2. 주석 추가:
   - 코드의 주요 부분에 설명 추가.
   - 동적 할당, 객체 배열 사용, 메모리 해제 등 설명.

3. 메모리 할당 확인:
   - `if (!dp)` 문으로 메모리 할당 실패 시 처리.

4. 배열 형태의 메모리 해제:
   - `delete[] dp`로 올바르게 배열 메모리 해제.

이 코드는 Dog 객체의 배열을 동적으로 생성하고, 각 객체의 나이를 설정한 후 출력하는 예제
`const` 정확성을 개선하고, 메모리 관리를 적절히 수행
const 함수를 지정할 수 있는건 지정해서 소스를 보기 편하게 함.
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
using std::cout;
class Cat {
private: //생략가능
	int age;
	std::string name;
public:
	Cat(int age, std::string n) {
		this->age = age;
		name = n;
		cout << name << "고양이 객체가 만들어졌어요.\n";
	}
	~Cat() { cout << name << "객체 바이\n"; };
	int getAge() const;
	std::string getName() const;
	void setAge(int age);
	void setName(std::string pName);
	void meow() const;
};
int Cat::getAge() const {
	return age;
}
void Cat::setAge(int age) {
	this->age = age;
}
void Cat::setName(std::string pName) {
	name = pName;
}
std::string Cat::getName() const {
	return name;
}
void Cat::meow() const {
	cout << name << "고양이가 울어요\n";
}
int main() {
	Cat nabi(1, "나비"), yaong(1, "야옹"), * pNabi;
	cout << nabi.getName() << " 출생 나이는 " << nabi.getAge() << "살이다.\n";
	cout << yaong.getName() << " 출생 나이는 " << yaong.getAge() << "살이다.\n";
	pNabi = &nabi;
	cout << pNabi->getName() << " 출생 나이는 " << pNabi->getAge() << "살이다.\n";
	nabi.setName("Nabi");
	nabi.setAge(3);
	cout << nabi.getName() << " 나이는 " << nabi.getAge() << "살이다.\n";
	yaong.meow();
	nabi.meow();
	return 0;
}

동적으로 할당하게 되면 heap라는 공간에 할당