Front-end Developer

0%

함수 오버로딩

  • 동일한 이름을 가진 함수 구현을 허용
  • 단, 매개변수 목록이 달라야 한다.
  • 하나의 시그니처를 가진 함수는 하나만 있어야 하고, 시그니처는 함수의 이름과 매개변수를 포함한다. 즉 매개변수와 함수의 이름이 전부 같지 않으면 다른 함수로 여긴다. 반환형은 시그니처의 일부가 아니므로 다르더라도 오버로딩이 허용이 안된다. e.g. void A(), int A()는 허용되지 않는다.
  • 매개변수가 다르다 => 오버로딩OK
  • 승격/묵시적 변환을 해도 상관없다 => 오버로딩OK
  • 매개변수가 아예 승격이 불가능 한 경우 => 오버로딩OK
    • static string[] GetStudents(string name) //함수 바디 생략
    • static string[] GestStudents(int age) //함수 바디 생략
1
2
3
4
5
static void Print(int score);               // (1)
static void Print(string name); // (2)
static void Print(float gpa, string name); // (3)
static int Print(int score); // (4)
static int Print(float gpa); // (5)

(4)에서 컴파일 오류가 발생하고 나머지는 모두 문제 없다. (4)는 반환형이 오버로딩을 허용하지 않기 때문에 컴파일 오류가 발생한다.

함수 오버로딩의 장점

1
2
static float AverageFromInts(int[] scores); //함수 바디 생략
static float AverageFromFloats(float[] floats) //함수 바디 생략

위 두 함수의 경우 FromInts, Fromfloat가 없어도 매개변수 형에서 어떤 함수인지 유추 가능하다.

함수 오버로딩의 문제점

잘못된 함수 호출이 일어날 수 있다.

1
2
3
4
5
6
7
static string[] GetStudents(float height); // 함수 바디 생략
static string[] GetStudents(int age); //함수 바디 생략

// 메인 함수
int height = 175;
GetStudents(height); //GetStudents(int age)가 호출됨.
GetStudents(175); //GetStudents(int age)가 호출됨.

동일한 매개변수 함수가 없다면 승격/묵시적 형변환을 통해 일치하는 함수를 찾는다. 만약 원래 있던 코드 static string[] GetStudents(int age); //함수 바디 생략가 더이상 필요치 않게 되어 삭제했다고 가정한다. 그런데 알고보니 GetStudents(age);와 같이 나이를 호출하던 코드가 남아있었던 것이다. 이때 int age는 float height로 묵시적 변환이 된다. 따라서 예상치 못하게 age가 아닌 heigth가 17인 학생을 찾게 된다.

1
2
3
4
5
6
static string[] GetStudents(float height); //함수 바디 생략

//메인 함수
int age = 17;
GetStudents(age); //GetStudents(float height)가 호출됨.
GetStudents(17); //GetStudents(float height)가 호출됨.

잘못된 함수 호출을 방지하기 위해서 위와 같은 경우에서는 오버로딩을 쓰지 않는 것이 더 좋다.

1
2
3
4
5
6
7
8
9
static string[] GetStudentsByHeight(float height); //함수 바디 생략
static string[] GetStudentsByAge(int age); //함수 바디 생략

//메인 함수
int height = 180;
GetStudentsByHeight(height); //int가 float로 컴파일 됨
GetStudentsByHeight(170.3f); //float니까 문제없이 컴파일 됨
GetStudentsByAge(17); //int니까 컴파일 됨
GetStudentsByAge(170.3f); //float에서 int는 불가능하므로 컴파일 에러 발생

기본값 인자

  • 중복되는 매개변수가 많거나 약간의 차이만 가지고 있는 함수는 기본값 인자를 사용할 수 있다.
  • 매개변수를 선언할 때 미리 기본값을 정해둔다.
  • 매개변수는 히나 이상 가능하다.

    • 매개변수의 목록 중간에 기본값 인자가 아닌 것이 오면 안된다.
    • 매개변수의 기본값 인자는 가장 마지막에 있어야 한다.
    1
    2
    3
    4
    5
    //기본값 인자 difficulty = 0이 마지막이 아니라 중간에 있으므로 컴파일 에러 발생
    static string GetHP(int level, int mapID, int difficulty = 0, string name);

    //OK
    static string GetHP(int level, int mapID, string name, int difficulty = 0);
1
2
3
4
5
6
7
8
9
10
11
12
13
//함수 호출 시 선택적 호출
//GetFullAddress에 state가 있으면 그 매개변수를 쓰고, 없으면 ""를 쓴다.
//GetHP에 difficulty가 있으면 그 매개변수를 쓰고, 없으면 0을 쓴다.
static string GetFullAddress(string street, string city, string state = "");
static float GetHP(int level, int mapID, int difficulty = 0);

//메인함수
GetFullAddress("123 main street", "big city", "big state"); //ok
GetFullAddress("456 main street", "seoul"); //ok

GetHP(1, 1234, 10); // OK
GetHP(1, 1234); // OK
GetHP(1, 1234, 0); // OK

기본갑 인자의 문제점

나중에 누군가 기본값 인자를 중간에 추가할 때 이상한 일이 일어날 수 있다.

1
2
3
4
//원래코드
static float GetHP(int level, int mapID, int difficulty = 0);
//추가된 코드
static float GetHP(int level, int mapID, int decimalPoint = 1, int difficulty = 0);

int difficulty = 0이 한 자리 뒤로 밀리고, 그 자리에 int decimalPoint = 1이 들어왔다. 이렇게 되면 difficulty의 숫자를 바꾸어도 수치가 눈에 띄게 안바뀐다. 즉 예상치 못한 값을 얻게 된다. GetHP(10, 2456, 2);를 호출했을 때 level: 10, mapID: 2456, difficulty:2인 결과를 의도했는데, level: 10, mapID: 2456, difficulty:0, HP는 소수점 둘째자리에서 반올림 된다.

기본값 인자가 도중에 변경될 경우, 기존에 사용중인 코드에서 문제가 발생할 수 있다.

1
2
3
4
//원래코드
static float GetHP(int level, int mapID, int difficulty = 0);
//추가된 코드
static float GetHP(int level, int mapID, int difficulty = 1);

GetHP(10, 2456);이나 GetHP(10, 2456, 1); 모두 동일한 HP를 반환한다.

기본인자값의 코딩표준

  • 새 기본 매개변수는 언제나 가장 뒤에 위치해야 한다.(중간에 삽입하지 않는다.)
  • 기본값은 언제나 0으로 한다. 0이 아닌 경우 기본 매개변수로 사용하지 않는다.
  • 함수 오버로딩 대신 실제 함수 이름을 제대로 써주는 것이 좋을 때도 있음을 기억한다.(매개변수를 직접 넣어준다.)

out 매개변수

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
static bool Trydivide(float numerator, float denominator, ref float result)
{
if (denominator == 0.0f)
{
return false;
}

//result에 어떤 값을 대입하는데, 오타나 다른 어떤 이유로 값의 대입이 제대로 되지 않았다면
//result의 값이 바뀐 값으로 반영되지 않고, 원래 값 그대로 남아있게 된다.
result = numerator / denominator;

return true;
}

static void Main(string[] args)
{
//result1이나 result2는 실질적으로 쓰이지 않는 값이지만 ref result1, ref result2의 값을 출력하기 위해 문법상 필요하다.
//ref 매개변수로 쓸 수 있는 변수는 반드시 초기화를 해야하기 때문이다.
float result1 = 0.0f;
bool bSuccess1 = TryDivide(10.0f, 0.0f, ref result1);
float result2 = 0.0f;
bool bSuccess2 = TryDivide(10.0f, 5.0f, ref result2);
}

ref를 썼을 때 아쉬운 점을 보완하기 위해 out 키워드를 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static bool Trydivide(float numerator, float denominator, out float result)
{
if (denominator == 0.0f)
{
result = 0.0f;
return false;
}

result = numerator / denominator;

return true;
}

static void Main(string[] args)
{
//result1, result2는 어차피 안에서 쓰이지 않으니 초기화 할 필요가 없다.
//명시적으로 result1, result2를 출력할 값이라고 알려준다.
float result1;
bool bSuccess1 = TryDivide(10.0f, 0, out result1);
float result2;
bool bSuccess2 = TryDivide(10.0f, 5.0f, out result2);
}

out 키워드를 쓸 때 함수 안에서 대입을 하지 않으면 컴파일 오류가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
//대입하지 않았으므로 컴파일 에러
static bool TryAdd(float num1, float num2, out float result)
{
return false;
}

//OK
static bool TryAdd(float num1, float num2, out float result)
{
result = 0.0f;
return false;
}

if/else if 문에서 대입하지 않은 곳이 있어도 오류가 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//컴파일 오류
static bool TryDivide(float numerator, float denominator, out float result)
{
if(denominator == 0.0f)
{
return false;
}
//중략
}

//OK
static bool TryDivide(float numerator, float denominator, out float result)
{
if(denominator == 0.0f)
{
result = 0.0f;
return false;
}
//중략
}

키보드 입력 예외처리

아래 코드에서 숫자 이외의 값을 입력하는 예외 상황이 생기면 에러가 발생한다.

1
int num = int.Parse(Console.ReadLine());

이는 TryParse()로 해결할 수 있다.

1
2
3
int num;
bool bSuccess = int.TryParse(Console.ReadLine(), out num);
//bSuccess의 값에 따라 코드를 작성

References
실무 프로그래밍 입문(C#)

문자열 분할

게임 몬스터 데이터를 읽어와야한다고 가정한다. 각 몬스터는 어떤 특정한 정형화된 형식을 만들어야 프로그램으로 읽을 수 있다.

1. 키와 값
키가 어떤 용도의 데이터인지 알려준다. 파일 안에서 순서가 바뀌어도 상관없다.

1
2
3
4
//name, HP, MP의 키값으로 데이터를 찾기 때문에 순서가 바뀌어도 상관없다.
name: "Pokemon"
HP:1
MP: 1000

위의 경우 보통 한 몬스터의 데이터만 한 파일 안에 저장한다. 만약 여러 몬스터 데이터를 저장해야 한다면 방법은 아래 두 가지가 있다.

  • 몬스터 하나당 파일 하나: 파일이 너무 많아진다는 단점이 있고, 이는 성능저하의 원인이 된다.
  • 한 파일에 배열과 같은 형태로 집어 넣는다: XML 또는 아래 코드와 같은 JSON 형태
    1
    2
    3
    4
    5
    6
    {
    "monstars": [
    {"name": "Pokemon1", "HP":1, "MP": 1000}
    {"name": "Pokemon2", "HP":2, "MP": 2000}
    ]
    }

2. 표
정형화된 다수의 데이터를 한 곳에 저장하기 용이하다. 아래 표처럼 엑셀과 같은 형태. 열 색인으로 어떤 용도의 데이터인지 결정한다. 순서를 바꿀 수는 없다.

name HP MP
pokemon1 1 1000
pokemon2 2 2000

표의 형태를 사용하는 경우 흔히 엑셀파일을 이용한다. 엑셀파일은 텍스트 파일이 아니기 때문에 CSV(comma-separated values)파일로 저장 가능하다. CSV는 텍스트 파일이며 각 값은 아래와 같이 쉼표로 분리된다.

1
2
3
name, HP, MP
pokemon1, 1, 1000
pokemon2, 2, 2000

몬스터 CSV데이터를 읽는 의사 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
string[] lines =
{
"pokemon1,1,1000",
"pokemon2,2,2000"
};

for (int i = 0; i <lines; ++i)
{
string line = lines[i];

//for 문을 써서 문자열을 읽어온 뒤 string[] tokens에 저장

Console.WriteLine($"몬스터 경고: tokens[0] (HP: {tokens[1]}, MP: {tokens[2]})");
}

토큰을 읽어오는 법

토큰: 연속된 데이터에서 쪼갤 수 있는 가장 작은 단위

  • 별도의 for문이 필요
  • string의 IndexOf(), Substring() 등의 함수 또는 첨자 연산자([])를 이용해서 구현 가능

IndexOf()

<문자열 변수 이름>.IndexOf(char);

1
2
string message = "C# is very very fun!";
int index = message.IndexOf('v'); //6 반환

char의 위치를 찾아서 색인을 반환하는 함수

  • 문자가 문자열에 여러 번 나타나면 가장 처음에 나타난 곳의 색인을 반환한다.
  • 찾는 문자가 문자열에 없다면 -1을 반환한다.
  • LastIndexOf()는 찾고자 하는 문자열이 가장 마지막에서부터 처음 나타난 곳의 색인을 반환하다.

Substring()

<문자열 변수 이름>.Substring(<색인>);

1
2
string nameMessage = "name: pokeMon";
string name = namemessage.Substring(6); //pokeMon 반환

지정된 문자 위치(<색인>)에서부터의 문자열을 반환하는 함수

첨자 연산자([])

<문자열 변수 이름>[<색인>];

for문을 돌린다고 가정하면 각 문자열이 어느 위치에 들어있는 지 알아야 하고, 이럴 때 첨자 연산자를 사용한다.

1
2
string HPMessage = "HP: 100";
char ch = HPMessage[4]; // '1'

<색인> 위치에 있는 문자 하나를 반환한다.


문자열 토크나이저

Split()

<문자열 변수 이름>.Split(char);

1
2
string text = "pokemon,1,10000";
string[] tokens = text.Split(','); // {"pokemon", "1", "10000"}
  • char는 문자열을 쪼갤 때 사용할 구분 문자
  • 원본 문자열은 변경 없이 그대로 유지
  • 쪼갠 문자열을 문자열 배열로 반환
  • 여러 버전의 Split() 함수가 존재

여러 개의 구분 문자가 문자열에 있을 때

<문자열 변수 이름>.Split(char[]);

문자형 배열(char[])에 여러 개의 구분 문자를 대입한다.

1
2
3
4
string text = "pokemon,1:10000";

char[] delimiters = {',', ':'};
string[] tokens = text.Split(delimiters); // {"pokemon", "1", "10000"}

구분 문자 사이가 비어 있을 때

1
2
3
4
string text = "pokemon, 1, 10000:, 10";

char[] delimiters = {',', ':'};
string[] tokens = text.Split(delimiters); // {"pokemon", "1", "10000", "", "10"}

‘,’ 또는 ‘:’를 기준으로 문자열을 쪼개고 있기 때문에 ‘10000:,’과 같은 케이스에서 ""가 나온다. 이럴 때 String.IsNullOrEmpty(String)나 for문을 사용하여 빈 문자열을 걸러줄 수 있다. 또는 StringSplitOptions.RemoveEmptyEntries를 사용하여 걸러줄 수 있다.

1
2
3
4
string text = "pokemon, 1, 10000:, 10";

char[] delimiters = {',', ':'};
string[] tokens = text.Split(delimiters, StringSplitOptions.RemoveEmptyEntries); // {"pokemon", "1", "10000", "10"}

Trim()

<문자열 변수 이름>.Trim();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
string firstName = "              Leon";
string lastName = "kim ";

//문자열 앞뒤 공백 제거
string trimmedFirstName = firstName.Trim(); //"Leon"
string trimmedLastName = lastName.Trim(); //"kim"

//문자열 앞 공백 제거
string trimmedFirstName = firstName.TrimStart(); //"Leon"
string trimmedLastName = lastName.TrimStart(); //"kim

//문자열 뒤 공백 제거
string trimmedFirstName = firstName.TrimEnd(); //" Leon"
string trimmedLastName = lastName.TrimEnd(); //"kim"

Trim()

  • 문자열 앞뒤로 있는 공백을 없앤 후 문자열을 반환
  • 원본 문자열은 변경 없이 그대로 유지

TrimStart()

  • 문자열 시작에서 공백을 제거 후 문자열을 반환
  • 원본 문자열은 변경 없이 그대로 유지

TrimEnd()

  • 문자열 뒤에서 공백을 제거 후 문자열을 반환
  • 원본 문자열은 변경 없이 그대로 유지

References
실무 프로그래밍 입문(C#)

2차원 배열이 없는 언어에서 배열의 배열을 써야할 때가 있고, 2차원 배열을 쓸 수 있더라도 배열의 배열을 쓰는 것이 더 좋을 때가 있다.

2D 배열의 문제

  • 직사각형 형태의 데이터만 지원 가능
  • 하지만 각 행마다 열 수가 달라져야 한다면? 작동은 하지만 깔끔하지 않다.

배열의 배열

배열이 2개 있는 것.

  • 바깥 배열(다른 배열을 포함하는 배열)
    • 2차원 배열의 행을 나타낸다고 볼 수 있다.
    • 1차원 배열
    • 1차원 배열의 각 요소의 형은 다시 1차원 배열(안쪽 배열)

n차원 배열은 이미 정방형(x,y)형의 모양이 있고, 각 요소가 실제 데이터인 것이고, 배열의 배열은 1차원 배열이 있고, 이 배열 안의 요소에 또다른 배열을 넣겠다는 뜻이다.

  • 안쪽 배열
    • 1차원 배열
    • 각 요소의 형은 실제 자료형
5줄짜리 바깥 배열
1 string
2 string string string string string
3 string string
4 string string string
5 string string string string string

5줄의 바깥 배열이 있고, 5줄 바깥 배열의 안에 string처럼 실제 자료형이 들어가는 1차원 배열을 말한다.

배열의 배열을 만드는 방법

  • string[][]:배열의 배열을 만드는데, 바깥 배열의 각 원소는 문자열 배열(string[])를 받는다.
  • classroom: 문자열 배열을 원소로 가지는 배열의 이름은 classroom이다.
  • new string[3][]: classroom은 3개짜리 1차원 배열이다.
  • new string [3][]: 각 원소는 문자열 배열(string[])을 가진다.
1
2
3
<자료형>[][]<변수명> = new <자료형>[<바깥 배열 원소 개수>][];

string[][] classromms = new string[3][];

바깥 배열의 원소에 접근하기

1
2
3
4
5
<자료형>[] <변수명> = <배열의 배열 이름>[<바깥 배열 색인>];

string [][] classrooms = new string[3][]; //반드시 필요
int classIndex = 0; //1반
string[] studentNames = classromms[classIndex]; //1반에 접근
1
2
3
4
5
6
7
8
9
static void Main(string[] args)
{
string[][] classrooms = new string[3][];

int classIndex = 0;
string[] studentNames = classrooms[classIndex];

Console.WriteLine(studentNames.Length);
}

위 코드의 경우 0이 나오지 않을까 생각하기 쉬운데, 0이 아닌 null이 나온다. 아무것도 없음을 의미한다. classrooms 배열 안에 3만큼의 문자열 배열을 문자열 배열을 담을 공간을 만드는 역할을 할 뿐이며, 현재 텅텅 비어있는 상태이다.


안쪽배열 만들기

1
<바깥배열이름>[<색인>] = new <자료형>[<안쪽 배열 원소 개수>];
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const int CLASS_COUNT = 3;
string[][] classrooms = new string[CLASS_COUNT][];

int[] STUDENT_COUNT_PER_CLASS = {3, 2, 5};

for (int i = 0; i < CLASS_COUNT; ++i)
{
classrooms[i] = new string[ STUDENT_COUNT_PER_CLASS[i]];
}

//위의 코드와 동일
classrooms[0] = new string[3];
classrooms[1] = new string[2];
classrooms[2] = new string[5];

안쪽 배열 접근하기

1반의 학생 정보를 담은 배열의 길이를 출력하는 코드

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static void Main(string[] args)
{
const int CLASS_COUNT = 3;
int[] STUDENT_COUNT_PER_CLASS = {3, 2, 5};
string[][] classrooms = new string[CLASS_COUNT][];

for (int i = 0; i < CLASS_COUNT; ++i)
{
//안쪽 배열 만들기
classrooms[i] = new string[STUDENT_COUNT_PER_CLASS[i]];
}

int classIndex = 0; //1반
int studentIndex = 0; //첫번째 학생
// 위에서 안쪽 배열을 만들었기 때문에 studentNames는 더이상 null이 아니다.
string[] studentNames = classrooms[classIndex];
studentNames[studentIndex] = "Severus";

Console.WriteLine(studentNames.Length);
Console.WriteLine($"Class 1 = Student 1 : {classrooms[classIndex][studentIndex]}");
}

위의 코드에서 안쪽 배열의 원소에 접근하는 방법은 다음의 2 가지가 있다. 이 2 가지 방법 중에는 방법2가 조금 더 좋다. 특히 for문을 돌리는 것과 같은 조건이 있을 때 더 좋다. 방법2가 deps가 더 얇기 때문이다. 방법1의 경우 바깥 배열의 색인부터 확인한 다음 그 바깥 배열의 안쪽 배열 색인을 찾아서 들어가는 구조이다. 즉 classrooms[0]인지 또는 classrooms[1]인지부터 확인한 후 해당하는 classrooms의 studentName를 찾아야 한다. 방법2의 경우 studentNames를 미리 만들어 두었기 때문에 clarooms의 색인을 찾는 과정을 점프하고 studentNames를 찾으면 된다. 즉 classrooms[0]인지 classrooms[1]인지를 찾는 과정을 점프하여 주어진 classrooms의 색인이 [0]이라면 곧장 classrooms[0]에서 해당하는 studentNames를 찾으면 되는 것이다.

1
2
3
//방법1

<바깥 배열 이름>[<바깥 배열 색인>][<안쪽 배열 색인>] = 값 대입;
1
2
3
//방법2
<안쪽 배열 자료형> <변수명> = <바깥 배열 이름>[<바깥 배열 색인>];
<변수명>[<안쪽 배열 색인>] = 값 대입;
1
2
3
4
5
6
7
8
9
10
//바깥 배열과 안쪽 배열을 만드는 코드 생략. string classrooms[3][]
int classIndex = 0;
int studentIndex = 0;

//방법1
classrooms[classIndex][studentIndex] = "Severus";

//방법2
string[] studentNames = classrooms[classIndex];
studentNames[studentIndex] = "Severus";

여기서 방법2는 classrooms[0]에 있는 것을 복사해서 studentNames에 넣은 것이라 원본인 classrooms는 바뀌지 않는 것이라 생각될 수 있다. 그러나 바뀐다. 보통 기본자료형은 값에 의한 전달을 한다. new로 만든 것은 기본적으로 그 자체가 참조형 데이터이다. 즉 new로 만든 것은 복사가 아니라 원본이 바뀌는 것이다.


안쪽 배열을 늘릴 수 있는가?

안쪽 배열은 1차원 배열이기 때문에 늘릴 수 없다. 즉 배열의 크기가 2인 배열을 3으로 만들 수는 없다.

1
2
3
4
5
string[][] classrooms = new string[CLASS_COUNT][];

classrooms[0] = new string[3];
classrooms[1] = new string[2];
classrooms[2] = new string[5];

원칙적으로는 배열의 크기를 늘릴 수 없지만 필요한 경우에는 아래의 방법으로 해결한다.

  • 크기가 3인 배열을 새로 만든다.
  • for문을 이용하여 기존의 배열 데이터를 새 배열로 복사한다.
  • 새 배열을 바깥 배열에 대입한다.

for문을 이용한 복사

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
string[][] classrooms = new string[CLASS_COUNT][];

//기존에 있던 크기가 2인 배열인 classrooms의 마지막 인덱스를 가져온다.
//학생들 이름을 넣는 코드는 생략
string[] classroom2 = classrooms[1];
//크기가 2인 배열을 3으로 늘린다.
string[] newClassroom2 = new string[classroom2.Length + 1];

//for 문을 돌면서 기존의 배열 데이터를 새 배열로 복사한다. 기존 배열의 길이인 2만큼 for문을 돌면서 newClassroom2에 크기 2인 배열의 값을 복사한다.
for (int i = 0; i < classroom2.Length; ++i)
{
newClassroom2[i] = classroom2[i];
}

//classroom의 크기는 2이고, newClassroom2의 크기는 3이기 때문에 for문을 돌았을 때 3번째 배열의 값은 없는 상태이다. (원래 배열의 크기가 2였으므로, 0, 1까지의 값만 복사된 상태이기 때문.)

//마지막 배열에 접근한다. 마지막 배열에 접근할 때 보통 Length -1을 사용한다.
//마지막 배열인 2번째 인덱스 배열의 값에 "Leanne"를 넣어준다.
newClassroom2[newClassroom2.Length - 1] = "Leanne";

//원래 있던 클래스에 새로 만든 newClassroom2를 넣는다. 이로써 크기가 2인 배열이 3이 되었다.
classrooms[1] = newClassroom2;

Array.Copy()를 사용한 복사

1
Array.Copy(원본배열이름, 새 배열(복사할 배열)이름, 원본 배열의 첫 번째 배열부터 복사할 원소의 개수);
1
2
3
string[] sourceArray = classrooms[1]; // classrooms[1];의 원소를 10개로 가정
string[] destinationArray = new string[2];
Array.Copy(sourceArray, destinationArray, destinationArray.Length);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
string[][] classrooms = new string[CLASS_COUNT][];

//학생들 이름을 넣는 코드는 생략
string[] classroom2 = classrooms[1];

string[] newClassroom2 = new string[classroom2.Length + 1];

//Array.Copy(원본배열, 새 배열(복사할 배열), 몇 개만큼 복사할 것인지)
//원본배열의 크기는 2, 새 배열의 크기는 3이다. 둘의 크기가 다르기 때문에
//원본배열의 크기인 2만큼 복사하기로 정한다.
Array.Copy(classroom2, newClassroom2, classroom2.Length);

//새 배열의 크기는 3이고, 위에서 배열이 2까지일때의 값을 복사했기 때문에
//마지막 배열의 인덱스에 새 값을 넣어준다.
newClassroom2[newClassroom2.Length - 1] = "Leanne";

//원래 있던 클래스에 새로 만든 newClassroom2를 넣는다. 이로써 크기가 2인 배열이 3이 되었다.
classrooms[1] = newClassroom2;

2D 배열 VS 배열의 배열

2D 배열 배열의 배열
다차원 2D 배열 바깥 배열이 1차원 1D 배열
배열의 각 원소가 배열형이 아니다. 행을 나타내는 바깥 배열의 각 원소도 1D 배열이다.
엑셀 형태 바깥 배열인 1차원 배열(행) 안에 안쪽배열인 1차원 배열(열)이 존재
비어 있는 원소가 있을 수 있다. 필요한 만큼 안쪽 배열읠 길이를 잡을 수 있다.

참고로 안쪽 배열에 1D 배열이 아닌 2D 배열을 원소로 가질 수 있긴 하지만 잘 쓰이진 않는다.


References
실무 프로그래밍 입문(C#)

가능한 경우의 수가 제한적인 경우, 이를 열거할 수 있다. 컴퓨터가 이해하기엔 정수와 다르지 않지만, 컴파일러에서는 enum으로 지정된 원소 외의 것이 들어오면 잡을 수 있다.

  • 정수형 상수의 집합 (부동소수점은 안된다.)
  • 각 원소마다 고유의 이름을 가짐
  • 집합 역시 고유의 이름을 가짐
  • enum은 변수로 사용 가능
  • enum은 사전에 정의된 값만 대입 가능하고, 정수형은 정수형 범위에 있는 어떤 값이든 대입 가능하다.

열거형의 정의(기본형)

유사한 성질을 가진 정보들을 나열할 때 정수형 상수보다는 enum을 사용하도록 한다.

1
2
3
4
5
6
7
8
9
10
//정수형 상수
const int HOME = 1;
const int SCHOOL = 2;

//enum으로 나열
enum EBookmark
{
HOME,
SCHOOL
};
1
2
3
4
5
6
7
enum <이름>
{
<원소1>,
<원소2>,
...,
<원소n>
};
  • 정의는 함수 밖에서 한다.
  • 첫 번째 원소의 기본값은 0
  • 아무 값도 대입해주지 않으면 원소의 값은 1씩 증가한다.
1
2
3
4
5
6
7
8
9
10
class Program
{
enum EDirection
{
North, //0
South, //1
East, //2
West //3
};
}

열거형의 정의(원소 값 직접 정의)

각 원소에 원하는 값을 대입 가능

  • 상수
  • 혹은 계산식
1
2
3
4
5
6
7
enum EDirection
{
North = 5,
South = 10,
East = 15,
West = East + 10
}
1
2
3
4
5
6
7
enum EDirection
{
North = 5,
South, //6
East, //7
West //8
}

대입 없이 변수를 정의만 하면 값이 0인 원소가 기본으로 들어간다.

1
2
3
4
5
6
7
8
9
10
enum EDirection
{
North,
South,
East,
West
};

//메인 함수
EDirection direction; //EDirection.North (대입 없이 기본값만 정의했으므로 기본값은 0)
1
2
3
4
5
6
7
8
9
10
enum EDirection
{
North = -2,
South, //-1
East, //0
West //1
};

//메인 함수
EDirection direction; //EDirection.East (초기화를 해주지 않으면 0인 East가 기본값이 된다.)

열거형 변수 정의 및 대입

1
<열거형 이름> <변수명> = <열겨형 이름>.<열거형 원소>

열거형 변수에는 해당 열거형의 원소만 대입 가능하다.

1
2
3
4
5
6
7
enum EDirection
{
North,
South,
East,
West
};
1
2
3
4
5
enum ESex
{
Female,
Male,
};
1
2
3
4
5
6
7
//메인 함수
EDrection direction1; //선언
EDrection direction2 = EDrection.East; //대입

EDrection direction1 = EDrection.East; //OK
EDrection direction2 = ESex.Male; //컴파일 오류
EDrection direction3 = 1; //컴파일 오류

EDrection direction2 = ESex.Male;이나 EDrection direction3 = 1;처럼 EDrection에 없는 값은 넣으면 컴파일 오류가 발생한다.

enum을 쓰면 좋은 점

  • 코드가 읽기 편하다.
  • enum에 지정된 값 외에 다른 값이 들어가면 컴파일 오류가 발생하여 문제 발생을 사전에 예방할 수 있다.
  • 함수 매개변수로 쓰이면 함수가 요구하는 인자형을 빨리 알 수 있으므로 함수에 잘못된 값이 넘어가는 것을 예방할 수 있다.

enum의 꼼수

enum의 마지막 원소를 배열 생성 시 배열 원소 개수로 사용 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
enum EDirection
{
North,
South,
East,
West,
MAX
};

//어떤 함수
string[] directions = new string[(int)EDrection.MAX];

for (int i = 0; i < directions.Length; ++i)
{
//코드
//directions[i]로 접근 가능
}

어서트(Assert)

코드 검증을 위한 코드. enum을 수정하고, 그 enum을 쓰는 함수의 case문은 수정하지 않을 경우 해결하는 방법이 어서트이다.

  • 절대로 발생하지 않아야 하는 조건을 런타임 중에 검사
    • 만약 발생한다면 코드가 올바르게 동작하지 않는 것
    • 함수의 선조건 검사에 쓰기 적당.
  • 디버그 모드에서만 동작
    • 릴리즈 모드에서 어서트 함수는 마치 주석처럼 무시된다.
    • 릴리즈 모드에서 동작하게 되면 성능 저하가 발생한다.
  • 최종 제품의 성능저하 없이 개발 중에 문제를 고치는 바람직한 방법.
1
2
3
4
5
6
7
//방식 1
Debug.Assert(<표현식>);
Debug.Assert(menu < 5); // menu가 5 이상일 때 어서트 발생

//방식 2 어서트가 발생했을 때 보여줄 메세지를 함께 작성
Debug.Assert(<표현식>, <메세지>);
Debug.Assert(menu < 5, "Wrong menu number!");
  • Debug.Assert()를 사용하기 위해서 System.Diagnotics 라이브러리를 추가해야 한다.
  • using System.Diagnotics로 추가한다.
  • Assert() 안에 들어가는 조건은 참이라고 가정하고 작성되는 것이므로 조건이 거짓일 때 프로그램은 일시 중단되고, 어서트 메세지가 출력창에 출력된다.

References
실무 프로그래밍 입문(C#)

값에 의한 전달 참조에 의한 전달
원본을 주지 않고, 복사본을 준다. 원본을 준다.
원본에 손상이 없다. 원본에 손상이 있다.

값에 의한 전달

  • 원본 변수 != 인자 : 함수 매개변수에 원본 변수의 사본이 전달
  • 호출된 함수의 인자 값이 변경되어도 호출자 함수에는 반영되지 않음.
    • Square함수의 값이 바꼈다고 해서 Main함수의 값도 바뀌는 것이 아니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Program
{
static void Square(double number)
{
number *= number;
}

static void Main(string[] args)
{
double number = 5;
Console.WriteLine($"Before: {number}"); //5
Square(number);
Console.WriteLine($"After: {number}"); //5
}
}

원본 변수의 값을 바꾸고 싶다면 어떻게 해야할까? return 키워드 사용. square를 호출 후 return되는 값을 Main 함수 내에 number에 재할당해준다. 이 역시 원본이 변경된 것은 아니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Program
{
static void Square(double number)
{
return number *= number;
}

static void Main(string[] args)
{
double number = 5;
Console.WriteLine($"Before: {number}"); //5
number = Square(number);
Console.WriteLine($"After: {number}"); //25
}
}

참조에 의한 전달

원본을 바꾸지 않는 값에 의한 전달과 달리 원본을 바꾸는 것참조에 의한 전달이라 한다. ref키워드는 값을 바꾸는 것이 아니라 원본을 전달해주겠다는 의미(원본을 바꾼다).

  • 원본 변수 = 인자 : 함수 매개변수에 원본 변수가 전달된다.
  • 호출된 함수의 인자의 값이 변경되면 호출자 함수에 반영된다.
    • Square함수의 값이 바뀌면 Main함수의 값도 바뀐다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Program
{
static void Square(ref double number)
{
return number *= number;
}

static void Main(string[] args)
{
double number = 5;
Console.WriteLine($"Before: {number}"); //5
Square(ref number);
Console.WriteLine($"After: {number}"); //25
}
}

ref 키워드 (c# 전용)

  • 참조에 의한 전달을 위해 c#에서 사용
  • 함수 호출 시 인자에 ref 키워드를 붙인다.
  • ref키워드는 다른 프로그래밍 언어에서 널리 쓰이지 않는다. C의 포인터처럼 비슷한 개념은 있다.

배열의 참조

아래 함수를 실행한 결과로 화면에 출력되는 값은?
1, 2, 3, 4, 5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
using System;

public class Program
{
public static void Main()
{
int[] array = new int[5] { 1, 2, 3, 4, 5 };
Square1(array);

for (int i = 0; i < array.Length; ++i)
{
Console.WriteLine(array[i]);
}
}

public static int[] Square1(int[] arr)
{
int[] result = new int[arr.Length];

for (int i = 0; i < arr.Length; ++i)
{
result[i] = arr[i] * arr[i];
}

return result;
}
}
  • Square1(array);{1, 2, 3, 4, 5}가 전달되었다.
  • Square 함수 내부에서는 {1, 2, 3, 4, 5}의 길이인 5와 같은 길이를 가지는 result라는 배열을 생성하고, for문을 돌면서 i가 5가 되기 전까지 result[i] = arr[i] * arr[i];라는 계산을 수행한다.
  • 그 결과로 반환되는 result1, 4, 9, 16, 25이다.

Main함수로 돌아가서 그럼 Main 함수가 반환하는 값은 1, 4, 9, 16, 25일까? 그렇지 않다.

마지막으로 화면에 출력되는 값은 1, 2, 3, 4, 5이다.
Square1 함수에는 array라는 베열의 참조가 전달되었다. 참조가 전달되었다는 의미는 Square 함수 내부에서 이 배열에 대한 어떠한 연산의 과정을 거쳐 배열 안의 요소의 값이 변경되면 array라는 배열도 변경된다는 것을 의미한다. 따라서 Square1 함수 내부에서 array라는 값에 대한 제곱을 수행하여서 1, 4, 9, 16, 25이 반환된다. 이 흐름대로라면 마지막으로 출력되는 값은 1, 4, 9, 16, 25이어야 정답이다. 하지만 int[] result = new int[arr.Length];라고 선언된 부분을 살펴보아야 한다. array라는 배열의 참조가 전달된 것은 맞지만 여기서 result라는 새로운 배열이 만들어졌기 때문에 Square1 함수 내부의 for문에서 연산된 결과는 array의 요소를 바꾸는 것이 아니라 새롭게 만들어진 result의 요소를 계산한 것이다. 따라서 1, 4, 9, 16, 25는 array의 요소를 변경한 것이 아닌 새롭게 만들어진 result라는 배열의 요소를 변경한 것이다. 즉 원래 있던 array배열과는 상관이 없어진 것이다. 따라서 마지막으로 출력되는 값은 1, 2, 3, 4, 5이다.


아래 함수를 실행한 결과로 화면에 출력되는 값은?
1, 4, 9, 16, 25
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;

public class Program
{
public static void Main()
{
int[] array = new int[5] { 1, 2, 3, 4, 5 };
Square2(array);

for (int i = 0; i < array.Length; ++i)
{
Console.WriteLine(array[i]);
}
}

public static void Square2(int[] arr)
{
for (int i = 0; i < arr.Length; ++i)
{
arr[i] *= arr[i];
}
}
}
  • Square2(array);{1, 2, 3, 4, 5}가 전달되었다.
  • Square2 함수 내부에서는 전달된 array값을 가지고 for문을 돌면서 arr[i] *= arr[i]라는 계산을 수행한다.
  • 그 결과로 반환되는 arr[i]1, 4, 9, 16, 25이다.

Main함수로 돌아가서 그럼 Main 함수가 반환하는 값은 1, 4, 9, 16, 25일까? 그렇다.

Square2함수와 앞서 살펴본 Square1 함수와는 큰 차이점이 있다. Square2에는 Square1함수의 result처럼 새롭게 할당되는 값이 없다. 따라서 여기서 for문 내부의 연산 결과 반환되는 값은 실제로 array라는 원본 배열의 요소를 변경시킨다. 그렇기 때문에 최종적으로 1, 4, 9, 16, 25가 출력되는 것이다.


References
실무 프로그래밍 입문(C#)

범위

기본적으로 어떤 범위 안에서 선언된 것은 범위 밖에서 쓰지 못한다.

1
2
3
4
5
6
7
8
9
10
11
static void Main(string[] args)
{
for (int i = 0; i < 10; ++i)
{
string name = "Leon";
}

//i, name은 for문 안에서 선언된 것이므로 for 문 밖에서 사용하려면 쓸 수 없다.
i = 200; //컴파일 오류
name = "hi"; //컴파일 오류
}

상위 범위에서 선언한 변수/상수는 하위 범위에서 사용 가능하다.

1
2
3
4
5
6
7
8
9
static void Main(string[] args)
{
string name = "Leon";

for (int i = 0; i < 10; ++i)
{
name = "hi"; //OK
}
}

함수의 범위

기본적으로 함수 안에서 선언한 모든 것은 그 함수에서만 사용 가능하다. 이를 지역 변수 라고 한다.

1
2
3
4
5
6
static int AddNumbers(int num1, int num2)
{
//sum은 지역 변수이다. 외부에서 이 변수를 사용할 수 없다.
int sum = num1 + num2;
return sum;
}

함수 밖에 있는 변수/상수는 사용할 수 없다.

result가 선언된 Square는 Main함수와 같은 레벨에 있다. Main함수를 포함하고 있는 것은 Square가 아니라 Program이다. result가 Program 내부에서 선언되었다면 사용할 수 있었을 것이지만 Square 안에 선언되어 있기 때문에 컴파일 오류가 난다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Program
{
static void Square(double number)
{
double result = number * number;
}

static void Main(string[] args)
{
double num = double.Parse(Console.ReadLine());
Square(num);
Console.WriteLine($"Result: {result}"); //컴파일 오류
}
}

함수 매개변수, 반환값 모두 복사된 것. 값에 의한 전달

함수 Square를 호출할 때 Main 함수 변수 number에 들어 있는 값을 복사해서 함수 Square의 매개변수 number에 대입한다. double number는 이름만 같을 뿐 소속이 다르다.(동명이인의 개념).

number *= numbernumber = number * number와 같은 의미이다. 이 연산의 결과는 함수 Square의 매개변수 number에만 영향을 주고, Main 함수의 number는 함수 속 연산의 영향을 받지 않는다. (두 함수에 같은 이름의 변수가 있다고 동일한 변수가 아니다.)

1
2
3
4
5
6
7
8
9
10
11
static void Square(double number)
{
number *= number;
return number;
}

static void Main(string[] args)
{
double num = 5;
double result = Square(number); //25
}

언제 함수를 작성할까?

  • 처음부터 함수로 시작하지 말자.
  • 현재 존재하는 혹은 향후에 발생 가능성이 높은 코드 중복을 피하고자 할 때 함수를 작성한다.
  • 코드 중복은 좋지 않다. 다른 사람이 중복 코드에 있는 버그를 고칠 때, 모든 코드를 수정할 것이라는 보장이 없기 때문.

함수를 잘 작성하는 방법

함수 대신 중괄호

함수가 길어지면 동일한 이름의 지역 변수가 생기는 경우가 있다. 하지만 같은 범위 내에 이름이 중복되면 컴파일 에러가 발생한다. 이럴 경우 중괄호를 사용하여 범위를 분리시킨다. 이처럼 조건문이나 함수의 중괄호가 아니라 중괄호만 사용하여 새 범위를 만들 수도 있다.

아래 코드에서 length는 2번 쓰여서 컴파일 에러가 발생할 것 같지만 발생하지 않는다. 아래 코드에서는 중괄호를 사용하여 스코프를 분리해주었기 때문에 두 함수의 범위가 다르다. 따라서 length라는 이름이 똑같이 쓰여도 에러가 발생하지 않는다. 각각의 length는 쓰여진 함수 내에서만 유효하기 때문이다.

1
2
3
4
5
6
7
8
9
10
11
string[] names;
{
int length = int.Parse(Console.ReadLine());
//코드 생략
}

int[] scores;
{
int length = int.Parse(Console.ReadLine());
//코드 생략
}

함수 대신 #region과 #endregion

  • c# 전용
  • 긴 함수를 짧게 만들 수 있다. (실제 함수 길이가 줄어드는 것은 아니다.)
  • VS에서 코드를 접거나 펼 수 있게 해준다.
1
2
3
#region GetScores
//코드 생략
#endregion
1
2
#region <이름>
#endregion

References
실무 프로그래밍 입문(C#)

함수

  • 입력이 들어가면 어떤 결과가 출력된다.
  • 중복되는 코드를 줄이는데 사용한다.
  • 자주 사용하는 코드의 재활용성이 올라간다.

함수 정의

1
2
3
4
static <반환형> <함수명>(<매개변수 목록>)
{
//함수 body
}
  • 함수 시그니처(함수명(필수), static, 매개변수 목록)을 포함해야 한다.
  • 반환형, 함수 body는 필수이다.

반환형

1
2
3
4
5
6
7
8
9
10
11
//반환형이 있는 함수
static int Add(int op1, int op2) //반환형 int를 생략하면 안된다.
{
return op1 + op2;
}

//반환형이 없는 함수 return을 쓰지 않아도 된다.
static void PrintHello(string name)
{
Console.WriteLine($"Hello, {name}!");
}
  • 프로그래밍 세계에세 함수의 출력
  • 반환형은 반드시 선언해야한다. (선언하지 않으면 컴파일 오류 발생.)
  • 반환값이 없을 수 있다. 없다면 void 사용.
  • 반환형이 void가 아니면 함수 body에 return 키워드를 사용하여 데이터를 반환한다.
  • 데이터를 반환하지 않으면 컴파일 오류 발생.

매개변수 목록

1
2
3
4
5
6
7
8
9
10
11
//2개의 매개변수가 있는 함수
static int Add(int op1, int op2)
{
return op1 + op2;
}

//매개변수가 없는 함수
static void PrintHello()
{
Console.WriteLonr('hello!;)
}
  • 프로그래밍 세계에서의 임력
  • int, byte와 같은 자료형 외에 int[], string과 같은 배열도 매개변수로 사용 가능.
  • 필수가 아니다.
  • 매개변수와 인자는 엄밀히 말하면 다르다.
    • 매개변수: 함수를 정의할 때 함수의 입력값을 선언
    • 인자: 함수를 호출할 때 함수로 전달하는 실제값

메인함수의 매개변수에 데이터 전달하기

메인함수는 프로그램을 실행할 때 자동으로 호출되는 함수로 이 메인함수에 매개변수를 넘기고 싶으면 visual studio 내부의 프로젝트 옵션 => 실행 구성 => Default => 인수에 전달하고 싶은 데이터를 입력한다.

1
2
3
4
5
6
7
static void Main(string[] args)
{
for (int i = 0; i < args.Length; ++i)
{
Consol.WriteLine($"args[{i}] = {args[i]}");
}
}

위에서 설명한 방법대로 Hello, C# is fun!이라고 데이터를 입력했다면 실행했을 때 아래와 같은 결과를 얻을 수 있다. args 요소 하나에 여러 단어를 넣고 싶을 때는 "Hello, C# is fun!" Bye!과 같이 문장을 큰따옴표로 감싼다.

  • args[0] = Hello,
  • args[1] = C#,
  • args[2] = is,
  • args[3] = fun!

큰따옴표로 감싼 문장은 아래와 같이 출력된다.

  • args[0] = Hello, C# is fun!
  • args[1] = Bye!

배열의 길이 Length

<배열 변수명>.Length

배열의 길이를 알려준다.

함수 Body

1
2
3
4
static int Add(int op1, int op2)
{
return op1 + op2;
}
  • 함수의 기능을 구현한 코드 블록, 그 함수의 범위를 나타냄.
  • 중괄호를 이용하여 표현
  • void가 아닌 반환형을 가지고 있다면 반드시 return 키워드를 사용하여 데이터를 반환한다.

함수 이름

  • 함수명은 어떻게 짓든지 상관없다.
  • 하지만 이해하기 쉽고, 가독성을 좋게 하기 위해 함수가 어떤 기능을 가졌는지 알 수 있도록 함수명을 정한다.
  • 호출자가 함수 내부를 알 필요가 없게 함수명을 명확하게 지어야 한다.

함수 호출

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

//반환형이 있는 경우
static int Add(int op1, int op2)
{
return op1 + op2;
}

//반환형이 있는 함수 호출
static void Main(string[] args)
{
int result = Add(123, 589);
}

//반환형이 없는 경우
static void PrintHello()
{
Console.WriteLine("just hello!");
}

//반환형이 없는 함수 호출
static void Main(string[] args)
{
PrintHello();
}
  • 반환형이 있는 경우: Add함수의 반환형이 int이므로, return 되는 값을 int result에 저장하고, 함수를 소괄호를 써서 호출한다. 매개변수가 있을 경우 소괄호 내에 적어준다.
  • 반환형이 없는 경우: 변수에 할당할 필요없이 소괄호를 써서 호출하기만 하면 된다.
  • 함수 호출 시 인자는 변수, 상수 모두 가능하다. int result = Add(123, 589);에서 상수 123, 589 대신 변수를 이용하여(num1, num2 가 변수라 가정) int result = Add(num1, num2);처럼 사용할 수 있다.

메인 함수

1
2
3
static void Main(string[] args)
{
}

콘솔 화면 출력 함수

1
2
Console.Write();
Console.WriteLine();

키보드 입력 함수

1
Console.ReadLind();

변환 함수

1
2
3
int.Parse();
double.Parse();
float.Parse();

코딩 표준

함수

정확하게 어떤 기능을 하는지 알려주는 단어를 사용한다.

  • 동사로 시작
  • 제일 첫 글자는 대문자 혹은 소문자(회사마다 코딩 표준은 다름)
  • 여러 단어를 연결한다면 파스칼 표기법으로 사용.

매개변수와 지역변수

정확하게 어떤 정보를 담는지 알려주는 단어 사용

  • 명사 사용
  • 제일 첫 글자는 소문자로 시작
  • 여러 단어를 연결한다면 카멜케이스 사용

선조건 후조건

함수가 무슨 일을 하는지에 대한 약속. 선조건을 만족하지 못하면 후조건을 보장할 수 없다.

선조건

  • 함수 실행 시작 전에 참으로 가정한 조건 e.g. Divede() 함수는 분모가 0이 아니어야 한다.
  • 함수 이름, 매개변수로 유추 가능하지만 부족하면 주석으로 추가 설명 (슬래시(/) 세번이면 자동으로 주석이 생성된다.)
    1
    2
    3
    4
    5
    6
    7
    8
    /// <summary>
    ///
    /// </summary>
    /// <param name = "numerator"></param>
    static float Divede(float number, float denominator)
    {
    return numerator / denominator;
    }

후조건

  • 함수 실행 후에 보장되는 조건 e.g. 두 정수를 더하면 정수의 결과가 나온다.
  • 함수 이름과 반환형으로 유추 가능

References
실무 프로그래밍 입문(C#)

  • 1차원 배열: [x]
  • 2차원 배열: 1차원 배열들의 모임. [x,y] e.g. 엑셀의 테이블형 데이터, 구구단
  • 3차원 배열: 2차원 배열들의 모임. [x,y,z]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main(string[] args)
{
//배열을 만드는데 콤마가 하나 있다. 2개가 들어온다.
int[,] table = new int[9,9];

for (int i = 0; i < 9; ++i)
{
for (int j = 0; j < 9; ++j)
{
table[i, j] = (i + 1) * (j + 1);
Console.Write($" {(i + 1), 2} * {j + 1, -2} = {table[i, j], -3}");
}
Console.WriteLine("");
}
}

2차원 배열 선언하기

1
2
int[,] table = new int[2,4];
<자료형>[,] <변수명> = new <자료형>[<행의 개수>, <열의 개수>];
  • [,]은 2차원을 의미: int[,]은 int형 2차원 배열.([,]와 같은 형태는 다른 언어에는 없다.)
  • <행의 개수> * <열의 개수> 개의 <자료형> 데이터를 담을 2D배열[,]을 만든다.
    • e.g. 2 * 4개의 int형 자료를 저장할 2차원 배열을 만든다.
  • 배열의 이름, 즉 그 배열을 저장할 변수의 이름은 <변수명>
    • e.g. 2 * 4개의 int형 자료를 저장할 2차원 배열을 만드는데 그 이름은 table이다.

2차원 배열의 선언과 동시에 대입하기

1
2
<자료형>[,] <변수명> = new <자료형>[,]{{<데이터>}, {<데이터>}};
<자료형>[,] <변수명> = new <자료형>[<행의 개수>, <열의 개수>]{{<데이터>}, {<데이터>}};
1
2
int[,] table1 = new int[,] { {1, 2, 3}, {2, 4, 6} };
int[,] table2 = new int[2, 3] { {3, 6, 9}, {4, 8, 12} };
table1 [0] [1] [2]
[0] 1 2 3
[1] 2 4 6
table2 [0] [1] [2]
[0] 3 6 9
[1] 4 8 12

for문 속 for문

2차원 배열을 순차적으로 접근하기 위해서는 for문이 2개 필요

  • 을 위한 for문
  • 을 위한 for문
1
2
3
4
5
6
7
8
9
int[,] table = new int[2, 3];

for (int i = 0; i < 2; ++i) //행을 위한 for 문
{
for (int j = 0; j < 3; ++j) //열을 위한 for 문
{
table[i, j] = (i + 1) * (j + 1);
}
}
table [0] [1] [2]
[0] 1 2 3
[1] 2 4 6
  1. table[0,0] = (0 + 1) * (0 + 1) = 1;
  2. table[0,1] = (0 + 1) * (1 + 1) = 2;
  3. table[0,2] = (0 + 1) * (2 + 1) = 3;
  4. table[1,0] = (1 + 1) * (0 + 1) = 2;
  5. table[1,1] = (1 + 1) * (1 + 1) = 4;
  6. table[1,2] = (1 + 1) * (2 + 1) = 6;

3차원 4차원 배열

3차원 배열

  • 많이 쓰이지 않음.
  • 주로 3차원 정보를 처리하는 프로그램 사용(e.g. 3D 블록데이터를 저장하는 게임(테트리스), 의료 프로그램(CT, MRI 등))

4차원 배열

  • for문 4개가 필요
  • 차원이 늘어날 수록 for문의 수도 증가

n차원

  • 물리 시뮬레이션
  • 머신 러닝

3차원, 4차원, n차원 등 차원이 늘어날수록 반복문도 많아진다.

  • 반복문이 많아질수록 성능이 기하급수적으로 떨어진다.
  • 프로그램의 런타임 복잡도는 내포(중첩)된 반복문 개수에 비례한다.
    • 반복문이 1개: N개의 요소에 방문, O(N)
    • 이중 반복문: N * N => O(N2)
    • 삼중 반복문: N * N * N => O(N3)
    • N = 100 이면 반복문 개수에 따라 100 => 10,000 => 1,000,000 증가
  • O(N3)을 O(N)으로 만들 수 있다면 100,000배 빨라질 수 있다.

References
실무 프로그래밍 입문(C#)

for문, while문, do…while문 세가지가 있고, 모두 호환이 된다.
break;를 사용하면 반복문의 실행을 종료한다.

for문

특정 코드를 정해진 횟수만큼 반복하는 구문

1
2
3
4
for(초기화 코드; 반복 조건식; 증감문)
{
반복할 코드
}
  • 초기화 코드: 처음 딱 한번 실행된다.
  • 반복 조건식: 반복문을 계속 실행하지 판단한다. 조건식이 참일 동안은 그 다음 오는 중괄호 사이의 코드를 실행한다. 조건식이 거짓이라면 for문은 종료된다.
  • 증감문: 반복할 코드를 실행한 후 증감문을 실행한다.
1
2
3
4
5
6
int[] ages = new int[3];

for(int i = 0; i < 3; ++i)
{
ages[i] = int.Parse(Console.ReadLine());
}

while 반복문

특정한 조건을 만족하는 동안 코드를 반복

  • 반복할 횟수가 꼭 정해져 있지 않다.
  • 무한 반복도 가능
1
2
3
4
while(조건식)
{
조건을 만족할 동한 반복할 코드
}
  • 조건식: 조건식이 참이면 중괄호 안의 반복할 코드를 실행
  • 조건식을 만족하면 코드를 실행하는 과정을 반복
1
2
3
4
5
6
7
8
int[] ages = new int[3];
int count = 0;

while(count < 3)
{
ages[count] = int.Parse(Console.ReadLine());
++count; //주의: ++count가 없으면 무한 반복 발생
}

while(true)

while문의 조건이 true이면 항상 참이니까 무한 반복하여 코드가 실행된다.

1
2
3
4
while(true)
{
// 조건을 만족할 동안 반복할 코드
}

무한 반복을 탈출하려면 아래와 같이 while(true)라고 작성한 후 코드 내부에서 if문을 사용하여 break;하는 방법이 있는데 한동안 이는 코드 내부에서 if문과 break;를 사용하는 것을 잊을 경우 무한 반복되기 때문에 나쁜 습관이라고 불렸다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
static void Main(string[] args)
{
string passcode = "3941a"; //실제 비밀번호를 이렇게 저장해서는 안됨.
string userInput = "";

while(true)
{
Console.Write("Please enter the password: ");
UserInput = Console.ReadLine();

if(passcode == userInput)
{
Console.WriteLine("Correct! welcome home!");
break;
}
Console.WriteLine("Wrong password!");
}
}

그러나 조건식이 많을 경우 while(true)를 쓰지 않고 처리하면 아래의 예시처럼 오히려 가독성이 떨어진다.

1
2
3
4
while(something1 || something2 || something3 || something4)
{
//something
}

아래 코드는 각 조건이 분리되어 가독성이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while(true)
{
if(something1)
{
break;
}
if(something2)
{
break;
}
if(something3)
{
break;
}
}

do-while 반복문

while문과 유사하며, do 구문의 중괄호 코드 블럭을 반드시 한 번은 실행

1
2
3
4
5
do
{
// 최소 한 번은 반드시 실행되는 코드
// 한 번 실행 후에는 조건식이 참일 때만 실행
}while(조건식);
  • 반복할 코드: 중괄호 사이에 작성된 코드는 무조건 1번은 실행된다.
  • 조건식: 반복할 코드 실행 후 거짓이면 코드의 실행을 종료하고, 조건식이 참이면 반복할 코드를 한 번 더 실행한다.
1
2
3
4
5
6
7
8
9
int[] ages= new int[3];
int count = 0;

do
{
ages[count] = int.Parse(Console.ReadLine());

++count;
}while(count < 3);

for문 vs while문

for문을 쓰는 것이 더 좋을 때

  • 반복문이 시작하는 시점에 범위가 정해져 있을 때
  • 배열의 모든 요소를 훑을 때

while문을 쓰는 것이 더 좋을 때

  • 반복문을 종료하는 시점이 반복문 실행 도중에 결정될 때

while문 vs do-while문

while문 do-while문
코드 블럭 한 번도 실행 안 될 수 있음 무조건 한 번은 실행 됨
사용 빈도 자주 사용 자주 사용하지 않음.

References
실무 프로그래밍 입문(C#)

switch문

매치 표현식의 결과 값에 따라 실행할 구문을 선택

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
switch(매치 표현식)

{
case <상수 라벨1>:
매치 표현식의 반환값이 상수 라벨 1과 일치할 때 실행하는 구문
break;
case <상수 라벨2>:
매치 표현식의 반환값이 상수 라벨 2와 일치할 때 실행하는 구문
break;
case <상수 라벨n>:
매치 표현식의 반환값이 상수 라벨 n과 일치할 때 실행하는 구문
break;
default:
매치 표현식의 반환값이 위의 상수 라벨과 하나도 일치하지 않을 때 실행하는 구문
break;
}
  • 모든 case 구문 다음에는 break 구문을 넣아야 한다. 없으면 컴파일 오류가 난다. (case를 여러개 묶은 경우는 break를 쓰지 않아도 된다.)
  • 해당하는 case 구문이 없으면 default로 가서 실행된다.
  • case에서 기본으로 사용한 수 있는 상수
    • int(기본), long, char, bool, string(c#), 부동소수점형들은 사용 불가.
  • case를 여러 개 묶을 수 있다.

배열

중복된 코드, 변수의 수를 동적으로 선언하지 못하는 문제와 같은 비효율적인 문제를 해결하기 위한 방법으로 배열이 나오게 되었다.

  • 동일한 자료형을 여럿 담을 수 있는 구조
  • 배열 안에 있는 각 데이터를 요소(element)라고 부른다.
  • 몇 개의 데이터를 담을지 결정한 뒤에 수는 바꿀 수 없고, 내용은 변경 가능하다.
  • 반복문과 합쳐서 쓰면 매우 유용하다.

배열 선언

<자료형>[] <변수명> = new <자료형>[<개수>];

  • e.g. float[] heights = new float[3];
  • <개수>개의 <자료형>자료를 담은 배열을 만든다.
  • 선언과 동시에 대입하기
    • 기본형: <자료형>[] <변수명> = new <자료형>[]{<데이터(콤마로 구분)>}; e.g. int[] ages = new int[]{30,14,27};
    • 단축형: <자료형>[] <변수명> = {<데이터(콤마로 구분)>}; e.g. int[] ages = {30, 14,27};
  • 요소에 접근하기: []안에 접근하고자 하는 데이터의 색인을 넣음.

char 배열과 문자열

  • 기본적으로 같은 데이터 방식
  • 일부 언어에서는 char 배열을 쓰면 되니까 문자열을 지원안한다.
  • 배열은 고정 길이를 가지고 있다.

References
실무 프로그래밍 입문(C#)