클래스
구조체와 비슷하며, 멤버 변수와 멤버 함수로 구성된 사용자 정의 데이터 타입이다.
사용자가 새로 정의한 데이터 타입이기 때문에 클래스를 추상적인 데이터 타입이라고 한다.
클래스는 아래와 같은 형태를 가진다.
class 클래스 이름
{
접근 지정자 클래스 이름() { ... } // 생성자
접근 지정자 ~클래스 이름() { ... } // 소멸자
접근 지정자 데이터형 멤버 변수(필드); // 변수 선언
접근 지정자 데이터형 메서드(){ ... }; // 메서드 선언
}
접근 지정자
접근 지정자 | 설명 |
public | 누구나 접근 가능 |
protected | 상속 관계에서 상속 받은 자식 클래스에서 접근 가능, 그 외에는 접근 불가 |
private | 그 클래스 내부에서만 접근 가능, 외부에서는 접근 불가 |
예시
Dog라는 클래스를 선언하고, 멤버 변수로는 name, 멤버 함수로는 printName()을 가진다.
name을 private로 설정하였기 때문에 외부에서 접근하면 에러가 발생한다. 따라서 외부에서 name이라는 변수에 접근할 수 있는 인터페이스를 만들어줘야 하고, 그것이 printName()이라는 멤버 변수이다.
이제 Dog라는 데이터 타입을 통해 변수를 선언할 수 있으며, 클래스를 통해 선언한 변수를 객체라고 한다.
객체는 new라는 연산자를 사용하여 생성할 수 있다. Dog a = new Dog(); 가 객체를 생성한 코드이다.
class Dog {
private string name = "NoName";
public void printName() {Console.WriteLine(name);}
}
class HelloWorld {
static void Main() {
Dog a = new Dog();
a.printName();
}
}
생성자
모든 변수는 선언되면 값을 초기화해야 한다. 객체 또한 본질적으로 변수이기 때문에 선언되면 초기화해야 한다.
객체를 생성할 때 초기화를 시켜주는 전용 메서드를 생성자(constructor)라고 한다.
생성자는 객체 생성 시 자동으로 호출되는 메서드이며, 클래스 이름과 일치해야 한다.
아래 코드는 생성자에서 멤버 변수 name 초기화하는 예시다.
class Dog {
public Dog() { name = "NoName"; } // 생성자
private string name;
public void printName() {Console.WriteLine(name);}
}
상속성
상속해주는 쪽을 부모 클래스, 상속받는 쪽을 자식 클래스라고 한다. 이미 완성된 클래스를 다른 클래스에 상속할 수 있다. 아래와 같이 콜론(:)을 사용하여 상속받을 수 있다.
class 클래스 이름 : 부모 클래스
{
// 멤버 목록
}
아래의 코드에서 부모 클래스는 Dog, 자식 클래스는 Pudle이다. 부모 클래스에게 존재하는 멤버 변수와 멤버 함수를 동일하게 별도로 가진다. 그래서 Pudle 객체도 멤버 변수 name과 멤버 함수 printName()을 가지고 있다.
class Dog {
public Dog() { name = "NoName"; } // 생성자
protected string name;
public void printName() {Console.WriteLine(name);}
}
class Pudle : Dog {
public Pudle() { base.name = "Pudle"; }
}
다형성
전달인자의 타입이나 개수가 다르다면 동일한 이름의 함수를 여러 개 만들 수 있다.
객체 지향에서는 대표적으로 오버로딩과 오버라이딩 기법이 있다.
오버로딩
오버 로딩은 함수의 이름이 같고, 매개 변수의 개수나 타입이 달라야 한다.
아래의 코드는 덧셈 함수를 오버로딩해서 구현한 코드이다.
#include <stdio.h>
int Plus(int a, int b){
return a + b;
}
double Plus(double a, double b) {
return a + b;
}
int Plus(int a, int b, int c) {
return a + b + c;
}
int main()
{
printf("Plus(%d, %d) = %d\n", 10, 20, Plus(10, 20));
printf("Plus(%lf, %lf) = %lf\n", 10.1, 20.1, Plus(10.1, 20.1));
printf("Plus(%d, %d, %d) = %d\n", 10, 20, 30, Plus(10, 20, 30));
return 0;
}
오버라이딩
오버라이딩은 부모 클래스로부터 상속받은 메서드를 자식 클래스에서 재정의하는 것을 말한다.
자식 클래스에서 상속받은 메서드를 변경해야 하는 경우 오버라이딩을 한다.
아래 코드에서 Pudle이 Dog를 상속받았다. 부모 클래스에 있는 메서드 printName()을 자식 클래스인 Pudle에서 재정의하여 사용하고 있다. r객체 Pudle의 printName() 메서드 실행 결과를 보면 오버라이딩 된 것을 확인할 수 있다.
class Dog {
public Dog() { name = "NoName"; } // 생성자
protected string name;
public void printName() { Console.WriteLine(name); }
}
class Pudle : Dog {
public Pudle() { base.name = "Pudle"; }
public void printName() { Console.WriteLine("오버라이딩: {0}", name); }
}
class HelloWorld {
static void Main() {
Dog a = new Dog();
Pudle b = new Pudle();
b.printName();
// a.printName();
}
}
인터페이스
메서드의 목록만 가지고 있는 명세로, 사용자 정의 타입이다.
메서드의 목록만 선언하고 구현은 하지 않는다. 공동 작업 시에 표준을 정하는 역할을 하며, 기존의 기능을 추가하거나 수정하는 역할보다는 동일한 개념의 기능을 새롭게 구현하는 기능을 가진다.
// 인터페이스 선언
접근지정자 interface 이름: 기반 인터페이스
{
}
// 인터페이스를 상속받는 클래스의 형태
접근지정자 class 자식클래스이름: 인터페이스
{
}
인터페이스의 이름 앞에는 대문자 I를 붙이는 코드 컨벤션이 있다. 아래 코드에서는 휴대폰이라는 인터페이스의 메서드들을 삼성과 애플에서 새롭게 구현하고 있다.
public interface Iphone {
void call();
void message();
}
public class Samsung : Iphone {
public void call() { Console.WriteLine("삼성 : 전화"); }
public void message() {Console.WriteLine("삼성 : 문자"); }
}
public class Apple : Iphone {
public void call() { Console.WriteLine("애플 : 전화"); }
public void message() {Console.WriteLine("애플 : 문자"); }
}
class HelloWorld {
static void Main() {
Samsung samsung = new Samsung();
samsung.call();
samsung.message();
Apple apple = new Apple();
apple.call();
apple.message();
}
}
메모리 관리
플랫폼 기반의 객체 지향 언어에서는 가비지 컬렉터가 있다. 가비지 컬렉터는 백그라운드에서 더 이상 사용되지 않는 메모리는 찾아 회수하며 메모리를 자동으로 관리한다.
가비지 컬렉터가 사용되지 않는 메모리를 회수하다 보면 2번째 그림과 같이 작은 빈 공간이 생긴다. 이 공간에 다른 데이터를 저장하기에는 공간이 부족할 수 있다. 이렇게 사용하지 못하는 작은 공간들이 발생하는 것을 단편화라고 하며, 가비지 컬렉터는 단편화 현상이 발생하면 마지막 그림과 같이 메모리를 이동시켜서 큰 덩어리를 만들어준다.
람다
익명 메서드
메서드를 미리 정의하지 않고 사용할 때 정의하는 메서드를 말한다.
익명 메서드는 내용 자체가 복잡하면 안 되며, 람다식에서 사용된다.
익명 메서드를 사용하면 코드가 간결해지고, 별도의 메서드를 만들지 않으므로 코딩 오버헤드를 줄일 수 있다는 장점이 있다.
람다식
람다식은 기존 익명 메서드를 더욱 간결하게 만든 것으로, 코드를 짧고 간결하게 표현한다.
아래 코드는 C#으로, delegate는 메서드를 참조하는 역할을 한다. 즉, delegate가 함수 포인터의 역할을 하고 있다.
delegate와 인수의 개수, 타입은 일치해야 한다.
class HelloWorld {
delegate int CalcDele(int x);
static void Main() {
CalcDele d = delegate (int x) { return x + 1;}; // 익명 메서드를 delegate에 넣는다.
Console.WriteLine(d(3));
}
}
아래와 같이 delegate 키워드 대신 람다식을 의미하는 =>를 사용하여 더 간단하게 표현할 수 있다. 주석으로 표시되어 있는 코드들은 모두 동일한 결과를 가지는 코드이다.
class HelloWorld {
delegate int CalcDele(int x);
static void Main() {
CalcDele d = x => x + 1;
// CalcDele d = (x) => { return x + 1; };
// CalcDele d = delegate (int x) { return x + 1;};
Console.WriteLine(d(3));
}
}
배운 점
- 클래스 작성 방법과 접근 지정자를 이용해 멤버들에 대해 접근 범위를 제어할 수 있음을 배웠다.
- 매개 변수의 개수나 타입이 다르면 동일한 이름을 가진 함수를 여러 개 선언할 수 있다.(오버로딩)
- 부모 클래스를 상속받았을 경우, 자식 클래스가 부모 클래스의 멤버들을 가지며, 메서드를 덮어쓸 수 있다. (오버라이딩)
- 가비지 컬렉터는 사용하지 않는 메모리를 회수하는 역할도 하지만, 단편화가 발생하면 데이터를 모아주는 컴팩션 기능도 수행한다.
- 람다식을 통해 익명 메서드의 동작 방식을 알게 되었다.
'데브코스' 카테고리의 다른 글
[11주차 주간 발표] const assertion과 RORO 패턴 (0) | 2024.05.09 |
---|---|
[11주차 - DAY3] 리터럴, 클래스 (0) | 2024.05.08 |
[10주차 - DAY4] 사용자 정의 자료형, 동적 할당, 객체 지향 (0) | 2024.05.02 |
[10주차 - DAY2] 포인터 (1) | 2024.04.30 |
[10주차 - DAY1] C언어 연산자, 분기문, 반복문 (0) | 2024.04.29 |