C++의 입출력 방식은 C 언어와 다릅니다. C++에서는 <iostream> 헤더와 std 네임스페이스를 사용하여 cout과 cin을 이용합니다.
#include <iostream>
using namespace std;
int main() {
cout << "Hello, World!" << endl;
return 0;
}
네임스페이스 (Namespace)
네임스페이스는 이름 충돌을 방지하기 위해 사용됩니다. 전역 공간에 너무 많은 변수, 함수, 클래스가 존재하면 이름이 중복될 수 있는데, 네임스페이스는 이러한 충돌을 줄여줍니다. C 언어에서는 이러한 이름 충돌 문제가 발생하기 쉽습니다.
#include <stdio.h>
int rand = 10; // stdlib.h의 rand 함수와 이름 충돌
int main() {
// 컴파일 오류: rand는 이미 선언되었습니다.
// printf("%d\n", rand);
return 0;
}
네임스페이스 정의
namespace 키워드를 사용하여 네임스페이스를 정의합니다.
namespace MyNamespace {
int value = 10;
int add(int a, int b) {
return a + b;
}
}
네임스페이스는 전역, 함수 지역, 클래스 등 다양한 범위(scope) 내에서 독립적으로 작용합니다. C++ 표준 라이브러리는 std라는 네임스페이스 안에 있습니다.
네임스페이스 사용
네임스페이스 멤버에 접근하려면 네임스페이스이름::멤버이름 형식을 사용합니다.
#include <stdio.h>
namespace UserData {
int count = 5;
int calculateSum(int x, int y) {
return x + y;
}
}
int main() {
printf("%d\n", UserData::count);
printf("%d\n", UserData::calculateSum(3, 7));
return 0;
}
중첩 네임스페이스 사용:
namespace Outer {
namespace Inner {
int data = 100;
}
}
int main() {
printf("%d\n", Outer::Inner::data);
return 0;
}
여러 파일에 걸쳐 동일한 이름의 네임스페이스를 정의하면 자동으로 병합됩니다.
네임스페이스 멤버 접근 방법
- 명시적 지정:
NamespaceName::member(권장) using선언:using NamespaceName::member;(특정 멤버만 사용)using namespace:using namespace NamespaceName;(모든 멤버 사용, 이름 충돌 위험)
#include <iostream>
namespace Example {
int var1 = 10;
int var2 = 20;
}
int main() {
// 1. 명시적 지정
std::cout << Example::var1 << std::endl;
// 2. using 선언
using Example::var2;
std::cout << var2 << std::endl;
// 3. using namespace (주의: 프로젝트에서는 권장하지 않음)
using namespace Example;
std::cout << var1 << std::endl; // var1은 이미 선언되었으므로 충돌 가능성 있음
return 0;
}
C++ 입출력 (iostream)
iostream은 표준 입출력 스트림 라이브러리입니다.
std::cin: 표준 입력 스트림 객체.std::cout: 표준 출력 스트림 객체.std::endl: 줄바꿈 문자를 출력하고 버퍼를 플러시합니다.<<: 스트림 삽입 연산자 (output).>>: 스트림 추출 연산자 (input).
C++ 입출력은 형식 지정이 필요 없고, 사용자 정의 타입도 지원합니다.
#include <iostream>
int main() {
int num;
double pi;
char initial;
std::cout << "정수, 실수, 문자를 입력하세요: ";
std::cin >> num >> pi >> initial;
std::cout << "입력된 값: " << num << ", " << pi << ", " << initial << std::endl;
// 입출력 속도 향상 (경쟁 프로그래밍 등에서 유용)
std::ios_base::sync_with_stdio(false);
std::cin.tie(nullptr);
std::cout.tie(nullptr);
return 0;
}
기본값 매개변수 (Default Parameter)
함수 선언 시 매개변수에 기본값을 지정할 수 있습니다. 함수 호출 시 해당 매개변수에 대한 실인수가 제공되지 않으면 기본값이 사용됩니다.
- 전체 기본값: 모든 매개변수에 기본값 지정.
- 부분 기본값: 일부 매개변수에 기본값 지정. 이때, 부분 기본값은 오른쪽부터 순차적으로 지정해야 합니다.
#include <iostream>
// 전체 기본값
void printValues(int a = 1, int b = 2, int c = 3) {
std::cout << "a: " << a << ", b: " << b << ", c: " << c << std::endl;
}
// 부분 기본값 (b와 c에 기본값 지정)
void processData(int x, int y = 10, int z = 20) {
std::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;
}
int main() {
printValues(); // 1, 2, 3
printValues(10); // 10, 2, 3
printValues(10, 20); // 10, 20, 3
printValues(10, 20, 30); // 10, 20, 30
processData(100); // 100, 10, 20
processData(100, 200); // 100, 200, 20
// processData(100, , 300); // 불가능: 부분 기본값은 순차적으로 지정해야 함
return 0;
}
함수 선언과 정의가 분리될 경우, 기본값은 함수 선언부에 지정해야 합니다.
함수 오버로딩 (Function Overloading)
동일한 범위 내에서 동일한 이름의 함수를 여러 개 정의하는 기능입니다. 단, 함수의 매개변수 목록(개수 또는 타입, 타입 순서)이 달라야 합니다. 반환 타입만 다른 것은 오버로딩으로 인정되지 않습니다.
#include <iostream>
// 매개변수 타입이 다른 오버로딩
int calculate(int a, int b) {
std::cout << "Using int calculate(int, int)" << std::endl;
return a + b;
}
double calculate(double a, double b) {
std::cout << "Using double calculate(double, double)" << std::endl;
return a + b;
}
// 매개변수 개수가 다른 오버로딩
void display() {
std::cout << "Using void display()" << std::endl;
}
void display(int value) {
std::cout << "Using void display(int)" << std::endl;
}
// 매개변수 타입 순서가 다른 오버로딩
void printInfo(int id, const char* name) {
std::cout << "ID: " << id << ", Name: " << name << std::endl;
}
void printInfo(const char* name, int id) {
std::cout << "Name: " << name << ", ID: " << id << std::endl;
}
int main() {
calculate(5, 10);
calculate(5.5, 10.1);
display();
display(100);
printInfo(1, "Alice");
printInfo("Bob", 2);
return 0;
}
참조 (Reference)
참조는 기존 변수에 대한 별명(alias)입니다. 새로운 변수를 만드는 것이 아니라, 기존 변수를 참조하는 것입니다. 참조 변수는 원본 변수와 동일한 메모리 공간을 공유합니다.
- 참조는 정의 시 반드시 초기화해야 합니다.
- 하나의 변수는 여러 참조를 가질 수 있습니다.
- 참조는 한 번 초기화되면 다른 변수를 참조하도록 변경할 수 없습니다 (실제로는 복사/할당이 일어납니다).
#include <iostream>
int main() {
int original = 10;
int& ref1 = original; // ref1은 original의 별명
int& ref2 = original; // ref2도 original의 별명
ref1 = 20; // original의 값이 20으로 변경됩니다.
std::cout << "Original: " << original << ", Ref1: " << ref1 << ", Ref2: " << ref2 << std::endl;
int another = 30;
ref1 = another; // 이것은 ref1이 another를 참조하게 되는 것이 아니라, ref1(original)에 another의 값을 복사하는 것입니다.
std::cout << "Original: " << original << ", Ref1: " << ref1 << ", Another: " << another << std::endl;
std::cout << "Address of original: " << &original << std::endl;
std::cout << "Address of ref1: " << &ref1 << std::endl; // original과 동일한 주소
std::cout << "Address of ref2: " << &ref2 << std::endl; // original과 동일한 주소
return 0;
}
참조의 활용
- 참조를 이용한 매개변수 전달: 함수 호출 시 객체 복사를 피하여 효율성을 높입니다. 포인터 전달과 유사한 효과를 내지만, 문법이 더 간결합니다.
- 참조를 이용한 반환: 함수에서 객체 자체를 반환할 때 복사를 피할 수 있습니다. (주의: 함수 내 지역 변수를 참조로 반환하면 안 됩니다.)
#include <iostream>
#include <vector>
// 참조를 이용한 값 교환
void swapValues(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
// 참조를 이용한 벡터 요소 추가 (효율성 증대)
void pushBackElement(std::vector<int>& vec, int value) {
vec.push_back(value);
}
// 참조를 반환하는 함수 (주의: 함수 내 지역 변수를 반환하지 않도록 함)
int& getElementByIndex(std::vector<int>& vec, size_t index) {
return vec[index];
}
int main() {
int x = 10, y = 20;
std::cout << "Before swap: x=" << x << ", y=" << y << std::endl;
swapValues(x, y);
std::cout << "After swap: x=" << x << ", y=" << y << std::endl;
std::vector<int> data = {1, 2, 3};
pushBackElement(data, 4);
for (int val : data) {
std::cout << val << " ";
}
std::cout << std::endl;
getElementByIndex(data, 0) = 100; // 참조 반환을 이용한 값 변경
std::cout << "First element after modification: " << data[0] << std::endl;
return 0;
}
포인터 변수에 대한 참조를 사용하여 이중 포인터 없이 함수 내에서 포인터 자체를 변경할 수 있습니다.
#include <iostream>
struct Node {
int data;
Node* next;
};
// 이중 포인터를 사용하지 않고 헤드 포인터 변경
void insertNode(Node*& head, int value) {
Node* newNode = new Node{value, nullptr};
newNode->next = head;
head = newNode;
}
int main() {
Node* listHead = nullptr;
insertNode(listHead, 10);
insertNode(listHead, 20);
Node* current = listHead;
while (current) {
std::cout << current->data << " -> ";
current = current->next;
}
std::cout << "NULL" << std::endl;
// 메모리 해제 (간략화)
while(listHead) {
Node* temp = listHead;
listHead = listHead->next;
delete temp;
}
return 0;
}