C++에서 파일을 다루는 방법에 대해서 작성한다 

  • 파일을 생성하는 방법 - ofstream  /  이미 있으면 삭제 후 다시만듬
  • 파일이 있는지 확인하는 방법 - ifstream.is_open()
#include <fstream>
#include <iostream>

int main() {
	using namespace std; 

	char automobile[50];
	int year; 
	double a_price;
	double b_price;

	ofstream outFile; // 출력을 위한 파일 객체 생성 
	outFile.open("carinfo.txt"); // 파일에 연결 - 이미 파일이 있으면 기존 파일 내용이 사라진다 
	if (outFile.is_open()) {
		cout << "파일 잘 열렸고 ";
	}
	else {
		cout << "파일 잘 안열렸고";
	}
	cout << "자동차 메이커와 차종을 입력하세요 : ";
	cin.getline(automobile, 50); // 파라미터 :  (char *_Str, stream size _Count)
	cout << "연식을 입력하세요 : ";
	cin >> year; 
	cout << "구입 가격을 입력하세요 : "; 
	cin >> a_price; 
	
	b_price = a_price * 0.913; 

	// cout 을 통해서 보기 
	cout << fixed; // std:fixed - 표현 소수점을 고정 소수점으로 변경 
	cout.precision(2); // 고정소수점으로 변경했기 때문에 소수점을 2개까지만 표현 
	cout.setf(ios_base::showpoint); // 끝에 소수점이후 값이 없어도 precision까지 표현 
	cout << "메이커와 차종 : " << automobile << endl;
	cout << "연식 : " << year << endl;
	cout << "구입 가격 : " << a_price << endl;
	cout << "현재 가격 : " << b_price << endl;

	// outFile을 통해 저장 
	outFile << fixed; 
	outFile.precision(2);
	outFile.setf(ios_base::showpoint);
	outFile << "메이커와 차종 : " << automobile << endl;
	outFile << "연식 : " << year << endl;
	outFile << "구입 가격 : " << a_price << endl;
	outFile << "현재 가격 : " << b_price << endl;

	outFile.close();
	return 0;
}
  • 이미 있는 파일을 읽어오는 코드. 
  • getline이라는 string 해더에 있는 맴버함수를 사용하였다. 
  • 파일에서 라인을 읽어올 때 개행문자만을 인식하여 읽어오고 싶었다 
  • ifstream >> buffer의 경우 띄어쓰기를 기준으로도 읽어와서 불편함이 있어 사용하지 않았다 
  • getline 함수는 string, cstring, std 등에 존재하여 수만은 오버로딩이 존재하기 떄문에 찾는데 시간이 걸렸다
#include <fstream>
#include <iostream>
//#include <cstring>
#include <string>
int main() {
	using namespace std; 
	ifstream inFile; 

	inFile.open("carinfo.txt");
	if (!inFile.is_open()) { // 파일을 여는데 실패했다면
		cout << "파일을 열 수 없습니다." << endl;;
	}
	else {
		cout << "파일을 열었습니다." << endl;;
	}
	string buffer; 

	while (inFile.good()) { // 파일의 입력이 양호하고 EOF가 아닌 동안 
		getline(inFile, buffer); // string의 getline 
		cout << buffer << endl;
	}

	return 0;
}

 

문자열을 비교하는 방식은 여러가지가 있다 

 

#include <cctype>
#include <iostream>


int main() {
	using namespace std;
	char ch;
	cin >> ch; 
	// 문자가 알파벳인지를 검증할 대 
	if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) {
		cout << "ch is alphabet";
	}
	else {
		cout << "ch is not alphabet";
	}
	// cctype 을 이용 
	if (isalpha(ch)) {
		cout << "isalpha? : ch is alphabet";
	}
	else {
		cout << "ch is not alphabet";
	}
	// 다양한 cctype 함수 
	// isalnum() : 알파벳 또는 숫자인지 
	// isalpha() : 알파벳인지 
	// isblank() : 빈칸 또는 수평탭 인지 
	// iscntrl() : 제어문자인지 
	// isdigit() : 십진숫자인지 (0 ~ 9) 
	// isxdigit() : 16진수 숫자인지 (0~9, a~f, A~F)
	// isgraph() : 빈칸이 아닌 인쇄할 수 있는 문자인지 
	// isprint() : 빈칸을 포함하여 인쇄할 수 있는 문자인지
	// islower() : 소문자인지 
	// isupper() : 대문자인지
	// ispunct() : , . ! ? ' " 등의 구두점 문자인지 
	// isspace() : 표준 화이트스페이스 ( 빈칸, 개행, 탭 등) 인지
	// tolower() : 대문자를 소문자로 
	// toupper() : 소문자를 대문자로 
}

 

for 문

	for (int i = 0; i < 5; i++) {
		cout << "for i : " << i << endl;
		for (int j = 0; j < 5; j++)
			cout << "for j : " << j << endl;
	}
	string word = "Hellow ! my name is C++";
	
	for (int i = word.size() - 1; i >= 0; i--) {
		cout << word[i] ;
	}
	int a1[4] = { 1,2,3,4 }; 
	int* pt = a1;
	for (int i = 0; i < 4; i++) {
		cout << a1[i] << endl;
	}
	*++pt; // 포인터를 증가 
	++*pt; // 지시되는 값을 증가 
	(*pt)++; // 지시되는 값 증가 
	*pt++; // 원래 주소를 참고하고 나서 포인터 증가
// 컴마 연산자를 이용하여 하나의 포문에 여러개의 증감, 변수선언을 할 수 있다 
	int i, j; // for문 내부에서 선언하지 않는것을 추천 
	for (i = 0, j = 5; j > i; i++, j--) {
	//for (i = 0, j = 5; j > i; ++i, --j) {
		cout << "i : " << i << " j : " << j << endl; 
	}
// range 기반 for 루프 - built-in array 
	int prices[5] = { 1,2,3,4,5 };
	int i = 0; 
	for (int x : prices) {
		cout << "x는 prices의 " << i << "번째 요소 : " << x << endl;
		i++;
	}
 // range 기반 for 루프- array 
    int i = 0;
	array<int, 5> arr = { 1,2,3,4,5 };
	for (int x : arr) {
		cout << "x는 arr의 " << i << "번째 요소 : " << x << endl;
		i++;
	}

for (;;) 는 무한루프이다

While 문 

// while문 사용 
	int i = 0; 
	const char name[20] = "Hong gil dong";
	while (name[i] != '\0') { // 하나의 문자를 나타낼 때는 작은 따움표, 문자열은 큰따움표
		cout << name[i] << "는 ASCII로 : " << int(name[i]) << endl;
		i++;
	}

if 문

	char t1[5] = "mate";
	if (t1 == "mate") {
		cout << "같다" << endl;
	}
	else {
		cout << "다르다" << endl;
	}
	if (strcmp(t1, "mate") == 0) { // 같으면 0을 반환
		cout << "같다" << endl;
	}
	else {
		cout << "다르다 : " << strcmp(t1, "mate") << endl;
	}
	string t2 = "mate"; // string 클래스는 간단하게 비교가능하다 
	if (t2 == "mate") {
		cout << "같다" << endl; 
	}
	else {
		cout << "다르다" << endl; 
	}

do while 

// do while 사용 
	int i = 0; 
	do {
		cout <<  i << endl;;
		i++;
		
	} while (i < 5);
	return 0;

 

if else문의 경우 ?: 연산자를 사용할 수 있다 

#include <iostream>

int main() {
	using namespace std; 
	int a = 6; 
	int b = 7; 
	int c = a > b ? a : b;
	cout << c; // b가 크기때문에 7 출력 
}

 

Switch

  • 조건결과가 정수일 경우 if else 문과 switch 둘다 사용할 수 있다  
  • 책에서는switch의경우선택사항의 개수가 3개 이상일 경우에 사용하는 것이 좋다고 작성되어 있다 
#include <iostream>

int main() {
	using namespace std; 

	// int user_input;
	char user_input;
	cout << "정수를 입력하세요 : ";
	cin >> user_input;

	switch (user_input) {
	case 1: 
		cout << "1을 입력하셨습니다." << endl;
		break; 
	case 2 : 
		cout << "2를 입력하셨습니다" << endl;
		break;
	case 3 : 
		cout << "3를 입력하셨습니다" << endl;
	case 4 : 
		cout << "4를 입력하셨을수도 있고 3을 입력하셨을 수도 있어요" << endl;
		break;
	case 'a':
		cout << "문자를 입력하셨습니다" << endl;
		break;
	default : 
		cout << "입력한 값이 없습니다." << endl;
			break;
	}

	return 0;
}

 

Vector Template Class

  • Vector template class는 동적 배열에 속하는 string 클래스와 유사하다 
  • 프로그램이 실행되는동안 vector 객체의 크기를 세팅할 수 있고
  • 새로운 데이터를 마지막에 추가하거나 중간에 데이터를 삽입할 수 있다
  • 실제로 vector 클래스는 메모리를 관리하기 위하여 new와 delete를 자동으로 진행한다 
  •  vector 클레스는 <vector> 헤더파일에 있다 
  • vector<typeName> vt(n_elements); 

 

Array Template Class

  • vector는 다소 비효율적인 면이있다 
  • 만약 사용자가 고정된 크기의 배열만 필요하다면 array를 사용하는 것이 더 좋을것이다 
  • 그러나 안전성과 편리성은 다소 줄어들 수 있다 
  • Array객체는 free store 대신에 고정된 크기의 고정 메모리 대입을 사용하여 built-In array와 동일한 수준의 효율성을 지닌다 
  •  array 클래스는 <array> 헤더파일에 있다 
  • array<typeName, n_elements> arr;  n_elements개의 typeName 데이터타입을 가진  array 객체를 생성해라 
#include <iostream>
#include <vector> // STL C++98
#include <array> // C++11


int main() {
	using namespace std; 
	// C, origin C++ 
	int a1[4] = { 1,2,3,4 };
	// C++ 98 STL 
	vector<int> a2(4); 
	a2[0] = 1;
	a2[1] = 2;
	a2[2] = 3;
	a2[3] = 4;

	// C++ 11 
	array<int, 4> a3 = { 1,2,3,4 }; 
	array<int, 4> a4 = a3; 

	cout << "a1[2] : " << a1[2] << " at " << &a1[2] << endl;
	cout << "a2[2] : " << a2[2] << " at " << &a2[2] << endl;
	cout << "a3[2] : " << a3[2] << " at " << &a3[2] << endl;
	cout << "a4[2] : " << a4[2] << " at " << &a4[2] << endl;

	return 0;
}

 

프로그램이 단지 하나의 값만 요구한다면 단순한 변수를 선언하는 것이 간단하다. 

하지만 배열이나 구조체, 문자열 같이 큰 데이터를 다룰 때에는 new를 사용하는 것이 더 효율적이다. 

프로그램이 컴파일시에 메모리를 차지하는 방식을 static binding이라고 하고 new 를 사용하여 프로그램 실행시에 메모리를 할당하는 방식을 dynamic binding이라 한다. 

 

Dynamic array

 

int main() {
	using namespace std;
	// 동적 배열의 선언 (dynamic array)
	// Darray의 값(주소)에는 배열의 첫번째 주소가 들어가있다
	int* Darray = new int[10]; 
	
	Darray[0] = 3;
	Darray[1] = 4;
	Darray[2] = 5;
	Darray[3] = 6;
	Darray[4] = 7;

	cout << "배열의 1번 값은 : " << Darray[1] << endl; 
	cout << "배열의 주소는 : " << Darray << endl; // 00000283BBECA4F0
	// 포인터 변수에 1을 더하면 그 포인터가 지시하는 데이터형의 바이트 수만큼 증가한다 
	Darray = Darray + 1; // 배열 포인터에서 +1 은 다음 원소를 가르키게 된다 
	cout << "이제는 배열의 0 번 값은 : " << Darray[0]; 
	cout << " 배열의 1번 값은 : " << Darray[1] << endl;
	cout << "배열의 주소는 : " << Darray << endl; // 00000283BBECA4F4  int형 배열이기 때문에 4가 증가됐다 

	Darray = Darray - 1; // 다시 시작 위치를 지시한다 
	cout << "배열의 주소는 : " << Darray << endl; // 00000283BBECA4F0

	// 동적 배열의 해제 
	delete [] Darray; // 동적 배열의 경우 반드시 []를 써줘야 배열 전체가 해제된다 


	return 0;
}

 

Dynamic Structure 

 

구조체의 경우 선언된 포인터 변수를 통해 맴버 변수에 접근 할 수 없다. 

포인터 변수는 해당 구조체의 주소값을 의미하기 때문이다 . 

그래서 c++에서는 포인터 변수에서 구조체의 맴버에 접근하기 위해 -> 기호를 사용한다

(*value).member 으로도 접근 가능하다 

int main() {

	struct Mystruct{
		string name;
		int price;
		bool type;
	};

	Mystruct* ps = new Mystruct; 

	ps->price = 1000; 
	ps->name = "12345678901234567890123";
	(*ps).name = "apple";
	ps->type = true; 

	cout << ps->price << endl;
	cout << ps->name<< endl;
	cout << ps->type << endl;

	cout << "*ps의 사이즈 : " << sizeof(*ps);
	cout << " price의 사이즈 : " << sizeof(ps->price);
	cout << " name의 사이즈 : " << sizeof(ps->name);
	cout << " type의 사이즈 : " << sizeof(ps->type);
	delete ps; 

	return 0;
}

 

new를 활용하여 메모리를 절약하는 방법 

new를 사용하지 않으면 선언되는 변수마다 80Byte의 공간이할당되고 프로그램의 종료까지 유지되지만 

다음과 같이 입력받은 값의 크기만큼의 공간을 새로 할당하고 해당 변수의 사용이 끝나면 delete로 반환하는 방식으로 메모리를 절약할 수 있다 

char* getname(void); 

int main() {
	char* name; 
	name = getname(); // name에는 새로운 길이의 배열의 시작 주소가 있음 

	cout << "name의 주소값 : " << (int*)name << endl;
	cout << "name의 값 : " << name << endl;
	delete name; // 메모리 해제 

	name = getname(); // 해제한 메모리를 다시 사용

	cout << "name의 주소값 : " << (int*)name << endl;
	cout << "name의 값 : " << name << endl;
	delete name; // 메모리 해제 

	return 0;
}

char* getname() { // 새 문자열을 가리키는 포인터  
	char temp[80]; // 임시 배열 - 해당 함수가 return하면 해당변수의 메모리는 반환된다 (automatic variable)
	cout << "이름을 입력하세요 : ";
	cin >> temp;
	char* pn = new char[strlen(temp) + 1]; // 배열을 입력한 크기만큼의 새로운 주소의 배열을 생성
	// strcpy 는 secure에 문제가 있어 사용 권장하지 않아서 strcpy_s를 사용
	// strcpy_s 는 두번째 인자로 변경할 사이즈를 정해줘야함 
	strcpy_s(pn, sizeof(*pn)*strlen(temp) + 1, temp);// 새로운 크기의 배열에 기존 temp의 값을 넣음 
	return pn; // 새로운 배열의 주소를 리턴
}

 

automatic storage, static storage, dynamic storage

  • C++에서는 데이터를 저장해 두기 위한 메모리를, 대입하는 방법에 따라  자동 공간(automatic storage), 정적 공간(static storage), 동적 공간(dynamic storage)로 구분한다
  • 정적 공간은 static 변수를 사용하거나 전역변수로 정의할 수 있다. 
  • 함수의 호출에 따라 사라지고 생기는 변수들은 자동공간(stack의 형태로)에 저장된다.
  • 동적 공간은 힙(heap)이라고도 한다
  • 동적 공간은 free store(자유 공간)이라 부르는 메모리 풀(memory pool)을 관리한다. 
  • 메모리 풀은 자동 변수와 정적변수가 사용하는 메모리와 분리되어 있다. 
  • stack의 경우  메모리상에서 데이터들이인접하여 존재하게 된다.
  • new와 delete를 사용하여 dynamic storage에 저장된 데이터를 관리를 해줘야한다 new로 생성한 메모리 주소에 값을 대입하고 delete를 하지않고 새롭게 new로 값을 대입하면 이전에 값은 메모리 어딘가에 떠돌게 되고 프로그램이 종료되지 않는이상 다시는 접근하기 어렵다. 이런것을 memory leak (메모리 누수)라고 한다   

 

C기반 언어를 사용하는 이유중 개인적인 생각으로 가장 중요한 부분이 포인터라고 생각한다 . 

포인터를 이용하여 메모리 주소값에 직접 접근하여 프로그램의 효율을 상승시킬 수 있다. 

 

포인터란? 

  • 포인터는 값 자체가 아니라 값의 주소를 저장하는 변수이다. 
  • 주소연산자(&)를 변수 앞에 붙이면 그 변수의 주소를 알아낼 수 있다.
  • cout을 사용할 때 주소값은 컴파일러에 따라 16진수 또는 10진수로 표현된다 
  • * 기호는 곱하기를 뜻하기도 하지만 포인터에서는 해당 메모리 주소에 들어있는 값을 의미한다 
    *메모리주소변수 = 100 
  • & 기호는 and를 뜻하기도 하지만 해당 변수의 메모리 주소를 의미한다  
    &값변수 = 00000079CA8FF534
int main() {
	using namespace std;

	int donuts = 8;
	double cups = 4.5; 

	// 변수의 주소를 알고싶다면 해당 변수앞에 &를 붙여라 
	cout << "donuts : " << donuts; 
	cout << " donuts의 주소 : " << &donuts << endl; // 000000B526CFF7C4
	cout << " donuts의 사이즈 : " << sizeof(donuts) << endl; // 4 Byte

	cout << "cups: " << cups;
	cout << " cups의 주소 : " << &cups << endl; // 000000B526CFF7E8
	cout << " cups의 사이즈 : " << sizeof(cups) << endl; // 8 Byte
		
	return 0;
}
int main() {
	using namespace std;
	int updates = 6;
	int* p_updates; // int형을 지시하는 포인터 선언

	p_updates = &updates; // int형의 주소를 포인터에 대입 

	cout << "값 : updates = " << updates; // 해당 변수에 대입된 값 
	cout << ", p_updates = " << p_updates << endl; // 해당 주소의 값 ( 포인터 )

	cout << "주소: &updates = " << &updates; // 변수가 저장된 메모리의 주소 
	cout << ", *p_updates = " << *p_updates << endl; // 메모리 주소에 저장된 값 

	// 포인터를 사용하여 값 변경 
	*p_updates = *p_updates + 1; // 메모리주소에 저장된 값에 더하기 1 을 함 
	cout << "변경된 updates = " << updates << endl;  // updates 의 메모리에 접근하여 값을 + 1 했기 때문에 7을 출력 

	return 0;
}
  • int* a 에서 a는 int형을 가르키는 포인터이다  
  • 포인터 변수는 그냥 단순한 포인터가 아니라, 항상 어떤 데이터형을 지시하는 포인터라고 해야한다 
  • char형의 주소와 double형의 주소는 크기가 같다. 그렇기에 특정 주소의 크기나 값만 가지고는 그 주소에 저장되있는 변수의 크기나 종류를 알 수 없다 
int main() {
	using namespace std; 

	int* ptr; 
	ptr = (int*)0xB80000000; // 포인터에 직접 주소를 매핑하려면 이렇게 int형을 가르키는 주소라고 데이터타입 형변환이 필요하다 
}
int main() {
	using namespace std; 

    // ptr이라는 메모리주소 변수에 int형이 필요할 때 
    // 이렇게 초기화를 하면 시스템에서 알아서 int형에 맞는 사이즈의 메모리를 찾아서 ptr에 대입해준다 
	int* ptr1 = new int; // ptr1은 데이터객체를(data object)를 지시한다 
    // 00000153F2AF6590
	cout << ptr1 << endl; 

	int higgens; 
	int* ptr2 = &higgens; // ptr2에 이런식으로 주소값을 대입할 수 있다. 

    // 위의 두 방식이 다른 점은 ptr1의 경우 
    // 오직 ptr1을 통해서만 (변수가없기때문에) 해당 값에 접근할 수 있다 
    // 하지만 higgens를 이용한 경우 
    // 해당 값에 접근할 수 있는 방식이 higgens를 이용하는 법과 ptr2를 이용하는 방법 두가지가 있게된다

	return 0;

}
int main() {
	using namespace std; 

	int nights = 1001; 
	int* pt = new int; 
	*pt = 1001; 

	double* pd = new double; 
	*pd = 10000001.0;

	cout << "nights의 값 : " << nights;
	cout << " nights의 메모리 위치 : " << &nights << endl;;
	cout << "int형 값 : " << *pt << " : 메모리의 위치 : " << pt << endl; 

	cout << "double형의 값 : " << *pd;
	cout << "메모리 위치 : " << pd << endl;
	cout << "포인터 pd의 메모리 위치 : " << &pd << endl; 

	cout << "pt의 크기 : " << sizeof(pt);// 8을 나타낸다 - 데이터 객체의 사이즈이기 떄문
	cout << " *pt의 크기 : " << sizeof(*pt) << endl; // 4를 나타낸다 - int형의 사이즈이기 때문
	cout << "pd의 크기 : " << sizeof(pd); // 8을 나타낸다 - 데이터 객체의 사이즈이기 떄문
	cout << " *pd의 크기 : " << sizeof(*pd) << endl; // 8을 나타낸다 - double형의 사이즈이기 때문

	return 0;
}

new로 생성한 메모리를 해제하는 delete 

int* ps = new int; 
delete ps ; // 정상 
delete ps ; // 이미 해제되었기 때문에 비정상 에러

int jugs = 5; 
int * pi = &jugs; 
delete pi; // new로 생성한 데이터 객체가아니기 때문에 비정상 에러

C++ 에서 코드의 가독성과 효율을 높이기 위한 구조체와 공용체, 열거체에 대해서 정리한다. 

 

구조체란 ? (struct)

  • 사용자가 정의할 수 있는 데이터 타입 
  •  하나의 변수안에 여러개의 변수를 저장 할 수 있다 ( python의 dictionary와 비슷)
struct MyStruct{
    int type : 4; // :뒤에 있는 숫자는 해당 데이터 타입의 bit수를 제한한다 
    int null_space : 4; // 이렇게 아무 값을 넣지 않을 공간을 미리 만들 수도 있다
    char name[20] : 21; 
    unsigned int price : 4;
}

 

공용체란 ? (union)

  • 서로 다른 데이터형을 한 번에 한 가지만 보관할 수 있는 데이터 형식 
  • 여러개의 데이터형을 선언 했을 때 하나의 데이터형만 사용이 가능하다 
  • 차지하는 메모리 공간은 선언된 데이터 타입중에 가장 큰 데이터타입의 크기로 설정된다 
union MyUnion{
    int int_val;
    float float_val;
    double double_val;
    long long_val;
}

// ...... // 
MyUnion test1; 

test1.int_val = 3; // int_val에 값 저장 
test2.float_val = 3.3; // int_val에 값이 소실되고 float_val이 저장된다

열거체란 ? (enum : enumeration)

  • 기호 상수를 만들 수 있다
  • 상수에 매칭되는 정수는 상수의 순서대로 0부터 시작하여 1씩 증가한다    
enum spectruum { red, orange, yellow, green, blue, violet, indigo, ultraviolet};

cout << red << endl;  // 0 출력
enum spectruum { red=100, orange, yellow, green, blue, violet, indigo, ultraviolet};

cout << red << endl;  // 100 출력
cout << orange << endl;  // 101 출력 (상수는 이전 상수의 값 + 1 이 된다)

 

+ Recent posts