공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 

 구조체 

  1. 구조체
    • 구조체 정의
      * 구조체 정의와 동시에 변수 선언
    • 구조체 변수의 선언
      * 선언과 동시에 초기화
      * 구조체 정의와 typedef 선언
      * 익명 구조체
    • 구조체 변수의 멤버에 접근
    • 구조체 변수의 주소 값
    • 구조체 크기
      * 구조체 멤버 정렬
    • 구조체 변수를 대상으로 하는 연산
    • 중첩된 구조체의 정의와 변수의 선언
  2. 구조체 배열
    • 구조체 배열 선언
      * 선언과 동시에 초기화
    • 구조체 배열 접근
  3. 구조체 변수와 포인터
    • 구조체 포인터 변수 선언, 초기화
    • 포인터 변수를 구조체의 멤버로 선언
  4. 구조체 포인터와 동적 할당
    • 구조체 비교
    • 구조체와 메모리 활용
  5. 매개 변수나 반환 값으로 사용되는 구조체 변수
    • 함수로의 구조체 변수 전달과 반환
    • 구조체 변수를 대상으로 하는 Call-by-reference

 


 

1. 구조체(structure)

  • 구조체는 관련 정보를 하나의 의미로 묶을 때 사용한다.
    기본 자료형을 조합하여 만든 자료형을 파생형(derived type)이라 한다.
  • 구조체는 보통 main함수 바깥에 정의한다. 만약 함수 안에 구조체를 정의하면 해당 함수 안에서만 구조체를 사용할 수 있다.

  하나 이상의 변수(포인터 변수와 배열 포함)를 묶어 새로운 자료형을 정의하는 것을 의미한다. 기본 자료형 변수를 묶어 새로운 자료형을 만드는 것이기 때문에 사용자 정의 자료형(user defined data type)이라고도 한다. 구조체를 통해 연관 있는 데이터를 하나로 묶을 수 있는 자료형을 정의하면 데이터의 표현 및 관리가 용이해진다.

 

 

구조체 정의

struct person	// 이름이 person인 구조체 정의
{
    char name[20];	// 이름 저장을 위한 멤버
    char phoneNum[20];	// 전화번호 저장을 위한 멤버
    int age;		// 나이 저장을 위한 멤버
};

배열도 (값의 저장이 가능한) 변수의 성격을 띠기 때문에 구조체의 멤버가 될 수 있다.

 

 

   구조체(공용체)의 최대 멤버 개수 

  C언어 표준에는 1023개로 정의되어 있으며 컴파일러마다 최대 개수는 달라질 수 있다.

 

 

  ■ 구조체 정의와 동시에 변수 선언

struct point{
    int xpos;
    int ypos;
} pos1, pos2, po3;	// 구조체 정의 뒤에 구조체 변수를 바로 선언할 수 있다.

구조체를 정의함과 동시에 변수를 선언하는 것도 가능하다. 구조체 정의 뒤에 바로 구조체 변수명을 써주면 된다.

 

 

 

 

구조체 변수의 선언

struct [type_name] [val_name];

// 구조체 변수 선언
struct person man;	// person 구조체의 변수 man 선언
struct point pos;	// point 구조체의 변수 pos 선언
  • ``struct`` : 구조체 변수를 선언할 때 맨 앞에 사용
  • ``type_name`` : 정의한 구조체의 이름
  • ``val_name`` : 구조체 변수 이름

구조체 변수의 구성

앞서 정의한 ``person``과 ``point``라는 구조체를 정의하고 구조체 변수 ``man``과 ``pos``를 선언하면 위와 같은 형태로 존재하게 된다.

 

 

 

  ■ 선언과 동시에 초기화

// 구조체 point, person 선언
struct point{
    int xpos;
    int ypos;
};

struct person{
    char name[20];
    char phoneNum[20];
    int age;
};

// 구조체 변수 선언과 동시에 초기화
struct point pos = {10, 20};	// 구조체 정의에 선언된 멤버 순서대로 초기화 값으로 들어감
struct person man = {"홍길동", "010-1234-5678", 20};

기본형 변수들처럼 구조체 변수도 선언과 동시에 초기화할 수 있다. 초기화 방법은 배열의 초기화 방법과 동일하다.

값을 중괄호``{ }``에 나열하면 구조체 정의에 선언된 멤버의 순서대로 초기화 값이 들어간다.

초기화 과정에서는 문자열 저장을 위해 @@strcpy@@함수를 호출하지 않아도 되며, 바로 값을 넣어주면 된다.

 

 

 

  ■ 구조체 정의와 typedef 선언

typedef [자료형][별칭]
typedef [자료형*] [별칭]

typedef int INT;		// int에 또다른 이름 INT 부여
typedef name1, name2, name3;	// 가장 마지막에 등장하는 단어를 중심으로 이뤄지므로 name1, name2의 또다른 이름을 name3으로 지정하는 것

// 선언 이후에는 아래와 같이 서로 대체가 가능하다.
INT num;
int num;	// typedef 선언 이후에도 원래 이름 사용 가능
INT * ptr;

``typedef``는자료형의 별칭을 만들 때 사용한다. 구조체뿐만 아니라 모든 자료형의 별칭을 만들 수 있다. (포인터 별칭은 포인터라는 의미로 앞에 P를 붙이기도 한다.) typedef로 정의한 별칭을 사용자 정의 자료형, 사용자 정의 타입이라고 부른다.

 

  • 선언 이후에는 새로 부여한 이름을 사용하는 것이나 원래의 이름을 사용하는 것이나 차이가 없다.
  • 새 이름을 부여한 후에도 원래 이름을 사용할 수 있다.
  • 이름이 여러 개 일 경우 가장 마지막에 등장하는 단어를 중심으로 이뤄진다. (2행)
  • ##typedef##로 정의되는 자료형의 이름은 대문자로 시작하는 것이 관례이다.

 

// 1) 구조체를 정의한 이후 typedef를 사용하는 경우
// 구조체 point 정의
struct point{
    int xpos;
    int ypos;
};

typedef struct point Point;	// typedef로 struct point에 새로운 이름인 Point를 부여
Point pos;			// typedef 이후에는 이렇게 선언할 수 있다.

struct point pos;		// typedef를 사용하지 않으면 원래 이렇게 선언해야함
// 2) 구조체 정의와 동시에 typedef를 사용하는 경우
typedef struct point{
    int xpos;
    int ypos;
} Point;

// 이후에는 아래와 같이 구조체 변수 선언 가능
Point pos;


// typedef로 새로 부여한 이름이 일치하지 않는 경우 
// : typedef로 정의된 이름으로 해당 구조체를 부르는 것이 관례(아래의 예시는 simple도 되고 SoSimple도 됨)
typedef struct simple{
	...   
} SoSimple;

  이를 이용해서 구조체 변수를 선언할 때 ``struct``를 생략할 수 있도록 할 수 있다. 구조체를 정의한 이후에 ``typedef``를 사용할 수도 있지만, 구조체를 정의하면서 ``typedef``를 동시에 선언하는 2번 방법이 가장 많이 사용된다.

 

 

  익명 구조체(anonymous structure)

typedef struct{
	[자료형] [멤버이름];
} [구조체 별칭];

``typedef``를 사용하면 원래의 이름은 잘 사용하지 않기 때문에 아예 구조체를 선언할 때 이름을 생략하는 것도 가능하다.
이런 구조체를 익명 구조체라고 한다.

 

 

 

 

구조체 변수의 멤버에 접근

[구조체 변수 이름].[구조체 멤버 이름];

// 사용예시
pos.xpos = 20;				// 구조체 변수 pos의 멤버 xpos의 값을 20으로 저장
scanf("%d %d", &pos1.xpos, &pos1.ypos);	// 두 개의 정수를 입력받아 구조체 변수 pos1의 멤버 xpos, ypos에 저장
strcpy(man.name, "홍길동");		// 구조체 변수 선언 이후에 구조체 멤버에 문자열을 저장하기 위해서는 strcpy함수를 사용해야한다.

구조체 변수의 멤버에 접근할 때는 ``.``연산자를 사용한다.

구조체 멤버로 배열이 선언된 경우 배열의 접근방식을, 포인터 변수인 경우에는 포인터 변수의 접근방식으로 접근하면 된다.

 

#include <stdio.h>
#include <string.h> 

// 구조체 person 선언 
struct person{
	char name[20];
	char phoneNum[20];
	int age;
};

int main(void)
{	
	struct person man1, man2;	// 구조체 변수 man1, man2 선언
	
	// 구조체 변수 선언 이후에 구조체의 멤버에 문자열을 저장하려면 strcpy함수를 호출해야 한다.
	strcpy(man1.name, "안성준");	 
	strcpy(man1.phoneNum, "010-1122-3344");
	man1.age = 23;
	
	printf("이름 입력 : "); scanf("%s", man2.name);
	printf("번호 입력 : "); scanf("%s", man2.phoneNum);
	printf("나이 입력 : "); scanf("%d", &(man2.age));	// 문자열이 아니기 때문에 & 연산자를 사용해야함	
	
	printf("이름 : %s \n", man1.name); 
	printf("번호 : %s \n", man1.phoneNum); 
	printf("나이 : %d \n", man1.age);

	printf("이름 : %s \n", man2.name); 
	printf("번호 : %s \n", man2.phoneNum); 
	printf("나이 : %d \n", man2.age);
	return 0;
}

구조체를 선언하고 접근하는 예시이다.

구조체의 멤버가 배열인 경우 문자열 저장을 위해 @@strcpy@@함수를 이용해야 한다.

 

실행 결과

 

 

 

 

구조체 변수의 주소 값

  구조체 변수의 주소 값은 구조체 변수의 첫 번째 멤버의 주소 값과 동일하다.

 

#include <stdio.h>

// 구조체 point, person정의 
struct point{
	int xpos;
	int ypos;
};

struct person{
	char name[20];
	char phoneNum[20];
	int age;
};

int main(void)
{
	struct point pos = {10, 20};	// point형 구조체 변수 pos 선언 및 초기화 
	struct person man = {"박만근", "010-1234-5678", 40};	// person형 구조체 변수 man 선언 및 초기화 
	 
	printf("%p %p \n", &pos, &pos.xpos);
	printf("%p %p \n", &man, man.name); 
	
	return 0;
}

구조체 변수의 주소 값과 첫 번째 멤버의 주소 값을 비교하는 예시이다.

 

실행 결과

두 주소 값이 같은 것을 확인할 수 있다.

 

 

 

 

구조체 크기

  sizeof 연산자를 사용하면 된다.

  • sizeof(struct 구조체)
  • sizeof(구조체 별칭)
  • sizeof(구조체 변수)
  • sizeof 구조체 변수

 

 

  ■ 구조체 멤버 정렬

  구조체가 메모리에 올라갔을 때 멤버를 정렬(alignment)하는 기능이 있다. CPU가 메모리에 접근할 때 32 bit는 4 bytes 단위, 64 bit는 8 byte 단위로 접근한다. 만약 32 bit CPU에서 4 byte보다 작은 데이터에 접근할 경우 내부적으로 시프트 연산이 발생해서 효율이 떨어진다. 그래서 C언어 컴파일러는 CPU가 메모리의 데이터에 효율적으로 접근할 수 있도록 구조체를 일정한 크기로 정렬하게 된다. (정렬하면 안 되는 경우도 있다. ex) 사진 파일, 네트워크로 데이터를 전송할 때..)

 

#include <stdio.h>

struct PacketHeader {
    char flags;    // 1바이트
    int seq;       // 4바이트
};

int main()
{
    struct PacketHeader header;

    printf("flags size: %d\n", sizeof(header.flags));           // 1: char는 1바이트
    printf("seq size : %d\n", sizeof(header.seq));             // 4: int는 4바이트
    printf("header size : %d\n", sizeof(header));                 // 8: 구조체 전체 크기는 8바이트
    printf("struct size : %d\n", sizeof(struct PacketHeader));    // 8: 구조체 이름으로 크기 구하기

    return 0;
}

실행결과

구조체의 크기를 확인하는 예시이다.

전체 크기가 5 byte(char 1 byte + int 4 byte)가 나와야 하는데 실제로는 8 byte라고 나온다. C언어에서 구조체를 정렬할 때 멤버 중에서 가장 큰 자료형 크기의 배수로 정렬하기 때문이다. 위의 예시에서는 char flags뒤에 4 byte를 맞추기 위해 남는 공간에 3 byte가 더 들어가는데 이때 남는 공간을 채우는 것을 패딩(padding)이라고 부른다.

 

#include <stdio.h>
#include <stddef.h>   // offsetof 매크로가 정의된 헤더 파일

struct PacketHeader {
    char flags;
    int seq;
};

int main()
{
    printf("flags offset : %d\n", offsetof(struct PacketHeader, flags));    // 0
    printf("seq offset : %d\n", offsetof(struct PacketHeader, seq));      // 4

    return 0;
}

구조체에서 멤버의 위치(offset)를 구할 때는 offsetof 매크로를 사용한다. ``stddef.h`` 헤더에 정의되어있다.

offsetof 매크로에 구조체와 멤버를 지정하면 구조체에서 해당 멤버의 상대 위치가 반환된다. (첫 멤버의 상대 위치는 0) 위의 예시에서는 구조체가 4 byte 단위로 정렬하므로 seq의 위치는 1이 아닌 4가 나온다.

 

+ 구조체의 정렬 크기를 지정하는 것도 가능하다.

   참고 : dojang.io/mod/page/view.php?id=432

 

 

 

 

구조체 변수를 대상으로 하는 연산

  구조체 변수를 대상으로는 매우 제한된 형태의 연산만 허용된다. 덧셈이나 뺄셈 등 기타 연산을 하려면 프로그래머가 직접 함수를 정의해야 한다.

 

   구조체 변수로 가능한 연산 

  1. 대입 연산 (구조체 변수간 대입 연산의 결과는 멤버 대 멤버의 복사가 된다.)
  2. 주소 값 반환을 목적으로 하는 ``&``연산
  3. ``sizeof`` 연산 (구조체 변수의 크기 반환)

 

#include <stdio.h>

// 구조체 point(Point) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

int main(void)
{
	Point pos1 = {1, 2};
	Point pos2;
	pos2 = pos1;	// 구조체 변수로 대입연산을 하면 구조체 변수의 멤버간 복사가 이뤄진다. 
	
	printf("크기 : %d \n", sizeof(pos1));	// pos1의 전체 크기 반환 
	printf("[%d, %d] \n", pos1.xpos, pos1.ypos);
	printf("크기 : %d \n", sizeof(pos2));	// pos2의 전체 크기 반환 
	printf("[%d, %d] \n", pos2.xpos, pos2.ypos);

	return 0;
}

앞서 말했던 구조체 변수간 연산의 결과를 확인하는 예제이다.

 

실행 결과

 

 

#include <stdio.h>

// 구조체 point(Point) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

// 구조체 변수간 덧셈을 진행하는 함수 
Point AddPoint(Point pos1, Point pos2){
	Point pos = {pos1.xpos + pos2.xpos, pos1.ypos + pos2.ypos};
	return pos;
}

// 구조체 변수간 뺄셈을 진행하는 함수 
Point MinPoint(Point pos1, Point pos2){
	Point pos = {pos1.xpos - pos2.xpos, pos1.ypos - pos2.ypos};
	return pos;
 } 

int main(void)
{
	Point pos1 = {5, 6};
	Point pos2 = {2, 9};
	Point result;

	result = AddPoint(pos1, pos2);
	printf("[%d, %d] \n", result.xpos, result.ypos);
	
	result = MinPoint(pos1, pos2);
	printf("[%d, %d] \n", result.xpos, result.ypos);

	return 0;
}

함수를 정의해 구조체 변수간 덧셈, 뺄셈을 진행하는 예시이다.

 

실행 결과

 

 

 

 

중첩된 구조체의 정의와 변수의 선언

  구조체 변수도 구조체의 멤버로 선언될 수 있다. 구조체 안에 구조체 변수가 멤버로 존재하는 경우를 가리켜 구조체의 중첩이라고 한다.

 

#include <stdio.h>

// 구조체 point(Point), circle(Circle) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

typedef struct circle{
	Point cen;	// 구조체 변수의 멤버로 구조체 변수 선언 
	double rad;
} Circle;	// typedef로 struct Circle의 새로운이름 Circle 부여 

void ShowCircleInfo(Circle * cptr)
{
	printf("[%d, %d] \n", (cptr -> cen).xpos, (cptr -> cen).ypos);
	printf("radius : %g \n\n", cptr -> rad);
}

int main(void)
{
	Circle c1 = {{1, 2}, 3.5};	// 구조체 변수가 멤버로 올 경우 이중으로 중괄호{}를 사용해서 구분한다. 
	Circle c2 = {2, 4, 3.9};	// 중괄호를 사용하지 않으면 순서대로 초기화된다. 
	ShowCircleInfo(&c1);
	ShowCircleInfo(&c2);
	
	return 0;
}

  초기화 시 구조체 변수인 멤버는 중괄호``{ }``를 사용해서 구분한다. 중괄호를 사용하지 않으면 구조체에 선언된 멤버의 순서대로 초기화된다. 배열의 초기화와 마찬가지로 초기화하지 않은 멤버는 @@0@@으로 초기화된다.

 

실행 결과

 

 

 

 

 


2. 구조체 배열

  다수의 변수를 묶어 관리하기 위한 배열이 있는 것처럼 다수의 구조체 변수를 위한 구조체 배열이 있다.

 

   구조체 배열의 요소 개수 

  ``sizeof(구조체배열이름) / sizeof(struct 구조체이름)``

 

 

구조체 배열 선언

struct 구조체이름 변수이름[크기];

// 선언과 동시에 초기화 하려면 중괄호{}안에 중괄호를 사용한다. 각 요소는 콤마(,)로 구분한다.
struct 구조체이름 변수이름[크기] = {{.멤버이름1 = 값1, .멤버이름2 = 값2},
				  {.멤버이름1 = 값3, .멤버이름2 = 값4}};
struct 구조체이름 변수이름[크기] = {{값1, 값2}, {값3, 값4}};

// 모든 요소의 멤버를 초기화 하려면 구조체 배열을 선언하면서 {0, }을 할당하면 된다.
struct Point2D p[3] = {0, };

// 요소에 접근할 때는 인덱스, 멤버에 접근할 때는 점(.)을 이용한다.
p[0].x;

// 사용예시
struct point arr[4];

구조체 배열은 변수 이름 뒤에 대괄호[ ]를 붙인 뒤 크기를 설정한다.

각 요소에 접근할 때는 대괄호 안에 인덱스를 지정해 주면 되고, 각 요소의 멤버에 접근할 때는 점(.)을 사용한다.

 

구조체 배열의 구조와 접근

마지막 라인과 같이 배열 선언을 하면 그림과 같은 구조로 배열이 할당된다.

 

 

  ■ 선언과 동시에 초기화

// 구조체 person 정의
struct person{
    char name[20];
    char phoneNum[20];
    int age;
};

// 초기화 예시
struct person arr[3] = {	// 크기가 3인 구조체 배열 arr 선언
    {"박만근", "010-1111-1111", 20},	// 첫 번째 요소 초기화
    {"최창식", "010-2222-2222", 21},	// 두 번째 요소 초기화
    {"김말년", "010-3333-3333", 22}	// 세 번째 요소 초기화
}

구조체 배열을 선언과 동시에 초기화할 때는 배열의 길이만큼 중괄호``{ }``를 이용해서 초기화를 진행하면 된다.

 

 

 

 

구조체 배열 접근

#include <stdio.h>

// 구조체 point 정의 
struct point{
	int xpos;
	int ypos;
};

int main(void)
{
	struct point arr[3];	// 크기가 3인 구조체 배열 arr 선언
	
	for(int i = 0 ; i < 3 ; i++)
	{
		printf("점의 좌표 입력 : ");
		scanf("%d %d", &arr[i].xpos, &arr[i].ypos);
	}
	
	for(int i = 0 ; i < 3 ; i++)
		printf("[%d, %d] ", arr[i].xpos, arr[i].ypos);
	
	return 0;
}

구조체 배열도 배열과 마찬가지로 ``for``문을 통해 순차적으로 접근할 수 있다.

 

실행 결과

 

 

 

 

 


3. 구조체 변수와 포인터

  구조체 포인터 변수도 일반적인 포인터 변수의 선언 및 연산과 같다.

 

 

구조체 포인터 변수 선언, 초기화

struct point pos = {11, 22};	// point형 구조체 변수 pos의 멤버를 각각 11, 22로 초기화
struct point * pptr = &pos;	// point *형 구조체 포인터 변수 pptr에 구조체변수 pos의 주소값이 저장됨


// 구조체 포인터 변수를 이용해서 구조체 변수에 접근하는 방법은 2가지가 있다. (보통 2번 방법을 많이 사용)
// 1) *과 .연산자 사용
(*pptr).xpos = 10;	// pptr이 가리키는 구조체변수(pos)의 멤버 xpos에 10을 대입
(*pptr).ypos = 20;

// 2) 애로우 연산자(->) 사용
pptr->xpos = 10;	// pptr이 가리키는 구조체변수(pos)의 멤버 xpos에 10을 대입
pptr->ypos = 20;

구조체 포인터 변수를 이용해서 구조체 변수에 접근할 수 있다.

접근할 때 애로우 연산자 ##->##를 더 많이 사용한다.

 

#include <stdio.h>

// 구조체 point 정의
struct point{
	int xpos;
	int ypos;
};

int main(void)
{
	// 구조체 변수 pos1, pos2 선언
	struct point pos1 = {1, 2};
	struct point pos2 = {100, 200};
	struct point * pptr = &pos1;	// 구조체 포인터 변수 pptr 선언
	
    	// 구조체 포인터 변수 pptr을 이용해서 구조체 변수의 멤버에 접근
	(*pptr).xpos += 4;
	(*pptr).ypos += 5;
	printf("[%d, %d] \n", pptr->xpos, pptr->ypos);
	
	pptr = &pos2;
	pptr -> xpos += 1;
	pptr -> ypos += 2;
	printf("[%d, %d] \n", (*pptr).xpos, (*pptr).ypos);
	
	return 0;
}

구조체 포인터 변수를 정의하고, 두 가지 방법으로 구조체 변수에 접근하는 예시이다.

 

실행 결과

struct Data d[3] = { { 10, 20 }, { 30, 40 }, { 50, 60 } };
struct Data *ptr;
ptr = d;

printf("%d %d\n", (ptr + 1)->num1, (ptr + 1)->num2);	// d[1].num1, d[1].num2과 같음
printf("%d %d\n", (ptr + 2)->num1, (ptr + 2)->num2);	// d[2].num1, d[2].num2과 같음

구조체 포인터도 포인터 연산을 할 수 있다. void 포인터의 경우에는 struct [구조체이름] *로 형변환을 해야한다.

 

 

포인터 변수를 구조체의 멤버로 선언

// 구조체 point, circle 정의
struct point{
    int xpos;
    int ypos;
};

struct circle{
    double radius;
    struct point * center;	// 구조체 변수 circle의 멤버를 point *형 구조체 포인터 변수로 선언;
};
// TYPE형 구조체 변수의 멤버로 TYPE형 포인터 변수를 선언하는 것도 가능하다.
struct point{
    int xpos;
    int ypos;
    struct point * ptr;	// 구조체의 멤버로 선언한 구조체와 같은 형인 포인터 변수 선언
};

위와 같이 구조체의 멤버로 포인터 변수가 오는 것도 가능하다.

또한 2번째 코드처럼 선언한 구조체와 같은 형의 포인터 변수를 선언하는 것도 가능하다.

 

#include <stdio.h>

// 구조체 point, circle 정의 
struct point{
	int xpos;
	int ypos;
};

struct circle{
	double radius;
	struct point * center;    // 구조체 변수 circle의 멤버를 point *형 구조체 포인터 변수로 선언
};

int main(void)
{
	struct point cen = {2, 7};	// point형 구조체 변수 cen 선언 및 초기화
	double rad = 5.5;
	
	struct circle ring = {rad, &cen};	// circle형 구조체 변수 ring의 멤버인 center가 cen을 가리킴 
	printf("원의 반지름 : %g \n", ring.radius);
	printf("원의 중심 [%d, %d] \n", (ring.center) -> xpos, (ring.center) -> ypos);
	
	return 0;
}

멤버에 포인터 변수가 있는 구조체를 사용하는 예시이다.

구조체 변수 ``ring``의 멤버 ``center``가 다시 구조체 변수인 ``cen``을 가리키는 구조이다.

 

실행 결과

 

 

#include <stdio.h>

// 구조체 point 정의 
struct point{
	int xpos;
	int ypos;
	struct point * ptr;	// 구조체의 멤버로 선언한 구조체와 같은 형인 포인터 변수 선언
};

int main(void)
{
	struct point pos1 = {1, 1};
	struct point pos2 = {2, 2};
	struct point pos3 = {3, 3};
	
	pos1.ptr = &pos2;
	pos2.ptr = &pos3;
	pos3.ptr = &pos1;
	
	printf("점의 연결관계 ... \n");
	printf("[%d, %d]와(과) [%d, %d] 연결 \n", pos1.xpos, pos1.ypos, pos1.ptr -> xpos, pos1.ptr -> ypos);
	printf("[%d, %d]와(과) [%d, %d] 연결 \n", pos2.xpos, pos2.ypos, pos2.ptr -> xpos, pos2.ptr -> ypos);
	printf("[%d, %d]와(과) [%d, %d] 연결 \n", pos3.xpos, pos3.ypos, pos3.ptr -> xpos, pos3.ptr -> ypos);
	
	return 0;
}

두 번째 유형으로 설명한 선언한 구조체와 같은 TYPE의 포인터 변수를 멤버로 사용하는 구조체의 예시이다.

포인터 변수를 이용해서 삼각형을 이루는 세 점의 연결관계를 표현한다.

 

실행 결과

 

 

 

 

 


4. 구조체 포인터와 동적 할당

struct [구조체이름] *[포인터이름] = malloc(sizeof(struct [구조체이름]));	// sizeof안에 struct를 붙여줘야함 !
[구조체별칭] *[포인터이름] = malloc(sizeof(구조체별칭));		// typedef를 사용한 경우 struct 생략가능

// 접근할 때는 ->연산자를 사용한다.
strcpy(p1->name, "홍길동");
p1->age = 30;

// .으로 멤버에 접근하려면 괄호()와 역참조를 사용해야한다.
(*p1).age;

// 사용한뒤에는 항상 메모리를 해제해주어야 한다.
free([구조체 이름]);
  • 메모리의 사이즈를 할당할 때는 sizeof안에 struct를 넣어야 한다. (typedef로 지정한 경우는 생략 가능)
  • 구조체 포인터의 멤버에 접근할 때는 ``.``이 아닌 ``->`` 연산자(화살표 연산자, arrow operator)를 사용한다는 것에 주의한다. (.을 사용하려면 역참조를 이용해야 한다.)
  • 구조체 멤버가 포인터일 때 역참조를 하려면 맨 앞에 *를 붙이면 된다.
    ``*구조체변수.멤버``
    ``*구조체포인터->멤버``
  • 역참조한 것을 괄호( )로 묶으면 구조체 변수를 역참조한 뒤 멤버에 접근한다는 의미이다.
    ``(*구조체포인터).멤버``
    ``*(*구조체포인터).멤버``
  • ``구조체포인터 = &구조체변수``하면 구조체 변수의 메모리 주소를 구조체 포인터에 할당할 수 있다. 접근할 때는 마찬가지로 ->를 사용하면 된다.

 

#include <stdio.h>
#include <stdlib.h>

struct Data {
    char c1;
    int *numPtr;    // 포인터
};

int main()
{
    int num1 = 10;
    struct Data d1;
    struct Data *d2 = malloc(sizeof(struct Data));    // 구조체 포인터에 메모리 할당

    d1.numPtr = &num1;
    d2->numPtr = &num1;

    printf("%d\n", *d1.numPtr);     // 10: 구조체의 멤버를 역참조
    printf("%d\n", *d2->numPtr);    // 10: 구조체 포인터의 멤버를 역참조

    d2->c1 = 'a';
    printf("%c\n", (*d2).c1);      //  a: 구조체 포인터를 역참조하여 c1에 접근
                                   // d2->c1과 같음
    printf("%d\n", *(*d2).numPtr); // 10: 구조체 포인터를 역참조하여 numPtr에 접근한 뒤 다시 역참조
                                   // *d2->numPtr과 같음

    free(d2);

    return 0;
}

구조체와 구조체 포인터를 역참조하는 예시이다.

 

 

구조체 비교

  구조체 변수를 선언한 경우 비교 연산자로는 두 구조체의 내용이 같은지 알 수 없다. (컴파일 에러) 두 구조체의 멤버에 저장된 값이 모두 같은지 비교해주는 코드를 따로 작성해야 한다. 하지만 구조체를 포인터로 선언한 경우에는 포인터 자체를 비교 연산자로 비교해도 된다. 포인터를 ``==``연산자로 비교하면 주소 값을 비교하게 되는데, 두 포인터에 저장된 메모리 주소가 같으면 같은 구조체를 가리키고 있는 것이므로 구조체의 내용도 당연히 같다.

 

 

 

구조체와 메모리 활용

  구조체도 결국 메모리 공간을 차지하므로 메모리 관련 함수를 사용할 수 있다.

  • ``memset( )``함수를 사용하면 일일이 멤버에 값을 설정하거나 중괄호를 사용하지 않고, 구조체 변수나 메모리의 내용을 특정 값으로 한 번에 바꿀 수 있다. ``string.h``헤더나 ``memory.h``헤더를 포함하면 사용할 수 있다.
    ex) ``memset(&p1, 0, sizeof(struct Point2D));`` // 구조체 포인터인 경우 &생략
  • ``memcpy( )``함수를 사용하면 이미 생성하여 값을 저장한 구조체나 메모리를 다른 곳에 복사하는 것이 가능하다.
    ``string.h`` 헤더에 선언되어 있다.

 

 

 

 

 


5. 매개변수나 반환 값으로 사용되는 구조체 변수

함수로의 구조체 변수 전달과 반환

  구조체 변수도 함수의 인자로 전달하거나 ``return``문을 통해 반환하는 것이 가능하다. 함수에 전달되거나 반환되는 구조체 변수의 값은 매개변수에 통째로 복사되며, 구조체의 멤버로 배열이 선언되어도 똑같이 복사가 진행된다.

 

#include <stdio.h>

// 구조체 person(Person) 정의 
typedef struct person {
	char name[20];
	char phoneNum[20];
	int age;
} Person;	// typedef로 struct person의 새로운 이름 Person 부여 

void ShowPersonInfo(Person man)	// 함수의 매개변수로 Person형 구조체 변수 선언 
{
	printf("name : %s \n", man.name);
	printf("phone : %s \n", man.phoneNum);
	printf("age : %d \n", man.age);
}

Person ReadPersonInfo(void)
{
	Person man;
	printf("name? "); scanf("%s", man.name);
	printf("phone? "); scanf("%s", man.phoneNum);
	printf("age? "); scanf("%s", &man.age);
	
	return man;	// 함수의 리턴값을 Person형 구조체 변수로 지정 
}

int main(void)
{
	Person man = ReadPersonInfo();	// 함수의 반환값을 구조체 변수 man에 저장(복사됨) 
	ShowPersonInfo(man);
	
	return 0;
}

구조체 변수를 함수의 매개변수나 ``return``값으로 사용하는 예시이다.

 

실행 결과

인자의 전달 과정과 값의 반환 과정에서 구조체의 멤버로 선언된 배열도 통째로 복사가 되는 것을 확인할 수 있다.

 

 

 

 

구조체 변수를 대상으로 하는 Call-by-reference

#include <stdio.h>

// 구조체 point(Point) 정의 
typedef struct point{
	int xpos;
	int ypos;	
} Point;	// typedef로 struct point의 새로운이름 Point 부여 

// 원점대칭하는 함수 
void OrgSymTrans(Point * ptr)	// 구조체 포인터 변수 ptr을 함수의 매개변수로 선언 
{
	ptr -> xpos = (ptr -> xpos) * -1;
	ptr -> ypos = (ptr -> ypos) * -1;
}

void ShowPosition(Point pos)
{
	printf("[%d, %d] \n", pos.xpos, pos.ypos);
}

int main(void)
{
	Point pos = {7, -5};
	OrgSymTrans(&pos);	// 구조체 변수 pos의 주소값을 함수의 인자로 보냄 
	ShowPosition(pos);
	OrgSymTrans(&pos);	// 구조체 변수 pos의 주소값을 함수의 인자로 보냄
	ShowPosition(pos);
		
	return 0;
}

구조체 포인터 변수도 매개변수로 선언 가능하다.

그래서 위와 같이 구조체 변수를 대상으로 Call-by-reference 형태의 함수 호출을 구성할 수 있다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 메모리 구조와 동적할당  (0) 2020.06.15
[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 문자, 문자열  (0) 2020.05.28
[C] 스트림, 버퍼  (0) 2020.05.27
[C] 함수 포인터, void 포인터  (0) 2020.05.22

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 문자열 

  • 문자열을 할당하는 방식
    1. 문자열 리터럴을 포인터에 할당하는 방식
    2. 문자 배열에 할당하는 방식

 

 문자, 문자열 관련 함수 

  1. 문자 단위 입출력 함수
    • 문자 입력 함수 : getchar, fgetc
    • 문자 출력 함수 : putchar, fputc
      * 문자 입출력의 EOF
  2. 문자열 단위 입출력 함수
    • 문자열 입력 함수 : gets, fgets
      * 정해진 크기보다 많이 입력하는 경우(fgets)
      * \n이 포함된 경우
    • 문자열 출력 함수 : puts, fputs
  3. 입출력 이외의 문자열 관련 함수
    • 문자열 길이 반환 : strlen
      * fgets에서 enter(개행 문자, \n) 제외하고 받기
    • 문자열 복사 : strcpy, strncpy
    • 문자열 덧붙이기 : strcat, strncat
    • 문자열 비교 : strcmp, strncmp
  4. 변환 함수
    • 문자열 데이터 변환 함수 : atoi, atol, atof

 


 

문자열

  c언어에서는 문자열 끝에 항상 널 문자(NULL)가 붙는다. (\0으로도 표현 가능)

 

 

문자열을 할당하는 방식

  1. 문자열 리터럴을 포인터에 할당하는 방식

char *s = "hello";
printf("%p\n", s);	// 문자열이 저장된 메모리 출력

  문자열 리터럴을 포인터에 할당하면 문자열 리터럴이 변수 안에 저장되는 것이 아니라 문자열이 있는 곳의 메모리 주소만 저장된다. 또한 이 문자열 리터럴이 있는 메모리 주소는 읽기 전용이므로 다른 문자열을 덮어쓸 수 없다. (C언어 컴파일러는 문자열 포인터에 할당한 문자열 리터럴을 실행파일의 읽기 전용 데이터 섹션(데이터 세그먼트)에 배치한다. 따라서 실행 파일이 실행된 뒤에는 읽기 전용 메모리가 되며 쓰기를 할 수 없다. 읽기 전용 데이터 섹션의 이름은 운영체제별로 다르다.)

  • Windows PE : .rdata
  • 리눅스 ELF : .rodata
  • OS X Mach-O : __TEXT, __cstring

 

  - 문자열 리터럴의 최대 크기
  c언어 표준에는 최소 4095문자로 정의되어 있으며 Visual Studio 2015는 최대 2048 byte, GCC는 4095문자까지 사용 가능하다.

 

 

 

  2. 문자 배열에 할당하는 방식

char s[10] = "hello";	// 크기가 10인 char형 배열을 선언하고 문자열 할당
printf("%s\n", s);	// 문자열 출력

// 배열의 크기를 생략하는 경우
char str[] = "hello";	// 문자 배열을 선언하면서 문자열을 바로 할당할 때는 배열크기 생략가능

  문자열을 배열에 저장하는 방식은 배열 요소 하나하나에 문자가 저장된다. (남는 공간에는 모두 NULL이 들어간다.) 배열의 크기는 할당할 문자열보다 크게 지정해야 한다. 또한 배열을 선언한 즉시 문자열로 초기화해야 한다. (선언만 해놓고 나중에 할당할 수 없다. 굳이 할당할 경우 배열 요소 하나하나에 문자를 넣어야 한다.)

 

 

 

 

 


문자, 문자열 관련 함수

1. 문자 단위 입출력 함수

  ``printf``와 ``scanf`` 함수는 포맷을 지정해서 출력하는 함수이기 때문에 사용하는 메모리 공간도 크고, 해야 할 연산의 양도 많아 상대적으로 속도가 느리다. 또한 별도의 서식 지정을 해야 하기 때문에 문장을 구성하는 것도 번거로운 편이다. 그래서 단순히 문자 하나를 입출력하는 것이 목적이라면 문자 단위 입출력 함수를 사용하는 것이 더 낫다.

 

 

  ■ 문자 입력 함수 : getchar, fgetc

#include <stdio.h>
int getchar(void);
int fgetc(FILE * stream);

키보드로부터 하나의 문자를 입력받을 때 일반적으로 사용하는 함수들이다.

파일에 끝에 도달하거나 함수 호출 실패 시 ##EOF##를 반환한다.

(``EOF``가 ``-1``로 정의된 상수이기 때문에 문자와 관련된 함수임에도 반환형이 ``int``이다.)

 

  • ``getchar`` : ``stdin``(표준 입력 스트림)으로 부터 하나의 문자를 입력받아 반환하는 함수
  • ``fgetc`` : ``getchar``와 기능은 같지만 문자를 입력받을 스트림을 지정할 수 있다.

 

 

 

  문자 출력 함수 : putchar, fputc

#include <stdio.h>
int putchar(int c);
int fputc(int c, FILE * stream);	// 두 번째 매개변수인 stream이 문자를 출력할 스트림 지정에 사용됨

모니터로 하나의 문자를 출력할 때 일반적으로 사용하는 함수들이다. 

함수 호출 성공 시 쓰인 문자정보, 실패 시 ##EOF##를 반환한다.

 

  • ``putchar`` : 인자로 전달된 문자정보를 ``stdout``(표준 출력 스트림)으로 전송하는 함수
  • ``fputc`` : ``putchar`` 함수와 동일한 기능을 하지만 문자를 전송할 스트림을 지정할 수 있다. (``stdout``뿐만 아니라, 파일을 대상으로도 데이터 전송 가능) 

 

#include <stdio.h>

int main(void)
{	
	int ch1, ch2;
	
	ch1 = getchar();	// 문자 입력 
	ch2 = fgetc(stdin);	// enter키 입력 
	
	putchar(ch1);		// 문자 출력 
	fputc(ch2, stdout);	// enter키 출력 
	
	return 0;		
}

문자 단위 입출력 함수를 사용하는 예시이다.

 

실행 결과(입력 값에 따라 달라짐)

결과에는 문자 하나만 보이지만 실제로는 ``enter``키까지 2개의 문자가 입출력된 것이다.

@@enter@@키도 아스키코드 값이 @@10@@인 @@\n@@으로 표현되는 문자이기 때문에 입출력의 대상이 된다.

 

 

   문자 입출력의 EOF 

  ``EOF``는 End Of File의 약자로, 파일의 끝을 표현하기 위해 정의된 상수이다. 파일의 ``EOF``는 파일의 끝을 의미하지만 ``fgetc``와 ``getchar`` 함수는 아래에 있는 두 가지 조건중 하나가 만족되면 ``EOF``를 반환한다.

 

   fgetc 함수와 getchar 함수가 EOF를 반환하는 경우 

  1. 함수 호출의 실패
  2. 다음의 키가 입력되는 경우
    Window에서 ``Ctrl + z`` 
    Linux에서 ``Ctrl + d``

 

#include <stdio.h>

int main(void)
{	
	int ch;
	
	while(1)
	{
		ch = getchar();
		if(ch == EOF)	// 입력이 EOF가 될 경우 무한루프 종료
			break;
		putchar(ch);
	}
	 
	return 0;		
}

입력이 ``EOF``가 되기 전까지 계속해서 문자를 입력받고 출력하는 예시이다.

 

실행 결과(Window)

  Window에서 실행하면 ``Ctrl + z``했을 때 프로그램이 종료된다. ``getchar( )``이 반복문 안에 들어갔기 때문에 여러 개의 문자를 입력(공백 포함) 해도 문자의 수만큼 ``getchar`` 함수가 호출되면서 모든 문자를 읽어 들인다.

 

 

 

2. 문자열 단위 입출력 함수

  ``scanf`` 함수는 공백을 기준으로 문자열을 구분하기 때문에 공백이 포함된 형태의 문자열을 입력받는데 제한이 있다. 하지만 문자열 단위 입출력 함수를 사용하면 공백을 포함하는 문자열도 입력받을 수 있다.

 

 

  문자열 입력 함수 : gets, fgets

#include <stdio.h>
char * gets(char * s);
char * fgets(char * s, int n, FILE * stream);
// gets 함수 사용 예시
char str[7];
gets(str);	// 입력받은 문자열을 배열 str에 저장


// fgets 함수 사용 예시
char str[7];
fgets(str, sizeof(str), stdin);	// stdin으로부터 문자열을 입력받아 str의 길이만큼 str에 저장 (null문자 포함이므로 실제로는 arr - 1 길이)

키보드로부터 하나의 문자열을 입력받을 때 일반적으로 사용하는 함수들이다.

파일의 끝에 도달하거나 함수 호출 실패 시 ##NULL## 포인터를 반환한다.

 

  • ``gets``의 경우 배열을 넘어서는 길이의 문자열이 입력되면 할당받지 않은 메모리 공간을 침범하여 실행 중 오류가 발생하므로 가급적 ``fgets`` 함수를 호출하는 것이 좋다.
  • 문자열 중간에 삽입된 공백 문자도 문자열의 일부로 받아들인다.
  • ``fgets``는 @@\n@@을 만날 때까지 문자열을 읽어 들이는데, @@\n@@을 문자열의 일부로 받아들인다.
  • ``fgets``로 문자열을 입력받으면 문자열의 끝에 자동으로 null 문자가 추가된다.
    ex) 두 번째 인자가 ``n``이면 실제 들어가는 문자열의 개수는 ``n - 1``

 

 

  - 정해진 크기보다 많이 입력하는 경우 (fgets)

#include <stdio.h>

int main(void)
{	
	char str[7];
	
	for(int i = 0 ; i < 3 ; i++)
	{
		fgets(str, sizeof(str), stdin);
		printf("Read %d : %s \n", i + 1, str);
	}
	
	return 0;		
}

 

실행 결과 1

  입력받을 ``str``의 크기는 ``7``인데 입력된 문자열의 길이가 배열의 크기를 넘어선 경우 입력 버퍼에 남아있던 값들이 멋대로 ``fgets`` 함수의 인자로 들어가게 된다. (##null## 제외 ##6##개씩 들어감) 결과값을 보면 아직도 ``9``와 ``0``이 버퍼에 남아있는 것을 확인할 수 있다. 버퍼를 비우려면 ``fgets`` 함수를 더 호출하거나 입력 버퍼를 비워야 의도한 대로 입력을 ``3``번 받을 수 있다.

 

 

  - \n이 포함된 경우

실행결과 2

  바로 위에서 실행한 예제에 입력값을 바꾼 것이다. ``fgets`` 함수는 ``\n``을 만날 때까지 문자열을 읽어 들이는데, 읽어 들이고 난 후 ``\n``을 제외하거나 버리지 않고 문자열의 일부로 받아들인다. 그래서 printf 함수에는 개행 문자가 한 번만 사용됐지만 실제로는 문자열에 포함된 ``\n``까지 ``2``번 개행되었다. 문자열 중간의 공백도 마찬가지이다.

 

 

 

  ■ 문자열 출력 함수 : puts, fputs

#include <stdio.h>
int puts(const char * s);
int fputs(const char * s, FILE * stream);	// 두 번째 인자를 통해 출력의 대상을 지정할 수 있다.

모니터로 하나의 문자열을 출력할 때 일반적으로 사용하는 함수들이다.

함수 호출 성공 시 음수가 아닌 값을, 실패 시 ##EOF##를 반환한다.

 

  • ``puts`` : ``stdout``을 대상으로 출력. 출력 후 자동 개행
  • ``fputs`` : ``puts``와 기능은 같지만 출력의 대상 지정 가능. 출력 후 자동 개행 X

 

#include <stdio.h>

int main(void)
{	
	char * str = "Simple String";
	
	printf("1. puts test ------- \n");
	puts(str);				// 문자열의 주소값 전달
	puts("So Simple String");
	
	printf("2. fputs test ------- \n");
	fputs(str, stdout); 			// 문자열의 주소값 전달, 출력 대상을 stdout 스트림으로 지정
	printf("\n");				// fputs는 자동 개행이 되지 않기 때문에 별도의 개행 작업이 필요함
	fputs("So Simple String", stdout);
	printf("\n");				// fputs는 자동 개행이 되지 않기 때문에 별도의 개행 작업이 필요함
	
	printf("2. end of main ------- \n");
	
	return 0;		
}

``puts``와 ``fputs``의 차이점을 확인하는 예시이다.

@@puts@@함수와 달리 @@fputs@@ 함수는 자동 개행이 되지 않기 때문에 함수 호출 후 별도의 개행 작업을 해야 한다.

 

실행 결과

 

 

 

 

3. 입출력 이외의 문자열 관련 함수

  표준 C에서는 문자열과 관련된 다양한 함수가 정의되어 있다. 아래의 함수들은 헤더 파일 @@string.h@@에 선언된 문자열 관련 함수들이다.

 

 

  ■ 문자열의 길이 반환 : strlen

#include <string.h>
size_t strlen(cons char * s);

인자로 전달된 문자열의 길이를 반환하는 함수이다. (@@null@@은 길이에서 제외)

  이 함수의 반환형인 ``size_t``는 ``typedef unsigned int size_t``로 선언되어 있으므로 본래는 ``unsigned int``형 변수에 저장하고 서식 문자 ``%u``로 출력하는 것이 정확하나, 반환 값을 ``int``에 저장하고 서식 문자 ``%d``로 출력하는 것이 일반적이다.

 

 

  - fgets에서 enter(개행 문자, \n) 제외하고 받기

#include <stdio.h>
#include <string.h>

void RemoveBSN(char str[])
{
	int len = strlen(str);
	str[len - 1] = 0;	// \n이 저장된 위치에 null문자 삽입
}

int main(void)
{	
	char str[100];
	printf("문자열 입력 : ");
	fgets(str, sizeof(str), stdin);
	printf("길이 : %d, 내용 %s \n", strlen(str), str);
	
	RemoveBSN(str);
	printf("길이 : %d, 내용 %s \n", strlen(str), str);
	
	return 0;
}

  ``fgets``는 ``\n``까지 문자열의 일부로 받아들이기 때문에, 입력받은 문자열의 길이를 계산해서 ``\n``이 저장된 위치에 널 문자의 아스키코드값인 ``0``을 넣어 ``\n``을 제거하는 방법도 있다.

 

실행 결과

 

 

  ■ 문자열 복사 : strcpy, strncpy

#include <string.h>
char * s strcpy(char * dest, const char * src);
char * s strncpy(char * dest, const char * src, size_t n);
// strcpy 함수 사용예시
char str1[30] = "Simple String";
char str2[30];
strcpy(str2, str1);

// strncpy 함수 사용 예시
strncpy(str2, str1, sizeof(str2));	// str1의 문자열을 str2의 크기만큼 str2에 복사

문자열의 복사에 사용되는 함수들이다. 복사된 문자열의 주소 값을 반환한다.

 

  • ``strcpy`` : 문자열 복사. 문자열이 복사될 배열의 길이가 문자열의 길이보다 작지 않도록 주의해야 한다.
  • ``strncpy`` : 세 번째 인자 값만큼 문자열 복사. (복사될 배열의 길이를 넘어서지 않는 범위 내에서 복사할 때 유용하게 사용됨.) 그러나 문자열을 단순하게 값만큼 복사하기 때문에 세 번째 인자에 값을 넣을 때는 배열의 실제 길이보다 @@1@@ 작은 값을 전달해서 @@null@@문자가 삽입될 공간을 남겨두고 복사를 진행한 뒤, 끝에 @@null@@문자를 삽입해야 한다.

 

#include <stdio.h>
#include <string.h>

int main(void)
{	
	char str1[20] = "1234567890";
	char str2[20];
	char str3[5];
	
	// case 1
	strcpy(str2, str1);
	puts(str2);
	
	// case 2
	strncpy(str3, str1, sizeof(str3));
	puts(str3);
	
	// case3
	strncpy(str3, str1, sizeof(str3) - 1);
	str3[sizeof(str3) - 1] = 0;
	puts(str3);
	
	return 0;
}

``strcpy``와 ``strncpy``를 사용하는 예시이다.

 

실행 결과

  case 2의 ``str3``은 ``null``문자가 들어갈 자리를 고려하지 않고 배열의 크기만큼 복사를 진행했기 때문에 문자열 끝에 ``null``문자가 없어져 버렸다. 그래서 16행에서 출력할 때 문자열 출력이 끝나지 않고 이상한 값이 계속 출력되는 것이다. case 3처럼 복사를 진행하고 배열의 끝에 ``null``문자를 삽입해야 제대로 복사가 진행된다.

 

 

  ■ 문자열 덧붙이기 : strcat, strncat

#include <string.h>
char * strcat(char * dest, const char * src);
char * strncat(char * dest, const char * src, size_t));

문자열의 뒤에 다른 문자열을 복사하는 함수이다. 덧붙여진 문자열의 주소 값을 반환한다.

 

  • 덧붙이는 위치는 ``null``문자 다음이 아닌 ``null``문자가 저장된 위치이다. (그래야 붙인 이후에도 ##null##문자가 하나가 됨)
  • @@strncat@@에서 세 번째 인자가 @@8@@이면 @@null@@문자를 제외한 @@8@@만큼이라는 의미이다. 붙여질 때는 문자열 끝에 @@null@@문자가 자동으로 삽입되어 9개의 문자가 덧붙여진다.

 

#include <stdio.h>
#include <string.h>

int main(void)
{	
	char str1[20] = "First~";
	char str2[20] = "Second";
	
	char str3[20] = "Simple num : ";
	char str4[20] = "1234567890";
	
	// case 1
	strcat(str1, str2);	// str1의 크기가 str2를 덧붙일 수 있을 만큼 커야함
	puts(str1);
	
	// case 2
	strncat(str3, str4, 7);
	puts(str3);
	
	return 0;
}

``strcat``과 ``strncat``을 사용하는 예시이다.

 

실행 결과

 

 

  ■ 문자열 비교 : strcmp, strncmp

#include <string.h>
int strcmp(const char * s1, const char * s2);
int strncmp(const char * s1, const char * s2, size_t n);

  문자열을 ``==``연산자로 비교해버리면 문자열의 내용을 비교하는 것이 아니라 문자열의 주소 값을 비교하는 것이 되어버린다. 그래서 문자열을 비교할 때는 위의 함수를 사용해야 한다. 문자(문자열 끝의 ##null##문자까지 포함)를 아스키 값으로 비교해서 두 문자열의 내용이 같으면 ##0##, 다르면 ##0##이 아닌 값을 반환한다.

 

  • ``strcmp`` : 문자열 비교
  • ``strncmp`` : ``strcmp``와 같지만 세 번째 인자로 전달된 수의 크기만큼만 문자를 비교

 

#include <stdio.h>
#include <string.h>

int main(void)
{	
	char str1[20];
	char str2[20];
	
	printf("문자열 입력 1 : ");
	scanf("%s", str1);
	printf("문자열 입력 2 : ");
	scanf("%s", str2);
	
	if(!strcmp(str1, str2))			// 두 문자열이 같으면 0을 반환. if문에서 0은 false이므로 !연산하면 true가 됨.
		puts("두 문자열은 동일");
	else
	{
		puts("두 문자열은 동일하지 않음");
		
		if(!strncmp(str1, str2, 3))	// 두 문자열이 같으면 0을 반환. if문에서 0은 false이므로 !연산하면 true가 됨.
			puts("그러나 앞 세 글자는 동일");
	}
	
	return 0;
}

``strcmp``와 ``strncmp``를 사용해서 문자열을 비교하는 예시이다.

두 함수 모두 문자열이 같을 때 @@0@@을 반환하는데, C언어에서는 @@0@@이 @@false@@를 의미하므로 @@!@@연산을 하면 @@true@@가 된다.

 

실행 결과

 

 

 

4. 변환 함수

  @@stdlib.h@@에 선언된 함수이다.

 

 

  ■ 문자열 데이터 변환 함수 : atoi, atol, atof

int atoi(const char * str);	// 문자열 내용을 int형으로 변환
long atol(const char * str);	// 문자열 내용을 long형으로 변환
double atof(const char * str);	// 문자열 내용을 double형으로 변환

문자열로 표현된 정수나 실수 값을 해당 정수나 실수 데이터로 변환해야 하는 경우 사용한다.

 

#include <stdio.h>
#include <stdlib.h> 

int main(void)
{	
	char str[20];
	
	printf("정수 입력 : ");
	scanf("%s", str);
	printf("%d \n", atoi(str));
	
	printf("실수 입력 : ");
	scanf("%s", str);
	printf("%g \n", atof(str));
	
	return 0;
}

변환 함수를 사용하는 예시이다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 공용체(Union Type), 열거형(Enumerated Type)  (0) 2020.06.08
[C] 구조체  (0) 2020.06.01
[C] 스트림, 버퍼  (0) 2020.05.27
[C] 함수 포인터, void 포인터  (0) 2020.05.22
[C] 다차원 배열과 포인터  (0) 2020.05.19

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 스트림 

  • 스트림의 생성, 소멸
    * 표준 스트림

 

 표준 입출력과 버퍼 

  • 출력 버퍼 비우는 함수 : fflush
  • 입력 버퍼 비우기

 


 

스트림(stream)

  •  입력  : (프로그램을 기준으로) 프로그램 안으로 데이터가 들어오는 것
    입력장치의 예 : 키보드, 파일 등..
  •  출력  : (프로그램을 기준으로) 프로그램 밖으로 데이터가 나가는 것
    출력 장치의 예 : 모니터, 파일 등..
  •  스트림  : 외부장치와 프로그램과의 데이터 송수신 도구(프로그램과 입출력 장치를 연결해주는 다리 역할을 하는 매개체). 단방향으로만 데이터의 전송이 이루어진다. 소프트웨어적으로 구현되어 있으며 운영체제에서 제공한다.

 

 

스트림의 생성, 소멸

  ``console``(일반적으로 키보드와 모니터를 의미) 입출력을 위한 '입력 스트림'과 '출력 스트림'은 '표준 스트림(standard stream)'으로, 프로그램이 실행되면 자동으로 생성되고, 프로그램이 종료되면 자동으로 소멸한다. 반면 파일과의 연결을 위한 스트림의 생성은 프로그래머가 직접 요구해야 한다.

 

 

  ■ 표준 스트림(standard stream)

  ``stderr``는 ``stdout``과 기능은 같지만 입출력 리다이렉션(redirection) 시 ``stderr``는 표준 에러 스트림의 출력 대상을 변경시킬 수 있다는 점에서 차이가 있다.

스트림 이름 스트림 종류 기능
stdin 표준 입력 스트립 키보드 대상으로 입력
stdout 표준 출력 스트림 모니터 대상으로 출력
stderr 표준 에러 스트림 모니터 대상으로 출력

 

 

 

 

 


표준 입출력과 버퍼

  ``printf``와 ``scanf``, ``fputc``, ``fgetc``등은 ANSI C의 표준에서 정의된 함수기 때문에 '표준 입출력 함수'라고 한다. 표준 입출력 함수를 통해 데이터를 입출력하는 경우, 해당 데이터들은 운영체제가 제공하는 '메모리 버퍼'를 중간에 통과하게 된다.

  •  메모리 버퍼  : 데이터를 임시로 저장하는 메모리 공간
  • 버퍼링(Buffering)을 하는 이유
    데이터의 전송 효율성 때문. 외부장치의 입출력은 시간이 많이 걸리므로 데이터를 묶어 보내는 것이 더 빠르고 효율적이다.

  예를 들어 키보드를 통해 입력된 데이터는 일단 입력 버퍼에 저장된 다음(버퍼링 된 다음) 프로그램에서 읽혀진다. ##enter## 키가 눌리는 시점에 키보드로부터 입력된 데이터가 입력 스트림을 거쳐 입력 버퍼로 들어가게 된다. 그래서 ##enter##키가 눌리기 전에는 입력 버퍼가 비워져 있기 때문에 ##fgets## 함수가 문자열을 읽어 들이지 못한다.

 

 

 

출력 버퍼를 비우는 함수 : fflush

#include <stdio.h>
int fflush(FILE * stream);

// ex
fflush(stdout);	// 표준 출력 버퍼 비우기

이 함수는 인자로 전달된 스트림의 버퍼를 비우는 기능을 한다.

함수 호출 성공 시 ##0##, 실패 시 ##EOF##를 반환한다.

 

  • 출력 버퍼가 비워진 다는 것은 출력 버퍼에 저장된 데이터가 버퍼를 떠나 목적지로 이동된다는 의미이다.
  • 출력 버퍼가 비워지는 시점은 시스템이나 버퍼의 성격에 따라 달라진다.
  • ``fflush`` 함수를 사용하면 시스템의 어떤 표준 출력 버퍼라 할지라도 버퍼의 저장된 내용이 비워지면서 데이터가 목적지로 이동한다.
  • 파일을 대상으로 호출하는 것도 가능한데, 인자로 파일의 스트림 정보가 전달되면 해당 버퍼에 저장되어 있던 데이터가 버퍼를 떠나 파일에 기록된다.

 

 

 

입력 버퍼 비우기

  입력 버퍼의 비워짐은 데이터의 소멸을 의미하기 때문에 출력 버퍼를 비우는 것과 개념적으로 차이가 있다. 가끔 입력 버퍼에 남아있는 불필요한 데이터의 소멸을 위해 입력 버퍼를 비워야 하는 경우가 있다.

 

#include <stdio.h>

int main(void)
{	
	char perID[7];
	char name[10];
	
	fputs("주민번호 앞 6자리 : ", stdout);
	fgets(perID, sizeof(perID), stdin);
	
	fputs("이름 입력 : ", stdout);
	fgets(name, sizeof(name), stdin);
	
	printf("주민번호 : %s \n", perID);
	printf("이름 : %s \n", name);
	
	return 0;		
}

위의 예시는 ``fgets``를 통해 입력받은 데이터를 ``perID``라는 배열에 저장한다.

 

실행 결과

  그런데 ``fgets``를 두 번 사용했음에도 사용자에게 입력을 ``1``번밖에 받지 않는다. 입력 버퍼에 ``\n``이 남기 때문이다. 지금과 같은 경우에는 입력 버퍼에서 문자 하나만 지워주면 되지만 입력값을 예상하기 어려운 경우가 많으므로 지정한 자리수 외에는 전부 입력 버퍼에서 지워주어야 한다.

 

 

   위와 같은 결과가 나온 이유 

  1. ``fgets``는 ``\n``까지 문자로 받기 때문에 입력값이 ``950915``인 경우 ``950915\n``으로 7자리가 된다.
  2. 문자열의 마지막 값엔 ``null``이 들어가기 때문에 끝에 있던 ``\n``는 입력 버퍼에 남는다.
  3. 이후에 ``fgets``함수가 호출되면 ``\n``이 나올 때까지 읽어 들이기 때문에 버퍼에 하나 남아있던 ``\n``를 읽어버리게 된다.

 

 

#include <stdio.h>

void ClearLineFromReadBuffer(void)	// 입력 버퍼를 비우는 함수
{
	while(getchar() != '\n');	// null 문자가 아닐때까지 문자를 입력받음(버퍼 비우기)
}

int main(int argc, int argv[])
{	
	char perID[7];
	char name[10];
	
	fputs("주민번호 앞 6자리 : ", stdout);
	fgets(perID, sizeof(perID), stdin);
	ClearLineFromReadBuffer();	// 함수 호출
	
	fputs("이름 입력 : ", stdout);
	fgets(name, sizeof(name), stdin);
	
	printf("주민번호 : %s \n", perID);
	printf("이름 : %s \n", name);
	
	return 0;		
}

  입력 버퍼에 저장된 문자들은 읽어 들이면 지워지므로 ``null``문자가 나올 때까지 ``getchar( )``로 문자열을 입력받아 버퍼를 비우는 함수를 선언하면 된다. (저장하지 않고 받기만 해서 결과적으로는 버퍼에서 지워짐)

 

실행 결과

``perID``보다 큰 값을 입력했음에도 의도한 대로 ``fgets``가 동작하는 것을 확인할 수 있다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 구조체  (0) 2020.06.01
[C] 문자, 문자열  (0) 2020.05.28
[C] 함수 포인터, void 포인터  (0) 2020.05.22
[C] 다차원 배열과 포인터  (0) 2020.05.19
[C]이중 포인터(더블 포인터)  (0) 2020.05.12

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 포인터 

  1. 포인터
    • 포인터 변수
    • 포인터 변수의 선언
      * 포인터 변수의 크기
    • 포인터 형(type)
    • 포인터 변수와 관련된 연산자
      1. & 연산자
      2. * 연산자
    • null 포인터
    • 포인터 대상의 const 선언
      * 상수를 가리키는 포인터인 경우
      * 포인터 자체가 상수인 경우
      * 포인터가 상수이면서 상수를 가리키는 경우
  2. 포인터와 배열
    • 포인터 연산
    • 문자열의 선언방식
    • 포인터 배열
      * 문자열 배열
  3. 포인터와 함수
    • 함수의 호출 방식
      * Call-by-value
      * Call-by-reference
    • 함수의 인자로 배열 전달
      * 인자로 배열 전달 시 배열의 크기
  4. 이중 포인터(더블 포인터)
    • 이중 포인터 선언과 사용
    • 포인터 변수 대상의 Call-by-reference
    • 포인터 배열과 포인터 배열의 포인터 type
    • 다중 포인터 변수
  5. 다차원 배열과 포인터
    • 2차원 배열의 이름과, 2차원 배열의 첫 번째 행
    • 2차원 배열의 증감 연산
    • 2차원 배열의 포인터
    • 함수 인자로 2차원 배열 전달
    • 다차원 배열에서 arr[i]와 *(arr + i)
    • 배열 포인터와 포인터 배열
    • main 함수의 인자
      * 인자의 형성 과정
      * char * argv[ ]
  6. 함수 포인터
    • 함수 포인터 변수 선언
  7. void 포인터

 


 

함수 포인터

  프로그래머가 정의하는 모든 함수는 프로그램 실행 시 메인 메모리에 저장되어 실행된다. 배열처럼 함수의 이름은 함수가 저장된 메모리 공간의 주소 값을 의미하는데, 배열 이름과 마찬가지로 함수의 이름도 상수의 형태이다. 함수의 주소 값 저장을 위한 포인터 변수를 별도로 설정할 수 있는데, 이러한 포인터 변수를 '함수 포인터 변수'라고 한다.

 

 

함수 포인터 변수 선언

[반환형] (* [포인터 이름]) ([매개변수1 타입], [매개변수2 타입] ...)

// ex)
int SimpleFunc1(int num) {...}
void SimpleFunc2(char * str) {...}

// 함수 포인터 변수 선언
int (*fptr1) (int)
fptr1 = SimpleFunc1;	// 대입 연산이 끝나면 함수 이름과, 함수 포인터 변수에는 동일한 값이 저장됨.
fptr1(3)	// 대입 이후에는 포인터 이름으로 함수호출 가능

void (*fptr2) (char *) = SimpleFunc2;	// 선언하면서 동시에 대입가능

  함수 포인터 형(type)은 반환형과 매개변수의 선언을 통해서 결정짓는다. 그래서 반환형과 매개변수의 선언이 일치하면(매개변수의 type과 개수, 순서가 같으면) 함수 포인터 형도 일치한다. 함수 포인터 변수에 함수의 주소를 대입하고 난 이후에는 (동일한 값이 저장되어) 상수냐 변수냐가 이 둘의 유일한 차이점이 된다. 또한 함수 포인터 변수를 이용해서 함수를 호출하는 것도 가능하다.

 

#include <stdio.h>

void SimpleAdder(int n1, int n2)
{
	printf("%d + %d = %d \n", n1, n2, n1+ n2);
}

void ShowString(char * str)
{
	printf("%s \n", str);
}

int main(void)
{	
	char * str = "Function Pointer";
	int num1 = 10, num2 = 20;
	
	void (*fptr1)(int, int) = SimpleAdder;
	void (*fptr2)(char *) = ShowString;
	
	// 함수 포인터 변수에 의한 호출
	fptr1(num1, num2);
	fptr2(str); 
	
	return 0;		
}

함수 포인터 변수를 선언하고 사용하는 예시이다.

 

실행 결과

 

#include <stdio.h>

int WhoIsFirst(int age1, int age2, int (*cmp)(int n1, int n2))	// 매개 변수의 선언으로 함수 포인터 변수가 올 수 있다.
{
	return cmp(age1, age2); 	// (함수 포인터 변수인) 세번째 인자에 따라 다른 함수가 호출되어 리턴됨
} 

int OlderFirst(int age1, int age2)
{
	if(age1 > age2)
		return age1;
	else if(age1 < age2)
		return age2;
	else
		return 0;
}

int YoungerFirst(int age1, int age2)
{
	if(age1 < age2)
		return age1;
	else if(age1 > age2)
		return age2;
	else
		return 0;
}

int main(void)
{	
	int age1 = 20;
	int age2 = 30;
	int first;
	
	printf("입장 순서 1\n");
	first = WhoIsFirst(age1, age2, OlderFirst);
	printf("%d세와 %d세중 %d세가 먼저 입장 ! \n\n", age1, age2, first);
	
	printf("입장 순서 2\n");
	first = WhoIsFirst(age1, age2, YoungerFirst);
	printf("%d세와 %d세중 %d세가 먼저 입장 ! \n\n", age1, age2, first);
	
	return 0;		
}

함수 포인터 변수가 매개변수로 오는 것도 가능하다.

위의 예시는 함수 포인터를 매개변수로 사용하고, 세 번째 인자에 따라 다른 함수가 동작한다.

 

실행 결과

 

 

 

 

 


void 포인터

void * ptr;

  형(type)이 존재하지 않는 포인터를 ``void``포인터라고 한다. 어떠한 변수의 주소 값이든 다 담을 수 있고(원래 자료형이 다른 포인터끼리 메모리 주소를 저장하면 컴파일 경고(warning)가 발생한다.), 함수의 주소 값을 담는 것도 가능하지만, type이 없기 때문에 포인터 연산이 불가능하고 값의 변경이나 참조도 할 수 없다. 일단 주소 값에 의미를 두고 포인터 형(type)은 나중에 정해야 할 때 사용한다. 암시적으로 자료형이 변환되는 방식으로, 이러한 특성 때문에 void 포인터는 범용 포인터라고도 한다. void 포인터는 자료형이 정해져 있지 않아서 역참조를 할 수 없다. 메모리의 동적 할당에서 많이 사용한다. 

 

#include <stdio.h>

void SoSimpleFunc(void)
{
	printf("I'm so simple");
}

int main(void)
{	
	int num = 20;
	void * ptr;
	
	ptr = &num;
	/* 아래 두줄은 컴파일 에러
	*ptr = 20;
	ptr++;
	*/
	printf("%p \n", ptr);

	ptr = SoSimpleFunc;
	printf("%p \n", ptr);

	return 0;		
}

void 포인터를 사용하는 예시이다. 포인터 연산이 불가능하기 때문에 주석 처리된 부분은 오류가 난다.

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 문자, 문자열  (0) 2020.05.28
[C] 스트림, 버퍼  (0) 2020.05.27
[C] 다차원 배열과 포인터  (0) 2020.05.19
[C]이중 포인터(더블 포인터)  (0) 2020.05.12
[C] 포인터와 배열, 함수  (0) 2020.05.07

 

 

공부했던 자료 정리하는 용도입니다.

재배포, 수정하지 마세요.

 

 


 포인터 

  1. 인터
    • 포인터 변수
    • 포인터 변수의 선언
      * 포인터 변수의 크기
    • 포인터 형(type)
    • 포인터 변수와 관련된 연산자
      1. & 연산자
      2. * 연산자
    • null 포인터
    • 포인터 대상의 const 선언
      * 상수를 가리키는 포인터인 경우
      * 포인터 자체가 상수인 경우

      * 포인터가 상수이면서 상수를 가리키는 경우
  2. 포인터와 배열
    • 포인터 연산
    • 문자열의 선언방식
    • 포인터 배열
      * 문자열 배열
  3. 포인터와 함수
    • 함수의 호출 방식
      * Call-by-value
      * Call-by-reference
    • 함수의 인자로 배열 전달
      * 인자로 배열 전달 시 배열의 크기
  4. 이중 포인터(더블 포인터)
    • 이중 포인터 선언과 사용
    • 포인터 변수 대상의 Call-by-reference
    • 포인터 배열과 포인터 배열의 포인터 type
    • 다중 포인터 변수
  5. 다차원 배열과 포인터
    • 2차원 배열의 이름과, 2차원 배열의 첫 번째 행
    • 2차원 배열의 증감 연산
    • 2차원 배열의 포인터
    • 함수 인자로 2차원 배열 전달
    • 다차원 배열에서 arr[i]와 *(arr + i)
    • 배열 포인터와 포인터 배열
    • main 함수의 인자
      * 인자의 형성 과정
      * char * argv[ ]
  6. 함수 포인터
    • 함수 포인터 변수 선언
  7. void 포인터

 


 

다차원 배열과 포인터

2차원 배열의 이름과, 2차원 배열의 첫 번째 행

#include <stdio.h>

int main(void)
{
	int arr2d[3][3];
	printf("%d \n", arr2d);
	printf("%d \n", arr2d[0]);
	printf("%d \n\n", &arr2d[0][0]);
	
	printf("%d \n", arr2d[1]);
	printf("%d \n\n", &arr2d[1][0]);
	
	printf("%d \n", arr2d[2]);
	printf("%d \n\n", &arr2d[2][0]);
	
	printf("sizeof(arr2d) : %d \n", sizeof(arr2d));
	printf("sizeof(arr2d[0]) : %d \n", sizeof(arr2d[0]));
	printf("sizeof(arr2d[1]) : %d \n", sizeof(arr2d[1]));
	printf("sizeof(arr2d[2]) : %d \n", sizeof(arr2d[2]));
			
	return 0;
}

  위의 예시로 설명하면 ``arr2d``와 ``arr2d[0]``의 출력 결과는 같지만, ``arr2d``는 첫 번째 요소를 가리키면서 배열 전체를 의미하는 반면, ``arr2d[0]``은 첫 번째 요소를 가리키되 1행만을 의미하기 때문에 서로 다른 것이다. ``sizeof`` 연산하면 결과값이 다른 것을 확인할 수 있다.

 

실행 결과

 

 

 

 

2차원 배열의 증감 연산

#include <stdio.h>

int main(void)
{
	int arr1[3][2];
	int arr2[2][3];
	
	printf("arr1 : %p \n", arr1);
	printf("arr1 + 1 : %p \n", arr1 + 1);
	printf("arr1 + 2 : %p \n", arr1 + 2);
	
	printf("\narr2 : %p \n", arr2);
	printf("arr2 + 1 : %p \n", arr2 + 1);
	
	return 0;
}

  2차원 배열 이름을 대상으로 증가 및 감소 연산을 할 경우, 연산 결과는 각 행의 첫 번째 요소의 주소 값이 된다. 이 때문에 2차원 배열을 이루는 요소의 자료형이 동일하더라도 배열의 열 길이가 다르면 포인터 연산의 결과는 달라진다.

 

실행 결과

``arr1``은 열 길이가 ``2``인 2차원 배열이므로 ``sizeof(int) x 2``인 ``8``만큼 주소 값이 증가하고, 

``arr2``은 열 길이가 ``3``인 2차원 배열이므로 ``sizeof(int) x 3``인 ``12(16진수로 C)``만큼 주소 값이 증가하는 것을 확인할 수 있다.

 

 

 

 

2차원 배열의 포인터

[자료형] (*포인터이름) [열길이];

// ex)
int arr[3][4];
int (*ptr)[4];	// 2차원 배열의 포인터

// ex) 다차 배열 포인터인 경우
int* (*ptr1)[5];	// int* 형 변수로 이루어진, 열 길이가 5인 2차원 배열
int*** (*ptr2)[5];	// int***형 변수로 이루어진, 열 길이가 5인 2차원 배열

  2차원 배열의 이름으로 증감 연산을 하면 각 행의 첫 번째 요소로 이동하므로 포인터도 똑같이 증감 연산을 했을 때 각 행의 첫 번째 요소로 이동해야 한다. 따라서 ``int (*ptr)[4]``는 "포인터 ``ptr``이 가리키는 대상의 자료형이 ``int``형 이고 포인터 연산 시 ``sizeof(int) x 4``의 크기만큼 값이 증가 및 감소하는 포인터 형"이라는 의미이다. 열의 개수에 따라 증감 값이 달라지는 것이기 때문에 자료형만 같고 크기가 다른 2차원 배열이라도 열 길이만 같다면 같은 포인터형을 사용할 수 있다. 또한 괄호를 생략하면 포인터 배열이 되므로 주의한다.

 

 

#include <stdio.h>

int main(void)
{	
	int arr1[2][2] = {
				{1, 2},
				{2, 3}
			 };
	int arr2[3][2] = {
				{1, 2},
				{3, 4},
				{5, 6}
			 };
	int arr3[4][2] = {
				{1, 2},
				{3, 4},
				{5, 6},
				{7, 8}
			 };
	
	int (*ptr)[2];
	
	ptr = arr1;	// 열 길이가 같이 때문에 같은 포인터형 사용가능
	printf("** show 2,2 arr1 **\n");
	for(int i = 0 ; i < 2 ; i++)
		printf("%d %d \n", ptr[i][0], ptr[i][1]);
	
	ptr = arr2;	// 열 길이가 같이 때문에 같은 포인터형 사용가능
	printf("** show 3,2 arr2 **\n");
	for(int i = 0 ; i < 3 ; i++)
		printf("%d %d \n", ptr[i][0], ptr[i][1]);
	
	ptr = arr3;	// 열 길이가 같이 때문에 같은 포인터형 사용가능
	printf("** show 4,2 arr3 **\n");
	for(int i = 0 ; i < 4 ; i++)
		printf("%d %d \n", ptr[i][0], ptr[i][1]);
	
		
	return 0;
}

2차원 배열 포인터를 사용하는 예시이다. 열 길이가 모두 같기 때문에 같은 포인터형을 사용할 수 있다.

 

실행 결과

 

 

 

 

함수 인자로 2차원 배열 전달

#include <stdio.h>

void ShowArr2DStyle(int (*arr)[4], int column)
{
	for(int i = 0 ; i < column ; i++)
	{
		for(int j = 0 ; j < 4 ; j++)
			printf("%d", arr[i][j]);
		printf("\n");
	}
	printf("\n");
}

int Sum2DArr(int arr[][4], int column)
{
	int sum = 0;
	for(int i = 0 ; i < column ; i++)
		for(int j = 0 ; j < 4 ; j++)
			sum += arr[i][j];
	
	return sum;
}

int main(void)
{	
	int arr1[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};
	int arr2[3][4] = {1, 1, 1, 1, 3, 3, 3, 3, 5, 5, 5, 5};
	
	ShowArr2DStyle(arr1, sizeof(arr1)/sizeof(arr1[0]));
	ShowArr2DStyle(arr2, sizeof(arr2)/sizeof(arr2[0]));
	
	printf("arr1의 합 : %d \n", Sum2DArr(arr1, sizeof(arr1) / sizeof(arr1[0])));
	printf("arr2의 합 : %d \n", Sum2DArr(arr2, sizeof(arr2) / sizeof(arr2[0])));
		
	return 0;
}

  1차원 배열을 인자로 넣었을 때 ``int * param`` 과 ``int param[ ]``이 같았던 것처럼 2차원 배열에서도 ``int (*parr1)[7]``을 ``int parr[ ][7]``로 사용 가능하다. (마찬가지로 매개 변수일 경우에만 서로 대체 가능)

 

 

 

 

다차원 배열에서 arr[i]와 *(arr + i)

arr[2][1] = 4;
(*(arr + 2))[1] = 4;	// arr[2]는 *(arr + 2)이므로 변환가능
*(arr[2] + 1) = 4;	// arr[2]를 A로 치환하면 A[1]이므로 *(A + 1)꼴로 변환가능
*(*(arr + 2) + 1) = 4;	// 3행에서 arr[2]를 *(arr + 2)로 변환

@@arr[i] = *(arr+ i)@@는 1차원 배열뿐만 아니라 다차원 배열에서도 성립한다.

그래서 위의 4개 문장은 전부 동일한 의미이다.

 

// 참고 **arr과 a[0][0]은 동일한 표현이다.
// 변환과정
**arr
**(arr + 0)	// *arr은 *(arr + 0)이므로 변환가능
*(*(arr + 0) + 0) // *(arr + 0)을 A로 치환하면 *A인데, *A는 *(A + 0)으로 변환가능
*(arr[0] + 0)	// *(arr + 0)은 arr[0]이므로 변환가능
arr[0][0]	// arr[0]을 A로 치환하면 *(A + 0)이므로 A[0]

같은 이유로 ``**[배열 이름]``은 ``[배열이름][0][0]``과 동일한 표현이다.

 

#include <stdio.h>

int main(void)
{	
	int a[3][2] = { {1,2}, {3, 4}, {5, 6}};
	
	printf("a[0] : %p \n", a[0]);
	printf("*(a + 0) : %p \n", *(a + 0));
	
	printf("a[1] : %p \n", a[1]);
	printf("*(a + 1) : %p \n", *(a + 1));
	
	printf("a[2] : %p \n", a[2]);
	printf("*(a + 2) : %p \n", *(a + 2));
	
	printf("%d %d \n", a[2][1], (*(a + 2))[1]);
	printf("%d %d \n", a[2][1], *(a[2] + 1));
	printf("%d %d \n", a[2][1], *(*(a + 2) + 1));
	
	return 0;		
}

앞에서 변환한 식이 성립하는지 확인해보는 예제이다.

 

실행 결과

 

 

 

 

배열 포인터와 포인터 배열

int *whoA[4];	// 포인터 배열
int (*whoB)[4];	// 배열 포인터

  소괄호( )의 유무에 따라 포인터 배열과 배열 포인터로 나뉘므로 주의한다. 위의 경우 ``whoA``는 ``int``형 포인터 변수로 이뤄진 ``int``형 포인터 배열이고 ``whoB``는 열 길이가 ``4``인 ``int``형 2차원 배열을 가리키는 용도의 포인터 변수이다.

 

#include <stdio.h>

int main(void)
{	
	int num1 = 10, num2 = 20, num3 = 30, num4 = 40;
	int arr2d[2][4] = {1, 2, 3, 4, 5, 6, 7, 8};

	int * whoA[4] = {&num1, &num2, &num3, &num4};	// 포인터 배열 
	int (*whoB)[4] = arr2d;	// 배열 포인터
	
	printf("%d %d %d %d %\n", *whoA[0], *whoA[1], *whoA[2], *whoA[3]);
	for(int i = 0 ; i < 2 ; i++){
		for(int j = 0 ; j < 4 ; j++){
			printf("%d ", whoB[i][j]);
		}
		printf("\n");
	} 
		
	return 0;
}

배열 포인터와 포인터 배열의 차이점을 보여주는 예시이다.

 

실행 결과

 

 

 

 

main함수의 인자

int main(void) {...}
int main(int argc, char * argv[]) {...}

  보통 ``main`` 함수를 첫 번째 줄처럼 선언하지만 두 번째 줄처럼 정의하는 것도 가능하다. 이렇게 하면 프로그램 실행 시 @@main@@함수로 전달할 인자를 열거할 수 있으며, @@main@@함수 역시 이러한 인자를 전달받을 수 있도록 제한된 형태의 매개변수 선언이 가능하다.

 

 

  ■ 인자의 형성 과정

#include <stdio.h>

int main(int argc, int * argv[])
{	
	int i = 0;
	printf("전달된 문자열의 수 : %d \n", argc);
	
	for(i = 0 ; i < argc ; i++)
		printf("%d번째 문자열 : %s \n", i + 1 , argv[i]);
	
	return 0;		
}

``main``함수에 인자를 넣는 예시이다. 

 

실행 결과
인자의 구성

  위와 같이 실행하면 main함수로 총 4개의 문자열 정보가 전달된다. 첫 번째 인자로는 문자열의 수가, 두 번째 인자로는 문자열 배열이 전달된다. 최종적으로 ``main(4, strArr)``의 형태로 ``main``함수가 호출된다.

  공백이 문자열을 나누는 기준이 되며(마찬가지로 문자열 배열(@@char *@@형 배열)이기 때문에 위의 그림처럼 각 문자열의 끝에 @@NULL(\0)@@문자가 들어가고, 배열의 마지막에도 @@NULL@@이 들어간다. "I Like You"와 같이 @@"@@로 묶으면 공백을 포함하는 문자열을 인자로 전달할 수 있다. 위의 예시에서 첫 번째 문자열(``argv[0]``)인 ``test``는 C 코드가 컴파일되고 난 후 생성된 exe 실행파일이다.(``test.exe``)

 

 

#include <stdio.h>

int main(int argc, int * argv[])
{	
	int i = 0;
	printf("전달된 문자열의 수 : %d \n", argc);
	
	while(argv[i] != NULL)
	{
		printf("%d번째 문자열 : %s \n", i + 1 , argv[i]);
		i++; 
	}	
	
	return 0;		
}

``argv[ ]``배열의 마지막에 ``NULL``이 삽입되는 것을 이용해서 ``argv[ ]``의 모든 문자열을 출력하는 예시이다.

 

실행 결과

``"``로 문자열을 묶으면 공백을 포함하는 문자열을 생성해서 ``main``함수의 인자로 전달하는 것이 가능하다.

 

 

 

  ■ char * argv[ ]

// 아래의 두 문장은 같은 의미
char * argv[]
char **argv	// argv[]는 *(argv + 0)이므로 변환가능

  ``main``함수의 매개변수인 ``* argv[ ]``를 변환하면 ``**argv``가 된다. 이는 @@argv@@가 @@char@@형 더블 포인터 변수이고 @@char@@형 포인터 변수로 이루어진 1차원 배열 이름을 전달받을 수 있는 매개변수라는 의미이다.

 

 

#include <stdio.h>

void ShowAllString(int argc, char * argv[])
{
	int i;
	for(i = 0 ; i < argc ; i++)
		printf("%s \n", argv[i]);
}

int main(int argc, char * argv[])
{	
	char * str[3] = {
				"C Programming",
				"C++ Programming",
				"JAVA Programming"
			};
	ShowAllString(3, str);
	
	return 0;
}

``argc``와 ``argv[ ]``를 이용하는 예시이다.

``argc``에 인자의 개수, ``argv``에 문자형 배열을 넣어서 실행시킨다.

 

실행 결과

 

 

 

'기타 > C' 카테고리의 다른 글

[C] 스트림, 버퍼  (0) 2020.05.27
[C] 함수 포인터, void 포인터  (0) 2020.05.22
[C]이중 포인터(더블 포인터)  (0) 2020.05.12
[C] 포인터와 배열, 함수  (0) 2020.05.07
[C] 포인터  (0) 2020.05.05

+ Recent posts