int main() int main(void) / void main() / main() 메인함수 형태별 차이점
int main()은 여러 매개 변수를 사용하여 호출할 수 있다. int main(void)는 매개변수 없이만 호출이 가능하다. C에서 권장.
반응형
함수 정의 앞에 붙는 데이터형은 함수의 리턴값의 데이터형입니다. int main() 하면 main() 함수가 종료할때 정수형 값을 리턴하겠다는 뜻이고, void main() 하면 main() 함수가 종료할때 아무 값도 리턴하지 않겠다는 뜻이며, main() 하면 void main() 과 같습니다.
main() 함수는 프로그램의 엔트리포인트로서, 운영체제가 실행시켜주는 함수입니다. 따라서 main() 함수의 리턴값은 운영체제가 받아보게되죠. 운영체제는 프로그램이 종료할때 main() 함수의 리턴값을 받아보고 프로그램이 왜 종료되었는가를 판단합니다.
보통의 경우, main() 함수가 0 을 리턴하면 프로그램이 정상적으로 실행을 마치고 종료한것으로 간주하고, 0 이외의 값을 리턴할 경우 비정상적으로 종료된것으로 간주합니다.
그러나, 운영체제가 프로그램의 종료사유를 아는것이 사용자 입장에서는 아무 의미가 없는 동작입니다. 그래서 결론적으로, main() 함수가 어떤 값을 리턴하는지는 운영체제에게만 중요할 뿐, 사용자에게는 전혀 중요하지 않습니다. 그러니, int main() 든 void main() 이든 main() 이든 사용자 입장에서는 아무 차이점이 없고, 다만 운영체제의 입장에서는 약간의 의미가 있을수는 있습니다.
참고로, C언어 표준이 제시하는 가장 이상적인 main() 함수의 정의문은 이렇습니다. int main( int argc, char *argv[], char *env[] )
argc : 배열 argv[]의 크기 (커맨드라인 인수의 개수 +1) argv[]는 포인터 배열로 되어 있습니다 (예 : test.exe val1 val2 val3) argv[0] : 프로그램 파일의 경로와 파일명의 문자열을 가리키는 포인터 argv[1] : 첫 번째 커맨드라인 인수의 문자열을 가리키는 포인터 argv[2] : 두 번째 커맨드라인 인수의 문자열을 가리키는 포인터
콘솔프로그램에서 프로그램을 실행시킬 때 관리자모드로 실행시킨다던가, 파일이나 폴더를 만드는데 readonly 권한으로 만들기 등등으로 이해함
예제
#include <stdio.h>
#include <string.h>
main(int argc, char *argv[])
{
int i;
if (argc <= 1) { // 인수를 지정 안했을 때
printf("11");
return; // 오류방지
}
if (strcmp(argv[1], "enum") == 0) { //문자열 비교(길이와 함께)
for (i = 0; i < argc; i++) {
printf("argv[%d] : %s\n", i, argv[i]);
}
}
else if (strcmp(argv[1], "count") == 0) {
printf("커맨드라인 인수의 개수 : %d\n", argc-1);
}
}
포인터가 가리키는 대상을 서로 바꾸는 swap으로 아래는 효과없는 swap 함수의 호출 예제이다.
#include <stdio.h>
int main()
{
int A = 10, B=20;
int* pA, *pB;
pA=&A, pB=&B;
pswap(pA, pB);
// 호출 후
printf("pA가 가리키는 변수 : %d\n", *pA);
printf("pB가 가리키는 변수 : %d\n", *pB);
return 0;
}
// pA, pB 주소값이 p1, p2로 전달된다
// 우리는 p1, p2가 아니라 pA, pB가 가리키는 대상이
// 바뀌어야 하므로 잘못된 예
void pswap(int* p1, int* p2)
{
int* temp;
temp=p1;
p1=p2;
p2=temp;
}
무조건 포인터가 갔다고 call-by-reference이지 않다. 상대적인 것이다.
더블포인터 입장에서의 진정한 swap의 예
#include <stdio.h>
int main()
{
int A = 10, B=20;
int* pA, *pB;
pA=&A, pB=&B;
pswap(&pA, &pB); //주소값을 바꿔준다
// 호출 후
printf("pA가 가리키는 변수 : %d\n", *pA);
printf("pB가 가리키는 변수 : %d\n", *pB);
return 0;
}
void pswap(int** p1, int** p2)
{
int* temp;
temp=*p1;
*p1=*p2;
*p2=temp;
}
포인터 배열과 포인터 타입
1차원 배열의 경우 배열이름이 가리키는 대상을 통해 타입이 결정된다. (2차원 배열은 달라지는데 다음 단원에서 다룰 것) 포인터 배열도 마찬가지이다.
int arr[100] // 40바이트 할당, 1차원배열, 선
int arr[10][10] // 10*10, 2차원배열, 면
int arr[5][5][5] // 5*5*5, 3차원배열
2차원배열의 선언 : 2차원적 메모리 구조를 구성
12개*4바이트 = 48바이트로 구성되는 배열이 만들어짐.
2차원 배열 요소 접근 방법
#include <stdio.h>
int main()
{
int arr[3][3];
arr[0][0]=2; // 첫 번째 요소
arr[1][0]=4; // 두번째 행 첫번째 요소
arr[2][2]=8; // 마지막 요소
return 0;
}
예제
#include <stdio.h>
int main()
{
int somang[4][2];
//int somang[8]; 1차원배열로 바꿀 경우도 생각해 볼 것
int i,j;
int popu;
// 가구별 거주인구 입력
for(i=0; i<4; i++) {
for(j=0; j<2; j++) {
printf("%d 층 %d 호 인구 입력 : ", i+1, j+1);
scanf("%d", &somang[i][j]);
}
}
// 소망빌라 층별 인구 수 출력
for(i=0; i<4; i++) {
popu=0;
for(j=0; j<2; j++) {
popu+=somang[i][j];
}
printf("%d 층 전체 인구 : %d \n", i+1, popu);
}
return 0;
}
다차원 배열의 실제 메모리 구성
1차원 배열과 동일하나 ** 접근 방법을 2차원적으로 해석할 뿐이다 ** 2차원적으로 이해하는 습관을 가지자. (시스템의 메모리구조는 1차원적이라 플랫하게 할당되긴 한다)
2차원배열 선언과 동시에 초기화
// 행 단위로 모든 요소를 초기화
int somag[3][3]={
{1,2,3},
{4,5,6},
{7,8,9},
};
// 행 단위로 일부 요소만 초기화
// 나머지는 0으로
int somag[3][3]={
{1},
{4,5},
{7,8,9},
};
// 1차원 배열 형태의 초기화
int somag[3][3]={1,2,3 4,5,6 7};
// 문법적으로는 가능하나 비추
// 내부중괄호를 활용할 것
초기화 리스트에 의한 배열 크기의 결정
1차원 배열의 예
int arr[] = {1,2,3,4,5};
2차원 배열의 예
int arr[][] = {1,2,3,4,5,6,7,8}; //에러
int arr[][4] = {1,2,3,4,5,6,7,8};
int arr[][2] = {1,2,3,4,5,6,7,8};
3차원 배열의 선언과 의미
3차원적 메모리 구조를 의미 일반적으로 필요 앖고 개념만 이해할 것
int a[3][3][3]
연습문제
1. 가로의 길이가 9, 세로의 길이가 3인 int형 2차원 배열을 선언하여 구구단 2,3,4단을 저장 후 출력하라 (흰트 : 2중 for문)
wage2020=8590
wage2021=8720
result = wage2021-wage2020
print("2020 최저임금은 {} 원입니다.".format(wage2020))
print("2021 최저임금은 {} 원입니다.".format(wage2021))
print("두 해의 차이는 총 {} 원입니다.".format(result))
// 한 줄에 쓰기
wage2020=8590
wage2021=8720
result = wage2021-wage2020
print("""2020 최저임금은 {} 원입니다.
2021 최저임금은 {} 원입니다.
두 해의 차이는 총 {} 원입니다.""".format(wage2020,wage2021,result))
wage2020=8590
wage2021=8720
result = wage2021-wage2020
# print("""2020 최저임금은 {} 원입니다.
# 2021 최저임금은 {} 원입니다.
# 두 해의 차이는 총 {} 원입니다.""".format(wage2020,wage2021,result))
print("""2020 최저임금은 {2} 원입니다.
2021 최저임금은 {1} 원입니다.
두 해의 차이는 총 {0} 원입니다.""".format(result,wage2021,wage2020))
item="마우스"
quantity=12
item_price=59_000
total_price=quantity*item_price
myorder="{2}원인 {0}의 {1}개에 대한 총 금액은 {3}원입니다."
print(myorder.format(item,quantity,item_price,total_price))
name="길동"
age=27
print("이름은 {0}입니다. {0}의 나이는 {1}세".format(name,age))
my_car="제 차는 {car}이고 색은 {color}"
print(my_car.format(car="트럭", color="용달블루"))
pi=3.141592653589793
txt="원주율은 원 둘레와 지름의 비이고 값은 {:.2f}다"
#둘째자리까지만 출력
print(txt.format(pi))
print("hello {:<10} python".format(23))
#10개의 공백 출력
print("text {0:>20} ".format("샘플"))
print("{0:<10}".format("LEFT")) #왼쪽으로 붙어서 출력
print("{0:>10}".format("RIGHT")) #10칸 뛰고 우측으로 붙어서 출력
print("{0:_^10}".format("가운데")) #중앙
print("{0:@^10}".format("가운데")) #중앙
print("청춘은 청춘에서만 들어 것이다. 보배를 같은 너의 운다. 만천하의 실로 들어 심장은 것이다. 풀이 이것이야말로 곳으로 안고, 있는 있는가? 같이 살았으며, 위하여 가지에 봄날의 같이, 수 교향악이다. 주며, 얼음 얼음에 너의 이상은 바이며, 동력은 얼마나 속에서 이것이다. 품으며, 봄바람을 유소년에게서 것이다. 귀는 시들어 노래하며 것이다. 산야에 같이 불러 원질이 끓는 황금시대를 있으랴? 우리의 피고, 천하를 불러 이것이야말로 안고, 끓는 힘있다.
맺어, 부패를 길지 풀밭에 때에, 그들에게 위하여 봄바람을 보라. 곧 이상의 남는 황금시대다. 봄바람을 거선의 안고, 구하지 웅대한 풀이 곳이 부패뿐이다. 같으며, 피부가 이 구하지 용기가 예가 피다. 별과 얼마나 우는 이상 석가는 그들의 생생하며, 우리 약동하다. 끝까지 소금이라 곳으로 교향악이다. 앞이 이 온갖 불어 인류의 이것이다. 것이다.보라, 많이 꽃이 어디 대중을 있다. 가는 얼마나 열락의 그것은 얼음이 생의 인간의 황금시대의 찬미를 위하여서. 내려온 할지라도 힘차게 뛰노는 것이다.
따뜻한 않는 위하여 인간의 충분히 이 만천하의 황금시대다. 작고 천지는 가장 그들의 청춘의 그리하였는가? 보배를 열매를 고동을 것이다. 청춘의 풀이 무엇을 시들어 이것이야말로 인간의 이것이다. 천지는 불러 가슴에 그들의 이상은 만천하의 위하여 칼이다. 청춘의 설레는 천고에 아니한 놀이 인생을 우리의 사막이다. 공자는 길지 용기가 때까지 옷을 찾아다녀도, 있다. 이는 봄바람을 불러 사막이다. 아니한 무엇을 우리 창공에 있는 사랑의 하는 인간의 끓는 뿐이다." )
#error
역슬래시와 함께 줄을 바꾸거나 큰따옴표로 감싸준다.
print("청춘은 청춘에서만 들어 것이다. \
보배를 같은 너의 운다. 만천하의 실로 들어 심장은 것이다.\
풀이 이것이야말로 곳으로 안고, 있는 있는가? 같이 살았으며 " )
print("청춘은 청춘에서만 들어 것이다."
"보배를 같은 너의 운다. 만천하의 실로 들어 심장은 것이다."
"풀이 이것이야말로 곳으로 안고, 있는 있는가? 같이 살았으며 " )
이번 단원에 알아두어야 할 핵심개념 : call-by-reference, call-by-value
함수의 인자로 배열 전달하기
기본적인 인자 전달 방식 : 값의 복사에 의한 전달
정확히는 val을 전달하는 게 아닌, val이 지니고 있는 값을 전달 '복사'해서 a를 초기화하고 있다.
배열의 함수 인자 전달 방식 : 배열이름(배열주소, 포인터)에 의한 전달
배열전체를 복사하여 함수의 인자로 넘길 수는 없다 (나중에 구조체로는 가능) 그래서 주소값만 복사해 전달하여 사용한다.
배열 전달의 예
#include <stdio.h>
void fct(int *arr2);
int main ()
{
int arr1[2] = { 1, 2 }; //arr1 배열이름이자 포인터는 int형 포인터, 주소를 가리킨다
fct (arr1); // arr1이 지닌 주소값을 배열의이름으로 전달하면서 fct 함수를 호출하면서 전달한다
printf ("%d\n", arr1[0]);
return 0;
}
void fct(int *arr2)
{
// arr2는 arr1의 주소값을 받는다
// int형 변수의 주소값이기 때문에 int형 포인터여야 한다 **
printf("%d\n", arr2[0]);
// *(arr2+0)와 같은 문장...
// *(arr2+0)이라는 주소값이 가리키는 메모리공간을 참조하라
// 1 출력
arr2[0]=3;
// *(arr2+0) = 3; 과 같은
}
배열이름, 포인터의 sizeof 연산
배열이름 : 배열전체 크기를 바이트로 반환 포인터 : 포인터의 크기를 바이트로 반환
#include <stdio.h>
int main()
{
int arr[5];
int* pArr=arr;
printf("%d\n", sizeof(arr)); //배열 이름 : 배열전체 크기를 바이트단위로 반환 (20)
printf("%d\n", sizeof(pArr)); // 포인터의 크기를 바이트단위로 반환(4 or 8)
//printf("%d\n", sizeof(*pArr)); //4
printf("%d\n", sizeof(int)); // 4byte
return 0;
}
배열의 길이는 인자로 전달받은 함수 내에서 계산이 불가능하고 함수를 호출할 때 아래처럼(sizeof(배열)/sizeof(자료형) 계산하여 전달을 해야 한다...
#include <stdio.h>
int ArrAdder(int* pArr, int n);
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int SumOfArr;
// 배열의 길이가 바뀌어도 아래 코드는 그대로 쓸 수 있다.
SumOfArr = ArrAdder(arr, sizeof(arr)/sizeof(int));
// SumOfArr = ArrAdder(arr, 10); //하드코딩
// sizeof(int) int의 크기는 시대에 따라 다를 수 있다
printf("배열의 길이체크 : %d\n", sizeof(arr)); //40
printf("배열의 총 합 : %d\n", SumOfArr);
return 0;
}
//배열의 시작번지, 배열의 길이
int ArrAdder(int* pArr, int n)
{
int sum = 0;
int i;
// n 대신 sizeof(pArr)를 쓰면 배열의 길이가 아니고 포인터 크기가 반환
for(i=0; i<n; i++)
sum+= pArr[i];
return sum;
}
int* pArr vs int pArr[ ]
int* pArr을 많이 쓰기를 권해드림. 배열과 포인터를 혼동하기 때문이다. 둘은 같은 의미이나, 선언 int pArr[ ] 은 함수의 매개 변수 선언 시에만 사용 가능 매개변수 선언 시가 아니면 불가
// int function(int* pArr)
// 변수의 주소값 전달인지, 배열의 이름 전달인지 알수가 없다
// 배열이 인자로 전달됨을 알 수 있다 -- 의미명확
// 매개변수 선언 시 아래 방식을 선호한다
// 배열의 이름이 아니고 진짜 포인터다
// sizeof 연산 시 포인터 크기가 나온다
int function(int pArr[])
{
int a = 10;
pArr = &a;
return *pArr;
}
배열이 갖고있는 요소 중 최대값을 반환하는 예
#include <stdio.h>
int MaxVal(int pArr[], int n);
int main(void)
{
int arr[10] = {4,8,3,7,2};
int max;
max = MaxVal(arr, sizeof(arr)/sizeof(int));
printf("최대값 : %d\n", max);
return 0;
}
// 배열의 시작번지, 배열의 길이
// int pArr[]은 포인터변수
// int MaxVal(int *pArr, int n)
int MaxVal(int pArr[], int n)
{
int max, i;
printf("sizeof(pArr): %d\n", sizeof(pArr));
max=pArr[0];
for(i=1; i<n; i++)
if(max<pArr[i])
max=pArr[i];
return max;
}
#include <stdio.h>
int MaxVal(int pArr[], int n);
int main(void)
{
int arr[10] = {4,8,3,7,2};
int max;
//int *p;
//int p[]; //error - 배열 선언인데 size 생략했다고 판단됨
max = MaxVal(arr, sizeof(arr)/sizeof(int));
printf("최대값 : %d\n", max);
return 0;
}
// 배열의 시작번지, 배열의 길이
// int pArr[]은 포인터변수
// int MaxVal(int *pArr, int n)
int MaxVal(int pArr[], int n)
{
int max, i;
printf("sizeof(pArr): %d\n", sizeof(pArr));
max=pArr[0];
for(i=1; i<n; i++)
if(max<pArr[i])
max=pArr[i];
return max;
}
Call-By-Value
값의 복사에 의한 함수의 호출 call : 함수 호출을 뜻함 가장 일반적인 함수 호출 형태
#include <stdio.h>
int add(int a, int b);
int main()
{
int val1 = 10;
int val2 = 20;
printf("결과: ", add(val1, val2));
//call by value
return 0;
}
// 복사
int add(int a, int b)
{
// val1과 다른 변수
return a+b;
}
call-by-value에 의한 swap
swap함수 : 서로 바꾸는 기능을 하는 것 가짜 swap? 찐 swap은 val2와 val1의 진짜 값이 변경됨 직접적으로 val1, val2의 값을 바꿀 수 없고, 코드 실행 후에도 그대로이다.
#include <stdio.h>
int main()
{
int val1=10;
int val2=20;
swap(val1, val2);
printf("val1 : %d\n", val1);
printf("val2 : %d\n", val2);
return 0;
}
void swap(int a, int b)
{
int temp=a;
a= b;
b=temp;
printf("a : %d\n", a);
printf("b : %d\n", b);
}
note : previous implicit declaration of 'swap' was here swap(... 묵시적 선언 오류 : 함수 선언을 하지 않고 먼저 사용했다는 뜻.
묵시적 선언 오류 수정 후
#include <stdio.h>
void swap(int a, int b);
int main()
{
int val1=10;
int val2=20;
swap(val1, val2);
printf("val1 : %d\n", val1);
printf("val2 : %d\n", val2);
return 0;
}
void swap(int a, int b)
{
int temp=a;
a= b;
b=temp;
printf("a : %d\n", a);
printf("b : %d\n", b);
}
call-by-reference
참조(참조를 가능케하는 주소값)를 인자로 전달하는 형태의 함수 호출 main함수 내 지역변수에 접근할 수 있었다.
#include <stdio.h>
int main()
{
int val = 10;
adder(&val);
// val의 주소 전달
printf("val: %d", val);
// 11 출력
return 0;
}
void adder(int* pVal)
{
// val의 주소값이 전달됨 (Ox10 등..)
// 포인터변수 pVal이 가리키는 변수의 값 1 증가
(*pVal)++;
}
call-by-reference에 의한 swap
#include <stdio.h>
int main()
{
int val1 = 10;
int val2 = 20;
printf("before val1: %d\n", val1);
printf("before val2: %d\n", val2);
swap(&val1, &val2); //주소전달
printf("After val1: %d\n", val1);
printf("After val2: %d\n", val2);
return 0;
}
void swap(int* a, int* b)
{
int temp = *a;
*a=*b;
*b=temp;
}
call-by-value는 값을 전달 / call-by-reference는 주소 값을 전달하는 함수 호출이다. call-by-value는 값을 복사해 전달해서 다른 메모리공간을 접근, 값을 변경할 수 없고 call-by-reference는 주소값을 전달해서 주소값에 해당하는 메모리공간에 접근 가능하다.
scanf 함수 호출 시 &를 붙이는 이유
scanf는 메인함수의 val에 접근하여 값을 대입하는 것 접근하려면 주소값을 알아야 한다. (call-by-reference 방식)
#include <stdio.h>
int main()
{
int val;
scanf("%d", &val);
#include <stdio.h>
int main()
{
char str[100];
printf("문자열 입력 : ");
scanf("%s", str);
// 배열 이름이 주소값이기 때문에 &를 붙이지 않는다
포인터와 const 키워드
const는 선언 첫부분, 포인터 이름 앞에, 두 군데 모두 붙일 수 있다.
1. 포인터가 가리키는 변수의 상수화
int a = 10;
const int* p = &a;
// 포인터가 가리키는 대상(a)을 상수화
// 포인터를 이용한 변경이 불가하다
// p의 관점에서 a가 상수화
// *p=30; //error
a=30; //ok
2. 포인터 상수화
int a = 10;
int b = 20;
int* const p = &a;
// 포인터가 갖고있는 값 자체를 상수화
// 한번 가리킨 포인터의 대상 변경 x 주소값 변경 ㄴ
// 변수는 변경 가능
// p=&b //error
*p=30; //ok
3. 둘 다
const int* const p = &a;
const 키워드를 사용하는 이유
컴파일 시 잘못된 연산에 대한 에러메시지 프로그램을 안정적으로 구성
#include <stdio.h>
float PI = 3.14;
int main()
{
float rad;
PI = 3.07; //실수..바뀌면 안되는 대상
// 그러나 컴파일에러도 없다
// 경고메세지까지 구현했어야 함
scanf("%f", &rad);
printf("원의 넓이는 %f\n ", rad*rad*PI);
return 0;
}
아래처럼 컴파일에러가 발생될 수 있게 하여 안정적으로 코드를 짜도록 한다.
#include <stdio.h>
const float PI = 3.14;
int main()
{
float rad;
PI = 3.07; // 컴파일에러 발생
scanf("%f", &rad);
printf("원의 넓이는 %f\n ", rad*rad*PI);
return 0;
}