1. C와 C++의 차이
C의 비효율성을 개선하고자 C++이 나왔다는 정도만 알면 된다
2. HelloWorld로 본 C++
#include <tchar.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[]) {
std::cout << "Hello, World" << std::endl;
return 0;
}
우리가 알고있던 main의 형태가 아니라 _tmain이라는 신기한 코드가 예제로 나왔다
옆에 있는 _TCHAR*도 마찬가지이다
위 둘은 유니코드를 지원하기 위해 재선언된 것이다
VScode 가 아닌 다른 환경에서 실습한다면 int main으로 선언해도 괜찮다고 한다
- std는 '네임스페이스(Namespace)' 라고 하며 '소속'과 같은 의미이다
- :: 는 범위 지정 연산자라고 한다
- cout은 콘솔 출력을 담당하는 객체이다
- << 는 '연산자 함수' 이다
위 개념에 따라서 std::cout은 std 네임스페이스에 속한 cout이다 라고 설명할 수 있다
endl도 비슷하다
책에서는 다음과 같이 설명한다
"std에 속한 cout 객체에 문자열과 endl 객체를 넘겨(<<) 문자열을 화면에 출력해달라"
이전에 있던 printf가 잘만 있는데 왜 굳이 cout을 쓰느냐 묻는다면 세탁소로 비유할 수 있다
우리가 세탁을 직접할 수는 있지만, 세탁소에 가서 직원에게 맡기는 것만큼은 못한다
C++은 객체 지향 언어로 "세탁을 전문으로 하는 객체에게 맡긴다!" 라는 생각을 가지고 코딩해야하는 언어이다
3. 인스턴스와 입출력 흐름
앞으로는 인스턴스(instance)라는 말을 많이 쓰게 된다
사람인 철수는 '사람'이라는 형식에 대한 하나의 예시(instance)가 된다
객체지향 프로그래밍 환경에서는 모든 것을 다 객체로 표현하고 객체의 형식을 갖는 변수를 인스턴스라 한다.
std::cout도 그렇다
cout은 iostream클래스의 객체(인스턴스)로, std 네임스페이스에 속해있다
cout은 C와는 달리 자료형을 지정하지 않아도 된다
알아서 자료형을 맞춰서 출력한다는 뜻이다.
std::cin은 cout의 반대로, 입력을 담당한다
아래 예제를 보며 cout과 cin을 입력해보자
#include <tchar.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[]) {
int nAge;
std::cout << "나이를 입력하세요." << std::endl;
std::cin >> nAge;
char szJob[32];
std::cout << "직업을 입력하세요. " << std::endl;
std::cin >> szJob;
std::string strName;
std::cout << "이름을 입력하세요" << std::endl;
std::cin >> strName;
std::cout << "당신의 이름은 " << strName << "이고, "
<< "나이는 " << nAge << "살이며, "
<< "직업은 " << szJob << "입니다" << std::endl;
return 0;
}
4. 자료형
C++답게 변수를 선언하는 방법이 있다
바로 int a(10); int b(a); 다음과 같이 선언하는 것이다
10은 int 자료형 인스턴스인 a의 초깃값이고, 이어지는 b(a)에서는 변수 a를 복사해 b를 선언한다.
b는 a를 복제해서 만든다는 말은 이후 '복사 생성자'로 이어지게 된다
auto는 잘 쓰진 않지만 초기값의 형식에 맞춰 선언하는 인스턴스의 자료형을 자동으로 결정하는 자료형이다
5. 메모리 동적 할당
C나 C++에서 가장 중요시되는 부분은 포인터이다
본래대로라면 malloc()과 free()를 사용할테지만 C++에서는 조금 다르다
바로 new와 delete 연산자이다
new와 delete연산자는 내부에서 malloc() 과 free() 함수를 호출한다
사용방법은 다음과 같다
#include <tchar.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[]) {
//인스턴스만 동적으로 생성하는 경우
int* pData = new int; //자료형 *변수이름 = new 자료형
//초깃값을 기술하는 경우
int* pNewData = new int(10);
*pData = 5;
std::cout << *pData << std::endl;
std::cout < , *pNewData < , std::endl;
delete pData;
delete pNewData;
return 0;
}
예시에서는 int 인스턴스를 한 개만 만들었지만, 인스턴스 여러 개가 필요할 경우 배열 형태로 생성할 수 있다
다만 주의할 점이 있는데, 바로 '배열 형태로 동적 생성한 것은 반드시 배열 형태를 통해 삭제해야한다는 것'이다.
#include <tchar.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[]) {
// 객체를 배열 형태로 동적 생성한다
int* arr = new int[5];
for (int i = 0; i < 5; i++) {
arr[i] = (i + 1) * 10;
}
for (int i = 0; i < 5; i++) {
std::cout << arr[i] << std::endl;
//배열 형태로 생성한 대상은 반드시 배열 형태를 통해 삭제한다
delete[] arr;
}
return 0;
}
malloc()함수와 new 연산자의 다른 점을 하나 꼽자면 '생성자 함수 호출'이다.
아직 배우지 않았지만 외우고 가라한다
"new 연산자는 객체의 생성자를 호출하고, delete 연산자는 객체의 소멸자를 호출한다"
6. 참조자 형식
참조자(Reference) 형식은 C에는 없고, 포인터와 유사한 형식이다
그러나 보기에는... 완전히 다르다
선언과 동시에 반드시 초기화해야하고
변수가 없는 참조자란 존재할 수 없다
#include <tchar.h>
#include <iostream>
int _tmain(int argc, _TCHAR* argv[]) {
int a = 5;
int& rData = a;
return 0;
}
쓰는 방식은 포인터와 같다
C에서 함수를 호출할 때 매개변수로 주소를 전달하는 기법을 참조에 대한 호출이라고 배우는데,
여기서 참조는 바로 주소를 의미한다.
우리가 큰 값을 직접 주고 넘기는 것보다 주소를 넘기는 게 편하고 빠르기 때문에 사용하는데,
주소라는 말보다는 참조라는 말을 주로 사용하게 될 것이다.
#include <tchar.h>
#include <iostream>
using namespace std;
//매개변수가 int에 대한 참조 형식이다.
void TestFunc(int& rParam) {
//피호출자 함수에서 원본의 값을 변경
rParam = 100;
}
int _tmain(int argc, _TCHAR* argv[]) {
int nData = 0;
TestFunc(nData);
cout << nData << endl;
return 0;
}
여기서 주의할 것은 TestFunc(&nData)라고 기술하지 않는 것이다.
참조 매개변수(int&) 는 “원본 객체의 또 다른 이름(별칭, alias)”이기 때문에 호출할 때 &를 붙이지 않는다.
나는 C언어...를 복습하질 않아서 까먹은 탓인지 C++이라 뭔가 다른건지
헷갈려서 ChatGPT에게 질문해 다음과 같은 예시코드를 받았다
#include <iostream>
using namespace std;
// 참조 버전
void RefFunc(int& r) {
cout << "[RefFunc] r의 주소: " << &r << endl;
r = 100;
}
// 포인터 버전
void PtrFunc(int* p) {
cout << "[PtrFunc] p에 저장된 값(=주소): " << p << endl;
cout << "[PtrFunc] p의 자기 주소: " << &p << endl;
*p = 200;
}
int main() {
int nData = 0;
cout << "초기 nData 값: " << nData << endl;
cout << "nData 주소: " << &nData << endl;
cout << "---------------------------" << endl;
RefFunc(nData); // 참조로 전달
cout << "RefFunc 호출 후 nData 값: " << nData << endl;
cout << "---------------------------" << endl;
PtrFunc(&nData); // 포인터로 전달
cout << "PtrFunc 호출 후 nData 값: " << nData << endl;
return 0;
}
그리고 다음과 같은 설명도 덧붙여줬다
🔎 실행 결과 예시 (환경에 따라 주소값은 다를 수 있음)
nData 주소: 0x7ffee8a55a1c
---------------------------
[RefFunc] r의 주소: 0x7ffee8a55a1c
RefFunc 호출 후 nData 값: 100
---------------------------
[PtrFunc] p에 저장된 값(=주소): 0x7ffee8a55a1c
[PtrFunc] p의 자기 주소: 0x7ffee8a55a10
PtrFunc 호출 후 nData 값: 200
✅ 여기서 볼 수 있는 차이
- RefFunc에서 r의 주소와 nData의 주소가 완전히 동일합니다.
→ 즉 r은 그냥 nData의 또 다른 이름일 뿐이에요. - PtrFunc에서는 p가 nData의 주소값을 저장하고 있고, &p는 또 다른 주소(포인터 변수 자체의 주소)입니다.
→ 즉 포인터는 "주소를 담는 별도 변수"라는 걸 알 수 있습니다.
가능하다면 포인터 대신 참조 형식을 사용하라는데 아직 제대로 이해 못해서 다시 찾아오도록 하겠다
r-value 참조라는 것도 있는데, r-value란 대입 연산자의 두 피연산자 중에서 오른쪽에 위치한 연산자를 말한다
문법은 일반 참조자와 비슷한데 &&가 두번 연속해서 등장한다
int &&rData = 3;
#include <tchar.h>
#include <iostream>
using namespace std;
int TestFunc(int nParam) {
int nResult = nParam * 2;
return nResult;
}
int main() {
int nInput = 0;
cout << "Input number : ";
cin >> nInput;
// 산술 연산으로 만들어진 임시 객체에 대한 r-value 참조
int&& rdata = nInput + 5;
cout << rdata << endl;
// 함수 반환으로 만들어진 임시 객체에 대한 r-value 참조
int&& result = TestFunc(10);
// 값을 변경 할 수 있다.
result += 10;
cout << result << endl;
return 0;
}
핵심은
(1) int &&rData = nInput + 5; 와
(2) int &&result = TestFunc(10); 이다.
(1)은 임시 결과에 대한 r-value 참조자 선언과 정의고, (2)는 반환값에 대한 r-value 참조자 선언과 정의이다.
임시 결과는 연산에 활용된 직후 소멸하는 r-value이다.
r-value 참조자는 곧 소멸할 대상에 참조자를 부여할 수 있다는 것이 핵심이다.
3+4+5 연산에서 3+4의 결과를 임시 결과라고 한다는 것은 너무나 당연해서 쉽게 간과할 수 있지만 꼭 기억해야한다
7. 범위 기반 for 문
배열 요소의 개수를 변경하면 for문도 수정해야하는 번거로움이 있지만 범위 기반 for 문을 사용한다면 그러지 않아도 된다
배열 요소의 개수를 for문에 쓰지 않기 때문이다
#include <iostream>
using namespace std;
int main() {
int aList[5] = { 10, 20, 30, 40, 50 };
// 전형적인 C 스타일 반복문
for(int i = 0; i < 5; ++i) {
cout << aList[i] << ' ';
}
cout << endl;
// 범위 기반 C++11 스타일 반복문
// 각 요소의 값을 n에 복사한다.
for(auto n : aList) { // 전체 요소에 접근, 읽기만 가능
cout << n << ' ';
}
cout << endl;
// n은 각 요소에 대한 참조다.
for(auto &n : aList) { // 쓰기도 가능
cout << n << ' ';
}
cout << endl;
return 0;
}
for (int i=0; i<5; i++)를 for (auto n : a List)로 작성하는데,
이것은 각 요소의 값을 n에 복사하는 것이다.
실수도 줄일 수 있을 뿐만 아니라 효율적으로 향상시킬 수 있는 좋은 코드이다.
나는 두 개의 차이가 궁금해서 좀 더 물어봤다
(1) for (auto n : aList)
- 여기서 n은 aList의 각 요소를 ‘복사’한 임시 변수입니다.
- 즉, aList에 들어있는 값을 그대로 가져와서 새 변수 n에 복사해 사용해요.
특징
- 원본 aList는 변경되지 않습니다.
- n을 바꿔도 aList의 값은 그대로입니다.
- 읽기 전용처럼 쓸 수 있지만, 복사 비용이 발생합니다(특히 요소 타입이 큰 객체일 경우 성능 손실).
(2) for (auto& n : aList)
- 여기서 n은 **aList의 각 요소에 대한 참조(별칭)**입니다.
- 따라서 n을 바꾸면 실제 aList의 요소가 바뀝니다.
특징
- 원본 aList의 값을 직접 수정할 수 있습니다.
- 복사 과정이 없으므로 성능상 더 효율적입니다(특히 큰 객체일 때).
- 단, 원본 데이터가 바뀔 수 있다는 점을 조심해야 합니다.
예시 코드
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> aList = {1, 2, 3};
cout << "(1) auto n : aList" << endl;
for (auto n : aList) {
n = n * 10; // 복사본을 바꾸는 것뿐
}
for (auto n : aList) {
cout << n << ' '; // 원본은 그대로
}
cout << endl;
cout << "(2) auto& n : aList" << endl;
for (auto& n : aList) {
n = n * 10; // 원본에 직접 적용
}
for (auto n : aList) {
cout << n << ' '; // 원본이 바뀜
}
cout << endl;
}
실행 결과
✅ 정리
- (1)은 읽기 전용처럼 동작: 요소가 n에 복사됨.
- (2)는 원본 직접 접근: 요소가 n에 참조로 전달됨 → 수정 가능.
대충 for (auto n : aList)은 수정이 안되지만 for (auto& n : aList)은 수정이 된다는 말이다
n은 별칭일 뿐이고, 실제로는 aList와 같은 것이다
반면 for (auto n : aList)에서의 n은 이름만 다른 같은 것이 아니라 복사한 진짜 다른 것이다.
이처럼 오늘은 C++의 아주 기초를 다져보았다
연습문제를 풀고 마무리하겠다
#연습문제
1.
#include <iostream>
#include <string>
using namespace std;
int main(void){
string name;
int age;
cout << "이름을 입력하세요" << endl;
cin >> name;
cout << "나이를 입력하세요" << endl;
cin >> age;
cout << "나의 이름은 " << name << "이고, " << age << "살입니다." << endl;
return 0;
}
2. auto는 대입된 값을 보고 자동으로 자료형을 선언해주는 예약어이다
3.
#include <iostream>
using namespace std;
int main(void){
char *string = new char[12];
delete[] string;
return 0;
}
4.
#include <iostream>
using namespace std;
void Swap(int& a, int& b) {
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int num1 = 5, num2 = 10;
cout << "num1= " << num1 << ", num2 = " << num2 << endl;
Swap(num1, num2);
cout << "num1= " << num1 << ", num2 = " << num2 << endl;
return 0;
}
5.
모르겠어서 찾아봤다
- 일반 참조: T&
- 상수 참조: const T&
const는 원본을 수정할 수 없지만 임시 객체나 리터럴에 바인딩 할 수 있다
| 일반 참조 (T&) | 상수 참조 (const T&) |
| 수정 가능 | 수정 불가능 |
| lvalue만 참조 | lvalue + rvalue(임시) 참조 가능 |
6.
#include <iostream>
using namespace std;
int main(void){
int aList[5] = { 10,20,30,40,50 };
for (auto &n : aList) {
cout << n << " ";
}
return 0;
}
숫자 값을 변경하는 것이 아니니 &n이 아니라 n으로 써도 된다
'교재 > 이것이 C++이다' 카테고리의 다른 글
| 이것이 C++이다 - [3장] 클래스 (0) | 2025.10.03 |
|---|---|
| 이것이 C++이다 - [2장] C++ 함수와 네임스페이스 (0) | 2025.09.11 |