참조변수 Reference variable

꼭 포인터를 사용하여 * 사용하지 않고 참조변수 &를 사용할 수 있다.

참조변수는 참조하고 있는 변수의 주소를 공유한다.
즉, 참조는 변수의 또다른 별명처럼 사용할 수 있다.
참조변수는 반드시 초기화를 해야한다(리터럴은 불가능).

함수에서도 참조변수(reference)를 활용할 수 있다.
파라미터를 복사를 할 필요가 없어 빠르다.
또 주소를 전송하기에 함수에서 값을 변경할 수 있다.

#include <iostream>

using namespace std;

void doSomething(int &n)
{
	n = 10;
	cout << "In doSomething : " << n << endl;
	cout << "In doSomething : " << &n << endl;
}
void doSomethingPointer(int *n)
{
	*n = 10;
	cout << "In doSomethingPointer : " << n << endl;
	cout << "In doSomethingPointer : " << &n << endl;
}

struct SubA {
	int v1;

};

struct A {
	int v1;
	SubA subA;
};

int main()
{

	A a;
	a.subA.v1 = 3;
	cout << a.subA.v1 << endl;
	//구조체에서 참조를 활용할 수 있다.
	int &v1 = a.subA.v1;
	v1 = 4;
	cout << a.subA.v1 << endl;


	int val = 5;
	//파라미터로 int자료형을 보내면 복사가 된다.
	doSomething(val);
	cout << val << endl;
	cout << &val << endl;

	//파라미터로 참조변수를 보내면 복사가 되지 않는다.
	//즉, 값이 변경 가능하다.
	doSomethingPointer(&val);
	cout << val << endl;
	cout << &val << endl;

	//포인터에서 참조변수를 사용가능하다.
	int *ptr = nullptr;
	ptr = &val;

	//참조 
	int &ref = val;

	ref = 10;
	cout << ref << endl;
	cout << val << endl;

	//const활용
	const int val2 = 3;
	//불가능하다.
	//int &ref2 = &val2;
	const int &ref2 = val2; 

	return 0;
}

함수에 파라미터로 const 참조변수를 사용하면 편해진다.

그냥 참조변수에 대입하는 값은 L_Value만 가능하다.

const를 사용하면 L_Value만 아닌 연산도 가능해진다.

 

#include <iostream>

using namespace std;

//참조변수
void doSomething(const int &const a) {
	cout << &a << endl;
	cout << a << endl;
}


//포인터
void doSomething2(const int *const a) {
	cout << a << endl;
	cout << *a << endl;
}


int main()
{
	int val = 5;
	
	const int &ref = val;
	int &ref2 = val;

	//포인터,참조변수 두개의 차이

	//그냥 참조변수는 L_Value만 가능하다.
	//int &ref3_1 = 3 + 4;
	const int &ref3 = 3 + 4;
	cout << ref3 << endl;
	cout << &ref3 << endl;

	doSomething(val);
	//ref3의 주소값이 인자가 된다.
	doSomething(ref3);
	doSomething(1 + 3);

	//메모리주소,연산전송이 안된다.
	doSomething2(&val);
	//doSomething2(ref3);
	//doSomething2(1 + 3);
	return 0;
}

 

동적 할당 배열 Dynamically Allocation Arrays

동적 할당 배열은 메모리를 나중에 할당 받을 수 있다.
배열에 들어갈 값을 미리 정해놓을 수 있지만 할당받을 크기보다 크다면 에러가 난다.

동적 할당 배열 반납방법 :  delete[]

#include <iostream>

using namespace std;

int main()
{
	//정적 할당 배열은 컴파일될때 배열의 크기가 고정되어야한다.
	const int length = 5;
	//정적 할당 배열
	int arr[length] = { 1,2,3,4,5 };
	
	int dlen;
	cin >> dlen;

	//동적 할당 배열 받기
	int *ptrArr = new int[dlen];

	for (int i = 0; i < dlen; i++) {
		cout << "i : " << i << endl;
		ptrArr[i] = i;
		cout << (uintptr_t)&ptrArr[i] << endl;
		cout << ptrArr[i] << endl;
	}

	delete[] ptrArr;
	ptrArr = nullptr;

	return 0;
}

포인터에도 const를 사용할 수 있다.

 

const의 위치마다 결과가 다르다.


주소에 접근해 데이터를 변경할 수 없다.

const int *ptr = &value; 


메모리 주소를 변경할 수 없다.

int *const ptr = &value; 


메모리주소,주소안의 데이터를 모두 변경할 수 없다.

const int *const ptr = &value;

const활용 자세한 설명

#include <iostream>

using namespace std;

int main()
{

	int value = 5;
	

	const int *ptr2 = &value;
	cout << *ptr2 << endl;
	//역참조는 불가능하다.
	//포인터를 이용해 값을 변경하지 않겠다는 표현으로 생각할 수 있다.
	//*ptr = 6;


	int value2 = 3;
	//주소에 있는 값을 바꾸지 않겠다는 말이다.
	ptr2 = &value2;
	cout << *ptr2 << endl;
	//에러가난다.
	//*ptr2 = 6;

	//포인터 자체를 상수로 만들기
	int *const ptr = &value;
	//주소값을 변경할 수 없다.
	//ptr = &value2;

	//모든것이 변경 불가능하다.
	const int *const ptr3 = &value;
	return 0;
}

-정적배열을 선언할때 배열의 크기가 커지면 용량이 커진다 
-정적할당은 stack에 들어가고 동적할당은Heap에 들어간다. 

 

메모리 할당의 종류
정적메모리 할당 Static Memory Allocation
-전역변수,static변수 한번만들면 프로그램 종료시까지 유지된다.

자동메모리할당
-블록안에서 변수를 선언,정적배열 선언 블럭밖으로 나가면 메모리가 OS로 다시 할당된다.

동적메모리 할당 Dynamic Memory Allocation
동적 메모리 할당은 new로 할당받는다.
메모리를 할당받는것이기에 포인터로 받아야한다.
delete로 반납해주어야한다.
delete를 하더라도 메모리의 주소는 있기때문에 garbage값이 있다.

int *ptr = new int (8);


다른 프로그램이 메모리를 모두사용하고 메모리를 할당받지 못할때도있다.
- 프로그램이 죽어버릴게 짜는 방법
- 다른 프로그램의 메모리 반납을 기다리는 방법
(std::nothrow)을 사용한다.메모리 할당이 실패하면 nullptr이 할당된다.

int *ptr = new (std::nothrow)int (8);



메모리누수 memory leak
동적메모리 할당을 사용할때 반납을 잊으면 메모리 누수가 발생한다.

#include <iostream>

using namespace std;

//정적메모리 할당
int a = 0;

int main()
{

	//자동메모리 할당
	{
		int array[10];
	}

	int *ptr = new (std::nothrow)int (8);
	cout << *ptr << endl;
	//ptr이 delete가 되면 ptr2도 메모리가 반납된다.
	int *ptr2 = ptr;

	//de-reference
	*ptr = 7;

	cout << *ptr << endl;
	delete ptr;

	//delete로 메모리를 반납했기에 garvage값이 출력된다.
	//cout << *ptr << endl;
	//방지하는방법 0,NULL,nullptr을 사용한다.
	ptr = nullptr;
	if (ptr != nullptr) 
		cout << *ptr << endl;

	while (true) {
		//delete를 하면 메모리 누수memory leak이 발생한다.
		int *ptr = new int;
		cout << ptr << endl;
		delete ptr;
	}
	
	return 0;
}

포인터는 메모리의 주소를 저장하는 변수다.
포인터 연산을 하면 데이터 타입에 따라 주소를 옮겨준다.
즉, 실생활을 생각하면 지하철의 앞칸, 뒷칸의 열차번호는 현재열차번호를 알면 알 수 있다.

포인터 변수를 선언하고 변수에 연산 +1,-1해주면 앞뒤 주소를 알 수 있다.

	int val = 7;
	int *ptr = &val;

	//int형 앞뒤 주소를 알 수 있다.
	cout << uintptr_t(ptr) << endl;
	cout << uintptr_t(ptr + 1) << endl;
	cout << uintptr_t(ptr - 1) << endl;


포인터 연산으로 배열의 값과 주소를 똑같이 알아올 수 있다.
(uintptr_t)를 사용하면 16진수가 아닌 8진수로 출력된다.

cout << uintptr_t(ptr) << endl;

int형 char형 배열 포인터 연산

#include <iostream>

using namespace std;

int main()
{
	int val = 7;
	int *ptr = &val;

	cout << uintptr_t(ptr) << endl;
	cout << uintptr_t(ptr + 1) << endl;
	cout << uintptr_t(ptr - 1) << endl;

	int arr[] = { 9,7,5,3,1 };

	int *ptrArr = arr;

	for (int i = 0; i < 5; i++) {
		cout << arr[i] << " addr : " << (uintptr_t)&arr[i] << endl;
		cout << *(ptrArr + i) << " addr : " << (uintptr_t)(ptrArr + i) << endl;
	}

	char name[] = "Choi";

	const int n_name = sizeof(name) / sizeof(name[0]);
	cout << "n_name : " << n_name << endl;

	for (int i = 0; i < n_name; i++) {
		cout << *(name + i);
	}

	return 0;
}

배열문자열
char배열로 문자열을 저장할 수 있다.

char myStr[] = "string";

주의할점 : 마지막에 \0이 들어있다.
확인하는방법 : 아스키코드로 출력해보면 마지막에 0이 출력된다.

cout으로 출력하면 \0이 나타나면 출력이 멈춘다.

strcmp   : 문자열 비교할때 사용

strcpy_s : 문자열 복사할때 사용
strcat_s : 문자열 붙일때 사용

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
	char myStr[] = "string";

	for (int i = 0; i < 7; i++) {
		cout << myStr[i] << endl;
		cout <<(int) myStr[i] << endl;
	}

	myStr[255];
	cin.getline(myStr, 255);
	cout << myStr << endl;

	myStr[0] = 'A';
	cout << myStr << endl;

	int i = 0;
	while (true)
	{
		if (myStr[i] == '\0') break;

		cout << myStr[i] << " code : " << (int)myStr[i] << endl;
		i++;
	}
	
	char cpy[255];
	//strcpy(cpy, myStr);
	char myStr2[] = "Test !";
	strcpy_s(cpy, 255, myStr2);

	//문자열이 같으면 0출력 틀리면 -1출력
	cout << strcmp(cpy, myStr2) << endl;
	
	//문자열 붙이기
	strcat_s(cpy, myStr2);
	cout << cpy << endl;

	return 0;
}

문자열 심볼릭 상수

 

문자열은 기본적으로 문자의 배열이다.
배열은 포인터와 호환이 된다.

컴파일러가 헷갈리게 하는 부분을 조심하자.

심볼릭 상수는 그냥 특별하게 생각하자.

기호적인 상수로 선언할때 같은 문자열이라면 여러개 생성하지않고 같은 주소를 바라본다.

#include <iostream>

using namespace std;

int main()
{
	char a[] = "Jack Jack";
	//"Jack Jack"은 리터럴이다.
	//포인터는 메모리의 주소만 가르킨다.
	//메모리를 만들것인가 하는 정보가 없다.
	//char *name = "Jack Jack";

	//기호적인 상수처럼 사용할 수 있다.
	const char *name = "Jack Jack";
	const char *name2 = "Jack Jack";

	//컴파일러가 리터럴이 같을 경우 같이 메모리를 사용하게한다.
	cout << (uintptr_t)name << endl;
	cout << (uintptr_t)name2 << endl;
	cout << &name << endl;
	cout << &name2 << endl;

	int int_arr[5] = { 0 };
	char ch[] = "char";
	const char *ch2 = "char2";

	//cout에서 문자열은 특별히 처리한다.
	cout << int_arr << endl;
	cout << ch << endl;
	cout << ch2 << endl;

	char s = 's';
	cout << &s << endl;

	return 0;
}

포인터와 정적배열의 관계를 이해하면 포인터의 성질을 이해할 수 있다.
포인터와 배열은 매우 비슷하다. 
배열이 편의성기능이 몇가지 추가되어있다.
ex) 배열은 sizeof해보면 배열은 총 Byte가 출력된다. 포인터는 포인터의 Byte가 출력된다.

array변수는 포인터와 비슷하다. 배열의 첫번째 주소를 담고 있다.

	int arr[5] = { 9,7,5,3,1 };
	//배열의 첫번째 주소가 출력된다.
	cout << arr << endl;


확인방법 : de-reference 해보면 배열의 첫번째 데이터가 출력된다.

문제가 되는부분
함수 파라미터로 배열을 넘겨줄때 포인터로 넘어간다.
확인방법 : sizeof()를 활용해보면 포인터의 Byte가 출력된다.

함수안에서 포인터로 직접접근하여 값을 변경할 수 있다.

void printArray(int arr[]) {
	//(생략)...
	//직접접근하여 값변경하기
	*arr = 1000;
}

배열이 struct나 class에 들어가있으면 포인터로 변환되지 않는다.

#include <iostream>

using namespace std;

void printArray(int arr[]) {
	cout << "======" << endl;
	cout << arr << endl;
	cout << *arr << endl;
	cout << sizeof(arr) << endl;

	//직접접근하여 값변경하기
	*arr = 1000;
}

struct A
{
	int arr[3] = { 1,2,3 };
};

void doSomethig(A a) {
	//포인터로 변환되지 않는것을 확인할 수 있다.
	cout << sizeof(a.arr) << endl;
}

int main()
{
	int arr[5] = { 9,7,5,3,1 };

	cout << arr << endl;
	cout << *arr << endl;
	cout << sizeof(arr) << endl;
	//같은 주소를 보고있는지 확인하기
	int *ptr_arr = arr;
	cout << ptr_arr << endl;
	cout << *ptr_arr << endl;
	cout << sizeof(ptr_arr) << endl;

	char name[] = "choi";
	cout << *name << endl;
	printArray(arr);
	printArray(ptr_arr);
	cout << arr[0] << "   " << *arr << endl;
	
	cout << "======" << endl;

	A a;
	cout << a.arr[0] << endl;
	cout << sizeof(a.arr) << endl;
	
	doSomethig(a);

	return 0;
}

 

 

 

포인터의 위험성
포인터에 쓰레기값이 들어가있을때  de-reference할때 문제가 생길 수 있다.
이런문제를 방지하기위해 null pointer가 있다.

nullptr을 사용하여 체크할 수 있다.

double *ptr2 = nullptr;//modern c++ 


포인터의 주의할점
함수의 파라미터로 선언되어있는 포인터 변수도 복사가 되는것이기에 주소값이 다르다.
즉, 내부에 저장되어있는 주소값은 같지만 포인터 자체변수의 
주소값은 다른 파라미터들처럼 복사되어 주소값이 다르다.
잘 생각해보면 당연하다.

 

#include <iostream>
#include <cstddef>

using namespace std;

void doSomething(double *ptr)
{
	if (ptr != nullptr)
	{
		//nullptr이 아니면 동작해라
		cout << ptr << endl;
		cout << "address of pointer func() " << &ptr << endl;
		//내부의 저장된 주소값은 같다.
		cout << "address of pointer func() " << ptr << endl;
	}
	else
	{
		cout << "nullPtr" << endl;
	}
}

int main()
{
	double *ptr = 0;//c-style
	double *ptr2 = nullptr;//modern c++

	doSomething(ptr2);
	doSomething(nullptr);
	double d = 3.0;
	ptr = &d;
	doSomething(ptr);
	cout << "address of pointer main() " << &ptr << endl;
	//내부의 저장된 주소값은 같다.
	cout << "address of pointer main() " << ptr << endl;
	std::nullptr_t nptr;//nullptr만 사용할 수 있다.
	
	return 0;
}

포인터는 c,c++중요한 특징이다.

컴퓨터 내부에서 작동하는 방법 즉, CPU와 메모리의 협력방법을 알 수 있다.

포인터의 기본적인 사용법

변수를 선언할때 변수가 사용할 공간을 os로 부터 빌려온다.
이때 변수는 메모리의 주소를 가르키고 있고 주소에 접근 하여 데이터를 가져온다.

 

또 지역변수는 스택 영역을 사용하고 동적할당메모리는 힙 영역을 사용한다.

변수명 앞에 &(adrress-of operator)을 사용하면 주소값을 알 수 있다.

그 메모리주소를 담는 변수를 포인터라고 부른다.

변수를 사용할때 *(de-reference operator)는 포인터가 가르키고 있는것에 대해
직접적으로 접근하는 것을 말한다.  

간단하게 생각하자! 포인터는 그냥 메모리 주소를 담는 변수이다.
주소값을 직접적으로 입력하는것은 가능하지 않다.

int *ptr_x = &x;


형식으로 선언한다.

함수가 리턴타입으로 포인터를 가질 수 있다.

포인터를 선언하는 이유
기본적인 이유는 array때문에 사용한다.
함수에 array가 파라미터로 들어가면 복사가 된다.
100만개라고 생각한다면 복사를 하면 엄청 느려진다.

포인터로 주소와 데이터의 갯수만 알려주면 속도를 줄일 수 있다.

다른언어들에서도 내부적으론 포인터를 사용하고 있다.
사용자정의자료형에서도 포인터를 사용할 수 있다.
포인터의 주소는 비트의 값다를때 크기만 다르지 저장하는 값은 고정이다.

 

#include <iostream>
#include <typeinfo>

using namespace std;

int* nullPoint(int* a)
{
	return nullptr;
}

struct A {
	int a, b, c;
};

int main() {

	int x = 5;

	cout << x << endl;
	cout << &x << endl;
	cout << *(&x) << endl;

	//포인터 선언
	//변수의 주소를 포인터에 넣어준다.
	//데이터 타입과는 상관없이 중립적이다.
	//다만 de-reference할때 타입을 알아야하기에 타입을 정해준다.
	int *ptr_x = &x;

	cout << ptr_x << endl;
	//de-reference 가리키고있는 메모리안에있는 데이터를 가져온다.
	cout << *ptr_x << endl;
	//포인터임을 확인 할 수 있다.
	cout << typeid(ptr_x).name() << endl;

	A a;
	A *ptr_a;

	//12Byte이다.
	cout << sizeof(a) << endl;
	//포인터는 4Byte이다.
	cout << sizeof(ptr_a) << endl;

	return 0;
}

'개발 소발 > 개발 C++(기초)' 카테고리의 다른 글

c++ 포인터와 정적배열  (0) 2019.07.25
c++ 널 포인터 Null Pointer  (0) 2019.07.25
c++ 정적 다차원 배열  (0) 2019.07.25
c++ 배열 선택정렬,버블정렬  (0) 2019.07.24
c++ 배열 array 배열반복문사용  (0) 2019.07.24

컴퓨터 속에 메모리는 1차원적인 주소공간을 가지고 있다.
하지만 우린 아파트같은 구조도 필요하다.
(한층에 다섯개 집 층이 3개) 15개의 집을 생각해보자.

이렇게 사용할 수 있게하는 것이 다차원배열이다.

int a[2][2]형식으로 생성한다.
2차원,3차원,4차원 배열까지 생성가능하다.

초기화 특이점 = {0}으로 하면 전부 0으로 초기화 된다.

메모리차원에서보면 데이터가 일차원적으로 나열되어 있다.
기억하자 메모리에는 일차원적으로 나열되어있다.

 

2차원 배열의 예

#include <iostream>

using namespace std;

int main() {

	//1층에 5개집이 있는 3층 건물
	const int num_rows = 3;
	const int num_columns = 5;
	for (int i = 0; i < num_rows; i++) {
		cout << i << " -------------------------------------------" << endl;
		for (int s = 0; s < num_columns; s++) {
			cout << "[rows:" << i << ']' << '[' << s << ']' << '\t';
		}
		cout << endl;
		cout << " -------------------------------------------" << endl;
	}

	//위에 코드를 다차원 배열로 생성하기
	int array[num_rows][num_columns] = //초기화하기
	{
		{1,2,3,4,5},
		{6,7,8,9,10},
		{11,12,13,14,15}
	};
	//형식으로 초기화 할 수도 있다.
	array[0][0] = 100;

	for (int i = 0; i < num_rows; i++) {
		for (int s = 0; s < num_columns; s++) {
			//메모리가 4byte씩 증가하는걸 확인할 수 있다.
			cout << array[i][s] << " &: " << (int)&array[i][s] << '\t';
		}
		cout << endl;
	}

	return 0;
}

3차원 배열의 예

#include <iostream>

using namespace std;

int main()
{
	int a[2][3][4] = { 0 };
	for (int i = 0; i < 2; i++) {
		for (int j = 0; j < 3; j++) {
			for (int s = 0; s < 4; s++) {
				cout << "i : " << i << " j : " << j << " = " << a[i][j][s] << endl;
			}
			cout << "-----------------------------------------" << endl;
		}
		cout << "================================================" << endl;
	}


	return 0;
}

순서를 맞춰주는것을 정렬이라한다.
논리적으로 생각해보고 펜으로도 써보자.

 

기본적인 선택정렬을 알아보자.
목표 : 배열에서 가장 큰수와 작은수의 자리를 바꾼다.

 

#include <iostream>

using namespace std;

int main()
{
	const int length = 5;

	int aArr[length] = { 3,5,2,1,4 };
	/*
	정렬순서
	3,5,2,1,4
	1,5,2,3,4
	1,2,5,3,4
	1,2,3,5,4
	1,2,3,4,5
	*/
	for (int i = 0; i < length-1; i++) {
		int temp = 0;
		int arr = i;
		for (int s = 0; s < length - i; s++) {
			cout << aArr[arr] << " > " << aArr[s + i] << endl;
			if (aArr[arr] > aArr[s + i]) {
				arr = s + i;
				
			}
		}
		{
			temp = aArr[arr];
			aArr[arr] = aArr[i];
			aArr[i] = temp;
		}
	}

	cout << "sort" << endl;
	for (int i = 0; i < length; i++) {
		cout << aArr[i] << endl;
	}

	return 0;
}

기본적인 버블정렬을 알아보자.
목표 : 배열의 데이터를 순차적으로 비교하여 
앞데이터가 다음배열보다 클경우 자리를 바꾼다.

 

#include <iostream>

using namespace std;

int main() {

	int len = 5;
	int a[] = { 5,2,3,4,1 };

	for (int i = 0; i < len-1; i++) {
		for (int s = 0; s < len - (i+1); s++) {
			//cout << "s : " << s << " a[s+1] : " << a[s + 1] << endl;
			if (a[s] > a[s + 1]) {
				int temp = a[s + 1];
				a[s + 1] = a[s];
				a[s] = temp;
			}//end of if
		}//end of for s
	}//end of for i
	for (int i = 0; i < len; i++) {
		cout << a[i] << endl;
	}
	return 0;
}

+ Recent posts