배열(array): 거의 모든 프로그램밍 언어에서 기본적으로 제공되는 데이터 타입, 여러 개의 동일한 데이터 타입의 데이터를 한 번에 만들 때 사용
// 배열이 없는 경우
int a1, a2, a3, a4, a5, a6;
// 배열이 있는 경우
int A[6];
배열의 가장 기본적인 특징: <인덱스, 요소> 쌍의 집합
즉, 인덱스(index)가 주어지면 해당하는 요소(element)가 대응되는 자료구조
- 객체: <인덱스, 요소> 쌍들의 집합
- 연산:
create(n) ::= n개의 요소를 가진 배열의 생성
retrieve(A,i) ::= 배열 A의 i번째 요소 반환
store(A,i,item) ::= 배열 A의 i번째 위치에 item 저장
int A[6];
배열의 요소 | 메모리 주소 |
---|---|
A[0] | 기본주소 = base |
A[1] | base + 1 * sizeof(int) |
A[2] | base + 2 * sizeof(int) |
A[3] | base + 3 * sizeof(int) |
A[4] | base + 4 * sizeof(int) |
A[5] | base + 5 * sizeof(int) |
int A[3][4]; // 3개의 행과 4개의 열이 만들어짐
// 초기화 방법
int A[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
#include <stdio.h>
#define MAX_SIZE 10
// 배열을 매개변수로 받는 함수
void sub(int var, int list[]){
var = 10;
list[0] = 10;
}
// 주 함수
int main(){
int var; // 정수 변수 선언
int list[MAX_SIZE]; // 정수 배열 선언
var = 0;
list[0] = 0;
sub(var, list);
printf("var=%d list[0]=%d\n",var,list[0]);
}
-> 프로그램 결과를 통해 sub()에서 배열 원소의 값 변경이 그 호출자인 main()에 영향을 미친다는 것을 알 수 있음.
- 객체: <인덱스, 요소> 쌍들의 집합
- 연산:
create(n) ::= n개의 요소를 가진 배열 생성
retrieve(A,i) ::= 배열 A의 i번째 요소 반환
store(A,i,item) ::= 배열 A의 i번째 위치에 item 저장
다항식의 모든 차수에 대한 계수 값을 배열에 저장하는 방법
ex)
을 배열 coef에 저장, 다항식의 최고 차수는 변수 degree에 저장
#define MAX_DEGREE 101 // 다항식 최대 차수 + 1
typedef struct{
int degree;
float coef[MAX_DEGREE];
} polynomial;
polynomail a = {5,{10,0,0,0,6,3}};
단점: 다항식의 대부분의 항의 계수가 0인 희소 다항식의 경우 공간 낭비가 심함
장점: 다항식의 덧셈이나 뺄셈 시에 같은 차수의 계수를 쉽게 찾을 수 있음
#include <stdio.h>
#define MAX(a,b) (((a)>(b))?(a):(b))
#define MAX_DEGREE 101
typedef struct { // 다항식 구조체 타입 선언
int degree; // 다항식의 차수
float coef[MAX_DEGREE]; // 다항식의 계수
} polynomial;
// C = A+B 여기서 A와 B는 다항식
polynomial poly_add1(polynomial A, polynomial B){
polynomial C; // 결과 다항식
int Apos = 0, Bpos = 0, Cpos = 0; // 배열 인덱스 변수
int degree_a = A.degree;
int degree_b = B.degree;
C.degree = MAX(degree_a, degree_b); // 결과 다항식의 차수
while(Apos <= A.degree && Bpos <= B.degree){
if(degree_a > degree_b){ // A항 > B항
C.coef[Cpos++] = A.coef[Apos++];
degree_a--;
} else if (degree_a == degree_b) { // A항 == B항
C.coef[Cpos++] = A.coef[Apos++] + B.coef[Bpos++];
degree_a--;
degree_b--;
} else { // A항 < B항
C.coef[Cpos++] = B.coef[Bpos++];
degree_b--;
}
}
return C;
}
// 주 함수
int main(){
polynomial a = { 5, { 3, 6, 0, 0, 0, 10 } };
polynomial b = { 4, { 7, 0, 5, 0, 1} };
polynomial c;
c = poly_add1(a, b);
printf("C's degree: %d\n", c.degree);
printf("C's coef: ");
for (int i = 0; i <= c.degree;i++){
printf("%.0f ", c.coef[i]);
}
printf("\n");
return 0;
}
#define MAX_TERMS 101
struct{
float coef;
int expon;
}terms[MAX_TERMS];
장점: 구조체 배열 안에 항의 총 개수가 MAX_TERMS를 넘기지 않으면 많은 다항식을 저장할 수 있음
단점: 하나의 다항식이 시작되고 끝나는 위치를 가리키는 변수들을 관리해야 함. 차수도 저장해야하기 때문에 다항식에 따라서는 계수만을 저장하는 첫 번째 방법보다 공간이 더 많이 필요할 수도 있음.
#include <stdio.h>
#include <stdlib.h>
#define MAX_TERMS 101
struct {
float coef;
int expon;
} terms[MAX_TERMS] ={{8,3},{7,1},{1,0},{10,3},{3,2},{1,0}};
int avail = 6; // 현재 비어 있는 요소의 인덱스를 가리킴
// 두 개의 정수를 비교
char compare(int a, int b){
if(a>b)
return '>';
else if(a==b)
return '=';
else
return '<';
}
// 새로운 항을 다항식에 추가
void attach(float coef, int expon){ // 해당 항목들을 배열 terms의 다음 빈 공간에 더하는 함수
if(avail > MAX_TERMS){
fprintf(stderr, "Too many terms\n");
exit(1);
}
terms[avail].coef = coef;
terms[avail++].expon = expon;
}
// C = A + B
void poly_add2(int As, int Ae, int Bs, int Be, int *Cs, int *Ce){
float tmpcoef;
*Cs = avail;
while(As <= Ae && Bs <= Be){
switch (compare(terms[As].expon,terms[Bs].expon))
{
case '>': // A의 차수 > B의 차수
attach(terms[As].coef, terms[As].expon);
As++;
break;
case '=': // A의 차수 = B의 차수
tmpcoef = terms[As].coef + terms[Bs].coef;
if(tmpcoef){
attach(tmpcoef, terms[As].expon);
}
As++;
Bs++;
break;
case '<': // A의 차수 < B의 차수
attach(terms[Bs].coef, terms[Bs].expon);
Bs++;
break;
}
}
// A의 나머지 항들을 이동
for (; As <= Ae;As++){
attach(terms[As].coef, terms[As].expon);
}
// B의 나머지 항들을 이동
for (; Bs <= Be; Bs++) {
attach(terms[Bs].coef, terms[Bs].expon);
}
*Ce = avail - 1;
}
// 주 함수
int main(){
int cs, ce;
poly_add2(0, 2, 3, 5, &cs, &ce);
for (int i = cs; i <= ce;i++){
printf("(%0.f %d) ", terms[i].coef, terms[i].expon);
}
printf("\n");
return 0;
}
행렬(matrix) 표현 방법
#define MAX_ROWS 100
#define MAX_COLS 100
int matrix[MAX_ROWS][MAX_COLS];
장점: 두 행렬 관련 여러 가지 연산이 쉬움
단점: 많은 항들이 0으로 되어 있는 희소 행렬의 경우, 2차원 배열을 사용하면 메모리 낭비가 심함.
#include <stdio.h>
#define ROWS 3 // 행의 개수
#define COLS 3 // 열의 개수
// 희소 행렬의 덧셈 -> 두 행렬의 크기가 같아야 더할 수 있음
void sparse_matrix_add1(int A[ROWS][COLS], int B[ROWS][COLS], int C[ROWS][COLS]){ // C = A + B
for (int r = 0; r < ROWS;r++){
for (int c = 0; c < COLS;c++){
C[r][c] = A[r][c] + B[r][c];
}
}
}
// 주 함수
int main(){
int arr1[ROWS][COLS] = { { 2, 3, 0 }, { 8, 9, 1 }, { 7, 0, 5 } };
int arr2[ROWS][COLS] = { { 1, 0, 0 }, { 1, 0, 0 }, { 1, 0, 0 } };
int arr3[ROWS][COLS];
sparse_matrix_add1(arr1, arr2, arr3);
for (int r = 0; r < ROWS;r++){
for (int c = 0; c < COLS; c++) {
printf("%d ", arr1[r][c]);
}
printf(" ");
for (int c = 0; c < COLS; c++) {
printf("%d ", arr2[r][c]);
}
printf(" ");
for (int c = 0; c < COLS;c++){
printf("%d ", arr3[r][c]);
}
printf("\n");
}
return 0;
}
장점: 메모리 낭비가 줄어듬
단점: 두 행렬 관련 여러 가지 연산이 복잡함
#include <stdio.h>
#include <stdlib.h>
#define ROWS 3 // 행의 개수
#define COLS 3 // 열의 개수
#define MAX_TERMS 10
typedef struct{ // 하나의 요소를 나타내는 구조체
int row; // 각 데이터가 저장된 요소의 번호
int col;
int value;
} element;
typedef struct{
element data[MAX_TERMS]; //1차원 구조체 배열
int rows; // 2차원 배열의 행의 개수
int cols; // 열의 개수
int terms; // 항의 개수
} SpareMatrix;
// 희소 행렬의 덧셈
SpareMatrix sparse_matrix_add2(SpareMatrix A, SpareMatrix B)
{ // C = A + B
SpareMatrix C;
int ca = 0, cb = 0, cc = 0; // 배열의 항목을 가리키는 인덱스
// 배열 A와 배열 B의 크기가 동일한지 확인
if(A.rows != B.rows || A.cols != B.cols){
fprintf(stderr, "SpareMatrix size error\n");
exit(1);
}
// 배열 A와 배열 B의 크기가 동일할 경우에만 실행되기 때문에 배열 A의 행, 열 크기를 배열 C의 행, 열 크기로 지정해줌
C.rows = A.rows;
C.cols = A.cols;
C.terms = 0; // 1차원 구조체 배열의 크기 == 항의 크기
while (ca < A.terms && cb < B.terms){
// 각 항목의 순차적인 번호를 계산한다.
int ind_A = A.data[ca].row * A.cols + A.data[ca].col;
int ind_B = B.data[cb].row * B.cols + B.data[cb].col;
if(ind_A < ind_B){
// 배열 A의 항목이 앞에 있으면
C.data[cc++] = A.data[ca++];
}else if(ind_A == ind_B){
// A와 B가 같은 위치
if((A.data[ca].value + B.data[cb].value)!=0){
C.data[cc].row = A.data[ca].row;
C.data[cc].col = A.data[ca].col;
C.data[cc++].value = A.data[ca++].value + B.data[cb++].value;
}else{
ca++;
cb++;
}
}else{
// 배열 B의 항목이 앞에 있으면
C.data[cc++] = B.data[cb++];
}
}
// 배열 A나 B에 남아 있는 항들을 배열 C로 옮김
for (; ca < A.terms;){
C.data[cc++] = A.data[ca++];
}
for (; cb < B.terms;) {
C.data[cc++] = B.data[cb++];
}
C.terms = cc; // 1차원 구조체 배열의 크기 == 항의 크기
return C;
}
// 주 함수
int main(){
SpareMatrix a = { { { 1, 1, 5 }, { 2, 2, 9 } }, 3, 3, 2 };
SpareMatrix b = { { { 0, 0, 5 }, { 2, 2, 9 } }, 3, 3, 2 };
SpareMatrix c;
c = sparse_matrix_add2(a, b);
printf("A: ");
for (int i = 0; i < a.terms;i++){
printf("(%d %d %d) ", a.data[i].row, a.data[i].col, a.data[i].value);
}
printf("\n");
printf("B: ");
for (int i = 0; i < b.terms; i++) {
printf("(%d %d %d) ", b.data[i].row, b.data[i].col, b.data[i].value);
}
printf("\n");
printf("C: ");
for (int i = 0; i < c.terms; i++) {
printf("(%d %d %d) ", c.data[i].row, c.data[i].col, c.data[i].value);
}
printf("\n");
return 0;
}
void transpose(int A[ROWS][COLS], int transA[COLS][ROWS]){
for (int r = 0; r < ROWS;r++){
for (int c = 0; c < COLS;c++){
transA[c][r] = A[r][c];
}
}
return;
}
int main(){
int arr1[ROWS][COLS] = { { 2, 3, 0 }, { 8, 9, 1 }, { 7, 0, 5 } };
int arr4[COLS][ROWS];
transpose(arr1, arr4);
printf("A\n");
for (int r = 0; r < ROWS; r++) {
for (int c = 0; c < COLS; c++) {
printf("%d ", arr1[r][c]);
}
printf("\n");
}
printf("\n");
printf("D\n");
for (int r = 0; r < COLS;r++){
for (int c = 0; c < ROWS;c++){
printf("%d ", arr4[r][c]);
}
printf("\n");
}
return 0;
}
구조체(structure): 타입이 다를 수 있는 데이터를 묶는 방법
// 구조체 정의
struct 구조체명{
항목1;
항목2;
...
};
// 구조체 변수 생성
struct 구조체명 변수명;
ex) 사람을 나타내는 구조체
항목 1. 문자 배열로 된 이름
항목 2. 나이를 나타내는 정수 값
항목 3. 키를 나타내는 실수 값
// 구조체 형식 정의
struct person{
char name[10];
int age;
float height;
};
// 구조체 변수 선언
struct person a;
// person은 구조체 태그인 동시에 새로운 타입의 이름이 됨
typedef struct person{
char name[10];
int age;
float height;
} person;
// 구조체 변수 선언
person a;
strcpy(a.name,"tom");
a.age = 20;
a.height = 180.5;
// 구조체 정의
typedef struct person{
char name[10];
int age;
float height;
} person;
// 구조체 대입 검사 프로그램
main(){
person a, b;
b = a; // 하나의 구조체 변수의 내용을 다른 구조체에 대입할 수 있는지 여부 -> 가능
}
// 구조체 비교 검사 프로그램
main(){
person a, b;
if(a > b){
printf("A가 B보다 크다\n"); // 구조체 변수끼리 비교할 수 있는지 여부 -> 불가능
}
}
// a가 b보다 크면 1을 반환
// a와 b가 같으면 0을 반환
// a가 b보다 작으면 -1을 반환
int compare(person a, person b){
if(a.age > b.age) return 1;
else if(a.age == b.age) return 0;
else return -1;
}
// 구조체 비교 프로그램
main(){
person a, b;
a.age = 30;
b.age = 20;
if(compare(a,b)){
printf("a가 b보다 나이가 많음");
}
}
자체 참조 구조체(self-referential structure): 특별한 구조체로서 구성 요소 중에 자기 자신을 가리키는 포인터가 한 개 이상 존재하는 구조체를 말함
typedef struct ListNode{
char data[10];
struct ListNode *link; // link 필드가 ListNode를 가리키므로 이 구조체는 자체 참조 구조체임
}ListNode;
#define MAX_STUDENTS 20
#define MAX_NAME 100
// birthday 구조체 선언
typedef struct{
int month;
int day;
}BirthdayType;
// student 구조체 선언
typedef struct{
char name[MAX_NAME];
BirthdayType birthday; // birthday 구조체 포함
}StudentType;
StudentType students[MAX_STUDENTS];
void main(){
strcpy(students[0].name,"HongGilDong");
students[0].birthday.month = 10;
students[0].birthday.day = 28;
}
typedef struct Point{
int x;
int y;
} Point;
Point p1, p2;
p1 = {1,2};
p2.x=9; p2.y=8;
#include <math.h>
int get_distance(Point p1, Point p2){
int ans = sqrt(pow(abs(p1.x - p2.x), 2) + pow(abs(p1.y - p2.y), 2));
return ans;
}
포인터 변수(pointer variable): 다른 변수의 주소를 가지고 있는 변수 = 다른 변수를 가리킨다는 것
char a ='A';
char *p;
p = &a; // p는 a라는 변수를 가리키는 포인터 변수
*p = 'B'; // 변수 a의 내용이 바뀜
void *p; // p는 아무것도 가리키지 않는 포인터
int *pi; // pi는 정수 변수를 가리키는 포인터
float *pf; // pf는 실수 변수를 가리키는 포인터
char *pc; // pc는 문자 변수를 가리키는 포인터
int **pp; // pp는 포인터 변수를 가리키는 포인터
struct test *ps; // ps는 test 구조체를 가리키는 포인터
void(*f)(int); // f는 int를 매개변수로 갖고 반환 값을 갖지 않는 함수를 가리키는 포인터
pi = (int *)p; // p를 정수 포인터로 변경하여 pi에 대입
#include <stdio.h>
void swap(int *px, int *py){
int tmp;
tmp = *px;
*px = *py;
*py = tmp;
}
int main(){
int a = 1, b = 2;
printf("Before Swap a: %d, b: %d\n", a, b);
swap(&a, &b);
printf("After Swap a: %d, b: %d\n", a, b);
return 0;
}
// 1차 함수의 기울기와 y절편 값을 반환하는 함수
#include <stdio.h>
typedef struct{
int x;
int y;
} PointType;
// 기울기와 y절편 계산
int get_line_parameter(PointType p1, PointType p2, float *slope,float *yintercept){
if(p1.x == p2.x)
return -1;
else{
*slope = (float)(p2.y - p1.y) / (float)(p2.x - p1.x);
*yintercept = (float)p1.y - (*slope * (float)p1.x);
return 0;
}
}
int main(){
PointType p1 = { 1,2 }, p2 = { 9,8 };
float s, y;
if(get_line_parameter(p1,p2,&s,&y) == -1){
printf("error\n");
}else{
printf("slope: %f, yintercept: %f\n", s, y);
}
}
int main(){
struct{
int i;
float f;
}s, *ps;
ps = &s;
ps->i = 2;
ps->f = 3.14;
}
char a; // 문자 변수 선언
char *p; // 문자 포인터 선언
char **p; // 문자 포인터의 포인터 선언
a ='A';
p = &a; // 변수 a와 포인터 p를 연결
pp = &p; // 포인터 p와 포인터의 포인터 pp를 연결
#include <stdio.h>
void foo(int a){
printf("foo: %d\n",a);
}
int main(){
// f는 함수의 주소를 담는 포인터 타입
void (*f)(int);
f = foo;
f(10); // == foo(10)
(*f)(10); // == f(10)
}
int A[6], int *pi;
pi = &A[3];
p : 포인터
*P : 포인터가 가리키는 값
*p++ : 포인터가 가리키는 값을 가져온 다음, 포인터가 한 칸 증가함
*p-- : 포인터가 가리키는 값을 가져온 다음, 포인터가 한 칸 감소함
(*p)++ : 포인터가 가리키는 값을 증가시킴
int *pi = NULL;
main(){
char *pi; // 포인터 pi는 초기화가 안되어 있음
*pi = 'E'; // 위험한 코드
}
int *pi;
float *pf;
pf = (float *)pi; // 정수의 포인터를 실수의 포인터로 바꾸는 예제
메모리 할당 방법
정적 메모리 할당: 프로그램이 시작되기 전에 미리 정해진 크기의 메모리를 할당받는 것
-> 메모리의 크기는 프로그램이 시작하기 전에 결정되며 프로그램의 실행 도중에 그 크기가 변경될 수 없음.
int buffer[100]; // 배열 선언
char name[] = "data structure";
-> 프로그램이 처리해야 하는 입력의 크기를 미리 알 수 없는 경우에도 고정된 크리의 메모리를 할당하므로 비효율적일 수 있음.
-> 처음에 결정된 크기보다 더 큰 입력이 들어오다면 처리하지 못함. 더 작은 입력이 들어온다면 메모리 공간이 낭비됨
동적 메모리 할당(dynamic memory allocation): 프로그램이 실행 도중에 동적으로 메모리를 할당받는 것
-> 프로그램에서 필요한 만큼의 메모리를 시스템으로부터 동적으로 할당받아 사용하고, 사용이 끝나면 시스템에 메모리를 반납함.
main(){
int *pi;
pi = (int*)malloc(sizeof((int)); // 동적 메모리 할당
...
동적 기억 장소 사용
...
free(pi); // 동적 메모리 해제
}
-> malloc 함수가 반환했던 포인터 값을 잊어버리면 안됨.
(char *)malloc(100); // 100 바이트 할당
(int *)malloc(sizeof(int)); // 정수 타입 1개를 저장할 메모리 할당
(struct Book *)malloc(sizeof(struct Book)) // Book 구조체의 메모리 할당
// malloc을 이용하여 1000바이트 메모리를 할당하고,
// free를 이용하여 메모리를 반납
#include <stdio.h>
#include <malloc.h>
int main(){
char *string;
string (char *)malloc(1000);
if(string == NULL){
printf("Insufficient memory availalbe\n");
}else{
printf("Allocated 1000 bytes\n");
free(string);
printf("Memory free\n");
}
}
void *calloc(int num, int size)
배열 형식의 메모리를 할당
배열 요소의 크기는 size 바이트이고 개수는 num
요소들은 0으로 초기화된다는 점이 특이
반환 값은 malloc과 동일
sizeof 연산자
sizeof 키워드: 변수나 타입의 크기를 숫자로 반환(크기의 단위는 바이트)
sizeof 대상은 식별자이거나 데이터 타입일 수 있음
구조체의 경우 접근 속도를 빠르게 하기 위한 패딩 바이트에 의해서 그 크기가 예상과 다를 수 있음
size_t i = sizeof(int); // 32비트 CPU인 경우 4(int 타입의 크기는 4바이트)
struct AlignDepends{
char c;
int i;
};
size_t size = sizeof(struct AlignDepends); // 구조체 크기
// 우리의 예상은 char 1바이트 + int 4바이트 = 5바이트 이지만,
// 메모리 접근 속도를 빠르게 하기 위해서 char에 4바이트가 할당 될 수 있음. 따라서 구조체 전체 크기는 8이 될수도 있음
int array[] = {1,2,3,4,5}; // sizeof(array) is 20
// sizeof(array[0]) is 4
size_t sizearr = sizeof(array) / sizeof(array[0]); // 배열의 크기
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Example{
int number;
char name[10];
};
int main(){
struct Example* p;
p = (struct Example*)malloc(2 * sizeof(struct Example));
if(p==NULL){
fprintf(stderr, "can't allocate memory\n");
exit(1);
}
p->number = 1;
strcpy(p->name, "Park");
(p + 1)->number = 2; // (p+1)은 두 번쨰 구조체를 가리킴
strcpy((p + 1)->name, "Kim");
for (int i = 0; i < 2;i++){
printf("number: %d, name: %s\n", (p + i)->number, (p + i)->name);
}
free(p);
}
int main(){
double *pl;
pl = (int *)malloc(double);
pl = 23.92;
}
-> 동적 메모리 할당시 변환시킨 데이터 타입이 일치하지 않음
<참고자료>
천인국 · 공용해 · 하상호 지음,『C언어로 쉽게 풀어쓴 자료구조』, 생능출판(2016)