강께르의 개발일지
[수업] 20210915_3일차 본문
C#에서는 헤더와 코드가 분리되어 있지 않아 한 파일 내에 선언과 정의를 한 번에 적어놓는다.
- 클래스에 대해 -
문법 모양
(접근 제어 권한) (클래스 키워드) (이름)
{
(바디, 멤버 선언 및 정의)
}
바디에 들어갈 수 있는 것들 :
멤버 변수 / 메소드 / 생성자 / 소멸자 / 프로퍼티 / 인덱서 등
멤버 변수, 필드
선언 방법 : (접근 제어 권한) (데이터형) (식별자)
접근 제어 권한을 필드마다 하나씩 따로 부여해줘야 한다.
필드 초기화 : 선언과 동시에 할당이 가능하다. 인스턴스 생성 시, 메모리에 로드될 때 할당된다.(그 후 생성자 호출)
readonly 키워드와 const 키워드
readonly : 컴파일 타임에 할당되지 않고 런타임 중에 약속된 값으로 할당하게 하는 키워드, 필드 초기화 혹은 생성자 초기화 때 사용하여 초기 할당만 허용한다. 런타임형 상수
const : 컴파일 타임에 값이 할당되는 상수 키워드, 컴파일형 상수
기본 데이터형에 대해서만 const
사용자 정의 데이터형에 대해서만 readonly
외부에서 봤을 때, 두 키워드의 차이점은 없어보이지만 내부 동작의 차이점이 있는 것이다.
메소드, 함수
(접근 권한) (반환형) (식별자) (매개변수) (바디)
반환형이 void면 return문을 신경쓰지 않아도 되지만
원래는 반환형에 맞춰서 return문을 작성해야한다.
int Foo(int x) => x * 2; // 이렇게도 함수를 선언, 정의를 한다
// => : 람다식 연산자
int Foo(int x)
{
return x * 2;
}
메소드 내에 정의된 메소드 : 지역 메소드
메소드 내에서만 쓸 수 있는 메소드라고 한다.
메소드 내에서 자유롭게 접근하기에 접근 권한은 필요 없다.
static 멤버는 static 메소드에서만 쓸 수 있다.
인스턴스 없이 사용하는 static 메소드이기 때문에 만약 이 메소드에 일반 멤버가 있다면 어느 인스턴스의 멤버를 쓰는 것인지 알 수가 없어서 static 메소드에는 static 멤버만 사용한다.
* C#에서는 맨바닥에서도 코드를 굴릴 수 있다. 하지만 순서를 고려하기에 그런 코드들은 최상위에다가 적도록 하자.
메소드 오버로딩
이름이 같지만 매개변수가 다른 메소드들
반환형 기준으로 오버로딩할 수 없다. 만약 그 반환값을 갖고 대입이나 특정 처리를 하지 않은 식을 수행한다면 그 반환형이 무엇인지 특정할 수 없기 때문에 반환형은 기준이 될 수 없다.
가변길이 매개변수의 오버로딩의 경우
배열 매개변수 메소드와 가변길이 매개변수 메소드가 오버로딩되어 함께 있을 경우 그 기준이 모호하기에 허용하지 않는다.
생성자
인스턴스가 생성될 때 호출되는 함수
클래스가 갖고 있는 멤버를 초기화하는 역할
생성자도 오버로딩이 가능
생성자에게 전달된 매개변수에 따라 수행한다.
생성자 내에서 다른 생성자를 호출하게 되면 여러 개 생성자를 같이 쓸 수 있다.
생성자를 굳이 정의하지 않으면 컴파일러가 기본 생성자를 추가해서 동작한다. -> 그 생성자의 바디는 특정 동작을 수행하지 않는다.
필드 초기화와 생성자가 함께 있어서 필드를 초기화하는 상황이라면?
필드 초기화가 먼저 수행되어 메모리에 올라간다.
그리고 생성자를 호출한다. -> 필드 초기화를 마친 readonly 변수는 생성자에서 할당하려고 하면 에러가 난다.
생성자 접근 권한은 무조건 public
하지만 특정 상황에서는 private 생성자를 사용한다.
예) 팩토리 패턴
얕은 복사와 깊은 복사
얕은 복사 : 인스턴스를 참조하는 값만 복사
깊은 복사 : 인스턴스를 새로 만들어서 복사하고자 하는 인스턴스의 멤버 값을 싹 다 복사
clone, interface가 다 이 맥락에서 나왔다는데... 추후 설명듣기로하고...
소멸자
접근 권한을 붙이면 에러난다
~(식별자) (바디)
C#에서는 잘 안쓴다 -> 왜?
프로그래머가 소멸자 호출하는 것을 예측할 수 없고
가비지 컬렉터가 소멸에 대해 관리하기 때문에 굳이 신경쓰지 않는다.
DeConstructor
매개변수가 하나 있어야 동작하는 메소드
보통 멤버에 값을 할당하는 생성자와 반대되는 개념
멤버에 있는 값들을 끄집어내는 기능을 구현
DeConstructor가 적용이 되면
(float width, float height) = rect1; 같은 것이 가능하다.
매개변수에다가 인스턴스의 값을 대입하는 것이다.
아래 코드와 같이 사용이 가능하다.
var rect1 = new Rectangle(10f, 20f);
//(float width, float height) = rect1;
// (var width, var height) = rect;
// var (width, height) = rect;
(_, float height) = rect1;
class Rectangle
{
public readonly float Width, Height;
public Rectangle(float width, float height)
{
Width = width;
Height = height;
}
public void Deconstruct(out float width, out float height)
{
width = Width;
height = Height;
}
}
객체 이니셜라이져
public에 한해서 인스턴스를 생성하면서 멤버를 초기화할 수 있다.
priavate 멤버는 안된다.
var bunny 3 = new Bunny() { LikesCarrots = true, LikesHumans = false }
이런 식으로 생성하면 생성 시 초기화한다.
순서는 아마 생성자보다 먼저 메모리에 올라가 있을 것이다.
using System;
var bunny1 = new Bunny();
var bunny2 = new Bunny("Bunny 2");
var bunny3 = new Bunny() { LikesCarrots = true, LikesHumans = false};
var bunny4 = new Bunny("Bunny 4") { LikesCarrots = true, LikesHumans = true };
public class Bunny
{
public string Name;
public bool LikesCarrots;
public bool LikesHumans;
public Bunny() { }
public Bunny(string n) { Name = n; }
}
this에 대해서
셀프 레퍼런스라고 부른다. 자기 자신의 참조값을 반환한다.
클래스 내에서 인스턴스 메소드를 구현하여 this를 호출한다는 가정 하에 인스턴스 메소드를 호출하는 인스턴스는 항상 존재해야한다.
호출한 인스턴스의 레퍼런스를 사용하고 싶으면 this를 사용한다.
* 만약 중복한 식별자를 사용하는 매개변수나 변수가 존재한다면 해당 변수로부터 가장 가까운 스코프의 선언부의 이름을 사용한다.
C++에 없는 멤버들의 등장
프로퍼티
겟과 셋 구문을 지녀 그에 대해 값에 대한 예외처리를 넣어줄 수 있어 무결성 검사를 해줄 수 있는 기능을 제공한다.
그냥 public을 사용하면 무차별적으로 접근하고 할당하는 일로 특정 값에 대한 보장을 못해주기에 이 데이터를 은닉해줘야 할 필요가 있다.
겟 셋은 그때에 의미가 있다. 겟 셋을 통해서 값을 특정 조건에 부합하면 읽거나 쓰거나 할 수 있다.
그런 겟 셋에 대한 모음이 프로퍼티인 것이다.
특정한 맥락에서만 사용할 수 있는 키워드가 있는데 셋구문에 value 키워드가 그것이다.
value 키워드는 대입을 통해서 set구문이 호출되는데 그 set구문을 호출할 때 전달하는 값을 의미한다.
꼭 멤버 변수에 대한 겟 셋 목적으로 사용하지 않아도 된다.
프로퍼티의 겟 구문만을 가지고 어떤 연산 결과를 반환하는 목적으로 사용할 수 있다.
만약 프로퍼티만 만들고 그에 해당하는 멤버 변수를 만들지 않는다면?
오토 프로퍼티라고 해서 그 멤버 변수를 컴파일러가 알아서 만든다.
하지만 그걸 프로그래머가 접근할 방법이 없다.
오로지 프로퍼티의 겟 셋으로만 쓰는 것이다.
겟셋 중괄호 바깥에 대입하면 그에 대한 초기화 값을 지정할 수 있다.
public decimal CurrentPrice { get; set; } = 20;
겟 셋 구문의 접근 제어 권한을 달리 부여할 수 있다.
클래스 내부에서만 구문을 쓰고 싶다면 private 선언!
readonly처럼 쓰이는 경우
set 대신에 init을 추가한다. set구문이 없어서 대입을 못한다.
프로퍼티 초기화에서만 사용 가능하다.
인덱서 indexer
Sentence s = new Sentence();
Console.WriteLine(s[3]); // fox
s[3] = "kangaroo";
Console.WriteLine(s[3]); // kangaroo
class Sentence
{
string[] words = "The quick brown fox".Split();
public string this[int arg1, string arg2]
{
get { return words[arg1]; }
set { words[arg1] = value; }
}
public string this[int wordNum] // indexer
{
get { return words[wordNum]; }
//set { words[wordNum] = value; }
}
}
프로퍼티 정의하는 부분과 유사
문자열이나 배열에 대괄호가 들어가는 정수를 인덱서로 구현
얘를 왜 사용?
컴파일러는 타입 검사만 해주지 널 검사와 같은 예외 검사는 해주지 않는다.
인덱서도 그냥 구현하면 검사가 느슨한데, 그거에 대한 무결성 검사를 추가할 수 있다.
인덱서의 셋이 있을 수도 없을 수도 있다.
인덱서의 매개변수로 하나 이상을 사용할 수 있고 다른 여러 데이터형을 받을 수 있다.
인덱서도 오버로딩이 가능하다.
오토로는 선언할 수 없다. 자동으로 변수를 만들어주는 걸 하지 않아 프로퍼티와 구분하자.
나중에 인덱서를 왜 만들까?
컬렉션을 따로 만들기보단 사용자 정의 데이터형에 대하여 매니저를 만들 때 사용할 것이다.
static
인스턴스 생성자 말고 static 생성자
무조건 파라미터가 없는 생성자다.
언제 호출? : 프로그램이 시작되어서 타입 단위 호출되는 것이다.
있다고만 알아두자.
static 관계에서의 초기화 순서
using System;
Console.WriteLine(Foo.X); // 3
class Foo
{
public static Foo Instance = new Foo();
public static int X = 3;
Foo()
{
Console.WriteLine(X); // 0
}
}
필드 초기화는 순서대로 호출되기 때문에 멤버변수의 순서를 유의해야한다.
출력은 0과 3이 나온다.
static 클래스
static 멤버만을 가지는 클래스
프로퍼티와 멤버 모두 static 키워드를 가질 수 있다.
전역과 비슷하게 쓰일 수 있는 static은 C#에서는 지양해야하는 부분이다.
전역이라는 개념이 사실상 C#에서는 없기 때문이다.
patial
클래스를 두 부분으로 나눠서 쓸 수 있다.
기능 분리, 코드의 크기가 크다는 경우에 키워드를 붙여 분할하여 구현 가능
단순히 코드를 나눈다는 목적이다.
상속
객체 지향 언어의 특징
C#은 다중 상속을 지원 X 구조체 상속 X
인터페이스(추상 클래스와 유사) : 클래스한테 상속이 가능, 다중 상속 허용
C++은 구조체 상속 O
다형성
부모 클래스로 자식 클래스의 인스턴스를 참조할 수 있게 하는 것이다.
지역변수, 매개변수에서도 이와 같은 동작은 수행된다.
형 변환
참조형간의 형 변환
상속 관계에 있는 애들끼리 형변환만 가능하다.
부모 -> 자식 / 자식 -> 부모
하지만 부모에서 자식으로 형변환하는 것은 부작용이 있다.
참조형 변수는 참조하는 인스턴스가 같으면 true
그렇기에 다른 클래스형 변수여도 참조값을 가진 인스턴스가 같으면 같은 것이다.
업캐스트 : 자식 인스턴스를 부모클래스 형변환하는 것
부작용이 없다. 왜냐면 자식 인스턴스에는 이미 부모 클래스의 멤버를 갖고 있기 때문이다.
다운캐스트 : 부모 인스턴스를 자식클래스 형변환하는 것
부작용이 있다. 왜냐면 부모 인스턴스에는 자식 클래스 멤버를 갖고 있지 않아서 형변환을 하면 Exception이 발생한다.
클래스 형 변환을 한다는 것은
갖고 있는 참조값을 형 변환에 맞는 데이터형으로 바꾸는 처리를 하는 것을 리턴 받아 그 참조값을 변수에 대입하여 인스턴스를 참조하는 것이라고 이해하면 되겠다.
as 연산자 / is 연산자
as : 상속 관계 형 변환 연산자
형 변환하고 싶은 데이터형을 as 연산자의 오른쪽에 쓴다.
형변환이 안되는 것이라면 NULL을 리턴
is : 상속 관계 조건 연산자
형 변환이 가능하다면 true를 리턴
다른 의미로는 is 연산자의 오른쪽에 있는 클래스의 상속관계에 왼쪽 변수의 데이터형이 들어간다면 true
가상 메소드
상속 관계 내에서 사용하는 개념
프로퍼티 인덱서에게도 사용 가능
자식클래스에서 재정의는 override 키워드를 사용
호출하고 있는 메소드는 실제 인스턴스의 데이터형을 따라서 결정
참조하고 있는 변수는 상관 없다.
하지만 참조하고 있는 변수 기준으로 메소드를 호출하고 싶다면 virtual - override를 안쓰면 된다.
멤버 하이딩
필드 하이딩 : 상속 관계에서 멤버 이름이 같은 변수가 있다면 부모가 아닌 자식 클래스의 이름을 사용하기 위해 new 키워드를 붙여서 부모 클래스의 멤버변수를 hiding할 수 있다.
new를 붙이나 안 붙이나 똑같으나 컴파일러에게 의도된 코드라고 명시할 수 있다.
메소드 하이딩 : 하이딩을 이용한다면 참조하고 있는 변수의 클래스 데이터형에 따라서 출력하거나 수행한다.
하이딩과 가상함수가 섞인 계층 구조면 그려봐서 파악하는 것이 좋다.
가상함수는 가장 가까운 오버라이딩 함수를 수행할 것이다.
추상 클래스
C++의 특징을 그대로 갖고 있다.
클래스 인스턴스 생성 불가, 자식 클래스에서 쟂ㅇ의
abstract 키워드를 추가해 쓴다.
추상 프로퍼티는 겟 구문만 쓴다. 이는 원형만 제공하는 모습과 똑같다.
-> 오토 프로퍼티와 모양은 유사하지만 동작은 달리 한다.
가상 소멸자는 신경써서 사용해야한다.
하지만 C#에서는 고려 대상이 아니다.
Sealed
더 이상 오버라이드하지 않고 스킵한다.
만약 가상 함수를 이미 Sealed로 오버라이드를 한 클래스를 상속해서 다시 오버라이드를 한다면 에러가 난다.
더 이상의 오버라이드를 허용하지 않게 보호하는 것이다.
base 키워드
부모 클래스의 메소드를 호출하고 싶을 때, this 사용하는 것처럼 base를 사용해서 호출할 수 있다.
현재 클래스의 바로 위 상속관계의 클래스의 인스턴스 참조값을 의미한다.
이를 통해 이미 부모 클래스에서 구현한 부분을 호출해줄 수 있어 코드의 중복을 줄일 수 있다.
생성자도 상속받는다.
생성자의 순서는 상속받은 최상위 클래스의 생성자부터 자식 클래스의 생성자로 바디를 수행한다.
내가 만약 생성자를 호출하고 부모클래스의 생성자를 특정하지 않았다면 부모클래스의 기본 생성자를 선택
생성자의 멤버 이니셜라이저에 base()처럼 생성자를 선택할 수 있다.
생성자는 최상위부터 최하위로
소멸자는 최하위부터 최상위로
'프로그래밍 > C#' 카테고리의 다른 글
[수업] 20210916_4일차 (0) | 2021.09.17 |
---|---|
[문제 풀기] 클래스에 관하여 (0) | 2021.09.16 |
[수업] 20210914_2일차 (0) | 2021.09.14 |
[문제 풀기] 문자열에 대해서 (0) | 2021.09.14 |
[수업] 20210913_1일차 (0) | 2021.09.14 |