같은 자료형의 기억 장소를 하나의 이름으로 관리하는 것
같은 자료형의 기억장소가 연속적으로 나열
배열은 0부터 시작하는 인덱스 번호를 통해 관리
배열 속 변수는 배열이 관리하고 있는 기억장소 중 첫번째 기억 장소의 주소 값을 가짐
배열의 주소 값을 담고 있는 변수도 포인터 변수
배열은 변수와 마찬가지로 선언과 동시에 생성
자료형 배열이름[기억장소개수];
기억장소는 배열을 선언할 때의 개수만큼 만들어지며, 각 기억장소의 크기는 자료형에 따름
int array1[10];
배열은 0부터 시작하는 인덱스 번호를 가지고 기억장소에 접근 가능
array[0] = 10;
array [1] = 20;
만약 배열을 선언할 때 사용한 개수의 범위를 넘어서는 경우 C언어의 종류에 따라 오류가 발생할 수 있음
int array1[2];
array1[0] = 10;
array1[1] = 20;
printf("array1[0] : %d\n", array1[0]);
printf("array1[1] : %d\n", array1[1]);
배열이 관리하는 기억장소를 처음부터 끝까지 사용할 경우 for문과 같이 사용
for문의 반복횟수를 제어하는 변수를 0부터 시작하게 하고 배열의 개수만큼 반복하게 한다면 처음부터 끝가지 모든 기억장소에 접근하는 코드 생성 및 사용 가능
... 생략...
int i;
for (i = 0; i < 3; i++) {
printf("array1[%d} : %d\n", i, array1[i]);
}
배열이 관리할 값을 미리 알고 있다면 배열 선언 시 초기화 가능
기억 장소의 개수 설정은 선택 사항
배열 변수: 배열의 주소 값을 가지고 있는 변수
함수를 호출할 때 배열의 주소 값을 전달하면 함수 안에서 배열 사용 가능
void test(int v1[]) {
int i;
for (i = 0; i < 10; i++) {
printf("v1[%d] : %d\n", i, v1[i]);
}
}
int main()
{
int array1[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
int array2[] = { 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000 };
test(array1);
test(array2);
}
배열을 담고 있는 변수는 배열의 주소값을 가지고 있기 때문에 포인터 변수와 동일
배열은 []를 통해 관리할 수도 있지만 포인터 변수로도 관리가 가능
배열의 주소값을 가지고 있는 포인터 변수는 배열의 첫번째 기억장소를 가르키며 다음과 같이 배열 요소에 접근 가능
배열을 만들 때 함수안에서 지역 변수로 만들게 되면 함수가 끝난 후 배열이 소멸 됨. 이 문제는 배열 앞에 static을 붙여주면 프로그램이 종료될 때까지 배열이 소멸되지 않음
void test1(int *v1){
int i;
for (i = 0; i < 10; i++) {
printf("*(v1 + %d) : %d\n", i, *(v1+i));
}
}
int* test2() {
static int array2[3] = { 1, 2, 3 };
return array2;
}
int main()
{
int array1[] = { 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 };
printf("array1[0] : %d\n", array1[0]);
printf("array1[1] : %d\n", array1[1]);
printf("array1[2] : %d\n", array1[2]);
printf("*array1 + 0 : %d\n", *array1);
printf("*(array1 + 1) : %d\n", *(array1 + 1));
printf("*(array1 + 2) : %d\n", *(array1 + 2));
int i;
for (i = 0; i < 10; i++) {
printf("array1[i] : %d\n", i, array1[i]);
}
for (i = 0; i < 10; i++) {
printf("*(array1+ %d) : %d\n", i, *(array1 +i));
}
test1(array1);
int* array3 = test2();
for (i = 0; i < 3; i++) {
printf("*(array3 + %d) : %d\n", i, *(array3 + i));
}
}
- C의 리터럴 중 ""에 묶여 있는 것을 문자열이라고 함
- C에서는 문자열을 char 타입의 배열로 관리하며 주로 포인터 변수를 사용
char a1[6] = { 'H', 'e', 'l', 'l', 'o', '\0' }; //문자열을 받아오는 경우, 기억공간 마련
printf("a1 : %s\n", a1);
const char *a2 = "Hello"; //문자열을 직접 적는 경우
printf("a2 : %s\n", a2);
char a3[11] = { '안', '녕', '하', '세', '요', '\0' };//한글 1글자는 2byte 차지, char 문자열은 1byte
printf("a3 : %s\n", a3);
//한글은 1글자에 2byte를 차지하기 때문에 배열에 담지 않고 포인터형 변수로 작성해야함
const char* a4 = "안녕하세요";
printf("a4 : %s\n", a4);
C언어에서 대표적으로 많이 사용하는 문자열 함수: strcpy, strcat, cstrlen
Strcpy(문자열1, 문자열2): 문자열 1에 문자열2를 복사
Strcat(문자열1, 문자열2): 문자열1 + 문자열2
Strlen(문자열): 문자열의 바이트 수를 구한다 (한글 2byte).
참고: C언어는 따로 글자수를 얻어오는 함수가 제공되지 않음
char str1[12] = "Hello";
char str2[12] = "World";
char str3[12];
int len;
strcpy(str3, str1);
printf("strcpy : %s\n", str3);
strcat(str1, str2);
printf("strcat : %s\n", str1);
len = strlen(str1);
printf("strlen : %d\n", len);
len = strlen("안녕하세요");
printf("strlen : %d\n", len);
배열은 같은 타입의 기억장소를 하나로 묶어서 관리하는 것.
구조체는 다양한 타입의 기억장소를 하나로 묶어서 관리하는 개념.
구조체를 정의하고 구조체 안에 변수들을 선언하면 구조체를 통해 변수들을 관리할 수 있음
구조체의 기억공간 주소값은 변수명에 담김
구조체{
int a;
int b;
int c;
Char *d;
int e;
}변수명;
struct Student1{
int kor;
int mat;
int eng;
const char* name;
int age;
}stu;
struct Student2 {
int kor;
int mat;
int eng;
const char* name;
int age;
};
int main()
{
stu.kor = 100;
stu.mat = 80;
stu.eng = 70;
stu.name = "홍길동";
stu.age = 30;
printf("stu.kor : %d\n", stu.kor);
printf("stu.mat : %d\n", stu.mat);
printf("stu.eng : %d\n", stu.eng);
printf("stu.name : %s\n", stu.name);
printf("stu.age : %d\n", stu.age);
struct Student2 s1;
struct Student2 s2;
s1.kor = 90;
s1.mat = 80;
s1.eng = 70;
s1.name = "최길동";
s1.age = 40;
printf("s1.kor : %d\n", s1.kor);
printf("s1.mat : %d\n", s1.mat);
printf("s1.eng : %d\n", s1.eng);
printf("s1.name : %s\n", s1.name);
printf("s1.age : %d\n", s1.age);
struct Student2 stu_array[3];
stu_array[0].kor = 10;
stu_array[1].kor = 20;
stu_array[2].kor = 30;
int i;
for (i = 0; i < 3; i++) {
printf("kor : %d\n", stu_array[i].kor);
}
}
구조체를 하나만 만들어 사용할 경우: 구조체 정의 시 마지막에 변수명을 기술
구조체를 정의하고 다수로 만들어 사용할 경우: 마지막에 변수명을 설명하지 않고 구조체를 필요한 만큼 만들어 사용
구조체도 배열로 만들어 사용 가능
struct test1 {
unsigned int a1;
unsigned int a2;
};
struct test2 {
unsigned int a1:1;
unsigned int a2:1;
};
int main()
{
struct test1 t1;
struct test2 t2;
printf("t1 : %d\n", sizeof(t1));
printf("t2 : %d\n", sizeof(t2)); //sizeof는 bit 단위 불가, byte 단위
t1.a1 = 1;
t2.a1 = 1;
printf("t1.a1 : %d\n", t1.a1);
printf("t2.a1 : %d\n", t2.a1);
t1.a2 = 2; //1 0
t2.a2 = 2; // 0
printf("t1.a2 : %d\n", t1.a2);
printf("t2.a2 : %d\n", t2.a2);
}
비트 필드는 구조체를 정의할 때 구조체의 맴버의 사이즈를 비트 단위로 조절할 수 있는 개념
예를 들어 0과 1만 관리하는 변수가 있다고 하면 1bit만 있으면 관리가 가능하지만, C언어에서 가장 크기가 작은 기억공간은 char(1byte, 8bit) 이다.
이에 비트 단위 크기의 기억 장소를 사용할 수 있도록 비트 필드라는 개념을 제공
Struct A{
Unsigned 자료형 변수명:비트수
}
Unsigned 자료형을 많이 사용하는 이유: 부호가 없어 첫 비트를 일반 값을 기억하는 용도로 사용.
typedef는 새로운 타입을 정의하는 키워드
주로 구조체에서 많이 사용하며 구조체를 typedef로 정의하면 향후 구조체가 필요할 때 구조체 이름만으로 구조체 선언이 가능
struct test1 {
int a1;
int a2;
};
typedef struct test2{
int a1;
int a2;
} test2;
int main()
{
struct test1 t1;
t1.a1 = 100;
t1.a2 = 200;
printf("t1.a1 : %d\n", t1.a1);
printf("t1.a2 : %d\n", t1.a2);
test2 t2;
t2.a1 = 300;
t2.a2 = 400;
printf("t2.a1 : %d\n", t2.a1);
printf("t2.a2 : %d\n", t2.a2);
}
공용체는 구조체와 문법은 비슷하지만 개념이 다름.
공용체는 같은 기억장소를 여러 변수들이 공유하는 개념
(구조체는 여러 기억 장소를 하나의 이름으로 관리하는 개념)
실제 개발할 때 많이 쓰여지지는 않지만 필요한 경우가 있을 수 있음
union A{
int a1;
short a2;
char a3;
}
struct Test1 {
int a1;
short a2;
char a3;
};
union Test2 {
int a1;
short a2;
char a3;
};
int main()
{
struct Test1 t1;
t1.a1 = 65535;
printf("t1.a1 : %d\n", t1.a1);
union Test2 t2;
t2.a1 = 65535;
printf("t2.a1 : %d\n", t2.a1);
printf("t2.a2 : %d\n", t2.a2); // cf. 전체 비트가 모두 1이면 -1로 출력함
printf("t2.a3 : %d\n", t2.a3);
}
표준 입출력: 키보드 입력 + 콘솔 출력
C언어에서는 키보드 입력 및 콘솔 출력을 위한 함수를 제공
Getchar: 문자 하나를 입력 받음
Putchar: 문자 하나를 출력
Gets: 문자열을 입력 받음
Puts: 문자열을 출력
Scanf: 다양한 타입의 값을 입력받음
Printf: 다양한 타입의 값을 출력
printf("문자입력 :");
char a1 = getchar();
printf("a1 : %c\n", a1);
putchar(a1);
printf("문자열 입력 :");
char a2[100];
gets(a2);
printf("a2 : %s\n", a2);
puts(a2);
Scanf와 printf 에서 값을 출력할 때 사용하는 문자
char a1[100];
int a2;
/*
printf("문자열 입력 :");
scanf("%s", a1); // 배열값이라 따로 &를 붙이지 않음
printf("숫자 입력 :");
scanf("%d", &a2); //정수형 변수이기 때문에 & 붙임
*/
printf("값 입력:");
scanf("%s, %d", a1, &a2);
printf("a1 : %s, a2 : %d\n", a1, a2);
- C 언어는 파일에 데이터를 저장하고 읽어올 수 있는 함수를 제공
- fopen("파일명", 파일모드): 파일을 찾아 파일을 연다.
- fopen 함수로 파일을 열어주고 읽고 쓰기 한 다음 반드시 fclose로 닫아줘야 함
FILE* fp;
/*
fp = fopen("data1.txt", "w+");
fprintf(fp, "%s", "abcd");
fclose(fp);
*/
fp = fopen("data1.txt", "w+");
fprintf(fp, "%s", "cdef");
fclose(fp);
fp = fopen("data1.txt", "a+");
fprintf(fp, "%s", "kkkk");
fclose(fp);
FILE* fp;
fp = fopen("data2.txt", "w+");
fprintf(fp, "%s", "abcd kkk");
fclose(fp);
fp = fopen("data2.txt", "r");
char a1[255];
//fscanf(fp, "%s", a1); 문자열에 띄어쓰기가 있는 경우 %[^\n] 함께 기재하여 엔터까지 불러오는 것을 표기
fscanf(fp, "%[^\n]", a1);
printf("%s\n", a1);
fclose(fp);
- 개발자가 작업한 .c 파일은 컴파일 과정 후 컴퓨터가 인식할 수 있는 코드 생성
- C언어의 경우 전처리기가 전처리 명령어를 분석해 코드를 완성하고 컴파일러에게 전달함
- 전처리 명령어: 컴파일 전에 개발자가 만드는 코드를 완성
- 불필요한 코드를 목적 파일에 추가하지 않으므로 수행 속도에 도움
#define
#undef: #define 으로 정의된 상수를 제거
#include: 다른 파일에서 작성한 코드를 불러와 사용할 때 사용
#ifdef ~ #endif: #define을 통해 정의된 상수가 있다면 내부 코드가 추가되고 그렇지 않으면 제거
#ifndef ~ #endif: 지정된 상수가 #define을 통해 정의되어 있지 않다면 내부의 코드가 추가되고 정의되어 있다면 제거
#if, #else, #elif: if 문처럼 조건을 통해 코드를 완성 가능
#define DATA1 100
#define DATA2 200
int main()
{
printf("DATA1 :%d\n", DATA1); //여기서 DATA1은 변수가 아니다
printf("DATA2 :%d\n", DATA2);
int a1 = DATA1 + DATA2;
// int a1 = 100 + 200;
printf("DATA1 + DATA2 : %d\n", a1);
#undef DATA1
//printf("DATA1 : %d\n", DATA1);
}
#define DATA1
int main()
{
#ifdef DATA1
printf("DATA1이 정의되어 있습니다.\n");
#endif
#ifdef DATA2
printf("DATA2이 정의되어 있습니다.\n");
#endif
#ifndef DATA1
printf("DATA1이 정의되어 있지않습니다.\n");
#endif
#ifndef DATA2
printf("DATA2이 정의되어 있지않습니다.\n");
#endif
}
#define DATA1 100
int main()
{
#if DATA1 < 50
printf("DATA1은 50보다 작습니다.\n");
#elif DATA1 >= 50 && DATA1 <=100
printf("DATA1은 50이상이고 100이하 입니다.\n");
#else
printf("DATA1은 100보다 더 큽니다.\n");
#endif
}
C언어에서는 #define을 통해 밀 정의되어 있는 상수들이 있음
printf("__DATE__ : % d\n", __DATE__);
printf("__TIME__ : % d\n", __TIME__);
printf("__FILE__ : % d\n", __FILE__);
printf("__LINE__ : % d\n", __LINE__);
printf("__STDC__ : % d\n", __STDC__);
#define을 통해 함수를 정의할 수 있는데 이를 매크로 함수라고 함
매크로 함수는 전처리기에 의해 실행되고 매크로 함수를 사용하는 곳에 정의된 코드가 사용됨
#define은 한 줄로 정의해야 함 (줄바꿈 하고 싶다면 \ 활용)
#define FN1() printf("FN1 호출\n");
#define FN2(a, b) printf("FN2 호출 : %d, %d\n", a, b);
#define FN3(a, b) printf("FN3 호출 : "#a", "#b" \n");
#define FN4(a, b) \
printf("FN4 호출 : \n"); \
printf("a : "#a" \n"); \
printf("b : "#b" \n");
int main()
{
FN1(); //printf("FN1호출\n);
FN2(10, 20); //printf("FN2 호출 : %d, %d\n", 10, 20);
FN3(10, 20); //printf("FN3 호출 : 10, 20\n");
FN4(10, 20);
}
개발을 하다 보면 상수, 변수, 배열, 함수, 구조체 등을 많이 만들어 사용하는데 한번 만든 것들을 여러 파일에서 사용 가능함
코드를 복사해서 넣으면 수정 시 문제가 발생할 수 있음
헤더 파일: 자주 사용하는 여러 코드들을 한 번만 만들어 사용할 수 있도록 제공
헤더 파일에는 변수, 상수, 배열, 함수, 구조체 등을 선언하고 이를 가져다 사용할 수 있도록 함
#include<파일명>: C언어 자체에서 제공하고 있는 헤더파일 사용
#include"파일이름": 개발자가 만든 파일 이름 사용
헤더파일에는 함수를 선언하고, c소스 파일에서는 코드 구현
#include <iostream>
#include "test.h"
int main()
{
printf("data1 : %d\n", data1);
int i;
for (i = 0; i < 5 ; i++) {
printf("data2 : %d\n", data2[i]);
}
Struct1 st1;
st1.value1 = 1000;
st1.value2 = 2000;
printf("st1.value1 : %d\n", st1.value1);
printf("st1.value2 : %d\n", st1.value2);
printf("DATA3 : %d\n", DATA3);
printf("DATA4 : %d\n", DATA4);
function1();
}
Auto
Extern
Register
Static
#include "test1.h"
#include <iostream>
extern int global_a; //다른 곳에서 선언한 전역변수를 가져올 때, 값을 초기화할 수는 없음
extern int global_b;
int main()
{
int local_a1 = 100;
int local_a2 = 200;
printf("local_a1 : %d\n", local_a1);
printf("local_a2 : %d\n", local_a2);
register int i;
for (i = 0; i < 10; i++) {
printf("i : %d\n", i);
}
test2();
test2();
test2();
test3();
printf("global_a : %df\n", global_a);
printf("global_b : %df\n", global_b);
}
할당: 개발자가 어떤 값들을 관리하기 위해 기억공간을 마련하는 것
정적할당, 동적할당
개발자가 사용하고자 하는 기억공간의 크기가 이미 정해져 있으며 stack 이라는 공간에 만들어짐
우리가 지금까지 사용했던 방식이 모두 정적할당에 해당
자료형이나 구조체 등을 이용하여 변수를 선언하면 기억공간이 마련되고 기억공간을 이름이나 포인터 변수를 통해 접근할 수 있음
정적할당된 기억공간의 사용 범위가 종료되면 자동으로 소멸
typedef struct Struct1 {
int value1;
int value2;
} Struct1;
int main()
{
int data1 = 100;
int data2[] = {1, 2, 3};
printf("data1 : %d byte, %d\n", sizeof(data1), data1);
printf("data2 : %d byte\n", sizeof(data2));
Struct1 stu1;
printf("stu1 : %d\n", sizeof(stu1));
}
개발자가 원하는 만큼의 용량을 설정하여 기억공간을 할당하는 것.
Heap 영역에 만들어짐
(Cf. Stack vs heap: 메모리 할당량 S<H)
동적 할당의 경우 오로지 포인터 변수를 통해서만 접근이 가능
동적할당의 경우 자동으로 소멸되지 않으므로 개발자가 직접 소멸시켜야 함
Calloc(개수, 크기): 크기 만큼의 기억공간을 개수만큼 만듦.
만들어진 기억공간은 모두 0으로 초기화가 됨
Malloc(크기): 크기 만큼의 기억공간이 만들어지지만 초기화 되지 않음
Realloc: 기억공간의 크기를 다시 설정
Free: 동적할당된 기억공간을 소멸
typedef struct Struct1 {
int a1;
int a2;
} Struct1;
int main()
{
int * pt = (int*)malloc (4); //안정성을 높이기 위해 (int*)를 붙여준 것과 같이 형식을 맞춰 주는 것이 좋다.
*pt = 100;
printf("*pt : %d\n", *pt);
int i;
int* pt2 = (int*)malloc(4 * 3); //4바이트 공간을 3개 만든다
for (i = 0; i < 3; i++){
pt2[i] = 100 * (i + 1);
}
for (i = 0; i < 3; i++) {
printf("pt2 : %d\n", pt2[i]);
}
for (i = 0; i < 3; i++) {
*(pt + i) = 1000 * (i + 1);
}
for (i = 0; i < 3; i++) {
printf("pt2 : %d\n", *(pt2 + i));
}
Struct1* pt3 = (Struct1*)malloc(sizeof(Struct1));
(*pt3).a1 = 100;
(*pt3).a2 = 200;
printf("a1 : %d\n", (*pt3).a1);
printf("a2 : %d\n", (*pt3).a2);
free(pt);
free(pt2);
free(pt3);
}
소프트캠퍼스, 처음 시작하는 C언어, 구름EDU, URL, 2021년 8월 23일 수강