1. 변수의 영역과 데이터 공유
변수 사용 영역
- 지역 변수는 함수나 블록 내에서 선언되고 사용되는 변수로, 해당 블록이 종료되면 메모리에서 사라진다.
- 지역 변수는 함수나 코드 블록 내에서만 접근할 수 있어 데이터의 가시성을 제한하는 데 유용하다.
void example() {
int localVar = 10; // 지역 변수
printf("지역 변수: %d\n", localVar);
}
- 지역 변수는 {}로 감싸진 블록 안에서만 유효하다.
- 중첩된 블록에서 동일한 이름의 변수를 선언하면 블록 안의 변수가 우선적으로 사용된다.
void nestedBlockExample() {
int num = 5;
{
int num = 10; // 중첩된 블록 내의 지역 변수
printf("중첩 블록 내 num: %d\n", num); // 출력: 10
}
printf("외부 블록 내 num: %d\n", num); // 출력: 5
}
- 전역 변수는 함수 외부에서 선언된 변수로, 프로그램 전체에서 접근이 가능하다.
- 전역 변수는 메모리에서 프로그램이 종료될 때까지 유지된다.
int globalVar = 20; // 전역 변수
void printGlobalVar() {
printf("전역 변수: %d\n", globalVar);
}
- 정적 지역 변수는 static 키워드를 사용하여 선언되며, 함수가 호출될 때마다 초기화되지 않고 이전 값을 유지한다.
- 이러한 변수는 블록 내에서만 접근할 수 있지만, 메모리 상에서는 프로그램 종료 시까지 유지된다.
void staticExample() {
static int count = 0; // 정적 지역 변수
count++;
printf("count: %d\n", count);
}
- 레지스터 변수는 CPU의 레지스터에 저장되기를 요청하는 변수로, 빠른 접근이 필요할 때 사용한다.
- 컴파일러는 레지스터에 저장할지 여부를 결정하므로, 반드시 레지스터에 저장된다고 보장할 수는 없다.
void registerExample() {
register int i = 0;
for (; i < 10; i++) {
printf("%d ", i);
}
}
함수의 데이터 공유 방법
- 함수에 인수를 전달할 때 기본적으로 값이 복사되어 전달된다.
- 이 경우 함수 내에서 매개변수의 값을 변경해도 원본 변수에는 영향을 주지 않는다.
void passByValue(int a) {
a = 20;
}
int main() {
int num = 10;
passByValue(num);
printf("num: %d\n", num); // 출력: 10 (값이 변경되지 않음)
return 0;
}
- 주소를 전달하면 함수에서 직접 원본 변수의 값을 변경할 수 있다.
- 이를 포인터를 통해 구현한다.
void passByReference(int *p) {
*p = 20;
}
int main() {
int num = 10;
passByReference(&num);
printf("num: %d\n", num); // 출력: 20 (값이 변경됨)
return 0;
}
- 함수에서 주소를 반환하여, 다른 함수에서도 해당 메모리의 값을 참조할 수 있다.
- 단, 함수 내부에서 선언된 지역 변수의 주소를 반환하면 안 된다.
- 이는 함수가 종료되면서 메모리에서 사라지기 때문이다.
int* returnPointer(int *a) {
*a += 10;
return a;
}
int main() {
int num = 30;
int *p = returnPointer(&num);
printf("num: %d\n", *p); // 출력: 40
return 0;
}
2. 다차원 배열과 포인터 배열
다차원 배열
- 2차원 배열은 행과 열로 이루어져 있으며, 선언할 때 각 차원의 크기를 지정해야 한다.
int matrix[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
matrix[i][j] = i * j;
printf("matrix[%d][%d] = %d\n", i, j, matrix[i][j]);
}
}
- 2차원 배열을 선언과 동시에 초기화할 수 있다.
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
- 2차원 char 배열은 여러 개의 문자열을 저장할 수 있다.
char names[3][10] = {
"Alice",
"Bob",
"Charlie"
};
- 문자열을 2차원 배열에 초기화하여 여러 문자열을 관리할 수 있다.
for (int i = 0; i < 3; i++) {
printf("%s\n", names[i]);
}
- 3차원 배열은 3개의 차원을 가지며, 주로 여러 개의 2차원 배열을 관리할 때 사용된다.
int cube[2][3][4]; // 2층, 3행, 4열의 3차원 배열
포인터 배열
- 포인터 배열은 여러 개의 포인터를 저장하는 배열로, 각 포인터는 다른 변수나 배열을 가리킬 수 있다.
int a = 10, b = 20, c = 30;
int *arr[3] = {&a, &b, &c};
for (int i = 0; i < 3; i++) {
printf("arr[%d]가 가리키는 값: %d\n", i, *arr[i]);
}
- 포인터 배열을 사용하면 2차원 배열처럼 데이터를 관리할 수 있다.
char *messages[3] = {
"Hello",
"World",
"C Language"
};
for (int i = 0; i < 3; i++) {
printf("%s\n", messages[i]);
}
3. 응용 포인터
이중 포인터와 배열 포인터
- 이중 포인터는 포인터를 가리키는 포인터로, 포인터의 주소를 저장한다.
- 이중 포인터는 다차원 배열이나 동적 메모리 할당 시 유용하게 사용된다.
int num = 10;
int *p = #
int **pp = &p;
printf("num: %d, *p: %d, **pp: %d\n", num, *p, **pp); // 출력: 10, 10, 10
- 이중 포인터를 사용하면 함수 내에서 포인터 변수 자체의 값을 변경할 수 있다.
void changePointer(int **pp) {
static int newValue = 50;
*pp = &newValue;
}
int main() {
int value = 10;
int *p = &value;
changePointer(&p);
printf("p가 가리키는 값: %d\n", *p); // 출력: 50
return 0;
}
- 이중 포인터를 사용하여 포인터 배열을 함수의 매개변수로 전달할 수 있다.
void printMessages(char **messages, int size) {
for (int i = 0; i < size; i++) {
printf("%s\n", messages[i]);
}
}
int main() {
char *messages[] = {"Hello", "World", "C Programming"};
printMessages(messages, 3);
return 0;
}
- 배열의 요소와 배열 자체는 다른 주소를 가진다.
- 배열의 이름은 배열의 첫 번째 요소의 주소이지만, 배열의 주소와는 다르다.
int arr[5] = {1, 2, 3, 4, 5};
printf("arr: %p, &arr: %p\n", (void *)arr, (void *)&arr); // 출력: 서로 다른 주소
- 2차원 배열을 가리키는 포인터는 배열 포인터를 사용하여 선언할 수 있다.
int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
int (*pMatrix)[3] = matrix;
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", pMatrix[i][j]);
}
printf("\n");
}
- 2차원 배열에서 요소를 참조할 때는 행과 열의 인덱스를 사용하며, 배열 포인터를 통해 쉽게 접근할 수 있다.
함수 포인터와 void 포인터
- 함수 포인터는 함수의 주소를 저장하는 포인터로, 함수 호출을 동적으로 제어할 수 있다.
- 함수 포인터를 사용하면 코드의 유연성을 높일 수 있다.
int add(int a, int b) {
return a + b;
}
int main() {
int (*funcPtr)(int, int) = add;
printf("두 수의 합: %d\n", funcPtr(5, 7)); // 출력: 12
return 0;
}
- 함수 포인터는 콜백 함수나 동적 함수 호출 등에 사용된다.
- 이를 통해 함수의 호출 방식을 유연하게 구성할 수 있다.
- void 포인터는 어떤 자료형의 포인터든 가리킬 수 있는 범용 포인터이다.
- 하지만 역참조를 위해서는 명시적으로 형변환이 필요하다.
void printValue(void *ptr, char type) {
switch (type) {
case 'i':
printf("정수 값: %d\n", *(int *)ptr);
break;
case 'f':
printf("실수 값: %f\n", *(float *)ptr);
break;
}
}
int main() {
int num = 42;
float fnum = 3.14;
printValue(&num, 'i'); // 출력: 정수 값: 42
printValue(&fnum, 'f'); // 출력: 실수 값: 3.140000
return 0;
}
'IT & AI > AI 지식' 카테고리의 다른 글
JAVA 초보자를 위한 변수, 연산자, 데이터 입출력 이해 (1) | 2024.11.20 |
---|---|
C 언어 메모리 동적 할당, 구조체, 파일 입출력 (1) | 2024.11.20 |
C 언어 배열, 포인터, 문자열 활용 심화 (0) | 2024.11.19 |
C 언어 함수, 배열, 포인터 사용법과 예제 (0) | 2024.11.18 |
C 언어 연산자와 제어문 총정리 (0) | 2024.11.18 |