C study

자훈·2024년 3월 9일
0

C++ / C study

목록 보기
8/8
post-thumbnail

📌 C를 하는 이유

C++로 수업을 하는게 아니라 C로 진행하기 때문에, 익숙해지기 위해 블로그를 작성하고자 한다.

C++이 C 코드에 무언가를 더하여 만들어진 것이기 때문에, 내가 알고있는 기본적인 개념들은 큰 차이가 없으나 입력과 출력을 하는 방식이 매우 다르다는 것을 알고 있기 때문에 이 부분을 우선적으로 정리하고 가려고 한다. 그리고 자료구조를 공부하면서 포인터에 대한 부분이 헷갈리면 안되기 때문에 다시 한 번 배열과 포인터의 개념을 짚고 넘어갈려고 한다.

🔍 printf, scanf

C++을 해보면 알지만, cout과 cin을 통해 입력과 출력 명령을 실행하는 부분이 굉장히 단순하고 깔끔하다. 어려운 부분도 딱히 없다. 하지만 c의 경우 어떤 자료형을 입력받을지, 어떤 자료형을 출력할지 내가 일일이 선언해야 하며, 줄바꿈 선언 \n 또한 그 문장 안에 입력해주어야 한다. ""안에서 명령어를 입력하면 실행되며, 어떤 종류들이 있는지 우선적으로 알아보려고 한다.

#include <stdio.h>

int main(void){
	char ch;
    short sh;
   	int i ;
    long ll;
    float fl;
    double du;
    
    printf("char 형 변수 입력: ");
    scanf("%c", &ch);
    
    printf("short 형 변수 입력: ");
    scanf("%hd", sh);
    printf("int 형 변수 입력: ");
    scanf("%d", i);
    printf("long 형 변수 입력: ");
    scanf("%ld", ll);
    
    printf("float 형 변수 입력: ");
    scanf("%f", fl);
    printf("double 형 변수 입력: ");
    scanf("%lf", du);
    
    printf("char : %c , short : %d , int : %d ", ch, sh, i);
    printf("long : %ld , float : %f, double : %f \n", lo, fl, du);
   return 0;
}

이렇게 작성하여서 테스트를 해본다면 ...

짜잔! 이렇게 나온다. char1byte를 차지하니까, 한글을 넣으면 오류가 발생한다. (한글은 2byte!!)

그리고 추가적으로 알게 된 건, cin의 경우 입력을 받을 때, 참조기호를 사용하지 않았는데 scanf의 경우 참조기호를 사용한다는 점이다. 변수의 주소를 전달하여 값을 저장해야하기 때문이다

포인터 복습

#include <stdio.h>

int main(){
  int a;
  int b;
  const int *pa = &a; //int 변수형 값을 바꾸지 마라 
  int* const pb = &a; //주소값을 바꾸지 마라 
  const int* const pc = &a; // 아무것도 바꾸지 못함 
  
  *pb = 3;
  pa = &b;
  return 0;
}

포인터로 배열에 접근하는 코드이다. 이 방식을 이해하기 위해서는 주소 값의 변화가 어떤 기준으로 나타나는지, 어떤 주소값을 담고있는 것인지를 이해할 필요가 있다. 자세한 내용은 배열 섹션에서 다루니, 그것을 이해하고 올라와서 확인해보면 될 것이다.

핵심은 그 내용을 바탕으로 포인터 연산이 불가능하지 않다는 것이다.

#include <stdio.h>

int main(){
  int arr[10] = {100, 98, 97, 95, 89, 76, 92, 96, 100, 99};
  int *parr = arr;
  int sum = 0;

  while(parr - arr <= 9){
    sum += *parr;
    parr++;
  }
  printf("내 시험 점수의 평균 : %d\n", sum / 10);
  return 0;

포인터의 포인터

개념 요약 이해

포인터의 개념을 정확하게 이해하고 있어야 아래 포인터의 포인터 예제를 이해할 수 있다. 정확하게 이해하려고 노력해보자.

우선 포인터는 변수의 메모리 주소를 저장하는 변수이다. 메모리를 같은 위치에 할당을 받는 것이 아니라, 다른 메모리에 할당을 받지만, 같은 주소값의 데이터를 가리켜서 보는 것이 맞는 이해인 것 같다.

포인터 변수를 이용하여 해당 주소에 저장된 값을 변경하거나 가져올 수 있다.

a라는 변수와 b라는 포인터 변수가 있다면, int 형으로 선언하면, 서로 다른 주소를 가지고 있지만, b라는 것이 a의 레퍼런스를 가리킬 수 있고, 그 레퍼런스는 레퍼런스 b에 저장된다는 것이다.

이 개념을 가지고 아래 코드를 실행한 후 나오는 주소 값을 이해하면 된다.

#include <stdio.h>

int main(){
 int a;
 int *pa; 
 int **ppa;

 pa = &a; 
  //pa는 a의 주소값을 가지고 있음. 
  //정확히는 &pa의 위치에 &a의 정보를 담고 있는 것임. 
  
 ppa = &pa; 
  //ppa의는 pa의 주소값을 가지고 있음. 
  //위와 같은 원리로 작용하는 것 같음. 

 a = 3;
  printf("a : %d // *pa : %d // **ppa : %d \n", a, *pa, **ppa);
  printf("&a : %p // pa : %p // *ppa : %p \n", &a, pa, *ppa);
  printf("&pa : %p // ppa : %p \n", &pa, ppa);
  printf("&ppa : %p \n", &ppa);
}

결과는 이렇게 나온다.

결과 값에 대한 해석이 바로 이루어지지 않더라도, 각 행의 결과 값이 왜 일치하는지 거슬러, 개념 요약 이해를 보고 이해해보도록 노력하자.

🗂️ 배열

배열의 원리를 아는 것은 굉장히 중요하다. 단순한 데이터를 담아두는 공간으로 생각할 수도 있지만, 자칫하면 메모리를 너무 과도하게 사용하는 결과를 초래할 수도 있기 때문이다. 포인터와 배열이 같이가는 이유는 배열은 기본적으로 변수 주소의 시작점을 담고 있다.

int 형 자료를 예로 들어보자. int의 경우 4칸의 바이트를 차지하는 것은 이제 많이 들어서 알고 있을 것이다.

#include <stdio.h>

int main(){
 int arr[10];
 for(int i = 0; i < 10; i++){
    printf("배열의 주소는: %p\n", &arr[i]);
  }
  return 0;
}

이렇게 선언된 배열은...


주소값이 4차이가 나는 것을 확인할 수 있을 것이다. 이전에 C++ 포인터하면서도 얘기했지만, 주소의 시작점을 담고 있기 때문에, 주소값의 차이가 자료형의 바이트 만큼 차이가 나게 되는 것이다.

간단한 array를 활용한 코드를 보자.

#include <stdio.h>

int arr[10];

void sorting(int arr[10]) {
    int i, j, temp;
    for (i = 0; i < 10 - 1; i++) {
        for (j = 0; j < 10 - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                temp = arr[j];
                arr[j] = arr[j + 1];
                arr[j + 1] = temp;
            }
        }
    }
}

int main(void) {
  
  int i, ave = 0;
  for(int i = 0; i < 10; i++){
    printf("%d 학생들의 성적은 ?: ", i + 1);
    scanf("%d", &arr[i]);
  }

  for(int i = 0; i < 10; i++){
    ave += arr[i];
  }
  ave /= 10;
  sorting(arr);

  printf("평균은 %d \n입니다.", ave);
  for(int i = 0; i < 10; i++){
    printf("%d 학생의 성적은 %d 입니다", i + 1, arr[i]);
    if(arr[i] > ave){
      printf("합격입니다 \n");
    }
    else{
      printf("불합격입니다 \n");
    }
  }
  return 0;
}

정렬 함수인 sorting의 경우 버블 정렬 알고리즘으로 만든 것으로, 글쓴이분이 제시한 간단한 문제이므로 같이 만들어 적어보았다.
(참고로 버블정렬 알고리즘은 구현이 쉬운대신 성능이 좋은 것은 아니다)

array의 경우 자료의 시작 주소값을 가지고 접근하는 것이기 때문에, scanf를 사용할 때 & 레퍼런스의 사용은 굳이 하지 않아도 된다.

#include <stdio.h>
#include <math.h>

#define Size 201
/* size에 따라서 소수 출력하도록 만든 에라토스테네스의 체 */ 

int main(void){
  int a[Size] = {0};
  int i, j;
  for(int i = 2; i <= sqrt(Size); i++){ /* Size의 제곱근 이하에서 소수만 걸러냄. 그 배수랑 같이 걸러내는 반복문 */
    if(a[i] == 0){
      for(int j = 2; i * j < Size; j++){
        a[i * j] = 1;
      }
    }
  }
  for(int i = 2; i < Size; i++){
    if(a[i] == 0){ printf("%d\n", i);}
  }
  return 0;
}

에라토스테네스의 체도 구현이 가능하다.
제곱근 이하의 소수의 배수들을 지워나가며 소수를 찾는 방법이다. 수가 커지면 물론 다른 방법이 좋지만, 적당한 수에서는 간단하게 구현가능한 코드이다.

🗂️ 2차원 배열

1차원 배열을 arr[x]의 방식으로 접근했다면, 이것들이 모여 층을 이루는 좌표로 접근하는 것이 2차원 배열 arr[x][y] 이다.

이해하기 쉽게 쓰자면
arr[가로, 행][세로, 열]이므로 공간을 상상해보면 되겠다.
간단하게 아래에 arr[3][2] 자료를 시각화해서 보여주겠다. 공간상에 아래와 같이 위치해있다고 생각해도 되고,

arr[0][0] arr[0][[1]
arr[1][0] arr[1][[1]
arr[2][0] arr[2][[1]

선형 구조라고 생각해도 된다.

arr[0][0]
arr[0][1]
arr[1][0]
arr[1][1]
arr[2][0]
arr[2][1]

아래는 간단히 성적을 입력하고, 출력하는 코드이다. y값을 조정하며 다른 자료들을 저장하기 편리한 이유를 직접만들며 확인해보자.

#include <stdio.h>

int score[3][2]; //배열명 [가로 행][세로 열]
int i, j; 

int main(void) {
  for(int i = 0; i < 3; i++){
    for(int j = 0; j < 2; j++){
      if(j == 0){
        printf("%d 번 째 학생의 국어점수: ",i + 1);
        scanf("%d", &score[i][j]);
      }
      else if(j == 1){
        printf("%d 번 째 학생의 수학점수: ", i + 1);
        scanf("%d", &score[i][j]);
      }
    }
  }
  for(int i = 0; i < 3; i++){
    printf("%d 번 학생의 국어점수: %d, 수학 점수: %d \n", i + 1, score[i][0], score[i][1]);
  }
  return 0;
}

선형 1차원 배열일 경우에는 위와 같이 section이 나뉘게 되는 자료일 경우에, 접근함과 저장에 있어 불편함이 있지만

2차원 배열을 사용하여 행과 열을 통해 분리 저장함으로써 접근과 저장이 모두 용이해진다.

혹시나 포인터로는 어떻게 나타나는지 궁금하다면, 아래의 예시 코드를 통해 확인해보길 바란다.

#include <stdio.h>

int main() {
    int arr[3][4] = {
        {10, 20, 30, 40},
        {50, 60, 70, 80},
        {90, 100, 110, 120}
    };

    printf("2D Array Address:\n");
    for (int i = 0; i < 3; i++) {
        for (int j = 0; j < 4; j++) {
            printf("arr[%d][%d]: %p, Value: %d\n", i, j, &arr[i][j], arr[i][j]);
        }
    }

    return 0;
}

0개의 댓글