매개변수와 실인자의 구분
매개변수(Parameter)
인자(Argument)
#include <iostream>
using namespace std;
int foo(int x, int y);
// parameter: 함수가 어떤 기능을 하는지, 바꿔 주는 역할
int foo(int x, int y)
{
// like... int x, y;
return x + y;
} // x and y are destroyed here, 함수가 끝남과 동시에 소멸됨
int main()
{
int x = 1, y = 2;
foo(6, 7); // 6, 7: arguments (actual parameters)
foo(x, y + 1); // x에 있는 값이 argument로, 함수의 parameter로 들어간다
// 항상 x에 있는 값만 전달되는 것은 아니다? (값, 참조, 주소에 의한 전달)
return 0;
}
값에 의한 전달
Passing Arguments by Value (Call by Value)
외부로 영향을 끼치지 않는다! (깔끔하다)
#include <iostream>
using namespace std;
void doSomething(int y)
{
// 함수를 호출할 때 매개변수로 y를 필요로 함, 내부적으로 int y가 선언되고 거기에 5라는 값이 복사됨
// 주소가 다르기 때문에 함수 안에서 무슨 짓을 하더라도 main에 영향을 줄 수 없음
cout << "In func " << y << " " << &y << endl;
}
int main()
{
doSomething(5);
int x = 6;
cout << "In main " << x << " " << &x << endl;
// x라는 변수가 아니고, x 안에 있는 6이라는 value(값)만 argument로써 parameter로 전달
doSomething(x);
doSomething(x + 1);
return 0;
}
참조에 의한 인수 전달
Passing Arguments by Reference (Call by Reference)
#include <iostream>
#include <cmath> // sin(), cos()
#include <vector>
using namespace std;
void addOne(int &y) // int y라면 아무런 의미 없는 함수
{
cout << y << " " << &y << endl;
y += 1;
}
// return값을 여러 개 줘야 할 때, reference 사용
// 입력을 보통 앞에, 출력을 reference로 뒤에
void getSinCos(const double degrees, double &sin_out, double &cos_out)
{
static const double pi = 3.141592; // static: 함수 안에서 재사용
//static const double pi = 3.141592 / 180.0; // 나누기 연산 한 번만 진행
const double radians = degrees * pi / 180.0;
//const double radians = degrees * pi;
sin_out = std::sin(radians);
cos_out = std::cos(radians);
}
// const 없이는 리터럴만 받지 못함
// ex) foo(6); //error!
void foo(const int &x)
{
cout << x << endl;
}
// int *&ptr == (int*) &ptr
void foo2(int *&ptr)
{
cout << ptr << " " << &ptr << endl;
}
void printElements(const vector<int>& arr)
//void printElements(int (&arr)[4])
{
}
int main()
{
int x = 5;
cout << x << " " << &x << endl;
addOne(x); // x라는 변수 자체가 넘어감
cout << x << " " << &x << endl;
cout << endl;
double sin(0.0);
double cos(0.0);
getSinCos(30.0, sin, cos);
cout << sin << " " << cos << endl;
cout << endl;
foo(6);
cout << endl;
int *ptr = &x;
foo2(ptr);
cout << ptr << " " << &ptr << endl;
//int arr[]{ 1,2,3,4 };
vector<int> arr{ 1,2,3,4 };
printElements(arr);
return 0;
}
주소에 의한 인수 전달
Passing Arguments by Address (Call by Address)
#include <iostream>
using namespace std;
typedef int* pint;
//void foo(pint ptr) 주소라는 값을 값에 의한 전달한 것, 주소값이 복사가 됨! 포인터 변수도 변수다
void foo(int *ptr)
{
cout << *ptr << " " << ptr << " " << &ptr << endl;
//*ptr = 10; // const int *ptr의 경우, error!
}
void foo2(double degrees, double *sin_out, double *cos_out)
{
// 같은 변수가 넘어온 것이 아님! main 변수의 주소와 parameter의 주소는 다름
// => 값에 의한 전달
*sin_out = 1.0;
*cos_out = 2.0;
}
void foo3(const int *ptr, int * const ptr2, int *arr, int length)
{
for (int i = 0; i < length; i++)
cout << arr[i] << endl;
arr[0] = 1.0; // 메모리 주소에 접근해서 값을 변경, []는 de-referencing
//*ptr = 1.0; // error!
*ptr2 = 1.0;
int x = 1;
ptr = &x; // 가능!
//ptr2 = &x; // error!
// 구현하는 중간에 주소를 바꾸는 경우는 적음
}
int main()
{
int value = 5;
cout << value << " " << &value << endl;
int *ptr = &value;
foo(ptr);
foo(&value);
//foo(5); // error! literal이라서 주소가 없음
cout << &ptr << endl; // 함수에서 찍은 것과 주소 다름!
double degrees = 30;
double sin, cos;
foo2(degrees, &sin, &cos); // 주소로 넣어 줘야 함
cout << sin << " " << cos << endl;
return 0;
}
다양한 반환 값들(값, 참조, 주소, 구조체, 튜플)
#include <iostream>
#include <array>
#include <tuple>
using namespace std;
struct S
{
int a, b, c, d;
};
int getValue(int x)
{
int value = x * 2;
return value;
}
int* getValue2(int x)
{
int value = x * 2;
return &value;
}
int* allocateMemory(int size)
{
return new int[size]; // 주소 반환
}
int& getValue3(int x)
{
int value = x * 2;
return value;
}
int& get(std::array<int, 100>& my_array, int idx)
{
return my_array[idx]; // return reference
}
S getStruct()
{
S my_s{ 1,2,3,4 };
return my_s;
}
std::tuple<int, double> getTuple()
{
int a = 10;
double d = 3.14;
return std::make_tuple(a, d);
}
int main()
{
// return by value
int value = getValue(3); // getValue(int)에서 return된 값이 복사되어 들어감
// return by address
int value2 = *getValue2(3); // 권장X, getValue2(int)의 value는 함수가 끝나면 사라짐! 안전하지 못함
int *value3 = getValue2(3); // 더 위험함!!
// warning C4172: returning address of local variable or temporary: value
//int *array = new int[10];
int *array = allocateMemory(1024);
delete[] array;
// return by reference
int value4 = getValue3(5);
int &value5 = getValue3(5); // reference로 받으면? 함수가 끝나면 사라지는 지역변수의 주소값을 받음
// warning C4172: returning address of local variable or temporary: value
cout << value5 << endl;
cout << value5 << endl; // 쓰레기값 호출
std::array<int, 100> my_array; // 메모리가 명확하게 잡혀 있음
my_array[30] = 10;
get(my_array, 30) *= 10; // 변수인 것처럼 사용 가능
cout << my_array[30] << endl;
cout << endl;
// return struct
// 구조체를 만들 때마다 함수를 만들어야 함
S my_s = getStruct();
my_s.b;
// return tuple
std::tuple<int, double> my_tp = getTuple();
cout << std::get<0>(my_tp) << endl; // a
cout << std::get<1>(my_tp) << endl; // d
// C++ 11
auto[a, d] = getTuple(); // 각각 변수를 선언하면서 받아 줌
cout << a << endl;
cout << d << endl;
return 0;
}
인라인 함수
inline functions
#include <iostream>
using namespace std;
inline int min(int x, int y)
{
return x > y ? y : x;
}
int main()
{
// 프로그램 실행 시, 함수를 가져오고 변수 x,y를 선언하고 계산 후 return 받아서 출력하는 과정을 겪음
// 짧은데 자주 사용되는 함수의 경우, 이러한 과정을 줄이기 위해 인라인 함수를 사용하기도 함(주로 헤더파일)
cout << min(5, 6) << endl;
cout << min(3, 2) << endl;
// 다음과 같이 작동함
cout << (5 > 6 ? 6 : 5) << endl;
cout << (3 > 2 ? 2 : 3) << endl;
// inline: 강제로 이 함수를 인라인으로 해라!(X) 이렇게 할 수 있으면 해 주세요(O)
// 요즘 컴파일러들이 자동으로 inline 처리 해 주기도 한다
// 인라인 함수가 엄청 많고, 컴파일러가 모두 인라인 처리한다면 프로그램이 커짐!
return 0;
}
함수 오버로딩
function overloading: 동일한 이름의 함수를 여러 개 만드는 것
#include <iostream>
#include <string>
using namespace std;
int addInt(int x, int y)
{
return x + y;
}
int addDouble(double x, double y)
{
return x + y;
}
// 함수 오버로딩!
int add(int x, int y)
{
return x + y;
}
double add(double x, double y)
{
return x + y;
}
/*
error! return 타입만이 다르다면 오버로딩 불가능!
int add(double x, double y)
{
return x + y;
}
*/
// 매개변수로 값을 돌려받는 방법
void getRandom(int &x) {}
void getRandom(double &x) {}
typedef int my_int;
// 같은 타입으로 인식됨! error C2084: function 'void print(int)' already has a body
void print1(int x) {}
void print1(my_int x) {}
void print2(char *value) {}
void print2(int value) {}
void print2(const char *value) {} // "a" 예제 해결!
void print3(unsigned int value) {}
void print3(float value) {}
int main()
{
addInt(1, 2);
addDouble(1.0, 2.0);
// int인지 double인지 신경 쓰고 싶지 않아 => 매개변수는 다르지만 작동이 같은 경우, 오버로딩 사용
add(1, 2);
add(3.0, 4.0);
// 이름이 같아도 매개변수가 다르면 다른 함수로 동작함
// 주어진 인자와 가장 잘 맞는 매개변수인 함수를 찾아서 실행함
// 따라서 여기서 어떤 add를 사용할지 '컴파일 타임'에 결정되어야 함
int x;
getRandom(x); // 입력인지, 함수에 값만 전달하는지 헷갈림
// int x = getRandomInt(x); // return type int인 경우
// int x = getRandom(int());
print2(0); // print(int)
print2('a'); // print(int)
//print2("a"); // error!
// error C2668: 'print3': ambiguous call to overloaded function
//print3('a');
//print3(0);
//print3(3.141592);
// 의도대로 명확하게 적으면 모호성을 제거할 수 있음! => 애초에 오버로딩할 때 명확하게 구현하기
// 이름으로 구분하는 게 억지로 사용하는 것보다 좋은 경우도 있음, 주석 달기
print3((unsigned int)'a');
print3(0u);
print3(3.141592f);
return 0;
}
매개변수의 기본값
#include <iostream>
#include <string>
using namespace std;
// 기본값을 설정할 경우, 한 군데서만 해야 함!! (declaration or definition)
void print(int x = 10, int y = 20, int z = 30); // in header, 보통 여기에 많이 설정함
//void print(int x, int y = 20, int z) error!
void print(int x, int y, int z)
{
cout << x << " " << y << " " << z << endl;
}
void print2(std::string str) {}
void print2(char ch = ' ') {}
void print3(int x) {}
void print3(int x, int y = 20) {}
int main()
{
print();
print(100);
print(100, 200);
print(100, 200, 300);
print2(); // char 타입
//print3(10); // error! error C2668: 'print3': ambiguous call to overloaded function
return 0;
}
함수 포인터
#include <iostream>
#include <array>
#include <functional>
using namespace std;
int func(int x)
{
return 5;
}
int goo()
{
return 10;
}
bool isEven(const int &number)
{
if (number % 2 == 0) return true;
else return false;
}
bool isOdd(const int &number)
{
if (number % 2 != 0) return true;
else return false;
}
//void printNumbers(const array<int, 10> &my_array , bool print_even)
void printNumbers(const array<int, 10> &my_array,
bool (*check_fcn)(const int &) = isEven) // 기본값 설정도 가능
{
for (auto element : my_array)
{
if (check_fcn(element) == true) cout << element << " ";
//if (!print_even && element % 2 == 1) cout << element << " ";
}
cout << endl;
}
typedef bool(*check_fcn_t)(const int &);
//using check_fcn_t = bool(*)(const int &); // using도 가능
void printNumbers2(const array<int, 10> &my_array,
check_fcn_t check_fcn = isEven) // typedef 및 using 사용 가능
{
// ...
}
void printNumbers3(const array<int, 10> &my_array,
std::function<bool(const int &)> fcnptr)
{
// ...
}
int main()
{
func(1); // 이 함수가 어느 주소에 있는지 알아내고, 그 프로그램을 실행시킴
cout << func << endl; // 함수의 주소 출력
int(*fcnptr)(int) = func; //fcnptr은 변수명임, 변경 가능
cout << fcnptr(1) << endl; // 함수 실행
//fcnptr = goo; func() => func(int) 수정 후에는 error!
//cout << fcnptr() << endl;
std::array<int, 10> my_array = { 0,1,2,3,4,5,6,7,8,9 };
printNumbers(my_array);
printNumbers(my_array, isOdd);
// C++ 11
std::function<bool(const int &)> fcnptr = isEven;
printNumbers3(my_array, fcnptr);
return 0;
}
스택과 힙 the stack and the heap
메모리는 여러 구역으로 나누어 사용된다.
code: 우리가 작성한 프로그램
bbs: uninitialized data segment, 0으로 초기화된 global & static 변수
data: initialized data segment, global & static 변수
stack: 지역변수, 비교적 속도가 빠름, 사이즈가 작음(stack overflow)
heap: 사이즈가 큼, 함수에서 delete를 하지 않으면 heap의 메모리를 가지고는 있으나 사용하지 못하게 됨(메모리 누수)
std::vector를 스택처럼 사용하기
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// int *v_ptr = new int[3]{1, 2, 3}; 와 비슷할 것임
std::vector<int> v{ 1, 2, 3 };
v.resize(2);
// size, capacity? capacity만큼의 용량을 가지고 있고 그 중 size만 사용한다
for (auto &element : v)
cout << element << " ";
cout << endl;
cout << v.size() << " " << v.capacity() << endl; // 출력: 2 3
//cout << v[2] << endl; runtime error!
//cout << v.at(2) << endl; runtime error!
int *ptr = v.data();
cout << ptr[2] << endl; // 출력: 3
// 직접 동적 메모리 할당시 resize를 하려면,
// 메모리 2개를 받아 놓고 3개 중 2개를 복사하고, 원래 3개짜리를 delete해야 함
// vector의 경우, 작은 쪽으로 리사이즈할 때 메모리는 그대로 가지고 있되 접근을 차단함
// reserve: 메모리의 용량을 미리 확보해 놓음
// 뒤에 새로 원소를 추가할 때, 따로 메모리 확보하는 과정을 거칠 필요가 없기 때문에 속도가 빠름
v.reserve(1024);
for (unsigned int i = 0; i < v.size(); i++)
cout << v[i] << " ";
cout << endl;
cout << v.size() << " " << v.capacity() << endl;
return 0;
}
#include <iostream>
#include <vector>
using namespace std;
void printStack(const std::vector<int> &stack)
{
for (auto &e : stack)
cout << e << " ";
cout << endl;
}
int main()
{
std::vector<int> stack;
//stack.reserve(1024);
stack.push_back(3);
printStack(stack);
stack.push_back(5);
printStack(stack);
stack.push_back(7);
printStack(stack);
stack.pop_back();
printStack(stack);
stack.pop_back();
printStack(stack);
stack.pop_back();
printStack(stack);
return 0;
}
재귀적 함수 호출
Recursive Function Call
#include <iostream>
using namespace std;
void countDown(int count)
{
// 같은 코드를 따로따로 실행시키고 있는 것, 코드는 다른 곳에 저장되어 있고 주소를 찾아서 호출함
// 스택 오버플로우 주의!
cout << count << endl;
if(count > 0)
countDown(count - 1);
}
int sumTo(int sumto)
{
if (sumto <= 0)
return 0;
else if (sumto <= 1)
return 1;
else
return sumTo(sumto - 1) + sumto;
}
int main()
{
countDown(2);
cout << sumTo(10) << endl;
return 0;
}
#include <iostream>
using namespace std;
// 0 1 2 3 5 8 13 21 ... fibo
int fibo(int num)
{
if (num == 0) return 0;
else if (num == 1) return 1;
else return fibo(num - 1) + fibo(num - 2);
}
int main()
{
int num;
for (int i = 0; i <= 20; i++)
{
cout << fibo(i) << endl;
}
return 0;
}
방어적 프로그래밍의 개념
#include <iostream>
#include <string>
using namespace std;
int main()
{
// syntax error
//int x
// semantic errors
int x;
cin >> x;
if (x >= 5) // x > 5
cout << "x is greater than 5" << endl;
// violated assumption
string hello = "Hello, my name is Jack jack";
/*
int ix;
cin >> ix; // index보다 큰 수가 들어오면 런타임 에러!
cout << hello[ix] << endl;
*/
cout << "Input from 0 to " << hello.size() - 1 << endl;
while (true)
{
int ix;
cin >> ix; // index보다 큰 수가 들어오면 런타임 에러!
if (ix >= 0 && ix <= hello.size() - 1)
{
cout << hello[ix] << endl;
break;
}
else
cout << "Please try again" << endl;
}
return 0;
}
단언하기 assert
디버깅 시, 컴파일러 도움을 받을 때 사용
release 모드에서는 아무것도 일어나지 않음, runtime에 체크함
#include <iostream>
#include <cassert> // assert.h
#include <array>
using namespace std;
void printValue(const std::array<int, 5> &my_array, const int& ix)
{
assert(ix >= 0);
assert(ix <= my_array.size() - 1);
cout << my_array[ix] << endl;
}
int main()
{
int number = 5;
// ...
// 주석만 남겨 놓는다면 직접 값을 찍어 봐야 함
assert(number == 5);
std::array<int, 5> my_array{ 1,2,3,4,5 };
printValue(my_array, 100);
int x = 5;
const int x2 = 5;
//assert(x == 5); // 가능!
//static_assert(x == 5); // error! 컴파일 타임에 달라질 수 있기 때문에 불가능
static_assert(x2 == 5, "x should be 5!");
return 0;
}
명령줄 인수 command line arguments
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
for (int count = 0; count < argc; count++)
{
cout << argv[count] << endl;
}
return 0;
}
프로젝트 우클릭 > Properties > Debugging
#include <iostream>
#include <string>
using namespace std;
int main(int argc, char *argv[])
{
for (int count = 0; count < argc; count++)
{
std::string argv_single = argv[count];
if (count == 1)
{
int input_number = std::stoi(argv_single);
cout << input_number + 1 << endl;
}
else
cout << argv_single << endl;
}
return 0;
}
생략부호 Ellipsis
#include <iostream>
#include <cstdarg> // for ellipsis
using namespace std;
// count: parameter로 들어올 argument들의 개수
// 사용하기 위험하고 디버깅 힘듦...
double findAverage(int count, ...)
{
double sum = 0;
va_list list;
va_start(list, count);
for (int arg = 0; arg < count; arg++)
sum += va_arg(list, int); // int로 변환
va_end(list);
return sum / count;
}
int main()
{
cout << findAverage(1, 1, 2, 3, "Hello", 'c') << endl; // 1만 들어감
cout << findAverage(3, 1, 2, 3) << endl;
cout << findAverage(5, 1, 2, 3, 4, 5) << endl;
cout << findAverage(10, 1, 2, 3, 4, 5) << endl; // error! 개수 맞춰 줘야 함
return 0;
}
해당 포스트는 '홍정모의 따라하며 배우는 C++' 강의를 수강하며 개인 백업용으로 메모하였습니다.