여기에서는 C/C++언어에서 객체의 이름을 짓는 기본 규칙을 설명한다. 변수(variable), 함수(function), 그리고 클래스(class)의 이름을 지정하는데 사용되는 이름을 식별자(identifier)라고 한다. 변수란 어떤 데이터를 저장하는 그릇으로 이해하면 되며 함수나 클래스에 대해서는 뒤에서 자세히 설명할 것이다.


  식별자를 만드는 데에는 다음과 같은 제약 사항이 있다.


        ❶ 알파벳 대소문자(a, b, …, z, A, B, …, Z), 숫자(0,1,2, …9), 밑줄(_)을 조합하여 만든다.

        ❷ 숫자로 시작해서는 안 된다.

        ❸ 최대 길이는 32자이다.

        ❹ C/C++언어의 예약어는 식별자로 쓸 수 없다.


특수문자로는 유일하게 밑줄(_)문자가 식별자를 만드는데 사용이 되며 이외의 다른 특수문자는 식별자롤 사용할 수 없다. 아래의 예는 올바른 식별자이다.


------------------------------------------------------

  iA cStatus iMotor10 i_AVR_Name _reg For

------------------------------------------------------


아래의 예는 올바르지 않은 식별자이다.


------------------------------------------------------

  123a   : 숫자로 시작하면 안 됨

  A@Bc : 특수문자 @은 사용 불가

  %pi    : 특수문자 % 사용 불가

  for      : 키워드는 사용 불가

------------------------------------------------------


  한 가지 주의할 점은 C언어에서는 식별자를 사용할 때 대소문자를 구별한다는 점이다. 즉 다음 식별자들은 모두 서로 다른 것으로 구별된다.


------------------------------------------------------

  ia, iA, Ia, IA

------------------------------------------------------


이러한 특징은 거의 대부분의 프로그래밍 언어들에 있어서 공통적으로 해당된다.​

[#00070]


Posted by 살레시오
,

  C++의 클래스에는 복사 생성자 (copy constructor)라는 것이 있는데 일반 생성자에 비해서 처음 C++을 익힐 때 그 동작에 대해서 간과하기 쉽다. 하지만 그 동작에 대해서 꼼꼼하게 살펴보지 않으면 논리적 오류가 만들어지기 쉽다.


  복사생성자가 호출되는 경우는 다음과 같은 경우가 있다.


-----------------------------------------------------------------

ClassOne c1, c2; // c1이 만들어지면서 일반 생성자를 호출

ClassOne c3 = c1;// ① 선언과 동시에 대입이 일어날때 복사 생성자를 호출

ClassOne c4(c1); // 위와 동일하게 복사생성자를 호출한다. 즉, ClassOne c4=c1 과 완전히 동일하다.

...

c2 = func(c1); //

...

ClassOne func(ClassOne co) { // ② 함수의 입력 인수 co 는 복사생성자로 생성됨

...

ClassOne cr;

....

return cr; // ③ cr을 넘겨줄 때도 복사 생성자 호출

}

------------------------------------------------------------------


사용자가 작성하지 않은 경우 디폴트 복사 생성자가 자동으로 만들어지지만 얕은 복사를 수행한다. 이 경우 멤버 변수에 동적으로 메모리가 할당되는 포인터라도 있다면 문제가 발생한다.


  따라서 사용자가 포인터의 내용까지도 따로 복사해 주는 깊은 복사를 수행하는 복사생성자를 항상 작성해 두는 것이 바람직하다.

[#00064]


Posted by 살레시오
,

  이미 생성된 인스턴스에 다른 인스턴스를 대입할 때 대입 연산자(=)가 이용되며 실제로는 복사가 수행된다. 다음과 같은 간단한 클래스를 고려해 보자.


-------------------------------------------

class Led {

    public :

        int pin;

        char *name;

};

-------------------------------------------


이 클래스는 복사 생성자도 없고 =연산자도 오버로딩되지 않았다. 이 경우 디폴트=연산자 함수는 얕은 복사를 수해하게 된다. 이제 다음과 같은 두 예를 보자


-------------------------------------------

//ex1

Led led1;

led1.pin=13;

Led led2 = led1; // 복사생성자 호출


//ex2

Led led1, led2;

led1.pin=13;

led2 = led1; // 대입 함수 호출

-------------------------------------------


<ex1>에서 led2가 생성될 때는 복사생성자가 실행되고  <ex2>에서는 대입 연산 함수가 호출된다.


하지만 멤버변수에 포인터가 있으므로 여기에 문자열이 저장된 경우 얕은 복사는 문제가 일어나게 된다. 


-------------------------------------------

Led led1, led2;

led1.name = new char[3];

strcpy(led1.name, "hi");


led2 = led1; // 얕은 복사가 일어나므로 문제를 야기함.

-------------------------------------------


심지어 깊은 복사를 수행하는 복사생성자가 정의되어 있어도 대입 연산자는 얕은 복사만을 수행하므로 주의해야 한다. 즉, 대입연산은 자동으로 사용자가 정의한 복사 생성자를 호출하지 않는다. 아래와 같이 깊은 복사를 수행하는 복사 생성자를 작성했다고 하자.


-------------------------------------------

class Led {

    public :

        int pin;

        char *namer;

        Led(const Led& src) {

            pin = src.pin;

            name = new char[strlen(src.name)+1];

            strcpy(name, src.name);

        }

};

-------------------------------------------


복사 생성자는 생성자이므로 인스턴스가 새로 생성될 때 수행된다. 따라서 name필드에 이전에 메모리를 할당한 적이 없을 것이니 해제할 필요도 없다.


  이렇게 작성한 뒤에서 대입연산은 여전히 얕은 복사를 수행하게 된다. 즉, 복사생성자와 대입연산자는 별개이다.  대입 시에도 깊은 복사를 수행하려면 대입연산자를 오버로딩하여 사용자가 작성해주어야 한다. 한 가지 주의할 점은 대입연산자는 이미 생성된 인스턴스에다 복사해 넣는 것이므로 기존의 문자열을 해제하는 코드가 추가로 필요하다는 것이다. 이것이 대입연산자와 복사생성자의 차이이다.


-------------------------------------------

Led& Led::operator= (const Led& src) {

    if (this == &src) return *this; // 자기 대입 방지


    pin = src.pin;

    i(name != NULL) delete[] name; // (주의)

        name = new char[strlen(src.name)+1];

        strcpy(str, src.name);


    return *this;

}

-------------------------------------------


이제는 대입연사자를 사용하면 깊은 복사가 일어나게 된다. 만약 복사 생성자와 코드가 많이 중복된다면 중복되는 부분은 private 멤버 함수로 따로 작성하는 것이 더 효율적일 것이다.

[#00063]


Posted by 살레시오
,

_ (1) 정수형 상수 


 표에서 분류한 것과 같이 정수형은 크게 다섯 가지(char, short, int, long, long long)의 자료형이 있으며 바이트 수가 클수록 숫자 데이터의 범위도 커지게 된다. 정수형 변수에는 정수형 상수를 저장할 수 있으며 소수점(혹은 e/E)가 없는 숫자는 정수형 상수이다.


  예를 들어 부호가 있는 int 형 변수 ia 를 숫자 0으로 초기화하려면 다음과 같이 하면 된다.


---------------------------------------------------------------------------

int ia = 0;

---------------------------------------------------------------------------


이후에는 ia라는 int형 변수를 '사용'하여 정수 값을 저장하거나 산술 연산을 수행할 수 있다. 


---------------------------------------------------------------------------

int ia = 0;

ia = 34*567; // *는 곱셈 연산자임

---------------------------------------------------------------------------


  정수형 상수는 기본적으로 int형으로 간주되므로 저장하는데 내부적으로 4 byte(혹은 2 byte)가 소요가 된다. 만약 숫자 상수를 특정한 자료형으로 지정해 주고 싶다면 접미어를 붙여서 그 데이터형을 지정할 수 있는데 U(혹은 u)를 붙이면 unsigned int형이고 l(혹은 L)은 long형, ul(혹은 UL)은 unsigned long형임을 나타낸다.



접미어

자료형

예제

u/U

unsigned int

37u, 123U

l/L

long

12l, 12L

ul/UL

unsigned long

89ul, 89UL


--------------------------------------------------------------------------

unsigned int uia = 0u;

unsigned long ula = 123ul;

---------------------------------------------------------------------------


하지만 보통은 숫자 상수는 대입하려는 변수의 자료형에 자동으로 변환되어 저장되므로 굳이 이렇게 일일이 자료형에 맞추어 접미어를 붙여줄 필요는 없다. 즉,


---------------------------------------------------------------------------

unsigned long ula = 123; 

---------------------------------------------------------------------------


이라고 초기화하여도 123이라는 int형 상수는 자동으로 unsigned long 형으로 변환된 후 변수 ula에 저장된다.


  만약 숫자상수를 short 형이나 unsigned char 형으로 지정하고 싶다면 다음과 같이 해야 한다.


---------------------------------------------------------------------------

short sa = (short)1; 

unsigned char uca = (unsigned char)123;

---------------------------------------------------------------------------


이와 같이 숫자 상수 앞에 (자료형) 을 붙여기 자료형을 강제로 변환하는 것을 형변환(casting)이라고 한다. 하지만 이 경우도 아래와 같이 할 수 있다. 자동으로 형변환이 일어나기 때문이다.


---------------------------------------------------------------------------

short sa = 1; 

unsigned char uca = 123;

---------------------------------------------------------------------------


  한 가지 알아두어야 할 사실은 C/C++언어에서 정수를 표현할 때 0으로 시작되는숫자는 8진수이고, 0x로 시작하는 숫자는 16진수라는 것이다. 예를 들어서 123은 그냥 10진수 123이지만 0123 이라고 입력하면 8진수 123이기 때문에 10진수로 변환하면 83이다. 그리고 0x123은 16진수 123이기 때문에 10진수로 변환하면 291이 된다. 이것을 다음 표에 정리하였다.



숫자 상수

십진수값

비고

123

123

10진수

0123

83

8진수

0x123

291

16진수

0b1110

14

2진수(gcc에서 지원, C++14)


(2) 문자 상수


  C/C++ 언어에서는 정수형 변수에 정수뿐만 아니라 문자를 저장할 수 있으며 특별히 char형과 int형은 문자를 저장하는 목적으로도 자주 사용된다. 문자상수는 작은 따옴표(‘’)를 이용하여 표현되며 예를 들면 ‘a’, ‘+’, ‘0’ 등이 있다. 문자상수는 아스키(ASCII)코드라는 고유의 상수값이 정해져 있는데 다음 표에 몇몇 문자상수와 아스키코드값을 수록하였다.



종류

문자와 ASCII 코드

숫자 (문자)

ASCII 코드

‘0’

48

‘1’

49

‘2’

50

‘9’

57

알파벳 대문자

ASCII 코드

‘A’

65

‘B’

66

‘C’

67

...

‘Z’

90

알파벳 소문자

ASCII 코드

‘a’

97

‘b’

98

‘c’

99

...

‘z’

112

특수문자

ASCII 코드

‘&’

38

‘*’

42

‘+’

43




  char형은 크기가 1바이트이므로 부호 없는 unsigned char 변수인 경우 0부터 255까지의 작은 숫자를 저장할 수 있으며 short형은 2바이트로 부호 없는 숫자의 경우 0부터 65,535까지 저장할 수 있다. 만약 char형 변에 문자상수, 예를 들어서 ‘a’가 저장되면 내부적으로는 97이라는 숫자가 저장된다는 것을 알아야 한다.




bit7

bit6

bit5

bit4

bit3

bit2

bit1

bit0

‘a’ (97)

0

1

1

0

0

0

0

1


  부호가 있는 정수형 데이터는 음수를 2의 보수로 저장하기 때문에 저장범위가 char형은 –128~+127이 되고 short형은 –32,768~+32,767이 된다.


Posted by 살레시오
,