1. null문
세미콜론을 제외하고 아무 기호도 없는 구문을 null문이라고 한다. 즉 구문이 '무형'이라는 뜻이다.
아래와 같이 i 할당문, j 할당문의 사이에 있는 것이 null문이다.
i=0; ; j=1
null문은 주로 본문이 비어있는 루프에 쓰인다.
주로 방법2로 많이 사용한다. 루프의 본문이 비어있다는 것을 쉽게 파악할 수 있게 하기 위함이다.
for (d = 2; d < n && n % d != 0; d++) ; //방법 1
for (d = 2; d < n && n % d != 0; d++) { //방법 2
;
}
아래 예시는 2 이상의 수 중 n을 나눌 수 있는 최솟값을 구하는 코드이다.
이건 본문이 루프 밖에 있어서 루프 바로 뒤에 세미콜론을 붙여도 문제 없었지만
for (d = 2; d < n && n % d != 0; d++) ;
if (d < n) {
printf("%d is divisible by %d\n", n, d);
}
보통은 본문이 루프 안에 있을 것이다. 그래서 본문이 실행되지도 않은 채 루프만 돌게 하는 것을 방지하기 위해서
본문 내용이 없더라도 꼭 중괄호를 사용하는 것을 권장한다.
for (i = 10; i > 0; --i) ; /*** WRONG ***/
printf("T minus %d and counting\n", i);
2. 형변환
컴퓨터가 연산할 때는 피연산자들의 비트수가 반드시 같아야 한다.
따라서 C컴파일러는 피연산자들의 크기가 다를 때, 특정 형으로 변환해주는 규칙이 있다.
예를 들어 short(16bit) + int(32bit) 연산 시 컴파일러는 short값을 32비트로 변환해주는 작업을 한다.
이러한 변환은 프로그래머도 모르는 사이 컴파일러가 알아서 처리해주므로 '암시적 변환(implicit conversion)'이라고 한다.
프로그래머가 직접 변환 연산자를 사용하는 '명시적 변환(explicit conversion)'을 사용하기도 한다.
암시적 변환이 발생하는 상황 :
- 산술/논리 표현식에서 피연산자들의 형이 다를 때 (C언어에서는 기본산술변환(usual arithmetic conversion)이라는 것을 수행함)
- 할당에서 우항 표현식과 좌항 변수의 형이 다를 때
- 함수 호출의 입력변수 형이 이에 대응하는 입력값의 형과 다를 때
- return문의 표현식 형이 함수 반환형과 다를 때
2-1. 기본산술변환
더 큰 쪽으로 형을 일치시킴
항상 더 안전한 형으로 승진시켜 손실 없이 연산되게 한다.
아래의 경우 8bit의 char이 32bit의 int로 승진되어 int+int연산이 된다.
char c = 100;
int i = 200;
int result = c + i;
signed와 unsigned의 연산
1) 둘의 크기가 같다면 signed -> unsigned로 일치시킴
비트 크기도 같은데 뭘 일치시킨다는 거냐면 signed를 더 이상 음수로 해석하지 않고 unsigned로 해석하겠다는 의미이다.
아래 예제에서 i = -10은 int가 표현할 수 있는 최댓값인 4294967295에서 wrap around되어 4294967286으로 변환된다.
따라서 둘의 연산 결과는 result = 4294967286 + 10 = 4294967296으로 나온다.
그리고 unsinged int는 2^32개의 값 (4294967295)까지만 표현할 수 있으므로 4294967296은 wrap around되어 0이 최종적인 결과로 저장된다.
int i = -10;
unsigned int u = 10;
int result = i + u;
(참고) signed를 unsigned로 해석하는 과정을 mod 2^n이라고 함
나머지(mod)연산처럼 돌아서 다시 0부터 시작하는 원리이고 이것을 wrap around라고 함
(unsigned int)(-10)
= -10 mod 2^32
= -10 mod 4294967296
= 4294967286
2) 둘의 크기가 다르다면 더 큰 쪽으로 일치시킴
long long s = -10;
unsigned long long u = 10;
auto result = s + u; // 결과는 unsigned long long
long long s = 100;
unsigned int u = 200;
auto result = s + u;
실수형과의 연산
정수형 -> 실수형으로, 실수형 -> 더 넓은 실수형으로 변환된다.
변환 우선순위는 다음과 같다.
long double > double > float > 정수형
(참고) c언어에서 실수형은 3가지가 있다.
float : 4byte ex) 3.14f, 1.0f
double : 8byte ex) 3.14, 1.0
long double : 8~16byte ex) 3.14L, 1.0L
2-2. 할당 중 형변환
할당은 산술변환과 다르게 동작한다. 우항 표현식이 좌항 변수의 형으로 변환된다.
만약 넓은 범위로 할당된다면 문제가 없는데 아래처럼 실수를 정수에 할당하면 소수점 이하가 버려진다.
int i;
i = 842.97; /* 842 */
i = -842.97; /* -842 */
넓은 값을 좁은 값으로 할당하면 컴파일러가 경고를 띄운다.
float형에 고정 소수점 상수를 할당할 때 접미사 f를 넣어줘야 하는 이유이다.
f를 생략할 경우 double로 인식되어서 경고 문구가 출력될 수 있다.
2-3. 형치환
float f;
int a;
int b;
f = a/b;
3. 형정의
형정의(type definition)란 기존 자료형에 별칭(alias)을 만드는 키워드이다. 쓰는 목적은 다양하다.
1) 타입 이름이 너무 길어서 간단하게 쓰고 싶을 때
(primitive타입, 구조체 타입에게 별칭 붙여주기 가능)
typedef unsigned long long ull;
ull a = 10000000000;
typedef char* string;
string s = "hello";
typedef struct Point {
int x, y;
} P;
P p1 = {10, 20}; // struct Point 대신 별칭인 P를 쓸 수 있음
2) 포인터 타입을 깔끔하게 표현하고 싶을 때
아래처럼 하면 a, b 둘 다 int* 타입으로 선언할 수 있는데
int* a, b;처럼 정의하면 a는 포인터지만 b는 그냥 int로 선언됨
typedef int* IntPtr;
IntPtr a, b;
3) 추상화/캡슐화
구조체 내부 멤버들을 외부 파일에서는 보이지 않게 하고 오직 구조체 이름만 공개해서 외부 코드가 내부를 알 수 없게 만드는 것
(참고)
C90시절에는 상수, inline함수, bool타입도 없었기 때문에 #define을 사용했다.
#define은 전처리기 지시자(preprocessor directive)인데 컴파일 전에 문자열 치환(macros)을 해준다.
C90시절에 상수, inline함수, bool타입도 없어서 #define을 사용했는데 주로 상수값이나 간단한 치환이 필요할 때 사용되었다.
#define BOOL int //매크로 macros
typedef int Bool; //형정의 type definition
4. 배열
4-1. 1차원 배열
배열 선언하기 : 타입, 크기를 지정해줘야 한다.
(나중에 배열 길이를 수정하고 싶다면 배열 크기를 변수로 정의해주자. 이 방법을 '가변 크기 배열(variable-length array)'라고 하는데 VLA의 크기는 프로그램 컴파일 도중이 아닌 실행 도중에 정해진다. vla를 쓰면 프로그래머가 직접 배열 크기를 정해주지 않아도 되고, 프로그램이 실행될 때 딱 필요한 만큼만 공간이 할당되기 때문에 낭비가 적다.
int a[10];
int n;
scanf("%d", &n);
int arr[n]; //C99 only - length of array depends on n
원소 입력 받기 : 일반 변수처럼 &부호를 사용한다.
for(int i=0; i<N; ++i){
scanf("%d", &a[i]);
}
배열 참조하기 : 아무 정수표현식이나 넣어도 됨
a[i+j*10] = 0;
배열 초기화하기 :
int a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int a[10] = {1, 2, 3, 4, 5, 6}; // {1, 2, 3, 4, 5, 6, 0, 0, 0, 0}
int a[10] = {0, }; // {0, 0, 0, 0, 0, 0, 0, 0, 0, 0} 의도적으로 쉼표를 넣어 배열 길이보다 더 적게 초기화시켰다는 것을 알림
int a[] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 초기화시킬 거면 길이 지정해주지 않아도 됨
int a[15] = {[2]=34, [5]=3, [13]=21}; //지정 초기화
배열 크기 출력하기 :
sizeof(a) //10개의 정수를 갖는 배열이면 40 (각 정수가 4byte를 필요로 한다고 가정)
sizeof(a[0]) //배열의 원소 크기를 측정 -> 배열의 길이를 알 수 있음
예제)
//배열 순서 뒤바꾸기
//reverse.c
#include <stdio.h>
#define N 10
int main(void){
int a[N];
int i; // C99 이후부터 for루프 내에 변수 선언 가능
for(i=0; i<N; ++i){ //전위 연산자 사용하는 이유? 값 복사를 쓰지 않기 때문에 포인터나 객체에서 빠르다고 함 (작동 원리에 차이는 없음)
scanf("%d", &a[i]);
}
for(i=N-1; i>=0; --i){
printf("%d", a[i]);
}
return 0;
}
'정글' 카테고리의 다른 글
| 포인터.. 왜 쓸까? (2) | 2025.04.18 |
|---|---|
| Data-Structures Binary Search Tree Q4 - Post Order Iterative 풀이 (2) | 2025.04.17 |
| [정글] c언어 특강 (2) | 2025.04.16 |
| [C언어] 기초 정리(2) - 포인터 (0) | 2025.04.11 |
| [정글/회고] 알고리즘 특강 (0) | 2025.03.19 |