본문 바로가기
# Semiconductor/- Semicon Academy

[Harman 세미콘 아카데미] 20일차 - Github 사용법, C++(함수 오버로딩~class 기초)

by Graffitio 2023. 7. 14.
[Harman 세미콘 아카데미] 20일차 - Github 사용법, C++(함수 오버로딩~class 기초)
728x90
반응형
[Github]

 

진정한 개발자는 Github를 사용한다.

깃허브 홈페이지
로그인하면 뜨는 나의 대시보드

 

깃허브에는 우리가 만든 소스 파일을 저장(push)하고 불러올(pull) 수 있는 기능이 존재한다.

실제로는 수많은 기능들이 존재하지만, 처음이니까 이 기능부터 알아보자

 

<Push/Pull 사용법>

① 리포지토리 생성

② Visual studio를 Github와 연동(?)

③ 소스 파일을 Push/Pull

 

 

① 리포지토리 생성

     리포지토리

     : 개발자가 애플리케이션 소스 코드에 대한 변경을

       수행 및 관리하는 데 사용하는 중앙화된 디지털 스토리지

좌측 상단의 Crsate reposistory 클릭
위에서부터 순서대로 선택 후 녹색 버튼 클릭

readme.md : 나의 repos를 설명해주는 역할을 한다.

gitignore : 굳이 업로드할 필요 없는 파일들을 필터링한다.

 

② Visual studio를 Github와 연동

리포지토리 링크 복사
gitignore에 들어가서 해당 내용 추가(디렉토리 단위로 올리지 말라는 의미)
리포지토리 복제 클릭
리포지토리 링크 붙여넣고 복제시작
연동만 시켰고, 아직은 아무 것도 없음.
아까 생성한 readme 와 gitignore만 존재
작업물 붙여넣기
우측에 복사한 파일들 나타남
작업물 수정 가능

 

 

③ 소스 파일 Push/Pull

메세지나 입력(필수)하고 모두 커밋 버튼을 누르면, 커밋 생성 완료
왼쪽부터 Fetch, Pull, Push

Fetch

로컬 저장소와 원격 저장소의 변경 사항이 다를 때 이를 비교 대조하고

   git merge 명령어와 함께 최신 데이터를 반영하거나 충돌 문제 등을 해결한다.

Pull

모트 리포지토리에서 로컬 리포지토리로 내려받는 것

Push

로컬 리포지토리의 파일을 리모트 리포지토리로 올리는 것

 

푸시했는데, 에러 발생

에러 발생 이유 : repos 복제만 됐지, 사용자명과 메일이 연동되지 않았기 때문에 에러 발생

원격관리 클릭
사용자 이름과 e-mail 입력해주면 문제 해결

<그래도 안 된다면?>

나의 깃허브 계정으로 로그인이 안 되어 있어서 오류 발생할 수도 있음.

 

리포지토리 복제
순서대로 클릭하면 오류 해결됨
푸시하면 정상적으로 업로드된 것을 확인할 수 있다.
내용 변경하면, 좌측에 파란색 Bar가 나타난다.
변경 내용이 있다고 버퍼에 일시적으로 저장됨.

 


 

[C++]

 

함수 오버로딩

 

일반적인 C언어 프로그램에서는 abs(), fabs()처럼 함수명에 신경을 많이 써야 한다.

이러한 귀찮음을 해결하기 위해 함수 오버로딩을 사용한다.

 

함수 오버로딩은 이름은 같지만, argument와 정의된 내용은 다른 것으로 사용할 수 있게 해주는 기능이다.

C++의 특징 중 하나이며, 컴파일러는 함수명이 같아도 인수나 내용만 다르면 다른 함수로 취급한다.

 

<예시>

절대값(|-1|)을 만들어주는 함수 abs()

|   | 내에는 정수와 실수 둘 다 사용할 수 있게 만들어야 하는데, 그러기 위해서는 함수를 2개 만들어야 한다.

여기서 함수명에 신경을 좀 덜 쓰기 위해 함수 오버로딩 사용

 

#include <stdio.h>

int abs(int val);
float abs(float val);

int main()
{
	int i = -1;
	float a = -2.0;

	printf("i : %4d ===> i의 절대값은 %4d 입니다.\n", i, abs(i));
	printf("a : %4.2f ===> a의 절대값은 %4.2f 입니다.\n", a, abs(a));

	return 0;
}

int abs(int val) // argument val의 절대값
{
	if (val < 0) return -val;
	return val;

	// return (val < 0) ? -val : val; // 이렇게 한 문장으로 써줄 수도 있다.
}

float abs(float val)
{
	return (val < 0) ? -val : val;
}

 


 

매개변수를 설정하는 "디폴트 값"

 

예를 들어 y = 1 / (x₁ - x₂) 에서

x₁ - x₂ = 0이 되면, 연산에 있어 가장 치명적인 오류가 발생한다.

이와 같이 절대로 발생하지 말아야 할 상황을 커버하기 위해 디폴트 값이 존재한다.

디폴트에는 제약 사항이 있다.

함수의 argument가 여러 개 있고 부분적으로 디폴트 값을 설정한다면,

ex) int sum(int a, int b, int c = 10)

stack 영역에 인자(argument)는 왼쪽에서부터 채워지므로 디폴트값은 오른쪽부터 채워야 한다.

▶ First in / Last out & Last in / First out, 선입 선출의 개념으로 생각하면 쉽다.

컴파일러는 순서대로 값을 읽어들이는데, 디폴트값이 중간중간에 껴있어 버리면 오류가 발생할 수 있다.

따라서 디폴트 값은 인자가 채워지는 왼쪽과 반대인 오른쪽부터 채워져야 한다.

▶ int abs(int a, int b, int c = 3, int d = 1)

 


 

함수의 inline 설정

 

컴파일러는 선언되어 있는 함수를 호출과 반환할 때, 여러가지 과정을 거치게 되고

만약 빈번하게 함수의 호출이 발생한다면, 로스가 발생하고 클럭도 많이 잡아먹게 된다.

 

따라서 함수 호출에 따른 불필요한 사전/사후 처리를 제외하기 위해 inline 이라는 것이 존재하며

코드를 그냥 메인 함수로 따와서 실행해버리는 기능이다.


하지만, inline을 사용하면 중복되는 코드가 많아지고 복잡해지며 지저분해진다.

따라서 아주 꼭 필요한 경우가 아니면 권장되지 않는 기능이다.

 


 

매크로

 

함수를 더 편하게 쓰기 위해서 매크로라는 기능이 존재한다.

따지고 보면, 함수는 아니고 함수같은 모양을 하고 있다.

 

일반적인 함수는 자료형에 있어 독립적이지 못하다.

▶ 자료형 선언이 필요하다.

반면에 매크로는 자료형에 있어 독립적이기 때문에 따로 자료형을 선언할 필요가 없다.

 

※ 주의할 점

    - 매크로 정의 시, 괄호는 최대한 많이 쓸 것

       ex) x=-1일때, -1<0?--1:-1 이렇게 --라는 이상한 char 발생

    - 공백없이 정의할 것(오류 발생 소지가 있다.)

 

<예시>

절대값 함수는 정수와 실수타입 두 가지를 다 만들어 줘야 한다.

반면에 매크로는 하나만 정의해두면 어떤 자료형이든 사용 가능하다.

 

#include <stdio.h>
#define ABS(x) (((x)<0)?(-(x)):(x)) // 매크로 정의

int main()
{
	float f = ABS(-3.4296);

	printf("i의 절대값 : %4d\n", ABS(-1));
	printf("f의 절대값 : %4.2f\n", f);


	return 0;
}

 


 

Namespace

 

C++에서는 변수, 함수, 구조체, 클래스 등을 서로 구분하기 위해 이름으로 사용되는 다양한 내부 식별자(identifier)를 가지고 있다.

하지만 프로그램이 복잡해지고 여러 라이브러리가 포함될수록 내부 식별자 간의 충돌이 발생할 가능성도 커졌다.

이러한 문제를 해결하기 위해 namespace를 사용할 수 있다.

 

정의

내부 식별자에 사용할 수 있는 유효 범위를 제공하는 선언적 영역을 의미한다.

C++에서는 namespace 키워드를 사용하여 사용자가 새로운 네임 스페이스를 정의할 수 있다.

일반적으로 헤더파일에서 정의되며, 언제나 수정할 수 있도록 개방되어 있다.

 

namespace Modesty
{
    void invest(); // 함수의 원형
    int stock; // 변수의 선언
}

namespace Semiconductor
{
	ㅇ;
    float HBM; // 변수 선언
    int MOSFET; // 변수 선언
}

 

namespace로의 접근 방법

namespace에 접근하기 위해서는

1. 범위 지정 연산자(::, scope resolution operator)를 사용하여 해당 이름을 특정 namespace로 제한하면 된다.

2. using 지시자, using 선언을 활용하여 접근

 

#include "namespace.h"

Modesty::stock = 3;
Semiconductor::MOSFET = 67;

// 기본적으로 정의되어 있는 namespace인 std //
std::cout // console out : 모니터로 출력해라.
std::cin // console in : 키보드로 입력을 받아라.
// using 지시자 //
using namespace Modesty; // using namespace 네임스페이스명;
// 해당 블록에서만 해당 네임스페이스의 모든 이름을 사용할 수 있게 해준다.

// using 선언 //
using Modesty::stock; // using 네임스페이스명::변수명;
// 단 하나의 변수를 ::없이 쓸 수 있게 해준다.

 


 

Call by value & Call by reference

 

1. Call by value

    : "값" 을 받아서 사용하고자 하는 함수의 매개 변수에 전달하는 방식

 

#include <stdio.h>

// call by value //
void swap(int a, int b);

int main()
{
	int i = -100;
	int j = 100;

	printf(" i : %d, j : %d\n", i, j); // 처리 전 데이터
	printf(".... swap porcessing ....\n");
	
	swap(i, j);

	printf(" i : %d, j : %d\n", i, j); // 처리 후 데이터

	return 0;
}

void swap(int a, int b)
{
	int c = a;
	printf("\n    |.... in swap porcessing ....\n");
	printf("    | a : %d, b : %d     \n", a, b); // 원본 데이터
	printf("    | .... swap porcessing ....\n");
	a = b;
	b = c;

	printf("    | a : %d, b : %d     \n\n", a, b); // swap 이후의 데이터
}

.

i의 값은 a에 복사되고, j의 값은 b에 복사되어 전달하기 때문에

i와 j의 값이 swap된 것이 아닌, a와 b의 값만 바뀌었다.

 

2. Call by reference

    : "주소값" 을 받아서 사용하고자 하는 함수의 매개 변수에 전달하는 방식

 

#include <stdio.h>

 // call by reference //
 // 따지고 보면, call by pointer //
void swap(int *a, int *b); // 함수 프로토타입 선언

int main()
{
	int i = -100;
	int j = 100;

	printf(" i : %d, j : %d\n", i, j); // 처리 전 데이터
	printf(".... swap porcessing ....\n");

	swap(&i, &j); // 반드시 주소값을 넣어줘야 한다.(&연산자 사용)

	printf(" i : %d, j : %d\n", i, j); // 처리 후 데이터

	return 0;
}

void swap(int *a, int *b)
{
	int c = *a; // c는 a가 가리키고 있는 곳의 값을 받는다.
	printf("\n    .... in swap porcessing ....\n");
	printf("     a : %d, b : %d     \n", *a, *b); // 원본 데이터
	printf("     .... swap porcessing ....\n");
	*a = *b;
	*b = c;

	printf("     a : %d, b : %d     \n\n", *a, *b); // swap 이후의 데이터
}

value 대신 메모리 주소값을 저장하는 포인터 변수를 받아와 a가 가리키는 변수인 i를 바꾸는 함수가 되었다.

 


 

Reference의 이해

 

C++에는 reference라는 개념이 있다.

일종의 별명이라고 생각하면 편한데, "변수의 주소" 라는 의미를 가진다.

마치 포인터의 대용처럼 쓰면서 변수처럼 사용되고

함수 호출 시, argument에 &를 붙여주지 않아도

붙인 것처럼 취급해주겠다고 만든 것이 바로 reference이다.

 

즉, int num = 100; 일때,

int &n = num; 이렇게 써주면 num 변수의 주소는 n 으로 취급하고

n은 num의 reference 가 된다.

 

※ 주의 사항

1. 상수를 대상으로는 reference 선언이 불가능하다.

2. 반드시 선언과 동시에 초기화가 같이 이루어져야 한다.

3. 포인터처럼 NULL로 초기화하는 것도 불가능하다.

4. 다른 변수로 assign할 수 없다.

    ∵ 포인터처럼 메모리 공간을 차지하는 것이 아닌, 하나의 별명처럼 존재하는 것이기 때문

 

<Call by reference 에 적용>

#include <stdio.h>

// 진짜 call by reference // 
void swap_ref(int &a, int &b);

int main()
{
	int i = -100;
	int j = 100;

	printf(" i : %d, j : %d\n", i, j); // 처리 전 데이터
	printf(".... swap porcessing ....\n");

	swap_ref(i, j); // call by value처럼 보이지만, 사실상 call by reference

	printf(" i : %d, j : %d\n", i, j); // 처리 후 데이터

	return 0;
}

void swap_ref(int &a, int &b) // call by reference : 주소, 진짜 reference type
                              // ref는 선언과 동시에 초기화되어야 한다.
                              // 하지만, 함수가 호출되면서 동시에 초기화되기 때문에 조건 만족(a=i, b=j)
                              // int &a = i : a는 i의 별명으로 작용한다. 실제로 쓸 때는 &떼고 쓴다
                              // &는 그저 별명을 이걸로 지어줄 것이다. 하는 용도
{
    int c = a; // c는 지역변수, a는 main함수에서 호출된 외부 변수, 변수 i의 대리인
    printf("\n    .... in swap porcessing ....\n");
    printf("     a : %d, b : %d     \n", a, b);
    printf("     .... swap porcessing ....\n");
    a = b;
    b = c;
    printf("     a : %d, b : %d     \n\n", a, b);


}

위의 결과와 동일하게 출력됨

 


 

자료형  bool

 

요즘은 C와 C++의 경계가 많이 모호해졌기에, 크게 문제가 되지는 않지만

간혹 오류를 발생시키는 경우가 있기 때문에 알아 두는 것이 좋다.

 

C에서는 참 / 거짓을 1 또는 0으로 표현했었다.

하지만 bool 자료형을 쓰게 될 경우, true / false로 표현해야 한다.

  ex) while (1) ===> while(true)

 


 

New & Delete

 

C에서 malloc과 free가 있다면, C++에서는 new와 delete가 있다.

malloc을 사용할 경우에는 크기를 바이트 단위로 계산하여 같이 선언해줬어야 했지만

new 연산자를 사용한다면, 굳이 그럴 필요가 없다.

 

※ 주의 사항

new 연산자로 할당된 메모리 공간은 반드시 delete로 소멸시켜줘야 한다.

할당된 공간을 계속 점유하고 있는 건 낭비이므로 꼭 해제해줘야 한다.

 

// 사용법 // 

#include <iostream>
using std::cout;
using std::endl;

int main()
{
    // 1 byte만큼 heap에 메모리 동적 할당 //
    char* pchar = new char;
    *pchar = 'a';
    cout << "메모리 주소 :" << (void*)pchar << "\t값 :" << *pchar << endl;
    cout << "heap 크기 :" << sizeof(*pchar) << endl;
    delete pchar;
    
    // 4 byte만큼 heap에 메모리 동적 할당 //
    int* pnum = new int;
    *pnum = 3;
    cout << "메모리 주소 :" << pnum << "\t값 :" << *pnum << endl;
    cout << "heap 크기 :" << sizeof(*pnum) << endl;
    delete pnum;
    
    return 0;
}


 

C++의 표준 헤더

 

쉽다.

.h 를 빼고 앞에 c를 붙이면 된다.

ex) #include <stdio.h> → #include <cstdio>

     #include <string.h> → #include <cstring>

 


 

클래스(Class)

 

1. 클래스란? 

C++에서의 구조체는 바로 Class이며, 구조체의 상위 호환이라고 생각하면 된다

구조체가 연관있는 데이터들의 묶음을 의미한다면, 클래스는 여기에 기능적인 부분을 더한 것

ex) 구조체에는 변수밖에 못 들어가지만, 클래스에는 함수도 삽입 가능하다.

 

클래스는 객체 지향 프로그래밍(OOP)에서 특정 객체를 생성하기 위해 변수(variable)와 메서드(method)를 정의하는 일종의 틀이며, 내부적으로 객체를 정의하기 위한 상태 값을 위미하는 멤버 변수와 클래스의 동작인 메서드(함수)로 구성된다. 또한 객체 지향 프로그래밍에서는 모든 데이터를 객체(object)로 취급하며 이 객체들의 조합으로 프로그래밍하는 방식을 의미한다.

용어 같은 말 의미
클래스 - 객체들을 소프트웨어 내에서 구현하기 위해 만든 설계도
객체 object, 인스턴스 클래스를 대상으로 생성된 변수
멤버 변수 클래스 속성 클래스 내에 생성된 멤버 변수
멤버 함수 메서드(method) 클래스 내에 정의된 멤버 함수

클래스를 붕어빵틀이라고 하면, 객체는 붕어빵이고  붕어빵 하나 하나를 클래스의 인스턴스라고 한다.

근데 시험볼 거 아니면, 굳이 상세하게 외울 필요는 없다. 어짜피 제대로 구분하는 사람 별로 없음

그냥 클래스가 객체고 오브젝트이자 인스턴스구나~ 생각하면 편하다.

 

2. 접근 제어 지시자

정보의 은닉이나 캡슐화를 위해 접근 제어 지시자를 사용한다.

지시자 설명
public 어디서든 접근 가능(외부에서도 모두 접근 가능)
private 클래스 내부에서 정의된 함수에서만 접근 허용
(중요한 정보를 감출 때 사용하며, 접근제어지시자의 default형)
protected 기본적으로 private지만, 상속 관계에 놓여 있을 때 유도 클래스에서는 접근 허용

 

3. 클래스 사용 방법

class 클래스명
{
    접근 제어 지시자 :
    멤버 변수 :
    멤버 함수
}

 

<클래스의 생성>

// 클래스의 생성 //

class Mycar
{
private :
    // 멤버 변수
    int fuel = 0;
    bool power = false;
    
public :
    // 메소드(멤버 함수)
    void go()
    {
    	this->fuel--; // this는 클래스 내의 멤버 변수를 의미
    }
    
    void oiling(int n)
    {
    	this->fuel += n;
    }
    
    void fuel_check()
    {
    	std::cout << "연료 : " << fuel << std::endl;
    }
};

 

<클래스의 사용>

// 클래스의 사용 //

int main()
{
    Mycar car; // 클래스 생성

    // 메서드 호출
    car.oiling(100);
    car.fuel_check();
    for (int i = 0; i < 10; i++) car.go();
    car.fuel_check();
    car.oiling(100);
    for (int i = 0; i < 10; i++) car.go();
    car.fuel_check();
}

 

<클래스의 배열 사용>

int main()
{
    Mycar car[3]; // 클래스 생성

        for (int i = 0; i < 3; i++)
        {
            car[i] = Mycar(); // 클래스 배열을 사용할 때는 생성 즉시 바로 초기화시켜줘야 한다.
        }

    // 메서드 호출
    car[0].oiling(100);
    car[0].fuel_check();
    for (int i = 0; i < 10; i++) car[0].go();
    car[0].fuel_check();
}

 


 

생성자와 소멸자

 

생성자와 소멸자는 클래스 객체가 생성 및 소멸될 때, 자동으로 호출되는 일종의 함수이다.

생성자와 소멸자의 경우, 따로 기술하지 않아도 컴파일러가 알아서 만들어서 넣는다.

단, 이렇게 생성된 생성자/소멸자는 아무런 기능이 없으며 프로그래머가 따로 기능을 만들어줄 수 있다.

(생성자와 소멸자는 데이터 타입도 없고, return문도 존재하지 않는다.)

또한 함수의 일종이므로 오버로딩과 디폴트값 설정이 가능하다.

 

class Mycar
{
private :
    // 멤버 변수
    int fuel = 0;
    bool power = false;
    
public :

    // 생성자
    Mycar()
    {
    	this->fuel = n;
        this->power = true;
    }
    
    // 생성자 다중 정의
    Mycar(int n)
    {
    	this->fuel = n;
        this->power = true;
    }
    // 소멸자(다중 정의 불가능)
    ~Mycar()
    {
    	std::cout << "소멸되었습니다." << std::endl;
    }
};

 

728x90
반응형