[cin, cout]
std::cout << 출력할 데이터;
삽입연산자(<<)는 오른쪽에 위치한 출력할 데이터를 출력 스트림에 삽입한다. 출력 스트림에 삽입된 데이터는 스트림을 통해 출력 장치로 전달되어 출력된다.
std::cin >> 저장할 변수;
추출연산자(>>)를 통해 사용자가 입력한 데이터를 입력 스트림에서 추출하여, 오른쪽에 위치한 변수에 저장. 이때 cin 객체는 자동으로 사용자가 입력한 데이터를 오른쪽에 위치한 변수의 타입과 동일하게 변환시켜준다.
(Q.출력 스트림, 입력 스트림이란.(추후 수정))
[문자열 입력 방법]
scanf 이용
#include <stdio.h>
int main(void)
{
char input[숫자1] // char형 배열로만 가능하다
scanf_s("%s",input, 숫자2); // 숫자2 <= 숫자1
printf("%s",input);
}
char형 배열로만 가능하다
gets(), puts()
#include <stdio.h>
int main(void)
{
char str[50];
gets_s(str);
put(str);
//printf("%s",str); 도 가능
}
gets() 함수 : enter 키를 누르기 전까지 공백을 포함한 모든 문자열을 입력받는다.
puts() 함수 : 오직 문자열만 출력한다. printf()함수와는 다르게 문자열을 화면에 출력하고 자동으로 줄바꿈을 한다.
cin, cout
#include <iostream>
int main(void)
{
char str[50];
std::cin >> str;
// std::cin.getline(str, 50);
std::cout << str;
}
cin
- 표준 입력 버퍼에서 개행 문자를 제외한 값을 가져온다( 공백, 개행 무시)
cin.get()
- 표준 입력 버퍼에서 문자 하나만 가져온다( 공백, 개행 포함)
cin.getline(변수의 주소, 최대 입력가능 문자수, 종결 문자)
- 종결 문자를 Null로 바꿈, 종결 문자 생략시 엔터로 간주한다.
- 최대 입력 가능 문자수보다 많은 문자를 입력한 경우에 n-1개 만큼 입력받고 n 번째 문자는 null 로 처리한다.
- 공백, 개행 입력을 받는다
- 문자열만 입력받는다
- cin.getline()과 getline()은 다른 함수이다.
( 입력한 값을 배열에 저장할 때 : cin.getline() / 입력한 값을 string객체에 저장할 때 : getline()
[문자 코드 종류와 처리 원리]
1. ASCII
- 각 문자가 1byte에 할당된다.
2. 멀티바이트(MBCS)
- ASCII 에 2byte 형의 문자들을 포함한 문자 집합( 1byte 짜리 문자와 2byte 짜리 문자를 혼용)
3. 유니코드
- 모든 문자를 각 2byte 에 할당하여 만든 문자 집합.
이때 2byte임을 선언하기 위해서
wchar_t 를 사용하고 입력값에 L(long) 을 앞에 붙인다.
// char 배열명[숫자] = "asdf" 가 아스키 코드에서는 가능하지만 2byte 형에서는 불가능 하다
// -> wchar_t 배열명[숫자] = L"asdf" 로는 가능하다.
처리 원리
- 문자를 입력 -> 특정 전압이 컴퓨터에 전달 -> 이 전압을 bit 로 표현한것을 아스키 코드 값으로 변환 -> 아스키 테이블을 뒤져 값에 해당하는 문자 출력( 이때 숫자 또한 아스키 테이블에 존재하며, 문자로 취급되는 숫자와 아스키 코드의 값인 숫자는 구분된다.)
[std::string]
#include <string> 이 필요
std::string myName; // 선언
std::string myName("shin"); // 초기화 (숫자도 가능하나 텍스트로 처리된다.(조작할 수 없다))
muName = "kim"; // 문자열 값 할당
출력
std::string myName("shin");
std::cout<<"my name is"<<myName; // 출력 my name is shin
[C-style string]
null 로 끝나는 문자열
정의
char iString[] = "string";
char 배열을 선언하고 문자열 리터럴로 초기화 하면된다. null 종결자를 문자열 끝에 자동으로 추가한다. 따라서 IString은 길이가 7인 배열이다.
ctd:out 은 c 스타일 출력할 때 null 종결자를 만날 때까지 문자를 출력한다. 배열의 마지막인 null 이 다른 갚으로 덮어쓰여 있는 경우 std::cout 은 메모리 슬롯이 0을 만날 때까지 모든 값 출력. ( 문자열의 길이보다 배열의 크기가 더 크면 된다!)
포인터를 사용하여 c 스타일 문자열 기호 상수를 만드는 방법
#include <iostream>
int main()
{
char str[] = "asdf";
std::cout<<str; // 고정 배열의 경우
const char * str = "asdf";
std::cout<<str; // 포인터를 사용하여 C 스타일 문자열 기호 상수를 만드는 경우
return 0;
}
이때
const char * str1 = "aaaaa";
const char * str2 = "aaaaa";
이 때 str1과 str2 는 같은 주소를 가리킨다. 따라서 const 가 아닌 경우 str1 을 변경하면 str2에 영향을 줄 수 있다.
=> 나중에 수정할 수 잇는 문자열 변수가 필요한 경우 문자 배열을 사용한다. 읽기전용 문자열 리터럴이 필요한 경우에는 문자열 리터럴에 대한 const 포인터를 사용하자.
int main()
{
int array1[5] = {1, 2, 3, 4 ,5};
char array2[] = "hello";
const char * name = "shin";
std::cout<<array1<<'\n'; //000000C7CA76FA38
std::cout<<array2<<'\n'; // hello
std::cout<<name<<'\n'; // shin
return 0;
}
std::cout 은 char형 이외의 자료형 포인터에 대해서는 포인터값 즉 (포인터가 보유하고 있는 주소)를 출력한다.
그러나 char* 또는 const char* 유형의 객체를 전달하면 문자열을 출력한다고 가정한다.(포인터 값(주소)를 출력하는 것 대신에 지시된 문자열을 출력한다.)
int main()
{
char c = 'A';
std::cout<<&c; // 출력 A儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆儆?맕$?
return 0;
}
이 경우 변수 c의 주소를 출력하겨로 하였으나 &c는 char*타입을 가지고 있으므로 std::cout 은 이것을 문자열로 출력하려 한다. &c를 문자열이라고 가정하기 때문이다 따라서 A 를 출력한 다음 널 종결자를 가진 어떤 메모리를 만날 때까지 출력하는 것.
[문자열 추가]
int main()
{
std::string a("12");
std::string b("34");
std::cout<< a + b << '\n'; // 출력 1234
a += "ggg";
std::cout << a; // 출력 12ggg
return 0;
}
[문자열 길이]
int main() {
std::string name("shin");
std::cout << name << " has " << name.length() << "characters\n"; //출력 shin has 4characters
return 0;
}
[전처리기(Preprocesser)]
프로그램을 컴파일할 때 컴파일 직전에 실행되는 별도의 프로그램.
전처리기가 실행되면 각 코드 파일에서 지시자(directives : #로 시작하는 코드)를 찾는다.
컴파일러가 실행되기 직전에 1. 단순히 텍스트를 치환(수식의 어떤 부분에 그와 대등한 무언가를 바꿔 넣는 행위)하는 역할, 2. 디버깅에 대한 도움, 3. 헤더 파일의 중복 포함 방지 기능을 실행
1. include
- #include : 전처리기는 해당 파일의 내용을 지시자의 위치에 복사한다.
- #include <헤더파일명> : <>는 컴파일러와 함께 제공되는 헤더 파일을 include 할 때 사용. 이때 헤더파일명에 해당하는 헤더파일은 c++ 런타임 라이브러리의 헤더파일로써 운영체제의 특별한 위치에 존재.
- #include "헤더파일명" : 소스파일이 있는 디렉터리에서 헤더 파일을 include 하도록 전처리기에 지시. 보통 자신이 작성한 헤더파일을 include
2. Macro
#define 지시자를 사용해서 매크로(macro)를 만들수 있다.
- 함수 매크로
#define 매크로함수명(전달인자) 매크로확장문자역
전달인자의 형태를 고려하여 치환하는 동작을 한다.
#include <stdio.h>
#define ADD(x) (x+x)
int main(void)
{
int num = 5;
printf("num = %d, num + num = %d\n", num, ADD(num));
return 0;
}
// 출력 num = 5, num + num = 25
이때 ADD * 4와 같이 사용할 경우
매크로확장문자역이 괄호로 닫혀있을 경우 즉 (x + x) 일 때 (5 + 5) * 4 즉 40라는 결과값이 나온다.
매크로 확장 문자역이 괄호로 닫혀있지 않을 경우 즉 x + x 일 때 5 + 5 * 4 즉 25라는 결과값이 나온다.
( 매크로 확장 문자역이 생긴 모습 그대로 적용된다는 것을 기억하자. 전처리기 실행 단계에서 문자 차원의 치환이 일어나는 것을 기억하자)
#include <stdio.h>
#define DOUBLE(x) (x*x)
int main(void)
{
int num = 5;
printf("%d\n", DOUBLE(++num)); // -> DOUBLE(++num*++num)
return 0;
}
이와 같은 경우에서 출력값이 36(6 * 6)이 아닌 49인 이유는 ++num이 연산이 완료된 후에 DOUBLE 의 x*x 로 치환되는 것이 아니라 먼저 치환되어 ++num*++num 의 형식이 되고 이렇게 되었을 때 num은 * 앞에서 한번 뒤에서 한번 총 두번 1이 더해진 값으로 곱하게 되는 것이다.
#define 식별자 대체자1
int main()
{
코드1(식별자가 대체자 1로 치환되어 연산)
#undef 식별자 // 앞서 정의한 define 식별자 해제
#define 식별자 대체자2 // 대체자 1을 대체자 2로 정의
코드2( 식별자가 대체자 2로 치환되어 연산)
}
#define으로 정의한 매크로를 해제하고 싶을 때에는 #undef를 사용한다.
-객체 매크로
#define 식별자
또는
#define 식별자 대체텍스트
대체 텍스트가 있는 경우 전처리기가 이 지시자를 발견하면 코드상의 식별자 들은 대체텍스트로 대체된다.
이때 식별자는 일반적으로 공백을 나타내는 밑줄을 사용하여 대문자로 모두 입력한다.
대체 텍스트가 없는 경우는 보통 조건부 컴파일(conditional compilation)을 하기위해 사용된다.
3. 조건부 컴파일(conditinal compilaion)
- #if
조건문 if 와 비슷한 역할. if조건문이 false 인 경우에 실행이 되지 않는다고 해도 컴파일은 된다면 , #if 는 0일 경우 컴파일자체가 되지 않는다. -> 즉 #if 는 if 와 다르게 조건에 따라 소스코드를 삽입하거나 삭제하기 위해 사용되는 지시자이다.
- #elif
조건문의 else if 에 해당
- #else
조건문읭 else 에 해당
- #endif
전처리 조건문의 종료에 해당한다.
- #ifdef
오로지 조건이 define 에 정의가 되었는지에 대한 여부를 따진다. 조건이 true인지 false 인지는 중요하지 않다. 오로지 정의가 되어 있나 혹은 없나 이분법적으로만 나누기 때문에 #elif 를 사용할 수 없다.
조건부 전처리기 예시
https://jhnyang.tistory.com/299
[포인터]
주소값
해당 데이터가 저장된 메모리의 시작 주소
주소가 증가하는 단위는 바이트 단위이다.(비트 단위는 주소가 부여되지 않는다.
포인터(pointer, 포인터 변수)
메모리의 주소값을 저장하는 변수.
선언 구문 ( 자료형과 변수 이름 사이에 * 가 붙는다.)
자료형* 변수명;
여러 포인터 변수를 선언하는 경우 별표가 각 변수에 포함 되어야 한다.
int * iPointer1, *iPointer2; // int 형 두개의 포인터 선언
int * iPointer1, iPointer2; // iPointer1는 int에 대한 포인터이지만 iPointer2는 단순한 int 이다.
포인터는 선언만 해서는 초기화 되지 않으며, 초기화 되지 않은 값은 쓰레기 값이다.
값 할당
포인터는 메모리 주소만 저장하며, 포인터로 하는 가장 많은 자업은 다른 변수의 주소를 저장하는 것이다.
포인터 = &변수;
변수의 주소를 얻으려면 주소 연산자(&)를 사용한다.
int value = 10;
int *ptr = &value; //변수값의 주소로 ptr 초기화
여기에서 ptr은 value 변수의 주소값을 가지고 있으므로 ptr에 대해 value변수를 '가리킨다'고 할 수 있다.
포인터 변수의 자료형은 가리키는 변수의 자료형과 같아야 한다.
c++ 에서는 포인터에 리터럴 메로리 주소를 직접 할당할 수 없다.
역참조(dereferencing pointer)
포인터 변수에 대해서, 역참조 연사자 *를 통해 포인터가 가리키는 주소의 값을 알 수 있다.
int value = 10;
std::cout << &value; //value의 주소를 출력 // 010FFBC0
std::cout << value; // value 의 값을 출력 // value 의 값을 출력 // 10
int * ptr = &valuel
std::cout<<ptr; // ptr의 값(ptr이 가리키는 변수의 주소값)을 출력한다. // 010FFBC0
std::cout<<*ptr; // ptr을 역참조한다.ptr이 가리키는 변수의 값(=value)을 출력 // 10
포인터 변수가 자료형이 필요한 이유는 자료형이 없다면 포인터가 역참조 시 사리키는 내용을 해석하는 방법을 알지 못하기 때문이고 포인터의 자료형과 가리키는 변수의 자료형이 같아야 하는 이뉴는 역참조 될 때 비트를 다른 자료형으로 잘못 해석하기 때문이다.
*ptr은 value와 같게 취급되므로 마치 일반 변수처럼 값을 할당할 수 있다.
크기
포인터는 실행 파일이 컴파일된 구조적 형태에 따라 달라진다 .32비트 실행 파일은 32비트 메모리 주소를 사용하므로 포인터의 크기는 4byte 이다. 64비트 실행 파일에서 포인터는 8byte 이다.
[널포인터(null pointer]
null 값은 포인터가 아무것도 가리키지 않는다는 것을 의미하는 특수 값이다. null 값을 가진 포인터를 null 포인터라 한다.
c++ 에서 포인터로 null 값을 지정하려면 리터럴 0 으로 초기화 하거나 할당하면 된다.
포인터가 null 이면 부울값 false 로, null이 아닌 경우에는 불린 값 true 로 반환된다.
( 다른 값을 지정하지 않을 땐 포인터를 null 값으로 초기화 할 것)
null 포인터에 역참조를 하면 정의 되지 않은 동작이 발생하고. 대부분은 응용프로그램이 중단된다.
nullptr
널 포인터 값(null pointer value)을 나타내는 포인터 리터럴(pointer literal)
포인터를 표현하는 값 중에 "널을 표현한 값"
int * ptr1 = 0;
int * ptr2 = nullptr;
두 포인터 변수 모두 같은 결과 값을 가지고 있지만 포인터 변수임을 강조하기 위해 포인트 변수에만 사용할 수 있는 nullptr 을 사용할 것
[문자열과 포인터]
char IString[10] = "asdfg"; // ROM 에서 복사받은 지역변수(배열)
IString[0]; // 배열의 각 요소에 접근 (쉽게 수정 가능)
const char* pString = "asdfg"; // ROM에 있는 문자열의 시작주소를 받아옴
// 여기에서 주소를 애시당초 const 로 주기 때문에 이 주소를 const 가 아닌 주소로 받으면 변경이 가능하다.(강제 캐스팅)
char* pStr = (char*)"asdfg";
pStr[0] ="j"; // 컴파일러에서는 오류가 나지 않지만 런타임에서 오류가 난다.