cpp 기초
Cpp 기본 문법
- C와 다른 기본사항
- 디폴트 파라미터
- 함수 오버로딩
- 레퍼런스
- iostream
cpp 확장자 : cpp 문법 기준으로 이 파일이 컴파일 됩니다.
modern은 기본적으로 알고있는게 좋아요. : 최소, modern cpp 11, 14 나머지는 회사 일하면서 익히는게 차라리 나아보여요
C와 다른 기본사항 (C++에서 새로 생긴/강화된 개념)
C++은 C를 기반으로 하지만 객체지향과 타입 시스템, 표준 라이브러리 등이 확장되어 있습니다. 초보자가 알아두면 좋은 차이점:
- 네임스페이스(namespace)
- 전역 이름 충돌을 피하기 위해
std::같은 네임스페이스를 사용합니다. - 예:
std::cout,std::string
- 전역 이름 충돌을 피하기 위해
- 입출력 스트림(iostream) vs C의 stdio
- C++은
cin,cout,cerr같은 스트림을 제공합니다. 타입 안전하고 C++ 스타일 입출력에 적합합니다.
- C++은
- 참조(Reference)
- C에는 없는 개념으로, 포인터보다 안전하게 변수의 별칭을 만들 수 있습니다.
- 함수 오버로딩, 디폴트 파라미터
- 같은 이름의 함수를 여러 시그니처로 정의할 수 있습니다. 파라미터에 기본값을 줄 수 있습니다.
- 객체지향과 생성자/소멸자, 클래스
- 클래스, 접근 제어( public/private/protected ), 생성자/소멸자, 연산자 오버로딩 등.
- bool 타입
- C++에는
bool타입이 있어 참/거짓을 명확하게 표현합니다.
- C++에는
- new/delete와 RAII
- 동적 메모리 할당에
new/delete를 사용하고, 스마트 포인터로 메모리 관리를 권장합니다.
- 동적 메모리 할당에
간단 예시 (네임스페이스와 iostream):
#include <iostream>
int main() {
std::cout << "Hello, C++!" << std::endl;
return 0;
}
디폴트 파라미터 (Default parameters)
함수 선언에서 일부 파라미터에 기본값을 줄 수 있습니다. 호출 시 해당 인자를 생략하면 기본값이 사용됩니다.
- 규칙
- 기본값은 오른쪽(마지막 파라미터)부터 지정해야 함.
- 선언(또는 헤더)에만 기본값을 주고 정의에서는 생략하는 관습이 일반적입니다.
예시:
#include <iostream>
void greet(const std::string& name, const std::string& title = "님") {
std::cout << "안녕하세요, " << name << title << "!" << std::endl;
}
int main() {
greet("철수"); // "안녕하세요, 철수님!"
greet("영희", " 선생님"); // "안녕하세요, 영희 선생님!"
}
기본값을 사용하면 함수 호출이 간결해지지만, 모호성이나 유지보수 문제를 만들 수 있으니 적절히 사용하세요.
함수 오버로딩 (Function overloading)
같은 함수 이름으로 인자 타입이나 개수, cv-qualifier, ref-qualifier 등을 달리한 여러 함수를 정의해 하나의 이름으로 다양한 동작을 제공하는 C++ 기능입니다. 컴파일러는 호출 시 전달된 인자를 바탕으로 가장 적합한(최적 매칭) 오버로드를 선택합니다.
핵심 개념
- 오버로딩은 이름은 같지만 서로 다른 시그니처(매개변수 목록)를 가진 함수들로 구성됩니다.
- 반환 타입만 다른 것은 오버로드가 되지 않습니다(반환형은 오버로드 결정에 참여하지 않음).
- 멤버 함수의 경우 const, &, && 같은 멤버 한정자도 시그니처의 일부로 취급됩니다.
이름 맹글링(Name mangling)
- C++는 오버로딩을 지원하기 때문에 컴파일러는 함수의 시그니처(매개변수 타입, 네임스페이스, 클래스 등)를 반영한 내부 심볼명을 생성합니다(이를 네이밍 맹글링이라 함). 이 덕분에 링커는 동일한 기본 이름을 가진 서로 다른 시그니처를 구분할 수 있습니다.
- 예: 같은 이름 print라도 print(int)와 print(double)은 각기 다른 맹글링된 심볼을 가집니다.
- extern “C”를 사용하면 맹글링을 막아 C 스타일 심볼명을 유지합니다(오버로딩 불가).
- 디버깅/심볼 확인 도구:
-
Linux: nm c++filt로 맹글링 해제 - Windows: dumpbin /SYMBOLS
-
예제: 기본 오버로딩
// 예제: 간단한 오버로드
#include <iostream>
#include <string>
void print(int x) { std::cout << "int: " << x << '\n'; }
void print(double x) { std::cout << "double: " << x << '\n'; }
void print(const std::string& s) { std::cout << "string: " << s << '\n'; }
int main() {
print(10); // int
print(3.14); // double
print("hello"); // string (const char* -> std::string 생성자: user-defined conversion)
}
모호성 예제
void f(int);
void f(double);
f(1.0f); // float -> int (promotion+conversion?) vs float -> double (promotion) 중 어떤 게 더 좋은지 규칙에 따라 선택 또는 모호성
템플릿과 오버로딩
- 일반적으로 non-template 오버로드가 template보다 우선됩니다(동일한 우수성인 경우).
- 함수 템플릿의 특수화/부분적 매칭 규칙이 적용되어 더 구체적인 템플릿이 선택될 수 있습니다.
- SFINAE는 템플릿 오버로드 선택을 세밀하게 제어할 때 유용합니다.
권장 스타일
- 오버로드는 명확할 때만 사용하세요. 암묵 변환이 일어날 가능성이 높다면 별도 함수 이름을 고려하세요.
- 관련 오버로드는 같은 파일/헤더에 모아 문서화하고, 필요하면 주석으로 우선순위/의도 설명을 추가하세요.
- 템플릿 기반 오버로딩은 제약을 명확히 하여 읽기 쉬운 코드를 유지하세요(concepts 또는 enable_if).
요약
- 함수 오버로딩은 같은 이름으로 여러 동작을 제공하는 강력한 도구입니다.
- 컴파일러는 후보 선정 → 가능성 판정 → 변환 순위 비교 → 최종 선택의 절차로 가장 적합한 오버로드를 고릅니다.
-
이름 맹글링은 오버로딩을 구현하는 내부 메커니즘으로, extern “C”를 사용하면 맹글링을 피할 수 있습니다.
레퍼런스 (Reference)
레퍼런스는 기존 변수의 별칭(alias)입니다. 선언 후 다른 대상을 가리키도록 변경할 수 없습니다. 문법은 타입& 이름 입니다.
- 장점: 포인터보다 문법이 간단하고 널(null) 상태를 가질 수 없음(따라서 안전).
- 주로 함수 인자의 전달(복사 방지, 수정 허용)에 사용됩니다.
const레퍼런스로 읽기 전용 참조 전달도 가능.
예시:
#include <iostream>
void increment(int& x) { // x는 원래 변수의 별칭
x += 1;
}
void printConst(const int& x) { // 복사 없이 읽기 전용 접근
std::cout << x << std::endl;
}
int main() {
int a = 5;
increment(a); // a는 6이 됨
printConst(a); // 6
}
iostream (입출력)
C++ 기본 입출력은
- 주요 객체
- std::cout: 표준 출력
- std::cin: 표준 입력
- std::cerr: 표준 에러(버퍼링 없음)
- std::clog: 표준 로그(버퍼링 있음)
- 기본 사용법
#include <iostream>
#include <string>
int main() {
std::string name;
int age;
std::cout << "이름: ";
std::getline(std::cin, name); // 공백 포함 입력
std::cout << "나이: ";
std::cin >> age; // 공백 기준 토큰 입력
std::cout << "이름: " << name << ", 나이: " << age << std::endl;
}
- 포맷 제어
- std::endl: 줄바꿈 후 버퍼 플러시
- std::setw, std::setprecision 등은
에서 제공
#include <iostream>
#include <iomanip>
double pi = 3.14159265;
std::cout << std::fixed << std::setprecision(3) << pi << std::endl; // 3.142
- 버퍼링 / 플러시
- std::endl은 줄바꿈과 동시에 버퍼를 비우므로 자주 쓰면 성능에 영향.
- 필요 시 std::flush 사용.
nullptr
nullptr는 C++11부터 도입된 널 포인터 리터럴입니다. 기존의 NULL(보통 0으로 정의되던 것)보다 타입이 명확하고 안전합니다.
- 특징
nullptr의 타입은std::nullptr_t로, 포인터와만 비교/대입되는 것이 안전합니다.- 함수 오버로딩에서 포인터와 정수(0)를 구분할 수 있게 해줍니다.
예시 비교:
void f(int);
void f(char*);
f(0); // f(int)가 호출될 수 있음 (정수 0)
f(nullptr); // f(char*)가 호출됨 (포인터 널)
널 포인터 확인:
int* p = nullptr;
if (p == nullptr) {
// 안전하게 널 체크
}
정리 팁:
- 처음엔 std::cout/std::cin과 기본 문법(변수, 제어문, 함수)에 익숙해지세요.
- 참조와 포인터의 차이, 값 전달 vs 참조 전달을 실습해보세요.
- 디폴트 파라미터와 오버로딩은 API 설계 시 편리하지만 남용하면 혼동을 초래합니다.
클래스와 객체지향 (Class vs 객체지향 프로그래밍)
클래스는 C++의 문법적 도구일 뿐이고, 객체지향은 그 문법을 이용한 설계·구조화 방법론입니다. 즉 클래스는 데이터를 묶고 연산을 정의하는 수단이고, 객체지향은 무엇을 클래스로 묶을지·어떤 책임을 부여할지·계층과 상호작용을 어떻게 설계할지에 관한 사고방식입니다.
핵심 요점
- 클래스: 데이터(멤버)와 동작(멤버 함수)을 하나로 묶는 문법 요소.
- 객체지향은 문법 위의 설계 원칙들(캡슐화, 단일 책임, 응집도/결합도, 상속, 다형성 등).
- 레벨 차이: 객체지향은 메모리 뷰, 어셈블리, 링킹 심볼 같은 로우레벨보다 한 단계 추상화된 설계 관점이다.
- 다중 패러다임: C++는 객체지향을 지원하지만 함수형, 절차적 접근 등 다양한 스타일을 혼합해 쓸 수 있다.
- 무엇을 클래스로 만들까: 변화가 적고 책임이 명확한 단위를 클래스(또는 타입)로 묶는 것이 좋음(단일 책임 원칙).