C++ 이론(2)

Hi Beck·2023년 6월 28일

TIL-Language(C & C++)

목록 보기
28/34

복습 내용 정리

1.std::cout
-> 스탠다드 해더파일(iostream) 안에 있는 표준함수인 cout를 불러오겠다.

2.c언어에서는 해더파일들을 가져와서 참조했었는데 c++은 해더파일이 아닌 그냥 파일 통째로 가져온다.

::은 namespace라는건데 작업공간을 의미한다.코드를 작업하는 공간의 소유주를 정해놓는다라고 생각하면된다.문법이라고 생각하면되는데 아래처럼 이름을 지정해주면 코드를 칠 수 있는 공간이 확보되고 그 안에 코드를 치면되는데 이걸 main에서 std::cout처럼 "namespace명::함수이름"으로 쓰면된다.

EX)

namespace seo {
int SPEED()
}

int main(){
seo::SPEED
}

이걸 왜 만들었냐? 협업할 때 각자 만든 동일하거나 유사한 기능의 함수들이 겹치는데 이걸 구분하기위해 만들어준 문법이다.

EX) seo::SPEED 와 kim::SPEED로 구분

::치기 귀찮으니깐 써주는 문법
using namespace 생략할이름;
== 내가 코드에서 이 이름의 네임스페이스를 사용하겠다.
== 이 네임스페이스의 이름을 생략하겠다.

3.출력할때 문자 쓰고 꺽쇠쓴 후 변수 이름쓰면 문자 변수값이 출력됨
입력할때는 꺽쇠 반대로,그리고 입력값 2개면 꺽쇠로 2개 나눠서 순서대로만 써주면됨
(cin >> 입력한 값을 변수쪽으로 읽어드림)

4.생성자(=Constructor, C++에 특화된 문법)
=객체(클래스 변수,클래스로 만든 객체가 변수다.)가 생성될 때 필드(=맴버변수)나 여러 메소드(=맴버함수)들을 초기화하는 함수

특징1 : 일반 함수 정의문과 다르게 생성자 함수는 모양이 다른데 반환형이 없다.
(이유: 일반함수랑 구분하기 위해서,같은 모양이면 구분이 힘듬)

특징2 : 생성자의 이름은 무조건 그 클래스의 이름과 동일해야된다.

특징3 : 생성자는 무조건 public에서 선언되어야한다.

특징4 : 생성자는 문법적으로 강제하는 함수다
= 모든 클래스에는 생성자가 반드시 1개 이상은 존재한다.
내가 객체에서 생성자를 지정해주지않아도 빌드할때 컴파일러가 자동으로 기본 생성자 하나를 만들어준다.

예시)
class Test{
int scores;
public
test(){
score=100;
}
}

이렇게 내가 원하는 값으로 초기화를 시켜줄 수도 있고 score=100;를 안해줄 수도 있는
Default Constructor로 넣어줄 수 있다.

Default Constructor는 매개변수가 없거나 초기화 된 매개변수를 가진 생성자을 의미한다.

  • c언어 구조체도 기본생성자가 자동으로 초기화해준다.

무조건 만들어주냐?

  • 클래스에 기본자료형(int,int형 배열 등)만 존재한다.
    -> 초기화를 직접 해줄필요가 없다.

  • 클래스에 사용자정의자료형이 존재한다.(=클래스 안 클래스)
    -> 생성자가 자동으로 생성되지않는다.(컴파일러마다 상이)

즉,해줄 수도 있고 안해줄 수도 있다는것은 그냥 우리가 직접해주는게 합리적이다.
이때 우리가 만들어주면 컴파일러에서 따로 안만들어준다.

특징5 : 생성자는 함수인데도 불구하고 호출할 필요가 없다.==자동으로 호출된다.

언제냐면 "객체가 생성될 때"다.
(엄연히 정확하게 말하자면 생선된 직후임)

클래스가 정의될 때가 아닌 객체가 선언(=생성)될 때이다.
class Mycar{} x
Mycar car; o -> 이때 마이카의 생성자가 불려지는것이다.그러면서 정의문 안에 있던 변수들이 초기화가 되는것

특징6

class mycar{

public:
test();

private:
int num; //멤버변수 선언

}

mycar::test(){
num = 0;
cout << "mycar 생성자 호출" << endl;

}

int main(){

mycar A; //객체 선언

}

위 예시 코드를 보면 클래스 마이카 정의문에 public안에 멤버함수로 생성자 함수 하나만 선언했는데 얘도 C언어 함수처럼 클래스 내부에다가는 선언만 해놓고
(주의할 점은 생성자도 정의랑 선언은 다르다. 정의 -> mycar(){} 선언 -> mycar();)
외부에다가는 생성자 정의문을 만들어주는게 가능해서 클래스 외부에다가 생성자함수를 정의한 것이다.

근데 생성자를 밖에다가 정의할때는 일반함수와 구분해줘야된다.
생성자는 반환형이 없는게 일반함수와 구분할 수 있는 특징이였는데 클래스 외부에서 구분할려면 반환형이 있든없든 생성자 함수는 앞에 namespace처럼 클래스명::을 정의문 생성자이름 앞에 써주면된다.
mycar::test() OR void mycarspeed::test()

생성자 정의문안에는 변수와함수들을 초기화할 수 있으니 변수와 함수들이 들어갈 수 있음

메인에서 클래스 변수 즉 객체가 선언(생성)될 때 동시에 호출돼서 생성자정의문에서 초기화로 넣어준 코드들이 실행된다.
(코드 실행하면 출력문에 써줬던 글이 출력됨)

결론 : 객체가 생성될 때 생성자가 자동으로 불린다.

특징 7 : 생성자가 1개가 아닐 수도 있다.
우리가 봤던 생성자(기본 생성자)는 인자, 매개변수가 없었다.
근데 인자를 받는, 매개변수를 받는 생성자를 만들 수 있다.(int형을 받는, float형을 받는)

이런 생성자도 정의문은 기본형이랑 비슷하다.

클래스명::생성자(매개변수)
{

}

ex)
Mycar::Myclass( )
{

}
Mycar::Myclass(int NUM)
{

}
Mycar::Myclass(float f1, float f2)
{

}

이렇게 생성자를 여러개 정의하고 메인에서 변수를 선언해서 실행해도 에러가 나지않는다.
근데 이렇게 정의된 생성자가 많아도 객체가 선언될 때는 하나의 생성자 함수만 불린다.

그러면 여러개 중 불릴 생성자를 어떻게 판단하냐?
-> 메인 함수에서 변수 선언문의 넘겨주는 인자값의 자료형을 보면된다.

ㄱ. 기본생성자가 불리는 경우 -> 선언된 변수의 인자가 아무것도 없을때
ex) Mycar a;
ㄴ. int형을 받아오는 생성자가 불리는 경우 -> 선언된 변수의 인자가 int인 경우
ex) Mycar b(5);
// Mycar b = Mycar (5)(일반 호출문과 비슷하며, 생성자로 호출하는 개념으로 보면된다.)와 같다.
// == "나는 int형 하나 가지고 b라는 객체로 만들겠어

그렇다면 생성자를 왜 이렇게 여러개 정의하냐?
-> 생성자는 멤버변수와 멤버함수를 초기화해주는 함수라고 생각하면 내가 객체를 선언할때 그에 필요한 변수들의 초기값을 가져와서 선언 동시에 초기화 시켜주고싶을때가 있어서 변수의 자료형만큼 여러개 생성자를 만들어주는것이다.

보통은 생성자를 하나로 하는데 여기에 인자 모두를 받아올 수 있도록한다.
(==내가 가지고 있는 변수들에 대해서 초기화할 수 있는 값을 다 가지고 있는 생성자)

ex)
class Mycar{

public
MYcar();
}

Mycar::Mycar(int num,float f1,float f2)
{
num1 = num;
float1 = f1; //이때 f1의 값은 메인함수에서 객체를 생성할 때 넘겨주는값임
float2 = f2;
cout << "여러개 생성자 호출 가능" << endl;
}

int main(){
Mycar a(5, 1.5, 3.1);

}

  1. 초기화 리스트(Initialization List)
    생성자에서 필드를 간단하게 초기화하는 방법 -> (b)의 방식

생성자에서 초기화하는 방법은 두 가지가 존재하는데 (a)하나는 위처럼 생성자 정의문 내부에 넣어주는법이 있고 (b)두번째는 아래처럼 쓰이는 방법이다.

Ex)
class::생성자명()
:num(0) // num=0과 같음
{

}

(b)의 방식은 언제쓰이냐?
-> 레퍼런스 즉 참조형변수 or const변수를 클래스 정의문 안에 멤버변수로 넣었을때
(선언과 초기화 동시에 해줘야되는 변수가 이 2개임)

  • const변수(=상수형 변수,상수화 변수)
    : 상수화 시켜버려서 값이 한번 정해지면 변하지않는 변수

레퍼런스 변수는 정의문에선 살아있지않고 객체가 생성될때 같이 생성되는데 이때 레퍼런스 특성상 생성 동시에 초기화까지 되어야한다.근데 생성 직후에 생기는 생성자에서 레퍼런스를 초기화시키면 너무 늦어서 에러가 난다.그럼 여기서 언제 참조형 변수를 초기화 해야되냐면 바로 (b)처럼 생성자명과 중괄호 사이에 참조형변수를 초기화시켜주면 참조형변수는 생성 동시에 초기화하는게 가능해진다.

정리
참조형변수 초기화 -> (b) -> 객체 생성 동시에
그 외 다른 변수 초기화 -> (a) -> 객체 생성 직후

(b)의 문법을 바로 초기화 리스트라고 한다!그리고 이게 가능한 이유는 이런 목적을 위해 특별히 만들어진애여서(생성자보다 먼저 불리는 애를 만들어줄태니 생성자 안에 표기해놔라)

근데 그냥 클래스 정의문 내에서 변수 옆에 초기화식을 넣어주면 안되냐?
-> 그래도 된다. 그래서 생성자 문법이 중요성이 높은것은 아니다.

  1. 결국 생성자는 자동으로 초기화시켜줄때도 있고 직접해줄때도 있지만 컴파일러에서 자동으로 초기화를 하는데 있어 제대로 하지않을 가능성이 있으니 왠만하면 직접 생성자를 만들어주자.(100% 컴퓨터를 믿지말자) 본인이 생성자를 컨트롤하는게 중요하다.

-> 이게 바로 클래스를 다루는 첫번째 조건이다!!!

7.동적할당영역

프로그래머가 힙영역에서 직접 메모리를 빌려왔으면 반납도 직접 해줘야된다.내가 반납하는것을 까먹으면 그 메모리는 영원히 못쓴다.(운영체제는 내가 계속 쓰고있는걸로여김)

이 반납하는 행위를 생성자와 소멸자란 것으로 컨트롤해서 반납한다.
생성자를 쓰는건 c++에서 단점이 아니라 동적할당영역과 관련하여 장점이다.

전역변수는 운영체제가 시작과 동시에 끝날때까지 쓸 수 있는 장점이 있는 변수다.
근데 전역변수의 단점도 있는데 전역변수로 쓸 수 있는 공간이 작다.
또한 만약 int형 배열의 크기가 100억이라하면 너무 많아서 메모리 컨트롤이 불가능하다.

만약 배열의 크기가 1억이라면,이 배열을 선언해놓은 다음에 초기화를 0으로 해놓고,다음과 같이 코드가 있다고 가정하면

int num[1000000000]; 선언
num = 0;

int main() {
num //num을 이용한 코드1
num //num을 이용한 코드2
num //num을 이용한 코드3

...

추가적인 코드 10000줄이 있다고 가정

}

이때 int형 변수 num은 main 함수 내에서 계속 살아있다.(만 줄이 끝날때까지)
num이 쓴 세줄만큼만 살아있으면되는데 이후 다른 코드에서도 살아있고 그말은 곧 메모리가 계속 할당이 되어있어 메모리가 낭비가 된다.

이런 낭비를 막고자 운영체제가 하나의 공간을 제공하는데 그게 바로 힙영역이다.
여기에선 개발자가 원할때 메모리를 할당받고 원할때 메모리 반납이 가능하다.
(지역변수도 사라지긴하지만 괄호 영역 안에서 중간에 반납이 불가한걸 생각해보면 특수능력임)

물론 위 상황에 NUM을 지역변수 내에서 따로 쓰는 방식으로 유의미하게 만들어도되는데 여기서는 반환을 해주는 작업을 별도로 해줘야된다.(복사값)

그런고로 동적할당영역에선 NEW라는 함수를 이용한 문법으로 메모리를 할당받고 반납해야된다.

8.동적할당영역 문법
(동적 : 동적으로 크기를 바꿔가면서 할당 받을 수 있다.)

ㄱ.main함수에서 메모리를 할당받고싶을때

int* ptr = new int; 라는 문법을 쓴다.

여기서 new는 함수이고 int는 자료형인데 이것은 곧 "new int의 결과값을 ptr에 넣어준다"다. 즉 new int의 결과값은 주소값이라는것을 추측할 수 있다.

-> 포인터변수에 힙영역 중 임의의 공간의 주소값을 받아오는게 목적이다.

우리가 포인터변수를 사용할 땐 일반적으로 아래와 같이 주소값을 담는다.
int num = 3;
int * p = #

num에 접근할 수 있는 방법은 2가지인데 이름과 포인터가 가리키는 주소값이다.근데 지금 여기선 new int는 가리키는 메모리의이름을 모른다.(변수 실체에 대한 이름이 x)

이름을 몰라도 할당받을 수 있는게 힙공간의 특징인데 이 문법이 의미하는것은 "운영체제로부터 int형 만큼의 메모리를 할당해줘"와 같다.

이 문법을 실행하면 힙영역에 운영체제가 int형 변수 하나를 선언한다.(단,이름없는 변수로) 그럼 이 변수에는 어떻게 접근하냐?
-> 이름으로 접근하는것은 안되고 주소값으로 접근해야되는데 이때 NEW 함수가 주소값을 알려주는것이다.

NEW 함수 = "이름 없는 변수를 힙공간에 만든 후 그 주소값을 가져와줘"

이렇게 주소값이 포인터변수 ptr로 들어오면, 그 변수에 접근할때는 포인터 쓰는것처럼

  • ptr = 3; 식으로 값을 안에 있는 값을 바꿔주면된다.

ㄴ.main함수에서 할당받은 메모리를 반납하고싶을때의 문법

delete ptr;

ptr은 이전에 할당받았던 주소값을 가지고있는 포인터변수고 이것을 넣어주면 알아서 반납이 된다.

9.동적할당의 특별한 장점은 배열을 할당 받을 수 있다는것인데, 배열은 크기가 정해져있고,우리는 크기를 맘대로 바꿀 수 없다.근데 동적배열할당을 하게되면 배열의크기를 우리가 정할 수 있게된다.

배열의 크기에는 보통 상수가 들어가고 변수는 들어가지못한다.코드에 따라서 배열의크기가 달라진다는게 불가능하기때문이다.코드를 짤 때 배열의크기는 무조건 고정되어있어야한다.근데 여기선 변수가 들어가는게 가능해진다.

만약 할당을 할 때 배열로 동적배열할당을 할 시 아래와 같이 코드를 짜면

int * ptr = new int[10];

//int형 이름없는 배열 10개(40바이트)를 힙영역에서 할당해주고 그 주소값을 가져와줘
(이때의 주소값은 첫번째 배열의 주소값 이걸 ptr에 넣어주는 것임,배열의주소는 배열의 이름이지만 여기선 이름을 모르니 x)

여기선 상수 대신에 변수가 들어갈 수 있다.
cin >> num을 써주는 식으로 우리가 배열 크기를 입력할 수 있음

10.변수가 들어가면 좋은 이유
유동적인 코드를 만들 수 있다.상수를 배열의 크기로 정한 코드는 데이터 관리를 할때 현황이 바뀌면 그때마다 코드를 일일히 고쳐야되는데 동적배열할당을 받으면 cin 함수로 배열의크기를 입력받아 유동적으로 할 수 있다.

profile
Emotional realizer

0개의 댓글