인라인함수 Inline Functions

사용법 : 함수앞에 inline을 붙여준다.

기존 함수호출시
1.함수를 호출한다.
2.함수를 호출하면 파라미터에 데이터를 복사한다.
3.함수의 로직을 실행하고 반환값을 반환해준다.

inline함수 호출시
inline함수를 사용하면 컴파일할때 로직을 바로 실행한다.

강제로 로직을 실행시키는게 아닌 컴파일러에게 권유?한다고 생각하면된다.
또, 최근 컴파일러는 inline이 없어도 논리적으로 inline이 낫다고 생각하면 그렇게 컴파일한다.

이런게 있다는것만 알아두자.

#include <iostream>

using namespace std;

inline int min(int x, int y) {
	return x > y ? y : x;
}

int main()
{
	cout << min(3,5) << endl;

	//inline함수는 아래 처럼 바로실행된다고 생각하면된다.
	cout << (3 > 5 ? 5 : 3) << endl;
	return 0;
}

반환값

return by value
리턴타입으로 선언된 자료형을 반납해준다.
단점은 변수가 여러번 생성된다.

return by address
리턴타입으로 포인터형식 즉 주소값을 받는다.
동적할당할때 사용할 수 있다.
단점, delete위치가 애매하다.
주소값만 받아오기에 안에 값이 다를 수 있다.

return by reference
변수를 선언해서 받으면 문제없이 받아올 수 있다.
받는쪽에서 참조변수로 받으면 문제가 될 수 있다.
메모리가 안정적으로 선언된 변수,자료형에서는 사용하기 용이하다.

반환값 여러개 받기
구조체를 선언해 여러개를 받는 방법이 있다.
단점은 함수하나를 만들때마다 구조체를 만들어줘야한다.
튜플tuple을 사용하는 법이있다.
get<0>(튜플명)으로 받아야하는 귀찮음이 있지만
c++ 17에선 auto로 받을 수 있다.

 

#include <iostream>
#include <tuple>

using namespace std;

int returnByValue(int a) {
	a += 4;
	cout << "value : " << &a << " a : " <<a << endl;
	return a;
}


int* returnByAddress(int size) {
	return new int[size];
}

int& returnByReference(int a) {
	int b = 10;
	cout << "reference : " << &b << " b : " << b << endl;
	return b;
}

tuple<int,double> getTuple()
{
	int a = 10;
	double b = 3.14;
	return make_tuple(a, b);
}

int main()
{
	//Return By Value
	int val = returnByValue(3);
	cout << "&val : " << &val << " val : " << val << endl;
	
	//동적할당 포인터 반환
	int *ptr = returnByAddress(3);
	cout << "ptr : " << ptr << endl;

	int rVal = returnByReference(3);
	cout << "&rVal : " << &rVal << endl;
	cout << rVal << endl;
	cout << rVal << endl;
	
	int &rVal2 = returnByReference(3);
	cout << "&rVal2 : " << &rVal2 << " rVal2 : "<<rVal2 << endl;
	val = returnByValue(5);
	cout << "&val : " << &val << " val : " << val << endl;
	
	//튜플사용하기
	tuple<int, double> my_t = getTuple();
	cout << get<0>(my_t) << endl;
	cout << get<1>(my_t) << endl;

	//c++ 17 에서 사용가능
	//auto[a, b] = getTuple();
	return 0;
}

주소에 의한 인수전달
Call By address

포인터를 통해 주소를 전달하는걸 말한다.
포인터 변수도 주소값을 담는 변수다. 
즉, 파라미터로 가면 포인터변수가 복사된다.
포인터 자체의 주소는 달라지고 포인터가 가르키고있는 주소는 같다.

주소에 접근해 값을 바꿀 수 있기에 포인터를 사용해 함수에 출력인것 처럼 사용할 수 있다.
const를 사용하면 값변경이 불가능하다.

Call By Reference와 비슷해보이지만 다르다. 오히려 Call By Value와 비슷하다.
다만 메모리 주소를 통해 접근할 수 있다.

 

#include <iostream>

using namespace std;

void ptrTest(const int *ptr) {
	cout << "func() &ptr " << &ptr <<" "<< ptr << " " << *ptr << endl;
}

int main()
{
	int value = 5;
	cout << value << " " << &value << endl;
	int *ptr = &value;

	ptrTest(&value);

	//main()과 func()의 포인터 주소가 다르다.
	cout << "main() &ptr " << &ptr << endl;
	
	ptrTest(ptr);

	return 0;
}

참조에 의한 인수전달
Call by reference

참조에 의한 인수전달은 메모리 주소를 그대로 넘긴다.
변수를 그대로 사용한다고 생각하면 된다.

 

함수를 리턴 타입을 정해 받으면 하나만 값을 받을수 있다.
함수의 출력을 가지고 올때도 사용한다.

참조를 사용하면 여러개의 데이터의 값을 변경할 수 있다.


참조할때 단점
리터럴은 참조파라미터로 사용이 불가능하다.(const사용하면 가능)

포인터도 참조가 가능하다.
파라미터 받을때 int* &ptr형식으로 하면 가능하다.

정적배열로 참조로 가능한데 거의 사용가능하지않는다.
vector(동적할당배열)를 사용하는 경우가 많다.

#include <iostream>
#include <cmath>

using namespace std;

void addOne(int &y) {
	//Call by Value라면 아무의미가 없다.
	cout <<"y : " << y << " &y : " << &y << endl;
	y = y + 1;
}


void addOnePointer(int* &y) {
	//Call by Value라면 아무의미가 없다.
	cout << "y : " << y << " &y : " << &y << endl;
	y = y + 1;
}

//2개의 데이터 값 변경
void getSinCos(const double degrees, double &sin_out, double &cos_out) {
	static const double pi = 3.141592;
	double radians = degrees * pi / 180.0;
	sin_out = std::sin(radians);
	cos_out = std::cos(radians);
}

int main()
{
	//리터럴은 불가능하다.
	//addOne(7);
	
	int x = 5;
	cout << "x : " << x << " &x : " << &x << endl;
	addOne(x);
	cout << "x : " << x << " &x : " << &x << endl;

	double sin_out (0.0);
	double cos_out (0.0);

	//sin_out,cos_out을 참조해 두개 변수 모두 값이 변경된다.
	getSinCos(30.0, sin_out, cos_out);
	cout << "sin_out : " << sin_out << endl;
	cout << "cos_out : " << cos_out << endl;

	//포인터 reference보내기
	int *ptr = &x;
	cout << "ptr : " << ptr << " &ptr : " << &ptr << endl;
	addOnePointer(ptr);


	return 0;
}

Parameter 매개변수와 Argument 인자

매개변수와 인자의 용어차이

매개변수는 함수의 기능을 바꾸어주는 기능을 한다.
매개변수는 함수가 끝나는 동시에 메모리에 반납이 된다.
즉, 새로운 변수가 생성되어 데이터가 복사되는 것이다.

인자는 함수를 사용할때 들어가는 변수,리터럴을 말한다.

#include <iostream>

using namespace std;

int foo(int a, int b);

//함수에서 사용되는 변수가 매개변수 parameter이다.
int foo(int a, int b) {
	cout << "a : " << a << " b : " << b << endl;
	return a + b;
}

int main()
{
	int a = 3, b = 4;

	//함수를 불러올때 사용되는 변수가 인자 argument이다.
	foo(6, 7);
	foo(a, b);

	return 0;
}

 

 

값에 의한 인수 전달
Call by value

함수는 파라미터가 어떤게 들어오는지에따라 결과가 달라진다.
함수를 사용할때 선언된 변수를 인수에 넣게되면 변수를 함수로 보내는게 
아닌 변수주소안의 데이터를 복사해서 보낸다.

그러므로 인자안에서 사칙연산이 가능한 것이다.

Call by value는 함수안에서 생긴결과가 밖으로 영향주지 못한다.

#include <iostream>

using namespace std;

void doSomething(int y) {
	cout << "In fuc " << y << " " << &y << endl;
}

int main()
{
	//리터럴 7이 함수에 복사한다.
	doSomething(7);

	int y = 6;
    //doSomething(y);안의 변수 주소와 다른걸 볼 수 있다.
	cout << "In main " << y << " " << &y << endl;
	doSomething(y);
	doSomething(y+1);

	return 0;
}

정적배열에선 std::array를 사용가능하다. 

기존 정적배열은 함수 파라미터로 배열을 보내면 사이즈를 측정할 수 없었다.

array를 사용하면 정적배열을 간단히 사용할 수 있다.
.size()를 사용하면 사이즈가 출력된다.
파라미터로 복사되어도 그대로 출력된다.
파라미터는 복사되기때문에 array가 크기가 크면 reference &를 사용하는게 좋다.

.at()을 사용하면 사이즈오버를 미리 체크해준다.

#include <iostream>
#include <array>
#include <algorithm>

using namespace std;

void arrCheck(const array<int, 5> arr) {
	cout << &arr << endl;
	for (int i = 0; i < arr.size(); i++) {
		cout << arr[i] << endl;
		cout << arr.at(i) << endl;
	}
}

void arrRefCheck(const array<int,5> &arr) {
	cout << &arr << endl;
	for (int i = 0; i < arr.size(); i++) {
		cout << arr[i] << endl;
		cout << arr.at(i) << endl;
	}
}

int main()
{
	//int array2[5] = {};

	array<int, 5> arr = {1,10,3,7,5};
	cout << "arrSize() : " << arr.size() << endl;
	cout <<"main() : "<< &arr << endl;
	arrCheck(arr);
	arrRefCheck(arr);

	//sort
	std::sort(arr.begin(), arr.end());
	arrCheck(arr);
	arrRefCheck(arr);

	//sort 역순
	std::sort(arr.rbegin(), arr.rend());
	arrCheck(arr);
	arrRefCheck(arr);

	return 0;
}



std::vector는 동적 array를 대체해서 손쉽게 사용가능하다.
std::vector는 동적할당이 되기때문에 처음부터 사이즈를 정해주지 않아도 된다.

std::array와 같이 .size(),.at()이 사용가능 하다.
동적할당이므로 .resize()로 크기를 변경할 수 있다.

동적할당은 delete를 해야하는데 vector은 자동으로 사라진다.

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	std::vector<int> array;
	
	std::vector<int> array2 = { 1,2,3,4,5 };
	cout << array2.size() << endl;

	vector<int> arr = { 1,2,3,4 };
	for (auto &a : arr) {
		cout << a << endl;
	}

	return 0;
}



포인터도 변수다 그러므로 메모리주소를 가지고 있다.

즉, 변수이기에 포인터에대한 포인터를 생성할 수 있다.

2중포인터 기본개념

 

int **ptr은 int *(*ptr)로 이해할 수 있다.
즉, *ptr이 가지고 있는 주소값을 또 포인터로 보게하는 것이다.

 

2중포인터 예

ptr은 int val의 주소값을 가진다.

int val = 5;
int *ptr = &val;

ptrptr은 ptr(val주소값을 가지고있는 포인터)의 주소값을 가진다.

int **ptrptr = &ptr;

de-reference도 가능하다.

cout << **ptrptr << endl;

 

동적 메모리 할당을 이용해 다차원배열 생성

 

동적 메모리 할당을 이용해 다차원배열생성을 알아보기전에

배열과 포인터는 같다는걸 잊지말자!


2차원 배열을 동적할당으로 생성한다고 보면 우선 포인터 3개를 생성한다.

int *(*rows) = new int *[3]; 
//포인터 찍어본결과 임의의 주소값이 들어있다. 


그다음 for문으로 포인터에 접근해 포인터에 배열을 선언해준다.

for (int i = 0; i < 3; i++) {
	rows[i] = new int[5]{};
}

아래 배열과 비슷하게 생성된다.

int rows[3][5] = {};


비슷하다고한 이유는?

메모리주소 형식이 다르다.
위에 선언된 정적2차원배열은 메모리주소가 이어진다.

 

하지만 동적 메모리 할당을 이용하면 메모리 주소가 이어지지 않는다.
위에 말했듯이 포인터도 메모리 주소가 있다.
동적할당 3개를 받은 포인터에 

rows[i] = new int[5]{}; 


의 새로운 포인터주소를 정의해주면 주소가 이어지진않지만 2차원배열이 생성되는걸 알 수 있다.

 

즉, 동적할당된 3개의 포인터에 새로운 배열의 주소를 정의해준다.

 

동적할당은 사용한후 메모리를 반납하는걸 잊지말자!

for (int r = 0; r < row; r++) {
	delete[] rows[r];
}
delete[] rows;

 

#include <iostream>

using namespace std;

int main()
{
	int *ptr = nullptr;
	int **ptrptr = nullptr;

	int value = 5;
	//value의 주소 ptr포인터에 저장
	ptr = &value;
	//ptr포인터의 주소 ptrptr포인터에 저장
	ptrptr = &ptr;

	cout << "==============================================" << endl;
	cout << "다중 포인터 기본예" << endl;
	cout << "value의 주소값 : " << &value << endl;
	cout << endl;

	cout << "ptr포인터의 자신의 주소값 : " << &ptr << endl;
	cout << "ptr포인터에 저장된 주소값 : " << ptr << endl;
	cout << "ptr포인터 de-reference : " << *ptr << endl;

	cout << endl;
	cout << "ptrptr포인터의 자신의 주소값 : " << &ptrptr << endl;
	cout << "ptrptr포인터에 저장된 주소값 : " << ptrptr << endl;
	cout << "ptrptr포인터 de-reference(ptr이 가지고있는 주소) : " << *ptrptr << endl;
	cout << "ptrptr포인터의 double de-reference : " << **ptrptr << endl;
	cout << endl;

	//배열과 포인터는 같다.
	int a[3] = { 1,2,3 };
	int *aa = a;
	cout << "==============================================" << endl;
	cout << "배열과 포인터 비교" << endl;
	cout << "==============================================" << endl;
	cout << "a[3]" << endl;
	for (int i = 0; i < 3; i++) {
		cout << "\t배열안의 값 a : " << a[i] << endl;
		cout << "\t배열의 주소 &a : " << &a[i] << endl;
		//cout << *ptrTest[i] << endl;
	}
	cout << "==============================================" << endl;
	cout << "*aa" << endl;
	for (int i = 0; i < 3; i++) {
		cout << "\t배열안의 값 aa : " << aa[i] << endl;
		cout << "\t배열의 주소 &aa : " << &aa[i] << endl;
		//cout << *ptrTest[i] << endl;
	}
	cout << "==============================================" << endl;

	cout << "1차배열 동적할당" << endl;
	cout << "*ptrTest = new int [3]" << endl;
	int *ptrTest = new int [3];
	for (int i = 0; i < 3; i++) {
		cout << "\t배열안의 주소 &ptrTest[i] : " << &ptrTest[i] << endl;
		cout << "\t배열안의 값 ptrTest[i] : " << ptrTest[i] << endl;
		ptrTest[i] = 3;
		cout << "\t배열안의 값 ptrTest[i] : " << ptrTest[i] << endl;
		//cout << *ptrTest[i] << endl;
	}
	cout << "==============================================" << endl;
	cout << "2차배열 동적할당" << endl;
	cout << "int *(*ptrTest2) = new int *[3]" << endl;
	
	int *(*ptrTest2) = new int *[3];
	cout << "ptrTest2 : " << ptrTest2 << endl;
	for (int i = 0; i < 3; i++) {
		cout << "\t배열안의 주소 &ptrTest2[i] : " << &ptrTest2[i] << endl;
		cout << "\t배열 동작할당 전 ptrTest2[i]: " << ptrTest2[i] << endl;
		//에러가난다
		//cout << "\t배열 동작할당 후 *ptrTest2[i]: " << *ptrTest2[i] << endl;
		//cout << *ptrTest2[i] << endl;
		ptrTest2[i] = new int[5]{};
		cout << "\t배열 동작할당 후 ptrTest2[i]: " << ptrTest2[i] << endl;
		cout << "\t배열 동작할당 후 *ptrTest2[i]: " << *ptrTest2[i] << endl;
	}
	cout << "==============================================" << endl;
	
	const int row = 3;
	const int col = 5;

	int (*arr2)[col] = new int [row][col];
	for (int i = 0; i < row; i++) {
		cout << "상수로 동적할당" << endl;
		cout << &arr2[i] << endl;
		for (int s = 0; s < col; s++) {
			cout << &arr2[i][s] << endl;
		}
	}
	cout << "==============================================" << endl;
	int **arr3 = new int *[row];
	for (int i = 0; i < row; i++) {
		cout << "다중포인터 주소" << endl;
		cout << &arr3[i] << endl;
	}
	cout << "==============================================" << endl;
	for (int i = 0; i < row; i++) {
		cout << "다중포인터 생성" << endl;
		arr3[i] = new int[5]{};
		for (int s = 0; s < col; s++) {
			cout << &arr3[i][s] << endl;
		}

	}
	cout << "==============================================" << endl;
	int arr[row][col] = 
	{ 
	{1,2,3,4,5},
	{},
	{} 
	};
	
	int *(*rows) = new int *[row];
	cout << "rows에 저장된 주소값 : " << rows << endl;
	cout << " *de-reference : " << *rows << endl;
	cout << "&포인터의 주소값 : " << &rows << endl;
	cout << endl;

	
	//초기화
	for (int r = 0; r < row; r++) {
		rows[r] = new int[col];
		cout << "초기화" << endl;
		cout << "&rows[" << r << "] : " << &rows[r] << endl;
		cout << "rows[" << r << "] : " << rows[r] << endl;
		int *a = &rows[r][0];
		cout << "a : " << a << endl;
		cout << "rows[" << r << "][0] : " << rows[r][0] << endl;

	}
	cout << endl;

	cout << "*de-reference : " << *rows << endl;
	cout << "**de-reference : " << **rows << endl;
	cout << "pointer : " << rows << endl;
	cout << endl;

	for (int r = 0; r < row; r++) {
		cout << "&rows["<< r <<"] : " << &rows[r] << endl;
		
		cout << endl;
		for (int i = 0; i < col; i++) {
			rows[r][i] = arr[r][i];
			cout << "&rows[" << r << "]["<< i <<"]" << &rows[r][i] << endl;
			cout << "rows[" << r << "][" << i << "]" << rows[r][i] << endl;
		}

		cout << "===================================" << endl;
	}
	//메모리 제거
	for (int r = 0; r < row; r++) {
		delete[] rows[r];
	}
	delete[] rows;

	//다중배열 사용하지 않기
	int *arrx2 = new int[row*col];
	for (int r = 0; r < row; r++) {
		for (int i = 0; i < col; i++) {
			cout << " i + col * r : " << i + col * r << endl;
			arrx2[i + col * r] = arr[r][i];
		}
	}

	
	for (int r = 0; r < row; r++) {
		for (int i = 0; i < col; i++) {
			cout << arrx2[i + col * r] << " ";
		}
		cout << endl;
	}

	delete[] arrx2;

	return 0;
}

포인터는 주소를 저장한다.

size에 상관없이 주소는 같다.
데이터 타입이 다르더라도 주소형식은 같다. 

자료형과 상관없이 포인터(주소값)저장할때 사용한다.

void pointer는 generic pointer 라고한다.

	void *ptr = nullptr;
	ptr = &i;



void pointer의 한계
*를 사용한 de-reference가 안된다.
자료형을 알 수 없어 +1연산이 안된다.

void포인터형에서 값을 알려면 casting을 해야한다.

	cout << *(static_cast<int*>(ptr)) << endl;


void포인터 사용이유
과거 다형성 구현할때 사용됬었다.

#include <iostream>

using namespace std;


enum Type {
	INT,
	FLOAT,
	CHAR,
};
int main()
{

	int i = 5;
	float f = 3.4f;
	char c = 'c';

	void *ptr = nullptr;

	ptr = &i;
	ptr = &f;
	ptr = &c;

	//자료형을 알 수 없어 연산할 수 없다.
	//cout << ptr + 1 << endl;
	ptr = &i;
	cout << *(static_cast<int*>(ptr)) << endl;
	
	return 0;
}

기존for문은 조건을  입력해주어햐한다.
for-each 반복문을 사용하면 반복문을 실행시킬때 훨씬 간단히 사용할 수 있다.

for-each조건문 사용하면 자료형만 알면 간단히 반복문을 작성할 수 있다.(auto사용가능)

	for (int num : arr) {
		cout << num << endl;
	}


for-each로 array의 내용을 바꾸고 싶다면 &참조변수를 사용해야한다.

	for (int &num : arr) {
		num = 10;
	}


array를 동적할당하면 for-each를 사용 불가능하다.
vector벡터를 사용하면 가능하다.
vector는 동적할당 배열을 사용하기 쉽도록 std안에 들어가 있는것이다.

	vector<int> arr2 = { 1,2,3,4,5,6,7 };
	for (int num : arr2) {
		cout << num << endl;
	}

for-each문은 가시적으로 보기도 좋다.

#include <iostream>
#include <vector>

using namespace std;

int main()
{
	int arr[] = { 1,2,3,4,5,6,7,8,9 };
	for (int num : arr) {
		cout << num << endl;
	}

	//값 변경하기
	for (int &num : arr) {
		num = 10;
	}
	//10으로 변경됬는지 확인
	for (int num : arr) {
		cout << num << endl;
	}
	
	vector<int> arr2 = { 1,2,3,4,5,6,7 };
	for (int num : arr2) {
		cout << num << endl;
	}


	return 0;
}

 

포인터와 참조를 통해 구조체나 클래스의 멤버에 접근할 수 있다.

포인터는 ->를 사용하여 멤버에 접근한다.
(*포인터명)으로 접근할 수 있으나 잘 사용하지 않는다.

	Person *ptr = &person;
	ptr->age = 30;
	(*ptr).age = 40;


참조는 기존 구조체,클래스와 같이 사용할 수 있다.
참조변수명.멤버 형식으로 사용가능하다.

	Person &ref = person;
	ref.age = 20;


참조에 포인터를 de-reference해서 값을 넣어줄 수 있다.

	Person ref2 = *ptr;
	ref2.age = 50;

포인터,참조사용 멤버접근 cout찍어보기

#include <iostream>

using namespace std;

struct Person {
	int age;
	double weight;
};

int main()
{
	Person person;
	cout << "addr : " << &person << endl;
	//멤버 선택 연산자
	person.age = 10;
	

	//참조 주소값 가져오기
	Person &ref = person;
	ref.age = 20;
	cout << "addr ref : " << &ref << endl;

	Person *ptr = &person;
	cout << "addr &ptr : " << &ptr << endl;
	cout << "addr ptr : " << ptr << endl;
	ptr->age = 30;
	//()를 꼭 해줘야한다.
	(*ptr).age = 40;

	Person ref2 = *ptr;
	cout << "addr ref2 : " << &ref2 << endl;
	ref2.age = 50;

	return 0;
}

+ Recent posts