C 기초 정리

  그동안 C언어책들을 이것저것 본걸 다 합치면 아마 못해도 수백번은 본듯.. 그래도 언제나 심심할때 아무 C 책이나 꺼내들고 읽기 시작하면 재미있다. 하도 많이 본 내용이라 기본적인걸 눈으로 훌고 지나가고 조금 심도 있는 부분만 다시 곱씹어 읽으면 책을 꺼내들고 앉은자리에서 한권 다 금방 읽어버릴 정도이다. 요새 너무 많은 언어를 한꺼번에 사용하다보니 갑자기 아 C에서는 이걸 어떻게 선언했었지? 또는 C언어에서 주석처리가 #였나? //였나? 이런생각이 들기까지 한다. 항상 심심할때면 책을 펼치고 다시 훑어 보던 내용들을 여기에 좀 정리해 놔야겠다. 또 한 책을 다 정리하고나서 다른책을 정리 할때 중복되는 내용은 빼고 유용한 부분만 계속 추가로 정리할 생각이다.

* 주석처리 : /* ~ */ 또는 단일행 주석처리 //

* 변수의 선언 : int val; val=10; 또는 int val = 10;

* 변수의 자료형 : char, int, double

* 변수 선언시 주의 사항
   1. 변수의 이름은 알파벳, 숫자, 언더바(_)로 구성
   2. C 언어는 대 소문자를 구별, Val과 val은 서로 다른 변수
   3. 변수의 이름은 숫자로 시작 불가, 이미 지정된 키워드 사용 불가
   4. 변수명에 공백 포함 불가

* 기본 연산자 : = , + , - , * , / , %(나머지)

* 기타 대입 연산자 : += , -= , *= , /= , %=
   a = a + b; -> a += b;

* 부호의 의미로 + , - 사용 가능 : val = 20; -val -> -20

* 증감 연산자 : ++a(증가후 연산), a++(연산후 증가), --b(감소후 연산), b--(연산후 감소)

* 관계 연산자 : <, >, ==, !=, <=, >=

* 논리 연산자 : &&, ||, !

* 콤마(,) 연산자 : 보통 두개 이상의 구문을 한번에 쓸때 [예: int a=1, b=2; printf("a"), printf("b");]

* 연산자 우선순위

  1. ( ) , [ ] , -> , .
  2. sizeof , & , ++ , == , ~  , ! , *(간접 지정 연산자) , +(단항 연산자) , -(단항 연산자)
  3. *(곱셈 연산자), / , %
  4. +(이항 연산자) , -(이항 연산자)
  5. << , >>
  6. < , <= , >= , >
  7. == , !=
  8. &
  9. ^
  10. |
  11. &&
  12. ||
  13. ?:(삼항 연산자)
  14. = , += , *= , /= , %= , &= , ^= , |= , <<= , >>=
  15. ,(콤마 연산자)

* printf("문자열");, prtinf("서식",변수);

* scanf("서식", &변수); , scanf("서식1 서식2", &변수1,&변수2);  [변수지정이 다른 프로그래밍 언어와 달리 &를 썼음에 유의]
   [여러개 입력 받을때는 스페이스바, tab, enter로 입력이 구분된다]

* 미리 정의된 키워드들
   auto , _Bool , break , case , char , _Complex , const , continue , default , restrict , do , double , else , enum , extern , float , for , goto , if , _Imaginary
   inline , int , long , register , return , short , signed , sizeof , static , struct , switch , typedef , union , unsinged , void , volatile , while

* 진수 표현 : int a = 10;[10진수] , int b = 0xa;[0x로 시작하므로 16진수] , int c = 012;[0으로 시작하므로 8진수]

* 정수의 표현방식 : 해당 비트중 첫번째 비트가 부호 비트, 음수는 2의 보수로 표현[1의 보수후 1을 더함]

* 실수의 표현 방식 : 첫비트는 부호 비트, m , e [+- 1.m * 2의 e-127승]
   실수가 이런 방식으로 사용되기 때문에 부동소수점 오차를 가지고 있다.

* 비트 단위 연산자

  1. & : AND연산
  2. | : OR연산
  3. ^ : XOR 연산, 두개의 비트가 같으면0, 다르면 1을 반환
  4. ~ : NOT 연산
  5. <<, >> : 비트 이동(shift) <<(왼쪽으로 이동) , >>(오른쪽으로이동)

* 자료형과 표현 범위(Visual C++ 기준, Turbo C 에서는 int 를 2바이트로 처리)
  1. char(1바이트) : -128 ~ +127
  2. short(2바이트) : -32768 ~ +32767
  3. int(4바이트) : -2147483648 ~ +2147483647
  4. long(4바이트) : -2147483648 ~ +2147483647
  5. float(4바이트) : 3.4*10의 -37승 ~ 3.4 * 10의 38승
  6. double(8바이트) : 1.7*10의 -307승 ~ 1.7*10의 308승
  7. long double(8바이트 혹은 그이상) : 시스템에 따라 차이가 있음

* sizeof : 해당 자료형의 크기 반환 [예: sizeof 변수명, sizeof(int)] [대부분 sizeof에 ( )를 사용해 쓰다보니 sizeof를 함수라 생각하는데 sizeof는 함수가 아니다]

* 우리가 쓰는 컴퓨터는 int형식을 가장 빠르게 처리한다.

* 실수 자료형의 정밀도 : float(소수 이하 6자리) , double(소수 이하 15자리) , long double(double과 같거나 좀더 큼)

* unsigned 자료형의 표현 범위, 정수형민 가능 실수형은 unsigned 없음

  1. char : -128 ~ +127 , unsigned char : 0 ~ (127 +128)
  2. short : -32768 ~ +32767 , unsigned short : 0 ~ (32767 + 32768)
  3. int : -2147483648 ~ +2147483647 , unsigned int : 0 ~ (2147483647 + 2147483647)
  4. long : -2147483648 ~ +2147483647 , unsigned long : 0 ~ (2147483647 + 2147483648)

* 하나의 문자는 ' ' 로 여러문자(문자열)은 " " 로..

* 상수 : 상수도 메모리 공간에 저장된다, var = aaa + 10; [여기서10이 상수인데 이런 이름 없는 상수를 리터럴 상수라하고, 이런 상수도 메모리상에 공간을 점유한다.] 실수 상수 3.14 는 기본적으로 double 형이다 float f = 3.14; 이런식으로 코딩하면 에러가 난다. float에다 double을 집어넣으려 해서 데이타가 잘려나갈수 있다고 경고를 한다. 컴파일러에 따라 경고를 안하기도 함. float형 실수는 3.14f 이렇게 표현.

* 접미사를 통한 상수의 자료형 표현 u,U : unsigned int / l,L : long / ul, UL : unsigned long / f, F : float / l , L : long double
   [예: 304U , 304L , 304UL , 3.15F , 3.15L]

* 심볼릭상수 : 마치 변수처럼 이름을 가지는 상수이다. const를 사용해 선언한다.
   [예: const int MAX = 100;] MAX라는 심볼릭 상수에 100을 정의, 차후에 값 변경 불가. 선언과 값 배정도 동시에 해야 한다.

* 자료의 형 변환 : 자동 형 변환(묵시적 형 변환)과 강제 형 변환(명시적 형 변환)이 있다.

   자동 형 변환 : int n = 5.25; [대입문의 경우 int형 변수에 double형 값을 저장하려고 할때 5.25가 int형식으로 자동 형변환된다. n변수에는
   5가 저장]
   표현 범위가 더 넓은 변수로의 형변환은 잃어나지 않고 더 작은 표현 범위를 가지는 변수에 대입될때 형변환이 일어난다.
   정수의 승격 : 정수가 가장 빠르게 처리되므로 정수형으로 형변환 해도 데이타의 손실이 발생 하지 않는 자료형은 정수형으로 형변환된후
   연산을 모두 마친후에 다시 원래의 형으로 되돌아가서 저장된다.
   또 다른 예로 double e1 = 5.5 + 7; 이경우 정수7이 double형 7.0으로 바뀐후 연산된다.

   강제 형 변환 : 코딩을 하는 사람이 명시적으로 형을 기술해주는 것을 말한다.
   [예: float f = (float)a / b;] a변수의 형을 float로 변환하고 있다.

* printf 출력 특수문자
   \a 경고음, \b 백스페이스 , \f 폼피드 , \n 개행 , \r 캐리지 리턴 , \t 수평탭 , \v 수직탭 , \\ 백슬래시 , \, 작은 따옴표 , \" 큰따옴표

* printf 출력 서식
   %c 단일문자
   %d 부호 있는 10진 정수
   %i 부호 있는 10진 정수, %d와 같음
   %f 부호 있는 10진 실수
   %s 문자열
   %o 부호 없는 8진 정수
   %u 부호 없는 10진 정수
   %x 부호 없는 16진 정수, 소문자 사용
   %X 부호 없는 16진 정수, 대문자 사용
   %e e 표기법에 의한 실수
   %E E 표기법에 의한 실수
   %g 값에 따라서 %f, %e 둘 중 하나를 선택
   %G 값에 따라서 %f, %E 둘 중 하나를 선택
   %% %기호 출력
* 필드 폭 지정
   %8d 필드 폭 8칸, 오른쪽 정렬
   %-8d 필드 폭 8칸, 왼쪽 정렬
   %+8d 필드 폭 8칸, 오른쪽 정렬, 양수는 + 음수는- 붙임

* scanf에서 float 값을 받아들이는 서식자는 %f, double 값을 받아들이는 서식자는 %e가 아니고 %le
  변수 선언은 float와 double

* 순환문에서 루프 생략 continue, 루프 탈출 break

* goto 문에서 위치 지정은 label: , 점프는 goto label;

* 함수의 선언
   해당 함수 사용 전에 미리 함수 본체가 선언되어져있거나 소스파일 처음 #include 뒤에 int Add(int a, int b); 이런식의 선언문두고 기술은 함수사용 뒤에 해도됨.

* 지역 변수 : 함수 본체내에 정의
   전역 변수 : 소스 파일 처음 #include 뒤에 선언

* 독립 함수 내에 지역 변수 선언시 함수 호출시마다 값이 일정하지만 함수내에 static int val = 0 이런식으로 선언하면 함수 호출시 val변수의 값이 유지된다.

* 변수 선언시 앞에 register를 붙이면 CPU 내의 레지스터에 변수를 저장해서 사용하므로 가장 빠르다. 하지만 굳이 선언하지 않아도 컴파일러 최적화에 의해 자동으로 수행되기도 한다.

* 배열의 선언
   형식 배열명[배열길이]; 배열길이에 변수 사용불가, 상수만 가능; (int size = 10; int arr[size]; 컴파일 에러 발생)
   선언과 동시에 초기화 : int arr[5] = {1, 2, 3, 4, 5}; int arr[ ] = {1, 2, 3, 4, 5}; int arr[5] = {1,2}

* 문자열 변수
   char str[6] = "Hello"; 문자5개와 널문자1개, 공백문자도 문자로 취급, 널문자는 아스키코드0이고 강제 삽입은 \0
   끝에 널문자가 없는건 문자열이 아니라 문자 배열이다. char arr[] = {'a','b','c'}은 문자배열, char arr[] = {'a','b','c','\0'}은 문자열.
   문자열을 문자배열처럼 다루어 처리할떄 널문자를 삭제하거나 위치를 변경하지 않도록 조심.

* 포인터(=포인터변수) : 메모리의 주소 값을 저장하기 위한 변수, 포인터의 크기는 4byte
   선언 : int a*; char *b; double *c;
   주소저장 : int* a = &aa;
   int a=1000;(Direct Access) | int* Pa = &a;(Indirect Access)

* 포인터 사용전에 정확한 주소를 할당해야 한다.
   int *Pa; 또는 int *Pa = 100; // Pa는 쓰레기 값으로 초기화
   *Pa = 10;  // 엉뚱한 주소 접근 가능, 에러 발생

* 배열의 이름도 포인터. 포인터 방식으로 접근 가능. 단 포인터의 값을 바꿀수 없는 상수 포인터 변수라는점.
   int a[5] = {0, 1, 2, 3, 4};
   printf("%d, %d \n", &a[0], &a[1]); 배열 요소의 주소 출력
   printf("%d \n", a); 배열의 시작 번지 출력

* 포인터를 배열처럼 사용 가능
   int arr[3] = {0, 1, 2};
   int *ptr; // 또는 int* ptr = arr 이렇게 한줄로 선언 가능
   ptr = arr;
   printf("%d %d %d", ptr[0], ptr[1], ptr[2]);

* 포인터의 증감 연산(포인터가 가리키는 타입에 따라 주소가 크기만큼 이동한다)
   ptr++; ptr += 3; --ptr; ptr = ptr1 + 2; 주소에 대한 증감X, 현재 포인터의 다음 요소 또는 이전 요소를 가리킨다.

* 포인터 변수의 초기화(NULL화)
   int* ptr1 = 0; // int* ptr1 = NULL; 과 같은 문장
   char* ptr2 = 0; // char* ptr2 = NULL; 과 같은 문장
   double* ptr3 = 0; // double* ptr3 = NULL; 과 같은 문장
   ptr1 -> 0 , ptr + 1 -> 4 // ptr3 -> 0 , ptr 3 + 1 -> 8

* 포인터의 핵심 arr[i] = *(arr+i)

* char str1[5] = "abcd" -> str1을 포인터로 접근해 각각의 문자에 접근 가능
   char *str2 = "ABCD" -> "ABCD"라는 문자열상수가 메모리에 저장된후 그 처음주소가 *str2에 저장됨, 포인터를 통해 문자열상수 수정 불가.( 상수이므로)

* 컴파일러에 따라다르지만 최적화를 수행하는 컴파일러의 경우 똑같은 문자열 상수가 메모리에 2번 저장되게 될때 컴파일러가 한번만 저장하고 그 주소를 2개의 변수가 공유하게 한다. 2개의 같은 문자열상수 선언하고 주소 찍어보면 똑같이 나옴.

* Turbo C/C++ 컴파일러는 문자열 상수의 조작도 허용하고 같은 문자열 상수가 2번 선언되면 다른 메모리에 저장.

* 배열 요소에 문자열 포인터 사용
   char arr[3] = {"aaa", "bbb", "ccc"};
   arr[0] -> "aaa"의 첫 주소 저장

* 포인터를 이용해 함수의 인자로 배열 넘길수 있다.
   void fct(int *arr2); 또는 void fct(int arr2[]);-> 포인터를 인자로 받기 위한 함수 선언
   int main(void)
   {
      int arr1[2] = {1, 2};
      fct(arr1);
      printf("%d \n", arr1[0]);
      return 0;
   }
// 배열의 주소와 배열의 크기와 배열요소수 같이 넘길때 함수(배열의주소, 배열의크기(sizeof(배열))/배열종류의크기(sizeof(type)))

* 변수의 call-by-reference
int val = 10;
adder(&val);

void adder(int* pVal)
{
  (*pval)++;
}

* scanf의 함수 호출시 &의 유무
scanf("%d", &val); -> val이라는 변수에 접근해 내용을 변경하기위해 &를 붙여 주소를 넘김
scanf("%s", str); -> 배열명 str도 주소를 나타내므로 &없이 사용 가능

* 포인터의 상수화 : 포인터 앞에 const붙이면 상수화되어 가리키는 메모리 주소의 변경이 불가능.
   만약 선언시 const int* const p = &a; 이런식으로 선언하면 포인터자체도 변경불가, 포인터에 담긴 메모리의 값도 변경 불가

* 다차원 배열의 선언
int aaa[3][3]; -> 각 배열 요소에 0이 아닌 쓰레기 값으로 채워진다.

int aaa[3][3] = {
     {1,2,3},
     {4,5,6},
     {7,8,9}
};

int aaa[3][3] = {
     {1},  -> 1,0,0
     {4,5}, -> 4,5,0
     {7,8,9} -> 7,8,9
}; -> 부족한 부분은 0으로 채워진다.

int aaa[3][3] = {1,2,3,4,5,6,7}; -> 3채원 배열에 1차원 원소 나열로 입력해도 순서대로 채워짐 모자른 부분은 0으로 채워짐);

int aaa[][] = {1,2,3,4,5,6,7,8} -> 2*4, 4*2, 1*8, 8*1 어떤 배열이든 될수 있으므로 이렇게 선언하면 안된다.
int aaa[4][] = {1,2,3,4,5,6,7,8} -> 이경우 4*2의 배열이 생성되면서 값이 들어간다.
int aaa[][4] = {1,2,3,4,5,6,7,8} -> 이경우 2*4의 배열이 생성되면서 값이 들어간다.

* 포인터의 포인터(더블 포인터) : int **dp;
더블 포인터가 가리키는 것은 싱글 포인터
double val = 3.14;
double *ptr1 = &val;
double **ptr2 = &ptr1;

int* arr[10] -> arr의 첫번째 요소가 int형의 포인터이므로 arr의 타입은 더블 포인터

* 배열을 포인터 형식으로 사용할때, 2차원 배열의 배열이름에 포인터 연산하면 행단위로 주소 이동.
   1차원 배열의 경우 배열이름에 포인터연산 수행하면(arr+1) 다음 원소로 넘어갔다. arr[0] -> arr[1]
   2차원 배열의 경우 배열이름에 포인터연산 수행하면(arr+1) 다음 원소로 넘어가는게 아니고 다음행 첫번째 원소로 넘어간다. arr[0][0] -> arr[1][0]
   2차원 배열의 행 포인터 arr[0]에 포인터 연산 수행하면 1차원 배열처럼 다음 원소로 넘어간다.

   이런 2차원 배열을 정확하게 컨트롤 할수 있는 포인터의 선언은 int arr[2][4] -> int (*parr)[4]
   int는 int형 변수를 다룬다는 뜻이고, (*parr)은 포인터라는 뜻이고, [4]는 포인터 연산시 4개씩 주소를 건너뛴다는 뜻이다.
   함수선언에서 int (*paar)[4]로 받은 배열포인터의 접근은 parr[i][j] 이렇게 가능. parr의 배열의 이름이라고 생각하면됨.
   함수선언시 배열 포인터 선언은 int (*parr)[4] 대신 int parr[][4] 로 선언가능. 이형식은 함수에 인자로 선언할때만 가능.
   만약 함수 인자가 아닌 일반적인 선언으로 int parr[][4]를 사용하면 포인터가 아니라 크기를 생략한 배열로 인식하게 되므로 포인터의 역활을 못하고 주소를 저장할수 없게 된다.

* int (*parr)[4]과 int* parr[4] 차이점.. int (*parr)[4]는 int형을 다루는 포인터 연산시 4칸씩 건너뛰는 배열을 다루는 포인터의 선언이고, int* parr[4]는 int형 포인터를 저장하는 배열의 선언이다... 꼭 명심하도록...