구조체 변수도 포인터로 선언할 수 있으며 문법은 기본 자료형의 경우와 동일하다.


Point *pa;


이렇게 선언하면 pa는 Point 구조체 포인터 변수이다. 포인터 변수의 경우 필드는 다음과 같이 ‘->’ 연산자를 사용하여 접근할 수 있다.


pa->x = 0;
pa->y = 1;


즉, 구조체 포인터의 접근자는 점(.)이 아니라 ‘->’ 이다. 혹은 미리 정의된 Point 변수를 대입시킬 수도 있다.


Point a = {0,1};
Point *pa;
*pa = a;


 함수로 구조체 변수를 넘길 때 복사본이 넘어가는 것은 기본 자료형과 같다. 따라서 어떤 함수에서 넘겨받은 구조체 변수의 필드를 변경하는 것은 원래 변수에 아무런 영향을 미치지 않는다.


ex08-10.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

void showInfo(Point a) {
printf("(%f, %f)", a.x, a.y);
}

void changeToOrigin(Point a) {
a.x = 0;
a.y = 0;
}

int main(int argc, char **argv) {
Point a = {11,12};
printf("a:(%.2f, %.2f)\n", a.x, a.y);
changeToOrigin(a);
printf("a:(%.2f, %.2f)\n", a.x, a.y);
}

실행 결과

a:(11.00, 12.00)
a:(11.00, 12.00)


위 예에서 changeToOrigin()함수를 호출할 때 Point 변수 a를 넘기고 이것이 함수 내부에서 원점으로 변경되었지만 원래 변수에는 전혀 영향을 미치지 않느다. 왜냐면 구조체 변수의 복사본이 함수로 넘어가기 때문이다.


 하지만 함수로 포인터를 넘기면 원래 구조체 필드를 직접 접근할 수 있다.


ex08-10b.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

void showInfo(Point a) {
printf("(%f, %f)", a.x, a.y);
}

void changeToOrigin(Point *a) {
a->x = 0;
a->y = 0;
}

int main(int argc, char **argv) {
Point a = {11,12};
printf("a:(%.2f, %.2f)\n", a.x, a.y);
changeToOrigin(&a); //<- 포인터를 넘긴다.
printf("a:(%.2f, %.2f)\n", a.x, a.y);
}

실행 결과

a:(11.00, 12.00)
a:(0.00, 0.00)


위에서는 changeToOrigin()함수가 Point형 포인터를 받아서 필드를 변경한다. 호출하는 쪽에서도 a변수의 포인터(&a)를 넘겨준다. 이러면 changeToOrigin()함수 내부에서 원본을 변경할 수 있다. 실행 결과에서 보면 변수 a의 필드들이 변경되었음을 알 수 있다.

Posted by 살레시오
,

 기본 자료형과 마찬가지로 구조체 변수도 배열을 형성할 수 있다. 예를 들어 Point 형 변수 세 개를 배열로 생성하고 싶다면 다음과 같이 하면 된다.


Point pa[3];


이렇게 하면 크기가 3인 Point형 배열 변수 pa가 생성된다. 초기화 방법은 배열의 초기화와 동일하다.


Point p0 = {0,0};
Point p1 = {1,1};
Point p2 = {2,2};
Point pa[3] = {p0, p1, p2};


또는 다음과 같이 바로 초기화를 할 수도 있다.


Point pa[3] = {{0,0}, {1,1}, {2,2}};


배열 각 요소를 통하여 구조체의 필드를 접근할 수 있다.


Point pa[3];
pa[0].x = 0;
pa[0].y = 0;
pa[1].x = 1;
pa[1].y = 1;
pa[2].x = 2;
pa[2].y = 2;


동일한 방법으로 초기화 이후에도 각 필드의 값을 자유롭게 접근하여 읽거나 변경할 수 있다.


 배열의 크기가 커지면 반복문을 이용하여 구조체 필드를 초기화하는 것이 일반적이다. 예를 들면 다음과 같다.


ex08-09.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

int main(int argc, char **argv) {
Point pta[100];
for (int k=0; k<100; k++) {
pta[k].x = 1;
pta[k].y = 1;
}
}


또는 미리 정의된 Point형 변수를 이용할 수도 있다.


ex08-09.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

int main(int argc, char **argv) {
Point pt = {1,1};
Point pta[100];
     // 100개의 요소를 pt로 초기화시킨다.
for (int k=0; k<100; k++) {
pta[k] = pt;
}
}


이와 같이 구조체도 배열을 생성하여 사용할 수 있다.

Posted by 살레시오
,

 어떤 구조체의 필드가 다른 구조체의 변수가 될 수도 있다. 직사각형의 정보를 갖는 Rect라는 구조체를 작성해 보자. 이 구조체는 마주보는 두 꼭지점의 의 좌표를 가지고 있어야 한다.


typedef struct {
double x;
double y;
} Point;

typedef struct {
Point leftTop;
Point rightBot;
} Rect;

여기에서 보면 Rect 구조체는 두 개의 Point 구조체 변수를 가지고 있다. 이런 식으로 구조체의 필드가 다른 구조체의 변수가 될 수 있다.


 구조체 Rect를 초기화 시키는 방법은 다음과 같이 몇 가지 방법이 있다.


// 초기화 방법 1 : 모든 구조체 변수를 생성과 동시에 초기화함
Rect ra = {{0, 1}, {11, 12}};
// 초기화 방법 2 : 이미 정의된 Point 변수 이용
Point p1 = {0, 1}, p2 = {11, 12};
Rect rb = {p1, p2};
// 초기화 방법 3: 선언 후 각각의 필드에 초기값 대입
Rect rc;
rc.leftTop.x = 0;
rc.leftTop.y = 1;
rc.rightBot.x = 11;
rc.rightBot.y = 12;


이제 사각형의 면적을 구하는 함수를 추가한 전체 소스 코드는 다음과 같다.


ex08-08.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

typedef struct {
Point leftTop;
Point rightBot;
} Rect;

double calcArea(Rect);

int main(int argc, char **argv) {
// 초기화 방법 1 : 모든 구조체 변수를 생성과 동시에 초기화함
Rect ra = {{0, 1}, {11, 12}};
// 초기화 방법 2 : 기정의 된 Point 변수 이용
Point p1 = {0,1}, p2 = {11, 12};
Rect rb = {p1, p2};
// 초기화 방법 3: 선언 후 각각의 필드에 초기값 대입
Rect rc;
rc.leftTop.x = 0;
rc.leftTop.y = 1;
rc.rightBot.x = 11;
rc.rightBot.y = 12;
printf("area of ra : %f\n", calcArea(ra));
printf("area of rb : %f\n", calcArea(rb));
printf("area of rc : %f\n", calcArea(rc));
}

double calcArea(Rect r) {
double dx = r.rightBot.x - r.leftTop.x;
double dy = r.rightBot.y - r.leftTop.y;
return dx*dy;
}

실행 결과

area of ra : 121.000000
area of rb : 121.000000
area of rc : 121.000000


이와 같이 구조체의 필드로 다른 구조체 변수도 얼마든지 사용할 수 있다. 다만 구조체를 정의하는 순서에 유의하면 된다. 당연한 얘기지만 Rect 구조체가 Point 구조체 뒤에 와야 한다.

Posted by 살레시오
,

 구조체는 정의되면 새로운 자료형처럼 사용할 수 있으며 기본 자료형을 함수에 넘기는 것과 동일한 방법으로 구조체 변수도 사용할 수 있다. 전 절에서 예로 든 Point 구조체를 예로 들어서 두 점간의 거리를 구하는 함수를 작성해 보자. 함수의 이름은 getDist()라고 하고 Point 구조체 변수 두 개를 받아서 double형 값(거리)를 반환해야 한다. 함수 본체는 다음과 같이 작성할 수 있다.


double getDist(Point a, Point b) {
double dx = b.x - a.x;
double dy = b.y - a.y;
double dist = sqrt(dx*dx + dy*dy);
return dist;
}


여기서 함수 헤더를 보면 기본 자료형의 경우와 동일한 방법으로 포인터 변수를 받음을 알 수 있다. 지역변수 a와 b는 Point형 변수이므로 Point구조체의 필드를 사용할 수 있다.


 두 점의 거리를 구하는 전체 프로그램은 다음과 같다.


ex08-06.c

#include <stdio.h>
#include <math.h>

typedef struct {
double x;
double y;
} Point;

double getDist(Point a, Point b);

int main(int argc, char **argv) {
Point a = {1.1, 2.2};
Point b = {3.3, 4.4};

double dist = getDist(a, b); //함수 호출
printf("a = {%f, %f}\n", a.x, a.y);
printf("b = {%f, %f}\n", b.x, b.y);
printf("dist = %f\n", dist);
}

double getDist(Point a, Point b) {
double dx = b.x - a.x;
double dy = b.y - a.y;
double dist = sqrt(dx*dx + dy*dy);
return dist;
}

실행 결과

a = {1.100000, 2.200000}
b = {3.300000, 4.400000}
dist = 3.111270


함수 getDist() 내에서 수학함수 sqrt()를 사용하기 위해서 math.h를 인클루드 시켰다.


 이와 같이 구조체를 정의하는 것은 새로운 자료형을 만드는 것과 같다. 구조체가 한 번 정의되면 기본 자료형과 동일한 방법으로 변수를 생성할 수 있고 함수의 인자로 넘길 수 있으며 함수의 반환값이 될 수도 있다. 예를 들어서 두 점의 좌표를 받아서 Point형 변수를 반환하는 간단한 함수를 작성해 보자.


ex08-07.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

Point getPoint(double, double); //← (1)

int main(int argc, char **argv) {
Point a = getPoint(0,0);
Point b = getPoint(1,1);
printf("a = {%f, %f}\n", a.x, a.y);
printf("b = {%f, %f}\n", b.x, b.y);
}

Point getPoint(double x, double y) {
Point a = {x, y};
return a;
}

실행 결과

a = {0.000000, 0.000000}
b = {1.000000, 1.000000}


이 예에서 getPoint()함수는 Point형 변수를 반환한다. 따라서 함수를 선언할 때 반환형을 (1)과 같이 명시해야 한다. 그리고 함수 내부에서는 Point형 변수를 return해야 한다.

Posted by 살레시오
,

 구조체를 초기화하는 방법은 선언과 동시에 초기화하는 방법과 이미 선언된 구조체를 초기화시키는 방법 두 가지가 있다. 선언과 동시에 초기화하는 예를 들면 다음과 같다.


ex08-01

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

int main(int argc, char **argv) {
Point a = {1.0, 1.0}; //<--
}


이렇게 Point타입으로 a란 변수를 만들면서 해당하는 값을 바로 넣어주는 방법을 사용할 수 있으며 이 방법을 사용할 때에는 해당 구조체의 멤버변수 순서와 같은 차례로 초기값을 넣어야 한다.

 구조체 변수를 먼저 선언하고 나중에 필드를 초기화시키는 방법도 있다. 같은 예를 들면 다음과 같다.


ex08-02

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

int main(int argc, char **argv) {
   Point a;
   a.x = 1.0; //<--
   a.y = 1.0; //<--
}


여기서 a.x는 a라는 구조체 변수의 필드 x를 나타낸다. a.y는 필드 y를 나타낸다. 이와 같이 구조체 변수의 필드는 점(.)으로 구분하여 접근한다.


 다른 예로 Person이라는 구조체를 작성해 보자. 필드로는 나이와 키 그리고 이름 정보를 가지고 있어야 한다..


ex08-03.c
#include <stdio.h>

typedef struct {
char strName[10];
int iAge;
float fHeight;
} Person;

int main(int argc, char **argv) {
Person park = {"salesio", 45, 171.5};
Person jang = {"sophia", 45, 165.0};
printf("name:%s, age:%d, height:%.1f\n",
             park.strName, park.iAge, park.fHeight);
printf("name:%s, age:%d, height:%.1f\n",
             jang.strName, jang.iAge, jang.fHeight);
}
실행 결과
name:salesio, age:45, height:171.5
name:sophia, age:45, height:165.0


이 예에서 Person구조체의 필드는 세 개로 strName, iAge, fHeight 이고 각각 문자열 변수, 정수형 변수, 실수형 변수이다. park과 jang이라는 Person 구조체 변수를 생성한 뒤 각각의 필드를 출력하는 예제이다.


   같은 구조체형이라면 대입연산자 =를 이용해서 모든 멤버변수의 값을 복사할 수 있다. 즉, 다른 구조체 변수의 필드값으로 새로운 구조체 변수의 필드를 초기화할 수 있다. 앞에서 예를 든 Point 구조체를 이용하여 예를 들어보자.


ex08-04.c

#include <stdio.h>

typedef struct {
double x;
double y;
} Point;

int main(int argc, char **argv) {
Point a = {1.1, 1.2};
Point b = a; // a를 이용하여 b를 초기화
Point c = b; // b를 이용하여 c를 초기화
c.y = 2.0;
printf("a = {%f, %f}\n", a.x, a.y);
printf("b = {%f, %f}\n", b.x, b.y);
printf("c = {%f, %f}\n", c.x, c.y);
}

실행 결과

a = {1.100000, 1.200000}
b = {1.100000, 1.200000}
c = {1.100000, 2.000000}


위에서 Point변수 c는 필드 y가 2.0으로 변경되었으며 출력 결과에 그것이 반영되어 있음을 알 수 있다.

Posted by 살레시오
,

 지금까지 C언어가 제공하는 여러 타입의 데이터를 써왔다. char, int, float 등의 기본 자료형  변수에는 하나의 데이터만 담을 수 있다. 어떤 대상이 여러 종류의 데이터를 가져야 하는 경우에는 구조체를 사용하는데 구조체는 단일 변수들을 묶어서 하나의 이름으로 관리할 수 있는 방법을 제공한다. 예를 들어 어떤 회사원의 정보를 저장하려면 이름도 필요하고 생년월일이나 주소, 사번 등등 많은 요소가 그 한사람에 관련된 정보가 될 것이다. 이들 요소를 모두 따로 만들어도 나타내는 일이 가능은 하지만 한 이름으로 모든 요소를 관리하면 효율적일 것이다. 즉, 구조체는 간단히 말해서 변수들의 모임이라고 할 수 있다. 변수들의 모임이라는 점에서 배열과 유사한 점이 있지만 배열은 같은 형의 데이터들의 모임이고 구조체는 서로 다른 형의 데이터들의 모임이라고 생각하면 된다.


 예를 들어 점의 좌표 정보를 갖고 있는 구조체를 작성해 보자. 좌표는 x값과 y값을 가진다. 구조체는 struct 이라는 키워드를 이용하여 선언한다.


struct {
   double x;
   double y;
};


이것은 double형 변수 x와 double형 변수 y를 갖는 구조체를 선언한 것이다. x와 y를 구조체의 필드(field)라고 한다.


 하지만 이것만으로는 이 구조체를 이용하여 데이터(변수)를 생성할 수 없다. 이 구조체를 이용하여 변수를 생성하려면 이 구조체에 이름을 지정해주어야 한다. 이를 위해서 보통  typedef 명령과 조합하여 구조체를 정의하는 방법이 많이 사용된다. typedef는 전에도 나왔지만 새로운 변수형을 선언하는 명령이다. 다음 예를 보자.


typedef struct {
   double x;
   double y;
} Point;


이와 같이 작성하면 struct {,,,} 구조체를 Point라는 이름으로 정의한 새로운 자료형이 생긴 것과 같이 쓸 수 있다. 그리고 이후로는 Point란 이름으로 변수를 선언할 수 있다.


Point a, b;


이렇게 하면 변수 a와 b는 구조체인 Point 변수이며 각각 별도의 필드 x와 y를 갖는다.

Posted by 살레시오
,

구조체는 개별적인 데이터를 하나로 묶는 자료형이며 Scilab에서도 이것을 지원한다. 예를 들어서 어떤 사람의 이름, 나이, 시력을 각각 name, age, sight 라는 이름으로 저장하고 싶다면 다음과 같이 struct()함수를 사용한다.


[그림 1] 구조체의 생성


구조체의 각각의 데이터를 필드(field)라고 칭한다. 이 예에서 person1 구조체의 필드는 name, age, sight 이다. 함수 struct() 은 반드시 짝수 개(0도 가능함)의 입력 인수를 가져야 하고 홀수 번째의 인수는 구조체의 필드 이름으로서 반드시 문자열이어야 한다. 짝수 번째 입력 인수는 진전에 입력된 필드명을 가지는 값으로서 Scilab 객체(행렬, 문자열, 등등)가 온다.


 위와 같은 구조체를 정의하는데 있어서 또 다른 방법은 개별적인 요소를 하나하나 직접 생성/입력하는 방법이 있다. 두 가지 문법이 있는데 다음과 같다.


structName(‘fieldName’)
structName.fieldName

    

두 가지 방법에는 큰 차이점이 있다. 전자의 경우는 필드명에 임의의 문자열을 사용할 수 있으나 후자의 경우는 일반적인 식별자 규칙을 따라야 한다는 것이다.


[그림 2] 구조체의 생성 (다른 방법)


구조체의 필드를 접근한다던가 아니면 추가하는데 있어서도 위에서 설명한 바와 같이 두 가지 방법이 있다. 예를 들어서 age필드를 접근하려면


a = person1.age // 읽기
person1.age = 43 // 변경
a = person1(‘age’) // 읽기
person1(‘age’) = 43 // 변경


새로운 필드도 자유롭게 추가할 수 있다.


person1.height = 170  // height라는 새로운 필드 추가
person1(‘height’)=170 //위와 동일함


 만약 두 개 이상의 구조체가 서로 같은 필드 이름들을 가지고 있다면 이것들을 하나의 행렬로 묶을 수 도 있다.


[그림 3] 구조체를 요소로 가지는 행렬


이 예제에서 구조체 sA와 sB는 똑같은 필드명을 가지고 있으므로 하나의 행렬로 묶을 수 있다. 필드의 값은 데이터형이 서로 달라도 상관없으며 중요한 것은 필드명이다. 필드명이 다른 두 구조체를 하나의 행렬로 묶으려고 한다면 에러를 발생한다.


 한 가지 알아야 할 것은 한 구조체에서 다른 구조체로 대입을 할 때 Scilab에서는 구조체의 내용물이 복사되는 것이지 참조가 되는 것은 아니라는 것이다. 위의 예에서


sC = sA


라고 하면 sC 변수에는 sA의 모든 구조체 필드들이 “복사된” 새로운 구조체가 생성되는 것이다. 따라서 이후에


sA.c = [1 0]


이라고 sA에 새로운 필드를 추가한다고 해서 sC에 영향을 미치지는 않는다. 이것은 구조체를 함수에 넘길 때도 마찬가지로 적용되는데 함수 내부에서 넘겨받은 구조체를 변형시킨다고 해도 원래의 구조체와는 아무런 상관이 없다는 것을 알아두어야 한다.


 구조체와 관련된 함수들은 다음과 같다.


[표 1] 구조체와 관련된 함수들

함수명

기능

struct(...)

구조체 생성

isstruct(s)

s가 구조체인지 테스트

isfield(s,f)

f가 s의 필드인지 테스트

length(s)

사용자 필드 수 + 2

fieldnames(s)

getfield(n, s)

구조체 s의 필드명을 문자열 행렬로 반환

구조체 s의 n번째 필드값을 반환

setfield

(사용 빈도가 매우 낮을 것 같음)

s = null()

구조체 s 삭제


여기서 getfiled() 함수는 구조체의 필드명을 문자열벡터로 반환하는데 3번째 요소부터 사용자 필드라는 것에 유의해야 한다. 아래 예를 보자.


[그림 4] getfiled()함수의 반환 객체


이 예에서 getfield()함수의 첫 번째 인자는 s1의 자료형이 구조체임을  나타내는 문자열이고 (내부적으로 구조체는 mlist 임.) 두 번째 인자인 dim은 Scilab에서 자동으로 생성하는 필드이다. 따라서 사용자 필드는 세 번째부터 마지막까지이다. 사용자 필드명만 뽑아내려면 다음과 같이 하면 될 것이다.


>> fields = getfield(1, s1)
>> fields = fields(3:$)


또는 간단하게 fieldnames()함수를 사용하면 된다.


>> fields = fieldnames(s1)


만약 사용자의 첫 번째 필드인 s1.A 의 값을 읽어내려면 다음과 같이 하면된다.

    

[그림 5] getfield()함수


이 내용은 혼동하기 쉬운데 이렇게 이해하면 될 것 같다. getfield(n, s) 함수는 구조체 s의 내부적인 필드”값”를 반환하는데 내부적으로 s구조체의 첫 번째 필드는 모든 필드명의 문자열벡터, 두 번째 필드는 dims, 세 번째 필드부터가 사용자 필드이다. 따라서 해당되는 것을 반환하는 것이다. 만약 위에서 예를 든 s1의 경우에는 다음 표와 같이 내부적인 데이터를 가진다.


[표 2] getfiled() 함수 반환 객체의 내부 인덱스

내부 인덱스

필드명

필드값

(getfield 함수의 반환값)

1


[ ‘st’ , ‘dims’, ‘A’, ‘b’ ]

2

‘dims’

[1 1]

3

‘A’

[1 2; 3 4]

4

‘b’

[0 1]


따라서 getfield(3, s1) 이라고 하면 s1구조체의 3번째 필드의 필드값이 반환된다고 이해하면 된다.


 하지만 구조체의 필드를 접근하는 데에는 getfield()함수나 setfield()함수를 이용하는 것 보다는 dot(.) 연산자나 문자열키로 읽거나 쓰는 방식이 더 일반적이다.


Posted by 살레시오
,

 C++은 기본적으로 C와 호환성을 갖게끔 설계되었으며 이것은 C로 작성된 프로그램은 큰 수정 없이 C++컴파일러가 컴파일을 할 수 있다는 의미이다. C에서 struct(구조체)가 있으므로 C++도 이것이 있는데 표준 C구조체의 기능을 확장하여 C++의 클래스와 동일한 기능을 갖는다는 점에 유의해야 한다. 따라서 구조체도 멤버 변수뿐만 아니라 멤버 함수도 가질 수 있으며 접근 지정자도 사용 가능하다. 예를 들어서

struct Name {
   private:
       int iA;
       void func1();
   public:
       double dA;
       void func2();
   protected:
       short sA;
};

C++의 구조체의 인스턴스 생성은 클래스와 동일한데 C언어의 경우에는 struct 키워드를 붙여야 하지만 C++에서는 그렇지 않다.


Name ins1;

 

구조체와 클래스의 유일한 차이점은 구조체의 기본 지정자가 public 이라는 점이다. 즉, 접근 지정자가 명시적으로 지정되지 않으면 구조체는 public으로 지정된다.

struct Name {
   int iA;
   double dA;
};

이 예에서 멤버 변수 iA와 dA는 모두 public이므로 외부에서 자유롭게 접근 가능하다. 이렇게 정해진 이유는 역시 C언어와의 호환성 때문인데 C언어의 구조체 멤버는 기본적으로 외부에서 모두 접근이 가능하기 때문이다. 따라서 위의 구조체는 다음의 클래스와 기능적으로 동일하다.


class Name {
   public:
       int iA;
       double dA;
};

 구조체를 사용하느냐 클래스를 사용하느냐는 전적으로 사용자의 몫이겠지만 C++ 프로그램을 작성한다면 궂이 C언와의 호환성 때문에 남겨 놓은 구조체를 이용하는 것 보다는 클래스를 사용하는 것이 더 바람직하다.



Posted by 살레시오
,