728x90
728x90
배열이름은 첫 번째 요소의 주소값을 나타낸다
#include <stdio.h>

int main()
{
    int a[5] = {0,1,2,3,4};
    
    printf("%d, %d \n", a[0], a[1]);
    printf("%d 번지, %d 번지 \n", &a[0], &a[1]);
    printf("배열 이름 : %d \n", a);
    
    return 0;
}

 

배열 이름 vs 포인터

포인터 = 주소값 + 자료형
배열 이름도 포인터다. 상수 포인터라고 한다.

공통점은 이름이 있는 것, 메모리의 주소를 나타내는 것
차이점은 배열이름은 상수, 포인터는 변수라는 것

 

배열 이름은 상수다
#include <stdio.h>

int main()
{
    int a[5] = {0,1,2,3,4};
	int b = 10;
	a = &b; //a는 상수이므로 오류. 변수였다면 가능
    
    return 0;
}

 

a가 상수인 이유?

a 배열 생성과 동시에 첫번째 요소(0x10등의 메모리 주소)를 가리키고,
그것을 a 배열이름으로 하고 소멸 전까지 가리킨다. 즉 상수다.
그래서 상수적 특성을 지닌 포인터다.

 

배열 이름의 타입 결정짓는 요령?

배열 이름도 포인터라 타입이 있다.
배열 이름이 가리키는 배열 요소에 의해 결정된다.

 

배열 이름을 포인터처럼, 포인터를 배열이름처럼 활용?
#include <stdio.h>

int main()
{
    
    int arr[3] = {0, 1, 2};
    
    // arr 배열이름은 포인터다
    //배열이름의 자료형정보는 배열이름이 가리키는 요소형 자료형과 같다
    
    int *ptr;
    int val = 10;
    ptr = arr; 
    // 양측 타입이 같다
    // ptr은 변수라 대입 가능
    // 배열이름과 포인터는 변수,상수 여부를 빼면 큰 차이가 없다
    // 그러나 ptr은 변수, arr은 상수
    
    printf("%d, %d, %d \n", ptr[0], ptr[1], ptr[2]);
    // arr[0]과 ptr[0]의 결과는 같다
    printf("%d, %d, %d \n", arr[0], arr[1], arr[2]);
    
    ptr = &val;
    printf("%d \n", *ptr); //ptr이 가리키는 변수 출력
    
    return 0;
}

 

 

포인터 연산 : 포인터가 지닌 값을 증가/감소시키는 연산

포인터가 가리키는 대상의 자료형에 따라서 증가, 감소되는 값이 차이를 보인다.

int* arr1; //arr1을 참조하니 4바이트 메모리공간을 참조한다.

 

주소값이 1만 증가하지 않고, 포인터의 타입(자료형)에 따라 차이가 있다.
포인터 연산 설명을 위한 예로 실제로 이렇게 사용 ㄴㄴ

#include <stdio.h>

int main()
{
    
    int* ptr1 = 0; //null포인터 : 아무것도 가리키지 않
    char* ptr2 = 0;
    double* ptr3 = 0;
    
    printf("%d, %d, %d \n", ptr1++, ptr2++, ptr3++); //선연산 후증가
    printf("%d, %d, %d \n", ptr1, ptr2, ptr3); //자료형의 값만큼 증가된다
    printf("%d, %d, %d \n", ptr1--, ptr2--, ptr3--); //감소
    printf("%d, %d, %d \n", ptr1, ptr2, ptr3); 
    
    return 0;
}

 

포인터 연산을 통한 배열 요소의 접근
#include <stdio.h>

int main()
{
    int arr[5] = {1,2,3,4,5};
    // 배열이름은 첫번째 요소를 가리킨다
    
    int* pArr = arr; // int 형으로 양측이 같다..arr첫 요소를 가리킨다
    
    printf("%d \n", *pArr); 
    
    printf("%d \n", *(++pArr)); // 타입에 따라 int 형 값만큼 증가 (4바이트를 건너뛴다)
    printf("%d \n", *(++pArr)); 
    
    printf("%d \n", *(pArr+1)); // pArr의 값 자체는 그대로
    printf("%d \n", *(pArr+2)); 
    
    printf("%d \n", *(++pArr)); 
    
    return 0;
}

 

중대한 결론
#include <stdio.h>

int main()
{
    int arr[2] = {1,2};
    int* pArr=arr; //가리키는 요소가 int라 자료형도 int
    
    // 배열이름을 통한 출력
    printf("%d, %d\n", arr[0], *(arr+1)); //int의 포인터형이라는 정보로 4바이트를 건너뜀
    // printf("%d, %d\n", *(arr+0), arr[1]); //같은 결과
    
    // 포인터 변수를 통한 출력
    printf("%d, %d\n", pArr[0], *(pArr+1)); // 포인터로도 배열의 인덱스연산이 가능
    // *(pArr + 0) == *pArr == pArr[0];
    // arr[1] == *(arr+1); 
    // 즉 arr[i] == *(arr+i); 
    
    return 0;
}
arr[i] == *(arr+i);
//arr이 포인터거나 배열이름인 경우

 

 

문자열 표현 방식의 종류

- 배열 기반의 문자열변수
- 포인터 기반의 문자열 상수

char str1[5] = "abcd";
char *str2 = "ABCD";

 

큰따옴표로 표현되는 문자열은 상수다..
printf ("abcd");의 경우 문자열이 아니라 올라간 메모리공간의 주소값이 인자로 전달되는 것

#include <stdio.h>

int main()
{
    char str1[5] = "abcd";
    char *str2 = "ABCD";
    
    printf("%s\n", str1);
    printf("%s\n", str2);
    
    str1[0] = 'x'; //가능
    str2[0] = 'x'; //상수라서 변경불가능, 에러
    // 에러 나지는 않는데 무시되고 정지..
    //*(str2+0) = 'x'; //역시 안됨
    
    printf("%s\n", str1);
    printf("%s\n", str2);
   
    printf("%d\n", *(str2+0)); //int값 반환
    printf("%d\n", str2[0]); //int값 반환
    
    return 0;
}

 

포인터 배열 : 배열의 요소로 포인터를 지니는 배열
int* arr1[10];
#include <stdio.h>

int main()
{ 
    int a= 10, b=20, c=30;
    
    int* arr[3] = {&a,  &b, &c}; 
    
    printf("%d\n", *arr[0]); // arr 0번째 공간이 가리키는 메모리공간을 참조
    
    return 0;
}

int형 포인터를 저장할 수 있는 배열..배열의 요소는 포인터이다...int형 포인터배열

 

모든 문자열은 선언과 동시에 메모리공간에 올라가고 그 자리에 문자열의 주소값이 리턴
#include <stdio.h>

int main()
{ 
    
    // 캐릭터형 포인터 3개가 묶여서 형성된 배열
    // 캐릭터형 변수에 주소값 3개가 저장
    // ** 모든 문자열은 선언과 동시에 메모리공간에 올라가고 그 자리에 문자열의 주소값이 리턴된다 **
    char* arr[3] = {"Fervent-lecture", "TCP/IP", "Socket Programming"};
    //char* arr[3] = {0x1000, 0x2000, 0x3000}; 
    
    printf("%s\n", arr[0]); // F부터 저장
    printf("%s\n", arr[1]); // T~
    printf("%s\n", arr[2]); // S~
    
    return 0;
}
728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
포인터 (변수) : 메모리의 주소값을 저장하기 위한 변수

(주소값과 포인터는 다른 것)

앞으로 .. 그림으로도 그려가보면서 이해하는게 도움이 많이 될 것이다.
또한 말로 풀어서 설명해볼 수 있으면 더 좋음

 

int n = 7;
// 0x001~0x114
// 0x001부터 시작하는 메모리다..
// 주소값은 시작점만 말하면 된다

 

컴퓨터의 주소 체계에 따라 크기가 결정된다.

시장의 요구에 맞춰, 역사적으로 점진적으로 커져 왔다. 
Q. 8/16/32/64비트를 결정짓는 요소가 뭘까?
- CPU가 한 클럭 당 처리할 수 있는 사이즈

 

8비트 시스템에서는 주소값을 나타내는데 8비트를 사용한다. 메모리의 주소번지를 2의 8승만큼 만들 수 있다.
포인터는 1바이트가 되어야 한다.

64비트 시스템도 주소값을 나타내는데 32비트를 사용한다.

32/64비트 시스템은 32비트로 주소를 표현하고 (2의 32승, 4바이트)
포인트의 크기는 최소 4바이트가 되어야 한다.

32비트 시스템 기반 4바이트

32/64비트 시스템은 32비트로 주소를 표현하기 때문이다.

pN이라는 포인트는 n을 가리킨다

 

int *a;
// int 형 변수/상수를 가르킬 수 있는 포인터

char *b;
// char형 변수/상수를 가르킬 수 있는 포인터

double * c;
// double형 변수/상수를 가르킬 수 있는 포인터


// 아래는 다 같은 표현이다
int* a;
int * a; //지양
int *a;

 

A형 포인터 (A*): A형 변수의 주소값을 저장

 

주소 관련 연산자

& 연산자 : 변수의 주소 값 반환
* 연산자 : 포인터가 가리키는 메모리 참조
-- * 연산자는 포인터선언, 포인터가 가리키는 메모리에 접근, 곱셈 등 다양한데 사용

어떠한 용도로 사용됐는지는 피연산자를 보고 알 수 있다.

a*b   // 곱셈
int * p;   // 포인터선언
*p; //포인터가 가리키는 메모리에 접근

 

예시

int a = 2005;
int *pA = &a; // Ox30 등이 들어간다
// pA가 a를 가르키게 된다.

print("%d", a); // 직접접근
print("%d", *pA); // 간접접근
// pA라는 포인터가 가리키는 변수 a를 참조, 2005가 출력
// 포인터가 가르키는 메모리공간을 참조

print("%d", pA); // a의 주소, Ox30
#include <stdio.h>

int main()
{
    int a = 2005;
    int* pA = &a;  //pA 변수가 a변수를 가리킨다.
    
    printf("pA: %d \n", pA); //주소값이 10진수 정수형으로 출력
    printf("&a: %d \n", &a); 
    
    (*pA)++;
    
    printf("a: %d \n", a); 
    printf("*pA: %d \n", *pA); 

    return 0;
}

 

 

 포인터에 다양한 타입이 존재하는 이유?
- 포인터타입은 참조할 메모리의 크기 정보를 제공

int* a;
char* c;
// 포인터는 둘 다 32비트..4바이트로 메모리의 주소값 저장

 

왜 포인터에 타입을 두는 걸까?
- 포인터 타입에 따라 몇 바이트를 읽어들일지 차이가 난다.

#include <stdio.h>

int main()
{
    int a = 10;
    int* pA = &a; 
    double e= 3.14;
    double* pE= &e;
    
    printf("%d %f", *pA, *pE); 

    return 0;
}

 

주소값은 포인터다? x
- 포인터는 주소값 외에도... 타입(자료형정보), 가리키는 대상, 등을 갖고 있다

포인터 = 주소값 + 자료형.

 

잘못된 포인터 사용 사례

#include <stdio.h>

// 초기화 안한 사례
int main()
{     
    int* pA; // 어딘가를 가르키는 중 (쓰레기값 들어있)
	
    *pA = 10; // 운영체제에 의해 문장이 중지되고 경고메세지 나옴
	// 컴파일은 됨

    return 0;
}
#include <stdio.h>

int main()
{
     
    int* pA = 100; //100이 어딘 줄 알고
    *pA = 10; // 운영체제가 허용하지 않는다

    return 0;
}

 

연습문제

int형 변수 num1과 num2를 선언과 동시에 각각 10과 20으로 초기화하고,
int형 포인터 변수 ptr1과 ptr2를 선언하여 각각 num1과 num2를 가리키게하자.

그리고 이상태에서 포인터 변수ptr1과 ptr2를 이용해서 num1의 값을 10 증가시키고 num2의 값을 10 감소시키고
이제 두포인터 변수 ptr1과ptr2가 가리키는 대상을 서로 바꾸자. 즉, 포인터 변수 ptr1이 num2를 가리키게하고, 포인터 변수 ptr2가 num1을 가리키게 하자.

그리고 마지막으로 ptr1과 ptr2가 가리키는 변수에 저장된 값을 출력하자.

 

#include <stdio.h>

int main()
{
    int num1 = 10;
    int num2 = 20;
    int *ptr1 = &num1;
    int *ptr2 = &num2;
    int *temp;
    
    // 값 변화
    *ptr1 += 10;
    *ptr2 -= 10;
    
    // ptr1, ptr2가 가리키는 변수에 저장된 값 출력
    printf("1- *ptr1 : %d\n", *ptr1);
    printf("1- *ptr2 : %d\n", *ptr2);
    
    // 가리키는 대상 변화
    temp = ptr1;
    ptr1 = ptr2;
    ptr2 = temp;

    
    // ptr1, ptr2가 가리키는 변수에 저장된 값 출력
    printf("*ptr1 : %d\n", *ptr1);
    printf("*ptr2 : %d\n", *ptr2);
   
    return 0;
}

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
11.1.2.3.4

 

11.1 배열

둘 이상의 변수를 동시에 선언하는 효과
많은 양의 데이터를 일괄 처리할 때 유용 (for 등)
지역/전역적 특성 - 함수 호출 vs 프로그램 종료 전까지 메모리공간에..

선언에 필요한 것 : 길이/요소자료형/이름
길이 : 변수의개수 (상수)
요소자료형 : 배열을 구성하는 변수의 자료형
이름 : 접근할 때 사용되는 이름

배열요소의 위치 : 인덱스 연산자 [ ] (정수)로 입력
인덱스는 0부터 시작하며 거리를 나타냄

int arr3[5] = {1,2}; //나머지는 0으로 채워진다.



문자열 상수 : 문자열이면서 상수의 특징.

-문자열선언(메모리, 함수호출 순서로 진행

printf("hello \n");



문자열 변수 : 문자열이면서 변수의 특징.

char str1[5] = "Good";
char str2[] = "morning"; // 지정되어 있지않으면 문자열의 길이 +1만큼 컴파일러에의해 채워진다.
// 문자열배열

 

문자열의 특징 

- 어떤 문자열이든 무조건 끝에 null문자를 지닌다.
-- 문자열의 실제길이 + 1 로 메모리공간을 할당하고 큰 길이가 저장되는 이유

- null 문자 : '\0'
-- 아스키코드값으로 0
--- 문자열의 끝을 나타내는 특수문자, 다른용도로도 쓰임
-- 0의 아스키코드값은 49, 즉 0이 null이 아니다

char str[6] = "Hello";

 

- null 문자를 지녀야 하는 이유

-- 문자열의 끝을 표현하기 위해서 
-- 표현해야 하는 이유 : 쓰레기 값과 실제 문자열의 경계를 나타내기 위해
-- printf 함수는 null 문자를 통해 출력범위를 결정

char str[100] = "Hello World";
printf("%s \n", str);

// 아래 경우도 null문자를 만났기 때문에 AB만 출력
char str[5] = "AB";
// AB\0??...
printf("hello \n");
// hello\n\0

 

문자열과 char 배열의 차이

char arr1[] =  "abc"; 
// 메모리공간 3+1 할당
// char 배열이자 문자열

char arr2[] =  {'a', 'b', 'c'};
// 길이가 3인 배열
// 문자열이 아니다. 끝에 null문자가 없어서

char arr3[] =  {'a', 'b', 'c', '\0'};
// 길이가 4인 배열
// 문자열이다. 끝에 null문자가 있어서
// char 배열이자 문자열

 

null문자는 반드시 끝이지 앞에 있으면 안된다.

 

문자열 변수임을 입증하는 예제

#include <stdio.h>
 
int main(void) 
{
	int i;
	char ch;
	char str[6] = "Hello";

	printf("--변경 전 문자열--\n");
	printf("%s \n", str);

	for(i=0; i<6; i++)
		printf("%c | ", str[i]);

	//문자열 변경
	for(i=0; i<3; i++)
	{
		ch = str[4-i]; //null문자는 건들지 않음
		str[4-i] = str[i];
		str[i] = ch;
	}

	printf("\n\n--변경 후 문자열--\n");
	printf("%s\n", str);
	
	return 0;
}

#include <stdio.h>

// abcd 입력
int main(void) 
{
	char str[30];
	printf("문자열 입력 :");
	scanf("%s", str); // &를 안붙이는건 다음 포인터단원에서 나옴

	printf("입력된 문자열 : %s \n", str);
	
	return 0;
}

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
파일 처리의 흐름

파일 포인터를 준비, 파일을 열기, 읽고 쓰기, 파일을 닫기

 

C 파일포인터

파일 스트림을 가리키는 포인터. 파일스트림.

파일포인터 : 하드디스크에서 메모리로 읽어들일 화일의 `위치 주소` 및 `버퍼링`에 대한 추상화정보를 가지고 있는 포인터

 

파일열기 : fopen()

파일을 못열면 NULL 반환

 

파일닫기 : fclose()
FILE* fp;
fp = fopen("abc.txt", "w"); // w / r / a : 쓰기 읽기 추가로 쓰기
if (fp == NULL) return;
fclose(fp);

 

파일쓰기 : fprintf()
fprintf(fp, "%s", a);
// 포인터명, 서식, 문자열배열명

 

파일 읽기 : fgets()

파일에서 문자열을 한 줄씩 읽어들임

fgets(s, 29, fp);
// 읽어온값을 저장할 문자열배열명, 읽어들일 최대 문자수, 파일포인터명

 

파일의 끝 : feof()
#define _CRT_SECURE_NO_WARNINGS

#include <stdio.h>
#include <string.h>

main()
{
    FILE* fp;
    char s[256];
    int i = 1;

    fp = fopen("abc.txt", "r");
    if (fp == NULL) {
        return;
    }

    while (feof(fp) == 0) {
        fgets(s, 255, fp);
        printf("%04d: %s", i, s);
        i++;
    }
    fclose(fp);
}

 

 

 

예시

#define _CRT_SECURE_NO_WARNINGS  // fopen 보안 경고로 인한 컴파일 에러 방지
// include 위에 넣어야 한다.

#include <stdio.h>

main()
{

    FILE* fp;
    char a[] = "Cats and dogs.", s[30];    
    
    // 파일 쓰기 프로그램
    fp = fopen("abc.txt", "w"); // write   
	if (fp == NULL) return;    
    fprintf(fp, "%s", a);
    fclose(fp);

	// 파일 읽기 프로그램
    fp = fopen("abc.txt", "r"); // read    
    if (fp == NULL) return;
    fgets(s, 29, fp);
    printf("%s\n", s);
    fclose(fp);

}

 

 

반응형

 

 

int main 이랑 void main이랑 main이랑..차이점

https://coding-restaurant.tistory.com/432

 

C언어 출력타입 %d, %f, %e, %o, %x, %u, %g ......
  • %d: 10진수(정수형)
  • %f: 실수형
  • %e: 지수형
  • %o: 8진수
  • %x: 16진수
  • %u: 부호없는 10진수
  • %g: 실수형 자동출력
  • %p: 포인터의 주소
  • %c: 하나의 문자로 출력
  • %s: 문자열


출처: https://byunggni.tistory.com/48 [띵돌이의 블로그]

728x90
728x90

'C, C++' 카테고리의 다른 글

메인 함수(엔트리포인트)  (0) 2021.09.08
strcmp, strncmp - 두 문자열 일치 비교, 길이 특정  (0) 2021.09.08
[C] strcpy, strncpy  (0) 2021.08.27
c++ define 함수  (0) 2021.05.26
[공유] C++  (0) 2021.05.12
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

[C] strcpy, strncpy

C, C++ 2021. 8. 27. 17:24
728x90
728x90

strcpy : char*, char[] 타입의 문자열을 복사하는 함수
문자열은 다른 배열이나 포인터(메모리)로 복사할 수 있습니다. strcpy 함수는 문자열을 다른 곳으로 복사하며 함수 이름은 string copy에서 따왔습니다(string.h 헤더 파일에 선언되어 있습니다).

strncpy : 문자열을 일정 길이만큼 복사
null문자를 안붙일 수 있다. 

 

헤더파일

<string.h> // c
<cstring> // c++

 

문법

char *strcpy(char *_Dest, char const *_Source);
// strcpy(대상문자열, 원본문자열);
char *strcpy(char *_Dest, char const *_Source, size_t num);
//  size_t num 복사할 문자열의 길이

 

활용

#define _CRT_SECURE_NO_WARNINGS    // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>    // strcpy 함수가 선언된 헤더 파일

int main()
{
    char s1[10] = "Hello";    // 크기가 10인 char형 배열을 선언하고 문자열 할당
    char s2[10];              // 크기가 10인 char형 배열을 선언

    strcpy(s2, s1);        // s1의 문자열을 s2로 복사
    
    printf("%s\n", s2);    // Hello

    return 0;
}

 

c언어 문자열에서 사용해야 한다. string 클래스 형태의 문자열에서는 사용못함
strcpy는 문자열을 복사할 때 끝에 널문자(\0)도 포함시켜 복사한다.
strncpy는 널문자를 마지막에 포함하는 걸 보장하지 않는다.
strncpy는 문자열을 복사한 뒤에도 문자열 공간에 자리가 남으면 널문자로 나머지 공간을 채운다. 

 

 

 

 

예시

#define _CRT_SECURE_NO_WARNINGS    // strcpy 보안 경고로 인한 컴파일 에러 방지
#include <stdio.h>
#include <string.h>    // strcpy 함수가 선언된 헤더 파일

int main()
{
    char dest1[13] = "";
    char dest2[13] = "";    
    char source[10] = "computer";
    
    printf("복사값 : %s\n", strcpy(dest1, source));
    printf("복사값 : %s\n", strncpy(dest2, source, 3)); 
    
    return 0;
}
#include <stdio.h>
#include <string.h>    // strcpy 함수가 선언된 헤더 파일

int main()
{
    char dest1[13] = "helloworld";
    char source[10] = "hey";
    
    printf("문자열길이 : %d\n", strlen(dest1)); //10진수
    printf("복사 : %s\n", strcpy(dest1, source)); //문자열
    printf("dest1[7] : %c\n", dest1[7]); //문자1개
    
    
    return 0;
}

 

#include <stdio.h>
#include <string.h>    // strcpy 함수가 선언된 헤더 파일

int main()
{
    char dest1[5] = "hey";
    char source[10] = "hello";
    
    printf("문자열길이 : %d\n", strlen(dest1));
    printf("문자열길이 : %d\n", strlen(source));
    
    printf("복사 : %s\n", strcpy(dest1, source));
    
    
    
    return 0;
}

 

 

 

c언어 출력타입

예제 출처

728x90
728x90

'C, C++' 카테고리의 다른 글

메인 함수(엔트리포인트)  (0) 2021.09.08
strcmp, strncmp - 두 문자열 일치 비교, 길이 특정  (0) 2021.09.08
[c] 파일포인터와 fopen, fclose, fprintf, fgets  (0) 2021.08.30
c++ define 함수  (0) 2021.05.26
[공유] C++  (0) 2021.05.12
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

비주얼스튜디오는 워크스페이스와 프로젝트 단위로 프로그램을 관리한다.

 

 

01 비주얼 C++ 6.0 사용방법

 

비주얼스튜디오에서 새 프로젝트, 콘솔앱을 선택해 새 프로젝트를 실행한다.
그러면 도스 프롬프트 기반의 프로젝트를 작성하게 된다.

프로젝트가 생성되면 소스파일 우클릭 > 추가 > 새항목 > C++ 소스파일 메뉴를 선택해 파일을 생성한다.

 

// 사용한 C++ 코드

// testBook.cpp : 이 파일에는 'main' 함수가 포함됩니다. 거기서 프로그램 실행이 시작되고 종료됩니다.
//

#include <iostream>

int main()
{
    std::cout << "Hello World!\n";
    return 0;
}

// 프로그램 실행: <Ctrl+F5> 또는 [디버그] > [디버깅하지 않고 시작] 메뉴
// 프로그램 디버그: <F5> 키 또는 [디버그] > [디버깅 시작] 메뉴

// 시작을 위한 팁: 
//   1. [솔루션 탐색기] 창을 사용하여 파일을 추가/관리합니다.
//   2. [팀 탐색기] 창을 사용하여 소스 제어에 연결합니다.
//   3. [출력] 창을 사용하여 빌드 출력 및 기타 메시지를 확인합니다.
//   4. [오류 목록] 창을 사용하여 오류를 봅니다.
//   5. [프로젝트] > [새 항목 추가]로 이동하여 새 코드 파일을 만들거나, [프로젝트] > [기존 항목 추가]로 이동하여 기존 코드 파일을 프로젝트에 추가합니다.
//   6. 나중에 이 프로젝트를 다시 열려면 [파일] > [열기] > [프로젝트]로 이동하고 .sln 파일을 선택합니다.

 

 

빌드 > 컴파일을 누르거나 Ctrl + F7을 눌러 컴파일한다.

오류가 없으면 자동 생성된 Debug 폴더에 .obj 오브젝트 파일이 생긴다. 
(프로젝트 파일에서는 보이지 않는다)

 

빌드 > 빌드를 눌러 컴파일된 오브젝트 코드를 라이브러리 파일과 링크한다.

오류가 없으면 .exe 실행 파일이 생긴다.
오류가 있으면 Debug 폴더의 exe 파일에 저장 오류를 띄운다.

 

 

빌드 > 실행을 누르거나 Ctrl + F5를 누르면 실행된다.

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
OOP 단계별 프로젝트는 총 7단계로 진행될 예정
1단계에서는 구현에 필요한 틀을 제시. C스타일로 구현할 예정.

 

구현하려는 프로그램 : 은행계좌 관리 프로그램
구현하려는 기능 : 계좌개설, 입금, 출금, 전체고객 잔액조회

통장 계좌번호는 중복되지 않는다.(중복검사를 하지 않음)
입금액, 출금액은 무조건 0보다 크다.(입금, 출금액의 오류검사 하지 않음)
고객 계좌정보는 계좌번호, 고객이름, 고객의 잔액을 저장, 관리한다
둘 이상 고객정보 저장을 위해 배열을 사용한다
계좌번호는 정수이다.

 

계좌 개설과정 실행 예
-----Menu-----
1. 계좌개설
2. 입금
3. 출금
4. 계좌번호 전체 출력
5. 프로그램 종료
선택: 1

[계좌개설]
계좌ID: 115
이름: 이우석
입금액: 15000

 

계좌개설 후에도 계속해서 메뉴가 출력되어 추가 메뉴 선택이 가능해야 한다.
입력한 정보를 대상으로 입금 진행 후 출력될 전체정보 화면이다.

 

입금 및 정보 조회 예
-----Menu-----
1. 계좌개설
2. 입금
3. 출금
4. 계좌번호 전체 출력
5. 프로그램 종료
선택: 2

[입금]
계좌ID: 115
입금액: 70
입금완료

-----Menu-----
1. 계좌개설
2. 입금
3. 출금
4. 계좌번호 전체 출력
5. 프로그램 종료
선택: 4

계좌ID: 115
이름: 이우석
입금액: 15070

 

메뉴에서 계좌정보 전체 출력을 선택하면 모든 계좌의 ID, 이름, 잔액정보가 출력되어야 한다.

 

// 은행 계좌 관리 프로그램
//#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <cstring>

using namespace std;
const int NAME_LEN = 20; //이름의길이

void ShowMenu(void); //메뉴출력
void MakeAccount(void); //계좌개설
void DepositMoney(void); //입금
void WithdrawMoney(void); //출금
void ShowAllAccInfo(void); //잔액조회

// 버튼 1~5까지 부여
//1. 계좌개설
//2. 입금
//3. 출금
//4. 계좌번호 전체 출력
//5. 프로그램 종료
enum {MAKE=1, DEPOSIT, WITHDRAW, INQUIRE, EXIT};

typedef struct
{
    int accID; //계좌번호
    int balance; //잔액
    char cusName[NAME_LEN]; //고객이름
} Account;

Account accArr[100]; //Account 저장을 위한 배열
int accNum = 0; //저장된 Account 수

int main()
{
    int choice;

    while (1)
    {
        ShowMenu();
        
        cout << "선택 : ";
        cin >> choice;
        cout << endl;

       switch(choice)
        {
        case MAKE:
            MakeAccount();
            break;
        case DEPOSIT:
            DepositMoney();
            break;
        case WITHDRAW:
            WithdrawMoney();
            break;
        case INQUIRE:
            ShowAllAccInfo();
            break;
        case EXIT:
            return 0;
        default:
            cout << "잘못된 선택" << endl;
        }
    }
    return 0;
}

// 메뉴출력
void ShowMenu(void) {
    /*cout<< "-----Menu----- \n "
        "1. 계좌개설 \n " 
        "2. 입금 \n "
        "3. 출금 \n "
        "4. 계좌번호 전체 출력 \n "
        "5. 프로그램 종료" <<endl;*/
    cout << "-----Menu-----" << endl;
    cout << "1. 계좌개설" << endl;
    cout << "2. 입금" << endl;
    cout << "3. 출금" << endl;
    cout << "4. 계좌번호 전체 출력" << endl;
    cout << "5. 프로그램 종료" << endl;
}

// 계좌개설
void MakeAccount(void)
{
    int id;
    char name[NAME_LEN];
    int balance;

    cout << "[계좌개설]" << endl;
    cout << "계좌ID:(숫자로 입력) "; cin >> accArr[accNum].accID;
    cout << "이름: "; cin >> accArr[accNum].cusName;
    cout << "입금액: "; cin >> accArr[accNum].balance;
    cout << endl;
      
   /* accArr[accNum].accID = id;
    accArr[accNum].balance = balance;
    strcpy_s(accArr[accNum].cusName, NAME_LEN, name);*/
    cout << accNum << endl;
    accNum++; 
}

// 입금
void DepositMoney(void)
{
    int money;
    int id;
    cout << "[입  금]" << endl;
    cout << "계좌ID: "; cin >> id;
    cout << "입금액: "; cin >> money;

    for (int i = 0; i < accNum; i++)
    {
        if (accArr[i].accID == id)
        {
            accArr[i].balance += money;
            cout << "입금완료" << endl << endl;
            return;
        }
    }
    cout << "유효하지 않은 ID 입니다." << endl << endl;
}

// 출금
void WithdrawMoney(void)
{
    int money;
    int id;
    cout << "[출  금]" << endl;
    cout << "계좌ID: "; cin>>id; 
    cout << "출금액: "; cin >>money;

    for (int i = 0; i < accNum; i++)
    {
        if (accArr[i].accID == id)
        {
            if (accArr[i].balance < money)
            {
                cout << "잔액부족" << endl << endl;
                return;
            }
            accArr[i].balance -= money;
            cout << "출금완료" << endl << endl;
            return;
        }
    }
    cout << "유효하지 않은 ID 입니다" << endl << endl;
}

// 전체고객 잔액조회
void ShowAllAccInfo(void)
{
    for (int i = 0; i < accNum; i++)
    {
        cout << "계좌ID: "<<accArr[i].accID<<endl;
        cout << "이름: " << accArr[i].cusName<< endl;
        cout << "잔액: " << accArr[i].balance<< endl<<endl;
    }
}

 

 

메모

 

 

작성하면서 나온 에러

E0349 : 이러한 피연산자와 일치하는 ">>" 연산자가 없습니다.  #include "string" 추가

계좌ID에 기대한 int 값이 아니라 문자열이 들어가면
처리되지 않은 예외가 있습니다.  ... 위치를 기록하는 동안 액세스 위반이 발생했습니다 와 같은 에러메세지가 출력된다.
형식이 잘못된 데이터가 들어가면 튕겨내고 읽는게 계속 반복된다. 그래서 무한 반복..

더보기

표준 stream 관련 입력 함수들은 대개 주어진 format과 일치하지 않는 데이터가 들어오면 무시해버립니다. 무시한다는 것이 입력된 data를 날려버린다는 것이 아니라, 에러로 처리하고 그 data는 그대로 보존되는 것입니다.

따라서 첫번째에 문자를 입력하면, 입력 버퍼에 문자가 들어가게 되고, 이는 변수 i의 타입인 int로 해석될 수 없기 때문에 error로 처리되고 입력 버퍼에는 그대로 문자가 남아있게 됩니다.

따라서 다음에 다시 변수 i로 입력을 받게 하더라도 무시되는 것을 볼 수 있습니다. (두번째 cin >> i)

결국 제대로 된 프로그램을 쓰려면 입력을 받을 때 문자열로 받은 다음 이것을 다시 원하는 포맷으로 변경하는 작업을 거치는 것이 일반적입니다.

C++을 쓴 지 꽤 되서 답변이 정확할 지 잘 모르겠습니다. :-)

같은 이유에서 scanf(3)도 그럴듯한 프로그램에서는 전혀 쓰이지 않습니다. 쓰인다면 fscanf, sscanf가 주로 쓰이죠.

제가 보기에는 C++에서 이를 해결하려면,
일단 string type으로 입력을 받아서 처리하던지, 아니면,
string stream buffer를 쓰면 해결될 것 같습니다. :-)

마지막으로 책 하나 추천해드리죠. 이 책을 보시면 iostream에 관해 전문가가 되실 수 있을 겁니다:

"Standard C++ IOStreams and Locales. - Advanced Programmer's Guide and Reference", Angelika Langer and Klaus Kreft, Addison Wesley

 

출처 : https://kldp.org/node/19262

C FAQ: http://www.eskimo.com/~scs/C-faq/top.html
Korean Ver: http://cinsk.github.io/cfaqs/

 

LNK1168 : 쓰기용으로 열 수 없습니다. 에러는 프로젝트가 실행되고 있어서니까
프로그램을 종료시켜서 해결이 안되면 작업끝내기로 끝내주고
그도 안되면 새로 프로젝트를 만드는 방법도 있는 듯 하다.

큰 3가지 이유와 관련된 내용 : https://codecollector.tistory.com/1

728x90
728x90

'C, C++ > 열혈 C++ 프로그래밍' 카테고리의 다른 글

02. C언어 기반의 C++ 연습문제  (0) 2021.09.14
02-3. 참조자(Reference)의 이해  (0) 2021.09.13
02-1 챕터 02의 시작에 앞서  (0) 2021.08.19
01-5 이름공간  (0) 2021.08.19
01-4 인라인 함수  (0) 2021.08.18
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
c언어 복습 유도 확인학습 문제

 

 

문제 1 : const 키워드의 의미

다음 문장들을 대상으로 키워드 const가 갖는 의미를 설명한다.

const int num=10;
const int * ptr1=&val1;
int * const ptr2=&val2;
const int * const ptr3=&val3;

 

const는 변수를 상수화하기 위해 사용

const란 변수를 상수화하기 위해 쓴다.
상수는 반드시 선언과 동시에 값을 할당하여 초기화한다.
초기화 안하면 컴파일 에러 발생

const int n = 10; // n=12 x

 

데이터상수화, 상수포인터

n이라는 포인터가 아니라, 포인터가 가리키는 대상을 상수화한다.
n이라는
포인터를 이용해서 데이터를 변경하는 것을 막겠다.
(포인터 외에 변수를 변경하는 것은 막지 못함)
메모리공간은 그대로이다.

const int* n = 12;
// *n = 20 (x)
//  a = 20 (0)

 

포인터상수화
int* const n = 12;   
// n = &a (x)

 

 

데이터상수화 + 포인터상수화 
const int* const n;   // 데이터상수화, 포인터상수화 둘 다 불가

 

 

 

문제 2 : 실행중인 프로그램의 메모리 공간

실행중인 프로그램은 OS로부터 메모리 공간을 할당받고, 크게 데이터, 스택, 힙 영역으로 나뉜다. 각 영역에는 어떠한 변수가 할당되는지 설명하며 C언어의 malloc과 free 함수와 관련해서도 설명한다.

 

void function(int);

int main(void)
{
	int size;
	cin>>size;
	function(size);
	return 0;
}

void function(int i)
{
	int array[i];
	// stack에 올려야되는데 올리지 못한다
	// 그래서 heap에 선언하기 위해 
	// malloc과 free 함수를 사용한다.
}

 

OS는 메모리공간의 효율적 관리를 위해 나눠서 메모리공간을 할당한다.

데이터영역?

전역변수, 스태틱변수가 들어간다.
전역/static 변수가 초기화 되는 시점만 다르고 생성되는 시점은 같다.
전역 변수 : 프로그램이 실행되자마자 할당되어 프로그램 종료 시까지 남아있다.
static 변수 : 프로그램 시작과 동시에 메모리공간에 할당되나, 호출 시 변수 초기화가 된다.

stack의 용도 및 특징?

컴파일 타임에 메모리의 크기가 결정될 수 있는 것들을 저장하기 위한 메모리 공간

heap의 용도 및 특징?

런타임에 메모리의 크기가 결정되는 변수나 배열과 같은 것들을 저장하기 위한 메모리 공간
프로그래머가 관리 (할당, 해지)

malloc & free 함수의 필요성?

malloc은 heap에 메모리공간을 할당 / free 함수 호출로 반드시 할당된 걸 해지 (미호출 시 미해제)
사용할 줄도 알아야 한다.

// C

#include <stdlib.h> //malloc 함수가 포함된 헤더 파일

void* malloc(size_t size)
// size_t : 동적으로 할당할 메모리의 크기
// 반환값 : 성공 시 할당 메모리의 첫번재 주소, 실패 시 null
#include <stdio.h>
#include <stdlib.h>

void main()
{
    int* arr;
    arr = (int*)malloc(sizeof(int) * 4); // size 4 동적할당

    arr[0] = 100;
    arr[1] = 200;
    arr[2] = 300;
    arr[3] = 400;

    for (int i = 0; i < 4; i++) {
        printf("arr[%d] : %d\n", i, arr[i]);
    }

    free(arr); //동적할당 해제
}

 

 

문제 3 : Call-by-value vs. Call-by-reference

함수의 호출형태는 크게 값에 의한 호출과 참조에 의한 호출로 나뉜다. 이 둘을 나누는 기준이 무엇인지, 두 int형 변수의 값을 교환하는 Swap 함수를 예로 들어가며 설명한다.

call by value 함수 : 값에 의한 호출, 함수가 매개변수를 값으로 전달받음
call by reference 함수 : 함수의 매개변수를 참조변수로 선언하는 방식.

참조변수 기능이 추가되면서 C언어에는 없던 C++에서 추가된 개념.

 

// Call-by-value
void SwapByValue(int num1, int num2)
{
	int temp = num1;
	num1=num2;
	num2=temp;
}

// Call-by-reference
void SwapByRef(int * ptr1, int * ptr2)
{
	int temp = *ptr1;
	*ptr1=*ptr2;
	*ptr2=temp;
}
#include <iostream>

void plus(int &a) { // 참조 매개변수
	a++;
}

int main() {
	int a = 10;
	plus(a); // Call by Value 처럼 사용
	std::cout<<a<<std::endl;

	return 0;
}
728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
이름공간 : 공간에 이름을 붙이는 행위

프로젝트를 여럿이서 진행 시 발생될 중복 문제를 예방한다.
아래와 같을 경우 컴파일 에러가 발생한다.

#include <iostream>

using namespace std;

void funtion(void) {
    cout<<"A.com에서 정의한 함수" <<endl;
}

void funtion(void) {
    cout<<"B.com에서 정의한 함수" <<endl;
}


int main(void)
{
    funtion();
    return 0;
}
// 컴파일 에러 발생

 

 

namespace 형태
namespace BestComImpl 
{
	//이름공간 내부
}

 

이름공간을 쓰는 방법

범위지정연산자 :: 를 통해 이름공간을 지정한다.
scope resolution operator

#include <iostream> 

namespace BestComImpl 
{
    void SimpleFunc(void) {
        std::cout<<"BestComImpl이 정의한 함수" <<std::endl;
    }
}
 
 // PromgComImpl이름공간 안에 SimpleFunc 함수 정의    
namespace PromgComImpl 
{
    void SimpleFunc(void) {
        std::cout<<"ProgCom이 정의한 함수" <<std::endl;
    }
}

int main(void)
{
    BestComImpl::SimpleFunc(); // 호출문장
	PromgComImpl::SimpleFunc(); 
    return 0;
}

 

이름공간 기반 함수 선언, 정의 구분
#include <iostream> 

namespace BestComImpl 
{
    void SimpleFunc(void);
}
 
namespace ProgComImpl 
{
    void SimpleFunc(void);
}

int main(void)
{
    BestComImpl::SimpleFunc(); // 호출문장
	ProgComImpl::SimpleFunc(); 
    return 0;
}

void BestComImpl::SimpleFunc(void)
{
    std::cout<<"BestComImpl이 정의한 함수" <<std::endl;
}
 
void ProgComImpl::SimpleFunc(void)
{
    std::cout<<"ProgCom이 정의한 함수" <<std::endl;
}

 

동일한 이름공간에 정의된 함수를 호출할 때에는 이름공간 명시팔 필요 없다.

#include <iostream> 

namespace BestComImpl 
{
    void SimpleFunc(void);
}
 
namespace BestComImpl 
{
    void PrettyFunc(void);
}
 
namespace ProgComImpl 
{
    void SimpleFunc(void);
}

int main(void)
{
    BestComImpl::SimpleFunc();
    return 0;
}

void BestComImpl::SimpleFunc(void)
{
    std::cout<<"BestComImpl이 정의한 함수" <<std::endl;
    PrettyFunc(); //동일이름공간
    ProgComImpl::SimpleFunc(); //다른이름공간
}

void BestComImpl::PrettyFunc(void)
{
    std::cout<<"So Pretty" <<std::endl;
}
 
void ProgComImpl::SimpleFunc(void)
{
    std::cout<<"ProgCom이 정의한 함수" <<std::endl;
}

 

이름공간 중첩 : 다른 이름공간 안에 이름공간이 삽입 가능하다.
namespace Parent 
{
    int num=2;
    
    namespace SubOne
    {
        int num=3;    
    }
    namespace SubTwo
    {
        int num=4;    
    }
}
// 선언된 이름공간이 달라 이름충돌 문제가 발생하지 않는다.

각 변수 num에 접근 방법

std::cout<<Parent::num<<std::endl;
std::cout<<Parent::SubOne::num<<std::endl;
std::cout<<Parent::SubTwo::num<<std::endl;

 


 

문제 01-4 : 파일의 분할

C++을 제대로 공부하려면 헤더파일의 의미와 정의방법, 헤더파일에 삽입할 내용과 소스파일에 삽입할 내용을 구분하는 방법, 둘 이상의 헤더파일과 소스파일을 만들어서 하나의 실행파일로 컴파일하는 방법을 알아야 한다. (C언어 기본서 참조) 

실습으로 아래 코드를 3개의 파일로 분할해서 컴파일해본다.
헤더파일 : main함수를 제외한 나머지 두 함수의 선언 넣기. SimpleFunc.h라고 저장
소스파일1 : main함수를 제외한 나머지 두 함수의 정의 넣기. SimpleFunc.cpp로 저장
소스파일2 : main 함수만 삽입. SimpleMain.cpp 로 저장

 

#include <iostream> 

namespace BestComImpl 
{
    void SimpleFunc(void);
}
 
namespace ProgComImpl 
{
    void SimpleFunc(void);
}

int main(void)
{
    BestComImpl::SimpleFunc(); // 호출문장
	ProgComImpl::SimpleFunc(); 
    return 0;
}

void BestComImpl::SimpleFunc(void)
{
    std::cout<<"BestComImpl이 정의한 함수" <<std::endl;
}
 
void ProgComImpl::SimpleFunc(void)
{
    std::cout<<"ProgCom이 정의한 함수" <<std::endl;
}

 

헤더파일 (.h) 

// 헤더 가드의 시작
//#ifndef ADD_H
//#define ADD_H

namespace BestComImpl 
{
    void SimpleFunc(void);
}
 
namespace ProgComImpl 
{
    void SimpleFunc(void);
}

// 헤더 가드의 끝
//#endif

소스파일1

#include <iostream> 
#include "SimpleFunc.h" // 헤더파일 포함 

void BestComImpl::SimpleFunc(void)
{
    std::cout<<"BestComImpl이 정의한 함수" <<std::endl;
}
 
void ProgComImpl::SimpleFunc(void)
{
    std::cout<<"ProgCom이 정의한 함수" <<std::endl;
}

소스파일2

//#include <iostream> 
#include "SimpleFunc.h" // 헤더파일 포함

int main(void)
{
    BestComImpl::SimpleFunc(); // 호출문장
	ProgComImpl::SimpleFunc(); 
    return 0;
}

 


std::cout, std::cin, std::endl

그간 namespace를 썼던 예시.
이름공간std에 선언된 cout, 이름공간std에 선언된 cin, 이름공간std에 선언된 endl을 뜻한다.

헤더파일 <iostream>에 선언되어 있는 cout, cin, endl은 이름공간 std 안에 선언되어 있었던 것이다.
이름충돌을 막기 위해 C++ 표준 다양한 요소들은 이름공간 std 안에 선언되어 있다.

 

std::cout
// std는 이름공간.
namespace std
{
	cout .....
	cin .....
	endl.....
}

 

 

using 을 사용한 이용공간 명시

키워드 using을 사용해서 범위지정 없이 호출하는 방법.
지역변수 선언처럼 선언 지역을 벗어나면 효력을 잃게 된다.
프로그램 전체 영역에 효력을 미치려면 전역변수처럼 함수 밖에 선언한다.

형식

using A_COM::function;
// A_COM 이름공간 안의 function 함수를 사용하겠다

 

예시

#include <iostream>

using namespace std;

namespace A_COM 
{
    void funtion(void) {
        cout<<"A.com에서 정의한 함수" <<endl;
    }
}

using namespace A_COM;

int main(void)
{
    funtion();
    return 0;
}
#include <iostream>

using namespace std;

namespace A_COM 
{
    int i;
    void funtion(void) {
        cout<<"A.com에서 정의한 함수" <<endl;
    }
}

// 한 줄에 선언
using namespace A_COM;

// using A_COM::function;
// using A_COM::i;

int main(void)
{
    i=10;
    funtion();
    return 0;
}
#include <iostream>

// 한번에 선언
// using namespace std;

// namespace의 목적 : 이름충돌 방지인데
// 목적과 다르게 이름충돌이 발생할 수도 있어
// 아래 방식을 추천한다

// 각각 선언
using std::cout;
using std::cin;
using std::endl;


int main(void)
{
    cout<<"Hello World!"<<endl;
    
    return 0;
}

 

이름 공간에 별칭 붙이기
namespace ABC = AAA::BBB::CCC;

 

범위 지정 연산자 기반, 전역 변수 접근
int val = 100;

int main(void)
{
	int val = 100;
	::val += 1;
    // 예시를 위한 예시이지만 실제로는 변수명을 다르게 할 것
    
	return 0;
}

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90

C : 매크로, C++ : 인라인함수

 

매크로의 종류는 단순, 함수형, 객체형이 있다. (참조)

#define MY_MECRO // 단순 매크로 선언
#define PI (3.14) // 매크로 상수 선언
#define PC "Personal Computer" // 매크로 상수 선언
#define PRN(x) printf("%d\n",x) // 매크로 함수 선언

 

C 매크로 함수

매크로로 변수/함수를 정의하여 매크로 함수를 통한 인라인을 한다.
전처리기에 해당하여 컴파일 전에 처리 그리고 치환된다.
함수 형식 매크로를 정의해서 함수 대신 사용할 수 있다.
함수 형식 매크로를 여러번 사용하면 프로그램 데이터 크기가 커지므로 단순하고 짧은 처리 시 사용한다.


#define 등... (더보기)

 

#include <iostream>
#define SQUARE(x) ((x)*(x))

int main(void){
	std::cout<<SQUARE(5)<<std::endl;
    // 전처리 과정을 거쳐 아래와 같이 변환
    // std::cout<<((5)*(5))<<std::endl; 
	// 함수의 몸체부분이 함수의 호출문을 대체
    
    return 0;
}
1. 매크로 함수의 전체를 괄호(())로 감싸야 합니다.
2. 매크로 함수의 인수들도 각각 괄호로 감싸야 합니다.
#define SQUARE(x) ((x)*(x))

 

 

매크로 함수 장점 : 실행속도 향상

함수 호출에 의한 성능 저하가 일어나지 않기 때문에 일반 함수에 비해 실행속도가 빠르다.
성능향상에 도움이 되는 상황 : 단순하고 짧은 처리 시 사용하기

단순 치환만 하여 인수 타입에 구애받지 않는다.
여러 개의 명령문을 동시 포함할 수 있다.

 

매크로 함수 단점 : 구현의 어려움, 디버깅

정의하기 어렵고, 복잡한 함수는 매크로로 표현하는데 한계가 있다.
오류 디버깅하기 어렵다. 
함수 크기가 증가하면 괄호가 늘어나 가독성이 떨어져 간단한 함수 대체 시 권장

 

INLINE

인라인 함수 : 프로그램 실행 소스코드 라인(line) 안으로 (in) 함수 정의가 들어가 버린 함수

C++ 인라인함수는 매크로함수를 개선한 느낌

매크로를 이용한 함수의 인라인화 : 전처리기에 의해 처리
키워드 inline을 이용한 함수의 인라인화 : 컴파일러에 의해 처리

 

#include <iostream>

// 인라인 함수 정의
// 키워드 inline 선언으로 정의됨
inline int SQUARE(int x){
    return x*x;
}

int main(void)
{  
    // 인라인 함수이므로 몸체부분이 호출문을 대신함
   std::cout<<SQUARE(5)<<std::endl;
   std::cout<<SQUARE(12)<<std::endl; 

   return 0;
}

 

 

C++의 인라인 키워드로 인라인화 : 함수 앞에 inline을 붙여준다.

컴파일러로 처리로 인해,
1. 매크로 함수보다 디버깅하기 좋다.
2. 구현하기 편하고 컴파일러로 최적화 기회 제공

함수의 인라인화가 해가 될 경우 키워드를 무시하거나 필요 시 일부 하수를 임의로 인라인 처리하기도 한다.

 

의존성이 적은 게 인라인 함수에 없는 매크로 함수의 장점

자료형에 의존적이지 않은 매크로 함수

 

#define SQUARE(x) ((x)*(x))

std::cout<<((12)*(12)); //int형 함수호출
std::cout<<((3.14)*(3.14)); //double형 함수호출
inline int SQUARE(int x){
    return x*x;
}

std::cout<<((12)*(12)); //int형 함수호출
std::cout<<((3.14)*(3.14)); //double형 함수호출 //9가 출력

 

인라인 함수에서는 자동 변환이 되지 않아 데이터의 손실 발생
이를 막으려면 함수 오버로딩이 필요하나 함수 정의가 늘어나 간결하려는 원래의 목표와 멀어지므로
C++ 템플릿을 사용하면 자료형에 의존적이지 않은 함수 사용이 가능하다.

C++에서 typename 키워드와 class 키워드를 같은 것으로 생각한다.

 

#include <iostream>

template <typename T>
inline T SQUARE(T x){
    return x*x;
}

int main(void)
{
   std::cout<<SQUARE(5.5)<<std::endl;
   std::cout<<SQUARE(12)<<std::endl; 

   return 0;
}

 

 

 

템플릿 : 여러 자료형으로 사용할 수 있도록 하는 틀

함수 템플릿클래스 템플릿으로 나누어진다. 
타입마다 별도의 함수나 클래스를 만들지 않고도 여러 자료형으로 사용할 수 있도록 하는 틀
(여러 색을 모아두었다 그때마다 골라 쓰는 다색 볼펜)

함수 템플릿 설명 더보기

 

#include <iostream>
using namespace std; //C++ std 생략

template <typename T>

void Swap(T& a, T& b);

int main(void)
{
    int c = 2, d = 3;
    cout << "c : " << c << ", d : " << d << endl;
    Swap(c, d);
    cout << "c : " << c << ", d : " << d << endl;

    string e = "hong", f = "kim";
    cout << "e : " << e << ", f : " << f << endl;
    Swap(e, f);
    cout << "e : " << e << ", f : " << f << endl;

    return 0;
}


template <typename T>

void Swap(T& a, T& b)
{
    T temp;
    temp = a;
    a = b;
    b = temp;
}

 

앞장에서 작성했던 swap을 좀 더 간결하게 할 수도 있을 것 같다.

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
디폴트값은 기본적으로 설정되어 있는 값

C++의 특성, 디폴트 매개 변수

 

디폴트 매개 변수는 전달되지 않은 인자 대신으로 들어갈 변수 기본 값

전달된 인자가 없을 때 대입

 

#include <iostream>

int function(int a=0){
    return a+1;
}

// void의 경우 return값이 없어 컴파일 오류 발생
// void function(int a=0){
//     //return a+1;
// }

int main(void)
{
   std::cout<<function(11)<<std::endl; 
   //모니터로 이 데이터를 보낸다(출력)
   // return되는 결과값을 보낸다.
   // return 타입이 void면 안된다.
   
   std::cout<<function()<<std::endl;
    
    return 0;
}

 

 

디폴트 값은 함수의 선언 부분에만 표현하면 된다.

함수 원형을 별도로 선언하는 경우 매개변수의 디폴트값은 선언 부분에만 위치한다.

 

#include <iostream>

int Adder(int num1=1, int num2=2); //함수 선언

int main(void)
{   
   std::cout<<Adder()<<std::endl; 
   std::cout<<Adder(5)<<std::endl;
   std::cout<<Adder(3, 5)<<std::endl; 
   return 0;
}

int Adder(int num1, int num2) { // 함수 정의
    return num1+num2;
}

 

 

부분적 디폴트 값 설정

부분적 값 설정도 가능한데 오른쪽 매개변수의 디폴트값을 비우는 형태는 불가하다.
오른쪽 매개변수의 디폴트 값부터 채우는 형태로 정의한다. 
이유 : 함수에 전달되는 인자가 왼쪽부터 오른쪽으로 채워지기 때문

 

#include <iostream>

int BoxVolume(int length, int width=1, int height=1);
// 매개변수가 채워지는 방향은 왼쪽에서 오른쪽

int main()
{
   
   std::cout<<"[3,3,3] : "<<BoxVolume(3,3,3)<<std::endl; 
   std::cout<<"[5,5,1] : "<<BoxVolume(5,5)<<std::endl; 
   std::cout<<"[7,def,def] : "<<BoxVolume(7)<<std::endl; 
   return 0;
}

int BoxVolume(int length, int width, int height){
    return length*width*height;
}


// 분리할 경우 함수 선언부에만 디폴트 매개변수를 정의한다.
// 정의부에도 중복되면 컴파일 에러 발생

 


 

문제 01-3-1 : 아래 코드에서 정의된 함수 BoxVolume을 매개변수의 디폴트 값 지정 형태 말고 함수 오버로딩 형태로 재구현해보자. main 함수는 수정하지 않고, 결과가 동일하게 해야 한다.

 

#include <iostream>

int BoxVolume(int length, int width=1, int height=1);

int main(void)
{   
   std::cout<<"[3,3,3] : "<<BoxVolume(3,3,3)<<std::endl; 
   std::cout<<"[5,5,D] : "<<BoxVolume(5,5)<<std::endl; 
   std::cout<<"[7,D,D] : "<<BoxVolume(7)<<std::endl; 
   //std::cout<<"[D,D,D] : "<<BoxVolume()<<std::endl; 
   // 모든 매개변수에 디폴트 값이 없어 인자가 전달되지 않으면 컴파일 에러 발생
   
   return 0;
}

int BoxVolume(int length, int width, int height){
    return length*width*height;
}

 

 

오버로딩 형태로 재구현

 

 

#include <iostream>

int BoxVolume(int length, int width, int height){
    return length*width*height;
}

int BoxVolume(int length, int width){
    int height = 1;
    return length*width*height;
}

int BoxVolume(int length){
    int width = 1;
    int height = 1;
    return length*width*height;
}

int BoxVolume(){
    int length = 1;
    int width = 1;
    int height = 1;
    return length*width*height;
}

int main(void)
{   
   std::cout<<"[3,3,3] : "<<BoxVolume(3,3,3)<<std::endl; 
   std::cout<<"[5,5,D] : "<<BoxVolume(5,5)<<std::endl; 
   std::cout<<"[7,D,D] : "<<BoxVolume(7)<<std::endl; 
   std::cout<<"[D,D,D] : "<<BoxVolume()<<std::endl; 
   return 0;
}

 

 

문제 01-3-2 : 다음과 같은 형태의 함수 오버로딩은 문제가 있다. 

 

 

#include <iostream>

int function(int a=10) {
    return a+1;
}

int function(void) {
    return 10;
}

int main(void)
{
   std::cout<<function(10)<<std::endl;
   
   // 아래는 둘 다 호출이 가능하여 컴파일 에러 발생
   // std::cout<<function()<<std::endl; 
   return 0;
}

 

디폴트 매개변수와 함수 오버로딩이 둘 다 있을 때 발생되는 문제

인자값이 없는 함수를 호출하면 : 컴파일 오류 발생 (둘 다 호출 가능해서)
따라서 함수를 구분하여 사용할 것.

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,
728x90
728x90
C와 다르게 C++이 가지는 특성

C++은 함수오버로딩이 가능하지만 C에서는 함수의 이름이 같으면 컴파일 오류가 발생한다.
C는 함수의 이름정보만 갖고 찾고, 
C++은 함수이름, 매개변수의 선언을 갖고 함수를 찾기 때문이다.

 

함수 오버로딩 : 같은이름의 함수를 중복해 사용하기

매개변수의 선언형태가 다르면 같은이름의 함수정의 허용
함수호출 시 전달되는 인자를 통해 호출하고자 하는 함수를 구분(갯수, 자료형(타입))

 

void MyFunc();
int MyFunc();

* 반환형으로는 호출 함수를 구분 못함. 컴파일 오류

 

#include <iostream>

void MyFunc(void)
{
    std::cout<<"MyFunc(void) called"<<std::endl;
}

void MyFunc(char c)
{
    std::cout<<"MyFunc(char c) called"<<std::endl;
}

void MyFunc(int a, int b)
{
    std::cout<<"MyFunc(int a, int b) called"<<std::endl;
}

int main(void)
{
    MyFunc();
    MyFunc('A');
    MyFunc(12, 13);
    return 0;
}

 

 

   // 동작하지는 않는 이해를 위한 예시
   auto* temp  = new temp[1000];
    
    temp[0] = (int)10;
    temp[1] = (double)10.0;
    
    for(int i = 0; i< 1000 i++ )
    {
        swap(temp[i], temp[i]);
    }
    
    
    // 이렇게 쓰기보다 위에 반복문으로 한번에 처리
    swap(temp[0], temp[0]);
    swap(temp[1], temp[1]);
    swap(temp[0], temp[0]);
    swap(temp[1], temp[1]);
    swap(temp[0], temp[0]);
    swap(temp[1], temp[1]);
    swap(temp[0], temp[0]);
    swap(temp[1], temp[1]);

 


 

문제 01-2 : 다음 main 함수에서 필요로 하는 swap 함수를 오버로딩 해서 구현해보자.

* swap : 바꾸다. 즉 두 변수의 값을 교환하는 함수. 교환하기 위해서는 중간에 임시 변수가 필요하다.

급하게 본 포인터와 주소 연산자(&), 역참조 연산자(*)

 

#include <iostream>

void swap(int *a, int *b)
{
    // 역참조 연산자 사용 
    // 받아온 주소값을 역참조하여 
    // 특정 주소에서 값에 접근한다
    
    int temp = *a;
    *a=*b;
    *b=temp;
    //std::cout<<a<<' '<<b<<std::endl;
} 

void swap(char *a, char *b)
{
    char temp = *a;
    *a=*b;
    *b=temp; 
} 

void swap(double *a, double *b)
{
    double temp = *a;
    *a=*b;
    *b=temp; 
} 

int main(void)
{
    int num1=20, num2=30;
    swap(&num1, &num2); //메모리 주소값을 보냄
    std::cout<<num1<<' '<<num2<<std::endl;
    
    char ch1='A', ch2='Z'; 
    // error: invalid conversion from ‘const char*’ to ‘char’ [-fpermissive]
    //쌍따옴표는 여러개를 표현하는 용이라 오류 발생. 한글자는 홑따옴표 사용
    // %c 같은 경우 쌍따옴표를 사용
    
    swap(&ch1, &ch2);
    std::cout<<ch1<<' '<<ch2<<std::endl;
    
    double dbl1=1.111, dbl2=5.555;
    swap(&dbl1, &dbl2);
    std::cout<<dbl1<<' '<<dbl2<<std::endl;
    
    return 0;
}

 

더보기
#include <iostream>
using namespace std;

template <typename T>

void Swap(T *a, T *b);

int main(void) { 
    int num1=20, num2=30; 
    Swap(&num1, &num2); //메모리 주소값을 보냄 
    std::cout<<num1<<' '<<num2<<std::endl; 
    
    char ch1='A', ch2='Z'; 
    Swap(&ch1, &ch2);
    std::cout<<ch1<<' '<<ch2<<std::endl; 
    
    double dbl1=1.111, dbl2=5.555; 
    Swap(&dbl1, &dbl2); 
    std::cout<<dbl1<<' '<<dbl2<<std::endl; 
    
    return 0; 
}

template <typename T>

void Swap(T *a, T *b)
{
    T temp;
    temp = *a;
    *a = *b;
    *b = temp;
}

 

 

 

포인터 변수의 자료형은 가리키는 변수의 자료형과 같아야한다.

포인터는 메모리 주소만 보유할 수 있다. 아래처럼 정수 리터럴을 넣으면 메모리 주소가 없어 컴파일 오류가 발생한다.

int *ptr = 5;
// 틀린 코드

 

근데 C++에서는 포인터에 리터럴 메모리 주소는 직접 할당할 수 없다. 정수 리터럴을 넣는 것과 같게 생각해서

 

double *dPtr = 0x0012FF7c;
//틀린 코드

 

포인터 변수가 있다면 역참조 연산자 * 를 통해 포인터가 가리키는 주소값을 알 수 있다.
포인터의 자료형과 변수 자료형이 다르면 역참조시 비트를 다른 자료형으로 잘못 해석한다. 

 

int value = 5;
int *ptr = &value;
std::cout << ptr; // value 메모리주소 0012..어쩌고
std::cout << *ptr; // ptr을 역참조(ptr이 가리키는 주소의 값 =value) 5

 

*ptr은 value와 같게 취급, 변수처럼 값을 할당할 수 있다.

 

int value = 5;
int *ptr = &value;

*ptr = 7; // 이게 가능하다.
std::cout << value; //7로 변경

 

728x90
728x90
블로그 이미지

coding-restaurant

코딩 맛집에 방문해주셔서 감사합니다.

,

v