c 공부 2024.03.25
03.22 과제
정처기 실기 문제 아무언어로 문제하나를 해석
1
2
3
4
5
a = "REMEMBER NOVEMBER"
b = a[:3] + a[12:15];
c = "R AND %s" % "STR"
print(b+c)
문제 풀이
a = “REMEMBER NOVEMBER” (문자열 “REMEMBER NOVEMBER”를 변수 a에 할당).
b = a[:3] + a[12:15]: 문자열 슬라이싱을 사용하여 변수 a의 첫 3글자(“REM”)와 13번째부터 15번째 글자까지(“NOV”)를 잘라내어 이어 붙인 결과를 변수 b에 할당. 결과적으로 b의 값은 “REMNOV”가 됨.
c = “R AND %s” % “STR”: 문자열 포매팅을 사용하여 “%s”를 “STR”로 대체하고, 그 결과를 변수 c에 할당. 따라서 c의 값은 “R AND STR”이 됨.
- print(b+c): 변수 b와 c의 값을 이어붙인 결과를 출력합니다. b의 값은 “REMNOV”이고, c의 값은 “R AND STR”이므로, 최종적으로 “REMNOVR AND STR”이 출력됨.
- 최종 출력 : “REMNOVR AND STR”
03.25 공부
포인터란?
포인터는 메모리 주소를 저장하는 변수로, C언어에서 데이터를 효율적으로 관리하고 함수 간에 데이터를 공유할 수 있게 해줌
포인터의 사용 이유
메모리의 효율적 사용: 직접 메모리 주소에 접근하여 작은 메모리 공간을 효율적으로 사용할 수 있음.
함수 간 데이터 전달: 함수에 변수의 주소를 전달함으로써, 복사본을 생성하지 않고 원본 데이터를 함수 간에 공유할 수 있음.
동적 메모리 할당: 실행 시간에 메모리 크기를 결정할 수 있어, 필요한 만큼의 메모리를 할당하고 해제할 수 있음.
포인터의 기본 사용법
변수의 주소를 가져오기 위해 주소 연산자 &를 사용하고, 포인터를 선언하기 위해서는 변수 타입 앞에 _를 붙임. 주소 연산자 &: 변수의 메모리 주소를 얻는 데 사용됨. 역참조 연산자 _: 포인터가 가리키는 메모리의 값을 접근하는 데 사용됨.
포인터 예제 코드 변수의 주소 출력:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
int main() {
int a;
double b;
char c;
// 변수의 주소 출력
printf("int형 변수의 주소: %p\n", (void*)&a);
printf("double형 변수의 주소: %p\n", (void*)&b);
printf("char형 변수의 주소: %p\n", (void*)&c);
return 0;
}
포인터를 사용하여 값 할당 및 접근:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <stdio.h>
int main() {
int a = 10;
int *pa; // int형 포인터 선언
pa = &a; // a의 주소를 pa에 할당
// 포인터를 통해 a의 값 접근
printf("pa가 가리키는 주소의 값: %d\n", *pa);
printf("pa의 값 (즉, a의 주소): %p\n", (void*)pa);
return 0;
}
포인터와 const, volatile, restrict
const: 포인터가 가리키는 값의 변경을 금지.
volatile: 컴파일러 최적화에서 변수를 제외하도록 함. 외부에서 예기치 않게 변경될 수 있는 변수에 사용됨.
restrict: 포인터가 가리키는 데이터가 오직 해당 포인터를 통해서만 접근될 것임을 명시. 최적화를 위해 사용됨.
구조체와 포인터 C언어의 구조체는 데이터를 그룹화하는 방법을 제공하며, 객체지향 프로그래밍의 클래스와 유사한 역할을 함. 포인터와 함께 사용하면 구조체의 메모리 주소에 직접 접근하여 구조체 내의 데이터를 효율적으로 관리할 수 있음
구조체 예제:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
struct student {
int id;
char name[50];
float percentage;
};
int main() {
struct student record = {1, "John", 90.5};
struct student *ptr;
ptr = &record;
printf("ID는: %d \n", ptr->id);
printf("이름은: %s \n", ptr->name);
printf("백분율은: %f \n", ptr->percentage);
return 0;
}
위 코드에서 ptr->id, ptr->name, ptr->percentage는 각각 구조체 record의id, name, percentage 필드에 접근하는 방법을 보여줌. 포인터 ptr은 구조체 record의 주소를 가리키고 있으며, -> 연산자를 통해 해당 구조체의 필드에 접근할 수 있다.
구조체와 포인터의 고급 사용
구조체와 포인터를 함께 사용하면 다음과 같은 복잡한 구조도 표현할 수 있음:
- 구조체 배열에 대한 포인터
- 포인터를 포함하는 구조체
- 구조체 포인터를 포함하는 구조체
구조체 배열에 대한 포인터
구조체 배열에 대한 포인터를 선언하여, 배열의 각 요소(구조체 인스턴스)에 접근할 수 있음.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
struct student {
int id;
char name[50];
};
int main() {
struct student stdArray[2] = { {1, "John"}, {2, "Jane"}};
struct student *ptr;
ptr = &stdArray[0]; // 배열의 첫 번째 요소의 주소를 ptr에 할당
for(int i = 0; i < 2; i++) {
printf("ID: %d, Name: %s\n", (ptr+i)->id, (ptr+i)->name);
}
return 0;
}
포인터를 포함하는 구조체
구조체 내에서 다른 구조체를 가리키는 포인터를 선언할 수 있음. 이를 통해, 예를 들어, 연결 리스트와 같은 데이터 구조를 구현할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
struct node {
int data;
struct node *next;
};
int main() {
struct node n1, n2, n3;
n1.data = 1;
n2.data = 2;
n3.data = 3;
// 노드 연결
n1.next = &n2;
n2.next = &n3;
n3.next = NULL;
// 연결 리스트 순회
struct node *ptr = &n1;
while(ptr != NULL) {
printf("Data: %d\n", ptr->data);
ptr = ptr->next;
}
return 0;
}
구조체 포인터를 포함하는 구조체
구조체 내에서 다른 구조체를 가리키는 포인터를 선언하여, 복잡한 계층적 구조를 표현할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <stdio.h>
struct address {
char city[50];
char state[50];
};
struct person {
char name[50];
struct address *addr;
};
int main() {
struct address addr1 = {"New York", "NY"};
struct person person1 = {"John Doe", &addr1};
printf("Name: %s, City: %s, State: %s\n", person1.name, person1.addr->city, person1.addr->state);
return 0;
}
정처기 문제
첫번째
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <stdio.h>
int main()
{
// 정수형 포인터 배열 arr 선언. 이 배열은 정수의 주소를 저장할 수 있습니다.
int* arr[3];
// 정수형 변수 a, b, c를 선언하고 각각의 값을 초기화합니다.
int a = 12, b = 24, c = 36;
// arr 배열의 첫 번째 요소에 a의 주소를 저장합니다.
arr[0] = &a;
// arr 배열의 두 번째 요소에 b의 주소를 저장합니다.
arr[1] = &b;
// arr 배열의 세 번째 요소에 c의 주소를 저장합니다.
arr[2] = &c;
// 포인터 배열의 두 번째 요소인 b의 값을 역참조하여 얻은 뒤 (*arr[1]),
// 포인터 배열의 첫 번째 요소인 a의 값을 역참조하여 얻습니다 (**arr).
// 여기에 1을 더합니다.
// 결과적으로 b의 값(24) + a의 값(12) + 1이 되어
// printf를 통해 37을 출력하게 됩니다.
printf("%d", *arr[1] + **arr + 1);
return 0;
}
두번째
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <stdio.h>
// 'jsu'라는 이름의 구조체를 만듭니다. 이 구조체에는 이름, 두 과목의 점수, 두 점수의 합, 그리고 추가 계산을 위한 값이 들어 있습니다.
struct jsu {
char name[12]; // 이름을 저장할 문자 배열입니다. 최대 11글자까지 저장할 수 있습니다.
int os, db, hap, hhap; // os와 db는 과목 점수, hap은 두 과목 점수의 합, hhap는 추가 계산을 위한 값입니다.
}; // 세미콜론으로 구조체 선언을 끝냅니다.
int main()
{
// 'st'라는 이름의 'jsu' 구조체 배열을 선언하고 초기화합니다. 배열에는 3개의 요소가 있습니다.
struct jsu st[3] = {
{"데이터1", 95, 88}, // 첫 번째 요소: 이름은 "데이터1", os 점수는 95, db 점수는 88
{"데이터2", 84, 91}, // 두 번째 요소: 이름은 "데이터2", os 점수는 84, db 점수는 91
{"데이터3", 86, 75} // 세 번째 요소: 이름은 "데이터3", os 점수는 86, db 점수는 75
};
// 'p'라는 포인터를 선언하고 'st' 배열의 첫 번째 요소의 주소로 초기화합니다.
struct jsu* p;
p = &st[0];
// "데이터2"라는 이름의 성적을 계산.
// 'hap'은 국어(os) 점수와 다음 이름("데이터3")의 수학(db) 점수를 더한 값.
(p + 1) -> hap = (p + 1)->os + (p + 2)->db;
// 이제 "데이터2" 의 'hhap'을 구하기. 'hhap'은 방금 구한 'hap'에
// 첫 번째 이름("데이터1")의 국어(os) 점수와 수학(db) 점수를 더한 값.
(p + 1) -> hhap = (p + 1)->hap + p->os + p->db;
// 마지막으로, "데이터2"의 'hap'과 'hhap'을 더한 값을 출력함.
printf("%d", (p + 1) -> hap + (p + 1) -> hhap);
return 0;
}
세번째
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
// 2행 3열의 2차원 배열을 선언하고 초기화합니다.
int arr[2][3] = { {1, 2, 3}, {4, 5, 6}};
int (*p)[3] = NULL; // 포인터 p를 선언하고 NULL로 초기화합니다.
// 위의 상태에서는 NULL NULL NULL
p = arr; // 포인터 p에 배열 arr의 주소를 할당합니다.
//123 / 456 / NULL
// 첫 번째 행의 두 번째 열 요소와 두 번째 행의 세 번째 열 요소의 합을 출력합니다.
// arr[0][1] + arr[1][2] = 2 + 6 = 8
printf("%d, ", *(p[0] + 1) + *(p[1] + 2));
// 두 번째 행의 첫 번째 열 요소와 두 번째 행의 두 번째 열 요소의 합을 출력합니다.
// arr[1][0] + arr[1][1] = 4 + 5 = 9
printf("%d", *(*(p + 1) + 0) + *(*(p + 1) + 1));
return 0;
}
int (*p)[3]는 3개의 int를 포함하는 배열을 가리키는 포인터를 선언.
즉, p는 int[3] 배열을 가리키는 포인터.
이는 p가 한 번에 3개의 정수를 담고 있는 배열 전체를 가리키고,
이런 배열이 여러 개 있을 때 각각을 가리킬 수 있다는 의미.p = arr;에서 p는 arr의 첫 번째 배열(즉, {1, 2, 3})을 가리키게 됨. arr 자체가 int[2][3] 타입이므로,
p는 이중 배열의 첫 번째 ‘행’을 가리키는 것으로 이해할 수 있음.*(p[0] + 1)과 *(p[1] + 2)는 포인터 연산과 역참조를 사용하여 배열의 특정 요소에 접근.
여기서 p[0]는 배열의 첫 번째 ‘행’을, p[1]은 두 번째 ‘행’을 나타냄.
그리고 각각의 + 1과 + 2는 해당 ‘행’에서의 열 인덱스를 나타냄. 따라서 *(p[0] + 1)는 첫 번째 ‘행’의 두 번째 열에 있는 요소(값 2)를,
*(p[1] + 2)는 두 번째 ‘행’의 세 번째 열에 있는 요소(값 6)를 참조.((p + 1) + 0)과 ((p + 1) + 1)도 마찬가지 방식으로 작동.
여기서 *(p + 1)은 두 번째 ‘행’을 가리키며, 그 다음 + 0과 + 1은 그 ‘행’에서 첫 번째와 두 번째 열을 가리킴.p는 단일 포인터이지만, 이 포인터를 통해 int[3] 배열을 가리키고,
이러한 배열이 여러 개 있을 때 각 배열의 시작점을 가리킬 수 있음.
따라서 p, p + 1, p + 2 등을 통해 여러 ‘행’에 접근할 수 있음.
컴파일에 따라 다름 *을 int뒤에 붙이든지 arr앞에 붙이든지 그치만 결국 같은 포인터 선언을 하는거임
정리
포인터와 구조체를 이용하면 C언어에서 매우 유연하고 강력한 데이터 구조를 설계할 수 있음. 이러한 기능은 메모리 관리, 데이터 구조 구현, 시스템 프로그래밍 등 다양한 분야에서 활용함. 포인터와 구조체의 이해는 C언어 뿐만 아니라 프로그래밍 전반에 걸쳐 중요한 기초를 다지는 데 도움이 됨.