포인터와 정적배열의 관계를 이해하면 포인터의 성질을 이해할 수 있다.
포인터와 배열은 매우 비슷하다. 
배열이 편의성기능이 몇가지 추가되어있다.
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;
}

배열array 
- 비슷한것이 나열되어 있는것

학생성적을 기록하고 싶은데
학생이 여러명이면?
int형을 학생의 이름으로 여러개 선언하면 기억하기힘들다.

해결책
같은형의 변수를 여러개 불러올수 있다.
배열 사용 : int student[5];
배열은 0부터 시작한다.
생성한 배열보다 큰 배열번호에 입력하면 에러가 난다.

배열 초기화하기
int a[3] = { 1,2,3 };
int a[] = { 1,2,3 };
int a[3];
a[0] = 1; 
위에 방식으로 초기화 할 수 있다.

[]로 선언하는 배열의 크기는 시작할때 정해주어야한다.
사용하고 싶다면 #define를 사용한다.

기본자료형말고 구조체등도 배열로 선언할 수 있다.

#include <iostream>

using namespace std;

//구조체선언
struct Rectangle
{
	int length;
	int width;
};

enum StudentName
{
	Jack,	//0
	Dash,	//1
	Violet,	//2
};

int main() 
{
	//배열만들기
	int a[3] = { 1,2,3 };

	cout << sizeof(a) << endl;
	a[Jack] = 1;
	a[Dash] = 2;
	a[Violet] = 3;

	int sum = 0;
	for (int i = 0; i < sizeof(a) / 4; i++) {
		sum += a[i];
		cout << a[i] << endl;
	}
	cout << sum << endl;

	cout << sizeof(Rectangle) << endl;
	//구조체 배열선언하기
	Rectangle r[10];
	cout << sizeof(r) << endl;
	r[0].length = 1;
	r[0].width = 2;
	
	//cin >> sum;
	//int ab[sum] <-형식은 안됌 사용하고싶다면 매크로 #define을 사용한다. 
	return 0;
}

 

배열의 메모리 알아보기
int형으로 선언할때 배열의 주소는 배열의 첫번째 주소를 가르킨다.
그리고 int형은 4Byte이기때문에 4씩 증가하는걸 볼수 있다.

함수의 파라미터로 배열을 보내면 배열의 주소가 달라진다.
배열의 데이터를 복사해온다.
복사해올때 포인터로 넘어온다. 
파라미터로 넘어온 배열을 sizeof를 해보면 포인터변수의 사이즈가 출력된다.

 

#include <iostream>

using namespace std;

#define NUM 2

void doSomething(int students[]) 
{
	cout << "doSomething" << endl;
	cout << (int)&students << endl;
	//포인터의 사이즈가 출력된다.
	cout << "sizeof: " << sizeof(students) << endl;
	cout << students[0] << endl;
	cout << students[1] << endl;
}

int main()
{
	int a[NUM];

	cout << (int)&a << endl;
	cout << (int)&a[0] << endl;
	cout << (int)&a[1] << endl;
	a[0] = 1;
	a[1] = 2;
	cout << sizeof(a) << endl;

	doSomething(a);

	return 0;
}

배열 반복문 활용

배열은 같은타입의 데이터가 메모리에 일렬로 나열되어있는것이다.
반복문을 활용하면 배열을 사용하기 쉽다.

sizeof로 배열의 크기를 알아볼수 있는데 파라미터로 넘어가면
포인터의 주소만 가르키기때문에 불가능하다.

 

#include <iostream>

using namespace std;

int main()
{
	const int num = 3;
	int a[num] = { 1,50,3 };

	//배열 크기 알아보기
	int a2[] = { 1,50,3 };
	const int num2 = sizeof(a2) / sizeof(int);

	int totalScore = 0;
	int maxScore = 0;

	for (int i = 0; i < num; i++) {
		totalScore += a[i];
		cout << a[i] << endl;
		maxScore = (maxScore < a[i]) ? a[i] : maxScore;
	}

	double avgScore = static_cast<double>(totalScore) / num;
	cout << "avg : " << avgScore << endl;
	cout << "max : " << maxScore << endl;
	return 0;
}

난수만들기
random number만들기
컴퓨터는 랜덤숫자를 만들 수 없다.

 

#include <iostream>
#include <cstdlib>
#include <ctime>
#include <random>

using namespace std;

int main()
{
	//시드넘버
	srand(5324);
	//시드넘버변경하기
	srand(static_cast<unsigned int>(time(0)));
	for (int i = 1; i <= 100; i++) {
		cout << rand() <<"\t";
		if (i % 5 == 0) {
			cout << endl;
		}
	}

	random_device rd;
	mt19937 ran(rd());
	uniform_int_distribution<> dice(1, 32);

	for (int a = 0; a < 5; a++) {
		cout << a << " : ";
		for (int i = 0; i < 6; i++) {
			cout << dice(ran);
			if (i == 5) {
				cout << endl;
			}
			else {
				cout << "\t";
			}
		}
	}
	
	return 0;
}

cin은 console에서 text입력받을때 사용한다.

ignore는 첫번째 버퍼에 값만 사용한다.
즉 2 3 4 여러개 입력해도 2만 입력이된다.
사용법 : cin.ignore(32767,'\n');

cin에서 변수형에 맞춰 입력이 틀렸을 경우 fail로 사용할 수 있다.

cin 올바른 사용예

#include <iostream>

using namespace std;

int main()
{
	int a;
	while (true) {

		cin >> a;
		
		if (cin.fail()) {
			cin.clear();
			cin.ignore(32767, '\n');
			cout << "다시입력" << endl;
		}
		else {
			break;
		}

	}
	cout << "a : " << a << endl;
	return 0;
}

for문
가장 많이 사용되는 반복문

for(사용할 변수(초기화);조건;증감연산자;){}
위에 방법으로 사용한다.
마지막 조건에 증감연산자말고 다른 조건도 가능하다.
로직 실행순서
for (int i = 0; i < 10; i++)//iteration
1.int i = 0;변수를 초기화한다.
2.i < 10;조건을 확인하고 {}을 실행한다.
3.i++를 실행한다.
4.i < 10;조건을 확인하고 {}을 실행한다.
5.i++를 실행한다.

특이점은 변수는 초기에 한번만 생성한다.
또 증감연산자는 {}에 로직이 사용되고 마지막에 증가시킨다.

변수,증감연산자(다른조건)는 여러개 만들 수 있다.

while문과 마찬가지로 unsigend int 오버플로우를 주의해야한다.

#include <iostream>

using namespace std;

int pow(int b, int e)
{
	int result = 1;
	for (int count = 0; count < e; count++) {
		cout << result << " * " << b << " = "<< endl;
		result *= b;
	}
	return result;
}

int main()
{
	cout << "test" << endl;
	for (int i = 0; i < 10; i++)//iteration
	{
		cout << i << endl;
	}

	//제곱구하기
	pow(2, 4);

	//여러개 변수 선언하기
	for (int i = 0,s = 0; (i + s) < 100; i++, s += 5) {
		cout << "i : " << i;
		cout << "\ts : " << s << endl;
	}//end of for i

	//2중 for문사용
	//구구단 사용
	for (int i = 2; i < 10; i++) {
		cout << i << endl;
		for (int s = 1; s < 10; s++) {
			cout << "\t" << i << " * " << s << " = " << i * s << endl;
		}
	}
	return 0;
}

break,contunue
반복문을 제어하는 방법

break는 현재{}에서 빠져나간다.
무한루프에서 빠져나올때 자주 사용한다.

continue는 조건에 맞으면 다음반복문(숫자 증감)으로 넘어간다.
do-while문에서는 while조건에 증감연산을 해주어야한다.

#include <iostream>

using namespace std;

void breakOrReturn()
{
	while (true)
	{
		char ch;
		cin >> ch;
		if (ch == 'b')
			break;
		
		if (ch == 'r')
			return;
	}
	cout << "break or return?" << endl;
}

int main()
{
	int count = 0;
	//while,for모두 사용가능하다 break사용하기
	while (true) {
		
		if (count > 10) {
			break;
		}
		cout << count << endl;
		count++;
	}

	//break 확인 함수
	breakOrReturn();

	//continue사용하기
	for (int i = 0; i < 20; i++) {
		if (i % 2 == 0) {
			continue;
		}
		cout << i << endl;

		//아래로직과 같다.
		/*if (i % 2 == 0) {
			cout << i << endl;
		}*/
	}

	//continue사용하요 특정숫자 뺴고 출력하기
	int i = 0;
	do
	{
		if (i == 5) {
			continue;
		}
		cout << i << endl;
	} while (i++ < 10);

	return 0;
}

반복문 while
컴퓨터의 장점
- 정확하다.
반복을 지루해하지 않는다.

현업에선 while문 보단 for문을 많이 사용한다.
하지만 while문을 꼭 사용하는 곳이 있다.

while(조건=true){}형식으로 사용한다.
조건이 완료되지 않으면 무한루프에 빠진다.

{}안에 변수를 선언에 사용할려면 static을 사용한다.
조건에 true를 입력하고 내부에서 if(조건)break;로 사용할 수 있다.

while문안에 while문을 사용할 수 있다.

while문은 가시적으로 간결하다.

문제점 
unsigned int에 --를 사용하면 오버플로우가 발생하여 문제가 생길 수 있다.

#include <iostream>

using namespace std;

int main()
{
	int a = 0;
	//a가 10보다 작다면 이라는 조건을 사용했다.
	while ( a < 10) {
		//변수사용하기! 잘 사용안한다.
		//static int a = 0;
		cout << "a : " << a << endl;
		a++;
	}

//goto문으로 while과 같이 동작하기
startNumAdd:

	cout << "goto a : " << a << endl;
	if (a < 20) {
		a++;
		goto startNumAdd;
	}

	a = 2;
	//unsigned를 사용하면 오버플로우가 발생하여 문제가 생긴다.
	//unsigned int count = 0;
	//while (count >= 0) {
	//	cout << count << endl;
	//	count--;
	//}

	//중복while문
	//구구단출력
	while (a < 10) {
		int b = 1;
		cout << "a : " << a << endl;
		while (b < 10) {
			cout << "\t"<< a << " * " << b << " = " << a*b << endl;
			b++;
		}
		a++;
		b = 0;
	}

	return 0;
}

do-while문
while문과의 차이
while문은 조건에 따라 한번도 실행안될 수 있다.
do-while문은 무조건 한번은 실행된다.

for,while,do-while문중 활용도는 가장 낮다.

#include <iostream>

using namespace std;

int main() 
{
	int select = 0;

	do
	{
		cout << "selectNum : " << select << endl;
		cout << "1~5 select" << endl;
		cin >> select;
	} while (select < 0 || select > 5);

	return 0;
}

switch-case 조건문

if문과 다르게 맞는 조건 뒤에 로직은 모두 동작한다.
멈추고 싶다면 break;를 사용한다.
{}scope를 사용가능하다 if문과 다르게 {}가 없더라도 여러줄 인식이된다.

주의할점
switch문 앞부분에 변수를 선언할 수 있으나 초기화는 못한다.
case문에서 값을 정해줄 수 있다.
{}scope가 없을시 case문에서 선언한 변수는 
switch문 앞부분에 선언한 것처럼 적용된다.

 

#include <iostream>

using namespace std;

enum class Colors
{
	BLACK,
	WHITE,
	RED,
	GREEN,
	BLUE,
};

void printColorName(Colors c)
{
	switch (c) {
		//여기에 선언된것과 같다.
		//int a;
	case Colors::BLACK:
		//{}scope가 있으면 내부에서만 적용된다.
		int a;
		a = 5;
		cout << "BLACK";
		break;
	case Colors::GREEN:
		//GREEN이 선택되면 a의 값은 garbage값이 출력된다.
		cout << a << endl;
		cout << "GREEN";
		break;
	default:
		cout << "X" << endl;
	}
	cout << endl;
}

int main()
{
	printColorName(Colors::BLACK);
	
	return 0;
}

goto


반복문 대신에 과거에 자주사용했다.
어셈블리어로 가면 동작은 거의 같다.
지정한곳으로 가게 명령하는 것이다.
레이블이름을 지어 그위치로 돌아간다.
책갈피로 생각하면된다.

 

돌아갈곳 지정은 돌아갈곳(이름지정):
돌아가기 작동은 goto 돌아갈곳;으로 한다.

주의할점
지금은 거의 사용하지 않는다.

 

#include <iostream>

using namespace std;

int main()
{
	double x;

//돌아갈곳 설정하기
notGood :

	cout << "cin start" << endl;
	cin >> x;
	if (x < 0.0)//goto문 실행방법
		goto notGood;

	return 0;
}

+ Recent posts