독하게 시작하는 C 프로그래밍 1 – 널널한 개발자 TV
https://www.youtube.com/watch?v=I5jmg6uUTbQ&list=PLXvgR_grOs1AQuQ-5mWbx0zdG0betdeoL&index=1
- 제1장 C 프로그래밍 입문 – 첫 번째
- 제1장 두 번째
- 제2장 자료형 – 첫 번째
- 제2장 두 번째
- 제3장 표준 입/출력 도구 – 첫 번째
- 제3장 두 번째
- 제4장 연산자 기본 – 첫 번째
- 독하게 시작하는 C 제4장 두 번째
- 독하게 시작하는 C 제5장 연산자 응용 – 첫 번째
- 독하게 시작하는 C 제5장 두 번째
- 독하게 시작하는 C 제6장 기본 제어문 – 첫 번째
- 독하게 시작하는 C 제6장 두 번째
- 독하게 시작하는 C 제7장 반복문
- 독하게 시작하는 C 제8장 배열
- 독하게 시작하는 C 제9장 – 배열을 활용한 프로그래밍 기법
- 독하게 시작하는 C 제10장 함수에 대한 기본 이론
2. 제1장 두 번째
HelloWorld.c
소스코드 (인간이 인식하는 언어)
High Level 언어
# 컴파일러 : 소스코드를 기계어로 번역
컴파일은 사람이 작성한 소스 코드를 CPU 가 인식할 수 있는 명령어로 번역하는 과정
1단계HelloWorld.c : 설계도
2단계 : HelloWorld.obj : 부품 (컴파일(Compile))
3단계 : HelloWorld.exe : 완성품 (링크(Link)) 실행가능한 기계어 코드
.h 선언
.c 정의
#include <stdio.h>
int main(void)
{
printf("Hello, World\n");
return 0;
}
# 함수 선언 정의
int main(void) : 프로그램 시작
반환형식 함수이름(매개변수)
#include : 전처리기 (프리프로세서) 컴파일 전에
# 컴파일을 했는데 오류가 나면 F4 키를 누루면 해당 오류 라인을 알려줌.
# void : 없음 (명확히 해주기 위해서 적음.)
3. 제2장 자료형 – 첫 번째
# 자료형 : 일정 길이의 메모리에 저장된 정보를 해석하는 방법
32bit : 42.95억
메모리 최대 범위 : 4기가
OS 32 bit : 램 4기가 밖에 못씀.
64비트
2의 64승 16EB
TB -> PB -> EB -> ZB -> YB
자료형은 숫자이다.
8비트 : char, unsigned char
16비트 : short, unsigned short
32비트 : int, unsigned int, long, unsigned long
# 변수는 사용에 앞서 반드시 선언해야 한다.
int a; 선언
a = 10; 정의
int a = 19; 선언 및 정의
변수: 이름, 주소, 값
4. 제2장 두 번째
C99
char : 8비트
short : 16비트
int : 32비트
long : 32비트 또는 64비트
long long int 64비트 %lld (long 형식이 모호해서 탄생됨)
# 부동 소수점 형식
float : 단정도 32비트 (유효 자릿수 소수점 이하 6자리) f 나 F를 붙인다. (없으면 double)
double : 배정도 64비트 (유효 자릿수 소수점 이하 15자리)
long double : 특수정도 (지원 안하는 컴파일러는 long 과 같이 취급) 80비트
(VC++ 은 지원 안함, 리눅스 계열에서 지원)
# 실수 형식 : 오차를 가짐 (근사값 처리 때문에)
# float 형식은 절때 쓰지 마라. 정확도가 떨어진다. 반드시 double을 써라.
# 배열의 이름은 주소
# 문자열 끝에는 항상 널문자가 온다. \0
# char szBuffer[12] (string zero : 널로 끝나는 문자열)
#include <stdio.h>
int main(void)
{
char ch1 = 'A', ch2 = 'B', ch3 = 'C';
char szData[4] = { 'A', 'B', 'C' };
char szNewData[4] = { "ABC" };
char szNewNewData[12] = { 'A', 'B', 'C' }; // ABC 나머지 공간은 0으로 채움
char szNewNewNewData[12] = { "ABC" }; // ABC 나머지 공간은 0으로 채움
return 0;
}
5. 제3장 표준 입/출력 도구 – 첫 번째
USER
Kernel
H/W
# User 는 요구를 하고 I/O 는 커널이 한다.
# buffered I/O
getchar() : 버퍼에서 1글자를 꺼내옴.
scanf() : 버퍼에서 형식 문자에 맞게 꺼내옴. (scanf_s()를 써라.)
gets() : 버퍼에서 한 줄씩 꺼내옴. (심각한 보안결함이 있다. gets_s()를 써라.)
# Buffer : Memory
완충기
유튜브에서 네트워크가 끊김이 있더라도
버퍼를 사용하기 때문에 사용자는 매끄럽게 영상을 감상할 수 있다.
# Non Buffered I/O : 버퍼를 사용하지 않고 직접 입력 받음
_getch() : #include <conio.h>
fflush() : 버퍼를 비우는 함수
6. 제3장 두 번째
getchar()
표준입력장치(stdin)로부터 문자 하나 반환
버퍼가 비어있다면 사용자로부터 입력을 받아 버퍼를 채운 다음 첫 번째 문자를 반환
버퍼가 채워져 있다면 버퍼에서 한 글자 반환
putchar(int c)
표준출력장치(stdout)인 콘솔에 한 글자 출력
putchar(“\n”);
‘\n’ : char (정수값)
“\n” : char[] (주소)
ASLR (Address Space Layout Random) : 메모리 해킹 방지
프로그램을 실행할 때마다 메모리의 위치가 랜덤으로 계속 바뀜
printf()
%c : int (char)
%d : int (부호 있는 10진수)
%o : int (Octal 8진수)
%u : unsigned int (부호 없는 10진수)
%x, %X : 16진수
%e, %E : (float, double) 지수형 소수
%f : double(float) (10진형 소수)
%g : double (%e 나 %f 의 짧은 형태로 출력)
%p : pointer (16진수 주소로 출력)
%s : string (문자열)
# printf(“%d\n”, ‘A’ + 1); // char 형과 int 형을 더하면 int 형으로 변환 (타입 프로모션)
#include <stdio.h>
int main(void)
{
char szName[12] = { "Hello" };
gets_s(szName, sizeof(szName)); // 마지막 fe 는 gets_s 함수가 갖다 붙인거임.
// Linux, UNIX : fgets(szName, sizeof(szName), stdin);
char szName[32] = { 0 };
printf("이름을 입력하세요 :");
gets_s(szName, sizeof(szName));
printf("당신의 이름은 ");
puts(szName);
puts("입니다.");
printf("%d\n", 'A' + 1); // char 형과 int 형을 더하면 int 형으로 변환 (타입 프로모션)
FILE* fp = stdin; // 브레이크포인트를 걸어서 stdin 버퍼를 확인해보자.
getchar();
getchar();
getchar();
getchar();
int nData = 0;
scanf_s("%d", &nData);
return 0;
}
#include <stdio.h>
int main(void)
{
FILE* fp = stdin;
int nAge = 0;
printf("나이를 입력하세요: ");
scanf_s("%d", &nAge); // 엔터키나 스페이스 키는 버퍼에 남아있는다.
getchar(); // 엔터키를 없애기 위해서. 스페이스키는 안됨.
fflush(stdin); // 엔터키를 없애기 위해서 버퍼를 비워준다.. 안해주면 아래 gets_s() 에서 작동 안함. (윈도우에서만. 해봤는데 안됨)
char szName[12] = { 0 };
printf("이름을 입력하세요: ");
gets_s(szName, sizeof(szName));
printf("%d, %s\n", nAge, szName);
return 0;
}
int nAge = 0;
printf("나이를 입력하세요: ");
scanf_s("%d%*c", &nAge); // %*c : 버퍼에서 한 글자 날리기
7. 제4장 연산자 기본 – 첫 번째
L-value R-value
int a;
a = 10;
변수(메모리) = 상수
l-value : Left 또는 Location (위치 지정자)
r-value : Right
lvalue 는 모두 rvalue 가 될 수 있다.
rvalue 는 lvalue 가 될 수 없다.
const int a = 10; 상수가 됨
a = 5; // Copy & Overwrite
# 표현 범위가 큰 형식이 이긴다.
‘A’ + 1;
char(1바이트) + int(4바이트) => int
65 + 1 = 66
type promotion (승격)
123.45 + 1 => double
double(실수, 8바이트) + int(4바이트)
5 / 2 = 2.5 (double 실수)
5 / 2 = 2 // 2.5 아님
int / int = int // 소수점 이하 절사
5 / 2.0 = 2.5 (printf 에서 %f 또는 %lf 로 출력)
int / double = double (type promotion)
# 나머지 연산자와 나누기 연산자는 둘 다 나누기를 한다.
7 / 2 = 3 (int)
7 % 2 = 1 (int)
# 몫과 나머지를 구한다면 / 연산자와 % 연산자 두 번 해야 함.
독하게 시작하는 C 제4장 두 번째
# 변수는 반드시 초기화 후 사용해야 한다.
프로젝트 / 속성 (Alt + F7)
# 초기화 하지 않으면 에러 메시지 출력하기
Configuration properties / C/C++ / 코드생성 / 기본 런타임 검사 / 모두(/RTC1, /RTCsu와 동일)
# 초기화 하지 않아도 변수 사용가능하려면
Configuration properties / C/C++ / 코드생성 / 기본 런타임 검사 / 기본값
AND &
OR |
XOR ^
NOT ~
Shift left <<
Shift right >>
#include <stdio.h>
int main(void)
{
printf("%d\n", 3);
pritnf("%d\n", ~3 + 1); // 3에 1의 보수를 한 후 2를 더하면 음수가 됨. (2의 보수)
return 0;
}
독하게 시작하는 C 제5장 연산자 응용 – 첫 번째
# Pointer
* 간접지정 연산자
& 주소 연산자
# sizeof 컴파일 타임 연산자
// 가급적 자주 사용하라.
// 성능에 영향을 안 줌.
int a = sizeof(1); // int형 4바이트
int b = sizeof('1'); // int형 4바이트 (작은 따옴표는 int 형이다.)
int c = sizeof(char); // char형 1바이트
int nData;
int d = sizeof(nData); // int형 4바이트
int e = sizeof(nData + 1.2); // double형(형 승격)
printf("%d %d %d %d %d", a, b, c, d, e); // 4 4 1 4 8
# wchar_t 는 환경에 따라 2바이트나 4바이트가 될 수 있다.
ucs16 : 2바이트
usc32 : 4바이트
sizeof(wchar_t); // 2
int aList[5];
memset(aList, 0, sizeof(aList)); // 유지보수를 위해서
memset(aList, 0, 20); // 왕초보
int aList[5] = { 0 };
printf("%d\n", sizeof(aList)); // 컴파일 되면 아래와 같은 기계어로 된다.
printf("%d\n", 20); // 컴파일 되면 위와 같은 기계어로 된다.
# alt + 8 : 디스어셈블 단축키 (머신코드)
// 아래 두 문장은 컴파일하면 똑같은 기계어 코드가 된다.
printf(“%d\n”, sizeof(aList));
printf(“%d\n”, 20);
# 관계 연산의 결과는 1 또는 0이다.
300 < 300.1F
정수 < 실수 (부동소수점 -> 오차 발생 -> 근사값 처리)
# 실수는 절대로 상등 또는 부등 연산하지 말라.
#include <stdio.h>
int main(void)
{
printf("%d\n", 2147483647); // 2147483647
printf("%d\n", 2147483648); // -2147483648
printf("%f\n", 2147483647.0); // 2147483647.000000
printf("%f\n", 2147483648.0); // 2147483648.000000
// 부동 소수점 오차로 값이 같다.
printf("%f\n", 2147483647.0F); // 2147483648.000000
printf("%f\n", 2147483648.0F); // 2147483648.000000
printf("%f\n", 2147483600.0F); // 2147483648.000000
printf("%f\n", 2147483648.0F); // 2147483648.000000
printf("%d\n", 2147483600.0F == 2147483648.0F); // 1
return 0;
}
# switch case 문에서
case 정수: // 실수는 부동소수점 오차 발생으로 switch 문에 사용하지 마라.
int aList[5]; // 배열임. 포인터가 아님. 주소이므로 포인터에 저장은 가능
int *aList; // 포인터임
# 이형 자료 간의 비교는 표현 범위가 큰 쪽으로 변환됨
printf(“%f\n”, 3 – 2.5); // 0.5 (결과는 double) (3이 double화 됨)
독하게 시작하는 C 제5장 두 번째
# 논리 연산자
&& and
|| or
! not
# 쇼트 서킷(Short Circit)
왼쪽에서 오른쪽으로
연산이 빨리 끝나는 식을 왼쪽으로 배치하라.
불필요한 계산은 하지 않도록 배치를 잘해라.
# 쇼트 서킷 예
마음 >= 김혜수 && 외모 >= 설현
마음은 3시간, 외모는 3초라고 한다면
1. A && B // A에서 3시간 걸려도 B에서 거짓이다면 비효율.
2. B && A // B에서 거짓이라면 3초면 끝난다.
1. 빠른 연산
2. 자주 적중할 조건
삼항 연산자
코드 단순화.
조건 ? 항 : 항
#include <stdio.h>
int main(void)
{
int nData = 0, nInput;
scanf_s("%d", &nInput);
if (nInput > 5)
nData = 10;
else
nData = 0;
nInput > 5 ? (nData = 10) : (nData = 0);
return 0;
}
독하게 시작하는 C 제6장 기본 제어문 – 첫 번째
독하게 시작하는 C 제6장 두 번째
# 리팩토링
내부 코드를 정리하는 것.
기능적으로는 달라지지는 않지만 최적화되어서 성능이 올라간다다.
# goto 문은 예외처리 코드에서 가끔 쓰이는 경우도 있다.
하지만 보통 goto문은 쓰지 않는다.
독하게 시작하는 C 제7장 반복문
#include <stdio.h>
int main(void)
{
char ch;
while ((ch = getchar()) != '\n')
{
putchar(ch);
}
return 0;
}
#include <stdio.h>
int main(void)
{
int nInput;
scanf_s("%d", &nInput);
int i = 0;
while (i < nInput)
{
putchar('*');
++i;
}
putchar('\n');
return 0;
}
# continue 가급적 쓰지마라. 흐름의 복잡도가 증가한다.
독하게 시작하는 C 제8장 배열
#include <stdio.h>
int main(void)
{
int aList[3] = { 0 };
aList[0] = 10;
aList[1] = 20;
aList[2] = 30;
return 0;
}
aList : 전체주소
&aList : 전체주소
aList + 0 : 상대주소 하나
&aList[0] : 상대주소 하나
&aList + 1 : 전체주소 + 1, aList[4] 전체주소
# 배열의 이름은 주소이다.
int aList[3];
aList : 메모리의 주소, 기준주소, int[3], int 3개, 첫번째의 주소
기준주소 + index(정수) => 상대 주소 하나
&aList + 1 : 전체 주소 단위, aList[4] 번째 주소, int[3] 크기, 쓰레기값.
#include <stdio.h>
int main(void)
{
int aList[3] = { 0 };
int* pData = aList; // 결과는 같다.
int* pData2 = &aList; // 결과는 같다.
return 0;
}
#include <stdio.h>
int main(void)
{
int aList[5] = { 30, 40, 10, 50, 20 };
char szBuffer[6] = { 'H', 'e', 'l', 'l', 'o', '\0' };
char szData[8] = { "Hello" };
char* pszBuffer = "Hello";
puts(szBuffer);
puts(szData);
puts(pszBuffer);
return 0;
}
#include <stdio.h>
int main(void)
{
char aData[3][4] ;
aData[0][3] = 10;
aData[0][4] = 11; // 실행은 되지만 오류
return 0;
}
“Hello” : 상수화된 문자배열
char[6]
배열 전체는 0번 요소의 주소로 식별
문자열 상수는 이름이 없으므로 주소로 식별한다.
주소는 포인터에 담는다.
문자열 -> 가변 길이
배열 -> 고정 길이
char aData[3][4];
aData[0][4]; // aData[1][0] 의 값. 오류
# 2차원 배열은 논리적으로 2차원 배열인 것이지 메모리 구조는 1차원이기 때문이다.
#include <stdio.h>
int main(void)
{
int aList[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
int nTotal = 0;
int i;
int* pList = (int*)aList;
for (int i = 0; i < 12; i++)
nTotal += pList[i];
printf("%d\n", nTotal);
return 0;
}
#include <stdio.h>
int main(void)
{
int aList[3][4] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
int nTotal = 0;
int i;
//int* pList = (int*)aList;
for (int i = 0; i < 12; i++)
nTotal += ((int*)aList)[i];
// nTotal += aList[0][i]; // 위와 같음
printf("%d\n", nTotal);
return 0;
}
int aList[3][4];
int aList[12];
위 두 개의 메모리 구조는 같다.
단 접근 방법만 다르다.
char aData[3][4];
char[4] 가 3개 있다는 뜻
aData + 1 하면 aData[1][0] 이 됨.
# 리팩토링
내부 코드를 정리하는 것.
기능적으로는 달라지지는 않지만 최적화되어서 성능이 올라간다다.
# goto 문은 예외처리 코드에서 가끔 쓰이는 경우도 있다.
하지만 보통 goto문은 쓰지 않는다.
독하게 시작하는 C 제9장 – 배열을 활용한 프로그래밍 기법
1. 버블정렬 : 매번 교환
2. 선택정렬 : 교환할 항 검색, 1회 교환, 인덱스 이용
3. 퀵소트
// 버블 정렬
#include <stdio.h>
int main(void)
{
int aList[5] = { 30, 40, 10, 50, 20 };
int i = 0, j = 0, nTmp = 0;
for (i = 0; i < 4; ++i)
{
for (j = i + 1; j < 5; ++j)
{
if (aList[i] > aList[j])
{
nTmp = aList[i];
aList[i] = aList[j];
aList[j] = nTmp;
}
}
}
for (i = 0; i < 5; ++i)
printf("%d\t", aList[i]);
return 0;
}
// 선택 정렬
#include <stdio.h>
int main(void)
{
int aList[5] = { 30, 40, 10, 50, 20 };
int i = 0, j = 0, nTmp = 0;
int nIndexMin = 0;
for (i = 0; i < 4; ++i)
{
nIndexMin = i;
for (j = i + 1; j < 5; ++j)
{
if (aList[nIndexMin] > aList[j])
{
nIndexMin = j;
}
}
if (i != nIndexMin)
{
nTmp = aList[i];
aList[i] = aList[nIndexMin];
aList[nIndexMin] = nTmp;
}
}
for (i = 0; i < 5; ++i)
printf("%d\t", aList[i]);
return 0;
}
독하게 시작하는 C 제10장 함수에 대한 기본 이론
int Add(int a, int b) // 함수 시그니처, 함수 원형
{ // 함수 바디 시작
int x; // 지역변수 + 자동변수
} // 끝
컴파일러가 기계어로 바꿀 때는 위에서 아래로 간다.
그러므로 특정 함수가 아래에 위치해 있다면 위에 함수 원형을 밝혀준다.
#include <stdio.h>
int g_nData = 0; // 전역 변수
int Add(int, int); // 원형 선언 -> 헤더 파일 (.h 파일)
int main(void)
{
int nResult = 0;
nResult = Add(3, 4); // 실인수, 임시결과
printf("%d\n", nResult);
return 0;
}
int Add(int a, int b) // 매개변수, 형식인수 // 정의 -> (.c 파일)
{
int g_nData; // 전역변수보다 지역변수가 우선한다.
int nResult; // 지역변수
nResult = a + b;
return nResult;
} // 스코프