LAB/C++

C++ 기초플러스 (05. 루프와 관계 표현식 - 07. 함수 - C++의 프로그래밍 모듈)

it-lab-0130 2024. 9. 24. 20:42
for 루프
// forstr1.cpp -- for 루프를 사용한 문자열 처리
#include <iostream>
#include <string>
int main()
{
    using namespace std;
    cout << "단어를 입력하세요: ";
    string word;
    cin >> word;

    // 문자열을 거꾸로 출력한다.
    for (int i = word.size() - 1; i >= 0; i--)
        cout << word[i];
    cout << "\n종료.\n";
    // cin.get();
    // cin.get();
    return 0; 
}

 

 

부수 효과와 시퀀스 포인트

 

부수 효과(side effect) 란?

수식을 평가할 때 변수에 저장되어 있는 값과 같은 것이 변경될 때 일어나는 효과를 말한다.

 

시퀀스 포인트 란?

프로그램의 실행이 다음 단계로 넘어가기 전에 모든 부수 효과들이 확실하게 평가되는 포인트이다.

C++에서 구문에 있는 세미콜론은 시퀀스 포인트를 표시한다.

그것은 구문에서 대입연산자, 증가연산자, 감소연산자에 의해 일어나는 모든 변화가 프로그램이 다음 구문으로 넘어가기 전에 반드시 일어나야 한다는 것을 의미한다.

 

증가 / 감소 연산자와 포인터
double arr[5] = {21.1, 32.8, 23.4, 45.2, 37.4}; 
double *pt = arr;   // pt는 arr[0], 즉 21.1을 지시한다
++pt;               // pt는 arr[1], 즉 32.8을 지시한다

// 접두형 연산자들에 적용되는 오른쪽에서 왼쪽으로의 결합 규칙은 
// *++pt의 경우에 (++가 *의 오른쪽에 있기 때문에) ++가 pt에 먼저 적용되고 나서, pt의 새로운 값에 
// *가 적용된다는 것을 의미한다.
*++pt;   // 포인터 증가, 값을 취한다; 즉, arr[2], 또는 23.4

// pt가 지시하는 값을 먼저 구하고 나서, 그값을 증가시킨다
++*pt;   // 지시되는 값 증가; 즉, 23.4를 24.4로 변경

// 지시되는 값 증가
(*pt)++;  // 24.4가 얻어진다; 그리고나서 ++연산자가 그 값을 25.4로 증가시킨다.

*pt++; // 원래 주소를 참조하고 나서 포인터 증가

 

콤마 연산자
// forstr2.cpp -- 문자열 뒤집기
#include <iostream>
#include <string>
int main()
{
    using namespace std;
    cout << "단어를 입력하세요: ";
    string word;
    cin >> word;

    // string 객체를 실제로 변경한다.
    char temp;
    int i, j;
    for (j = 0, i = word.size() - 1; j < i; --i, ++j)
    {                       // 블록 시작
        temp = word[i];
        word[i] = word[j];
        word[j] = temp;
    }                       // 블록 끝
    cout << word << "\n끝\n";
    // cin.get();
    // cin.get();
    return 0; 
}

 

- ++j , --i   :  두 개의 표현식이 하나의 표현식으로 간주된다.

 

while문
// while.cpp -- while 루프 문
#include <iostream>
const int ArSize = 20;
int main()
{
    using namespace std;
    char name[ArSize];

    cout << "영문 이름을 입력하세요: ";
    cin >> name;
    cout << "귀하의 영문 이름을 한 줄에 한 자씩:\n";
    int i = 0;                  // 문자열의 첫머리에서 시작
    while (name[i] != '\0')     // 문자열의 끝자리 처리
    {
        cout << name[i] << ": " << int(name[i]) << endl;
        i++;                   
    }
    // cin.get();
    // cin.get();
    return 0; 
}

 

- while문 조건으로 배열에 널문자 (\0)인지 검사한다.

 

 

잠시만 - 시간 지연 루프

 

예를들어, 메시지를 출력한 뒤에 사용자가 그 메시지를 읽기도 전에 다른 동작으로 후딱 전환하기 전에 시간을 지연시키는 동작을 넣어 사용되어 진다.

 

// waiting.cpp -- clock()을 시간 지연 루프에 사용한다.
#include <iostream>
#include <ctime> // clock() 함수와 clock_t형이 정의되어있다.
int main()
{
    using namespace std;
    cout << "지연 시간을 초 단위로 입력하세요: ";
    float secs;
    cin >> secs;
    clock_t delay = secs * CLOCKS_PER_SEC;  // 지연 시간을 시스템 단위 클록 수로 변환
    cout << "카운트를 시작합니다.\a\n";
    clock_t start = clock();
    while (clock() - start < delay )        // 시간이 경과할 때까지 대기
        ;                                   // 세미콜론에 유의
    cout << "끝 \a\n";
    // cin.get();
    // cin.get();
    return 0; 
}

3초 후

 


06. 분기 구문과 논리 연산자

레이블로 열거자 사용하기

 

// enum.cpp -- using enum
#include <iostream>
// create named constants for 0 - 6
enum {red, orange, yellow, green, blue, violet, indigo};

int main()
{
    using namespace std;
    cout << "컬러 코드 (0-6)를 입력하세요: ";
    int code;
    cin >> code;
    while (code >= red && code <= indigo)
    {
        switch (code)
        {
            case red     : cout << "입술은 붉었습니다..\n"; break;
            case orange  : cout << "머리카락은 귤색이었습니다.\n"; break;
            case yellow  : cout << "신발은 노랑색이었습니다.\n"; break;
            case green   : cout << "손톱은 초록색이었습니다.\n"; break;
            case blue    : cout << "스웨터는 파랑색이었습니다.\n"; break;
            case violet  : cout << "눈은 보라색이었습니다.\n"; break;
            case indigo  : cout << "분위기는 쪽빛이었습니다.\n"; break;
        }
        cout << "컬러 코드 (0-6)를 입력하세요: ";
        cin >> code;
    }
    cout << "끝\n";
    // cin.get();
    // cin.get();
    return 0; 
}

 

수를 읽어들이는 루프

 

// cinfish.cpp -- 수가 아닌 입력으로 루프를 종료시킨다.
#include <iostream>
const int Max = 5;
int main()
{
    using namespace std;
// 데이터를 입력받는다
    double fish[Max];
    cout << "오늘 낚은 물고기의 무게를 입력하세요.\n";
    cout << "물고기는 최대 " << Max
            << " 마리까지 낚을 수 있습니다. <입력을 끝내려면 q를 누르세요>.\n";
    cout << "fish #1: ";
    int i = 0;
    while (i < Max && cin >> fish[i]) {
        if (++i < Max)
            cout << "fish #" << i+1 << ": ";
    }
// 평균을 계산한다.
    double total = 0.0;
    for (int j = 0; j < i; j++)
        total += fish[j];
// 결과를 보고한다
    if (i == 0)
        cout << "물고기를 한 마리도 낚지 못하셨군요.\n";
    else
        cout << "물고기 "<< i << "마리의 평균 무게는 " 
             << total / i << " 그램입니다. \n";
            
    cout << "종료.\n";

//	cin.get();    // read end of line after last input
//	cin.get();    // wait for user to press <Enter>
    return 0; 
}

 

 

[수가 아닌 값을 입력 예외 처리]

// cingolf.cpp -- 수가 아닌 입력은 무시한다
#include <iostream>
const int Max = 5;
int main()
{
    using namespace std;
// get data
    int golf[Max];
    cout << "골프 점수를 입력하세요.\n";
    cout << "총 " << Max << " 라운드 점수를 입력해야 합니다.\n";
    int i;
    for (i = 0; i < Max; i++)
    {
        cout << "round #" << i+1 << ": ";
        while (!(cin >> golf[i])) {   // cin값이 0이 아닌이상 true
            cin.clear();     // 입력을 초기화 한다
            while (cin.get() != '\n')
                continue;    // 불량 입력을 제거한다
            cout << "골프 점수를 입력하세요: ";
        }
    }
// 평균을 계산한다
    double total = 0.0;
    for (i = 0; i < Max; i++)
        total += golf[i];
// 결과를 보고한다
    cout << "총 " << Max << " 라운드의 평균점수 = "
         << total / Max << endl;
            
    // cin.get();
    // cin.get();
    return 0; 
}

 

 

- must i? 입력 시 while( !(cin >> golf[i])) 표현식은 false가 되고 !(cin >> golf[i])는 true가 되어  루프가 진행된다. 

 


07. 함수 - C++의 프로그래밍 모듈

함수 원형과 함수 호출
// protos.cpp -- 함수 원형과 함수 호출
#include <iostream>
void cheers(int);       // 함수 prototype : 리턴 값이 없다.
double cube(double x);  // 함수 prototype: double형을 리턴한다.
int main()
{
    using namespace std;
    cheers(5);          // 함수 호출
    cout << "하나의 수를 입력하세요: ";
    double side;
    cin >> side;
    double volume = cube(side);    // 함수 호출
    cout << "한 변의 길이가 " << side <<" 센티미터인 정육면체의 부피는 ";
    cout << volume << " 세제곱센티미터입니다.\n";
    cheers(cube(2));    // 원형 보호에 의해 작동한다.
    // cin.get();
    // cin.get();
    return 0;
}

void cheers(int n)
{
    using namespace std;
    for (int i = 0; i < n; i++)
        cout << "Cheers! ";
    cout << endl;
}

double cube(double x)
{
    return x * x * x; 
}

 

- 함수안에서 std 이름공간의 멤버들을 사용하는 함수에만 using 지시자를 넣는다.

 

두 개의 매개변수를 사용하는 또 다른 함수
// lotto.cpp -- 승률 구하기
#include <iostream>

long double probability(unsigned numbers, unsigned picks);
int main()
{
    using namespace std;
    double total, choices;
    cout << "전체 수의 개수와 뽑을 수의 개수를 입력하세요: \n";
    while ((cin >> total >> choices) && choices <= total)
    {
        cout << "당신이 이길 확률은 ";
        cout << probability(total, choices);      // 승률을 계산한다
        cout << "번 중에서 한 번입니다.\n";
        cout << "다시 두 수를 입력하세요 (끝내려면 q를 입력): ";
    }
    cout << "끝\n";
    // cin.get();
    // cin.get();
    return 0;
}

// 이 함수는 numbers개의 수 중에서
// picks개의 수를 정확하게 뽑을 확률을 계산한다
long double probability(unsigned numbers, unsigned picks)
{
    long double result = 1.0;  // 이 자리에는 지역 변수들이 온다
    long double n;
    unsigned p;

    for (n = numbers, p = picks; p > 0; n--, p--)
        result = result * n / p ; 
    return result;
}

 

포인터를 배열 이름처럼 사용하는 방법

 

// arrfun1.cpp -- 배열 매개변수를 사용하는 함수
#include <iostream>
const int ArSize = 8;
int sum_arr(int arr[], int n);        // prototype
int main()
{
    using namespace std; 
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};


    int sum = sum_arr(cookies, ArSize);
    cout << "먹은 과자 수 합계: " << sum <<  "\n";   // using 사용하지만 std::cout으로 사용할 수 있다 
    // cin.get();
    return 0;
}

// 정수 배열의 합계를 리턴한다
int sum_arr(int arr[], int n)
{
    int total = 0;

    for (int i = 0; i < n; i++)
        total = total + arr[i];
    return total; 
}

 

*** cookies는 그배열의 하나의 원소의 주소이다.

 

배열을 매개변수로 사용하는 것의 의미

 

** 매개변수 배열은 첫 번째 원소의 주소값을 나타낸다.

 

// arrfun2.cpp -- 배열 매개변수를 사용하는 함수
#include <iostream>
const int ArSize = 8;
int sum_arr(int arr[], int n);
// using 지시자 대신 std::를 사용한다
int main()
{
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};

    std::cout << cookies << " = 배열 주소, ";

    std::cout << sizeof cookies << " = sizeof cookies\n";
    int sum = sum_arr(cookies, ArSize);
    std::cout << "먹은 과자 수 합계: " << sum <<  std::endl;
    sum = sum_arr(cookies, 3);        // 배열 원소 개수 속이기
    std::cout << "처음 세 사람은 과자 " << sum << " 개를 먹었습니다.\n";
    sum = sum_arr(cookies + 4, 4);    // 배열 원소 개수 속이기
    std::cout << "마지막 네 사람은 과자 " << sum << " 개를 먹었습니다.\n";
    // std::cin.get();
	return 0;
}

// 정수 배열의 합계를 리턴한다
int sum_arr(int arr[], int n)
{
    int total = 0;
    std::cout << arr << " = arr, ";

    std::cout << sizeof arr << " = sizeof arr\n";
    for (int i = 0; i < n; i++)
        total = total + arr[i];
    return total; 
}

 

배열의 내용 출력과 const로 보호하기

 

배열의 원본을 가지고 작업하는데 배열의 값이 변경되지 않도록 하려면 const 키워드를 형식 매개변수를 선언할 때 사용할 수 있다.

void show_array(const double ar[ ] , int n);

=> 포인터 ar이 상수 데이터를 지시하고 있다는 것을 의미하며, ar을 사용해서는 그 데이터를 변경할 수 없다는 것을 뜻한다.

이것은 원본 배열이 반드시 상수이어야 한다는 의미는 아니고, 다만 show_array() 함수가 ar을 사용하여 그 데이터를 변경할 수 없다는 뜻이다.

 

배열의 범위를 사용하는 함수

 

배열을 처리하는 함수에 접근하는 방법은

  1. 매개변수로 배열의 시작 위치를 지시하는 포인터를 전달한다.
  2. 매개변수로 배열의 크기를 전달한다.(그 포인터는 배열이 어디에 있는지와, 배열에 들어 있는 데이터의 종류가 무엇인지를 말해준다.)
// arrfun4.cpp -- 배열 범위를 사용하는 함수
#include <iostream>
const int ArSize = 8;
int sum_arr(const int * begin, const int * end);
int main()
{
    using namespace std;
    int cookies[ArSize] = {1,2,4,8,16,32,64,128};

    int sum = sum_arr(cookies, cookies + ArSize);
    cout << "먹은 과자 수 합계: " << sum <<  endl;
    sum = sum_arr(cookies, cookies + 3);        // 처음 3개의 원소
    cout << "처음 세 사람은 과자 " << sum << " 개를 먹었습니다.\n";
    sum = sum_arr(cookies + 4, cookies + 8);    // 마지막 4개의 원소
    cout << "마지막 네 사람은 과자 " << sum << " 개를 먹었습니다.\n";
    // cin.get();
    return 0;
}

// 정수 배열의 합계를 리턴한다
int sum_arr(const int * begin, const int * end)
{
    const int * pt;
    int total = 0;

    for (pt = begin; pt != end; pt++)
        total = total + *pt;
    return total; 
}

 

 

포인터 와 const

 

const라는 키워드는 포인터에 두 가지 방법으로 사용된다.

  1. 상수 객체를 지시하는 포인터를 만드는 것이다.(상수 객체를 지시하는 포인터를 사용하여 그 포인터가 지시하는 값을 변경할 수 없다.)
  2. 포인터 자신을 상수로 만드는 것이다. (상수 포인터를 사용하여 그 포인터가 지시하는 장소를 변경할 수 없다.)
상수를 지시하는 pt 포인터 선언
int age = 39;
const int *pt = &age;

이 선언은 포인터 pt가 const int(이 경우에는 39)를 지시하고 있음을 말해 준다. 그러므로 pt를 사용하여 그 값을 변경할 수 없다. 다시 말해, *pt가 const이므로 변경할 수 없다.

*pt += 1;    // pt는 const int를 지시하고 있으므로 사용할 수 없다
cin >> *pt  // 마찬가지 이유로 사용할 수 없다


*pt = 20;    // pt는 const int를 지시하고 있으므로 사용할 수 없다
age = 20;  // age는 const로 선언되지 않았기 때문에 사용할 수 있다

// const 변수의 주소를 const를 지시하는 포인터에 대입하는 것
const float g_earth = 9.80;
const float *pe = &g_earth;    // 사용가능

// const 변수의 주소를 일반 포인터에 대입하는것
const float g_moon = 1.63;
float *pm = &g_moon;           // 사용할 수 없다

 

*** const가 아닌 포인터를 const 포인터에 대입하는 것은 한다리 건너는 간접 지시인 경우에만 사용할 수 있다.

int age = 39;   // age++는 사용할 수 있다
int *pd = &age;   // *pd = 41은 사용할 수 있다
const int *pt = pd;  // *pt = 42는 사용할 수 없다

 

=> const가 아닌 주소나 포인터를 const 포인터에 대입할 수 있다는 규칙은 한다리 건너는 간접 지시자인 경우에만 유효하다

예를 들면, 그 포인터가 기본 데이터형을 지시하는 경우에만 유효하다.

=> 데이터형 자체가 포인터가 아니라면, const 데이터의 주소이든 const가 아닌 데이터의 주소이든 const를 지시하는 포인터에 모두 대입할 수 있다. 그러나 const가 아닌 데이터의 주소는 const가 아닌 포인터에만 대입할 수 있다.

 

함수와 구조체
// travel.cpp -- 함수와 구조체
#include <iostream>
struct travel_time
{
    int hours;
    int mins;
};
const int Mins_per_hr = 60;

travel_time sum(travel_time t1, travel_time t2);
void show_time(travel_time t);

int main()
{
    using namespace std;
    travel_time day1 = {5, 45};    // 5 시간, 45 분
    travel_time day2 = {4, 55};    // 4 시간, 55 분

    travel_time trip = sum(day1, day2);
    cout << "이틀간 소요시간: ";
    show_time(trip);

    travel_time day3= {4, 32};
    cout << "사흘간 소요시간: ";
    show_time(sum(trip, day3));
    // cin.get();

    return 0;
}

travel_time sum(travel_time t1, travel_time t2)
{
    travel_time total;

    total.mins = (t1.mins + t2.mins) % Mins_per_hr;
    total.hours = t1.hours + t2.hours +
                 (t1.mins + t2.mins) / Mins_per_hr;
    return total;
}

void show_time(travel_time t)
{
    using namespace std;
    cout << t.hours << " 시간, "
         << t.mins << " 분\n";
}

 

함수를 지시하는 포인터

 

** 함수를 지시하는 포인터를 선언할 때에도, 그 포인터가 지시하는 함수의 데이터형을 지정해야 한다.

즉, 함수 원형이 그 함수에 대해 제공하는 것과 동일한 정보를 선언이 제공해야 한다.

ex)

double pam(int);     // 함수원형

double (*pf)(int);     // 함수를 지시하는 포인터 선언 

=> pf는 하나의 int를 매개변수로 취하고 double형을 리턴하는 함수를 지시한다

일반적으로, 어떤 함수를 지시하는 포인터를 선언하는 절차는 다음과 같다. 함수의 원형을 먼저 작성한 다음, 함수 이름을
(*pf)형태의 표현식으로 대체한다. 이 경우에, pf는 그 데이터형의 함수를 지시하는 포인터이다.


*pf(int)는 pf( )가 포인터를 리턴하는 함수라는 것을 의미하지만, (*pf)(int)는 pf가 함수를 지시하는 포인터라는 것을 의미한다. 

 

[함수 주소 얻기]

함수 think( )가 있다면 뒤에 붙는 괄호를 빼고 함수 이름만 사용하면 think가 think( )의 주소가 된다.

process(think);   // process( )에 think( )의 주소를 전달한다.
=> process( )를 호출하면 process( )함수가 process( ) 내부에서 think( )함수를 불러낸다.
thougnt(think ( )); // thought( )에 think( )의 리턴값을 전달한다.
=> thought( )를 호출하면 think( )함수가 먼저 실행되고, think( )함수의 리턴값이 though( )함수에 전달된다.