C 기본 문법
기본 형태
1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
return 0;
}
위에 형태가 C를 사용하는 기본 형태다.
차근 차근 뜯어보자
1
#include <stdio.h>
해당 부분은 stdio.h이라는 헤더파일을 include 즉 포함한다는 의미로 외부 소스 파일에 정의된 변수나 함수를 쓰기 위해한다고 보면 된다.
여기서 stdio.h은 Tandard Input Output header 의 약자로 C에서 제공하는 입출력을 위한 표준 라이브러리다
1
2
3
4
5
int main(){
return 0;
}
여기서 int main(){} 부분은 프로그램이 가장 먼저 찾는 곳이며 이곳에서부터 실행을 시작하는 곳이다.
return 0;에 의미는 성공적인 종료이며 main()으로 return 되면 프로그램이 종료된다 C99 버전 업데이트 이후 이제는 개발자가 직접쓰지 않아도 성공적으로 종료되면 알아서 리턴하기 때문에 생략해도 문제는 없다.
주석
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(){
// 한줄 주석
/*
여러 줄 주석
*/
return 0;
}
주석이란 코멘트로 자신의 코드에 대해 설명을 해주는 것 컴퓨터에는 아무런 도움이 되지 않는 것이므로 컴파일러는 이 주석을 완전히 무시해 버린다. 주석을 쓰는 습관을 들여야 나중에 남들이 프로그램을 잘 알아볼 수 있고 자신이 짠 코드를 시간이 지나고 자신이 못 알아보는 불상사를 막을 수 있다.
변수
영어로는 Variable 우리말로는 변수라고 지칭하는 변수는 바뀔 수 있는 어떤 값을 보관하는 곳을 말한다
코드
1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
int a;
return 0;
}
이런 식으로 앞에 자료형을 적어주고 뒤에 이름을 적는 방식으로 선언 할 수 있다.
코드
1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
int num = 10;
return 0;
}
해당 코드는 int 형 num이라는 변수에 10을 넣는다는 뜻이다
이와 같은 변수에 앞에 사용할 수 있는 자료형은 다양한데
- 정수형
- char (문자형) : 1바이트 = -128~127
- short : 2바이트 = -32,768~32,767
- int : 4바이트 = -2,147,483,648~ 2,147,483,647
- long : 4바이트 = -2,147,483,648~ 2,147,483,647
- long long : 8바이트 = -9,223,372,036,854,775,808~ 9,223,372,036,854,775,807
- 실수형
- float : 4바이트 = -2,147,483,648~ 2,147,483,647
- double : 8바이트 = ±1.7 ×10^-307이상 ± 3.4 × 10^308 이하
- long double : 8바이트 이상 = double 이상
대략 이 정도가 있다 여기서 unsigned를 붙이면 음수없이 0부터 표현한다고 보면 된다
변수명 짓는 규칙
- 변수명에 띄어쓰기가 있으면 안된다.
- 숫자가 맨 앞에 위치하면 안된다.
- 변수명은 오직 영어,숫자,_ 로만 구성하는 걸 권장한다.
- 변수명이 C언어 예약어면 안된다 .
- 대소문자가 다르면 다른 변수명이다.
상수
상수란 변하지 않는 데이터를 담아 놓은 공간으로 변수와 비슷하지만 변하지 않는다는 차이점이 있다
코드
1
2
3
4
5
6
7
8
#include <stdio.h>
int main(){
const int num = 10;
return 0;
}
해당 코드는 num이라는 int형 상수에 10을 넣는다는 의미다
상수로 만들어주는 방법은 const을 변수 타입 앞에 선언해주면 된다
입출력
출력
코드
1
printf("Hello, World! \n");
출력
1
Hello, World!
printf는 괄호안에 적은 것들을 출력하는 함수이다.
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <stdio.h>
int main(){
char a = 'f';
char str[20] = "BlockDMask";
char* pStr = str;
int num1 = 10;
int num2 = -10;
printf("문자 출력 : %c\n", a);
printf("문자열 출력 : %s\n", str);
printf("10진 정수 출력(부호O) : %d\n", num1);
printf("10진 정수 출력(부호O) : %d\n", num2);
printf("10진 정수 출력(부호X) : %u\n", num1);
printf("두개 출력 : %d %d",num1,num2);
printf("8진 정수 출력(부호X) : %o\n", num1);
printf("8진 정수 출력(부호X) : %x\n", num1);
printf("주소 출력 : %p\n", pStr);
printf("주소 출력 : %p\n", str);
printf("주소 출력 : %p\n", &num1);
printf("주소 출력 : %p\n", &num2);
printf("기호 출력 : %%\n");
return 0;
}
출력
1
2
3
4
5
6
7
8
9
10
11
12
13
문자 출력 : f
문자열 출력 : BlockDMask
10진 정수 출력(부호O) : 10
10진 정수 출력(부호O) : -10
10진 정수 출력(부호X) : 10
두개 출력 : 10 -10%
8진 정수 출력(부호X) : 12
8진 정수 출력(부호X) : a
주소 출력 : 008FF904
주소 출력 : 008FF904
주소 출력 : 008FF8EC
주소 출력 : 008FF8EO
기호 출력 : %
%d와 같은 서식 문자 사용 후 ““뒤에 , &num2 와 같이 해당하는 서식 문자와 같은 타입에 변수를 적어주면 & 뒤에 있는 변수 값을 출력 할 수 있다
입력
예제 코드
1
scanf("%d", &num)
scanf는 문자열을 입력받는 함수로 printf처럼 따옴표 안에 서식 문자를 쓰고 따옴표 뒤에,를 적은 뒤 해당하는 타입에 변수를 & 뒤에 적어주면 해당 변수에 우리가 입력한 값이 들어간다.
예제 코드
1
scanf("%d, %d", &num1, &num2)
이런식으로 작성하면 여러개를 입력 받을 수 있다
printf와 scanf에서 사용하는 특수기호
- \’ : 작은따옴표
- \” : 큰 따옴표
- \? : 물음표
- \\ : 백 슬래시(\)
- \n : 줄 바꿈, 개행, new line
- \t : 수평 탭 (tab)
printf와 scanf에서 사용하는 서식 문자
- %c : 문자 표기
- %s : 문자열 표기
- %f, %lf : 실수 표기
- %u : 10진 정수 (부호 없음)
- %d : 10진 정수 (부호 있음)
- %o : 8진 정수 (부호 없음)
- %x : 16진 정수 (부호 없음)
- %lu : long 타입 (부호 없음)
- %ld : long 타입 (부호 있음)
- %llu : long long 타입 (부호 없음)
- %lld : long long 타입 (부호 있음)
- %p : 메모리 주소 표기
- %% : 기호 % 표기
연산자
산술 연산자
산술 연산자란 사칙연산을 다루는 기본적이면서도 가장 많이 사용되는 연산자를 뜻하며 그 종류로는 +, -, *, /, % 가 있다
대입 연산자
대입 연산자는 변수에 값을 대입할 때 사용하는 이항 연산자이다 종류로는 = 과 산술 연산자와 결합한 +=,-=,*=,/=,%가 있다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main() {
int a, b;
a = 10;
b = 5;
printf("a + b 는: %d \n", a + b);
printf("a - b 는: %d \n", a - b);
printf("a * b 는: %d \n", a * b);
printf("a / b 는: %d \n", a / b);
printf("a %% b 는: %d \n", a % b);
return 0;
}
출력
1
2
3
4
5
a + b 는: 15
a - b 는: 5
a * b 는: 50
a / b 는: 2
a % b 는: 0
- a = 10 a에 10을 넣는다
- b = 5 b에 5를 넣는다
- a + b a와 b를 더한다
- a - b a와 b를 뺀다
- a * b a와 b를 곱한다
- a / b a와 b를 나눈다
- a % b a와 b를 나눈 값에 나머지를 구한다
앞에서 미리 설명한 대입연산자에서 복한 연산자에 의미를 보면
- a += b 는 a = a + b와 같다
- a -= b 는 a = a - b와 같다
- a *= b 는 a = a * b와 같다
- a /= b 는 a = a / b와 같다
- a %= b 는 a = a % b와 같다
증감연산자
a++,++a,a–,–a와 같은 것을 증감연산자라고 하며 의미는 ++a는 a에 1을 더한 후 반환 a++는 a를 반환 후 1을 더함
비교 연산자
비교 연산자는 왼쪽의 피연산자와 오른쪽의 피연산자를 비교하여 상대적인 크기를 판단하는 연산자를 말한다
- ㅤ== 왼쪽의 피연산자와 오른쪽의 피연산자가 같으면 참을 반환
- ㅤ!= 왼쪽의 피연산자와 오른쪽의 피연산자가 같지 않으면 참을 반환
- ㅤ> 왼쪽의 피연산자가 오른쪽의 피연산자보다 크면 참을 반환
- ㅤ>= 왼쪽의 피연산자가 오른쪽의 피연산자보다 크거나 같으면 참을 반환
- ㅤ< 왼쪽의 피연산자가 오른쪽의 피연산자보다 작으면 참을 반환
- ㅤ<= 왼쪽의 피연산자가 오른쪽의 피연산자보다 작거나 같으면 참을 반환
논리 연산자
논리 연산자는 주어진 논리식을 판단하여, 참(true)과 거짓(false)을 결정하는 연산자를 말한다
- && AND 논리식이 모두 참이면 참을 반환
OR 논리식 중에서 하나라도 참이면 참을 반환 - ! NOT 논리식의 결과가 참이면 거짓을, 거짓이면 참을 반환
조건문
조건문이란 주어진 조건식의 결과에 따라 별도의 명령을 수행하도록 제어하는 명령문이다
if문
if 문은 조건식의 결과가 참(true)이면 주어진 명령문을 실행하고 거짓(false)이면 아무것도 실행하지 않는 조건문을 말한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main() {
int apple = 4;
if(apple>5){
printf("사과가 5개보다 많습니다");
}
else if (apple>3){
printf("사과가 3개보다 많습니다");
}
else {
printf("사과가 3개보다 적습니다");
}
return 0;
}
출력
1
사과가 3개보다 많습니다
이것이 if문에 형태이다 차근차근 뜯어보면
1
2
3
if(apple>5){
printf("사과가 5개보다 많습니다")
}
if를 적고 괄호 안에 조건을 적는다 여기서 만약 조건이 참이라면 중괄호에 적은 코드를 실행시킨다
1
2
3
else if (apple>3){
printf("사과가 3개보다 많습니다");
}
else if는 if문이 거짓일 때 실행된다 if랑 마찬가지로 괄호 안에 조건을 적고 여기서 만약 조건이 참이라면 중괄호에 적은 코드를 실행 시킨다
앞에서 apple을 4로 선언했으니 해당 조건 참이 되어 중괄호 안 코드인 printf(“사과가 3개보다 많습니다”)가 실행되어 “사과가 3개보다 많습니다”라는 문장이 출력되게 된다
1
2
3
else {
printf("사과가 3개보다 적습니다")
}
여기서 else는 위에 적은 조건들이 모두 거짓일 때 실행되는 곳이다
논리 연산자
논리 연산자인 &&와 | 를 사용하면 if문에 조건을 추가할 수 있는데 |
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
int main() {
int apple = 4;
int banana = 5;
if(apple>5 && banana>4){
printf("과일이 매우 많습니다");
}
else if (apple>5 || banana>4){
printf("과일이 많습니다");
}
else {
printf("과일이 적습니다");
}
return 0;
}
출력
1
과일이 많습니다
차근 차근 코드를 뜯어보자
1
2
3
if(apple>5 && banana>4){
printf("과일이 매우 많습니다");
}
우선 apple>5 && banana>4은 apple이 5보다 크다 와 banana가 4보다 크다는 조건을 모두 만족해야만 참이 된다
1
2
3
else if (apple>5 || banana>4){
printf("과일이 많습니다");
}
apple>5 || banana>4는 apple이 5보다 크다 와 banana가 4보다 크다는 조건 중 하나만 만족해도 참이 된다 그러니 “과일이 많습니다”라는 문장이 출력 된 것이다
조건문에서 자주 쓰는 조건 연산자
- ㅤ>=: 좌변이 우변보다 같거나 크면 참이 된다
- ㅤ>: 좌변이 우변보다 크면 참이 된다
- ㅤ<=: 좌변이 우변보다 작거나 같으면 참이 된다
- ㅤ<: 좌변이 우변보다 작으면 참이 된다
- ㅤ==: 좌변과 우변이 같으면 참이 된다
- ㅤ!=: 좌변과 우변이 다르면 참이 된다
삼항 연산자
삼항 연산자는 if 조건문을 짧게 표현할 수 있는 장점을 가진 연산자이다 하지만 가독성을 해칠 수 있기에 가독성을 해치지 않으면서 코드가 간결해지는 경우에만 삼항 연산자를 써야한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main(){
int num1 = 5;
int num2;
num2 = num1 ? 100 : 200;
printf("%d\n", num2);
}
출력
1
100
위 코드를 뜯어보자
1
num2 = num1 ? 100 : 200;
이 코드의 의미는 num1이 참이면 num2에 100을 넣고 거짓이면 200을 넣는다는 의미이다 이처럼
삼항연산자는 참 거짓을 판단할 변수를 적고 ? 뒤에 참일 때 값 : 뒤에는 거짓일 때 값으로 생겼다
앞에서 배운 비교 연산자와 함께 사용하면
코드
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(){
int num1 = 10;
int num2;
num2 = num1 == 10 ? 100 : 200;
printf("%d\n", num2);
}
출력
1
100
해당 코드를 뜯어보자
1
num2 = num1 == 10 ? 100 : 200;
이 코드의 의미는 num1이 만약 10이라면 num2에 100을 넣고 아니라면 200을 넣는다는 의미이다
Switch문
Switch문은 if문과 같은 조건 제어문으로 if랑 달리 <,>,<=,>=등과 같은 범위를 사용할 수 없고 if문은 조건식이 true일 경우에 실행된다고 하면 switch문은 비교할 변수가 어떤값을 가지냐에 따라 실행문을 선택되는 방식이다 ==만 사용하는 if이라고 생각하면 편하다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
int main() {
int apple = 4;
switch (apple){
case 1:
printf("사과 1개 있습니다");
break;
case 2:
printf("사과 2개 있습니다");
break
case 3:
printf("사과 3개 있습니다");
break
case 4:
printf("사과가 4개 있습니다");
break
default:
break;
}
return 0;
}
출력
1
사과가 4개 있습니다
이것이 switch문에 형태이다
뜯어보면 switch 열 괄호에 변수를 적고 case 옆에 값을 적으면 그 값이 참이면 밑에 문장이 실행되고 거짓이면 실행되지 않는다
만약 case에 값이 모두 거짓이면 default가 실행된다 그리고 switch문을 사용할 때는 case에 실행할 문장과 break를 적어야 한다
위에서는 apple을 4라고 적었으니 case 4:에서 걸려 안에 문장인 printf(“사과가 4개 있습니다”)가 실행되고 break로 탈출한 것이다
반복문
반복문이란 프로그램 내에서 똑같은 명령을 일정 횟수만큼 반복하여 수행하도록 제어하는 명령문을 말한다
대표적으로 c언어에서 많이 쓰이는 반복문은 while문,do-while문,for문이 존재한다
for문
코드
1
2
3
4
5
6
#include <stdio.h> int main() {
for (int i = 1; i < 10; i++{
printf(" %d \n",i);
}
}
출력
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
자세히 뜯어보자 우선 for문은 for 옆에 괄호에 초기식; 조건식; 증감식 그리고 중괄호 안에 실행할 코드가 있는 형식을 기본 구조로 가지고 있다
1
2
3
for (int i = 1; i < 10; i++{
printf(" %d \n",i);
}
위 코드를 보면 int i에 초깃값은 1로 설정되어 있고 i < 10보다 작으면 참으로 아래 적은 코드가 실행된다는 뜻이다 그리고 코드가 한법 실행시킬 때마다 i++ 즉 i가 1씩 증가한다는 것을 알 수 있다
제어 변수가 존재하지 않으면 무한히 반복하게 될 것이기 때문에 제어 변수를 통해 특정한 조건을 만족할 때만 반복을 계속하게 한다
반복문에서 사용하는 제어문
반복문에서 사용하는 자주 사용하는 제어문 중에는 continue와 break가 있다
각 역할을 살펴보면 break는 반복문을 종료시켜 반목문을 탈출하는 역할을 하고 continue는 반복문을 빠져나가지 않고 패스시켜주는 역할은 한다
while문
코드
1
2
3
4
5
6
7
#include <stdio.h>
int main() {
int i = 1;
while (i < 10) {
printf("%d \n",i);
i++;
}
출력
1
2
3
4
5
6
7
8
9
1
2
3
4
5
6
7
8
9
while문에 구조를 뜯어보면 while 옆에 괄호에 조건식을 적고 중괄호에 실행시킬 코드가 있는 간단한 형태로 되어있다
조건식이 참이라면 계속해서 while문을 돌아 코드를 실행시키는 구조를 가져있기 때문에 제어 변수 등으로 종료시켜주어야만 한다
1
2
3
4
5
int i = 1;
while (i < 10) {
printf("%d \n",i);
i++;
}
위에 코드를 살펴보면 초기 i에 값이 1이고 while은 i가 10보다 작다가 조건식이니 9까지 반복하다가 10이 될 때 종료된 것이다
do-while문
do-while문은 while문과 비슷한데
코드
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main() {
int i = 1;
do {
printf("%d \n");
i++;
}
while (i < 1);
}
출력
1
1
do-while문에 구조를 뜯어보면 do 옆에 중괄호안에 실행할 명령들을 적고 밑에 while에 조건식을 적는 형태를 가지고 있다
일단 조건식이 참이던 거짓이든 do안에 있는 코드를 실행한다는 점만 제외하면 while문과 같다고 보면 된다
1
2
3
4
5
6
7
int i = 1;
do {
printf("%d \n");
i++;
}
while (i < 1);
위에 코드를 보면 초기 i 값이 1인데 조건식이 i가 1보다 작다이니 while문이었다면 실행이 안 됐겠지만 do-while문이기에 일단 실행하여 i 값을 출력하고 종료된다
타입 변환 (형 변환)
C언어에서 다른 타입끼리의 연산은 우선 피연산자들을 모두 같은 타입으로 만든 후에 수행되는데 이처럼 하나의 타입을 다른 타입으로 바꾸는 행위를 타입 변환 또는 형 변환이라고 한다
c에서는 크게 두가지 타입 변환 방식이 있는데
- 묵시적 타입 변환(자동 타입 변환)
- 명시적 타입 변환(강제 타입 변환)
1번은 컴파일러가 자동으로 해주는 부분이니 넘어가고
2번 강제 타입 변환을 보자
코드
1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
int a = 2;
float b = (float)a;
printf(" 형 변환 없음 : %d 형 변환 : %f",a,b);
}
출력
1
형 변환 없음 : 2 형 변환 : 2.000000
이렇게 어떠한 변수의 형을 변화하려면 (바꾸려는 형) 변수 이름을 사용하면 된다
배열
배열(array)은 같은 타입의 변수들로 이루어진 유한 집합이다 배열을 구성하는 각각의 값을 배열 요소라고 하며 배열에서의 위치를 가리키는 숫자는 인덱스라고 한다
배열을 선언만 하고 초기화하지 않으면 각 배열 요소에 아무런 의미를 가지지 않는 쓰레기값이 저장되어 있게 된다
코드
1
2
3
4
5
6
#include <stdio.h>
int main() {
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
printf("%d",arr[1]);
}
출력
1
2
배열은 이처럼 (배열의 형) (배열의 이름)[원소 개수]; 로 선언 할 수 있다
다차원 배열
다차원 배열이란 2차원 이상의 배열을 의미하며 배열 요소로 또 다른 배열을 가지는 배열을 의미한다 하지만 보통 2차원 배열까지 만을 주로 사용한다
코드
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
int arr[3][3] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
printf("arr 배열의 2 행 3 열의 수를 출력 : %d \n", arr[1][2]);
printf("arr 배열의 1 행 2 열의 수를 출력 : %d \n", arr[0][1]);
}
출력
1
2
arr 배열의 2 행 3 열의 수를 출력: 6
arr 배열의 1 행 2 열의 수를 출력: 2
2차원에 배열에 선언법은 위에 적은 코드와 같이
타입 배열이름[열의길이][행의길이]로 정의 할 수 있다
1
int arr[2][3] = {1, 2, 3, 4, 5, 6};
잘 사용하지는 않지만 3차원 배열 선언법은
1
(배열의 형)(배열의 이름)[x][y][z];
이런 식으로 대괄호 안에 배열 크기를 적으면 된다
그 이상에 배열들도 이와 비슷하게 대괄호만 추가해 주면 된다
포인터
포인터란 메모리 상에 위치한 특정한 데이터의 (시작)주소값을 보관하는 변수을 말한다
1
포인터에 주소값이 저장되는 데이터의 형) *(포인터의 이름);
포인터는 위 코드와 같이 써서 사용할 수 있다
단항 & 연산자
단항 & 연산자는 피연산자의 주소값을 불러오는 연산자로
1
&(주소값을 계산할 데이터)
이렇게 사용할 수 있다
코드
1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>
int main() {
int *p;
int a;
p = &a;
printf("포인터 p 에 들어 있는 값 : %p \n", p);
printf("int 변수 a 가 저장된 주소 : %p \n", &a);
}
출력
1
포인터 p 에 들어 있는 값 : 0x7fff894c8b3c int 변수 a 가 저장된 주소 : 0x7fff894c8b3c
위 코드를 뜯어보면 포인터 p에
1
p=&a;
로 a가 저장된 주소를 넣었으니 같은 주소가 출력된다
물론 포인터 또한 변수 이기 때문에 특정한 메모리 공간을 차지하여 자신의 주소를 가진다는 것을 명심해야 한다
단항 * 연산자
단항 * 연산자는 주소값에서 해당 주소값에 대 응되는 데이터를 가져오는 연산자로
1
*(소 값을 보관하는 데이터의 형)
이렇게 사용할 수 있으며
단항연산자 * 의 의미는 나에 저장된 주소값에 해당하는 데이터로 생각하라는 뜻이다
코드
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main() {
int *p;
int a;
p = &a;
a = 2;
printf("a 의 값 : %d \n", a);
printf("*p 의 값 : %d \n", *p);
}
출력
1
2
a 의 값 : 2
*p 의 값 : 2
위 코드를 뜯어보면 a에 주소를 포인터 p에 넣고
1
a = 2;
라고 a에 값을 2로 만들었으니 포인터 p에 출력 또한 2로 출력된다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main() {
int *p;
int a;
p = &a;
p = 3;
printf("a 의 값 : %d \n", a);
printf("*p 의 값 : %d \n", *p);
}
출력
1
2
a 의 값 : 3
*p 의 값 : 3
위 코드 또한 뜯어보면 앞에서 말한 것처럼 단항 연산자 *는 포인터에 저장된 주소에 변수와 동일한 데이터로 인식하게 하니
1
p = &a;
로 변수 a에 주소가 담긴 포인터 p에
1
p = 3;
을 한 것은 a = 3과 같은 의미를 지녀 a에 값이 3으로 출력된 것이다
정리
코드
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int main(){
int a = 10;
int *P;
P = #
}
위와 같은 코드가 있을 때
1
2
3
4
5
6
* a = 10
* &a = 100
* p = 100
* *p = 10
* &p = 200
a에 주소와 p에 주소를 보기 쉽게 100과 200으로 가정하면 이렇게 요약할 수 있다
즉 앞에 기호가 없으면 자신의 값이 나오고 *가 붙으면 주소에 담긴 값을 &가 붙으면 자신의 주소 값이 나온다
라고 정리할 수 있다
포인터의 연산
포인터는 값을 증가시키거나 감소시키는 등의 제한된 연산만을 할 수 있다
그 규칙으로는
포인터끼리의 덧셈, 곱셈, 나눗셈은 아무런 의미가 없다
포인터끼리의 뺄셈은 두 포인터 사이의 상대적 거리를 나타낸다
포인터에 정수를 더하거나 뺄 수는 있지만 실수와의 연산은 허용하지 않는다
포인터끼리 대입하거나 비교할 수 있다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
int main() {
int a;
char b;
double c;
int* pa = &a;
char* pb = &b;
double* pc = &c;
printf("pa : %p \n",pa);
printf("pa+1 : %p \n",pa+1);
printf("pb : %p \n",pb);
printf("pb+1 : %p \n",pb+1);
printf("pc : %p \n",pc);
printf("pc+1 : %p \n",pc+1);
출력
1
2
3
4
5
6
pa : 0x7ffcf64a2e04
pa+1 : 0x7ffcf64a2e08
pb : 0x7ffcf64a2e03
pb+1 : 0x7ffcf64a2e04
pc : 0x7ffcf64a2e08
pc+1 : 0x7ffcf64a2e10
위 코드를 보면 int 형인 포인터 pa는 주소 값이 int 값인 4만큼만 증가하고 char 형인 pb는 주소 값이 char값인 1만큼 증가,double형인 pc는 8만큼 증가한 걸 볼 수 있다
코드
1
2
3
4
5
6
7
8
#include <stdio.h>
int main() {
int a;
int* pa = &a;
printf("pa 의 값 : %p \n", pa);
printf("(pa - 1) 의값: %p \n", pa - 1);
}
출력
1
2
pa 의 값 : 0x7ffe4f4fa47c
(pa - 1) 의 값 : 0x7ffe4f4fa478
마찬가지로 4만큼 빠진 걸 볼 수 있다
이중 포인터
이중 포인터란 포인터의 (시작)주소값을 보관하는 변수을 말한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int main()
{
int *P1;
int **P2;
int num = 10;
P1 = #
P2 = &P1;
printf("%d\n", **p2);
return 0;
}
출력
1
10
코드를 뜯어보면
1
2
3
P1 = #
P2 = &P1;
P1 포인터에 num에 주소 값을 담고 P2라는 이중 포인터에 P1 포인터에 주소 값을 담는다 그 후 포인터를 한 번 역참조하면
P1에 담겨진 num에 주소 값이 나왔겠지만 **로 포인터를 두 번 역참조하여 num에 값을 출력한다
포인터 배열
포인터 배열이란 말그대로 포인터들의 배열을 말한다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main() {
int *arr[3];
int a = 1, b = 2, c = 3;
arr[0]= &a;
arr[1] = &b;
arr[2] = &c;
printf("a : %d, *arr[0] : %d \n", a, *arr[0]);
printf("b : %d, *arr[1] : %d \n", b, *arr[1]);
printf("b : %d, *arr[2] : %d \n", c, *arr[2]);
printf("&a : %p, arr[0] : %p \n", &a, arr[0]);
}
출력
1
2
3
4
a : 1, *arr[0] : 1
b : 2, *arr[1] : 2
b : 3, *arr[2] : 3
&a : 0x7ffe8a2fa4e4, arr[0] : 0x7ffe8a2fa4e4
위 코드를 뜯어보자
1
int *arr[3];
이 부분은 말 그대로 int,char형 포인터와 같이 배열의 형을 int*로 선언한 것이다
여기서 배열의 각각의 원소는 int 를 가리키는 포인터 형으로 선언된다
1
2
3
arr[0]= &a;
arr[1] = &b;
arr[2] = &c;
이 부분은 배열에 각 인덱스에 변수에 주소를 집어넣은 것이다
그러니
1
2
3
a : 1, *arr[0] : 1
b : 2, *arr[1] : 2
b : 3, *arr[2] : 3
위와 같은 출력이 나온 것이다
함수
프로그래밍에서 함수(function)란 하나의 특별한 목적의 작업을 수행하기 위해 독립적으로 설계된 프로그램 코드의 집합이며
크게 표준 함수와 사용자 정의 함수로 구분할 수 있다
C언어에서 함수를 정의하는 방법은
반환 자료형과 함수이름 괄호안에 매개변수 목록 중괄호인 함수 몸체안에 함수가 실행할 코드를 적으면 된다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
int print_hello() {
printf("Hello!! \n");
}
int main() {
printf("함수를 불러보자 : ");
print_hello();
printf("또 부를까? ");
print_hello();
}
출력
1
2
함수를 불러보자 : Hello!!
또 부를까? Hello!!
코드를 뜯어보면 앞에서
1
2
3
4
5
int print_hello() {
printf("Hello!! \n");
}
로 print_hello라는 Hello!!\n을 출력하는 함수를 정의 했고
1
2
3
4
5
6
7
int main() {
printf("함수를 불러보자 : ");
print_hello();
printf("또 부를까? ");
print_hello();
}
에서 두번 호출하였기에 출력과 같은 결과가 나온 것이다
return
return은 뜻 그대로 현재 함수에서 빠져나가 그 함수를 호출한 곳으로 되돌아 가게 하는 것과 함수내의 변수 또는 일정값을 되돌려 주는 역할은 한다
코드
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int return_func() {
printf("난 실행된다 \n");
return 0;
printf("난 안돼 ᅲᅲ \n");
}
int main() {
return_func();
}
출력
1
난 실행된다
함수가 종료되는 방법은 두 가지가 있는데 하나는 반환이 되어 종료를 하게 되는 것이고 다른 하나는 함수의 끝 부분 까지 실행하여 종료되는 것이다
그렇기에 return 0에서 함수가 종료되어 뒤에 문장을 실행되지 못한 것이다
코드
1
2
3
4
5
6
7
8
#include <stdio.h>
int ret() {
return 1000;
}
int main() {
int a = ret();
printf("ret() 함수의 반환값 : %d \n", a);
}
출력
1
ret() 함수의 반환값 : 1000
해당 코드를 살펴보면 ret함수는 호출 시 1000을 리턴시키는 함수이기 때문에 a에 값은 1000이 된 것이댜
함수의 인자
여기서 함수에 대해 더 자세히 알아보면
코드
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int p() {
i += 4;
return 0;
}
int main() {
int i = 3;
p();
printf("i 값은 : %d \n", i);
}
출력
1
Error
해당 코드가 오류가 나는 까닭은 어떠한 함수를 호출할 때 호출된 함수는 함수를 호출한 곳에 대해서 어떠한 것도 알고 있지 않기 때문이다
즉 main 함수에서 box 함수를 호출하였을 때 main에서는 i가 3이라고 정의 되어있었지만
함수 p에서는 i가 몇인지 모르기 때문에 오류가 난 것이다
해당 함수를 제대로 사용하려면
코드
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int p(int *a) {
*a += 4;
return 0;
}
int main() {
int i = 3;
p(&i);
printf("i의 값은 : %d \n", i);
}
출력
1
i의 값은 : 7
이렇게 해야 우리가 목표하던 7이라는 값을 얻을 수 있다
코드를 자세히 뜯어보자
1
int p(int *a)
이 부분은 p를 호출할 때 어떤 값을 int 형 포인터a에 매개변수로 받아드리겠다는 것이고
1
p(&i);
이 부분은 p함수 안에서 정의된 포인터 a에 i에 주소를 전달하겠다는 의미이다 이렇게 되면
p에 매개 변수인 포인터 a에 i에 주소가 저장되어 a는 main에 있는 i를 가르키는 포인터가 되서 최종적으로 i 값을 7로 변경할 수 있게 된 것이다
이렇게 포인터를 사용하는 까닭은
코드
1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
int p(int i) {
i += 4;
return i;
}
int main() {
int i = 3;
p(i);
printf("i의 값은 : %d \n", i);
}
출력
1
i의 값은 : 3
포인터를 사용하지 않았을 때 이러한 결과가 나오기 때문이다
이러한 결과가 나오는 까닭은 알아보면
1
p(i);
부분으로 i에 값을 담아 보내면 main 함수에 i에 값인 3이 p함수에 전달되어 p함수에 i에 3이 들어간다 하지만 main에서 i와 p에서 i는 값과 함수명만 같을 뿐
전혀 다른 메모리 상의 또 다른 변수이기 때문에 p함수 속 i가 return 된다고 하여도 main함수 속 i에게 영향을 주지 못한다
그렇기에 포인터를 사용하는 것이다
함수의 원형
함수의 원형이란 함수의 정의 부분을 코드에 제일 윗부분에 써주어 컴파일러에게
소스코드내에 사용되는 함수에 대한 정보를 사용하는 것이다 다만 실제 프로그램에는 반영되지 않는다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
int swap(int *a, int *b); int main() {
int i, j;
i = 3;
j = 5;
printf("SWAP 이전: i : %d, j : %d \n", i, j);
swap(&i, &j);
printf("SWAP 이후: i : %d, j : %d \n", i, j);
}
int swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
return 0;
}
출력
1
2
SWAP 이전: i : 3, j : 5
SWAP 이후: i : 5, j : 3
함수 포인터
함수 포인터란 메모리 상에 올라간 함수의 시작 주소를 가리키는 역할을 하는 포인터를 말한다
함수의 시작 주소 값은 배열과 마찬가지로 이름이 그 시작 주소를 나타낸다
함수의 포인터의 선언법은
1
(함수의 리턴형) (*포인터 이름)(인자 타입)
위와 같다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <stdio.h>
int max(int a, int b);
int main() {
int a = 3, b = 5;
int (*pmax)(int, int);
pmax = max;
printf("a : %d \nb : %d \n",a,b);
printf("max(a,b) : %d \n", max(a, b));
printf("pmax(a,b) : %d \n", pmax(a, b));
}
int max(int a, int b){
if (a > b)
return a;
else
return b;
return 0;
}
출력
1
2
3
4
a : 3
b : 5
max(a,b) : 5
pmax(a,b) : 5
코드를 뜯어보자
1
2
int (*pmax)(int, int);
pmax = max;
이 부분은 반환형이 int 값이고 인자의 타입이 int형 2개인 함수를 가르리키는 포인터 pmax를 정의하고
그 정의와 일치한 max함수에 첫 번째 주소를 pmax포인터에 넣은 것이다
그러니 출력이
1
2
max(a,b) : 5
pmax(a,b) : 5
로 동일하게 니온 것이다
문자열
C언어에서는 큰따옴표(““)를 사용해 표현되는 문자열을 문자열 상수라고하며 C언어에서 문자열은 메모리에 저장된 일련의 연속된 문자들의 집합을 의미한다
문자열은 문자형 배열은 선언하여 사용 가능하다
코드
1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main() {
char a[] = "나는 c언어가 좋아요";
printf("%s\n",a);
char b[18] = "I like c language";
printf("%s\n",b);
}
출력
1
2
나는 c언어가 좋아요
I like c language
종료 문자열
문자형 배열로 선언된 문자열 변수는 문자열의 끝을 알려주기 위해 문자열에 속한 데이터가 끝나면 문자열의 끝을 의미하는 문자를 하나 더 삽입한 것이다
이 문자를 널(NULL) 문자라고 하며 ‘\0’으로 표시하고 아스키코드값은 0이다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
int main() {
char null_1 = '\0';
char null_2 = 0;
char null_3 = (char)NULL;
char not_null = '0';
printf("NULL 의 정수(아스키)값 : %d, %d, %d \n", null_1, null_2, null_3);
printf("'0' 의 정수(아스키)값 : %d \n", not_null);
}
출력
1
2
NULL 의 정수(아스키)값 : 0, 0, 0
'0' 의 정수(아스키)값 : 48
위 코드를 뜯어보면
1
2
3
char null_1 = '\0';
char null_2 = 0;
char null_3 = (char)NULL;
이 3개는 모두 널 문자를 뜻하고
1
char not_null = '0';
이것은 문자 ‘0’을 뜻하니
널 문자열에 아스키 코드 값인 0이 3개 문자열 ‘0’에 아스키 코드 값이 48이 출력된 것이다
구조체
구조체란 사용자가 C언어의 기본 타입을 가지고 새롭게 정의할 수 있는 사용자 정의 타입으로 기본 타입만으로는 나타낼 수 없는 복잡한 데이터를 표현할 수 있다는 장점이 있다
구조체의 정의 방법은
1
2
3
4
5
6
7
8
struct 구조체이름{
멤버변수1의타입 멤버변수1의이름;
멤버변수2의타입 멤버변수2의이름;
...
};
위와 같은 방법으로 선언한다
구조체의 사용방법은
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct Human{
int age;
int height;
int weight;
};
int main(){
struct Human a;
a.age = 99;
a.height = 185;
a.weight = 80;
printf("a 에 대한 정보 \n");
printf("나이 : %d \n", a.age);
printf("키 : %d \n", a.height);
printf("몸무게 : %d \n", a.weight);
}
출력
1
2
3
4
a 에 대한 정보
나이 :99
키 :185
몸무게: 80
위와 같이 사용할 수 있다
코드를 뜯어보면
1
struct Human a;
이 부분은 a를 Human 구조체 타입으로 만든 것이고
1
2
3
a.age = 99;
a.height = 185;
a.weight = 80;
그 후 a.age a.height a.weight로 구조체의 멤버에 값을 대입한 것이다
공용체
공용체는 union 키워드를 사용하여 선언하며 바로 모든 멤버 변수가 하나의 메모리 공간을 공유한다는 점을 제외하고 구조체와 같다
또한 모든 멤버 변수가 같은 메모리를 공유하므로 공용체는 한 번에 하나의 멤버 변수밖에 사용할 수 없다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef union{
unsigned char a;
unsigned short b;
unsigned int c;
} SHAREDATA;
int main(){
SHAREDATA var;
var.c = 0x12345678;
printf("%x\n", var.a);
printf("%x\n", var.b);
printf("%x\n", var.c);
}
출력
1
2
3
78
5678
12345678
열거형
열거형는 새로운 타입을 선언하면서 동시에 해당 타입이 가질 수 있는 정수형 상숫값도 같이 명시할 수 있는 타입을 말한다
이러한 열거형을 이용하면 프로그램의 가독성이 높아지고 변수가 지니는 값에 의미를 부여할 수도 있다는 장점이 있다
코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enum Days {MON, TUE, WED, THU, FRI, SAT, SUN};
int main(){
enum Days today;
today = SAT;
if (today >= SAT && today <= SUN){
puts("오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!");
}
else{
printf("주말까지 %d일 남았어요~ 조금만 더 힘내자구요!", 5 - today);
}
puts("각각의 열거형에 해당하는 정수값은 다음과 같습니다.");
printf("%d %d %d %d %d %d %d\n", MON, TUE, WED, THU, FRI, SAT, SUN);
}
출력
1
2
3
오늘은 주말이네요~ 주말에도 열심히 공부하는 여러분은 최고에요!
각각의 열거형 멤버에 해당하는 정수값은 다음과 같습니다.
0 1 2 3 4 5 6