Visual Studio 2019 에서의 프로젝트 생성.
파일 - 새로만들기 - 프로젝트 - 빈프로젝트 - 솔루션 탐색기 - 소스파일 오른쪽클릭 - 추가 - 새항목.
기본적으로 .cpp로 나와있지만 .c로 바꿔서 생성한다면 c로 생성이 가능하다.
C++이 C를 기반으로 한 것이기 때문에 확장자 변경만으로 C파일로 변경이 가능하다.
프로젝트 빌드 결과
솔루션 탐색기 오른쪽클릭 - 파일탐색기에서 폴더 열기 - debug로 들어가면 실행파일이 보인다.
라이브러리
C/C++에서는 #include 명령어를 이용해 다양한 라이브러리를 불러올 수 있다.
제일 많이 봤던 stdio는 Standard Input output 으로 입출력 라이브러리다.
코드의 시작
자바와 마찬가지로 main함수로부터 시작한다.
함수는 return 값이 없을 수 있으나 메인함수에서는 항상 0을 반환하는 것이 일반적이라고 한다.
C에서의 기본 출력함수는 printf이다.
system함수는 운영체제의 기본적인 기능을 이용할 수 있다.
공부 중에는 system("pause"); 를 가장 많이 사용하였고 pause명령어는 키보드를 입력하기 전까지 대기하는 기능을
수행한다.
기본적인 자료형
int, string, char, double, long long, bool.
int는 정수, string은 문자열, char은 문자, double은 실수, long long은 숫자가 긴 정수형, bool은 boolean이다.
string은 C 문법에는 포함되지 않지만 C++에서는 사용한다.
C에서 문자열을 포현하기 위해서는 char을 배열형태로 만들어서 사용하면 된다.
char a[20] = "HELLO WORLD" 이렇게 사용하면 문자열 형태처럼 사용할 수 있다.
입력
C에서 값을 입력받기 위해서는 scanf를 사용한다. scanf(%d, &a);
보통 실무에서는 많이 사용하지 않고 처음 이해하고 배울때만 사용을 많이 한다고 한다.
scanf는 C에서 취약한 함수로 분류되기에 visual studio에서는 바로 사용할 수 없도록 막아두었다.
그래서 #define _CRT_SECURE_NO_WARNINGS 를 include위치에 정의해줘야 사용할 수 있다.
scanf에 &를 사용하는 이유는 특정한 메모리 주소에 접근하여 데이터를 수정할 수 있도록 하기 위해서이다.
C언어에서는 포인터를 사용할 때 &를 사용한다.
메모리주소에 얼마만큼의 크기로 데이터를 쓸 지 경정해야 하는데 그럴때 사용하는 것이 형식지정자이다.
int(4byte)
|
%d를 이용해서 정수형 데이터를 입력 및 출력
|
long long(8byte)
|
%lld 를 이용해서 큰 정수형 데이터를 입력 및 출력
|
double(8byte)
|
입력시 %lf 출력시 %f로 큰 실수형 데이터를 처리
|
float(4byte)
|
%f를 이용해서 실수형 데이터를 입력 및 출력
|
string(제한없음)
|
%s를 이용해서 문자열 데이터를 입력 및 출력
|
char(1byte)
|
%c를 이용해서 문자 데이터를 입력 및 출력
|
double에서 입출력시 사용하는 형식지정자가 다른 이유는 입력할 때는 크기를 지정해줘야 하지만 출력할때는
값 자체를 이용해서 출력하는 것이므로 구체적인 크기를 지정하지 않아도 되는 것이다.
만약 %자체를 문자로 출력하고 싶다면 %%로 입력해줘야 문자로 인식하고 % 한개가 출력되게 된다.
실수형에서는 .0으로 소수점 자리수를 지정해준다.
%.2f 이런식으로 표시하는데 .은 소수점을 의미하고 2는 소수점 자리 수, f는 실수라는 의미이다.
한자리씩 끊어서 입력하는 방법도 있는데
scanf("%1d%1d%1d", &a, &b, &c);
printf("%d %d %d",a,b,c);
이렇게 코드를 작성하고 576을 입력한다면 5 7 6 이렇게 출력되게 된다.
한자리씩 끊어서 변수에 입력된다고 생각하면 된다.
이스케이프시퀀스(Escape Sequence)
특정한 표현을 출력하기 위해 사용하는 문법이다.
\n
|
줄바꾸기
|
\t
|
수평 탭 넣기
|
\\
|
백슬레시 넣기
|
\”
|
큰 따옴표 넣기
|
\b
|
백스페이스 넣기
|
포인터
C에 대해 얘기를 들을 때 마다 나왔던 포인터이다. 굉장히 중요하다는 얘기만 많이 들었다.
포인터 변수는 특이한 변수로 메모리 주소를 저장한다.
int a = 5;
int *b = &a;
이러한 코드가 있다면 b가 포인터 변수다. b는 &a라는 값을 갖고 있는데 &는 주소값을 의미하는 주소연산자이기
때문에 a의 주소값을 포인터 변수인 b가 갖고 있게 되는 것이다.
C에서는 그 주소값으로 해당 주소의 값을 가져오기 때문에 포인터 변수 b를 이용해 5라는 a의 값을 가져오는 것이
가능하다.
포인터는 특정 메모리의 주소값에 직접적으로 접근할 수 있기 때문에 사용할 때 조심해야 한다.
포인터 관련 연산자
주소연산자(&)
|
변수 앞에 붙어서 변수의 메모리 시작 주소값을 구한다
|
포인터(*)
|
포인터 변수를 선언할 때 사용한다
|
간접 참조 연산자(*)
|
선언된 포인터 변수가 가리키는 변수를 구한다
|
문자 입출력
char은 아스키 코드를 기준으로 받아 들인다.
a = 65;
printf("%c\n", a);
이 코드를 출력하게 되면 아스키코드표의 65에 해당하는 A가 출력되게 된다.
그래서 문자 입출력함수가 따로 있는데 getchar()이다.
이것은 단 하나의 문자를 입력 받는다.
입력 버퍼로 인해 흔히 발생하는 오류가 있다.
int main(){
int a;
char c;
scanf("%d", &a);
printf("%d\n", a);
scanf("%c", &c);
printf("%c\n", c);
}
위 코드에서 숫자를 입력하고 엔터를 치게 되면 그대로 입력이 끝나게 된다.
숫자는 a에 들어가게 되지만 엔터 또한 문자로 입력되면서 엔터가 c에 들어가면서 입력이 끝나게 되는 것이다.
char은 아스키코드로 관리하다보니 엔터도 문자로 인식하기에 문자로 입력을 받게 되는 것이다.
이때는 남아있는 입력버퍼를 지워줘야 한다.
int main(){
int a;
char c;
scanf("%d", &a);
printf("%d\n", a);
int temp;
while((temp = getchar()) != EOF && temp != '\n'){}
scanf("%c", &c);
printf("%c\n", c);
}
위 코드에서 EOF는 End Of File의 약자로 파일의 끝을 의미한다.
while문을 보면 파일의 끝이나 개행 문자를 만났을 때 입력을 멈추고 버퍼를 비운다는 의미라고 한다.
이 부분이 조금 이해가 안되는 부분이다.
자바를 사용하면서는 !=는 not의 의미이고 &&는 AND의 의미인데 왜 파일의 끝이나 개행문자를 만났을 때 입력을
멈추고 버퍼를 비운다는 것인지 잘 이해가 되지 않는다.
그래서 생각해보니 temp 아래로 코드가 더 있기 때문에 'EOF가 아니다.'는 true가 되고 temp에는 값을 주지 않았기
때문에 \n이 아닌게 되므로 입력버퍼를 비워준다는게 맞는 것 같다. 이거는 다른 C 강의도 찾아보고 기억해야
할 것 같다.
문자열
C언어에서 문자열은 메모리 구조상에서 마지막에 null값을 포함한다.
printf함수를 실행하면 컴퓨터는 내부적으로 null을 만날때까지 출력하도록 되어있기 때문이다.
HELLO WORLD를 출력한다고 하면
0
|
1
|
2
|
3
|
4
|
5
|
6
|
7
|
8
|
9
|
10
|
11
|
H
|
E
|
L
|
L
|
O
|
|
W
|
O
|
R
|
L
|
D
|
\0 (null)
|
이렇게 들어가는 것이다.
그래서 HELLO WORLD는 중간 공백을 포함해서 11글자에 마지막 null값까지 총 12자리의 배열 크기가 되는 것이다.
문자열 형태로 포인터를 사용하면 포인터에 특정한 문자열의 주소를 넣게 된다.
int main(void) {
char *a = "Hello World";
printf("%s\n", a);
}
이런식으로 배열 형태가 아닌 포인터 변수에 문자열 자체를 마치 상수처럼 넣을 수 있다.
이때 이렇게 큰 따옴포안에 문자열이 들어가는 구조를 문자열 리터럴이라고 한다.
이렇게 작성하게 되면 컴파일러가 알아서 문자열이 특정한 메모리 주소에 저장될 수 있도록 남아있는 메모리
공간중에서 주소를 결정해준다.
그리고 그 주소를 포인터인 *a가 갖고 있게 되는 것이다.
이렇게 문자열을 리터럴 방식으로 넣어준다면 읽기전용으로 상수형태로 넣어주는 것이다.
말 그대로 읽기전용이기 때문에 중간에 문자열을 바꿀 수 없고 굳이 변경해야 한다면 포인터에 선언한 문자열 자체를
변경해야 한다.
포인터로 문자열을 선언했다고 하더라고 배열처럼 처리하는 것은 가능하다.
int main(void){
char *a = "Hello World";
printf("%c\n", a[1]);
printf("%c\n", a[4]);
printf("%c\n", a[8]);
}
출력문을 이렇게 변경해준다면 출력결과는
e
o
r
이렇게 출력된다.
포인터로 선언되었지만 그 인덱스를 갖고 출력하는 것은 배열과 동일한 것이다.
C에서 문자열 함수는 <string.h> 라이브러리에 포함되어 있다.
strlen()
|
문자열의 길이를 반환
|
strcmp()
|
문자열 1이 문자열 2보다 사전적으로 앞에 있으면 -1 뒤에 있으면 1을 반환
|
strcpy()
|
문자열을 복사
|
strcat()
|
문자열 1에 문자열2를 더한다
|
strstr()
|
문자열 1에 문자열 2가 어떻게 포함되어 있는지를 반환
|
strlen은 중간 공백가지 포함해서 문자의 수를 출력해준다.
strlen의 경우는 <string.h>를 선언하지 않아도 최근에는 사용이 가능하다고 한다.
strcmp는 사전순이라고 되어있는데 오름차순이라고 생각하면 된다.
a와 c를 비교한다면 a가 사전순으로 앞에 있기 때문에 -1이 출력되는 것이고 반대로 비교한다면 1이 출력된다.
strcpy는
int main(void){
char a[20] = "Hello";
char b[20] = "World";
strcpy(a, b);
printf("복사된 문자 : %s\n", a);
}
이렇게 작성했을 때 World가 출력되게 된다.
앞에 있는 문자열에 뒤에 있는 문자열을 복사해서 붙여넣기 하는 것이다.
strcat은 두 문자열을 붙여준다고 생각하면 된다.
위 코드에서 strcpy가 아닌 strcat으로 바꿔준다면
HelloWorld가 출력되게 되는 것이다.
여기서 주의해야 할 점은 a에 b가 합쳐지는 것이기 때문에 배열 크기를 조절해줘야 한다.
b가 들어가도 될만큼의 여유공간을 a가 갖고 있어야 한다.
strstr은 긴 문자열에서 짧은 문자열을 찾아 그 위치를 반환한다.
int main(void){
char a[20] = "I like you";
char b[20] = "like";
printf("문자열 : %s\n", strstr(a, b));
}
위 코드에서 b의 값인 like의 위치를 찾아 그 뒤의 문자열까지 전체 출력해주기 때문에 like you가 출력된다.
문자열 입출력 함수(gets)
gets는 문자열 입출력을 수행하는 함수이며 scanf는 공백을 만날때까지 입력을 받지만
gets는 공백까지 포함하여 한줄을 입력 받는다.
scanf("%s", &a); 이렇게 사용했지만 gets는 gets(a)이렇게 사용한다.
실무에서는 gets보다는 gets_s를 많이 사용한다고 한다.
gets_s(a, sizeof(a));
이렇게 사용하는데 버퍼의 크기를 지정해주는 방식이다.
gets_s는 버퍼의 크기를 벗어나게 된다면 런타임 오류가 발생하기 때문에 안정성이 보장된다.
기존의 gets함수는 버퍼 이상으로 덮어쓰기 때문에 취약점이 존재한다.
레퍼런스
패스트캠퍼스 올인원 패키지 - 소프트웨어 베이직