#include <iostream>
// 인라인 함수 정의 // 앞에 inline이 추가됨
inline int add(int a, int b) {
return a + b;
}
int main() {
int result = add(5, 3); // 인라인 함수 호출
return 0;
}
2. 인라인 함수를 사용하지 않은 경우
#include <iostream>
// 일반적인 함수 정의
int add(int a, int b) {
return a + b;
}
int main() {
int result = add(5, 3); // 함수 호출
return 0;
}
두 예시의 가장 중요한 차이점은: 함수 호출 방식 (inline 키워드)
- inline O : 함수 호출 대신 함수 내용이 호출 지점에 직접 삽입됨
→ 함수 호출 오버헤드가 사라지고 코드 실행 속도가 향상됨
→ 따라서, 인라인 함수는 그 함수 호출이 아주 많은 경우 성능 향상에 유용함!
→단코드삽입이기때문에재귀가불가능!
↔️ inline X : 함수 호출이 발생하며, 함수 호출 및 복귀 주소 저장을 위한 스택 프레임이 생성됨
→작은 함수에서도 오버헤드 발생 (여러 번 호출될 시 성능 저하 가능)
**
어떤 함수가 특정 구조체에 의존적일 때, 해당 함수를 구조체 내부에서 상속하여 정의할 수 있음.
단, 구조체 코드에 대한 가독성을 높이기 위해서, 상속을 유지하되 구조체 정의 코드 밖으로 빼낼 수 있음.
이때 inline과 :: 코드를 활용함.
struct Car
{ int curSpeed;
void Accel(); //함수 원형을 구조체 내부에 상속시켜 정의하되
}
inline void Car::Accel() //구조체 코드 바깥으로 함수 정의를 빼냄 //이때 inline & :: 를 사용
{
//~~~~ }
"Call by value"와 "call by reference"는 함수 호출 방식을 나타내는 용어
이 두 가지 방식의 주요 차이점: 함수에 인수를 전달하는 방법 & 함수가 인수를 처리하는 방법
1. Call by Value (값에 의한 호출):
함수에 매개 변수로 ‘값’을 전달합니다.
함수 내에서 매개 변수는 별도의 변수로 취급되며, 인수의 값이 복사되어 매개 변수에 저장됨
따라서 함수 내에서 매개 변수의 값을 변경하더라도 호출자(caller/원본)의 변수는 영향을 받지 않음
C++에서 함수의 기본 호출 방식은 "call by value"
void modifyValue(int x) {
x = x + 10;
}
int main() {
int num = 5;
modifyValue(num);
std::cout << "num: " << num << std::endl; // 출력 결과: num: 5
return 0;
}
2. Call by Reference (참조에 의한 호출):
"주소/참조 값을 전달받아서, 함수 외부에 선언된 변수에 직접 접근하는 형태의 함수 호출"
- 함수 입력으로 주소/참조 값이 전달되었다는 사실이 중요한 게 아니라
- 입력된 주소/참조 값을 통해 직접 외부 변수를 참조하여 모종의 행위(읽기/조작 등)을 했을 때 call-by-reference라 칭할 수 있음
함수에 매개 변수로 ‘변수의 참조/주소’를 전달
함수 내에서 매개 변수는 원래 변수와 동일한 메모리 위치를 참조하므로, 함수에서 매개 변수를 수정하면 호출자(원본)의 변수가 직접 영향을 받음
C++에서는 매개 변수를 참조로 전달하려면 참조자(reference) 또는 포인터(pointer)를 사용해야 함
(1) 포인터 *를 활용 (주소)
void modifyValueByPointer(int* x) {
*x = *x + 10;
}
int main() {
int num = 5;
modifyValueByPointer(&num);
std::cout << "num: " << num << std::endl; // 출력 결과: num: 15
return 0;
}
- 늘 느끼는 건데.. (?)주소값은 메모리가 할당된 위치이고, 이주소값 자체는 변하지 않고 고정됨.
반면, 해당 주소에 저장된value 자체는 변형&조작의 대상이 됨.
- 단, 이하와 같은 방식으로, 포인터를 통해 value를 직접 조작하는 것을 막을 수 있다.
const int* ptr1 = # //ptr1를 이용해서 num의 value를 바꿀 수 없음 //*ptr1를 상수화시켰다는 의미
*주의할 점은, ptr = 2;는ptr이 const 변수이기 때문에 컴파일 에러가 발생 하지만
num = 2;는num이 non-const 변수이기 때문에 정상이다.즉, 포인터 변수가 가리키는 num 자체가 상수화가 되는 것이 아니다.
void modifyValueByReference(int& x) {
x = x + 10;
}
int main() {
int num = 5;
modifyValueByReference(num);
std::cout << "num: " << num << std::endl; // 출력 결과: num: 15
return 0;
}
- int& x에서 &는 C++에서 "참조자"를 나타내는 연산자
- 자료형 바로 뒤에 &가 붙음!!! (변수명 앞에 &가 붙는 게 아니라 ~~~.)
- 참조자는 변수에 대한 별명(alias)이며, 원본 변수를 직접 참조하게 함.
이것은 값(value) 자체가 아니라 변수에 대한 참조(reference)를 함수에 전달함을 의미함
- int& x에서 x는 참조자의 이름이고, int는 참조할 변수의 데이터 타입을 나타냄 (int& x: "정수형 변수에 대한 참조를 받는다")
참조자를 사용할 때의 특징
원본 변수의 별명: 참조자는 원본 변수의 별명으로 동작함, 따라서 참조자를 통해 값을 읽거나 수정하면 원본 변수에도 동일한 작업이 반영됨.
포인터와 다른 점: 참조자 != 포인터. 포인터는 메모리 주소를 저장하고 간접 참조를 통해 값을 읽고 쓰고 조작할 수 있지만, 참조자는 변수에 직접적으로 연결되어 있으므로 포인터보다 사용하기 간편함
#include <iostream>
// 전역 변수 (global variable)
// globalVar는 프로그램 전체에서 접근 가능하며 초기화된 데이터를 저장
int globalVar = 10;
int main() {
// 정적 변수 (static variable)
// staticVar는 main 함수 내에서 선언되었지만 프로그램이 시작될 때 메모리에 할당되며, 프로그램이 종료될 때까지 유지됨
static int staticVar = 5;
std::cout << "전역 변수: " << globalVar << std::endl;
std::cout << "정적 변수: " << staticVar << std::endl;
return 0;
}
#include <iostream>
int add(int a, int b) {
int result = a + b; // 지역 변수 (local variable)
return result;
// add의 지역변수 ; 함수가 호출될 때 메모리에 할당되고 함수가 종료될 때 소멸
}
int main() {
int x = 5; // 지역 변수 (local variable)
int y = 3; // 지역 변수 (local variable)
// main의 지역변수 ; 함수가 호출될 때 메모리에 할당되고 함수가 종료될 때 소멸
int sum = add(x, y); // add 함수 호출
std::cout << "합계: " << sum << std::endl;
return 0;
}
3. 힙(Heap) 영역:
- 동적할당된변수(Dynamically Allocated Variables): 프로그램실행중에필요한메모리를동적으로할당하고해제하는데사용됨. 이러한변수는개발자가메모리할당및해제를관리해야함. 예를들어, new (C++) 또는 malloc (C) 함수로메모리를할당하고 delete (C++) 또는 free (C) 함수로메모리를해제함.
#include <iostream>
int main() {
int* dynamicVar = new int; // 동적 할당된 변수 (dynamically allocated variable)
*dynamicVar = 7;
std::cout << "동적 할당된 변수: " << *dynamicVar << std::endl;
delete dynamicVar; // 메모리 해제
return 0;
// 프로그램 실행 중에 메모리를 할당하고 new 연산자를 사용하여 초기화함
// 이 변수는 개발자가 명시적으로 메모리를 해제해야 하며, delete 연산자를 사용하여 메모리를 해제함
}
c++에서, 배열 이름을 그 배열의 첫번째 원소의 주소로 인식함 // arr == &arr[0]
#include <iostream>
using namespace std;
const int SIZE = 8;
int sumArr(int arr[], int n);
int sumArr2(int*, int*);
void sizeArr(int* arr);
/*c++에서, 배열 이름을 그 배열의 첫번째 원소의 주소로 인식함*/
// arr == &arr[0]
int main()
{
int arr[SIZE] = {1,2,3,4,5,6,7,8};
int total = sumArr(arr, SIZE);
cout << "This is total1: " << total << endl;
// This is total1: 36
int total2 = sumArr2(arr, arr+SIZE);
cout << "This is total2: " << total2 << endl;
// This is total2: 36
cout << "size of arr[SIZE]: " << sizeof arr << endl;
// size of 'arr[SIZE]': 32 //argument
sizeArr(arr);
// size of 'arr': 8 //parameter
}
/*배열을 전달받을 때에는 배열 전체가 들어오는 것이 아닌, 첫번째 원소의 주소값이 들어옴*/
int sumArr(int arr[], int n) { //int* arr라고 써도 무방
int total = 0;
for (int i=0; i<n; i++)
total += arr[i]; //배열의 원소에 접근하는 대괄호는 배열이든 포인터든 사용가능
return total;
}
int sumArr2(int* begin, int* end) {
int total = 0;
for (int* pt = begin; pt != end; pt++)
total += *pt;
return total;
}
void sizeArr(int* arr) {
//int arr[] 일 때와 int* arr 일 때 결과가 동일함
cout << "size of array as parameter: " << sizeof arr << endl;
// size of array in sumArr(): 8
// 32가 아닌 8이 나옴
}
2. 함수와 구조체
구조체와 함수 - 함수 통해 구조체 자체를 직접 리턴할 수 있고 - 함수의 인풋으로 구조체 형식을 사용할 수도 있음
구조체 규모가 너무 커질 때, 함수는 구조체의 원본이 아닌 복사본으로 작업하기에 - 이때 구조체를 포인터로 사용할 시 훨씬 효율적이게 됨 - 이 경우 원소에 접근하려면 화살표를 사용함
#include <iostream>
using namespace std;
const int MINUTES = 60;
int totalmin(int hour, int minute);
struct Time{
int hour;
int minute;
};
Time sum(Time*, Time*); //구조체와 구조체를 더하여서 구조체를 Return하는 함수
void showTime(Time);
int main()
{
Time market;
market.hour = 2;
market.minute = 20;
Time library = {1, 11};
cout << "Total minutes to market: " << totalmin(market.hour, market.minute) << endl;
cout << "Total minutes to library: " << totalmin(library.hour, library.minute) << endl;
Time total = sum(&market, &library);
showTime(total);
}
/*
Total minutes to market: 140
Total minutes to library: 71
총 3시간 31분 걸렸습니다.
*/
함수 쪼개서 보기 - totalmin
int totalmin(int hour, int minute)
{
return hour * MINUTES + minute;
};
함수 쪼개서 보기 - sum
- 구조체를 return하는 함수
- 입력으로 구조체를 받음
- 이때 구조체의 주소값을 입력으로 받음
**당연하지만, 입력으로 Pointer를 지정해놓으면, 실제 함수 입력으로도 주소값을 넣어야 함(앞에 & 붙여서~)
/*
* 1. 함수 정의 제공
* 2. main 앞에, 함수 원형 명시 ; (1)함수 데이터형 (2)함수 이름 (3)(매개변수);
* 3. 함수 호출
* */
/* 함수의 두 타입
* 1. 리턴값이 있는 타입: 함수가 리턴하는 값의 데이터형으로 선언 & return 뒤 변수 있음
* 2. 리턴값이 없는 타입: void로 선언 & return 뒤 변수 없음 */
#include <iostream>
using namespace std;
const float PIE = 3.14;
/* main 함수 이전에 함수 원형 명시 */
//물론 원형 명시와 함께 함수를 정의하는 것도 가능
void cheers(int n);
float circle(int x);
int main(){
int a;
cout << "하나의 수를 입력하십시오." << endl;
cin >> a;
cheers(a);
int b;
cout << "원의 반지름을 입력하십시오." << endl;
cin >> b;
float c = circle(b); //입력과 리턴 모두 자료형과 함께 미리 선언해주어야 함
cout << "원의 넓이는 " << c << "입니다." << endl;
}
/* main 함수가 끝날 때 함수 정의 */
void cheers(int n) {
for (int i = 0; i < n; i++) {
cout << "cheers!" << endl;
}
}
float circle(int x) {
return x * PIE;
}
→ 당연히, main 함수 안에, 내가 따로 선언해준 함수를 명시해서 사용해주어야 한다!
2. 매개변수와 전달인자
#include <iostream>
using namespace std;
void helloCPP(int, int);
int main() {
int n; //파라미터(매개변수)
int m; //파라미터(매개변수)
cout << "n: " << endl;
cin >> n; //파라미터(매개변수)
cout << "m: " << endl;
cin >> m; //파라미터(매개변수)
helloCPP(n, m); //전달인자(argument)
}
void helloCPP(int n, int m) {
for (int i=0; i < n; i++)
cout << "Hello n" << endl;
for (int i=0; i < m; i++)
cout << "Hello m" << endl;
}
#include <iostream>
using namespace std;
int main(){
//분기 구문: if & switch
/*if(조건)
* 코드*/
if (true) {
cout << "참";
cout << "입니다." << endl; }
if (false)
cout << "거짓" << endl;
// if - else if - else 구문은 하나의 덩어리로 인식함
if (false)
cout << "거짓" << endl;
else {
cout << "참이라구요~!" << endl;}
// if 하나에 else 여러 개 가능
// 이때 else: 앞단 것의 차집합이 연쇄로 계산됨
// 양끝 말고는 else if를 사용하는 게 더 나을 듯.
cout << "종료됨" << endl;
/*
참입니다.
참이라구요~!종료됨*/
}
#include <iostream>
using namespace std;
int main(){
/*switch 구문*/
//선택할 수 있는 경우의 수가 3개 이상으로 넘어가면, if-else보다 switch 구문이 훨씬 효율적.!
// integer-expression에 해당하는 값의 case로 이동 -> 해당 값에 있는 코드가 실행됨
// e.g.
// switch (integer-expression) {
// case label1:
// code1 / break;
// case label2:
// code2 / break;
// ... }
int a;
cin >> a;
switch(a){
case 1:
cout << "1\n";
break; //switch 구문을 바로 종료시킴 //*break* 는 모든 반복문에서 사용 가능
case 2:
cout << "2\n";
continue; //이하 코드를 무시하고, 다음 loop를 돈다 //*continue* 는 반복문에서만 사용가능하다
case 3:
cout << "3\n";
break;
default:
cout << "case 이외의 수를 입력하셨습니다.\n";
}
cout << "switch 구문이 끝났습니다.";
//지정한 case 이외의 경우는,
//default 지정 시 해당 구문이 실행되거나, 그렇지 않을 경우 별다른 실행 없이 종료됨.
}
4. 예제
#공백 제외한 글자 수 세기
#include <iostream>
using namespace std;
const int SIZE = 30;
int main(){
char line[SIZE]; //문자 배열, string의 끝에는 개행문자 \0가 포함돼있음
cin.get(line, SIZE); //get을 통해 입력 받기
int word_cnt = 0;
for(int i = 0; line[i]!='\0'; i++){
if(line[i]==' ')
continue;
word_cnt++;
};
cout << "공백 제외 총 글자 수는 " << word_cnt <<"입니다~";
//영어로 하니까 괜찮은데 한글은 개수 처리가 애매하다, 다른 설정이 필요한 듯.
}
/*잠깐
* - 증가연산자
* - 감소연산자
* => 반복문의 카운터에 구현하면 유리함*/
int a = 10;
int b = 10;
cout << "a는 " << a << " b는 " << b << endl;
cout << "a++는 " << a++ << endl; //변수의 값을 판단한 후 증가시킴 -> 10 출력
cout << "++b는 " << ++b << endl; //증가시킨 후 변수의 값 판단 -> 11 출력
cout << "다시 a는 " << a << endl; //11
for(int j = 5; j>0; j--){ //부등식은 관계표현식의 일종
cout << j << endl;
}
1. 반복문 for
#include <iostream>
using namespace std;
int main(){
/*반복문 for*/
for(int i = 0; i < 5; i++) {
cout << i << "번째" << endl;
}
/*반복문의 네 가지 규칙
* 1. 반복문에 사용할 카운터(반복 횟수에 대한 변수)의 값을 초기화 (여기서 i=0)
* 2. 반복문을 진행할 것인지 '조건 검사' (여기서 i < 5)
* 3. 반복문 몸체를 수행
* 4. 카운터의 값을 변화시킴(여기서 i++) */}
2. 반복문 while, do while
#include <iostream>
using namespace std;
int main(){
/*반복문 while
* while(조건) {카운터변화}
* 조건에 true나 false와 같은 bool형도 들어갈 수 있음
* while로 짠 것을 for문으로도 짤 수 있고, vice versa, 둘 사이 복잡도 차이도 없음
* 단, while문은 bool데이터만으로, 관계표현식 없이도, 실행할 수 있음
* 또한, for문은 카운터 초기화, 카운터 변화 모두 for문 행에서 한꺼번에 정의할 수 있는데
* while문은 카운터 초기화를 while문 바깥에서 미리 선언해야 하며, 이는 while문을 벗어나서도 해당 변수를 사용할 수 있음을 의미.
*
*/
int k = 0;
while (k<3) {
cout<<k<<"번째"<<endl;
k++; //while문 내부에 반드시 '카운터변화' 행을 포함해야 함
}
string str = "panda"; //string은 명시적으로 개행문자'\0'를 포함
int l = 0;
while (str[l] != '\0'){
cout << str[l] << endl;
l++;
}
//일반 while문: 조건 검사 후 -> 반복 실행
//do while문: 반복을 실행 후 -> 조건 검사 //따라서 어떤 조건이든 최소한 1회는 실행 됨
int m = 0;
do{
cout << "while문 입니다.\n";
m++;
} while (m < 3);
}
3. 예제
3-1. 배열과 반복문
#include <iostream>
using namespace std;
int main(){
/*배열 기반 반복문*/
int a[10] = {1, 3, 5, 7, 9};
//1: index로 배열 원소 접근
for (int i = 0; i<5; i++){
cout << a[i];
} //13579
cout << "\n";
//2: 배열 자체로 원소 접근 //for (int 원소: 배열 이름) {}
//간결하지만, 실제 원소 개수보다 선언한 원소 개수가 많을 경우, 0으로 채워져 선언한 만큼 출력됨
for (int k : a) {
cout << k;
} //1357900000
}
3-2. 중첩 Loop
#include <iostream>
using namespace std;
int main(){
/*중첩루프
* 2차원 배열에서 많이 활용됨*/
int temp[4][5] = {
{1,2,3,4,5},
{11,22,33,44,55}
}; // 배열[행개수][열개수]
// 2차원 배열 생성 시 {{}}의 이중 중첩 활용 & 행 간 콤마로 연결
for (int row = 0; row < 4; row++) {
for (int col = 0; col < 5; col++) {
cout << temp[row][col] << " ";
} cout << endl;
}
/*
1 2 3 4 5
11 22 33 44 55
0 0 0 0 0
0 0 0 0 0 */