입출력 함수
-
C 언어
#include <stdio.h>
int main() {
int nbr = 1;
char sentence[1024];
print("Enter some characters: ");
scanf("%s", sentence);
printf("%d: %s\n", nbr, sentence);
printf("Hello World!\n");
return 0;
}
-
C++
#include <iostream>
int main() {
int nbr = 1;
char sentence[1024];
std::cin >> sentence;
std::cout << x << ": " << sentence << std::endl;
std::cout << "Hello World!" << std::endl;
return 0;
}
-
헤더 파일의 변화: <stdio.h> 에서 <iostream>
-
C언어에서 사용된 헤더파일은 대부분 앞에 c를 붙이고 ".h"를 제외하면 똑같이 사용 가능.
-
<stdio.h> == <cstdio> 또는 <stdlib.h> == <cstdlib> 또는 <string.h> == <cstring> 등
-
-
C++에서 표준 입출력 함수는 입력이나 출력 형식을 지정하지 않고 사용함. 소수점 자릿수 등을 따져야할 경우 C언어의 표준 입출력을 가져다 써야함.
-
속도는 C언어의 표준 입출력(printf/scanf)가 C++ 것(cin/cout)보다 빠름.
-
C++ 에서 개행문자는 std::endl 로도 표현 가능.
bool type
-
C언어에서는 조건문의 참, 거짓을 표현하기 위해 int 타입을 사용.
-
참: 0을 제외한 모든 값
-
거짓: 0
-
#include <stdio.h>
int main() {
int x = 1;
if (x) {
printf("x is true.\n");
} else {
printf("x is false.\n");
}
return 0;
}
-
C++에서는 참과 거짓을 명확하게 사용하기 위해 bool 타입이 추가됨.
-
참: true
-
거짓: false
-
#include <iostream>
int main() {
bool b = true;
if (b) {
std::cout << "b is true." << std::endl;
} else {
std::cout << "b is false." << std::endl;
}
// 표준 출력 시 int 타입
std::cout << b << std::endl; // 1
std::cout << !b << std::endl; // 0
// bool 포맷을 유지하기
std::cout.setf(std::ios::boolalpha);
std::cout << b << std::endl; // true
std::cout << !b << std::endl; // false;
// 다시 int로 출력하기
std::cout.unsetf(std::ios::boolalpha);
std::cout << b << std::endl; // 1
std::cout << !b << std::endl; // 0
return 0;
}
auto keyword
-
C++11 부터 추가된 auto 타입 연산자를 사용하면, 컴파일 타임에 타입을 추론해 어떤 타입인지 결정함.
-
기본 내장 타입을 포함해 컴파일 타임에 추론 가능한 모든 타입에 사용 가능.
-
만약 컴파일 타임에 추론이 불가능하다면, 오류가 발생함.
#include <iostream>
#include <vector>
int main() {
int i1 = 10;
double d1 = 3.14;
auto i2 = 10; // integer
auto d2 = 3.14; // double
// 복잡한 타입을 가지는 경우
std::vector<std::pair<int, double> > vPairs;
for (std::vector<std::pair<int, double> >::iterator iter = vPairs.begin();
iter != vPairs.end(); ++iter)
{
std::cout << "So complex!" << std::endl;
}
// auto keyword
for (auto iter = vPairs.begin(); iter != vPairs.end(); iter++)
{
std::cout << "auto keyword is good." << std::endl;
}
return 0;
}
범위 기반 for문(Range-based for)
-
for 문은 지정된 횟수만큼 반복하는 작업에도 사용하나, 배열이나 각종 컨테이너에 있는 각 요소들에 무언가를 수행하기 위해 순회하는 목적으로도 많이 사용함.
-
그러나, 조건문에 배열이나 컨테이너에 몇 개의 요소가 들어있는지 명시해야 한다는 단점이 있음.
-
범위 기반 for문은 요소들의 개수에 상관없이 반복문을 수행함.
#include <iostream>
auto mean(const Sequence& seq)
{
auto n = 0.0;
for (auto& x : seq) {
n += x;
}
return n / seq.size();
}
int main() {
int arr[] = { 1, 2, 3, 4, 5 };
for (auto& i : arr) {
std::cout << i << std::endl;
}
// same as
for (int i = 0; i < 5; ++i) {
std::cout << i << std::endl;
}
return 0;
}
메모리 할당과 해제
-
C언어
#include <stdio.h>
#include <stdlib.h>
int main() {
int i;
int** arr = (int**) malloc(sizeof(int*) * 5);
for (i = 0; i < 5; ++i) {
arr[i] = (int*) malloc(sizeof(int) * 5);
}
for (i = 0; i < 5; ++i) {
free(arr[i]);
}
free(arr);
arr = NULL;
return 0;
}
-
C++
#include <iostream>
int main() {
int** arr = new int*[5];
for (int i = 0; i < 5; ++i) {
arr[i] = new int[5];
}
for (int i = 0; i < 5; ++i) {
delete[] arr[i];
}
delete[] arr;
arr = NULL;
int* p1 = new int;
delete p1;
p1 = NULL;
int* p2 = new int[10];
delete[] p2;
p2 = NULL;
return 0;
}
널 포인터
-
C언어에서 널 포인터를 나타내기 위해 NULL이나 상수 0을 사용.
-
#define NULL 0
-
따라서 NULL == 0
-
-
NULL 은 포인터가 아니므로, 함수에 인자로 넘기는 경우 int 타입으로 추론되는 문제가 발생함.
-
C++에서는 nullptr 키워드로 널 포인터를 표현함.
-
nullptr은 널 포인터 리터럴(Literal) 이며, 실제 타입은 std::nullptr_t 이다.
#include <iostream>
// function overloading
void fn(int a) {
std::cout << "fn(int)" << std::endl;
}
void fn(int* a) {
std::cout << "fn(int*)" << std::endl;
}
int main() {
fn(0); // fn(int)
fn(NULL); // fn(int)
fn(nullptr); // fn(int*)
std::cout << sizeof(nullptr) << std::endl; // 4
std::cout << typeid(nullptr) << std::endl; // std::nullptr_t
int* p1 = new int;
delete p1;
p1 = nullptr; // DO NOT USE NULL
int* p2 = new int[10];
delete[] p2;
p2 = nullptr; // DO NOT USE NULL
return 0;
}
명시적 캐스팅
-
C언어에서는 int(..) 또는 char(...)를 통해 명시적(explicit) 캐스팅을 할 수 있으나, 개발자의 실수를 그대로 용납하기 때문에 차후에 오류가 생길 수 있음.
-
C++에서는 보다 명시적인 캐스팅 방법으로 캐스팅의 목적을 명확하게 명시함으로써 개발자의 의도를 컴파일러에게 전달하여 오류를 방지함.
-
static_cast<타입>(표현식);
-
const를 제외한 모든 명확한 타입 변환에 사용.
-
-
dynamic_cast<타입>(표현식);
-
기본 타입에 대한 포인터나 참조자를 파생 타입에 대한 포인터나 참조자로 안전하게 변환.
-
-
const_cast<타입>(표현식);
-
const 객체를 const가 아닌 타입으로 변환
-
-
reinterpret_cast<타입>(표현식);
-
비트 구성 형식을 저수준에서 재해석
-
#include <iostream>
int main() {
char* str = "Hello, World!";
int* intPtr = static_cast<int*>(str);
char* charPtr = static_cast<char*>(*intPtr);
std::cout << charPtr << std::endl;
/* In the case of C
int* intPtr = (int*) str;
char* charPtr = (char*)*pi;
*/
return 0;
}
범위 지정 열거체(Scoped enum)
-
C/C++ 에서 기존에 사용하던 열거형은 전역(global) 범위로 선언되기 때문에 A 열거체에서 사용한 T라는 이름은 B 열거체에서는 사용할 수 없음.
-
또한, 기본적으로 열거체의 타입은 int이기 때문에 묵시적으로 변환되어 서로 다른 열거체 변수끼리 더하거나 비교할 수 있음.
#include <stdio.h>
/* C/C++98 */
int main() {
enum TrafficLight { Red, Yellow, Green };
enum Job { Warrior, Ranger, Wizard };
enum Coffee : unsigned char { Latte = 10, Mocha = 25 };
int jobNum = Warrior;
int i = Green + Latte; // 12
if (Yellow == Ranger) {
printf("Same!\n");
} else {
printf("Different!\n");
}
return 0;
}
- C++11부터 위의 문제점들을 보완하기 위해 범위 지정 열거체를 제공.
- enum 대신 enum class를 사용.
- 묵시적 변환이 되지 않으므로, 명시적으로 타입을 지정하지 않으면 오류 발생.
- 서로 다른 열거체 변수끼리 연산할 수 없음.
#include <iostream>
// C++11
int main() {
enum class TrafficLight { Red, Yellow, Green };
enum class Job { Warrior, Ranger, Wizard, Green };
enum class Coffee : unsigned char { Latte = 10, Mocha = 25 };
Job jobNum = Job::Warrior;
int i = static_cast<int>(TrafficLight::Green)
+ static_cast<int>(Coffee::Latte);
// TrafficLight::Yellow != Job::Ranger
return 0;
}
Binary literal, separator
-
C/C++에서는 10진수 외에 8진수, 16진수를 표현할 수 있음.
-
8진수: 061 (=49) --> 0으로 시작
-
16진수: 0x3A (=58) --> 0x로 시작
-
-
C++14부터 2진수를 표현하는 방법이 추가됨. 0b로 시작 --> 0b110101
-
C++14부터 구분자(separator)가 추가되어 큰 숫자를 읽기 쉬움.
-
int INT_MAX = 21'4748'3647
-
#include <iostream>
int main() {
int decimal = 52;
int octal = 064;
int hexadecimal = 0x34;
int binary = 0b110100; // C++14
int maxInt_Cpp98 = 2147483647;
int maxInt_Cpp14 = 21'4748'3647; // C++14
return 0;
}
문자열(string) 자료구조
-
C언어에서는 문자열을 나타내기 위해 char[] 또는 char* 를 사용하였으나, 이는 비교(strcmp), 연결(strcat), 길이(strlen) 등 문자열 관련 함수를 사용할 때 불편함.
#include <stdio.h>
int main() {
char str1[30] = "Hello, World!";
char* str2 = "Hello, ke2ek!";
if (strcmp(str1, str2) == 0) printf("Same!\n");
else printf("Different!\n");
strcat(str1, str2);
printf("%s\n", str1); // Hello, World!Hello, ke2ek!
printf("%d, %d\n", strlen(str1), strlen(str2));
return 0;
}
-
C++에서는 std::string을 사용해 문자열을 나타냄.
-
#include <string>
-
문자열 연산: 비교(==), 연결(+), 길이(size() 또는 length())
-
#include <iostream>
#include <string>
int main() {
std::string str1 = "Hello, World!";
std::string str2 = "Hello, ke2ek!";
if (str1 == str2) std::cout << "Same!" << std::endl;
else std::cout << "Different!" << std::endl;
str1 = str1 + str2;
std::cout << str1 << std::endl; // Hello, World!Hello, ke2ek!
std::cout << str1.size() << ", " << str2.length() << std::endl;
return 0;
}
레퍼런스(Reference) 변수
-
다른 변수를 가리키는 변수 (포인터 기능의 일부)로, 다른 변수와 동일한 메모리 위치를 공유함.
-
레퍼런스 변수로 연산할 때는 포인터와 달리 *나 &를 추가해야 하는 문법이 없음.
-
레퍼런스는 항상 유효한 메모리 주소를 참조해야 하므로, const처럼 선언과 동시에 초기화를 해주어야 함.
#include <iostream>
// Pass-by-pointer
void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
// Pass-by-reference
void swap(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void print(int a, int b) {
std::cout << "a = " << a;
std::cout << ", b = " << b << std::endl;
}
int main() {
// int& ref; NEED TO ASSIGN.
int i = 10;
int &ret = i;
std::cout << "ref = " << ref << std::endl; // 10
ret += 10;
std::cout << "i = " << i << std::endl; // 20
std::cout << "ref = " << ref << std::endl; // 20
int a = 10, b = 20;
print(a, b); // a = 10, b = 20
swap(&a, &b); // Pass-by-pointer
print(a, b); // a = 20, b = 10
int c = 30, d = 40;
print(c, d); // c = 30, d = 40
swap(c, d); // Pass-by-reference
print(c, d); // c = 40, d = 30
return 0;
}
함수 오버로딩
-
C언어와 달리, C++에서는 여러 함수를 동일한 이름으로 선언할 수 있는 함수 오버로딩이라는 개념을 지원함.
-
함수 오버로딩 규칙
-
오버로딩된 함수는 인수의 개수가 서로 달라야 함.
-
인수의 개수가 같은 경우, 인수의 타입이 서로 달라야 함.
-
리턴 타입이 다른 경우, 같은 이름의 함수를 사용할 수 없음.
-
#include <iostream>
int sum(int num1, int num2);
int sum(int num1, int num2, int num3);
int sum(short num1, short num2);
// Compile Error!
// short sum(short num1, short num2);
int main() {
int a = 10, b = 20, c = 30;
int d = sum(a, b);
int e = sum(a, b, c);
short f = 40, g = 50;
int h = sum(f, g);
return 0;
}
네임스페이스(Namespace)
-
어떤 프로젝트에서 A와 B가 구현한 코드를 합치는데 동일한 이름의 변수나 함수가 있을 경우, 기본 타입을 확장해서 구분해줄 필요가 있음. C언어에서는 이런 문제를 해결할 수 없으나, C++에서는 네임스페이스를 이용해서 해결 가능.
-
네임스페이스는 namespace 이름 { ... } 으로 정의하며, 중첩해서 사용 가능.
-
네임스페이스 A에 있는 변수 a에 접근하려면, 범위 지정 연산자(::)를 사용해야 함. A::a
-
표준 라이브러리의 네임스페이스는 std로, cin/cout 같은 표준 입출력 사용 시 std::cin 또는 std::cout으로 접근해야 함.
-
네임스페이스를 매번 적기 귀찮을 경우, using namespace 이름; 을 헤더파일 밑에 선언해주면 됨.
-
표준 라이브러리 사용 시, using namespace std; 를 많이 사용함.
-
#include <iostream>
using namespace std;
namespace A {
void print() {
cout << "A's print()" << endl;
}
}
namespace B {
void print() {
cout << "B's print()" << endl;
}
}
int main() {
A::print(); // A's print()
B::print(); // B's print()
return 0;
}
'Programming Language > C++' 카테고리의 다른 글
예외 처리(Exception Handling) (0) | 2020.11.16 |
---|---|
객체 지향 프로그래밍(Object-Oriented Programming) - 4 (0) | 2020.11.15 |
객체 지향 프로그래밍(Object-Oriented Programming) - 3 (0) | 2020.11.10 |
객체 지향 프로그래밍(Object-Oriented Programming) - 2 (0) | 2020.11.06 |
객체 지향 프로그래밍(Object-Oriented Programming) - 1 (1) | 2020.11.06 |