Chapter.6 매크로와 선행처리기

  • 선행처리기와 매크로
    • 선행처리기 : 컴파일 이전의 처리
         
    • 선행처리 명령대로 소스코드의 일부를 단순 치환한다 이때 #을 붙이고, 세미콜론은 붙이지 않는다
  • 대표적인 선행처리 명령문
    • #define PI 3.141592
      • #define(지시자), PI(매크로), 3.141592(매크로 몸체) 라 표현한다
      • 뜻은 매크로PI를 매크로몸체 3.14~로 치환해라 라는 뜻
    • #define SQUARE(X) X*X
      • 함수와 유사한 메크로(functuon-like macro) 즉 매크로 함수 이다
      • SQUARE(X) 등장시 X*X로 치환하라는 뜻이다 ex) SQUARE(123) -> 123*123
    • 잘못된 매크로 정의
      • ex) SQUARE(3+2) 의 경우 3+2*3+2 = 11이 출력된다(25가 아니라)
      • 이때 3+2에 괄호를 쳐주면 (3+2)*(3+2) = 25로 정상적인 값이 나오게 된다
      • 따라서 매크로 몸체에 괄호를 쳐준다 SQUARE(X) ( (X)*(X) ) // 매개변수에 괄호, 전체 함수에 괄호
    • 두줄에 걸쳐 매크로 정의하기
      • define SQUARE(X) \
      • ((X)*(X))
    • 매크로 정의 시, 먼저 정의된 매크로도 사용 가능!
    • 매크로 함수의 장점
      1. 일반 함수에 비해 실행속도가 빠르다
      2. 자료형에 따라 별도로 함수를 정의하지 않아도 된다
    • 매크로 함수의 단점
      1. 정의하기가 정말로 까다롭다
      2. 디버깅 하기가 쉽지 않다
    • 따라서 매크로 할 함수의 특징
      1. 작은 크기의 함수
      2. 호출의 빈도수가 높은 함수
  • 조건부 컴파일을 위한 매크로
    • #if... ~ #endif = 참이라면
      • #if 뒤에 오는 인자가 참이면 #endif 전까지 있는 코드 컴파일, 참이 아니면 #endif 전까지 있는 코드 생략!
    • #ifdef... ~ #endif = 정의 되었다면
      • #ifdef 뒤에 오는 매크로가 정의되어 있으면 #endif 전까지 있는 코드 컴파일, 정의되지 않으면 #endif 전까지 있는 코드 생략!
    • #ifndef... ~ #endif = 정의 되었다면
      • #ifndef 뒤에 오는 매크로가 정의되지 않으면 #endif 전까지 있는 코드 컴파일, 정의되어 있으면 #endif 전까지 있는 코드 생략!
    • #else의 삽입 
      • #if, #ifdef, #ifndef 에 사용 가능하다
      • if문과 동일하게 사용된다
    • #elif의 삽입
      • #if 문에만 사용 가능하고 여러번 사용 가능하다
      • if문의 elseif와 동일하게 사용된다
  • 매개변수의 결합과 문자열화
    • 문자열 내에서는 매크로의 매개변수 치환이 발생하지 않는다
      • ex) define ST_job(A, B) "A의 직업은 B입니다" 선언후 ST_job(이중현, 학생) 실행시  A의 직업은 B입니다 가 치환된다
    • 따라서 문자열 내에서 매크로를 치환하려면 # 연산자를 사용면 된다
      • ex) define ST_job(A, B) #A "의 직업은" #B "입니다" 는 이중현의 직업은 학생입니다 로 치환된다
    • 필요한 형태대로 단순하게 결합하기 : 매크로 ##연산자
      • ex) define STNUM(Y, S, P) Y ## S ## P 선언후 STNUM(18, 11, 21) 은 181121 로 치환된다

Chapter.7 파일의 분할과 헤더파일의 디자인

  • 파일의 분할
    • 컴파일러는 파일 단위로 컴파일을 진행한다, 따라서 다른 파일의 정보를 참조하여 컴파일을 진행하지 않는다
    • 외부에 선언 및 정의되어 있다면 컴파일러에게 미리 알려줘야 한다!!
      • extern int num; -> 외부에 int num 이라는 변수가 선언되어 있다 라고 알려준다
    • 다른 파일에서 접근을 못하게 막고 싶다면 static을 선언하면 된다 (static int num;) -> 접근 범위를 파일 내부로 제한한다
  • 헤더파일의 디자인과 활용
    • #include 지시자의 의미 = 이 문장의 위치에 해당 파일에 저장된 내용을 가져다 놓으세요
    • 헤더파일을 include 하는 두가지 방법
      1. #include <헤더파일 이름> = C의 표준에서 정의하는, 기본적으로 제공되는 헤더파일을 포함시킬때 사용
      2. #include "헤더파일 이름" = 프로그래머가 정의한 헤더파일을 포함시킬때 사용
    • 헤더파일에 담겨야 하는 내용
      • extern 이 선언되어야 하는 함수, 변수를 미리 선언해 놓는다
      • 구조체를 저장해 사용할 경우 헤더파일에 저장해서 모두 사용할 수 있게 한다
      • 헤더파일의 중복 삽입 문제
      • 다음과 같은 경우 구조체 div가 두번 삽입된 형태가 되어 컴파일 에러가 발생한다
        • 헤더파일의 중복 삽입이 문제가 아니라 구조체의 중복 선언이 문제
        • 이때 #ifndef __STDIV_H__ 를 사용해 조건부 컴파일을 사용하면 된다 (크게 의미없는 매크로를 사용한다)
        • ex) stdiv2.h 헤더파일 구현시
          • #ifndef __STDIV2_H__
          • #define__ STDIV2_H__
          • ~~ ~~ ~~
          • #endif
        • 를 사용해서 중복 삽입이 됬을 경우 컴파일이 되지 않게 선언해주면 된다(중복 선언은 되지만, 안에 내용이 없는 상태로 치환되기 때문에 문제가 발생하지 않음)

 

 

 

 

 

 

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

C언어의 깊은 이해_3  (0) 2022.02.10
C언어의 깊은 이해_2  (0) 2022.02.09
C언어의 깊은 이해_1  (0) 2022.02.07
포인터와 배열의 시작_4  (0) 2022.02.07
포인터와 배열의 시작_3  (0) 2022.02.05

Chapter.4 파일 입출력

  • 파일과 스트림(Stream), 그리고 기본적인 파일의 입출력
    • 파일에 저장되어 있는 데이터 읽기
      • 파일에 저장되어 있는 데이터를 참조하기 위해서는 파일과 프로그램 사이의 통로를 만들어야 한다
      • 이때 프로그램과 파일을 잇는 통로 역활을 하는것이 Stream 스트림 이다
      • 스트림의 진행방향은 꼭 한 방향이다
             
      • 이 스트림의 형성은 운영체제의 몫이고 우리는 스트림 생성을 요구하면 된다
    • fopen함수 호출을 통한 파일과의 스트림 형성과 FILE 구조체
      • fopen 함수는 스트림을 형성(요구) 할 때 호출하는 함수이다
        • FILE * fopen(const char* filename, const char * mode); // 성공시 FILE 구조체의 변수 주소 값, 실패시 NULL
        • 첫 인자는 스트림을 형성할 파일의 이름, 두번째 인자로는 스트림 종류에 대한 정보
      • fopen 함수 호출 -> FILE 구조체 변수 형성 -> 구조체 변수에 파일에 대한 정보가 담긴다 즉 파일을 가리키는 지시자 역활
    • 입력 스트림과 출력 스트림의 생성
      • 출력 스트림의 형성을 요구하는 fopen / FILE * fp = fopen("data.txt", "wt"); // 파일 data.txt 과 wt모드로 스트림 형성
      • 입력 스트림의 형성을 요구하는 fopen / FILE * fp = fopen("data.txt", "rt"); // 파일 data.txt 과 rt모드로 스트림 형성
        • cf) 파일과 스트림이 형성된 경우를 파일이 열렸다(개방, 오픈) 이라고도 표현한다
    • 파일에 데이터 입력하기
      1. 출력 모드로 파일 열기 / FILE * fp = fopen("data.txt", "wt"); // 이때 파일에 경로 지정 가능!
      2. 파일에 데이터 입력하기 / fput('A', fp); // fput의 두번째 인자인 stream 에 파일 구조체 포인터를 입력해 준다
      3. 스트림의 연결을 종료해 주기 / fclose(fp);
    • 스트림의 소멸을 요청하는 fclose 함수
      • int fclose(FILE * stream); // 성공시 0, 실패시 EOF 반환
      • 파일을 닫아주는 이유
        1. 운영체제가 할당한 자원의 반환
        2. 버퍼링 되었던 데이터의 출력
      • 이때 출력버퍼의 데이터를 비우기 위해선 fflush 함수를 사용해도 된다 
      • 입력 버퍼의 경우 데이터를 비우면 소멸되기 때문애 fflush 함수를 사용할 수 없다!!!
    • 파일로부터 데이터 입력받기
      1. 입력 모드로 파일 열기 / FILE * fp = fopen("data.txt", "rt"); 
      2. 파일에서 데이터 입력받기 / int ch = fgetc(fp);
      3. 스트림의 연결을 종료해 주기 / fclose(fp);
  • 파일의 개방 모드(Mode)
    • 스트림을 구분하는 기준1: 읽기위해? or 쓰기위해?
           
    • 스트림을 구분하는 기준2: 텍스트 모드와 바이너리 모드 
      • 텍스트 파일과 바이너리 파일의 구분
        1. 텍스트 파일 : 문자 데이터 ex) 도서목록, 물품내역
        2. 바이너리 파일 : 컴퓨터가 인식하는 데이터 ex) 영상파일, 음원파일
      • 개행문자 \n 에 관한 부가설명
        • C언어에서는 \n을 개행문자로 인식하지만 모든 컴퓨터 환경이 그렇진 않다
          • ex) Winodw = \r\n, MAC = \r, Unix 계열 = \n
          • 이때 텍스트 모드로 파일을 열면 자동으로 변환해준다
      • 텍스트 모드, 바이너리 모드의 구분
        모드(mode) 스트림의 성격
        b 바이너리 모드
        t 텍스트 모드
        ' '(선언X) 텍스트 모드
  • 파일 입출력 함수의 기본
    • 전에 학습한 파일 입출력 함수를 이용한 파일 입출력
      1. int fputc(int c, FILE * stream) // 문자 출력
      2. int fgetc(FILE * stream) // 문자 입력
      3. int fputs(const char * s, FILE * stream) // 문자열 출력
      4. char * fgets(char * s, int n, FILE * stream) // 문자열 입력
        • cf) 문자열을 파일에 저장할 때는 널문자로 구분 X, 개행문자(\n)으로 문자열을 구분하게 된다
        • 따라서 문자열을 읽어들이는 함수를 호출하면 개행문자(\n)을 만날때 까지 게속 읽어들인다
    • feof함수 기반의 파일복사 프로그램
      • 파일의 마지막을 확인하는 feof함수
        • int feof(FILE * stream); // 파일의 끝에 도달하면 0, 아닐경우 다른 값 반환
    • 바이너리 데이터의 입출력: fread, fwrite
      • size_t fread(void * buffer, size_t size, size_t count, FILE * stream); // 성공시 전달인자 count, 실패시 작은값
        • ex) int buf[12];, fread((void*)buf, sizeof(int), 12, fp); // 성공시 12반환, 실패시 12보다 작은 값 반환
        • sizeof(int)크기의 데이터 12개를 fp로부터 읽어들여 배열 buf에 저장 
      • size_t fwrite(void * buffer, size_t size, size_t count, FILE * stream); // 성공시 전달인자 count, 실패시 작은값
        • ex) int buf[7] = { ~ };, fwrite((void*)buf, sizeof(int), 7, fp); // 성공시 7반환, 실패시 7보다 작은 값 반환
        • sizeof(int)크기의 데이터 7개를 buf로부터 읽어들여 fp에 저장
  • 텍스트 데이터와 바이너리 데이터를 동시에 입출력 하기
    • 서식에 따른 데이터 입출력: fprintf, fscanf
      • printf, scanf 함수와 동일하게 사용한다
      • fprintf(fp, "%s %c %d", name, sex, age); // name문자열, sex문자, age바이너리 데이터를 파일에 출력함
      • fscanf(fp, "%s %c %d", name, &sex, &age); // name문자열, sex문자, age바이너리 데이터를 파일에서 읽어들임
    • 텍스트와 바이너리 데이터의 집합체인 구조체 변수의 입출력
      • 구조체로 묶여있는 여러개의 데이터들을 하나의 바이너리 데이터로 인식시켜 입출력 시킬 수 있다
      • fread, fwrite 함수 사용
  • 임의 점급을 위한 '파일 위치 지시자'의 이동
    • 파일 위치 지시자란?
      • FILE 구조체의 맴버 중 하나로, 파일의 위치 정보를 저장하고 있다
      • fgets, fputs 를 사용하면 다음 문자열을 입출력 할 수 있게 파일 내의 위치를 참조하고 있다
    • 파일 위치 지시자의 이동: fseek
      • 파일 지시자를 이동시킬 때는 fseek 함수를 호출한다
      • int fseek(FILE * stream, long offset, int wherefrom); // 성공시 0, 실패시 0이 아닌 값 반환
        • stream으로 전달된 파일 위치 지시자를 wherefrom 으로부터 offset 바이트 만큼 이동시켜라!
               
      • 현재 파일 위치 지시자의 위치는?: ftell
        • 현재의 파일 위치 지시자의 정보를 확인하고 싶다면 ftell 함수를 호출하면 된다
        • long ftell(FILE * stream) // 파일 위치 지시자의 위치 정보 반환
          • 첫번째 바이트를 가리킬 경우 0을 반환하고, 세번째 바이트를 가리킬 경우 2를 반환한다.

Chapter.5 메모리 관리와 메모리의 동적할당

  • C언어의 메모리 구조
    • 메모리의 구성
         
    1. 코드영역(Code Area) : 실행할 프로그램의 코드가 저장되는 메모리 공간
    2. 데이터 영역(Data Area) : 전역변후와 static으로 선언되는 static 변수가 할당되는 공간(프로그램 종료시 까지 유지)
    3. 힙 영역(Heap Area) : 프로그래머가 원하는 시점에 할당하여 사용하고, 반환할 수 있는 영역
    4. 스택 영역(Stack Area) : 지역변수와 매개변수가 할당되는 영역, 함수를 빠져나가면 소멸하게 된다!
  • 메모리의 동적 할당
    • 전역변수와 지역변수로 해결이 되지 않는 상황
      • 문자열 배열을 함수로 반환하는 상황 -> (함수내에서 선언된 배열은 지역변수 이므로 함수가 끝나서 반환한 후에 사라진다 이때 반환된 값은 주소 값이기 때문에 주소 값이 가리키는 곳에는 아무것도 없는 상태가 된다)
      • 배열을 전역변수로 선언했을 경우 -> 하나의 배열로 여러 값을 입력받을 수 없게 된다, 여려개의 전역변수를 할당하면 데이터 영역에서 프로그램에 부하를 주게 된다
      • Solution = 생성과 소멸의 시기가 지역변수나 전역변수와 다른 유형의 변수를 선언해 준다!
    • 힙 영역의 메모리 공간 할당과 해제: malloc 과 free 함수
      • void *  malloc(size_t size); // 힙 영역으로의 메모리 공간 할당 / -> 메모리 공간의 첫번째 바이트 주소 값 반환
      • void free(void * ptr) // 힙 영역에 할당된 메모리 공간 해제
        • malloc 함수는 성공시 할당된 메모리의 주소 값, 실패시 NULL 반환
      • 이 주소 값을 이용하여 데이터에 접근하면 된다 즉 포인터 연산으로 값에 접근(연산) 한다!
    • molloc 함수의 반환형이 void*(보이드 형 포인터)인 이유와 힙 영역의 접근
      • molloc함수의 인자로는 숫자(Byte) 만 주기 때문에 포인터 형에 대한 결정을 스스로 할 수 없다
      • 따라서 형변환을 사용하여 직접 포인터 형을 맞추어서 선언해 주어야 한다
        • ex) int * ptr = (int*)malloc(sizeof(int)*7) // int형 포인터로 28byte 동적할당
    • free 함수를 사용하지 않을 시 발행하는 문제점
      • 결국 프로그램을 종료하면 힙 영역에 있던 변수들은 전부 헤제가 된다
      • 그러나 프로그램의 효율적인 메모리 활용을 위해 사용해 주어야 하는것이 좋다;;
    • malloc 함수와 비슷한 colloc 함수
      • void * calloc(size_t elt_count, size_t elt_size); // 성공시 할당된 메모리 주소 값, 실패시 NULL
        • malloc과 기능은 동일하지만 인자의 개수가 다르다, 초기화 차이가 있다
        • calloc 은 elt_size(몇 바이트 크기의 블록) elt_count(개수) 를 힙 영역에 할당해 달라는 뜻
          • ex) colloc(30, sizeof(int)); = malloc(120);
          • malloc은 메모리 공간을 초기화 하지 않지만 colloc은 메모리 공간의 비트를 0으로 초기화 한다
    • 힙에 할당된 메모리 공간 확장 시 호출하는 realloc 함수
      • 원래라면 한번 할당된 공간은 확장할 수 없다
      • 그러나 힙 영역에 할당된 공간은 늘릴 수 있다
      • void * realloc(void * ptr, size_t size); // 성공 시 새로 할당된 메모리의 주소 값, 실패시 NULL
        • int * arr = (int*)malloc(sizeof(int)*3);
        • arr = (int*)realloc(arr, sizeof(int)*7);
        • 이때 메모리 공간 상태에 따라 실행 결과는 2가지로 나뉜다
          1. 기존 메모리를 할당한 영역 뒤에 확장할 영역이 넉넉한 경우
            • 전 molloc 함수의 반환 주소 값과 realloc 함수의 반환 주소 값이 동일하다
          2. 기존 메모리 영역 뒤 확장할 영역이 없는 경우
            • 전 molloc 함수의 반환 주소값과 다른 새로운 주소 값이 반환된다(새로운 곳에 공간을 새로 할당한다)

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

C언어의 깊은 이해_4  (0) 2022.03.06
C언어의 깊은 이해_2  (0) 2022.02.09
C언어의 깊은 이해_1  (0) 2022.02.07
포인터와 배열의 시작_4  (0) 2022.02.07
포인터와 배열의 시작_3  (0) 2022.02.05

Chapter.2 구조체와 사용자 정의 자료형

  • 구조체란 무엇인가?
    • 구조체의 정의
      • 구조체(structure)라는 것은 하나 이상의 변수(포인터 변수와 배열 포함)를 묶어서 새로운 자료형을 정의하는 도구이다!
      • ex) 마우스의 좌표(x,y)를 위해서는 두가지의 int형 변수가 필요하지만 이를 구조체로 하나의 변수로 이용 가능
        • struct Point { int xpos;, int ypos;}; // point라는 이름의 구조체 안에 xpos와 ypos라는 int 형 변수가 있다
        • 이때 이 자료형의 이름은 int나 double같이 사용될 수 있는 Point 이다
    • 구조체 변수의 선언과 접근
      • struct (type_name) (val_name) ; // 구조체 변수의 선언 기본 형태
      • 맨 앞에는 struct 선언이 필수로 필요하다, 그런 다음 구조체의 이름, 구조체 변수의 이름을 선언해야 한다
        • ex) struct Point pos;
             
      • 접근 시에는 (구조체 변수의 이름.구조체 멤버의 이름)의 형태로 접근해야 한다
        • ex) pos.xpos = 20;, man.name[0] = L; printf("%s \n", man.name);, scanf("%d", &(man.age));
    • 구조체 변수의 초기화
      • 선언과 동시에 초기화 가능, 배열의 초기화와 매우 유사(동일), 멤버의 순서대로 초기화할 대상을 나열하면 된다
      • ex) struct person man={"이승기", "010-1234-5678", 25} 로 초기화 시 name에 이승기, phoneNum에 번호가 저장된다
  • 구조체와 배열 그리고 포인터
    • 구조체 배열의 선언과 접근
      • 구조체 배열도 일반 변수의 배열과 동일하다
        • ex) struct point arr[4];
             
    • 구조체 배열의 초기화
      • 배열의 길이만큼 중괄호를 이용하여 초기화를 진행하면 된다!
        • ex) struct person arr[2] = { {"이승기", "010-1234-5678", 25}, {"기승이", "010-8765-4321", 52} }
    • 구조체 변수와 포인터
      • 구조체 포인터 변수도 일반 포인터 변수와 다를바 없이 선언, 사용 된다
        • ex) struct point* pptre = &pos;
      • 구조체 포인터 변수도 일반 포인터 변수와 같이 *연산이 가능하다! 
        • (*pptr).xpos=10; // pptr이 가리키는 pos의 xpos(pos.xpos)에 10 저장
        • pptr->xpos = 10; // 구조체 포인터 변수는 (* ) 연산자 대신 -> 연산자로 동일한 연산을 수행할 수 있다!
    • 포인터 변수를 구조체의 멤버로 선언하기
      • 포인터 변수 또한 구조체의 멤버가 될 수 있다
        • struct circle { double radius;, struct point* center; };  
        • 접근법(struct circle ring), -> ( *(ring.center).xpos || (ring.center)->xpos )
        • struct point { struct point* ptr; } 선언 가능! // 같은 구조체 변수의 포인터 또한 멤버로 삼을 수 있다!
    • 구조체 변수의 주소 값과 첫번째 멤버의 주소 값
      • 구조체 변수의 주소 값은 첫번째 멤버의 주소 값과 동일하다!!!

Chapter.3 구조체와 사용자 정의 자료형2

  • 구조체의 정의와 typedef 선언
    • typedef 선언
      • typedef 선언이란 기존에 존재하는 자료형의 이름에 새 이름을 부여하는 것이다
      • ex) typedef int INT; // int num = INT num 같은 뜻으로 사용된다!, INT* ptr; // 포인터도 선언 가능
      • typedef 선언은 가장 마지막에 등장하는 단어를 중심으로 이루어진다
        • ex) typedef name1 name2 name3; // name3가 'name1 name2'에 부여된 새로운 이름!
        • cf) tpyeedf 로 선언된 자료형은 대문자로 시작하는 것이 관례이다(기본 자료형과 구분짓기 위해)
    • 구조체 정의와 typedef 선언
      • typedef struct point Point; // (struct point)에 Point라는 이름을 부여!!
      • 이렇게 선언해주면 구조체를 선언할 때 struct 를 해주지 않아도 된다
        • ex) Point pos;
        • [ struct point { ~ }; typedef struct point Point ] =  [ tpyedef struct point { ~ } Point; ]
    • 구조체의 이름 생략
      • 구조체 선언시 typedef 선언을 하게되면 사실상 구조체의 이름은 별 의미를 갖지 않게 된다 따라서 생략 가능해 진다
        • ex) typedef struct { ~ } Person;
  • 함수로 구조체 변수 전달과 반환
    • 함수의 인자로 전달되고 return문에 의해 반환되는 구조체 변수
      • 함수의 인자로 구조체 변수를 전달할 수 있고, 함수의 반환(return)문으로 구조체 변수를 반환할 수 있다!
      • 이때 구조체 변수 또한 구조체 포인터 변수로 call_by_reference 로 선언이 가능하다
    • 구조체 변수를 대상으로 가능한 연산
      • 구조체 변수를 대상으로는 매우 제한된 형태의 연산만 혀용이 된다
        1. 대입연산 ( 멤버 대 멤버 복사 )
        2. &연산 ( 주소 값 반환 목적 )
        3. sizeof 연산 ( 구조체 크기 반환 )
      • 구조체 변수로 다른 연산을 필요로 한다면 새로운 함수를 별도로 선언해서 사용해야 한다
  • 구조체의 유용함에 대한 논의와 중첩 구조체
    • 구조체를 정의하는 이유
      • 구조체를 통해서 연관있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면 데이터의 표현, 관리가 용이, 합리적인 코드
      • 구조체 배열을 통해서 종류가 다른 배열을 한번에 묶어서 사용할 수 있게 된다
    • 중첩된 구조체의 정의와 변수의 선언
      • 구조체 변수도 구조체 멤버로 선언될 수 있다
  • 공용체(Union Type)의 정의와 의미
    • 구조체와 공용체의 비교
      1. typedef struct sbox { int mem1;, int mem2;, double mem3; } SBox;
      2. typedef union ubox { int mem1;, int mem2;, double mem3; } UBox;
        • printf("%d", sizeof(SBox)); // 16출력 ( 모든 멤버 합한 크기 )
        • printf("%d", sizeof(UBox)); // 8출력 ( 가장 크기가 큰 멤버 )
               
        • 이때 구조체는 각 멤버가 각각 할당되어 멤버별도 다른 주소 값을 가진다
        • 한마디로 크기가 가장 큰 멤버 면수 하나만 할당되어 이를 공유하게 된다! 따라서 멤버의 주소 값도 하나다
    • 공용체의 유용함은 다양한 접근 방식을 제공하는데 있다
      • 하나의 메모리 공간을 둘 이상의 방식으로 접근할 수 있다!, 효율적인 메모리 사용
        • ex) 입력받은 변수를 바이트 별로 나누기?
  • 열거형(Enumerated Type)의 정의와 의미
    • 열거형의 정의와 변수의 선언
      • 저장이 가능한 값 자체를 정수의 형태로 결정한다
      • 변수에 저장이 가능한 값들을 열거하여 정의
        • ex) enum syllabel { Do = 1, Re = 2, Me = 3, Fa = 4} 
        • // Do를 정수을 의미하는 상수로 정의한다, 이 값은 syllabel형 변수에 저장 가능하다
    • 열거형 상수의 값이 결정되는 방식
      • 열거형을 정의하는데 있어서 상수의 값을 명시하지 않으면 0부터 1씩 증가하는 형태로 결정이 된다
        • ex) enum a {Red, Blue, White, Black}; = a {Red = 0, Blue = 1, White = 2, Black = 3};
        • enum b {Red = 0, Blue, White = 3, Black}; = b {Red = 0, Blue = 1, White = 3, Black = 4};
    • 열거형의 유용함은 이름있는 상수의 정의를 통한 의미의 부여에 있습니다
      • Do, Re, Me 같이 연관이 있는 이름을 동시에 상수로 선언할 수 있다
      • 둘 이상의 연관이 있는 이름을 상수로 선언함으로써 프로그램의 가독성을 높이는데 있다!

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

C언어의 깊은 이해_4  (0) 2022.03.06
C언어의 깊은 이해_3  (0) 2022.02.10
C언어의 깊은 이해_1  (0) 2022.02.07
포인터와 배열의 시작_4  (0) 2022.02.07
포인터와 배열의 시작_3  (0) 2022.02.05

Chapter.1 문자와 문자열 관련 함수

  • 스트림과 데이터의 이동
    • 무엇이 '입력' 이고 무엇이 '출력' 인가?
      • 입력 ~~~> |프로그램| ~~~> 출력
      • 즉 프로그램을 중심으로 안으로 흘러들어오는 것이 입력, 밖으로 흘러나가는 것이 출력 이다
        1. 입력 장치 : 키보드 마우스 화상카메라 등
        2. 출력 장치 : 모니터 프린터 등
    • 데이터의 이동수단이 되는 스트림
      • 스트림 : 프로그램 상에서 모니터와 키보드를 대상으로 데이터 입출력을 위해 연결시켜주는 다리(매개체)
           
      • 운영체제는 외부장치와 프로그램과의 데이터 송수신의 도구가 되는 스트림을 제공하고 있음
      • 스트림은 한 방향으로 흐르는 데이터의 흐름을 뜻함
    • 스트림의 생성과 소멸
      • 입출력의 종류
        1. 콘솔 입출력 : 프로그램과 장치 사이의 입출력, 프로그램 실행 시 자동 생성, 종료시 자동 소멸
        2. 파일 입출력 : 프로그램과 파일 사이의 입출력, 스트램 생성 요구 시 생성, 소멸 요구 시 소멸
      • 콘솔 입출력을 위한 스트림
        1. stdin : 표준 입력 스트림 - 키보드 대상으로 입력
        2. stdout : 표준 출력 스트림 - 모니터 대상으로 출력
        3. stderr : 표준 출력 스트림 - 모니터 대상으로 출력 // 후에 입출력 리다이렉션(redirection)시 사용
  • 문자 단위 입출력 함수
    • 문자 출력 함수 : putchar, fputc // 오류 발생시 EOF 반환
      1. int putchar(int c); // 문자 정보를 표준 출력 스트림(stdout)으로 전송하는 함수
      2. int fputc(int c, FILE* stream); // 문자 정보를 임의의 스트림으로 전송하는 함수(파일 대상 전송 가능)
    • 문자 입력 함수 : getchar, fgetc // 파일의 끝에 도달하거나 함수 호출 실패 시 EOF 반환
      1. int getchar(void); // 문자 정보를 표준 입력 스트림(stdin)으로 입력받는 함수
      2. int fgetc(FILE* stream); // 문자 정보를 임의의 스트림으로 입력받는 함수(파일 대상 입력 가능)
        • cf) 문자열 입출력 함수는 Enter키 또한 문자로 입력 받고 출력 할 수 있다('\n')
    • 문자 입출력에서의 EOF
      • EOF(End Of File) 은 더 이상 읽을 내용이 없다 라는 뜻
      • 키보드 대상 입력일 경우 EOF인 경우는 2가지 이다
        1. 함수 호출의 실패
        2. windows에서 CTRL + Z, Linux에서 CTRL + D 입력 시
    • 반환형이 int이고, int형 변수에 문자를 담는 이유는?
      • char형은 예외가 발생한다 -> char을 unsigned char로 처리하는 컴파일러도 존재한다
      • 이때 EOF는 -1로 정의된 상수이다 이때 unsigned char로 처리되면 엉뚱한 양의 정수로 변환되어 버린다
      • 따라서 어떠한 상황에서도 -1을 인식할 수 있는 int형으로 반환형을 정의해 놓은 것이다!
    • 문자열 입력 함수: gets, fgets
      1. char* gets(char* s); 
        • 배열 s의 길이보다 입력받은 문자열이 더 길 때 오류가 발생한다
      2. char* fgets(char* s, int n, FILE* stream); // stream으로 문자열을 입력받아 배열 s에 저장하되 n길이 만큼 저장해라
        • n길이보다 더 긴 문자열을 입력받으면 자동으로 n길이 만큼의 개수만 저장한다
        • 이때 문자열 끝에 자동으로 널 문자가 추가된다, 따라서 n-1개수의 문자열을 받는 것이다
        • 길이가 긴 문자열을 반복수행으로 입력받을 수 있다, 이때 중복으로 읽는 것이 아니라 다음 문자열 부터 입력받는다
        • 입력받을때 \n 또한 문자열의 일부로 받아들인다. 
        • fgets 함수는 \n을 만날때 까지 문자열을 읽어들이기 때문에 ' ' 즉 공백도 문자열의 일부로 받아들인다
  • 표준 입출력과 버퍼
    • 표준 입출력 기반의 버퍼
      • 우리가 지금까지 공부해온 입출력 함수를 가리켜 표준 입출력 함수 라 한다
      • 표준 입출력 함수는 운영체제가 제공하는 '메모리 버퍼'를 중간에 통과 한다
      • ex) 키보드로 입력을 할 때 글은 써지지만 Enter키를 누르기 전까지 입력되지 않는다 이 때가 입력 버퍼에 있을 때 이다
    • 버퍼링(Buffering)을 하는 이유는 무엇인가?
      • 데이터 전송의 효율성을 위해 존재한다
      • ex) 수레로 짐을 모아 한꺼번에 이동시키는 것과 동일한 이유
    • 출력버퍼를 비우는 fflush 함수
      • 출력버퍼가 비워진다는 것은 출력버퍼에 저장된 데이터가 버퍼를 떠나서 목적지로 이동됨 을 뜻한다
      • 버퍼가 비워지는 시점은 각 시스템, 버퍼의 성격에 따라 달라진다 따라서 fflush로 원할 때 비울 수 있어야 한다
      • int fflush(FILE* stream); // 호풀 성공시 0, 실패시 EOF 반환 ->ex)fflush(stdout) // 표준 출력 버퍼를 비워라
      • 파일 대상으로 호출 가능(파일에 입력), 사실 잘 사용되지는 않는다
    • 입력버퍼 지우기
      • 입력버퍼가 비워진다는 것은 출력버퍼와 다르게 입력버퍼에 존재하는 데이터의 소멸을 의미한다.
      • fflush(stdin) 을 사용하면 입력 버퍼가 비워질 수도, 다른 결과가 나올 수도 있다 // 따라서 쓰이지 않는다
      • 입력 버퍼에 문제가 생겼을 경우에는 직접 함수를 만들어 버퍼를 비워준다
        • ex) void ClearLineFromReadBuffer(void) { while(getchar() != '\n") } //  \n이 나올 때 까지 입력받아 버린다
        • 이는 fget() 함수 사용시 '\n' 이 나올 때 까지 받아들이고 그 전에 끝나면 그 뒤를 게속 받기 때문에 생긴 문제을 해결함
        • 이처럼 그 상황에 맞는 함수를 사용해야 한다
  • 입출력 이외의 문자열 관련 함수
    • 헤더파일 string.h에 선언되어 있는 문자열 관련 함수들 이다
    • 문자열의 길이를 반환하는 함수: strlen
      • size_t strlen(const char* s); // size_t 는 typedef unsigned int size_t 이다
      • ex) char str[ ] = "1234567";, strlen(str) 의 반환값은 7이다(\0은 읽지 않음)
    • 문자열을 복사하는 함수들: strcpy, strncpy // 복사된 문자열의 주소 값 반환
      1. strcpy : char* strcpy(char* dest, const char* src); // src의 문자열을 dest에 복사
      2. strncpy : char* strncpy(char* dest, const char* src, size_t c); // src의 문자열을 c만큼 dest에 복사
        • 이때, 길이가 긴 배열을 짧은 배열에 복사한다면 ex) str1 [7] = {1, 2 ~. 7}, str2 [3]
        • strncpy(str2, str1, sizeof(str2)-1);을 해줘서 널문자가 존재할 공간을 비워준 후 널문자를 입력해 주어야 한다!!!
        • str2[sizeof(str2)-1] = 0; // str2[ ] = {1, 2, \n}
    • 문자열을 덧붙이는 함수들: strcat, strncat // 덧붙여진 문자열의 주소 값 반환
      1. strcat : char* strcat(char* dest, const char* src); // dest문자열 뒤에 src문자열을 덧붙여라
      2. strncat : char* strncpy(char* dest, const char* src, size_t c) // dest문자열 뒤에 c만큼 src문자열을 덧붙여라
        • 이때 덧붙여지는 위치는 앞 문자열의 널문자 이다
        • strncat 사용시 c만큼 덧붙인 후 뒤에 널문자를 자동으로 넣어준다
               
    • 문자열을 비교하는 함수들: strcmp, strncmp // 두 문자열이 같으면 0, 다르면 0이아닌 값 반환
      1. strcmp : int strcmp(const char* s1, const char* s2); //s1>s2면 0보다 큰값, s1<s2면 0보다 작은값 
      2. strncmp : int strcmp(const char* s1, const char* s2, size_t  n); // 맨앞부터 n개 까지 비교
    • 그 이외의 변환 함수들
      1. int atoi(const char* str); // 문자열의 내용을 int 형으로 변환
      2. long atol(cont char* str); // 문자열의 내용을 long 형으로 반환
      3. double atof(cont char* str); // 문자열의 내용을 double 형으로 반환

 

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

C언어의 깊은 이해_3  (0) 2022.02.10
C언어의 깊은 이해_2  (0) 2022.02.09
포인터와 배열의 시작_4  (0) 2022.02.07
포인터와 배열의 시작_3  (0) 2022.02.05
포인터와 배열의 시작_2  (0) 2022.02.04

Chapter.9 함수 포인터와 void 포인터

  • 함수 포인터와 void 포인터
    • 함수 포인터의 이해
      • 모든 함수는 프로그램 실행 시 '메인 메모리'에 저장되어서 실행된다
      • 이때! 함수의 이름은 함수가 저장된 메모리 공간의 주소 값을 의미한다 // 이때 배열과 마찬가지로 상수 이다
      • 함수 포인터를 선언하기 위해선 두가지 정보를 알아야 한다 ex) int SimpleFunc(int num) { ~ }
        1. 반환형 int 
        2. 매개변수 선언 int num
      • 함수 포인터의 형(Type)을 정하기 위해선 반환형과 매개변수의 선언형태를 기준으로 선언한다
             
      • ex) double SoSimple(int num1, int num2) { ~ } // double (*fptr) (int, int) = SoSimple;
      • 이후 fptr은 SoSimple과 변수, 상수 차이를 제외한 모든 기능이 동일하다(함수 호출가능) // 유연성 ↑
    • 형(Type) 이 존재하지 않는 void 포인터
      • void * ptr; // 다음과 같이 선언되는 포인터 변수를 'void형 포인터 변수' 라 한다
      • void 형 포인터 변수는 무엇이든 담을 수 있는 바구니에 비유된다, 변수, 배열, 함수의 주소 마저 담을 수 있다
        1. 모든 변수를 담을 수 있다
        2. 아무런 포인터 연산을 할 수 없다(* 연산), 값의 변경이나 참조가 불가능하다
      • "주소 값에만 의미를 두고, 포인터 형은 나중에 결정할때" 유용하게 사용된다 // 동적할당과 관계깊다
  • main 함수로의 인자 전달
    • int main(void) { ~ } // int main(int argc, char* argv[ ]) { ~ } 두 형태 모두 main 함수 이다
    • main 함수를 통한 인자 전달
      • 컴파일 후 debug폴더에 exe 실행파일을 C:\ 이동 후 cmd 로 실행하면서 문자열을 같이 전달한다
               
      1. argc = 입력한 문자열의 개수
      2. *argv[ ] = 입력받은 문자열 배열의 주소 값

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

C언어의 깊은 이해_2  (0) 2022.02.09
C언어의 깊은 이해_1  (0) 2022.02.07
포인터와 배열의 시작_3  (0) 2022.02.05
포인터와 배열의 시작_2  (0) 2022.02.04
포인터와 배열의 시작_1  (0) 2022.02.02

Chapter.6 다차원 배열

  • 다차원 배열의 이해와 활용
    • 2차원, 3차원 배열은 존재, 4,5차원 배열은 X
      • ex) int arrOne[10];, int arrTwo[10][10];, int arrThree[10][10][10]; // 세로 가로 높이 순
    • 다차원 배열을 대표하는 2차원 배열의 선언
      • ex) int arr[3][4];
           
      • 즉 TYPE arr[세로길이][가로길이]; 이다
      • 이때 이 배열의 크기는 가로길이*세로길이*데이터크기 이다 // (앞의 배열은 48byte)
    • 2차원 배열요소의 접근
      • arr[N-1][M-1] = 20; // 세로 N열, 가로 M행 위치에 20 저장
    • 2차원 배열의 메모리상 할당의 형태
      • 메모리는 1차원이기 때문에 다음 행이 전 행의 마지막 열 뒤에 이어짐
      • ex) int 형 2차원 배열의 메모리 상태
               
    • 2차원 배열 선언과 동시에 초기화 하기
      • 중괄호 사이에 1행씩 중괄호로 선언하기

                     

      • 중괄호 안에 쉼표로 구분해서 선언하기
    •        
    • 배열의 크기를 알려주지 않고 초기화 하기
      • 가로 세로 길이 둘다 알려주지 않으면 절대 자동으로 길이를 설정할 수 없다
      • 따라서 2차원 배열을 설정할때는 가로길이는 꼭 넣어줘야 한다
      • ex) int arr1[ ][4] = {1, 2, 3, 4, 5, 6, 7, 8};
  • 3차원 배열
    • 3차원 배열의 논리적 구조
      • 정육면체 구조와 동일하다고 생각하면 된다
      • int arr[2][3][4]; // 높이2, 가로3, 세로4 인 int 형 3차원 배열
      • 물론 배열의 크기 또한 (높이 * 가로 * 세로 * 자료형 사이즈) 이다
    • 3차원 배열의 선언
      • 앞서 설명한 2차원 배열이 여러개 모여있는 형태이다

Chapter.7 포인터의 포인터

  • 포인터 변수를 가리키는 이중 포인터 변수(더블 포인터 변수)
    • 주소 값을 저장하는건 똑같은 포인터 변수를 가르키는 포인터 변수이다
    • ex) double num;, double* ptr = &num;, double** dptr = &ptr;
           
    • *dptr // ptr을 의미, **dptr, *(*dptr) // num 을 의미
  • 포인터 변수 대상의 Call-by-reference
    • 이전에 swap 함수 때와 같이 포인터 변수 대상으로 swap 함수를 선헌한다
      1. void SwapIntPtr(int *p1, int *p2) 일 때
        •  위처럼 ptr이 가리키는 주소는 변하지 않는다
      2. void SwapIntPtr(int **p1, int **p2) 일 때
        •  이처럼 ptr1의 주소를 불러오기 때문에 ptr의 값 자체를 변경시킬 수 있다
    • 포인터 배열과 포인터 배열의 포인터 형
      • Chapter.3 에서 설명한 포인터 배열에 대한 부가설명
      • int* arr1[20]; 선언시 포인터 배열의 이름인 arr1이 가리키고 있는 것은 배열 첫번째의 주소 즉 arr1은 이중포인터 이다
  • 다중 포인터 변수와 포인터의 필요성
    • 이중 포인터 뿐만 아니라 삼중, 사중으로 *연산자를 추가하여 다중 포인터를 생성할 수 있다
    • 포인터는 데이터의 효율적인 표현 및 저장방법을 위해 존재한다!

Chapter.8 다차원 배열과 포인터의 관계

  • 1차원 배열이름의 포인터 형과 2차원 배열이름의 포인터 형
    • 1차원 배열일 경우, int arr[10]; // arr 은 int형 포인터
    • 2차원 배열은 단순히 ~형 포인터로 선언할 수 없다
  • 2차원 배열 이름이 가리키는 것들?
    • 우선 int arr2d[3][3]; 인 배열을 선언했다면, arr2d가 가리키는 것은 인덱스 기준으로 [0][0]인 첫번째 요소 이다!
      • 그러나 arr2d와 마찬가지로 arr2d[0], arr2d[1] 또한 같은 의미를 가진다 // arr2d[0] 은 [0][0]을, arr2d[1]은 [1][0]
      • arr2d와 arr2d[0]는 가리키는 것은 같지만 의미하는 것은 다르다(sizeof또한 다르다)
        1. arr2d 는 첫번째 요소를 가리키되 배열 전체를 뜻하므로 sizeof(arr2d) 선언시 36byte가 반환된다
        2. arr2d[0]는 첫번째 요소를 가리키되 첫번째 행을 뜻하므로 sizeof(arr2d[0]) 선언시 12byte가 반환된다
    • 배열이름 기반의 포인터 연산: 배열이름에 +1
      • 1차원 배열의 경우 sizeof(TYPE)만큼 크기가 더해진다 즉 TYPE이 같다면 더해지는양은 동일!
      • 2차원 배열이 경우 sizeof(TPYE)*가로길이 만큼 크기가 더해진다 따라서 TYPE이 같아도 더해지는 양이 다르다
    • 최종결론! 2차원 배열이름의 포인터 형
      • 2차원 배열 이름의 포인터 형에 담겨야 하는 정보 두가지
        1. 가리키는 대상은 무엇인가?
        2. 배열이름(포인터)를 대상으로 증감연산시 실제 증가하는 양(byte)는 얼마인가
      • ex) int arr[3][4]인 경우
        • 가리키는 대상은 int형 변수이고, 포인터 연산시 sizeof(int)*4 크기단위로 증가, 감소 된다!
        • -> int (*ptr) [4]; // (가리키는 대상) (포인터) (가로길이)
        • 이러한 표현을 "배열 포인터 변수" 라 한다
      • 2차원 배열이름의 특성과 주의사항!
        • 주의! "배열 포인터"와 "포인터 배열"을 혼동하지 말자!
          • 배열 포인터는 배열을 가리키는 포인터의 선언! (int (*whoA) [4];)
          • 포인터 배열은 포인터로 이루어진 배열의 선언! (int *whoB [4];)
        • 2차원 배열을 함수의 인자로 전달하기
          • 선언법 ex) void Arr2(int (*arr)[4]) or void Arr2(int arr[ ][4]);
          • sizeof(arr)은 배열 전체 크기, sizeof(arr[0])은 배열의 가로크기, sizeof(arr)/sizeof(arr[0])은 세로크기 이다
        • 2차원 배열에서도 arr[i]와 *(arr+i)는 같다
          • int arr[3][2]; 선언시 3행 2열(마지막 요소)에 접근하는 법
          • cf) 2차원 배열에서의 덧셈
            • arr+i = arr[0][0] + i*Type*가로길이
            • arr[1] + i = arr[1][0] + i*Type 
              1. arr[2][1]
              2. (*(arr+2))[1] // arr+2경우 가로길이*2가 더해짐 //*(arr+2) 는 arr[2][0]
              3. *(arr[2]+1) // arr[2][0] + 1 
              4. *(*(arr+2)+1) // arr[2] = *(arr+2)

 

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

C언어의 깊은 이해_1  (0) 2022.02.07
포인터와 배열의 시작_4  (0) 2022.02.07
포인터와 배열의 시작_2  (0) 2022.02.04
포인터와 배열의 시작_1  (0) 2022.02.02
C언어의 기본_4  (0) 2022.01.29

Chapter3. 포인터와 배열 함께 이해하기!!!

  • 포인터와 배열의 관계
    • 배열의 이름은 무엇을 의미하는가?
      • 배열의 이름 = 포인터, 값을 바꿀 수 없는 '상수 형태의 포인터' 이다
      • 배열의 이름은 배열의 시작 주소 값을 의미하며, 그 형태는 값의 저장이 불가능한 상수 이다
      • 따라서 *연산도 가능하다
    • 1차원 배열이름의 포인터 형과 배열이름을 대상으로 하는 *연산!
      • 1차원 배열이름의 포인터 형은 배열 이름이 가리키는 대상을 기준으로 결정된다
      • ex) int arr[5]; // arr은 int형 포인터 상수, double arr[7]; // arr은 double형 포인터 상수
      • ex) int arr[3] = { 1, 2, 3}; *arr += 100; // arr 에는 {101, 2, 3} 의 형태로 저장되어 있다
    • 포인터를 배열의 이름처럼 사용할 수도 있다
      • ex)int arr[3] = {1, 2, 3};, int* ptr = &arr[0];(int* ptr = arr;), ptr[1 ] += 100 // arr = {1, 102, 3}의 형태로 저장됨
      • 많이 사용되지 않는다, 하지만 사용할 수는 있다.
  • 포인터 연산
    • 포인터 변수를 대상으로 하는 증가 및 감소연산
      • TYPE 형 포인터를 대상으로 n 증가 = n*sizeof(TYPE) 의 크기만큼 증가
           
    • 중요한 결론!! arr[i] == *(arr+i)
      • *(arr+1) 과 arr[i] 은 같은 의미 이다
      • *arr+1 은 arr[0]의 값+1을 의미하므로 *arr+i != *(arr+1)
  • 상수 형태의 문자열을 가리키는 포인터
    • 두 가지 형태의 문자열 표현
      1. char str1[ ] = "My String"; // 길이 자동 계산, M의 주소 값이 str1에 저장됨
      2. char * str2 = "Your String"; // Your String 의 가장 앞문자 Y의 주소 값이 str2에 반환되어 저장된다
      3. (이때 배열 이름인 str1은 상수, str2는 포인터 변수 이므로 다른 위치를 가리킬 수 있다. 따라서 다시 str2 = "abc" 선언시 Your String 은 변함이 없고, 새로 자동 할당된 문자열 abc의 a위치를 str2가 가리키게 된다!!)
               
    • 어디서든 선언할 수 있는 상수 형태의 문자열
      • char* str = "abc"; // char* str = 0x1234;(a의 주소)
      • 이때 printf(0x1234); 선언 시 abc가 출력된다 즉 printf()는 문자열의 주소 값을 전달받는 함수 이다!
      • ex) WhoAreYou("Hong"); // void WhoAreYou(char* str) { }
    • "큰따옴표로 묶여서 표현되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다"
  • 포인터 변수로 이뤄진 배열 :포인터 배열
    • 포인터 배열의 이해
      • 지금까지는 기본 자료형의 변수를 요소로 지니는 배열만 선언해왔지만, 포인터 변수도 변수이니 배열로 선언할 수 있다
      • ex) int* arr1[20]; // 길이가 20인 int형 포인터 배열 arr1
      • ex) int num1=10, num2=20, num3=30;, int* arr[3] = {&num1, &num2, &num3};
           
      • 이렇듯 포인터 배열도 기본 자료형 배열과 다르지 않다. 다만 주소 값을 저장할 수 있도록 포인터 변수를 대상으로 선언된 배열 일 뿐이다
    • 문자열을 저장하는 포인터 배열
      • ex) char* strArr[3] = {"Simple", "String", "Array"};
      •       char* strArr[3] = {0x1004, 0x1048, 0x2012}; 형태 이다// 임의의 주소 값
           
      • "큰따옴표로 묶여서 표현되는 문자열은 그 형태에 상관없이 메모리 공간에 저장된 후 그 주소 값이 반환된다"

Chapter4. 포인터와 함수에 대한 이해

  • 함수의 인자로 배열 전달하기
    • 인자전달의 기본 방식은 '복사'이다
      • 함수호출시 전달되는 인자는 인자에 저장된 '값'이 매개변수에 복사가 된다
      • 따라서 인자와 매개변수는 별개이며, 매개변수가 바뀐다고 인자에 영향을 미치지 않는다!
    • 배열을 함수의 인자로 전달하는 방식
      • 매개변수로 배열을 선언할 수 없다! 따라서 배열의 주소를 매개변수로 선언해서 인자값으로 전달해야 한다
      • ex) int arr[3] = {1, 2, 3};, int* ptr = arr;
        1. void SimpleFunc(int* param) { }
        2. void SimpleFunc(int param[]) { } // 둘은 완전히 동일한 선언이다
      • 이때 매개변수 값을 변경시, 메모리의 주소 값을 매개변수로 받았으므로 실제 인자값도 변한다!
      • cf) 함수 내에서 인자로 받은 배열의 길이는 계산할 수 없다. 따라서 필요시 길이 또한 매개변수로 설정해서 전달받을것!
  • Call-by-value vs. Call-by-reference★
    1. 값을 전달하는 형태의 함수호출:Call-by-value
      • 지금까지 했던 함수의 호출 방법
      • 인자의 값을 변경할 수 없다
      • ex) call-by-value 형태로 swap 함수 생성시 문제점
             
    2. 주소 값을 전달하는 형태의 함수호출:Call-by-reference
      • 주소 값으로 접근하기 때문에 인자의 값을 변경할 수 있다
      • ex) call-by-reference 형태로 swap 함수 생성
             
      • 이젠 scanf 함수 호출시 연산자& 사용 이유를 알 수 있다
        • 함수로 입력받은 인자값을 변경하기 위해 call-by-reference로 접근해야하기 때문에 주소 값을 받는다
        • 문자열 입력시는 변수이름 자체가 배열의 주소 값이기 때문이다!
  • 포인터 대상의 const 선언
    1. 포인터 변수가 참조하는 대상의 변경을 허용하지 않는 const 선언
      • ex) const int* ptr = &num;
      • const 선언의 위치가 맨 앞부분일 경우 포인터 변수 ptr을 대상
      • "포인터 변수 ptr을 이용해서 ptr이 가리키는 변수에 저장된 값을 변경하는 것을 허용하지 않음!
      • *ptr = 30; 불가능// ptr=&age; // 가능, num = 30; // 가능
      • (const 선언은 상수로 만드는 선언이 아니라 변경불가의 상태로 만드는 것!)
      • const 선언된 포인터 변수의 *연산을 불가능 하게 함
    2. 포인터 변수의 상수화
      • ex) int* const ptr = &num;
      • 주소 값이 저장되면 그 주소 값의 변경이 불가능 해 진다
      • *ptr = 30; // 가능, ptr=&age; // 불가능, num = 30; // 가능
    3. 두 형태의 const 선언 동시에 가능
      • const int* const ptr = &num;
      • *ptr = 30; // 불가능, ptr=&age; // 불가능, num = 30; // 가능

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

포인터와 배열의 시작_4  (0) 2022.02.07
포인터와 배열의 시작_3  (0) 2022.02.05
포인터와 배열의 시작_1  (0) 2022.02.02
C언어의 기본_4  (0) 2022.01.29
C언어의 기본_3  (0) 2022.01.26

Chapter1. 1차원 배열

  • 배열의 이해와 배열의 선언 및 초기화 방법
    • 배열이란? 둘 이상의 변수를 모아놓은것으로 '나란히' 선언되어 있음
    • 1차원 배열의 선언에 필요한것 세가지 ex) int oneDimArr [4];
      1. 배열이름 oneDimArr
      2. 자료형 int
      3. 길이정보 [4]
    • 배열의 위치 정보를 명시하는 인덱스값은 1이 아닌 0에서부터 시작된다
    • arr[idx] = 20; 으로 배열에 접근했을 때 idx+1번째 요소에 20을 저장한다
    • 배열의 초기화
      1. 배열의 길이정보가 명시되있고, 초기 값이 배열의 요소를 다 채울정도 ex)int arr[5] = {1, 2, 3, 4, 5}
      2. 배열의 길이정보가 명시되지 않음 ex) int arr[ ] = {1, 2, 3, 4, 5} // 컴파일러가 자동으로 채워줌(5로)
      3. 배열의 길이보다 적게 초기값이 선언됨 ex) int arr[7] = {1, 2, 3} // 나머지 값은 0으로 초기화 됨
  • 배열을 이용한 문자열 변수의 표현
    • char형 배열의 문자열 저장과 '널(NULL)' 문자
      • char str[14]="Good mornig!"; 선언시, char str[ ] ="Good mornig!" // 마지막 널문자 개수도 포함해 14개 이다 
        G o o d   m o r n i n g ! \0
    • 널 문자란? : 문자열의 끝을 나타내는 기호로 정수형으로 0으로 표현됨, 컴파일러가 문자열의 끝으로 인식하게 함
    • scanf 함수를 이용한 문자열의 입력 (%s), 이때 char형 배열의 경우 &연산자를 삽입하지 않는다
      • ex) scanf("%d", &num); scanf("%s", str);
    • scanf 함수로 입력받은 문자열도 끝에 널문자(\0)가 삽입된다
    • char arr1[ ] = {'H', 'i', '~'}; // 문자배열, char arr2[ ] = {'H', 'i', '~', '\o'} // 문자열
    • scanf 함수의 문자열 입력 특성으로 공백문자를 입력하지 못한다. scanf에서 공백은 데이터를 구분짓는 기준이기 때문
    • scanf함수로 He is my friend 를 입력받으면 "He", "is", "my", "friend" 이렇게 총 4개의 문자열이 입력된 것으로 인식한다

Chapter2. 포인터의 이해

  • 포인터란 무엇인가?  // C언어가 Low 레벨 언어의 특성을 지닌다고 할 수 있게 하는 장본인!
    • 포인터 변수(메모리의 주소 값 저장을 목적으로 선언됨)
      • 변수가 선언되면 다음과 같이 메모리상에 존재하게 되고 이때 각 '메모리의 주소 값'을 할당받게 된다!
      • ex) char ch1 = 'A', ch2 = 'Q';, int num = 7;
             
      • 이때 A의 메모리의 주소 값은 0x12ff74 이고 이것을 저장하는 형태가 포인터 변수 이다!
    • 포인터 변수와 &연산자
      • &연산자는 피연산자의 주소 값을 반환하는 연산자 이다
      • ex) int num=7; int* pnum; pnum = &num;
           
      • 포인터 변수 pnum이 int형 변수 num을 가리킨다 라고 표현함, pnum에 num의 시작번지 주소가 저장됨!
      • 포인터 변수의 크기는 시스템이 32비트인지 64비트인지에 따라 4byte, 8byte인지가 정해짐
    • 포인터 변수 선언하기
      • type * ptr; 형태 // int *, double *, float * 등의 타입
      • 이때 '포인터 형'(int *, char * 등) 또한 자료형 의 범주에 포함시키기도 한다
      • int * // int 형 포인터, int * pnum1 // int 형 포인터 변수 pnum1
  • 포인터와 관련 있는 연산자 (&연산자, *연산자)
    • 변수의 주소 값을 반환하는 &연산자
      • &연산자의 피연산자는 변수여하 함
      • 변수의 자료형에 맞지 않은 포인터 변수의 선언은 문제가 됨(↓ 설명)
    • 포인터가 가리키는 메모리를 참조하는 *연산자 (포인터 형을 선언할때 사용하는 *과는 다르다)
      • *연산자는 포인터가 가리키는 주소값의 메모리 공간(값)에 접근할 때 사용하는 연산자이다
      • ex) int num = 10; int* pnum = &num; *pnum = 20; // 이때 *pnum은 num의 값을 뜻하므로 num이 20이 됬다
      • 따라서 *pnum과 num이 의미하는 것은 같다!!
    • 다양한 '포인터 형'이 존재하는 이유!
      • 포인터 형은 메모리 공간을 참조하는 기준이 된다! (*연산과 관계)
      • ex) return *pnum; 만 보고는 pnum에 저장된 주소를 시작으로 몇 바이트 읽어들여 정수,실수 중 어느 것으로 해석해야 하는가를 알 수 없다
      • int* pnum; 으로 선언되있다면 pnum에 저장된 주소를 시작으로 4byte 읽어들여 정수로 해석하면 된다!!
      • 만약 double 형에 int* 형 포인터를 연결한다면 8byte크기에 실수 형태로 저장된 값을 4byte크기에 정수 형태로 읽어오기 때문에 알수없는 값이 저장된다
      • 포인터 형이 존재하지 않는다면 *연산을 통한 메모리 접근이 불가능하다
    • 잘못된 포인터의 사용과 널 포인터
      • 잘못된 포인터의 초기화 ex) int* ptr = 125; *ptr = 10; // 이때 125주소값에 접근하면 시스템 전체에 문제를 일으킨다!
      • 이때 포인터 변수를 선언한 후 초기화 시 널포인터(0)을 사용한다
      • ex)int* ptr=0; or int* ptr=NULL; 이때 0은 0주소값을 의미하지 않고 NULL값을 의미한다
      • 널포인터의 의미는 '아무것도 가리키지 않는다!' 란 뜻이다.

'IT공부 > 윤성우 열혈 C프로그래밍' 카테고리의 다른 글

포인터와 배열의 시작_3  (0) 2022.02.05
포인터와 배열의 시작_2  (0) 2022.02.04
C언어의 기본_4  (0) 2022.01.29
C언어의 기본_3  (0) 2022.01.26
C언어의 기본_2  (0) 2022.01.26

+ Recent posts