정보처리기사 실기 C 요약

SH·2023년 10월 2일
0

자격증

목록 보기
1/5

포인터

포인터는 하나만 알면 된다. p는 선언할 때를 제외하고 무조건 값을 가리킨다고 생각하면 되고 &는 주소를 가리킨다고 생각하면 된다.

포인터는 메모리 주소를 저장하는 변수입니다. 이 주소는 다른 변수의 위치나 어떤 함수의 시작 주소, 배열의 원소 등을 참조할 수 있습니다.

int *ptr; // int 형 변수의 주소를 저장할 수 있는 포인터

주소 연산자와 포인터

&는 주소 연산자입니다. 변수 앞에 이 연산자를 사용하면 해당 변수의 메모리 주소를 얻을 수 있습니다.

출처 : [C언어] scanf()함수에서 &(ampersand)를 쓰는 이유 (tistory.com)

역참조 연산자

*는 역참조 연산자로서, 포인터가 가리키는 주소에 저장된 값을 접근할 때 사용됩니다.

int x = 10;
int *ptr = &x; // x의 주소를 ptr 포인터에 저장
printf("%d", *ptr); // 10을 출력

포인터의 증가와 감소

포인터를 증가하거나 감소시키면 포인터가 가리키는 주소도 변경됩니다. 포인터의 자료형에 따라 주소의 증감 크기가 결정됩니다.

int arr[3] = {10, 20, 30};
int *ptr = arr; // 배열의 첫 번째 원소의 주소를 가리킴
ptr++; // arr[1]을 가리키게 됨, 20 출력

포인터와 배열

배열 이름은 배열의 첫 번째 원소의 주소를 가리킵니다. 따라서 포인터를 사용하여 배열의 원소에 접근할 수 있습니다.

int arr[3] = {10, 20, 30};
int *ptr = arr;
printf("%d", *(ptr+1)); // 20을 출력

다양한 포인터 형태

  • 포인터 배열
  • 다중 포인터
  • 함수 포인터
  • 구조체 포인터 등

문제1: 포인터를 활용하여 두 정수 값을 교환하기

문제 설명:

두 개의 정수 변수 ab가 주어졌을 때, 이 두 변수의 값을 포인터를 사용하여 교환하는 함수 swap을 작성하세요.

해결:

포인터를 사용하여 주어진 두 정수의 메모리 주소에 접근하고, 임시 변수를 활용하여 값을 교환합니다.

#include <stdio.h>

void swap(int *x, int *y) {
    int temp = *x;
    *x = *y;
    *y = temp;
}

int main() {
    int a = 5, b = 10;
    printf("Before swapping: a = %d, b = %d\n", a, b);
    swap(&a, &b);
    printf("After swapping: a = %d, b = %d\n", a, b);
    return 0;
}

이 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다:

Before swapping: a = 5, b = 10
After swapping: a = 10, b = 5

문제2: 포인터 배열을 사용하여 문자열 여러 개 출력하기

문제 설명:

다음은 문자열의 포인터 배열입니다: char *strArr[] = {"apple", "banana", "cherry"};. 포인터 배열을 사용하여 각 문자열을 한 줄씩 출력하는 함수 printStrings를 작성하세요.

해결:

문자열의 포인터 배열은 각 문자열의 첫 번째 문자를 가리키는 포인터들의 배열입니다. 따라서 포인터를 사용하여 배열의 각 원소에 접근하고, 해당 포인터를 사용하여 문자열을 출력합니다.

#include <stdio.h>

void printStrings(char *strArr[], int length) {
    for(int i = 0; i < length; i++) {
        printf("%s\n", strArr[i]);
    }
}

int main() {
    char *strArr[] = {"apple", "banana", "cherry"};
    int length = sizeof(strArr) / sizeof(strArr[0]);
    printStrings(strArr, length);
    return 0;
}

이 코드를 실행하면 다음과 같은 결과를 얻을 수 있습니다:

apple
banana
cherry

다차원 포인터

#include <stdio.h>

int main() {
    int value = 10;
    int *p = &value;    // p는 value의 주소를 저장합니다.
    int **pp = &p;      // pp는 p의 주소를 저장합니다.

    printf("value = %d\n", value);
    printf("*p = %d\n", *p);
    printf("**pp = %d\n", **pp);

    return 0;
}
//3개다 10으로 출력됨

사칙연산과 증감연산

1. 사칙연산자

사칙연산자는 기본적인 수학 연산을 수행하기 위한 연산자입니다.

  • 덧셈 (+) 두 피연산자의 합을 반환합니다.
    int a = 5, b = 3;
    int sum = a + b;  // sum은 8
  • 뺄셈 (-) 앞의 피연산자에서 뒤의 피연산자를 뺀 결과를 반환합니다.
    int a = 5, b = 3;
    int difference = a - b;  // difference는 2
    
  • 곱셈 (*) 두 피연산자의 곱을 반환합니다.
    int a = 5, b = 3;
    int product = a * b;  // product는 15
    
  • 나눗셈 (/) 앞의 피연산자를 뒤의 피연산자로 나눈 결과의 몫을 반환합니다. (피연산자가 정수일 경우, 결과도 정수로 반올림하지 않고 소수점 이하를 버립니다.)
    int a = 5, b = 3;
    int quotient = a / b;  // quotient는 1
    
  • 나머지 연산 (%) 앞의 피연산자를 뒤의 피연산자로 나눈 결과의 나머지를 반환합니다.
    int a = 5, b = 3;
    int remainder = a % b;  // remainder는 2

2. 증감연산자

증감연산자는 변수의 값을 1씩 증가시키거나 감소시키는 연산자입니다.

  • 전위 증가 (++변수) 변수의 값을 1 증가시킨 후, 증가된 값을 반환합니다.
    int a = 5;
    int b = ++a;  // a는 6, b는 6
    
  • 후위 증가 (변수++) 변수의 현재 값을 반환한 후, 변수의 값을 1 증가시킵니다.
    int a = 5;
    int b = a++;  // a는 6, b는 5
    
  • 전위 감소 (--변수) 변수의 값을 1 감소시킨 후, 감소된 값을 반환합니다.
    int a = 5;
    int b = --a;  // a는 4, b는 4
    
  • 후위 감소 (변수--) 변수의 현재 값을 반환한 후, 변수의 값을 1 감소시킵니다.
    int a = 5;
    int b = a--;  // a는 4, b는 5
    

증감연산자를 사용할 때 전위와 후위의 차이에 주의해야 합니다. 전위 연산자는 연산을 먼저 수행하고 값을 반환하며, 후위 연산자는 값을 먼저 반환하고 연산을 수행합니다.

문제: 복합 증감 및 사칙연산 표현식 해석하기

문제 설명:

아래의 C 코드에서 주어진 변수와 표현식들이 있습니다. result 변수에 어떤 값이 저장되는지 예측하세요.

#include <stdio.h>
int main() {
    int a = 5, b = 3, c = 4;
    int result = a++ + --b * c-- - ++a;

    printf("Result: %d\n", result);
    return 0;
}

해석:

  1. 표현식을 단계별로 분석하면 다음과 같습니다.

    a++: a의 현재 값인 5를 반환하고, a를 1 증가시킨 후 a는 6이 됩니다.

    -b: b를 1 감소시킨 후 감소된 값인 2를 반환합니다.

    c--: c의 현재 값인 4를 반환하고, c를 1 감소시킨 후 c는 3이 됩니다.

    ++a: 이 연산은 a++ 연산 후에 수행되므로, a는 현재 6입니다. a를 1 증가시킨 후 증가된 값인 7을 반환합니다.

  2. 따라서 주어진 표현식은 다음과 같이 계산됩니다:

    5 + 2 * 4 - 7 = 5 + 8 - 7 = 6

따라서 result의 값은 6입니다.

비트연산자

1. 비트 AND 연산자 (&)

두 비트 모두 1일 때만 결과가 1이며, 그렇지 않으면 결과는 0입니다.
int a = 5; // 0101 (이진)
int b = 3; // 0011 (이진)
int result = a & b; // 0001 (이진) -> 1 (십진)

int a = 5;     // 0101 (이진)
int b = 3;     // 0011 (이진)
int result = a & b; // 0001 (이진) -> 1 (십진)

2. 비트 OR 연산자 (|)

두 비트 중 하나 이상이 1일 때 결과는 1이며, 그렇지 않으면 결과는 0입니다.

int a = 5;     // 0101 (이진)
int b = 3;     // 0011 (이진)
int result = a | b; // 0111 (이진) -> 7 (십진)

3. 비트 XOR 연산자 (^)

두 비트가 서로 다를 때 결과는 1이며, 같을 때는 0입니다.

int a = 5;     // 0101 (이진)
int b = 3;     // 0011 (이진)
int result = a ^ b; // 0110 (이진) -> 6 (십진)

4. 비트 NOT 연산자 (~)

비트의 값을 반전시킵니다 (0은 1로, 1은 0으로).

int a = 5;     // 0101 (이진)
int result = ~a;   // 1010 (이진의 반전) -> -6 (2의 보수 표현으로 인해)

5. 왼쪽 시프트 연산자 (<<)

지정된 비트 수만큼 왼쪽으로 이동시킵니다. 오른쪽에는 0이 채워집니다.

int a = 5;     // 0101 (이진)
int result = a << 1; // 1010 (이진) -> 10 (십진)

6. 오른쪽 시프트 연산자 (>>)

지정된 비트 수만큼 오른쪽으로 이동 시킵니다. 양수의 경우 왼쪽에 0이 채워지며, 음수의 경우 왼쪽에 1이 채워집니다.

int a = 5;     // 0101 (이진)
int result = a >> 1; // 0010 (이진) -> 2 (십진)

조건문

1. if 문

if 문은 주어진 조건이 참일 경우 해당 코드 블록을 실행합니다.

int a = 10;

if (a > 5) {
    printf("a is greater than 5\n");
}

2. if-else 문

if-else 문은 주어진 조건이 참이면 if 블록을, 그렇지 않으면 else 블록을 실행합니다.

int a = 3;

if (a > 5) {
    printf("a is greater than 5\n");
} else {
    printf("a is not greater than 5\n");
}

3. if-else if-else 문

여러 조건을 확인할 때 사용됩니다. 조건은 위에서부터 차례대로 평가되며, 첫 번째로 참인 조건의 코드 블록만 실행됩니다.

int a = 5;

if (a > 10) {
    printf("a is greater than 10\n");
} else if (a == 5) {
    printf("a is 5\n");
} else {
    printf("a is not greater than 10 and a is not 5\n");
}

//출력 
//a is 5

4. switch 문

특정 변수의 값에 따라 다양한 경우를 처리하려면 switch 문을 사용합니다. 각 경우는 case 키워드로 표시되며, default 키워드는 주어진 경우와 일치하지 않는 경우에 실행됩니다.

int day = 3;

switch (day) {
    case 1:
        printf("Monday\n");
        break;
    case 2:
        printf("Tuesday\n");
        break;
    case 3:
        printf("Wednesday\n");
        break;
    default:
        printf("Another day\n");
}

//출력 
//Wednesday

//만약 break 문이 없다면 아래와 같음
//Wednesday
//Another day

break 문은 현재의 switch 블록을 종료하고, 프로그램의 제어를 switch 문 이후의 코드로 이동시킵니다. break를 생략하면, 해당 case 이후의 모든 코드(다음 casedefault 포함)가 실행됩니다. 이를 통해 여러 case에 대해 동일한 코드를 실행하도록 할 수 있습니다.

반복문

1. for 문

for 문은 초기화, 조건, 그리고 반복 후 작업의 세 부분으로 구성되며, 주로 명확한 반복 횟수를 알고 있을 때 사용됩니다.

for (int i = 0; i < 10; i++) {
    printf("%d ", i);
}

//출력
//0 1 2 3 4 5 6 7 8 9

위의 코드는 0부터 9까지의 숫자를 출력합니다.

반복문에서 전치연산과 후위연산, ++i, i++

1. 전위 연산 (++i)

전위 연산에서는 변수의 값을 먼저 증가시킨 후 그 값을 평가합니다. 예를 들어:

int i = 5;
int j = ++i;  // i는 먼저 증가하고, 그 후에 그 값을 j에 할당합니다.

이 경우 i는 6이 되고, j도 6이 됩니다.

2. 후위 연산 (i++)

후위 연산에서는 변수의 현재 값을 먼저 평가한 다음에 값을 증가시킵니다. 예를 들어:

int i = 5;
int j = i++;  // i의 현재 값을 먼저 j에 할당하고, 그 후에 i를 증가시킵니다.

이 경우 j는 5가 되고, i는 6이 됩니다.

for 문에서의 사용

for 문에서 전위 연산과 후위 연산을 사용하는 것은 일반적으로 큰 차이가 없습니다. 왜냐하면 for 문의 증감 부분에서는 연산의 결과만 중요하고, 그 값의 평가 순서는 중요하지 않기 때문입니다.

for (int i = 0; i < 10; ++i) {
    printf("%d\n", i);
}

for (int i = 0; i < 10; i++) {
    printf("%d\n", i);
}

는 동일한 출력을 합니다.

그러나, for 문의 제어 부분에서 후위 연산과 전위 연산을 혼합하게 되면 예상치 못한 동작을 할 수 있으므로 주의해야 합니다.

2. while 문

while 문은 주어진 조건이 참인 동안 코드 블록을 계속 실행합니다.

int i = 0;
while (i < 10) {
    printf("%d ", i);
    i++;
}

//출력
//0 1 2 3 4 5 6 7 8 9

이 코드는 for 문의 예제와 동일한 출력을 생성합니다.

3. do-while 문

do-while 문은 코드 블록을 먼저 한 번 실행한 다음, 주어진 조건이 참인 동안 계속 실행합니다.

int i = 0;
do {
    printf("%d ", i);
    i++;
} while (i < 10);

//출력
//0 1 2 3 4 5 6 7 8 9

이 코드 역시 위의 두 예제와 동일한 출력을 생성합니다.

do-while의 주요 특징은 조건의 참/거짓 여부와 관계없이 코드 블록이 최소 한 번은 실행된다는 것입니다.

4. break와 continue

반복문 내에서 사용될 수 있는 추가적인 제어문으로 breakcontinue가 있습니다.

  • break: 현재의 반복문을 종료하고, 반복문 뒤의 코드를 실행합니다.
  • continue: 반복문의 나머지 부분을 건너뛰고 다음 반복으로 넘어갑니다.

예제:

for (int i = 0; i < 10; i++) {
    if (i == 5) {
        break;  // i가 5일 때 반복문을 종료합니다.
    }
    printf("%d ", i);
}

//출력
//0 1 2 3 4
for (int i = 0; i < 10; i++) {
    if (i % 2 == 0) {
        continue;  // i가 짝수일 때, 나머지 코드를 건너뜁니다.
    }
    printf("%d ", i);
}

//출력
//1 3 5 7 9

배열

배열의 포인터 기본

정수 배열이 있을 때:

**int arr[5] = {1, 2, 3, 4, 5};**

arr는 이 배열의 첫 번째 원소인 arr[0]을 가리키는 포인터로 사용될 수 있습니다.

**int *p = arr; // arr는 &arr[0]의 주소와 동일합니다.**

p는 이제 arr[0]의 주소를 가지게 됩니다. 이를 이용하여 배열의 원소에 접근할 수 있습니다:

**printf("%d\n", *p);   // 1 출력, arr[0]의 값
printf("%d\n", *(p+1)); // 2 출력, arr[1]의 값**

포인터 연산

배열을 가리키는 포인터를 사용하면 포인터 연산을 통해 배열의 원소에 접근할 수 있습니다.

**p++; // 이제 p는 arr[1]을 가리킵니다.**

다차원 배열과 포인터

다차원 배열도 포인터로 표현할 수 있습니다. 예를 들어 2차원 배열의 경우:

**int matrix[2][3] = {
    {1, 2, 3},
    {4, 5, 6}
};**

포인터를 사용하여 이 배열을 가리키려면 포인터의 타입도 맞게 설정해야 합니다:

**int (*p_matrix)[3] = matrix;**

주의사항

  • 배열의 이름은 포인터처럼 동작하지만, 그것은 실제 포인터 변수가 아닙니다. 따라서 배열의 이름에 다른 주소를 할당할 수 없습니다.
  • 포인터 연산은 해당 포인터의 타입에 따라 다릅니다. 예를 들어, int 포인터의 경우 p+1은 주소에서 sizeof(int)만큼 증가한 위치를 가리킵니다.
  • 포인터를 사용하여 배열 외부의 메모리에 접근하는 것은 위험하며, 이로 인해 정의되지 않은 행동이 발생할 수 있습니다.
#include<stdio.h>
int a[5][5];
int main() {
	int i, j, k = 1;
	for (i = 0; i < 5; i++) {
		for (j = 4; j >= 0; j--) {
			a[i][j] = k;
			k++;
		}
	}
  
	for (i = 0; i < 5; i++) {
		for (j = 0; j < 5; j++) {
			printf("%3d", a[i][j]);
		}
		printf("\n");
	}
	return 0;
}

함수

C언어에서 함수에 대한 기본적인 아이디어와 사용법을 살펴보겠습니다.

함수의 기본 구조:

반환형 함수이름(매개변수1, 매개변수2, ...)
{
    // 함수 본체
    // 수행할 작업

    return 반환값;
}
  • 반환형: 함수가 반환하는 값의 데이터 타입입니다. 함수가 값을 반환하지 않는 경우, void를 사용합니다.
  • 함수이름: 함수를 호출할 때 사용되는 이름입니다.
  • 매개변수: 함수에 전달되는 입력값입니다. 여러 매개변수를 쉼표(,)로 구분하여 나열할 수 있습니다.
  • return: 함수에서 값을 반환할 때 사용됩니다. void 타입의 함수는 반환값이 없으므로 return문 없이 종료될 수 있습니다.

함수 예제:

#include <stdio.h>// 함수 정의
int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(5, 3);  // 함수 호출
    printf("The sum is: %d\n", result);
    return 0;
}

위의 예제에서 add 함수는 두 정수를 인수로 받아 그 합계를 반환합니다. main 함수에서는 add 함수를 호출하여 결과를 얻고 출력합니다.

함수의 장점:

  1. 모듈화: 함수를 사용하면 코드를 여러 작은 부분으로 나눌 수 있으므로 프로그램의 구조를 더 잘 이해할 수 있습니다.
  2. 재사용성: 한 번 정의된 함수는 프로그램의 여러 위치에서 여러 번 호출될 수 있습니다.
  3. 코드의 유지 및 디버깅: 함수를 사용하면 코드의 특정 부분에 문제가 발생할 경우 해당 함수만 수정하면 되므로 유지 관리와 디버깅이 쉽습니다.
  4. 추상화: 함수는 복잡한 작업을 숨기고 그 작업을 수행하는 간단한 인터페이스를 제공하여 코드의 복잡성을 줄입니다.

변수

변수의 종류:

  1. 지역 변수 (Local Variables)

    • 함수 내에서 선언된 변수입니다.
    • 해당 함수의 실행 도중에만 사용되며, 함수가 종료되면 메모리에서 해제됩니다.
    • 다른 함수에서는 접근할 수 없습니다.
    #include <stdio.h>
    
    void displayMessage() {
        // 이 함수 내에서만 사용할 수 있는 지역 변수
        int localVar = 100;
        printf("Local variable value: %d\n", localVar);
    }
    
    int main() {
        displayMessage();
    
        // 여기에서는 localVar에 접근할 수 없습니다.
        // 다음 줄은 오류를 발생시킵니다.
        // printf("%d", localVar);
    
        // 이것은 main 함수의 지역 변수입니다.
        int anotherLocalVar = 200;
        printf("Another local variable value: %d\n", anotherLocalVar);
    
        return 0;
    	}
  2. 전역 변수 (Global Variables)

    • 함수 외부에서 선언된 변수로, 프로그램의 어디서나 접근할 수 있습니다.
    • 프로그램의 시작부터 종료까지 메모리에 남아 있습니다.
    • 과도한 사용은 코드의 복잡성을 증가시킬 수 있으므로 주의해야 합니다.
  3. 정적 변수 (Static Variables)

    • static 키워드로 선언된 변수입니다.
    • 지역 변수로 선언될 경우, 해당 함수가 실행될 때마다 초기화되지 않습니다.
    • 전역 변수로 선언될 경우, 다른 소스 파일에서 접근할 수 없게 됩니다.
    • 프로그램의 실행 동안 메모리에 남아 있습니다.
  4. 레지스터 변수 (Register Variables)

    • register 키워드로 선언된 변수입니다.
    • vCPU의 레지스터에 저장되도록 요청하는 변수로, 빠른 접근 속도를 필요로 할 때 사용됩니다.
    • 컴파일러는 레지스터 변수를 실제로 레지스터에 저장할지 결정합니다.
  5. 외부 변수 (Extern Variables)

    • extern 키워드로 선언된 변수입니다.
    • 다른 소스 파일에 정의된 전역 변수를 참조하기 위해 사용됩니다.
    • 실제 메모리 할당은 extern 키워드가 없는 위치에서 이루어집니다.

예제:

#include <stdio.h>int globalVar = 10;         // 전역 변수

void function1() {
    int localVar = 20;      // 지역 변수
    static int staticVar = 30; // 정적 변수

    printf("Global variable: %d\n", globalVar);
    printf("Local variable: %d\n", localVar);
    printf("Static variable: %d\n", staticVar);

    staticVar++;
}

int main() {
    function1();
    function1();
    return 0;
}

위의 예제에서 function1을 두 번 호출했을 때, staticVar는 값이 증가된 상태로 유지되는 것을 확인할 수 있습니다.

0개의 댓글