객체 지향 문법
객체지향
현실 세계의 모든 것들을 '속성'과 '행위'로 정의한다.
클래스
기본 자료형 타입으로는 현실 세계의 모든 것들을 표현하기 힘들기 때문에, 개념이 비슷한 것들을 일반화하여 표현하기 위해 직접 타입을 정의한 것이다.
클래스로 정의된 타입은 모두 참조형으로 분류되므로 사용하려면 new 연산자로 메로리 할당을 해야한다.
필드
클래스의 속성을 의미한다. 또는 객체에 속한 변수이다. 멤버 변수라고도 한다.
메서드
클래스의 행위를 의미한다. 또는 객체에 속한 함수이다. 서브루틴, 프로시저라고도 한다. 메서드를 쓰는 이유는 다음과 같다.
- 중복 코드 제거 : 중복된 코드는 메서드로 묶어서 편하게 이용할 수 있다.
- 코드 추상화 : 메서드의 내부를 몰라도 입출력 인자의 용도만 안다면 사용 가능하다.
생성자
객체가 생성되는 시점에 자동으로 호촐되는 메서드. 반환타입을 명시하지 않는 점을 제외하면 일반 메서드를 정의하는 방법을 따른다.
소멸자
객체가 제거되는 시점에 자동으로 호출되는 메서드. C#은 C++와 달리 delete라는 예약어가 없고, GC가 호출돼야 소멸자가 호출된다. 그래서 정확히 언제 소멸자가 호출되는지 알 수 없다. 소멸자의 사용은 권장되지 않고, 주로 IDisposable을 구현하여 Dispose()를 사용하는 방법이 권장된다.
다음은 생성자와 소멸자의 예시이다.
class Book
{
public Book() //생성자
{
}
~Book() //소멸자
{
// 자원 해제
}
}
정적 멤버, 인스턴스 멤버
class Dog
{
static public int CountOfInstance; //Dog 클래스의 총 인스턴수 개수를 세기 위한 정적 필드
public string _name;
public Dog(string name)
{
CountOfInstance++; //객체가 생성될 때 CountOfInstance에 1씩 더해준다.
_name = name;
}
public static void PrintBark()
{
Console.WriteLine(CountOfInstance + " 마리 개가 짖습니다.");
//정적 메서드 또한 호출 시 Dog.PrintBark() 으로 호출한다. 정적 메서드 안에서는 인스턴스 멤버에 접근 불가
}
public static void Main()
{
Console.WriteLine(Dog.CountOfInstance);
Dog blackDog = new Dog("검둥개");
Dog whiteDog = new Dog("백구");
Console.WriteLine(Dog.CountOfInstance);
//CountOfInstance가 인스턴트 필드였다면 인스턴스 단위로 변수를 가지고 있기 때문에 총 인스턴스 개수를 셀 수 없다.
}
}
특정 클래스의 인스턴스를 의도적으로 단 한 개만 만들고 싶은 경우에는
class Dog
{
public static Dog KingDog = new Dog("왕개");
public string _name;
private Dog(string name) //생성자를 private으로 해놓으면 다른 곳에서 객체 생성 불가
{
_name = name;
}
public void Bark() //이 메소드를 쓰려면 Dog.KingDog.Bark() 이런식으로 호출 가능하다.
{
//멍멍
}
}
Main 메서드의 인자로 사용되는 string 배열 사용 예제
public void Bark() //이 메소드를 쓰려면 Dog.KingDog.Bark() 이런식으로 호출 가능하다.
{
Console.WriteLine("첫번째 인자로 멍멍이 입력되었습니다. 멍멍!");
}
public static void Main(string[] args)
{
if (args[0].Equals("멍멍"))
{
Dog.KingDog.Bark();
}
}
다음과 같은 실행 결과를 확인할 수 있다.
###정적 생성자 정적 멤버를 초기화하는 기능을 한다. 클래스에 단 한개만 존재 할 수 있다. 최초로 접근하는 시점에 우선적으로 단 한 번만 실행된다.
public static Dog KingDog = new Dog("왕개"); //위 아래는 같은 코드가 된다.
public static Dog KingDog = new Dog();
static Dog()
{
KingDog = new Dog("왕개");
}
네임스페이스 , using
자바의 패키지 개념과 같다. 이름이 같지만 소속도 다르고 실제 쓰임도 다른 클래스들의 이름 충돌을 막기 위해서 사용한다. 그리고 클래스들의 소속을 구분하는데 사용되는 것이 더 일반적이다.
namespace NamespaceEx1
{
class Dog
{
public Dog()
{
Console.WriteLine("네임스페이스1");
}
}
}
namespace NamespaceEx2
{
class Dog
{
public Dog()
{
Console.WriteLine("네임스페이스2");
}
}
}
//dog3를 위해선 여기서 using으로 어떤 네임스페이스를 쓸 건지 선언해줘야 함.
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
NamespaceEx1.Dog dog1 = new NamespaceEx1.Dog(); //"네임스페이스1" 출력
NamespaceEx2.Dog dog2 = new NamespaceEx2.Dog(); //"네임스페이스2" 출력
Dog dog3 = new Dog(); //에러 코드 상단에 using으로 어떤 네임스페이스를 선언하냐에 따라서 다른 결과 출력
}
}
}
캡슐화
접근 제한 유형
이름 | 의미 |
---|---|
internal | 동일한 어셈블리 내에서는 public에 준한 접근을 허용한다. 다른 어셈블리에서는 접근할 수 없다. |
internal protected | protected와 internal의 조합이다. 동일 어셈블리 내에서 정의된 파생 클래스에만 접근을 허용한다. |
접근 제한자를 명시하지 않은 경우에는 클래스는 internal, class 내부의 멤버들은 private으로 설정된다.
class Dog //접근 제한자 생략한 상태는 internal로 되기 때문에 객체 생성이 가능하나, private으로 접근 제한자를 바꾼다면 에러 발생.
{
void bark()
{
Console.WriteLine("멍멍!");
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
dog.bark(); //bark() 메서드의 접근 제한자가 생략되서 private으로 설정되었기 때문에 호출 불가.
}
}
프로퍼티
접근자/설정자를 편하게 정의하기 위해서 C#에서 제공하는 문법. (attribute(field)와 똑같이 속성으로 번역되기 때문에 주의)
class Dog
{
private string name;
public string Name //Visual Studio에서 name을 눌러 자동생성 가능.
{
get
{
Console.WriteLine("get호출");
return name;
}
set
{
Console.WriteLine("set호출");
name = value;
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
dog.Name = "검둥개"; //"set호출" 출력
string name = dog.Name; //"get호출" 출력
}
}
상속
class Animal
{
private string name;
private void Roam() //private으로 선언되어 있어서 다른 클래스에서 호출 불가.
{
Console.WriteLine("돌아다니기");
}
public void Eat() //public으로 선언되어 있어서 다른 클래스에서 호출 가능
{
Console.WriteLine("쩝쩝");
}
}
class Dog : Animal
{
protected string specific= "개"; //protected로 선언되어 있어서 Dog를 상속받는 BlackDog 클래스에서만 접근 가능.
public void Bark()
{
Console.WriteLine("멍멍!");
}
}
class BlackDog : Dog
{
public void PrintSpecific()
{
Console.WriteLine("나는 " + specific + "과에 속합니다."); //Dog클래스의 specific에 접근하여 읽어옴.
}
}
sealed class Cat : Animal
{
public void Meow()
{
Console.WriteLine("야옹!");
}
}
//class BlackCat : Cat //Cat클래스가 sealed 되어있기 때문에 상속 불가
//{
//}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
dog.Eat(); //Animal 클래스에서 상속받은 메서드. "쩝쩝" 출력.
dog.Bark();
//dog.Roam(); //private로 선언되어 있어서 호출 불가.
BlackDog blackDog = new BlackDog();
blackDog.PrintSpecific(); //protected로 선언된 specific 변수에 접근해서 "개"를 읽어옴. "나는 개과입니다." 출력.
Cat cat = new Cat();
cat.Eat(); //Animal 클래스에서 상속받은 메서드. "쩝쩝" 출력.
cat.Meow();
}
}
형변환
- as 연산자 : 형변환이 가능하면 지정된 타입의 인스탄스 값 반환, 그렇지 않으면 null 반환.
- is 연산자 : 형변환이 가능하면 true, 그렇지 않으면 false 반환.
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
Dog dog = animal as Dog;
Cat cat = new Cat();
if (dog == null) //animal은 Dog으로 변환 불가해서 null을 반환하므로 실행된다.
{
Console.WriteLine("Animal은 Dog로 변환 불가능!");
}
if (cat is Animal)
{
Console.WriteLine("cat는 Animal이다."); //변환 가능하므로 실행됨
}
if (animal is Cat)
{
Console.WriteLine("animal은 Dog다."); //변환이 안되므로 실행 안됨
}
}
}
System.Object
자바의 Object 클래스처럼 C#에도 모든 타입의 조상격인 object 타입이 있다. object는 참조형인데 값 형식의 부모 타입이기도 하다. 이런 불일치를 해소하기 위해서 모든 값 형식은 object 밑에 존재하는 System.ValueType에서 상속받게 하고있다. 이를 도식화하면 다음과 같다.
따라서 참조형식 = object로부터 상속받는 모든 타입 - System.ValueType로부터 상속받는 모든 값 타입 이다.
C#에서 정의되는 모든 형식은 object로 변환하고 다시 되돌리는 것이 가능하다.
ToString
static void Main(string[] args)
{
Animal animal = new Animal();
Console.WriteLine(animal.ToString()); //클래스의 전체 이름인 ConsoleApplication1.Animal 출력.
Console.WriteLine(animal.numberOfAnimal.ToString()); //기본 타입에선 갖고 있는 값 출력.
}
GetType
static void Main(string[] args)
{
Animal animal = new Animal();
int num = 5;
Type type = animal.GetType(); //Animal 클래스의 정보를 가지고 있는 System.Type의 인스턴스 호출.
Type intType = num.GetType(); //기본 자료형도 호출 가능.
Console.WriteLine(type.FullName); //ConsoleApplication1.Animal 출력.
Console.WriteLine(type.IsClass); //true 출력.
Console.WriteLine(intType.FullName); //System.Int32 출력.
Console.WriteLine(typeof(Animal).FullName);
//typeof 예약어는 클래스의 이름에서 Type 반환. ConsoleApplication1.Animal 출력.
}
####Equals
자바와 동일. 주의할 점은 값 형식은 해당 인스턴스가 소유하고 있는 값을 대상으로 비교한다. 참조 형식은 할당된 메모리 위치를 가리키는 값이 같은지 비교한다.
static void Main(string[] args)
{
int num1 = 5;
int num2 = 5;
int num3 = 6;
Console.WriteLine(num1.Equals(num2)); //가리키는 값이 같으므로 True.
Console.WriteLine(num3.Equals(num2)); //가리키는 값이 다르므로 False.
Animal animal1 = new Animal();
Animal animal2 = new Animal();
Animal animal3 = animal1;
Console.WriteLine(animal1.Equals(animal2)); //서로 가리키는 힙 메모리의 위치가 다르기 때문에 False.
Console.WriteLine(animal1.Equals(animal3)); //서로 같은 위치를 가리키고 있기 떄문에 True.
}
GetHashCode
특정 인스턴스를 고유하게 식별할 수 있는 4바이트 int 값을 반환한다. Equals의 참/거짓 판단은 이 GetHashCode값을 기준으로 이루어진다.
static void Main(string[] args)
{
short num1 = 256;
int num2 = 256;
short num3 = 256;
Console.WriteLine(num1.GetHashCode()); //num1과 num3는 같은 값을 가리키므로 HashCode값도 일치한다.
Console.WriteLine(num3.GetHashCode());
Console.WriteLine(num2.GetHashCode()); //int 형은 HashCode와 범위값이 일치하므로 그대로 반환하게 설정됨.
Animal animal1 = new Animal();
Animal animal2 = new Animal();
Console.WriteLine(animal1.GetHashCode()); //힙 메모리 내의 다른 주소를 참조하므로 서로 다른 값을 반환한다.
Console.WriteLine(animal2.GetHashCode());
}
System.Array
모든 배열은 Array 타입을 조상으로 둔다. 유용한 일부 프로퍼티와 메서드가 있다.
static void Main(string[] args)
{
int[] intArray = new int[] { 2, 3, 1, 6, 4, 5, 2 };
int i = 0;
Console.WriteLine(intArray.Rank); //배열의 차수 1
Console.WriteLine(intArray.Length); //배열의 길이
foreach (int num in intArray)
{
Console.Write(" "+intArray.GetValue(i++)); //정렬 전 배열 출력
}
Array.Sort(intArray); //배열 정렬
i = 0;
foreach (int num in intArray)
{
Console.Write(" " + intArray.GetValue(i++)); //정렬 후 배열 출력
}
int[] copyArray = new int[intArray.Length];
Array.Copy(intArray, copyArray, 5); //intArray에서 copyArray로 5개만 복사
i = 0;
foreach (int num in copyArray)
{
Console.Write(" " + copyArray.GetValue(i++)); //복사 후 배열 출력
}
}
}
this
class Book
{
string title;
decimal isbn;
string author;
public Book(string title) : this(title, 0) { } //this 예약어를 이용해 생성자 내에서 다른 생성자 호출
public Book(string title, decimal isbn) : this(title, isbn, string.Empty) { }
public Book() : this(string.Empty, 0, string.Empty) { }
public Book(string title, decimal isbn, string author)
{
this.title = title;
this.isbn = isbn;
this.author = author;
}
}
정적 멤버에선 this 예약어를 사용할 수 없다. this는 new로 할당된 '객체'를 가리키는 내부 식별자이기 때문이다.
base
자바의 super 예약어와 같다. this와 용법은 같지만 base는 가리키는 대상이 부모 클래스이다.
다형성
class Animal
{
virtual public void Sound() //virtual : 자식 클래스에 의해서 재정의 될 수 있다.
{
Console.WriteLine("소리를 낸다.");
}
}
class Dog : Animal
{
override public void Sound() // override : 부모로부터 상속받은 메서드와는 다르게 구현한다.
{
Console.WriteLine("멍멍!");
}
}
class Cat : Animal
{
override public void Sound()
{
Console.WriteLine("야옹!");
}
}
class Human : Animal
{
override public void Sound()
{
Console.WriteLine("안녕하세요!");
}
}
class Whale : Animal
{
new public void Sound()
{
Console.WriteLine("끼룩끼룩!"); //단순히 자식 클래스에서 동일한 이름의 메서드가 필요했을 때 new를 쓴다.
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
Cat cat = new Cat();
Human human = new Human();
Whale whale = new Whale();
Animal animal = human;
dog.Sound(); //자식 클래스의 인스턴스에 따라 다양하게 재정의 된다.
cat.Sound();
animal.Sound(); //오버라이딩을 했기 때문에 자식 클래스의 메서드 호출.
animal = whale;
animal.Sound(); //new 했기 때문에 부모 클래스의 원 메서드 호출.
}
}