내가 학교에서 C언어를 배우면서 메모해놨던 내용들을 정리해 놓은 것이다.
8bit = 1바이트
1byte - 컴퓨터에서 데이터를 처리하는 가장작은 단위
int a; float b; char=c;
세미콜론을 입력하면 문장이 끝나서 다음줄로 넘어가지 않아도 다음문장 바로 쓸수있다
int a;
a = 10;
대입문의 왼쪽에는 상수가 올 수 없다.
대입문의 오른쪽에는 수식이나 값이 오지만 수식의 최종 값은 반드시 하나만 올 수 있다.
char a[10] //문자배열을 선언
a = "hello"
이 코드는 오류가 난다.
배열의 이름은 해당 배열의 시작점의 주소값, 즉 상수값이기 때문이다. 대입문의 왼쪽에는 상수가 올 수 없다.
char *a //포인터 변수를 선언
a = "hello"
그러나 이건 가능하다.
첫째로 a는 상수가 아닌 주소값을 저장하는 변수이기 때문이고,
"hello"는 hello에 해당하는 h, e, l, l, o 이렇게 각각 값이 전해지는 것이 아니라, 메모리 어딘가에 할당되어 존재하는 "hello"라는 문자열의 시작 주소값만 포인터변수 a에 대입하는 것이다.
따라서 대입문의 오른쪽에는 최종값 하나만 올 수 있다는 조건도 충족한다.
\t : Tab버튼
\b : 백스페이스(지우기버튼)
\r : 커서를 처음으로 옮긴다
ex)
printf("hello!"); printf("\r"); printf("world") >>world!hello!를 출력하고 커서를 맨앞으로 옮겨 world를 출력하기 때문에 먼저 출력된 hello!는 world에 의해 덧씌워진다.
그리고 world는 5글자이니 hello!에서 앞에 5글자 제외하고 느낌표만 남아 world! 가된다.
\n : 줄바꿈. 글을 작성할때 엔터를쳐서 줄바꿈하는 것과 동일한 효과.
\' : 작은따옴표( ' )출력
\" : 큰따옴표( " )출력
\? : 물음표출력
\ : 역슬래쉬 출력
\a : 경고음. 스피커에서 경고음이 나온다.
(배열에 들어갈 요소의)자료형 배열이름[배열크기] = {요소1, 요소2 .....};
int a[10]; //배열을 미리 선언만 해놓고 나중에 값을 넣을 수도 있다
a[0] = 1; //컴퓨터는 0번째부터 센다. 단, 배열의 크기는 0개 라는 것은 없기 때문에 1부터 센다. ex) 10칸 짜리 배열은 9번재 요소까지 있다.
a[2] = 2;
int a[10] = {1, 2, 3, 4, 5, ...}; //int로 선언 됐으므로 a에는 int 요소만 들어갈 수 있다.
배열을 선언할 땐 배열의 안에 들어갈 값의 자료형과 똑같은 자료형으로 배열을 선언해야 한다.
int input;
scanf("%d", &input);
int x[input];
이 코드는 오류가 난다.
배열 선언할 때 배열의 크기는 자연수 상수만 올 수 있고 변수 같은것은 올 수 없다.
이렇게 값을 입력받아 해당 크기로 배열을 생성하는 것은 동적할당이란 것을 쓰지 않으면 불가능하다.
이것을 이해하려면 동적 메모리 할당과 정적 메모리 할당을 알아야 한다.
정적 메모리 할당은 메모리 할당 작업이 컴파일 할 때 일어난다.
동적 메모리 할당은 메모리 할당이 런타임(실행중)에 일어난다.
C언어는 배열을 생성할때는 기본적으로 정적 메모리 할당을 사용한다.
그렇다면 실행하기 전, 컴파일 할 때 메모리 할당이 일어나는 것이다.
따라서 배열의 메모리 할당 또한 컴파일 할 때 일어난다.
int x[3];
이런식으로 배열 선언 때, 배열의 크기에 값이 들어가 있으면 그냥 해당 값 만큼의 메모리를 할당해주면 된다.
int input;
scanf("%d", &input);
int x[input];
그런데 이런식으로 사용자에게 값을 입력 받아 배열을 생성하려는 경우를 보자.
사용자에게 입력을 받는 일은 컴파일 할 때가 아니라 런타임에 일어난다.
따라서 배열에 메모리를 할당하려 할 때에는 input 변수의 값이 비워져 있기 때문에 배열의 크기로 설정할 값을 전달 할 수 없다.
이 때문에 사용자에게 값을 입력받아 해당 값 만큼의 크기를 갖는 배열을 선언하는것이 불가능 한 것이다.
그러나 이것을 가능하게 해주는 방법이 있는데, 그것이 동적 할당이다.
동적 할당에 대해서는 이후 밑에서 다루도록 하겠다.
배열에서 값이 저장되지 않은 공간은 비워져 있다.
int a[10] = {1, 2};
크기가 10인 배열을 선언하고 요소를 2개만 채웠을 시, 남은 8개 공간은 비워져 있다.
char a[3][3];
1차원 배열말고도 2차원, 3차원... 등의 배열을 선언가능하다.
char a[3][3];
puts(a[1]);
이런식으로 2차원 배열인데 출력은 1번째 행만 설정하고 열은 설정안하면 1번째 행 전체가 출력된다.
int c[] = {1, 2, 3};
이런식으로 1차원 배열 일때, 선언과 동시에 초기화 한다면 배열 크기는 정의안해줘도 알아서 정의된다.
int c[][3] = {{1}, {1, 2}, {1, 2,3}};
이렇게 2차원 배열일때 선언과 동시에 초기화 한다면 행, 열 중에 행부분을 정의하지 않아도 알아서 정의 된다.
그러나 열부분은 반드시 써야한다.
행은 안써도 배열에 저장되는 데이터 갯수로 추측할 수 있지만, 열을 안쓰면 2차원 배열 내에 1차원 배열에 몇개의 데이터가 저장될 수 있는지 알 수 없어서 추측을 할 수 없기 때문이다.
int c[2][2] = {1, 2, 3, 4}
이렇게 다차원 배열일 때, 선언과 동시에 초기화 할 경우 {{1, 2}, {3, 4}} 이렇게 중괄호로 차원을 나누어 주지 않아도 된다.
그러면 배열의 인덱스 [0][0]부터 차례로 데이터가 저장된다. ([0][0]->[0][1]->[1][0]->[1][1] 이렇게 열 먼저 채우고 다음 행바뀜)
int c[2][2][2];
고차원 배열일때, c[0][0][1] -> c[0][1][1] -> c[1][1][1] 이렇게 뒤부터 채워진다.
ex) int score[2][10][3] -> 10명의 학생이 국, 영, 수 점수를 입력하는데 반이 2개다.
배열의 이름은 배열의 시작 주소값을 나타내는 상수이다.
int a[5] = {1,2,3,4,5};
int i = 3;
printf("%d", a[i]);
>>> 4
가장 기본적인 방법이다.
배열 a의 시작 주소를 100번지 라고 가정하면 a는 100번지를 뜻하는 상수값이다.
a[2]를 한다면 100 + 4 (int자료형의 크기) * 2, 즉 108번지를 뜻하게 된다.
즉, a[i] = a의 주소값 + i * (자료형 byte) 이며, 이는 a로부터 i만큼 떨어진 요소에 접근한다는 것을 뜻한다.
int a[5] = {1,2,3,4,5};
int *p = a;
int i = 3;
printf("%d", *(p+i));
>>> 4
포인터 변수를 이용해 요소에 접근하는 방법.
포인터변수 p에 +i를 함으로써 위에 a[i]와 동일한 효과를 낸다.
p+i로 요소의 주소값에 찾아간 다음, *(값연산자)를 이용해 요소값을 추출한다.
int a[5] = {1,2,3,4,5};
int i = 3;
printf("%d", *(a+i));
>>> 4
바로 위에 *(p+i)와 똑같은 방식이다.
a또한 배열의 시작점인 100이라는 주소값을 갖고있으니 가능하다.
int a[5] = {1,2,3,4,5};
int *p = a;
printf("%d\n", *(p++));
printf("%d\n", *(p++));
printf("%d\n", *(++p));
>>> 1
2
4
p에 a의 시작주소값을 저장하고, ++로 1씩 더하면서 요소에 접근한다.
전위, 후위 모두 가능.
배열의 시작 주소값이 아닌 이후 요소의 주소값을 알 수 있다면 p--도 가능하다.
그러나 *(a++), *(++a) 등의 배열이름으로 직접 하는건 안된다.
왜냐면 전위, 후위는 변수의 값을 변경시키는데, 배열의 이름은 배열의 시작주소값 상수이기 때문에, 상수는 변경될 수 없다.
int a[5] = {1,2,3,4,5};
int *p = a;
int i = 3;
printf("%d", p[i]);
>>> 4
a[i]와 똑같은 원리이다. 다른 점은 a는 배열의 시작 주소값을 가진 상수값이지만, p는 포인터 변수를 이용한 방법이라는것.
배열의 이름은 해당 배열의 첫 요소가 저장되어있는 포인터(주소값)을 나타내는 상수이다.
배열의 주소를 저장한 변수 자체에 배열의 값이 저장된것이 것이 아니다.
메모리 어딘가에 배열이 저장되어 있고, 그 배열의 시작주소를 변수가 가지고 있는 것이다.
char a[6];
scanf("%s", a);
>>> "hello" 입력
printf("%s", a);
>>> hello
따라서 위와 같은 상황에서 주소값을 뜻하는 &기호를 붙이지 않아도 된다.
만약 int a[3]의 배열을 선언한다면, a라는 배열에 3개의 int요소를 넣을 수 있으므로 총 12바이트의 공간이 할당된다.
배열의 요소(방)는 값 하나를 갖는 변수와 동급이다.
따라서 실제적인 값은 배열의 요소(방)에 저장되며, 자료형이 int이므로 하나당 4바이트를 할당받는다.
이 때 a의 첫번째 요소인 a[0]의 주소값이 100번지 라고 가정하자.
배열의 이름은 시작주소값을 나타내는 상수라고 했었으니 a라는 배열의 이름은 첫번째 요소의 주소값인 100번지를 나타낸다.
그리고 int는 4바이트이니 0번째 요소는 100~103까지의 주소를 할당 받는다.
배열은 요소가 서로 연속되게 붙어 있으니 두번째 요소는 104~107까지의 주소를 할당받는다.
세번째는 108~111의 주소를 할당받는다.
따라서 배열의 이름이자 100번지를 나타내는 a에 +1을 하면 이는 104를 뜻하며, 104는 104~107의 주소를 할당받은 두번째 요소의 시작주소이다.
따라서 a + 1은 &a[1]와 같은 의미이다. a+2는 a배열의 세번째 요소의 주소값(&a[2])을 뜻하게 된다.
주소값이 아닌 해당 주소의 값을 가져오고 싶다면 *(a+1)과 같이 사용하면 된다.
int a[5] = {1, 10, 5, 20, 3};
int *p;
p =a;
배열의 이름은 시작주소값을 나타낸다고 했었다.
따라서 포인터 변수p에 이런식으로 &를 붙이지 않고 a의 주소값을 넣을 수 있다.
a의 시작주소값을 200번지 라고 가정하자.
*(p+1) : 이 결과값은 a[1] = *(p+1) = 10이다. p에 a의 시작주소를 저장했고, 여기 +1을 함으로써 배열 a의 두번째 요소를 나타낸다.
p+2 : 이 결과값은 a의 시작 주소값인 200번지 + 2 * 4 (int이므로)가 된 208이다.
값연산자인 *를 붙이지 않아 주소값 그대로를 나타내게 된다.
*p+3 : 이 결과값은 정수 4이다.
*를 붙이긴 했으나 괄호를 씌우지 않아 p가 가지고 있는 a배열의 시작주소값, 즉 첫번째 요소를 반환하게 되어 첫번째 요소인 1에 +3을 함으로써 정수4가 된것이다.
p = a+2 : 이 결과값은 p에 a의 시작주소인 200번지 + 2 * 4를 한 값인 208이라는 주소가 저장된다.
C에는 문자열을 저장하기 위한 자료형이 존재하지 않는다.
따라서 문자열은 char 문자자료형을 이용한 문자배열을 이용해야 한다.
ex) char[6] = "hello"
그런데 이렇게 하면 컴퓨터는 어디가 문자열의 끝인지 알 수 없다.
그래서 배열의 끝에 \0를 붙여서 그곳이 문자열의 끝임을 표시한다.
따라서 위에 ex에서 hello는 5글자인데 6칸의 배열을 쓴 이유는 마지막에 추가될 \0를 염두에 두고 한것이다.
물론 출력 같은 작업을 할 땐 \0는 출력되지 않는다
따라서 문자배열과 문자열의 차이는 \0이 배열에 들어있느냐 없느냐의 차이이다.
문자배열에 문자를 하나씩 입력한 후 마지막에 \0를 입력하면 문자열로 인식한다.
문자배열은 출력할때 %s로 출력못한다.
인덱스를 하나씩 지정해서 %c로 출력해야 한다.
ex)
char a[6];
a = "hello"
이렇게 하면 오류가 난다. 왜냐면 a는 기억장소가 아니라, 기억장소의 시작주소를 의미하는 상수이기 때문이다.
if ( a == "hello" ) 이것도 당연히 오류다. a는 배열의 시작주소 상수 이므로.
char a[6] = "hello"; //선언과 동시에 초기화
char a[6] = {"hello"}; //이렇게 중괄호를 씌워도 된다. (보통 1차원 배열일땐 안씌운다)
char a[] = "hello"; //이렇게 배열의 크기를 정해주지 않아도 선언과 동시에 초기화를 할 수 있다.
scanf("%s", a); //&는 안넣어도 된다. a자체가 배열의 시작주소를 의미하므로.
a[0]='h'; a[1]='e'; a[2]='\0'; //문자배열에 하나씩 넣어주고 마지막에 \0를 넣는다.
char a[6] = {'h', 'e', 'l', 'l', 'o', '\0'}; //선언과 동시에 초기화 하는데, 하나씩 넣어주고 마지막에 \0를 넣는다.
char *a = "hello"; //포인터 변수를 사용해 문자열저장
gets(a); //gets를 이용해 a문자배열에 문자열을 입력받는다.
char a [6] = "hello";
a[0] = 'T';
printf("%s", a);
>>> Tello
문자열은 배열에 저장되므로 이렇게 한 개의 요소를 선택해 바꾸는 것도 가능
void abc(char x[], int y[]);
1차원 배열을 매개변수로 받아오려고 할때, 함수의 정의에선 대괄호만쓴다 (안에 숫자써도 무시됨)
void abc(char x[][10], int y[][5]);
2차원 배열을 매개변수로 받아올 땐 뒷쪽 대괄호에만 값을 입력한다.
안쓰면 2차원 배열의 실제 크기를 알 수가 없으므로 꼭 써야한다.
char array[2][5];
abc(array);
void abc(char (*x)[5] ); //매개변수를 포인터로 만든후 포인터배열에 넣는다
이렇게 2차원 배열을 매개변수로 넣는데, 그걸 포인터배열로 받아오려고 한다면 (*매개변수명)[크기] 이런식으로 괄호를 써줘야 한다.
그리고 매개변수에서 [5]로 크기를 지정한 이유는 array의 크기가 [2][5]인데, 그럼 5칸짜리 1차원배열이 2줄 있는것이다.
여기서 매개변수에는 1차원의 길이를 지정해 주는것이다.
따라서 array는 5칸짜리 1차원배열 2개 이므로 5칸으로 매개변수x의 크기를 지정한다.
char *array[5];
abc(array);
void abc(char (*x)[5]);
포인터배열을 매개변수로 넣을때도 매개변수명에 괄호 써줘야함
char a[5];
scanf("%s" a);
문자열을 입력받을 때는 주소값을 뜻하는 &기호를 붙이지 않는다.
int a[10];
scanf("%d", &a[0]);
문자열 외에 숫자, 문자를 입력 받을 때는 scanf로 입력 받을때 &를 붙여야 한다.
int a[10];
scanf("%d", a);
>>> 3입력
printf("%d", a[0]);
>>> 3
숫자나 문자를 입력받을 때 이런식으로 &를 붙이지 않으면, 배열의 첫번째 요소에 값이 저장된다.
왜냐하면 배열의 이름은 배열 첫 요소의 주소값을 저장한 변수이기 때문이다.
따라서 위와 같이 쓰면 첫번째 요소에는 값을 넣을 수 있지만, 이후의 요소에는 값을 넣을 수 없다.
int a[10];
scanf("%d, a[0]);
이렇게 인덱스만 써서도 입력받을 수는 없다.
a[0] <- 이건 a배열의 0번째 요소의 값을 불러오란 의미이기 때문에 입력을 할수가 없는것이다.
char a[10] = "가나다";
문자열배열은 문자열의 주소를 배열이 가지고 있는 형태라서 이런식으로 쓸 수 있다.
int a[5];
a[0] = 1;
숫자배열은 배열의 정확한 인덱스를 지정해 주어야 값을 저장할 수 있다.
또는 int a[5] = {1, 2, 3, 4, 5}; 이렇게 선언과 동시에 초기화를 하거나.
함수의 정의형식. []로 감싼 부분은 생략가능
[자료형] 함수이름([매개변수]) {함수 실행식};
함수 타입에는 리턴값이 있는 함수와 리턴값이 없는 void형 함수로 나뉜다.
리턴값이 없는 void형 함수는 보통 프로시쥬어라고 불린다.
리턴값이 있는경우, 리턴값이 정수냐 실수냐 등에 따라 함수도 int인지 float인지 등으로 자료형이 나뉜다
function(매개변수) {실행식};
만약 이와같이 함수 선언시에 void나 자료형을 나타내는 것이 하나도 없다면 자동으로 int형으로 인식한다.
return 식;
return문을 만나면 함수 실행을 중단하고 호출한 프로그램으로 제어를 반환한다.
이때, return값을 같이 반환하게 된다.
또한 식에는 수식이나 단순한 값도 올 수 있다.
return의 최종값은 무조건 한개가 되야한다.
return a,b,c; 이렇게 여러개 할 수 없다.
return 1; return a+2; return (a+b); return;
이것들 처럼 최종값이 한개가 되야함
또한 return에 포인터가 올 경우 함수의 자료형은 해당 포인터의 주소를 찾아 갔을때, 그 값의 자료형과 동일하게 하고, 함수이름앞에 *를붙인다.
ex)
char *function(){
char a = "A";
return &a;
}
변수a의 주소값을 return값으로 주기위해 a의 자료형인 char로 함수선언을 하고, 함수이름앞에 *를 붙였다.
함수를 정의할 땐 프로토타입 선언을 하는 방법이 있고, 프로토타입 없이 그냥 선언하는 방법이 있다.
그냥 선언하는 방법 먼저 살펴보자.
void func(int num){printf("%d", num);} //함수 선언
void main(){func(100);} //메인 함수
그냥 선언하는 방법은 위와 같다.
다음은 프로토타입 선언이다.
void func(int num); //프로토타입 선언
int main(func(100);){} //메인 함수
void function(int num) {printf("%d", num);} //정식 함수 선언
위와 같이 프로토타입 선언을 먼저하고 그 뒤에 정식으로 함수를 선언하는것이 프로토타입 선언이다.
프로토타입 선언을 하는 이유는 함수 정의 순서에 상관없이 호출하기 위해서, 그리고 관리의 용이성 때문이다.
ex)
void main(){func(100);} //메인 함수
void func(int num){printf("%d", num);} //함수 선언
위에 그냥 함수 선언코드에서 메인함수와 func함수의 위치만 바꿨다.
그런데 이 코드는 오류가 난다.
컴파일러는 위에서 아래로 컴파일을 진행하기 때문에, 메인함수를 컴파일 할때는 func함수가 정의되어 있지 않아서 찾을 수가 없기 때문이다.
따라서, 함수는 호출되기 전에 미리 정의되어 있어야 한다.
이런 문제를 해결하기 위해 프로토타입 선언을 활용한다.
void func(int num); //프로토타입 선언
int main(func(100);){} //메인 함수
void function(int num) {printf("%d", num);} //정식 함수 선언
이렇게 프로토타입 선언을 위에서 미리 해놓으면 함수의 정의 순서에 제약받지 않고 사용할 수 있다.
또한 위쪽에 프로토타입 선언을 해놓고 밑에서 재정의 하면 관리도 용이해 진다.
매개변수에는 수식이 올 수도 있다.
자료형만 맞춰주면 된다.
function( 1, 9 );
함수의 호출문이다.
이 때, 매개변수로 함수에 보내는 값을 실인수 라고한다.
실인수로 보낼 수 있는 것은 값, 변수에 저장된 값, 수식 등이다.
int function(int x, int y){ return x+y; }
함수의 선언문이다
함수선언문에서 괄호안에는 함수를 호출할 때 받아온 매개변수를 어디다 저장할 것인지 지정해준다.
직접적인 값(상수 등)을 보내든, 변수에 저장된 값을 보내든, 위와 같이 매개변수로 값을 전달하는 방식을 call by value라고 하고,
아래 처럼 주소를 통해 매개변수를 전달하는 call by reference라는 방법이 있다.
int a = 3, b = 5, c;
sum(a, b, &c);
printf("%d", c);
void sum(int x, int y, int *z) {
int sum;
sum = x + y;
*z = sum;
}
이와 같이 포인터를 통해 매개변수를 전달하고 결과를 받는 방법을 call by reference라고 한다.
이 방법으로 배열의 주소값또한 보낼 수 있다.
void function(int a[]){ 실행식 } //이건 배열을 만들어 매개변수로 배열의 주소값을 받는방법
void function2(int *a) { 실행식 } //이건 포인터변수를 만들어 배열의 주소값을 받는방법
void main(){
int a[5] = {1,2,3}
function(a); //배열의 이름은 배열의 시작 주소값이므로 이렇게 보낼 수 있다.
}
void printAry(int ary[], int size)
위와 같이 문자열이 아닌 다른 자료형의 배열을 보낼때는 배열의 요소중 몇번째 까지 접근할 것인지도 매개변수로 넣어줘야한다.
배열의 이름은 배열의 시작 주소를 담고 있으므로 실제 크기가 얼마인지는 알 수 없기 때문이다.
배열의 크기를 알 수 있는 방법은 다음과 같다.
int ary[5] = {1, 2, 3, 4, 5};
printf("%d", printAry(ary, sizeof(ary) / sizeof(int));
이렇게 sizeof(배열) / sizeof(배열의 자료형) 을 하면 배열의 크기를 구할 수 있다ㅏ.
문자열은 마지막에 \0가 있으므로 배열의 끝을 컴퓨터가 인식할 수 있으니 배열의 크기를 안보내도 된다
sizeof - 자료형, 변수의 크기를 알려줌
ex)
float a = 10000000000;
printf("int=%d ", sizeof(int));
printf("a=%d", sizeof(a));
>>> int=4 a=4
sizeof(a)는 float 자료형의 크기를 잰게 아니라 a라는, 10000000000라는 값의 주소를 담고 있는 변수이기 때문에 4로 나온다.
scanf(), printf(), getchar(), putchar(), gets(), puts(), fscanf(), fprintf(), fgets, fputs()
int scanf("%d", &a) - 표준 입력장치로 부터 입력형식에 따라 입력 받음, 입력 잘되면 1을 반환
int getchar() - 표준 입력장치로 부터 문자단위로 입력 받음 ex) ch=getchar()
int putchar(int c) - 표준 출력장치로 문자단위로 출력 ex) putchar(a)
char *gets(char *s) - 표준입력장치로 문자열 입력 받아 문자배열에 문자열 주소값 저장
ex)
char ary[11];
gets(ary);
printf("%s", ary);
int puts(char *s) - 표준 출력장치로 문자열 출력 ex) puts("hello")
system(), exit()
int system(char *s) - 괄호속, 매개변수로 전달된 문자열에 지정된 명령어를 실행 ex) system("cls")
exit(0); 또는 exit(1);을 사용하면 프로그램이 종료된다
exit(0);은 정상적인 종료, exit(1);은 비정상적 종료이다
isalpha(), isdigit(), isalnum(), isspace(), isupper(), islower(), toupper(), tolower(), toascii()
int isalpha(int c) - 매개변수 값이 영문자(알파벳)인지 확인한다.
int isdigit(int c) - 매개변수 값이 숫자인지 확인한다.
int isalnum(int c) - 매개변수 값이 영문자나 숫자인지 확인한다.
int islower(int c) - 매개변수 값이 소문자인지 확인한다.
int isupper(int c) - 매개변수의 값이 대문자인지 확인한다.
int isspace(int c) - 매개변수 값이 공백문자인지 확인한다.
is~ 형식의 함수들 모두 매개변수 값이 참이면 1 거짓이면 0을 반환
int toupper(int c) - 매개변수 값이 소문자인 경우 대문자 값을 반환 (매개변수 값 자체가 대문자로 바뀌는게 아닌, 리턴 값으로 대문자를 반환하는 것이다)
int tolower(int c) - 매개변수 값이 대문자인 경우 소문자 값을 반환
int toascii(int c) - 매개변수 값에 해당하는 아스키코드 값을 반환
to~ 형식의 함수들은 매개변수값이 조건에 해당하면 리턴값을 반환하는 방식. (매개변수가 조건에 해당하지 않으면 해당 매개변수를 그대로 반환)
ctype.h의 함수들은 모두 "문자"를 매개변수로 입력하며, 비파괴적 함수이다.
sqrt(), pow(), abs(), fabs(), sin(), cos(), tan(), floor(), ceil()
double sqrt(double x) - 매개변수로 전달된 수의 제곱근을 구한다.
double pow(double x, double y) - x에 전달된 수를 y만큼 제곱한다.
int abs(int x) - 정수 의 절대값을 구한다.
double sin(double x)
double cos(double x)
double tan(double x) - sin, cos, tan세 개 공통, 삼각함수 값을 구한다. 매개변수를 통해 전달되는 값은 라디안 값을 사용
double floor(double x) - 소수점 이하의 값을 버림한 값을 구한다. (소수점 이하가 1~9면 버림)
double ceil(double x) - 소수점 이하의 값을 올림한 값을 구한다. (소수점 이하가 1~9면 올림)
floor와 ceil은 내림, 올림만 한다. 이걸 이용해 반올림을 할 수 있다.
floor(값 + 0.5)소수점 값이 0~4면 0.5를 더해도 일의 자리가 안바뀌므로 소수점을 버림해도 일의자리는 값이 같다.
그런데 소수점이하가 5~9면 0.5를 더하면 일의 자리가 바뀌고, 이에 따라 올림하고 난 소수점을 버림하면 일의자리는 +1 증가한다.그러나 ceil로는 반올림이 안된다.
소수점 이하가 5일때, -0.5 하면 정수가 되므로 반올림이 안된다.
abs빼고 모두 double형을 입력받고 반환하며, 모두 비파괴적 함수이다
strlen(), strcpy(), strcmp(), strcat(), strupr(), strlwr(), strchr(), strstr()
int strlen(char *s) - 매개변수 문자열의 길이를 정수값으로 반환한다. \0은 포함하지 않는다. 배열크기가 10이더라도 문자열이 5글자면 5를 반환
char *strcpy(char* dest, const char* origin) - origin에 있는 문자열 전체를 그대로 dest에 붙여넣는다. 이때 origin문자열에 있던 \0 까지도 복사가 된다
dest에 있던 문자열이 origin에 있는 문자열보다 길어도, dest에 있던 문자열은 모두 사라지고 origin을 넣는다.
strcpy를 통해서 문자열을 저장할 때, 변수에는 확보된 실제 공간이 있어야 한다. 따라서 변수에 확보된 공간이 없는 빈 변수라면 strcpy를 사용할 수 없다.
ex)
char origin[] = "BlockDMask"; char dest[100]; strcpy(dest, origin);
int strcmp(const char *s1, const char *s2) - s1문자열과 s2문자열이 똑같은지 비교한다. 같다면 0을 반환하고, 일치하지 않을경우, s1 문자들의 아스키코드 합이 s2보다 크면 양수(1)가 반환, s2문자들의 아스키코드 합이 s1보다 크면 음수(-1)반환
char *strcat(char *dest, const char *origin) - origin에 있는 문자열을 그대로 dest에 있는 문자열의 끝에 이어붙이고 반환한다. dest 끝에 있던 \0은 사라진다. s1은 s1, s2를 합친 문자열로 변경되고, s2는 안변한다.
char *strupr(char *s) - 매개변수 문자열에 있는 소문자를 대문자로 변경한뒤 반환한다. (매개변수로 넘어온 문자열이 변경된다.) 파괴적 함수이다.
char *strlwr(char *s, char c) - 매개변수 문자열에 있는 대문자를 소문자로 변경한뒤 반환한다. (매개변수로 넘어온 문자열이 변경된다.) 파괴적함수이다.
char *strchr(char *s, char c) - 문자열 s에서 문자c가 처음으로 나타나는 위치를 가리키는 포인터를 반환. 문자열s에 문자c가 없으면 null포인터 반환 (있으면 해당 문자(c)의 위치(주소값) 반환. 매개변수로 넘어온 문자열(s)의 변경은 없다)
s = strchr("Hello", 'e');이 결과값은 s에 ello가 저장된다 왜냐면 반환값이 주소값인데, 문자열은 배열이고, 문자열의 특성 때문에 주소값을 받으면 자동으로 그 위치부터 문자열의 끝까지 나오게된다.
char *strstr(char s1[], char s2[]) - 문자열 s1에서 문자열s2가 처음으로 나타나는 위치를 가리키는 포인터를 반환. 문자열s1에 문자열s2가 없으면 null포인터 반환 (있으면 해당 문자열(s2)의 위치(주소값) 반환. 매개변수로 넘어온 문자열(s1)의 변경은 없다)
printf("%s", strstr("Hello", "ll");이 결과값은 "llo"가 출력된다. 이유는 strchr과 같이 s1에서 s2가 위치한 주소값을 반환하고, 문자열은 배열이고, 문자열의 특성때문에 해당 주소값에서 문자열의 끝까지 자동으로 나온다.
string.h의 파괴적함수 strcpy, strcat, strlwr, strupr
string.h의 문자열을 반환하는 함수 strcat, strupr, strlwr
malloc(), free()
동적할당을 위한 함수이다.
관련 설명은 동적할당 파트에서 다루겠다.
포인터는 변수의 주소값이다
이것은 16진수 정수로 이루어져 있다.
따라서 포인터 자체는 포인터 변수가 아니더라도 정수형 변수에도 저장할 수 있다.
주소를 불러올땐 항상 &(주소연산자)를 붙인다.
변수를 선언하면 1바이트마다 주소가 주어지는데, 포인터는 이 변수의 제일 첫번째 바이트의 주소를 말한다.
어차피 자료형으로 인해 해당 변수가 몇 바이트를 쓰는지를 알 수 있으니 제일 첫번째 주소만 기억하는 것이다.
요약하면 여러바이트에 걸쳐 저장되있으면 저장되는 시작 주소를 저장한다.
포인터 상수 - 주소 연산자를 사용하여 얻은 메모리 주소. 한 번 정해지면 변하지 않는다.
변수를 선언하고, 선언한 변수에 메모리가 할당되는 작업은 컴파일을 할때에 일어난다.
따라서 컴파일을 할때마다 같은 변수라도 주소값은 매번 달라진다.
변수의 포인터 값을 출력 할때는 %p라는 서식문자를 쓴다.
포인터 값을 출력할때 %p가 아닌 %d를 이용하면 16진수의 포인터 값이 아닌 10진수로 변환된 16진수 포인터값이 나온다.
ex)
int a = 50;
int *b;
b = &a;
printf("%d %p %d %p", a, a, &a, &a); //a의 주소값은 16진수 F, b의 주소값은 16진수 A라고 가정
>>> 50(a에 들어있는 값) 32(50의 16진수 값) 15(16진수 F의 10진수 값) F(a의 16진수 주소값)
%p를 사용할 때 해당 변수에 포인터 말고 다른 것이 들어있으면 16진수로 변환 되어 출력하는듯하다
printf("%d %p %d %p", b, b, &b, &b);
>>> 15(16진수 F의 10진수 값) F(a의 16진수 주소값) 10(16진수 A(변수a아님)의 10진수 값) A(b의 16진수 주소값)
포인터 변수는 포인터를 저장하는 변수이다
포인터 변수의 선언은 int *p; 이처럼 변수이름 앞에 *(값 연산자)을붙인다
또한 자료형은 포인터 변수에 담을 주소값의 주인 변수의 자료형과 일치해야 한다.
포인터 변수는 자료형에 상관없이 모두 4바이트를 할당 받는다.
어차피 변수자체나 변수의 값을 저장하는 것이아니라 주소값을 저장하는 것이기 때문이다.
ex)
int a = 1;
int *b; //a의 주소값을 저장하려면 포인터변수의 자료형을 a의 자료형인 int와 동일하게 선언해야 한다.
b = &a;
포인터 변수가 가진 주소값을 사용하려면 명령문에서 포인터변수앞에 *을 붙인다
int *a, b;
b = 10;
a = &b;
printf ("%d", *a);
>>> 10
이것은 포인터 변수를 선언할 때 사용하는 *과 전혀 다른 용도이므로 주의.
포인터 변수를 선언 할때를 제외하곤 *의 의미는 해당 포인터 변수가 가진 주소값의 주소를 찾아가 그 값을 반환한다는 의미이다
int a = 20, *b;
b = &a;
*b = 60;
위와 같이 포인터 변수를 선언하고, 대입문의 왼쪽에 *을 붙인 포인터 변수를 놓을 경우, 그 의미는 포인터 변수가 가진 주소값의 주소를 찾아가 거기에 존재하는 값을 60으로 바꿔라 라는 의미가 된다.
따라서 위 코드 이후 printf("%d", a); 를 하면 a의 값은 60으로 바뀌어져있다.
포인터 자체는 상수이기 때문에 포인터 변수가 아니더라도 정수형 변수에 포인터를 저장할 수 있다.
또한 포인터 변수에도 포인터 뿐만 아니라 정수값을 저장할 수 있다.
다만 두 경우 모두 실행은 되지만 경고를 날린다
그러나 실수형은 다르다.
실수형 포인터변수에는 실수를 저장할 수 없다.
다만 정수는 저장가능. 물론 경고메시지가 뜬다.
또한 자료형에 상관없이 포인터변수에 문자도 넣을 수 있는 듯 하다.
아마 문자는 아스키코드로 변환한 정수형태로 저장하기 때문인듯하다.
int a = 1;
int *b, *c;
b = &a;
c = b;
이렇게 사용도 가능하다 어차피 b가 가지고 있는건 a의 주소값이니 같은 int형의 포인터 변수인 c에도 a의 주소값을 넣게 되는것이다.
int a;
int *b = &a;
scanf("%d", b);
여기서 b에 &를 붙이지 않은 이유는 &b이렇게 쓰면 포인터변수인 b에 직접 값을 저장하라는 의미가 되기 때문이다
b가 가진 a의 주소를 이용해 a에 저장하고 싶으면 그냥 b라고 써야한다.
포인터 변수도 변수이므로 주소값이 존재한다.
따라서 포인터 변수의 주소값을 포인터 변수에 넣을 수 있다.
int i = 1;
int *ad_i = &i; //(주소값 : 100번지 라고 가정)
int **ad_ad_i = &ai_i; //ai_i가 가진 주소값이 아닌, ai_i변수 자체의 주소값을 전달하기 위해 &를 붙인다.
printf("%p %d", *ad_ad_i, **ad_ad_i);
>>> 100번지 1
*를 하나만 쓸 때는 ad_ad_i변수에 담긴 ad_i의 주소값을 가져오고, *를 두개 쓰면(**) ad_ad_i변수에 담긴 ai_i변수로 간 다음, ai_i에 담긴 주소로 가서 값을 가져온다.
포인터도 정수의 덧셈과 뺄셈 가능하다.
포인터에서 n을 더하면 n*m(자료형의 크기)만큼 증가한다.
int 자료형일 경우, int는 4바이트이기 때문에 원래 포인터가 100이었다 가정하면 +1하면 104가 된다.
104가 된 상태에서 다시 +3을 하면 4*3=12 이므로 116이 된다.
ex)
int a[10], *p;
p=a;
p[0]=1; //이렇게 포인터 변수를 배열처럼 쓸 수 있다.
for (int i=0; i<10; i++){
*(p+i) = i;
printf("%d", p[i]);
}
>>> 0123456789
포인터변수를 이용해 문자열의 주소값을 저장할 수 있다.
ex) char *a = "computer"
이때 a는 computer라는 문자열을 직접 저장하고 있는것이 아니다.
computer라는 문자열 배열이 어딘가에 따로 저장이 되어있고, 이 배열의 시작주소를 a가 가지고 있는 것이다.
char *a = "hello"; //hello라는 문자배열(문자열)을 어딘가에 생성한 후 그 시작주소를 a포인터변수에 넣는다
a = "Hello?"; //Hello? 라는 문자열을 하나 새로 만든 후, 그 시작주소를 a포인터배열에 넣는다. 그전에 있던 hello의 주소값은 덧씌워진다.
char *a = "hello";
printf("%d", sizeof(a));
>>> 4byte
a는 hello라는 문자열을 직접 저장한게 아니라 문자열의 시작주소를 가지고 있는 것이다.
포인터 변수는 무조건 4btye의 메모리를 할당받으므로 4btye가 출력된다.
char *month[5] = {"january", "february", "march", "april", "may"};
이런식으로 각각의 문자열을 생성한 뒤 이 문자열들의 주소값들을 저장하는 포인터배열을 만들 수 있다.
sizeof(month)를 하면 20byte가 나온다. 실제 문자열들을 저장한것이 아닌, 4바이트짜리 문자열주소값 5개를 저장한것이기 때문.
똑같은 것을 포인터배열을 쓰지않고 일반배열로 하려면 2차원 배열을 써야한다.
포인터 문자열에 접근할때는 *를 쓰지 않아도 된다.
ex)
char *a = "hello";
a = "bye"; //a에 *를 붙이지 않아도 된다.
printf("%s", a); //마찬가지로 여기도 *을 안붙여도 된다.
char *b[2] = {"hello", "bye"};
printf("%s", b[0]); //이것도 마찬가지로 *를 붙이지 않아도 된다.
포인터변수에 저장된 것이 변수의 주소 값이 아니라 값 자체의 주소 값, 즉 문자열 자체의 주소값인 경우 *를 붙이지 않아도 된다.
즉, 변수의 주소값은 주소를 찾아간 다음, 그 주소안에 있는 값을 꺼내와야 하는데, 문자열의 주소는 찾아가면 바로 문자열을 찾을 수 있으니 *를 안쓴다.
char *a;
scanf("%s", a); //이것은 불가능 하다. scanf는 변수에 입력값을 저장할 때, 변수에 실제로 확보된 공간이 있어야 한다.
a = "hello" //이건 가능
scanf("%s", a); //a에 문자열 넣은 뒤에 다시 이걸해도 불가능 왜냐면 a에 저장된 메모리 주소는 읽기전용이라 scanf를 통한 쓰기는 안된다.
ASCII코드 : 각 문자(알파벳,기호 등)에 해당하는 정수 숫자값이 정해저 있음
아스키 코드 목록 https://ko.wikipedia.org/wiki/ASCII
영어 대문자+32 를하면 소문자가 된다
소문자에서 -32하면 대문자된다.
ex) 대문자 C는 정수로 67이라는 값이다
따라서 정수형태만 출력가능한 %d를 사용하면
printf("%d", 'C'); 의 값은 67이 나온다
문자출력이 가능한 %c를 사용하면 숫자로 67을 써도 67의 문자값인 대문자C가 나온다
서식문자에 따라 같은 값을 입력 받아도 %d는 정수형을, %c는 문자형으로 출력한다
ASCII코드는 정수와 문자가 짝지어져 있기 때문에 실수형 변수에 값을 저장하면 문자형으로 출력되지않는다
int a, b, c; <-한번에 여러개 변수생성가능
int a=34, b=234; <- 변수생성과 동시에 초기화
변수를 구성하는 6가지 요소
1.이름 2.주소 3.자료형 4.값 5.유효범위 6.바인딩시간
변수는 한마디로 기억장소이다.
선언문을 쓰면 변수를 실제 사용할 수 있는 공간으로 만들고 준비하는 할당이라는 작업을 하는데 할당은 컴파일을 할 때에 일어난다.
변수의 이름은 변수에 저장된 값을 가르키기도 하지만, 해당 변수에 할당된 메모리 값의 시작점을 가르키기도 한다.
예를들어 int a; 로 a변수를 선언하면 컴파일 할 때 변수에 메모리가 할당된다.
이 때, int는 4바이트이니 4바이트의 메모리를 할당받는데, 이 할당된 메모리의 주소값이 변수에 저장되는 것이다.
그러나 4바이트를 할당받았다고해서 4바이트 모두의 주소값을 저장하고 있지는 않다.
a변수는 4바이트중 가장 첫번째 1바이트의 주소값만 가지고 있다.
어차피 int는 4바이트고, 변수에 할당된 4바이트는 모두 묶여있어서 다닥다닥 붙어있다.
따라서 첫번째 1바이트의 주소값을 가지고 있으면, 거기서 1바이트를 더하면 두번째 바이트의 주소값이고, 다시 1을 더하면 세번째 바이트의 주소값이다.
이런식으로 나머지 3바이트는 어차피 첫번째 1바이트의 주소값만 있으면 추론할 수 있다.
const int a = 10;
변수를 선언할 때 앞에 const를 붙이면 이는 상수로 선언되며, 변수의 초기값을 변경할 수 없다.
또한, const는 선언과 동시에 초기화를 해줘야 한다. 만약 const int a; 로 선언만 해놓고 a = 10;으로 초기화를 나중에 하면 에러가 난다.
왜냐면 const int a;를 선언했을 때 이미 a라는 변수 자체를 상수로 선언해서 a = 10과 같이 상수를 변경하는 행위를 하지 못하기 때문이다.
const는 포인터변수에도 사용할 수 있다.
int a = 1;
const int *p1 = &a;
int* const p2 = &a;
위와 같이 포인터변수에는 const를 넣는 위치가 두군데 있는데, 어디넣느냐에 따라 용도가 다르다.
const int *p1의 경우 : p1 = &b; 은 되지만, *p1 = 3; 은 안된다. 이 경우는 포인터변수에 저장된 주소값은 변경가능 하지만, 초기에 저장했던 주소의 값(a에 저장된 값 1)은 바꿀 수 없다.
그러나 a = 10 과 같이 포인터변수를 이용하지 않고 직접 a의 값을 바꾸는건 가능하다.
int* const p2의 경우 : *p2 = 3은 되지만, p2 = &b는 안된다. 이 경우는 p2에 저장한 주소값의 값(a변수에 저장된 값 1)은 바꿀 수 있지만 p2에 저장된 주소값(a의 주소값)은 바꿀수 없다
즉, 초기에 저장했던 주소값 이외에 다른값을 넣을 수 없다.
%d - 정수(char, short, int)
%f - 실수(float)
%g - 실수, 지수형태로도저장
%s - 문자열(char *(문자열))
%c - 문자(char, short, int)
%p - 포인터(void)
%ld - 정수(long)
%lld - 정수(long long)
%u - 양의 10진수 정수(unsigned int) //unsigned에 대해선 밑에 "컴퓨터의 저장방식"에서 다룬다
%o - 양의 8진수 정수(unsigned int)
%x, %X - 양의 16진 정수(float, double)
%lf - 실수(long double, double)
서식문자는 값을 입력받거나 출력할 때에 주로 사용되는데, 해당 값을 어떠한 형태로 입력.출력 할지 정하는 것이다.
예를들어 printf를 이용해 문자a를 출력할때, %d로 출력하면 a의 아스키코드 숫자값이 출력된다.
그런데 %c로 하면 a가 그대로 출력된다. 이처럼 입력.출력 할 때 어떤 형태로 값을 저장, 출력 할것인지 잘 생각해야 한다.
블록 {}중괄호 내에서 선언된 변수는 해당 블록 내에서만 사용가능하다 for문이나 if문, 함수 등도 포함
프로그램 내에서 어디서든 쓰일 수 있는 변수는 전역변수라고 하며 main함수 바깥에서 선언한다
지역변수
변수가 선언된 함수 내에서만 사용
함수 실행이 종료되면 변수 사용 불가
다른 함수에서 선언된 변수 이름과 동일한 변수이름 사용가능
전역 변수와 동일한 이름의 지역변수가 있으면 오류 가능성 있음
if, for문 등의 안에서도 지역변수 선언가능
전역변수
유효범위가 전체 프로그램
프로그램 내의 어디서나 사용가능
모든 함수에서 사용가능
main함수 바깥에서 선언
int a; //main함수 시작 전에 선언 함으로써 전역변수 선언
int main(){a = 10;} //main함수 시작
int main() {
int a = 10;
function();
}
void function() {
printf("%d", a);
}
이 코드를 실행하면 오류가 난다.
main안에서 선언된 변수는 main함수 안에서만 사용될 수 있기 때문이다.
int main() {
int a = 10;
function();
}
void function() {
int a;
printf("%d", a);
}
이것도 오류가 난다. 만약 이름이 같은 변수가 main에도 있고, 다른 함수 내에도 있다면, 가장 가까운 변수를 사용한다.
따라서 function을 호출하며 제어가 function으로 넘어갔으니, function에서 선언한 a변수를 사용할것이고, a는 초기화 되지않았기에 오류가 난다.
for (int a=1; a<=10; a++) {
printf("%d\n", a);
}
a=1; <-오류남
예시에서, 변수 a는 for문 안에서 초기화가 진행되었다.
그리고 for문이 끝난 후, 변수 a에 접근하려하면, a가 정의되어 있지 않다면서 오류가 나게된다.
a라는 변수는 for문 안에서 정의되었기 때문에, for문 안에서만 쓸 수 있는 것이다.
if문 등도 마찬가지이다.
정수
int - 시스템에 따라 자동지정, 대체로 long으로 결정됨
short - 2바이트
unsigned int - 4바이트, 양의 정수만 저장가능 //unsigned에 대해선 밑에 "컴퓨터의 저장방식"에서 다룬다
long - 4바이트
long long - 8바이트
char - 1바이트, 문자를담는데쓰임 (알파벳,숫자,기호) 한글은 한글자에 2바이트이다
C언어 에서는 문자를 아스키 코드로 변환해 정수형으로 변수에 저장한다
실수
float - 4바이트
double - 8바이트
double형은 scanf에서 %lf를 해줘야 하고, printf에선 %f를 해야한다.
float은 둘 다 %f
bool - 참,거짓을 저장 (1과0만 있으면 되지만 컴퓨터의 가장작은 단위가 1바이트이기 때문에 char와 같다고 볼 수 있다)
C에서 0이 아닌 모든 숫자는 참으로 간주한다.
형변환 : 자료형을 다른 자료형으로 바꾸는 작업
ex)
int math = 90, korean = 95, english = 96;
int sum = math + korean + english;
double avg = sum / 3; //형변환 안하고 계산
printf("%f", avg);
>>> 93.000000 (정상값93.6667)
정수 / 정수 = 정수
실수 / 실수 = 실수
실수 / 정수 = 실수
정수 + 정수 = 정수
정수 + 실수 = 실수
실수 + 실수 = 실수
이므로 위의 값은 정수로만 저장이 되어 값이93이된다
이같은 문제를 해결하려면
대안1
sum을 int대신 float이나 double같은 실수 자료형으로 저장한다
대안2
캐스트 연산자(형변환)을 사용한다
ex)
double avg = sum / 3; 에서
double avg = (double)sum / 3; 로 바꾸면
정상값인 93.66667이 출력된다
(바꾸고싶은자료형)변수 <-를 사용하면 해당 변수는 그 행 안에서 해당 자료형으로 취급된다
포인터의 형변환
포인터의 타입도 변수처럼 형변환이 가능하다.
포인터 형변환이 되면 읽어올 데이터의 양과 해석방법이 달라질 뿐 저장된 값에는 아무런 영향이 없다.
int item = 4; (주소값 : 100~103번지)
int* ad_item = &item;
char* ch_ad_item = (char*)ad_item; //int 타입의 포인터 변수를 char타입으로 형변환하여 저장
만약 ad_item을 읽어온다면 이건 int타입 포인터 변수이므로 100~103번지의 4바이트 전부를 읽어온다.
그러나 ch_ad_item을 읽어오면 이건 char타입 이므로 100번지의 1바이트 하나만을 읽어온다.
실수출력때 소수점지정 출력하는법
float a=12.647955
printf("a=%.5f", a);
실행결과 -> a=12.64796
%.5f <- 실수를 소수점 6번째 자리에서 반올림하여 5번째 자리까지 출력
%.0f <- 실수를 소수점 첫번째 자리에서 반올림해 정수로 만듬
문자, 문자열 출력
putchar() : printf보다 연산속도가 빠른 문자 출력함수이다
괄호안에 정수를 넣으면 대응하는 아스키코드가 출력되고 문자를 넣으면 그대로 출력한다.
puts() : 매개변수로 들어온 문자열의 주소값으로 가서 문자열을 출력한뒤 개행한다.
char a[10] = "hello";
puts(a);
>>> hello
입력받기
scanf("%d %d", &a, &b); //""따옴표 안에는 입력값을 어떤 형식으로 입력받아 저장할 것인지 적는다 위에있는 서식문자를 참고
, 쉼표 뒷쪽은 입력 값을 어디다 저장할 것인지 변수를 쓴다.
& <-이 기호는 포인터, 주소값을 나타내는 기호로 주소연산자이다. 주소를 불러올땐 이 기호를 쓴다.
scanf 변환값이 무시되었습니다 에러뜰시에
scanf를 scanf_s로 바꾼다
또는 맨위에 #define_CRT_SECURE_NO_WARNINGS 를 입력하면
아니면 #pragma warning (disable:4996) 을 입력하면
scanf를 그대로 입력해도 실행이 된다
scanf를 통해서 입력값을 저장할 때, 변수에는 확보된 실제 공간이 있어야 한다.
따라서 변수에 확보된 공간이 없는 빈 변수라면 scanf를 사용할 수 없다.
char a, b;
char c[5];
scanf("%c", &a); >>> 12345입력
printf("%c ", a); >>>결과 : 1 이렇게 %c로 하나의 문자만 입력 받는데, 거기다 문자열을 입력할경우 맨 앞의 한개의 문자만 변수에 저장된다.
scanf("%c", &b);
printf("%c ", b); >>>결과 : 2
scanf("%s", c);
printf("%s", c); >>>결과 : 345
처음 scanf에서 12345를 입력하면 나머지 scanf들은 입력을 받지 않고 자동으로 값이 저장되 printf에 의해 출력된다.
이 원리는,
위에 첫번째 scanf에서, 문자열을 입력할경우 12345가 버퍼에 저장된다.
그리고 a는 문자하나만을 입력받기 때문에 버퍼에 저장된12345중 맨 앞의 1만 a에 저장된다.
그러면 남은 2345는 여전히 버퍼에 저장되어 있는 상태이다.
그 상태로 바로 두번째 scanf를 쓰게되면, 사용자가 입력을 하지 않아도 자동으로 버퍼에 저장된 2345가 scanf로 인해 변수에 저장된다.
그러나 b도 문자하나만을 입력 받기에 345는 여전히 버퍼에 남아있고, 그 상태로 세번째 문자열을 입력받는 scanf로 인해 남은 345는 문자열로 c에 저장된다.
따라서 이 프로그램에서 처음에 12345를 입력하게 되면 나머지 scanf는 입력받지 않고 바로 1 2 345 라는 값이 뜨게된다.
그래서 위와 같은 상황은 프로그램 오류를 유발할 수 있으니, scanf는 한번 쓸때마다 버퍼를 비워주는 것이 좋다.
getchar : 콘솔창에서 입력받은 문자를 해당하는 아스키 코드의 정수타입으로 반환하는 함수이다.
따라서 char말고도 int에도 저장할 수 있다.
char a = getchar()
int a = getchar()
char a[10];
scanf("%s", a);
a문자 배열을 만들고 scanf를 이용해 문자열을 입력 받는다.
gets : gets가 문자열이라고 판단하는 기준은 개행이다. 따라서 엔터를 치기 전까지 입력한 모든 문자는 문자열에 포함된다.
char a[10]; //a문자 배열을 만듬
gets(a); //a에 문자열 저장
gets를 이용해 a문자배열에 문자열을 입력받는다.
특정 문자가 나올때 까지 문자열에 입력받기
char str[10];
scanf("%[^abc]", str);
a 또는 b 또는 c 문자가 나오기 전까지 모든 문자열을 버퍼에 저장
공백을 포함해서 입력받기
char str[10];
scanf("%[^\n]s", str);
입력 받을때 [^\n]을 %뒤에 넣어주면 \n(개행)으로 엔터를 칠때까지 모든 문자를 문자열에 받는다는 뜻
특정 문자 이외의 값이 나올때 까지 문자열에 입력받기
scanf("%[abc]", str);
a또는 b또는 c 이외의 값이 나오기 전까지 모든 문자열을 버퍼에 저장
특정 문자 이외의 값이 나올때까지 N자리까지만 입력받기
scanf("%5[abc]", str);
a또는 b또는 c 이외의 값이 나오기 전까지 모든 문자열을 5개 까지만 버퍼에 저장
00000000 00000000 00000000 00000000 양수일경우
10000000 00000000 00000000 00000000 음수일경우 (제일 앞 숫자가 1로 바뀐다)
제일 앞 숫자는 양수냐 음수냐를 결정짓는 숫자로써 변수에 양수 50이란 숫자를 저장할경우,
00000000 00000000 0000 0000 00110010 이렇게 된다 (2진수로 저장)
음수를 저장하는 방식은 양수와 음수의 합이 0이 되어야 하기때문에
양수인 00000000 00000000 00000000 00110010 의 보수(반대되는 숫자)인
11111111 11111111 11111111 11001101 에서 1을 더해
11111111 11111111 11111111 11001110 가 -50이 된다
이러면 50과 -50을 더했을 경우
00000000 00000000 00000000 00110010
+11111111 11111111 11111111 11001110
값은 0이 된다
unsigned를 쓸경우 제일 앞의 양수음수 결정숫자를 쓰지 않음으로써 양수의 저장만 가능하게 된다
ex) unsigned int a; 에서
int=long 이고 long은 4바이트이다.
4바이트는 32비트이고 음수 -2,147,483,648 부터 0을포함해 양수 2,147,483,647까지의 수를 저장할 수 있다
그런데 unsigned를 사용함으로써, 0부터 시작해 4,294,967,295까지의 수를 저장할수있다
*=, /=, %=, +=, -=, <<=, >>=, &=, ^=, l=
ex) a+=1 는 a=a+1와 같다
증감 연산자에는 전위와 후위가 있다.
전위 : ++a 또는 --a 는 값을 1증가(또는 감소) 한 후에, 속한 문장의 나머지를 진행 (선증가, 후연산)
후위 : a++ 또는 a-- 는 속한 문장을 먼저 진행한 후, 값을 1증가(또는 감소) (선연산, 후증가)
ex)
int a=10, b=++a; 의 값은 b=11 a=11 이 된다
int a=10, b=a++; 의 값은 b=10 a=11 이 된다
int a=10, b=(a--)+2; 의 값은 b=12 a=9 가 된다 즉, 후위는 문장이 끝나야만 연산이 진행된다
관계연산자는 조건을 만족하면 1을, 만족하지 않으면 0을 반환한다
a<b : a가 b보다 작은가? 결합방향->
a>b : a가 b보다 큰가? 결합방향->
a==b : a와 b가 같은가? 결합방향->
a!=b : a와 b가 다른가? 결합방향->
a<=b : a가 b보다 같거나 작은가? 결합방향->
a>=b : a가 b보다 같거나 큰가? 결합방향->
ex) a=10, b=12, c=(a==b); 이때 c=0 이다
a&&b : a와b 모두 참이면 연산결과로 참을 반환 결합방향->
a||b : a와b 둘 중 하나라도 참이면 연산결과로 참을 반환 결합방향->
!a : a가 참이면 거짓, a가 거짓이면 참을 반환 결합방향<-
참고로 | <- 이 기호는 Shift + \ 이다
ex) a=3, b=5
a==b&&b<12 의 결과는 0이다
a==b||b<12 의 결과는 1이다
!a 의 결과는 0이다 (*C언어는 0이 아닌 모든값을 참으로 간주한다)
int a = 1;
if (1 < a < 100) //이렇게 쓰면 안된다. 1 < a && a < 100 이렇게 써야한다.
이렇게 하면 왼쪽에 있는 1 < a가 먼저 비교된 후, 참 거짓(1, 0)을 반환하여 그걸 다시 100이랑 비교한다.
따라서 1<a이 참이든 거짓이든 100보다 작으므로 결과는 무조건 참이 나오게 되는것이다.
int num = -1;
int absnum = num < 0 ? -num : num;
printf("%d", absnum);
>>> 1
삼항연산자
식1 ? 식2 : 식3
의 형태로 쓴다
식1이 참이면 삼항연산자의 결과값이 식2,
식1이 거짓이면 삼항연산자의 결과값이 식3
식을 하나만 사용하도록 되어 있는 부분에 여러 식을 사용하고자 할 때 사용한다.
우선순위가 가장 낮다.
int a, b; <-이 ,는 구분을 위한 것
for (int a=0, b=0; a<10; a++) <- 이 ,는 연산자
식 하나만 쓸 수 있는곳에서 사용
여러개 식을 하나의 식으로 만든다.
p = 1+3, 4*5; 일때, p에는 최종적으로 20이 들어간다.
변수 = 식1, 식2 일때 결과값은 식2가 들어간다.
if는 괄호안의 연산을 먼저 실행하고 그것이 참이라면 중괄호안의 코드를 실행한다
(거짓일경우 실행하지 않는다)
else if는 처음의 if가 거짓인 상태에서 else if의 조건을 만족하면 참을 반환한다
else는 if와 else if가 모두 거짓일 경우 실행한다
else는 조건식이 없고 실행식만 있다.
if의 조건식에는 식이면 다 올 수 있다.
정수, 음수, 실수, 문자, 문자열 다 가능
int a;
if(a=1) printf("%d", a);
>>> 1
위와 같이 대입문의 형식도 if의 조건식에 들어갈 수 있다.
짝수, 홀수 찾아주는 프로그램
int a;
scanf("%d", &a);
if(a%2==0) printf("%d는짝수",a);
else printf("%d는홀수",a);
예제를 해석하면
만약 a를 2로 나눈값이 0이라면 짝수를 출력하고, 0이 아니면 홀수를 출력한다
if와 if else, else 는 전부 합쳐서 하나의 명령문으로 취급한다.
int num = 3;
if( num % 3 == 0);
{ printf("O"); }
else
{ printf("X"); }
>>> 에러
위 코드가 에러가 나는 이유는, if괄호 옆에 ;이 붙어 있어서 if가 참일때 제어가 ;로 넘어간다.
if문 뒤에는 참일때 실행할 명령문 하나가 와야한다. (단문, 복문{} , null문 등 모두 하나의 명령문으로 취급)
그런데 ;이 if의 조건식 뒤에 붙어있으므로 이것이 하나의 명령문으로 취급된다.
그럼 그 뒤 printf문은 if문 안의 명령이 아니라 따로 독립된 명령이 된다.
따라서 if문 뒤에 명령문이 2개가 오게 된다.
그럼 else또한 if else문이 아닌 else만 독립적으로 있는것으로 인식된다.
따라서 else는 if없이는 존재할 수 없으므로 에러가 난다.
switch 는 if와 비슷한 기능이다
switch(식)
{
case 값: 명령;
default : 명령;
}
switch (식)에는 식이 올 수 있다.
그러나 case의 값에는 식이 올 수 없고 정수(문자)만 올 수 있다.
ex)
int choice;
scanf("%d", &choice);
switch (choice) {
case 1: printf("새 게임. \n");
break;
case 2: printf("불러오기 \n");
break;
case 3: printf("설정 \n");
break;
default: printf("잘못입력하셨습니다 \n");
}
choice에 1~3의 값이 입력되면, 각 함수를 출력한다는 뜻이다
break의 기능은 멈춤이다
case가 조건을 만족하면 해당 case를 실행한 후, 뒤에 있는 case들은 조건을 따지지 않고 모두 실행한다.
만약 break가 없는 상태로 1을 입력하면, case1~3의 함수가 모두 출력되는 것이다.
따라서 이것을 막기 위해 break를 쓴다.
default는 1~3을 제외한 나머지 값이 입력됐을 때이다.
ex)
int i=1;
while (i<10) {
printf("%d\n", i);
i++;
}
while은 괄호안의 조건을 만족할시 중괄호 안의 함수를 반복한다
만족하지 못하면 반복을 멈추고 넘어간다
ex)
int i=15;
do {
printf("%d\n", i);
i++;
} while (i<10); //while의 조건식 옆에 ; 붙여야 됨
do while은 while의 조건을 만족하지 않아도 무조건 처음 한번은 중괄호 안의 내용을 실행하고,
그뒤 while의 조건을 만족하면 반복, 만족하지 않으면 반복하지 않는다
위의 예제에서, i의 값은 15로, while의 조건인 i<10를 만족하지 않지만, do로 인해서
한번은 실행 되기때문에, 프로그램을 실행해보면 15라는 값이 출력된다
일정하게 증가, 감소되는 변수가 필요할때, 배열의 모든 원소에 순차적으로 접근하고 싶을때, 특정 횟수만큼 작업을 반복하고 싶을때 쓴다.
for (초기식; 조건식; 증감식) { 실행식; }
초기식 : 반복문에서 사용하는 변수를 초기화 하는 식
조건식 : 반복 여부를 결정하는 식
증감식 : 루프 제어변수의 값을 변화시키는 식
ex)
int a;
for (a=1; a<=10; a++) {
printf("%d", a);
}
>>> 12345678910
int a;
for (a=1; a<=10;) {
printf("%d\n", a);
a++;
}
>>> 12345678910
이런 형태로 증감식을 실행식안에 넣을 수도 있다
조건식과 상관없이 가장 가까운 반복문 또는 switch문 한개를 벗어나게 한다.
중첩된 반복문의 경우 현재의 반복문 하나만 벗어난다.
가장 가까운 반복문의 현재 반복을 멈추고 다음 반복을 진행시킨다.
for문의 경우 continue를 써도 스텝을 1 소모한다.
for (int i = 0; i < 5; i++) {
if (i == 1) continue; //continue가 실행되면 continue밑은 실행하지 않고 바로 다시 조건식으로 넘어가서 다음 반복을한다.
printf("%d", i);
}
>>> 0234
구조체란 한마디로 사용자 정의 자료형이다.
구조체는 하나의 변수안에 여러가지의 자료형을 쓸 수 있도록 한 것이다.
변수안에 또다른 변수가 있다고 볼수도 있는데, 배열도 이와 비슷하지만 배열과의 차이점은 배열은 하나의 자료형만으로 이루어져 있지만,
구조체는 여러가지의 자료형을 이용할 수 있다.
예를들어 a라는 구조체를 새로 정의할때 int ary[10], char ary2[10], int a, char b, double c 이런식으로 여러가지의 자료형을 넣을 수 있다.
그러나 c언어에 없는 자료형을 새로 만드는것 이라기보단 이미 있는 자료형들(int, char, double 등)을 조립해 만드는 자료형이라는 표현이 적합하다.
구조체의 각 요소들은 배열과 마찬가지로 기억장소의 연속적인 위치에 저장된다.
구조체는 "자료형"이기 때문에 구조를 정의한다고 바로 사용할 수 있는것이 아니다.
해당 구조체(자료형)으로 변수를 선언하여 기억공간을 할당 받은후에 사용할 수 있는것이다.
비유하자면 int라는 자료형만 있다고 바로 사용할수 있는게 아니라 int a; 이런식으로 int형 변수를 선언해 기억공간을 할당받아야 사용할 수 있는것과 마찬가지이다.
한마디로 구조체는 한번 정의하면 int, char, double 등과 같은 자료형과 동급으로 취급된다는 의미이다.
구조체의 정의는 함수의 프로토타입 선언처럼 main함수 밖에서 선언해도 되고, main함수 안에서 선언해도 된다.
그러나 main함수 안에서 선언하면 스코프(적용범위)의 영향을 받는다.
지역변수처럼, main함수의 중괄호{} 안에서만 사용할 수 있는 것이다.
main함수 바깥에서 선언하면 전역변수처럼 코드 어디서나 사용할 수 있다.
또한 정의의 순서도 중요하다. 정의하기 전에 그 구조체를 사용할 순 없다.
무조건 정의가 먼저 되어있어야 사용할 수 있다.
정의 형태는 다음과 같다.
struct 구조체이름 //구조체 이름은 생략가능
{
//구조체 멤버 선언문
구조체자료형 구조체멤버이름
..............
}; //중괄호 밖에 세미콜론 달아야 한다.
ex) 성명, 나이, 전화번호로 구성된 person 이라는 구조체(자료형)을 정의한다면
struct person //구조체 이름(여기선 person)은 생략 가능하다
{
char name[11];
int age;
char tel[13];
};
이런 식으로 정의할 수 있다.
name[13], age, tel[13]에 해당하는, 구조체 내부의 변수들을 멤버라고 한다.
구조체 이름은 아래와 같이 생략도 가능하다
struct //구조체 이름을 생략했다.
{
char name[11];
int age;
char tel[13];
};
그러나 이러면 이름이 없기 때문에 자료형을 정의해도 쓸수가 없다.
따라서 나중에 구조체 변수를 선언하거나, 자료형이 쓰이는 곳에 구조체를 쓸 수도 없다.
따라서 아래와 같이 구조체를 정의함과 동시에 변수를 선언해야 한다.
struct
{
char name[11];
int age;
char tel[13];
}a, b, c; //구조체를 정의함과 동시에 a, b, c라는 구조체 변수를 선언했다.
이렇게 구조체를 정의하고나면 자료형이 쓰이는 모든곳에 기본자료형(int, char 등)과 동등하게 사용할 수 있다.
ex) 구조체를 반환하는 함수의 경우
struct person func (int x, char y){
........
}
이렇게 자료형이 쓰이는 곳에 struct person을 넣으면 된다
ex) 구조체를 매개변수로 넣는 함수의 경우
int func (struct person a, int b){
.........
}
정의한 person 자료형의 변수를 선언하려면
struct person a;
struct 구조체이름 변수이름; //이런식으로 선언한다.
위에선 a가 person자료형의 변수이다.
구조체 변수는 일반적인 변수와 동등하게 사용할 수 있다.
일반변수가 쓰이는 곳 모두 구조체 변수가 쓰일 수 있다.
person에는
char name[11];
int age;
char tel[13]
이렇게 3개의 변수가 들어있는것과 마찬가지이다.
따라서 person 변수 a의 크기는 11 + 4 + 13 = 28byte 이다.
구조체의 멤버에 접근하는 방법은
struct person a;
a.age = 23;
a.name = "park"; //이건 안된다. 문자열은 이렇게 못넣으니까. scanf나 다른 함수를 써야한다.
a.tel = "01012341234"; //이건 안된다. 문자열은 이렇게 못넣으니까. scanf나 다른 함수를 써야한다.
이와같이 [변수이름.멤버] 로 접근가능하다. 이것을 도트연산자 라고 한다.
도트연산자는 해당 멤버의 "값"을 가져오는 것이다. (주소값을 가져오는게 아님)
방법1 선언과 동시에 초기화
struct person a = {"park", 23, "01012345678"};
선언과 동시에 초기화 할때는 반드시 멤버의 순서대로 (여기선 name, age, tel) 값을 넣어주어야 한다.
struct person a = {"park", 23}; //이렇게 일부의 멤버에만 값을 넣는 것도 가능 단, 멤버 순서대로 값을 넣는건 동일
방법2 대입문을 통한 초기화
struct person a = {"kim", 25, "01012341234"};
struct person b;
b = a;
구조체 변수는 일반 변수와 동일한 취급 이므로 이렇게 변수에 대입시킬수도 있다.
b.age = 15; <- 또는 이렇게 멤버단위로 대입할수도 있다.
방법3 입력을 통한 초기화
struct person a;
scanf("%s %d %s", a.name, &a.age, a.tel);
이렇게 일반변수처럼 입력을 받아서 초기화 할수도 있다.
&를 붙인것과 안붙인것의 배열이기 때문. (배열의 이름은 배열의 시작주소를 의미하므로)
잘못된 예
struct person a;
a = {"kim", 25, "01012341234"}; <- 이건 안된다. 대입문의 오른쪽엔 식이 와야하는데, 이건 식이 아니기 때문이다
struct person a = {"kim"};
struct person b;
b.name = a.name <- 이것도 안된다. 문자열은 이런식으로 대입할 수 없다. strcpy를 이용해야 한다.
struct person a[10];
이렇게 구조체변수로 배열을 만드는 것도 가능하다. 구조체변수는 일반변수와 동급이므로.
구조체 배열일때 멤버에 접근하는 방법은 아래와 같다.
a[0].name;
a.name[0] <- 이렇게 하면 안된다. 이건 name멤버가 배열로 되어있을때 사용한다.
구조체 배열을 선언과 동시에 초기화 할 땐
struct person a[2] = {{"park", 23, "01012341234"} , {"kim", 21, "01023231212"}};
이렇게 한다.
만약 info라는 구조체를 먼저 정의해놨다면
struct info
{
int g;
char t;
}
struct person
{
struct info a;
int b;
char c;
};
이렇게 구조체 내부에 구조체를 사용할수도 있다.
단, info라는 구조체는 반드시 먼저 정의가 되어있어야 한다.
정의 되지않은 구조체를 사용할 순 없다.
중첩구조체를 사용하면 멤버에 접근할때
struct person p;
p.a.g = 123;
도트연산자도 이렇게 써야한다.
struct person *p;
이런식으로 구조체도 일반 변수와 똑같이 포인터 변수를 선언한다.
p의 크기는 포인터 변수이므로 4바이트 이다.
struct person a = {"park", 23, "01012341234"};
struct person *b = &a;
구조체 포인터 변수 b에 a의 주소값을 넣고 b를 이용해 a의 값을 출력하려면,
다음과 같이 멤버에 접근해야 한다.
printf("%s %d %s", (*b).name, (*b).age, (*b).tel);
단순히 *b 이렇게 쓰면 멤버에 접근할 수 없기 때문에 위와 같이 (*b)이렇게 괄호로 b의 주소값을 찾아간뒤 .age와 같이 도트연산자를 이용하여 멤버에 접근한다.
그런데 이렇게 쓰면 코드를 작성할때 번거롭기 때문에 다음과 같이 쓸 수도 있다.
printf("%s %d %s", b->name, b->age, b->tel);
이렇게 화살표모양의 연산자를 쓸수 있으며 이를 화살표연산자 라고 한다.
이 화살표 연산자는 구조체와 공용체 포인터 변수에서만 쓸수있다.
정리하면 다음과 같다
a.name = (*b).name = b->name
a.age = (*b).age = b->age
a.tel = (*b).tel = b->tel
함수의 매개변수로 구조체 포인터를 넘겨줄때
struct person x;
func(&x);
void func(struct person *a){
scanf("%s %d %s", (*a).name, &(*a).age, a->tel);
printf("%s %d %s", a->name, (*a).age, (*a).tel);
}
&(*a).age 에서 &를 쓴 이유는 도트연산자는 해당 멤버의 값을 가져오는 것이기 때문에 (*a).age 이렇게 썼다간 주소값이 아닌 a.age값이 가져와지기 때문.
괄호와 *를 쓴 이유는 a에 들어있는 x의 주소값을 찾아간 다음( * <-을 이용해) 그 멤버중 age에 가서 그값을 가져오는데,( .age <-도트연산자를 이용해서)
&주소연산자로 인해 x.age의 값이 아닌 x.age의 주소값을 가져오게 되는 것이다.
그니까 x.age멤버에 바로접근하는게 아니라 일단 x를 찾아가는게 먼저라는 것이다.
그래서 (*a)로 먼저 x를 찾아간후, .age 도트연산자로 age멤버의 값을 가져오고,
마지막으로 &주소연산자로 age의 멤버값이 아닌 age의 주소값을 가져오라는 명령이 되는것이다.
구조체 배열을 매개변수로 넘겨줄때
struct person x[5];
func(x);
void func(struct person *a){
scanf("%s %d %s", a[1].name, &a[1].age, a[1].tel);
printf("%s %d %s", a[1].name, a[1].age, a[1].tel);
}
typedef 기존자료형이름 새로운자료형이름;
typedef는 기존에 쓰던 자료형이름을 새로운 자료형이름으로 바꾸는 것이다.
ex)
typedef int INTEGER;
typedef double REAL;
INTEGER a;
REAL b;
위에서 int와 double이란 자료형의 이름을 INTEGER와 REAL로 변경했다.
따라서 이후에 int와 double 자료형을 쓸때 INTEGER와 REAL로 쓸수있다.
그리고 typedef로 자료형이름을 바꿔도 기존 자료형이름을 쓸수없는것이 아니다.
int를 INTEGER로 바꿔도 기존 자료형 이름인 int로 쓸수있다. int와 INTEGER 둘 다 쓸 수 있는것이다.
자료형의 크기나 타입(숫자, 문자 등)이 달라지는 것은 아니다.
그냥 자료형의 이름만 바꾸는 것이다.
typedef struct person
{
char name[10];
int num;
}person;
person p1; //변경된 자료형이름으로 변수선언
위와 같이 구조체 정의할때 struct앞에 typedef를 붙이고, 중괄호 닫힌뒤에 변경할 자료형이름을 써주면
구조체를 정의하는 동시에 자료형이름을 변경할 수 있다.
typedef struct //이렇게 구조체 이름 안써도 가능
{
char name[10];
int num;
}person;
person p1; //변경된 자료형이름으로 변수선언
struct person
{
char name[10];
int num;
};
typedef struct person person;
person p1;
물론 위와같이 정의가 끝난후에 변경하는 것도 가능하다.
해당 글 참고
https://kcoder.tistory.com/265
구조체가 여러 자료형의 변수를 조립해 만든 하나의 커다란 변수라면
공용체는 하나의 변수를 여러가지의 자료형이 공유하는 것이다.
공용체의 각 멤버들 중 가장 큰 크기의 기억공간을 할당한다.
ex) int, double, char 로 이루어진 공용체가 있다면 셋중 가장 큰 크기인 double(8바이트)의 크기대로 8바이트의 변수가 만들어진다.
구조체는 멤버에 각각 값을 저장할 수 있지만, 공용체는 하나의 변수를 각 자료형이 공유하기 때문에 멤버가 여러개 있더라도 하나의 값만 저장할 수 있다.
또한 이미 값이 저장되어 있는 공용체 변수에 다른 값을 저장하면 기존에 저장되어있던 값은 사라진다.
공용체도 구조체와 같이 한번 선언하면 일반 자료형과 동급으로 취급, 공용체변수도 일반 변수와 동급.
union 공용체이름 //공용체이름은 생략가능 (구조체와 같다)
{
//공용체멤버 선언문
//자료형 멤버이름
};
정의하는 것은 위와 같이 struct 대신 union을 쓴다는것 빼곤 구조체와 거의 똑같다.
ex)
union case1
{
int a;
char b;
double c;
}p1;
p1의 크기는 8바이트(double이 8바이트므로)
struct person
{
char name[10];
int age;
union
{
int a;
char b;
}p1, p2;
};
위와 같이 구조체 안에서 정의하는 것도 가능
union abc
{
int a;
int b;
};
이처럼 공용체 안에 동일한 자료형을 넣어도 된다.
union case1
{
int age;
char grade;
};
struct person
{
int a;
char b;
union case1 c;
};
이런식으로 구조체 안에 공용체를 넣을 수 있다. 공용체 안에 구조체를 넣는것도 가능
단, union case는 미리 정의되어 있어야 한다.
struct person p;
p.c.age = 10;
scanf("%c", &a.c.grade); //grade는 char이므로 %c로 했다.
요렇게 중첩 공용체일 경우 멤버와 서식문자에 신경써주어야 한다.
union case2
{
union case1 a;
int b;
};
이렇게 공용체 안에 공용체도 가능
union case1
{
int a;
char b;
double c;
}p, p2, 3;
구조체와 마찬가지로 이렇게 정의와 동시에 선언도 가능
union
{
int a;
};
이렇게 공용체 이름 없이도 선언 가능
union case1 p1;
p1.a = 10;
p1.b = 'A';
공용체는 하나의 변수를 공유하기 때문에 값은 하나만 저장가능.
따라서 이렇게 하면 p1.a 값은 사라지고 p1.b에 값이 저장된다.
union case1
{
int a;
char b;
double c;
};
union case1 p1;
p1.a = 1; //멤버에 접근하는 방식도 구조체와 동일 (도트연산자 사용)
p1.b = 'A';
p1.c = 1.2345
union case1 p1[5];
p1[0].a = 55;
scanf("%c", &p1[1].b);
이렇게 배열도 가능
화살표 연산자 또한 공용체에서 쓸 수 있다.
union case1 p1, *p2;
p2 = &p1;
(*p2).a = 10;
scanf("%d", &(*p).a); // = &p->a 이렇게 쓸수도 있다.
printf("%d", (*p).a); // = p->a
union case1
{
int a;
char b;
};
typedef union case case;
case1 p1, p2;
또는
typedef union
{
int a;
char b;
}case1;
case1 p1, p2;
입력이나 출력을 파일을 이용하여 처리하는 것
아래처럼 여러 파일을 동시에 열때는 파일 개수만큼 서로다른 파일포인터변수가 필요하다
FILE *f, *t;
f = fopen("a.txt", "r");
f = fopen("b.txt", "w");
그러나 아래와 같이 파일을 닫으면 해당 파일포인터변수에 다른 파일의 주소를 넣을 수 있다.
FILE *f;
f = fopen("a.txt", "r");
fclose(f);
f = fopen("b.txt", "w");
파일포인터 변수 또한 변수이기 때문에 유효범위(스코프)의 영향을 받는다.
-순차접근파일-
파일을 처음부터 끝까지 순서대로 처리
만약 다시 되돌아가서 읽거나 쓰고자 한다면 처음부터 다시 읽거나 써야한다.
파일 내용을 순차적으로 읽고 쓰기에 적합
텍스트 파일에서 사용
순차 파일의 처리 과정 : 파일열기 - 읽기/쓰기 - 파일닫기
-임의접근파일-
파일내의 임의의 위치에 임의의 순서로 처리
-파일 열기-
fopen("파일경로", "모드") 모드 : r(read 읽기), w(write 쓰기), a(추가쓰기)
r은 읽기만 할 수 있고, w는 쓰기만 할 수 있다. fopen은 파일을 열기만 하는 것이고 파일을 연 후 사용하기 위해선 FILE변수에 대입, 읽기/쓰기 함수사용 을 해야한다.
-파일 읽기-
fscanf(), fgets(), fgetchar() 등 기존 입력함수들 앞에 f를 붙임
-파일에 쓰기-
fprintf(), fputs(), fputchar() 등 기존 출력함수들 앞에 f를 붙임
-파일 닫기-
fclose()
입출력을 수행하기 위한 대상 파일을 가리키기 위해 선언된 변수
파일의 주소를 기억하는 포인터 변수이다.
파일포인터 변수의 자료형은 FILE
파일을 다루기 위해선 파일 변수를 선언해야 한다.
ex) FILE *file;
위와 같이 FILE형의 변수를 선언하는데, 이때 반드시 포인터 변수로 선언해야 한다.
파일 포인터 변수에는 파일의 주소가 저장 된다.
file = fopen("a.txt", "r");
이렇게 파일을 연 뒤 파일포인터변수에 저장해야 한다.
fopen("파일이름", "모드");
파일이 저장되있는 하드웨어 장치로의 통로를 여는 과정
파일열기를 성공하면 유효한 파일포인터, 실패하면 NULL포인터를 반환한다.
-r모드-
파일을 순차적으로 읽기위한 모드이다. (읽기만 할 수 있고, 쓸 수는 없다.)
r모드로 파일을 열때 해당 경로에 파일이 없으면 NULL이 파일포인터변수에 저장된다.
따라서 r모드는 파일을 연 후에 제대로 열렸나 확인하는 작업 필요
-w모드-
파일에 출력하기 위한 모드이다. (쓰기만 할 수 있다), w모드로 파일을 열때, 해당 경로에 파일이 없을 경우, 입력받은 경로로 새로 파일을 만든다.
따라서 w모드는 파일이 없어도 새로 파일을 만들어 열기 때문에 확인작업이 필요없다.
-a모드-
추가적으로 쓰기위한 모드이다. w모드와 마찬가지로 파일이 경로에 없으면 새로 만들어서 연다.
-w모드와 a모드가 따로 있는 이유-
w모드로 열면 기존에 txt파일에 무엇이 적혀있던 내용이 전부 초기화되고 새로 처음부터 쓰게된다.
따라서 이미 적혀있는 부분 뒤에 이어서 쓰고 싶을때 a모드로 열어서 이어쓸수있다.
fclose(파일포인터변수이름);
파일포인터변수의 이름을 매개변수로 받아 해당 파일을 닫는다.
fclose()가 성공적으로 실행되면 0을 반환
오류가 발생하면 EOF(End OF File)반환
파일을 다쓰고 더이상 쓸일이 없을 때 열었던 파일을 닫는다.
fscanf(파일포인터변수이름, "입력형식문자열", 매개변수리스트);
입력형식을 지정하여 다양한 자료형의 자료를 파일로부터 입력
성공적으로 실행하면 읽어온 자료의 항목 개수를 반환한다.
오류가 발생하거나 파일의 끝에 도달하면 EOF반환
fscanf에서 정보를 읽어오는 단위는 띄어쓰기나 개행문자 등으로 구분해서 읽어온다.
또한 순차접근방식에선 제일 위에서 부터 순서대로 읽어오므로 서식문자나 자료형에 주의해야 한다.
ex)
a.txt파일의 내용
hello
bye
FILE *f;
f = fopen("a.txt", "r");
char h1[10], h2[10];
fscanf(f, "%s", h1); //처음엔 가장 위에있는 hello를 읽어와서 h에 저장
fscanf(f, "%s", h2); //다음번에는 다음 순서인 bye를 읽어와서 h에 저장
printf("%s %s", h1, h2);
>>> hello bye
fprintf(파일포인터변수이름, "출력형식문자열", 매개변수리스트);
출력형식을 지정하여 다양한 자료형의 자료를 파일로 출력
성공적으로 실행하면 출력한 자료의 항목 개수를 반환한다.
오류가 발생하거나 파일의 끝에 도달하면 EOF반환
ex)
FILE *f;
f = fopen("a.txt", "w");
fprintf(f, "%c", a);
fprintf(f, "%d", 10);
fprintf(f, "%s", "hello");
a.txt파일 결과↓
a10hello
위와 같이 띄어쓰기나 개행 문자를 넣어주지 않으면 그대로 값이 붙어서 출력된다.
이후에 읽기모드로 열어서 fscanf(f, "%c%d%s", &a, &b, c) 로 읽으면 서로 다른 자료형이므로 구분되어서 읽혀져서 각각 따로 변수에 저장되긴 한다.
그러나 경고메시지를 출력하므로 웬만하면 개행이나 띄어쓰기로 값마다 구분을 하는것이 좋다.
그리고 같은 자료형을 붙여서 출력했을경우
ex) fprintf(f, "%s%s", "hello", "hi");
위의 결과 값이 hellohi 로 나왔을 경우
이 경우엔 fscanf(f, "%s", a); 로 읽으면 hellohi 로 문자열이 붙어서 읽힌다.
순차접근모드에서 r (읽기모드)로 열었을 경우 해당 경로에 파일이 없으면 에러없이 NULL이 저장된다
따라서 사용자는 제대로 파일이 열렸는지 확인할수가 없다.
그래서 읽기모드로 파일을 열땐 파일이 제대로 열렸는지 확인하는 코드를 작성하는게 좋다.
ex)
FILE *f;
if ((f = fopen("a.txt", "r")) == NULL){
printf("파일을 열 수 없습니다");
exit(1);
}
위와 같이 작성하면 파일이 열리지않아서 NULL이 저장되었을 때 메시지 출력후 프로그램을 종료시킨다.
FILE *f;
f = fopen("a.txt", "r");
if (f == NULL){
printf("파일을 열 수 없습니다");
exit(1);
}
if 조건문 안에 안넣고 이렇게 해도 된다.
파일을 열때 경로 없이 파일의 이름만 입력하면 프로그램의 실행파일이 있는 폴더 내에서 해당 파일을 찾는다.
ex) fopen("a.txt", "w"); //이렇게 경로없이 파일명만 입력할경우
따라서 원하는 경로에서 파일을 찾고 싶다면 fopen() 함수를 쓸때 파일명과 파일경로를 같이 적어줘야 한다.
ex) f = fopen("C:\upload\a.txt"); //이렇게 풀 경로를 적어줘야 한다.
저기서 파일경로에 \을 두번 쓴 이유는 C에서는 문자열내에 \가 하나만 있으면 이스케이프 시퀀스로 인해 제대로 인식이 안된다.
따라서 \을 두개 써서 컴퓨터에 전달될때는 \가 하나만 보이게 하는 것이다.
만약 파일의 처음부터 끝까지 전부 읽고 싶다면 반복문을 사용해야 하는데,
문제는 파일의 끝이 어디인지 프로그래머가 알아야 한다는 것이다.
파일의 끝이 어딘지 모른다면 반복문을 몇회 반복해야 할지 알수없기 때문이다.
따라서 프로그램언어에선 이를 위해 파일의 끝을 알려주는 방법들이 있다.
파일의 끝에선 사용자에게 보이지 않게 마크를 남기는데 그것을 EOF(End OF File)라고 한다.
C에선 파일의 끝인 EOF를 확인해주는 라이브러리 함수인 feof()를 제공한다.
feof() 의 사용법은 다음과 같다.
feof(파일포인터변수이름);
feof를 사용하면 해당위치에서 파일의 끝인지를 확인하고 끝이면 1, 끝이 아니면 0을 반환한다.
ex)
FILE *f;
int aaa;
f = fopen("a.txt", "r");
while ( !feof(f)){ //파일을 읽는것은 끝이 아닐때 읽어야 하므로 !를 붙였다.
fscanf(f, "%d" &aaa); //fscanf로 읽었으므로 다음 feof는 다음순서가 끝인지 아닌지 확인하게 된다
}
학생의 수를 입력 받아 해당 크기만큼의 배열에 각 학생의 번호를 저장하고 싶을 때, 아래와 같이 코드를 작성하면 에러가 난다.
int std;
scanf("%d", &std);
int std_num[std]; //에러
for(int i=0; i < std; i++){
scanf("%d", &std_num[i]);
}
에러의 이유는 C언어의 배열은 정적 할당을 사용하기 때문이다.
정적 할당은 컴파일 할 때에 일어나고, 동적할당은 런타임 중에 일어난다.
C의 배열은 정적 배열로, 컴파일 할 때에 메모리가 할당된다.
따라서 실행중에 정수 값을 입력 받아 해당 값으로 배열의 크기를 정할 수 없다.
배열의 메모리 할당은 실행전 컴파일 할 때 일어나는데, 배열의 크기를 정하는 std라는 변수는 실행중에 값이 입력되기 때문이다.
컴파일 할 때 std라는 변수를 찾아가보아도 값이 안들어 있으니 배열을 선언 할 수가 없는 것이다.
따라서 위의 학생 수 예제와 같이 실행중에 메모리를 할당받고 싶다면 동적 할당을 이용해야 한다.
동적할당 함수들
#include <malloc.h>
동적할당을 하려면 위 라이브러리를 추가해야 한다.
malloc(정수)
를 하면 메모리에서 해당 바이트 만큼의 공간을 할당 받고, 할당 받은 공간의 주소값을 리턴한다.
free(주소값)
을 하면 할당 받은 메모리를 다시 반환한다.
사용이 끝난 메모리는 다시 반환해야 한다.
앞서 살펴봤던 학생 수 예제의 경우 동적할당을 사용하여 해결 할 수 있다.
int num;
int *std_num;
scanf("%d", &num); //학생의 수를 입력 받음
std_num = (int*)malloc(num * sizeof(int)); //학생 수만큼의 값을 저장하기 위해 메모리를 할당 받음
for(int i=0; i<num; i++)
scanf("%d", &std_num[i]); //이렇게 배열을 동적으로 사용할 수 있다
위 예제에서 std_num = (int*)malloc(num * sizeof(int)) 부분을 설명하자면,
num * sizeof(int) 는 배열의 크기를 구하기 위한것이다.
(학생 수) * (int의 크기) 를 함으로써 학생들의 번호를 저장하기 위한 공간을 계산한다.
std_num에 값을 저장하는 이유는 malloc을 쓰면 메모리를 할당 받을 수 있지만, 이름이 없으니 해당 메모리를 불러와서 쓸 수 없다.
따라서 포인터 변수 std_num에 할당 받은 공간의 주소값을 저장한다.
그리고 이때, std_num은 정수형 포인터 변수이므로 (int*)로 해당 주소값을 int형으로 형변환 한다.