본문 바로가기

Software/C/C++

[C++] c++ 기본

C++ 언어의 기초

1. C 와 C++ 이 다른 점


C 에서는 프로그램을 작성할때 거의 대부분 <stdio.h>를 포함시켰었으나 C++ 에서는 <iostream.h>을 사용한다.
  입출력 함수로 애용되던 printf(), scanf()대신 cout, cin등의 객체를 주로 사용한다 
함수의 선언시에 프로토타입까지 반드시 선언해줘야 한다 
함수의 선언시에 매개변수(가인수)의 생략이 불가능하다 
C 의 확장자는 ".C" 인 것에 비해서 C++ 의 확장자는 ".CPP"를 사용한다.

2. C 보다 C++이 나아진 점

  변수 선언이 함수의 머릿부가 아닌 중간에서도 선언이 가능하다. 그러나 재정의는 불가능하다.
자동변수는 블럭을 벗어나면 소멸된다.

Ex)    for (int i = 0; i < MAX; i++) {...}
  for (int i = 0; i < MAX; i++)        for (int j = 0; j < MAX; j++) {...}
  for (int = 0, j; i < MAX; i++)        for (j = 0; j < MAX; j++) {...}
  for (int i = 0; i < MAX; i++) {        for (int j = 0; j < MAX; j++) { ... } }


  함수가 같은 이름으로 여러개 중복되어 정의될 수 있다.

흔히 다형성 (polymorphism) 이라고 한다.
인수의 갯수가 다르거나 데이터형이 틀리면 다른 함수로 인식한다는  것이다.
데이터형에 상관없이 함수를 호출할 수 있게 되어 매우 편리하다.  단, 인수의 갯수와 데이터형은 같으나 리턴값이 다른 경우의 중복  정의는 불가능하다.

Ex)
  int hap(int x, int y) { return(x+y); }   
double hap(double x, double y) { return(x+y); }

  hap(a, b); 이름은 같은 함수지만 각각 다른  함수가 호출되어 진다.
a와 b가 정수형이면 int hap()함수가 실수형이면 double hap()함수가 호출된다.


함수호출시 디폴트 인수를 지정할 수 있다.
함수호출시 인수를 생략하면 디폴트값이 대입된다.
함수선언시에 인수를 초기화해주면 디폴트인수가 된다.
디폴트 인수에 의해서 지정된 인수는 생략이 가능하며,  생략할 때는 반드시 뒤에서부터 차례로생략이 가능하다.

Ex)
  #include <iostream.h>
  #include <conio.h>
  void outchar(int x, int y, char c = 'D', int count = 4);
  main()
  {        clrscr();        outchar(10, 10, 'S', 5);  // (10, 10)에 'S'를 5번 출력한다.       
outchar(20, 20, 'T');      // (20, 20)에 'T'를 3번 출력한다.       
outchar(10, 20);          // (10, 20)에 'D'를 4번 출력한다.   
}
  void outchar(int x, int y, char c, int count)
  {
      int i;
      for (i = 1; i <= count; i++)
{            gotoxy(x + i, y)
;            cout << c; // printf()함수 대신 쓰이는 객체(object)이다.
      }
  }

출력과 입력

출력 cout  ( iostream.h )

printf()은 C에서 사용하는 출력함수이고 cout은 C++에서 사용하는 출력객체이다.cout은 printf() 보다 사용하기 더 간편하다. 아래의 문장을 보자.

cout<< "Hello C++";


이 문장은 화면에 "Hello C++"이라고 출력한다. <<은 좌측-쉬프트의 비트 연산자이지만 출력을 위해서도 사용된다.
얼마간, cout를 화면(출력 장치)으로 생각하자. 위의 문장은 "Hello C++"를 화면으로 보내라는 명령이라고 생각하면 된다.

Ex) cout으로 출력 1
#include<iostream.h>
#include<conio.h>
void main(){
clrscr();
int i=10;
cout<< "Hello C++";
cout<< "\n";
cout<< i;
cout<< "\n";
cout<< "End";
}

Ex) cout으로 출력 2
#include<iostream.h>
#include<conio.h>
void main(){
clrscr();
int i=65;
float f=1.2345;
double d=-1234.56789;
cout<< "int i is:" << i << "\n";
cout<< "float f is:"<< f << "\n";
cout<< "double d is:"<< d << "\n";
getch();
}

입력 cin ( iostream.h )

scanf()함수를 대체한 객체이다.
cout을 하면으로 생각할 수 있듯이 cin은 키보드라고 할수 있다. 키보드로부터 입력을 받을 때 사용한다. 아래의 문장을 보자.

cin >> a;

키보드로부터 변수 a에 값을 입력받는다.

Ex) cin으로 입력
#include<iostream.h>
#include<conio.h>
void main(){
clrscr();
int a;
cout <<"What is the number: ";
cin >> a;
cout << "\n\nThe number is " << a;
getch();
}

입출력 형식 조작

출력 형식 조작 ( iomanip.h )

입력하거나 출력하는 데이터를 스트림(stream)이라고 하고 이런 조작기들을 스트림 조작기(stream manipulators)라고 한다.

[표]  I/O 스트림 조작기

조작기
설명

dec
hex
oct
endl
ends
flush
setbase(int n)
resetiosflags(long f)
setiosflags(long f)
setfill(int c)
setprecision(int n)
setw(int n)
10진수 전환 베이스를 설정한다.
16진수 전환 베이스를 설정한다.
8진수 전환 베이스를 설정한다.
개행문자('\n')를 삽입하고 스트림 내용을 지운다.
문자열에 널 문자를 삽입한다.
출력 스트림의 내용을 지운다.
n진수로 전환 설정한다.
형식 플래그인 f.f에 의해 지정된 형식을 지운다.
형식 플래그인 f.f에 의해 지정된 형식을 설정한다.
c로 채우기 문자를 설정한다.
n으로 부동 소수점 유효자리를 설정한다.
n으로 필드 폭을 설정한다.


[표]  resetiosflags()와 setiosflags()를 위한 형식 플래그 값.

형식 플래그 이름
설명

ios::left
ios::right
ios::scientific
ios::fixed
ios::dec
ios::hex
ios::oct
ios::uppercase

ios::showbase
ios::showpos
ios::showpoint
setw() 폭 안에 출력을 좌측 정돈한다.
setw() 폭 안에 출력을 우측 정돈한다.
과학용 표기로 출력을 형식 지정한다.
10진수 형식으로 숫자를 형식 지정한다.
10진수로 숫자를 형식 지정한다.
16진수로 숫자를 형식 지정한다.
8진수로 숫자를 형식 지정한다.
16진수와 과학용 표기의 문자를 대문자로 형식 지정한다.
( 0x123을 0X123으로, 2.34e+05를 2.34E+05로 )
수치 베이스 접두 문자를 출력한다.( 16진수의 0x나 8진수의 0 )
양수를 출력할 때 플러스 부호, +를 출력한다.
정확도를 위해 필요하다면 끝의 0들을 표시한다.


형식 플래그의 값들은 상수이다. 영역 지정 연산자(::)는 나중에 설명한다.형식 플래그는 두 함수 resetiosflags()와 setiosflags()에서만 작동한다.

Ex) 조작기 사용
#include<iostream.h>
#include<iomanip.h>
void main(){
int num=220;
clrscr();
cout << "The decimal num is " << num << "\n";  // The decimal num is 220
cout << "The hexadecimal num is " << hex << num << "\n";
// The hexadecimal num is dc
cout << "The octal num is " << setbase(8) << num << "\n";
        // The octal num is 334
cout << setbase(10);
cout << 12345 << "\n";    //  12345
cout << setw(20) << 12345 << "\n";    // '              12345'
cout << setw(20) << setfill('*')<< 12345 << "\n";  // ***************12345
    cout << setiosflags(ios::left);
cout << setw(10) << 12345 << "\n";                        // 12345*****
cout << setiosflags(ios::hex) << 45 <<"\n";                  // 2d
cout << setiosflags(ios::hex | ios::uppercase) << 45 <<"\n";  // 2D
getch();
}

입력 조작

cin은 공백을 무시한다. 공백을 포함하려면 cin.get()을 사용한다.

get(char *string, int length [, char terminator])

[]부분은 생략할 수 있다. 함수의 매개변수 생략은 오버로드함수에 가서 배우기로하고 그냥 넘어가자. terminator는 생략하면 '\n'이 디폴트가 된다. 변수 string에 최대 length까지 받아들일 수 있으며 종료자를 만나면 중지한다.

Ex) get()으로 문자열 받아들이기
#include<iostream.h>
#include<iomanip.h>
#include<conio.h>
void main(){
char name[80];
clrscr();
cout << "What is your name? ";
cin.get(name,30);
cout << "your name is :" << name << endl;
getch();
}

get()은 입력버퍼에 개행문자를 남겨둔다. 따라서 get()을 두 번 사용하면 문제가 발생한다.

cout << "What is your name? ";
cin.get(name,30);
cout << "your name is :" << name <<endl ;
cout << "What is your city? ";
cin.get(city,30);
cout << "your city is :" << city << endl;

city에는 아무것도 입력 받지 못한다. 사용자의 입력을 기다리지 않고 끝나버린다. 이유는 첫 번째 get()이 '\n'을 남겨두고 두 번째 get()이 '\n'을 읽어드린다. 따라서 city="" 이다. 이를 해결하는 방법은 getline()을 써는 것이다.

Ex) getline()으로 두 번 읽기
#include<iostream.h>
#include<iomanip.h>
#include<conio.h>
void main(){
char name[80], city[30];
clrscr();
cout << "What is your name? ";
cin.getline(name,80);
cout << "your name is :" << name <<endl ;
cout << "What is your city? ";
cin.getline(city,30);
cout << "your city is :" << city << endl;
getch();
}

영역 지정 연산자( :: )

::(scope resolution operator)는 지역변수와 전역변수의 이름이 같을 때 전역변수를 사용한다고 C++에게 알려준다. 지역변수에 우선권을 갖는 전역변수를 원하면 변수 앞에 ::를 두면 된다. ::의 중요한 OOP적 사용은 나중에 보게 될 것이다.

Ex) cout으로 출력
#include<iostream.h>
#include<conio.h>
int a=10;            //전역변수
void main(){
int a=20;        //지역변수
clrscr();
cout << a<<endl;    // 지역변수 a=20
cout << ::a<<endl;  // 전역 변수 a=10
getch();
}

const 변수

const를 이용하여 프로그램 내에서 값을 변경하지 않도록 할 수 있다.

const float PI=3.141592;
const int NUM=123;

const는 만들면서 바로 초기화하여야 한다. 나중에 값을 바꾸려고 하면 에러가 발생한다.

NUM=100;  <-- 에러 발생

함수에서 const 매개변수
int func( const int x){
명령 }

위의 함수에서 const int x를 주목하자. 매개변수가 const int 형이다.
그럼 매개변수로서 const가 와야 하는가?... 그런 의미가 아니라 func()내에서 x의 값을 변경할 수 없다는 의미이다.
아래의 문장은 오류가 없다.

int a=10;
func(a);

그러나 매개변수의 값을 바꿀려고 하면 에러가 난다.

int func2(const int x){
x=10;    // 에러 발생
return 1;
}

inline함수

매크로함수와 그냥 함수의 중간적인 성격을 띤 함수이다.
일반적인 함수를 사용하면 필요할 때마다 호출하여 사용하므로  실행파일의 크기를 줄일 수 있으나 제어권의 이동이 심하므로  실행 속도가 느려진다.
매크로 함수는 컴파일시 전개되어 치환되므로 제어권 이동이  발생하지 않으므로 속도가 빠르지만 데이터형 지정을 할 수가  없고 실행파일의 용량이 커지게 된다.
인라인 함수는 컴파일시 통째로 매크로처럼 전개되어 속도면에서  큰 이득을 얻는다. 그리고 데이터형 체크를 할 수 있는 함수이다.  용량이 커지는 단점이 있긴 하지만 간단한 작업을 하는 함수의 경우  에 이용하면 매우 편리하다.



  일반 멤버 함수

  - 맴버 함수는 일반 C++ 함수와 크게 다를 것이 없고 선언 방식과 정의 방식 그리고 사용법에서 차이가 난다.

1. 맴버 함수의 선언

    - 맴버 함수의 선언은 맴버 함수가 속한 클래스의 내부에  일반 함수의 선언과 같은 방식으로 해주면 된다.

class A  {
int  funcA( int );
public :
int  funcB( int );
};

위에서 funcA 는 private 멤버 엑세스 콘트롤을 가지게  되어 class A의 외부에서나 하위 클래스에서 사용할 수  가 없게 되며 이는 멤버 데이터와 같다. 클래스 선언에  서 맴버 함수의  특별한 위치는  없고 작성자의 취향에  맞게 보기 좋은 위치를 선택하여 멤버 데이터와 함수를  선언하면 된다.

2. 맴버 함수의 정의

    - 맴버 함수가  속한 클래스의 이름을 한정되게 사용함으  로서 그 함수가 속한 클래스를 정확히 적어 준다.  그 예를 보면

int A::funcA( int a )  {    ...  }

와 같다.  이 때 A::funcA 라는 한정된 맴버 함수 이름  을 사용하여야만 한다.

3. 인라인 정의

    - C++ 에서는 함수 호출에 걸리는 시간을 줄여  주면서도  매크로보다 디버깅이 편한 인라인 함수를 도입하였다.  인라인으로 맴버 함수를 정의하는 방법은 두 가지가 있  다.  하나는 클래스의 선언 안에 정의하는 방법과 클래  스의 밖에 선언하는 방법이다.

class A  {
int funcA( int a ) {        ...      }
public :
int funcB( int b ) {        ...      }
};

와 같다. 이때 클래스 선언의 내부임으로 A::funcA라는  식의 한정된 이름은 불필요하다. 또 한가지 방법은 inl  ine 이라는 예약어를 사용하면 된다.

inline int A::funcA( int a )  {    ...  };

과같이 해주면 클래스의 내부에 정의한 것과 같은 효과  가 있다. 단 인라인 함수는 그 인라인 함수가 사용되는  모든 모듈의 앞부분에 정의되어 있어야만 한다. 그러므  로 인라인  함수를 클래스의  선언과 함께 헤더 화일에  넣는 것이 보통이다.
C++ 에서 인라인 함수가 가장 많이 사용되는 곳은 Muta  tor 라고 불리는 private, protected 멤버 범위를 가  진 멤버 데이터의 상태를 물어보는 함수들이다.  즉,

class Point  {
int x, y;
public :
int getX() const { return x; };
int getY() const { return y; };
};

맴버 함수 getX 와 getY 가하는 일은 단지 맴버 데이터  x, y 의 상태를 전달해주는 역할이다.  실제로 위와 같  은 인라인 함수는

int  i = point.x; // int i = point.getX();

와 같은 직접  대입과 똑같은 크기의 코드를 만들어 낸  다. getX와 같이 단순히 어떤 맴버 데이터를 단순히 리  턴 하는 함수 등은 반드시 인라인으로 정의하여 코드의  크기도 작아지고 속도도 빠르게 하자.  단 인라인 함수  에는 for, while, do - while 등의 반복 구문은 사용할  수가 없다. 사족을 붙인다면 버그의 가능성이나 중요한  함수는  개발의 마지막 단계에서  인라인으로 선언하는  것이 좋다.
 
4. 맴버 함수의 오버로딩

    - 인라인 함수와 함께 C++ 에 도입된 함수 정의 방식으로  같은 이름의 여러 다른  함수를 선언 및 정의하여 사용  할 수가 있는 함수들로서 맴버 함수에도 그대로 그  규  칙이 적용된다.

class A  {   
public :
    int    funcA( int );
    int    funcA( int, int );
    int    funcA( char );
    int    funcA( int, int, int, ... );
};

5. const 맴버 함수

    - const 맴버 함수는 다음과 같이 선언된다.

class A  {
public :
int funcA( int ) const {          ...      } 
};

const 예약어가 함수의 선언 뒤에 사용된 맴버  함수를  const 맴버 함수라고 부른다. const 맴버 함수는 그 함  수 내에서는 어떤 맴버 데이터의 변형도 일어나지 않는  다는 일종의  버그의 가능성을 줄이는 효율적인 코딩을  할 수 있도록 도와주는 '선언' 이다.  const 맴버 함수  내에서는 const 형이 아닌 함수를 호출할 수가  없으며  맴버 데이터도 변형할 수가 없다. 이 const 형 맴버 함  수와 const 형 함수인자를 잘 섞어서 쓰면 불필요한 버  그의 가능성을 상당 부분 줄일 수가 있으며  함수의 선  언만으로도 그  맴버 함수를 호출하여도 맴버 데이터가  변화되지 않는다는 것을  알 수 있어 코드를 읽기 편하  게 해준다.

6. 맴버 함수의 선언과 정의

    - 맴버 함수의 선언은 클래스의 선언 속에서 이루어진다.  하지만 맴버 함수의  정의는 클래스 외부에서 이루어지  는 것이 보통이다. 이 때 한 클래스에 선언된 맴버함수  모두가 정의될 필요는 없다. 또 한 클래스에 선언된 맴  버 함수가 같은 모듈에 들어  있을 필요도 없다.  만일  어떤  클래스가 있고 그 클래스에  속한 맴버 함수들이  모두 사용되지 않는다면 선언된 맴버 함수들 전체가 정  의되어 있지 않아도 된다.  이런 특성을 살리면 프로그  램 개발 과정에서 많은 시간적 이득을 볼 수가 있다.

[C++ 강좌3]

C++ 언어의 기초3

상수형 데이터
C 에서도 #define에 의한 상수가 있었지만 C++ 에서는 더욱 강력히  사용되며 const 예약어를 사용하여 정의한다.
  형식 : const 자료형 변수명 = 상수;
Ex)
    const double pi = 3.141592;
- 상수형은 정의와 동시에 반드시 초기화가 되어야 한다. 
- 상수형은 변화시킬 수도 없고, 변화해서도 안된다.  - C에서와 달리 자료형의 지정이 가능해졌다.
- 이 상수형은 pointer형에서 유용하게 쓰인다.
[1] const int *p;  --> 내용의 상수화 ; 주소의 변경 가능, 내용의 가능 불가   
[2] int * const p;  --> 주소의 상수화 ; 주소의 변경 불가, 내용의 가능 가능   
[3] const int *const p;  --> 내용과 주소의 상수화 ; 주소의 변경 불가, 내용의 가능 불가

new, delete 연산자에 의한 메모리의 동적할당
C 에서 malloc, free 등의 함수를 사용하던 메모리의 동적할당을  단순히 연산자 하나로서 가능해졌다.

new 연산자

형식 :  포인터변수 = new 자료형(크기);
    Ex)        int  *i = new int;        char *p = new char[5];
  - new 연산자는 sizeof(할당대상)의 메모리를 heap영역에 확보하여 선두번지값을 리턴한다. 메모리 확보에 실패한 경우 _new_handler 라는 함수를 호출하여 프로그램을 종료시키나, set_new_handler함수를 사용하여 수정을 하여 섬세한 처리를 할 수가 있다. (malloc의 경우: NULL값 리턴)  이렇게 해서 동적할당된 메모리는 사용이 끝나면 반드시 해제시켜야 한다.

delete 연산자    - 메모리의 해제는 delete 연산자에 의해서 가능하다.

  형식 : delete 포인터변수;
  위에서 할당되었던 메모리를 해제하기 위해서는 다음과 같이 한다.
        delete i;        delete p;
new로 생성된 메모리를 가리키는 포인터 변수는 일반변수와 마찬가지로 사용할 수 있게 된다. class의 객체를 생성할 때는 객체를 초기화시켜 주는 함수까지 호출시킬 수 있다.    다음 예를 보면 무슨 뜻인지 알 것이다.
Ex1) int *buffer = new int[5];
for (int i = 0; i <= 4; i++)            buffer[i] = i;
  Ex2) struct person {
char  name[15];
int age;
float height;
}
person *p = new person;
strcpy(p->name,"ACCESS");
p->age = 21;
p->height = 178.5;
};

4. linkage 지정

  linkage 란?

컴파일시에 컴파일러가 오브젝트 파일에 남겨놓는 링크에 관한 정보를 말한다. 따라서 링커는 linkage 정보를 보고 어떤 함수가 결합되어야 할지 판단하게 된다.  C에서는 함수끼리 이름만으로  구분이  가능했었으나 C++ 에서는 다형성에 의해서 이름만으로는 구분이 불가능하게 되었다.  따라서 C++에서 C 함수를 사용하기 위해서 컴파일러가 linkage를 C++에 알맞게 변화시키는데 이것을 네임 맹글링 (name mangling)이라고 한다.
  형식 :  함수의 프로토타입 앞에 extern "C" 를 붙여주면 된다.
Ex)extern "C" int myfunc(void);
extern "C" {int  myfunc(void);
void subfunc(char *c);
char *str(int n);
}
extern "C"  {        #include "user.h"    }

어셈블러로 짜여진 모듈을 사용할 때에도 extern "C"라고 써준다.
기존에는 프로젝트 파일을 아스키문서로 작성하였으나 Borland계열의 C++ 에서 프로젝트를 하기 위해서는 IDE내의 프로젝트 파일 에디터를 사용하여 무척 쉽게 작성이 가능하다. 게다가 어셈블러 모듈까지 프로젝트에 사용할 수가 있다.