Topic 4: 변수의 범위와 생명주기 🌍
🎯 학습 목표
- 변수의 스코프와 생명주기 개념을 이해할 수 있다
- 지역변수, 전역변수, 정적변수의 차이점을 설명할 수 있다
- 변수 선언 위치에 따른 영향을 파악하고 적절히 활용할 수 있다
🌟 변수의 세계: 범위와 생명주기
“변수들은 각자의 영역에서 살아가며, 태어나고 사라지는 시간이 정해져 있어요!”
변수의 **범위(Scope)**와 **생명주기(Lifetime)**를 이해하는 것은 올바른 프로그램을 만드는 핵심입니다.
🏠 아파트 비유:
- 각 방(함수)에 있는 물건(지역변수) → 그 방에서만 사용 가능
- 공용 공간(전역) 물건(전역변수) → 모든 방에서 사용 가능
- 관리실 장부(정적변수) → 한 번 만들어지면 계속 보관
🏠 지역변수 (Local Variable)
1. 기본 지역변수
#include <iostream>
using namespace std;
void testFunction() {
int localVar = 10; // 지역변수 - 이 함수에서만 사용 가능
cout << "함수 내부 localVar: " << localVar << endl;
// 여기서 localVar는 유효함
localVar = 20;
cout << "변경된 localVar: " << localVar << endl;
} // 여기서 localVar는 소멸됨
int main() {
testFunction();
// cout << localVar << endl; // 오류! localVar는 여기서 접근 불가
return 0;
}
출력:
함수 내부 localVar: 10
변경된 localVar: 20
2. 블록 스코프 (Block Scope)
#include <iostream>
using namespace std;
int main() {
int outerVar = 100; // 바깥쪽 변수
cout << "바깥쪽 outerVar: " << outerVar << endl;
{ // 새로운 블록 시작
int innerVar = 200; // 블록 내부 변수
cout << "블록 내부 innerVar: " << innerVar << endl;
cout << "블록 내부에서 outerVar: " << outerVar << endl; // 접근 가능
outerVar = 150; // 바깥쪽 변수 수정 가능
} // innerVar는 여기서 소멸
cout << "수정된 outerVar: " << outerVar << endl;
// cout << innerVar << endl; // 오류! innerVar는 접근 불가
return 0;
}
출력:
바깥쪽 outerVar: 100
블록 내부 innerVar: 200
블록 내부에서 outerVar: 100
수정된 outerVar: 150
3. 반복문과 조건문에서의 스코프
#include <iostream>
using namespace std;
int main() {
// for 루프의 변수 스코프
for (int i = 0; i < 3; i++) {
int loopVar = i * 10; // 매번 새로 생성됨
cout << "반복 " << i << ": loopVar = " << loopVar << endl;
}
// cout << i << endl; // 오류! i는 for 루프에서만 유효
// cout << loopVar << endl; // 오류! loopVar도 접근 불가
// if 문의 변수 스코프
bool condition = true;
if (condition) {
int ifVar = 42;
cout << "if 블록 내부 ifVar: " << ifVar << endl;
if (ifVar > 40) {
int nestedVar = 100; // 중첩 블록의 변수
cout << "중첩 블록 nestedVar: " << nestedVar << endl;
}
// cout << nestedVar << endl; // 오류! 중첩 블록에서만 유효
}
// cout << ifVar << endl; // 오류! if 블록에서만 유효
return 0;
}
🌍 전역변수 (Global Variable)
1. 기본 전역변수
#include <iostream>
using namespace std;
// 전역변수 선언 - 모든 함수에서 접근 가능
int globalCounter = 0;
string globalMessage = "Hello Global!";
void incrementCounter() {
globalCounter++; // 전역변수 수정
cout << "함수에서 globalCounter: " << globalCounter << endl;
}
void displayMessage() {
cout << "전역 메시지: " << globalMessage << endl;
}
int main() {
cout << "초기 globalCounter: " << globalCounter << endl;
incrementCounter();
incrementCounter();
globalCounter += 10; // main에서도 접근 가능
cout << "main에서 globalCounter: " << globalCounter << endl;
displayMessage();
return 0;
}
출력:
초기 globalCounter: 0
함수에서 globalCounter: 1
함수에서 globalCounter: 2
main에서 globalCounter: 12
전역 메시지: Hello Global!
2. 전역변수와 지역변수 이름이 같은 경우
#include <iostream>
using namespace std;
int number = 100; // 전역변수
void testSameName() {
int number = 200; // 지역변수 (전역변수를 가림)
cout << "함수 내부 number: " << number << endl; // 지역변수 출력
cout << "전역 number: " << ::number << endl; // ::로 전역변수 접근
}
int main() {
cout << "main의 전역 number: " << number << endl;
testSameName();
{
int number = 300; // 또 다른 지역변수
cout << "블록 내부 number: " << number << endl;
cout << "전역 number: " << ::number << endl;
}
return 0;
}
출력:
main의 전역 number: 100
함수 내부 number: 200
전역 number: 100
블록 내부 number: 300
전역 number: 100
⚡ 정적변수 (Static Variable)
1. 정적 지역변수
#include <iostream>
using namespace std;
void countCalls() {
static int callCount = 0; // 정적 지역변수 - 한 번만 초기화
callCount++;
cout << "이 함수는 " << callCount << "번 호출되었습니다." << endl;
}
void normalFunction() {
int normalCount = 0; // 일반 지역변수 - 매번 초기화
normalCount++;
cout << "일반 변수 값: " << normalCount << endl;
}
int main() {
cout << "=== 정적변수 테스트 ===" << endl;
countCalls();
countCalls();
countCalls();
cout << "\n=== 일반변수 테스트 ===" << endl;
normalFunction();
normalFunction();
normalFunction();
return 0;
}
출력:
=== 정적변수 테스트 ===
이 함수는 1번 호출되었습니다.
이 함수는 2번 호출되었습니다.
이 함수는 3번 호출되었습니다.
=== 일반변수 테스트 ===
일반 변수 값: 1
일반 변수 값: 1
일반 변수 값: 1
2. 정적변수를 활용한 실전 예제
#include <iostream>
#include <string>
using namespace std;
// ID 생성기
int generateID() {
static int nextID = 1000; // 정적변수로 ID 관리
return nextID++;
}
// 사용자 생성 함수
string createUser(const string& name) {
static int userCount = 0; // 생성된 사용자 수 카운트
userCount++;
int userID = generateID();
cout << "[사용자 생성] 총 " << userCount << "명의 사용자가 생성되었습니다." << endl;
return "User" + to_string(userID) + "_" + name;
}
int main() {
cout << "=== 사용자 생성 시스템 ===" << endl;
string user1 = createUser("김철수");
cout << "생성된 사용자: " << user1 << endl << endl;
string user2 = createUser("이영희");
cout << "생성된 사용자: " << user2 << endl << endl;
string user3 = createUser("박민수");
cout << "생성된 사용자: " << user3 << endl << endl;
cout << "추가 ID 생성: " << generateID() << endl;
cout << "추가 ID 생성: " << generateID() << endl;
return 0;
}
출력:
=== 사용자 생성 시스템 ===
[사용자 생성] 총 1명의 사용자가 생성되었습니다.
생성된 사용자: User1000_김철수
[사용자 생성] 총 2명의 사용자가 생성되었습니다.
생성된 사용자: User1001_이영희
[사용자 생성] 총 3명의 사용자가 생성되었습니다.
생성된 사용자: User1002_박민수
추가 ID 생성: 1003
추가 ID 생성: 1004
🔄 변수의 생명주기 (Lifetime)
1. 생명주기 비교
#include <iostream>
using namespace std;
int globalVar = 1; // 프로그램 시작부터 끝까지 생존
void demonstrateLifetime() {
static int staticVar = 10; // 첫 호출 시 생성, 프로그램 끝까지 생존
int localVar = 100; // 함수 호출 시 생성, 함수 끝에서 소멸
cout << "=== 함수 내부 ===" << endl;
cout << "전역변수: " << globalVar++ << endl;
cout << "정적변수: " << staticVar++ << endl;
cout << "지역변수: " << localVar++ << endl;
{
int blockVar = 1000; // 블록 진입 시 생성, 블록 끝에서 소멸
cout << "블록변수: " << blockVar++ << endl;
}
// blockVar는 여기서 소멸됨
}
int main() {
cout << "프로그램 시작 - 전역변수: " << globalVar << endl;
cout << "\n첫 번째 호출:" << endl;
demonstrateLifetime();
cout << "\n두 번째 호출:" << endl;
demonstrateLifetime();
cout << "\n세 번째 호출:" << endl;
demonstrateLifetime();
cout << "\n프로그램 끝 - 전역변수: " << globalVar << endl;
return 0;
}
출력:
프로그램 시작 - 전역변수: 1
첫 번째 호출:
=== 함수 내부 ===
전역변수: 1
정적변수: 10
지역변수: 100
블록변수: 1000
두 번째 호출:
=== 함수 내부 ===
전역변수: 2
정적변수: 11
지역변수: 100
블록변수: 1000
세 번째 호출:
=== 함수 내부 ===
전역변수: 3
정적변수: 12
지역변수: 100
블록변수: 1000
프로그램 끝 - 전역변수: 4
🎮 실전 프로젝트: 간단한 점수 카운터
정적변수를 활용한 점수 시스템
#include <iostream>
using namespace std;
void addScore(int points) {
static int totalScore = 0; // 정적변수 - 점수 누적
static int gameCount = 0; // 정적변수 - 게임 횟수
totalScore += points;
gameCount++;
cout << "게임 " << gameCount << "회: " << points << "점 획득!" << endl;
cout << "누적 점수: " << totalScore << "점" << endl;
cout << "평균 점수: " << (totalScore / gameCount) << "점" << endl;
cout << "------------------------" << endl;
}
int main() {
cout << "=== 점수 카운터 시스템 ===" << endl;
// 여러 번 점수 추가
addScore(100);
addScore(150);
addScore(80);
addScore(200);
return 0;
}
출력:
=== 점수 카운터 시스템 ===
게임 1회: 100점 획득!
누적 점수: 100점
평균 점수: 100점
------------------------
게임 2회: 150점 획득!
누적 점수: 250점
평균 점수: 125점
------------------------
게임 3회: 80점 획득!
누적 점수: 330점
평균 점수: 110점
------------------------
게임 4회: 200점 획득!
누적 점수: 530점
평균 점수: 132점
------------------------
🔧 변수 범위 문제 해결하기
1. 변수 이름 충돌 해결
#include <iostream>
using namespace std;
int value = 100; // 전역변수
void demonstrateScopes() {
cout << "=== 스코프 해결 방법들 ===" << endl;
// 방법 1: :: 연산자로 전역변수 접근
int value = 200; // 지역변수
cout << "지역변수 value: " << value << endl;
cout << "전역변수 value: " << ::value << endl;
// 방법 2: 다른 이름 사용
int localValue = 300;
int globalValue = ::value;
cout << "명확한 이름 - localValue: " << localValue << endl;
cout << "명확한 이름 - globalValue: " << globalValue << endl;
// 방법 3: 블록으로 스코프 분리
{
int blockValue = 400;
cout << "블록 내부 blockValue: " << blockValue << endl;
// 여기서는 바깥쪽 value들에 접근 가능
cout << "블록에서 지역변수: " << value << endl;
cout << "블록에서 전역변수: " << ::value << endl;
}
}
int main() {
demonstrateScopes();
return 0;
}
2. 함수 매개변수와 지역변수
#include <iostream>
using namespace std;
int globalX = 10;
// 매개변수 이름이 전역변수와 같은 경우
void processValue(int globalX) { // 매개변수가 전역변수를 가림
int localY = 20;
cout << "매개변수 globalX: " << globalX << endl; // 매개변수 값
cout << "실제 전역 globalX: " << ::globalX << endl; // 전역변수 값
cout << "지역변수 localY: " << localY << endl;
// 매개변수 수정 (원본에는 영향 없음)
globalX = 999;
cout << "수정된 매개변수: " << globalX << endl;
cout << "전역변수는 그대로: " << ::globalX << endl;
}
void processReference(int& refX, int& refY) { // 참조로 받기
cout << "\n참조 매개변수:" << endl;
cout << "참조 refX: " << refX << endl;
cout << "참조 refY: " << refY << endl;
refX = 777; // 원본 수정됨
refY = 888;
}
int main() {
cout << "처음 전역 globalX: " << globalX << endl;
processValue(globalX);
cout << "함수 호출 후 전역 globalX: " << globalX << endl;
int localVar = 50;
processReference(globalX, localVar);
cout << "\n참조 처리 후:" << endl;
cout << "전역 globalX: " << globalX << endl;
cout << "지역 localVar: " << localVar << endl;
return 0;
}
💭 생각해보기: 스코프와 생명주기 퀴즈
Q1. 다음 코드의 출력 결과는?
#include <iostream>
using namespace std;
int x = 1;
void func() {
static int x = 2;
x++;
cout << x << " ";
}
int main() {
func();
func();
cout << x;
return 0;
}
💡 정답 확인
정답: 3 4 1
- 첫 번째
func()
호출: 정적변수x
가 2에서 3으로 증가 → 출력 “3 ” - 두 번째
func()
호출: 정적변수x
가 3에서 4로 증가 → 출력 “4 ” main
에서: 전역변수x
출력 → “1”
정적변수는 함수 호출 간에 값이 유지되고, 전역변수와는 별개입니다.
Q2. 다음 중 변수의 스코프 규칙으로 올바른 것은?
- 전역변수는 어디서든 접근할 수 있다
- 지역변수는 선언된 블록 내에서만 유효하다
- 정적변수는 프로그램 종료까지 살아있다
- 모든 설명이 맞다
💡 정답 확인
정답: 4번 - 모든 설명이 맞다
- 1번: ✅ 전역변수는 프로그램 전체에서 접근 가능
- 2번: ✅ 지역변수는 선언된 스코프 내에서만 유효
- 3번: ✅ 정적변수는 프로그램 시작부터 끝까지 생존
- 4번: ✅ 모든 설명이 정확함
🏆 연습 문제
다음 예제들을 직접 만들어보세요:
-
🔢 번호 생성기:
- 정적변수를 사용해 호출할 때마다 증가하는 번호 반환
- 함수:
getNextNumber()
-
📊 방문자 카운터:
- 전역변수: 사이트 이름
- 정적변수: 총 방문자 수
- 함수:
visitPage()
- 방문자 수 증가 및 출력
-
🎯 함수 호출 추적:
- 정적변수: 각 함수의 호출 횟수
- 여러 함수에서 각각 호출 횟수 관리
💡 힌트:
// 예시: 번호 생성기
int getNextNumber() {
static int counter = 1000; // 정적변수 - 처음 한 번만 초기화
return counter++; // 현재 값 반환 후 증가
}
// 예시: 방문자 카운터
void visitPage() {
static int visitors = 0; // 정적변수 - 방문자 수
visitors++;
cout << "방문자 수: " << visitors << "명" << endl;
}
🎯 전체 예제 코드
#include <iostream>
#include <string>
using namespace std;
// 전역변수 - 사이트 정보
string siteName = "My Website";
// 번호 생성기
int getNextNumber() {
static int counter = 1000;
return counter++;
}
// 방문자 카운터
void visitPage() {
static int visitors = 0;
visitors++;
cout << siteName << " 방문자: " << visitors << "명" << endl;
}
// 함수별 호출 추적
void functionA() {
static int callCount = 0;
callCount++;
cout << "함수 A 호출 횟수: " << callCount << endl;
}
void functionB() {
static int callCount = 0;
callCount++;
cout << "함수 B 호출 횟수: " << callCount << endl;
}
int main() {
cout << "=== 정적변수 활용 예제 ===\n" << endl;
// 번호 생성기 테스트
cout << "생성된 번호: " << getNextNumber() << endl;
cout << "생성된 번호: " << getNextNumber() << endl;
cout << "생성된 번호: " << getNextNumber() << endl;
cout << endl;
// 방문자 카운터 테스트
visitPage();
visitPage();
visitPage();
cout << endl;
// 함수 호출 추적 테스트
functionA();
functionB();
functionA();
functionB();
functionB();
return 0;
}
출력:
=== 정적변수 활용 예제 ===
생성된 번호: 1000
생성된 번호: 1001
생성된 번호: 1002
My Website 방문자: 1명
My Website 방문자: 2명
My Website 방문자: 3명
함수 A 호출 횟수: 1
함수 B 호출 횟수: 1
함수 A 호출 횟수: 2
함수 B 호출 횟수: 2
함수 B 호출 횟수: 3
Last updated on