조무위키
조무위키
둘러보기
대문
최근 바뀜
임의의 문서로
미디어위키 도움말
도구
여기를 가리키는 문서
가리키는 글의 최근 바뀜
특수 문서 목록
문서 정보
행위
문서
토론
편집
역사 보기
C언어
편집하기 (부분)
경고:
로그인하지 않았습니다. 편집을 하면 IP 주소가 공개되게 됩니다.
로그인
하거나
계정을 생성하면
편집자가 사용자 이름으로 기록되고, 다른 장점도 있습니다.
스팸 방지 검사입니다. 이것을 입력하지
마세요
!
==맛보기2== 왠지 배열 다음으로 만들면 오류나길래 새로 만든다. ===포인터=== ---- {{제작자}} '''포인터는 그 부분만해서 책으로 내놓기도 하고 그만큼 지금까지 배웠던 것 보다는 높은 이해력을 요구한다.''' '''따라서 글을 쓰고있는 작성자도 비약적인 내용을 담을 수 있으며 잘못된 내용이 나올 수 있음을 경고한다.''' '''그러므로 잘못된 부분이 있다면 필히 지적해주길 바람''' 포인터는 C언어의 장점이라 할 수도 있으며, 또한 단점이라고 할 수도 있다. 그 이유는 저수준 접근과 입문자의 인간적 사고를 지녔던 일상과는 달리 컴퓨터 입장에서 봐야하기도 하는 이유 때문이기도 하다. 포인터는 간단하게 설명하자면 주소에 직접 접근하는 것을 일컫는데, 주소를 가리킨다는 의미에서 포인터라 하는 사람도 있다. 여기서 주소란 위에서 설명한 변수의 진짜 이름이라고도 할 수 있는데, 실질적인 메모리 공간의 위치를 주소라 한다. 주소는 4바이트 정수로 표시하는데 (정확히 하자면 unsigned int 즉 부호없는 정수) 대부분 16진수로 표기한다. 예를들어 내가 변수를 선언하였고 이름을 var 이라고 정했다 ( ex - int var; ) 변수는 값을 저장하기 위한 공간이라고 할 수 있는데, 이름을 정하는 이유는 그 공간의 진짜 주소인 메모리 주소를 일일이 기억하기 어렵기 때문이다. 실제로는 0x00000000 이런식의 주소에 저장되어있다. ( 물론 임의로 정한거고 실제는 안 이렇다. ( 절대적인건 아니고 다를 수 있다 codingground 사이트에서는 6바이트의 주소로 나오더라 ) ㄴ 아키텍쳐마다 다름 x86 이면 32bit, x86-64면 64bit 이런식 이걸로 이해가 안가는 사람들을 위해 내가 이해했던 방식으로 설명해준다. 예를 들어 니이름이 강동원이라고치자, 그리고 이글 읽는 너는 분명 급식충이니까 대충 디시중학교 2학년 18반 18번이라고 치자. 그리고 너는 헬조선 학생이라서 맨날천날 학교에 들러붙어서 사는 하이퍼 급식충이다. 그러면 선생님들이 너를 부를때 '야, 강동원이, 느그아부지 뭐하시노'라면서 부를 수도 있지만, '2학년 18반 18번 나와!'라면서 너를 간접적으로 부를수도 있다는 말이다. 그러니까 int name = 강동원 //만약에 문자열을 왜 int형으로 저장하냐는 새끼있으면 머가리 조져버린다. 이라고 선언했을때, 컴퓨터는 자동으로 니놈을 메모리 어딘가의 주소로 보내버린다. 보통은 간지나게 0x000A3같은 16진수로 표현되는데 이 주소에 접근하는거랑 2학년 18반 18번이랑 같은 표현이라는거다. 존나 추상적으로 설명해서 미안하지만, 그건 내가 글을 못써서 그렇다. 미안ㅠ 포인터 변수란것은 그 주소를 보관함으로써 가리키는 변수라고 하는데 간단하게 포인터 변수는 주소를 보관한다고 보면된다. 포인터 변수를 선언하려면 자료형과 이름 사이에 *를 붙이면 되는데. int <nowiki>*</nowiki> ptr; 이라고 선언 가능하다 그리고 이것을 포인터 변수라 하고 좀더 풀어쓰자면 <nowiki>'정수형 변수를 가리키는 포인터 변수 ptr'</nowiki> 이라고 할 수 있다. <nowiki>*</nowiki>이 붙은 모든 포인터 변수의 크기는 보통 4/8byte이며 이유는 주소 크기가 그렇기기 때문이다 cpu 아키텍쳐의 주소 크기를 따라감( double <nowiki>*</nowiki> ptr 도 4byte 라는 얘기) 포인터 변수를 만들었으니 무언가 주소를 보관하고자 한다면 int var; int <nowiki>*</nowiki> ptr = &var; 하면 var 변수의 주소 (ex - 0x00000000)를 ptr이 갖게 되며 이를 가리켰다 라고 표현한다. & 연산자는 옆에 붙은 변수의 주소를 반환한다 (&var = 0x00000000) 그니까 한마디로 ptr에 0x00000000 를 대입한셈! 이제 주소를 갖게 되었으니 한번 그 주소에 접근해보자 int var; int <nowiki>*</nowiki>ptr = &var; <nowiki>*</nowiki>ptr = 3; printf("%d",var); 출력결과는 3이 나올것이다. <nowiki>*</nowiki>ptr = 3; 이 구문은 ptr변수가 갖고있는 주소를 가리키라는 연산자이다. 따라서 갖고있는 주소는 var 변수의 주소이므로 var 변수에 3 대입하는것과 같다! 그러므로 var 변수를 출력하면 3이 나오게 되는 것이다. 왜 쓰는지 궁금할 수도 있는데 이것을 사용하는 이유는 말그대로 변수가 갖고있는 실주소에 접근할 수 있기 때문이다. (다른 이유도 있지만 쉽게 예를 들기 위한 이유이다.) 흔히 포인터에 관해 예를들때 Swap 함수를 만들어보는 예제를 많이들 사용하는데 변수 두개의 값을 바꾸는 Swap 함수를 만들어 본다고 하자. void Swap(int a,int b) { int temp = a; a = b; b = temp; } 이렇게 함수에 인자로 두 변수를 받고 temp에 a변수의 값을 보관하고 a에는 b를 넣고 b에는 a를 보관한 temp를 넣는다. 이렇게 하고 main 함수에서 Swap(var1,var2); 로 호출 했다고 하고 var1 var2를 출력해보면 전혀 안바뀌었는데 이런 이유는 함수 설명에서 알 수 있는데 '''함수의 인자는 값 전달이지 변수 그 자체를 전달하는 것이 아니다''' 함수 정의에서 인자로된 a와 b 변수는 그저 함수의 인자 전달을 위해 만들어진 가짜 변수 이며 매개변수 이기도 하다. (매개변수 뜻을 알면 알 수 있는데 말그대로 매개하는 변수이다.) 이런 이유 때문에 함수 정의부에서 적은 int a,int b 를 '''매개변수'''라 하고 실제로 넘겨준 var1,var2 변수를 '''인자'''라고 단어를 구분짓는 이유기도 하다. 아무튼 이러한 값만 전달하는 특성에 의해 값을 바꾸는 함수 Swap이 제대로 되지 않는다. 여기서 주소에 접근하는 포인터가 있다면 상황은 달라지는데 void Swap(int <nowiki>*</nowiki>a,int <nowiki>*</nowiki>b) { int temp = <nowiki>*</nowiki>a; <nowiki>*</nowiki>a = <nowiki>*</nowiki>b; <nowiki>*</nowiki>b = temp; } 라고하고 호출을 Swap(&var1,&var2); 이렇게 하면 이제 값이 바뀐다! 이유는 잘 이해 했다면 알 수 있는데 여기서 넘겨받은건 값이 아니라 '''주소''' 이며 ( &var1,&var2 ) 함수 내부에서도 <nowiki>*</nowiki>연산자로 a와 b에 보관돈 주소에 접근(가리킴)하여 서로 값을 바꾸었으므로 실제로 그 주소의 공간을 갖고있는 변수인 var1과 var2의 값이 바뀐것이다. ===포인터 연산=== ---- 디시위키 오랜만에 와서 문서 추가하니까 왜이러냐;; 원래 문서도 미리보기하면 오류나냐ㅋㅋㅋㅋㅋ 포인터는 아무 생각없이 일반 증감,가감 연산자를 쓸 경우 생각했던거와 다른 결과를 도출할 수 있는데, 여기서는 간단히 설명하겠다 포인터에 관련된 연산자는 아까 본듯이 <nowiki>*</nowiki> 와 & 가 있었고 ++나 --같은 연산자를 사용할 수 있는데 의미가 다르다. 간단하게 생각하자면 포인터 변수는 주소를 저장하는데 더하거나 뺄경우 주소의 증감 및 가감이 일어난다. 아까의 예제를 다시보자. int var = 0; int <nowiki>*</nowiki> ptr = &var; 이경우 var의 주소를 알기쉽게 0x00000000 이라고 할때 ptr이라는 포인터 변수로 var에 접근하여 값을 1 올리고싶다. 이 경우 (<nowiki>*</nowiki>ptr)++; 를 적으면 <nowiki>*</nowiki>연산자로 ptr이 가진 주소에 접근하여 ++연산자로 1 증감한다 ptr은 var의 주소를 가지고 있었으므로 var의 값은 최종적으로 1이 된다. 근데 엥? ptr++; 로 적어도 되는 각 아니냐? 해서 적었을경우 '''ptr은 값을 가지고 있는게 아니라 주소를 가지고 있기에 주소가 4 증가한다.''' '''4 증가하는 이유는 포인터 변수의 자료형 바이트 크기에 영향을 받기 때문이다 ( int형은 4바이트 ) ''' '''왜 4바이트냐 하면 배열같은 동일 자료형일 경우 다음 원소로 쉽게 접근하기 위해라고 생각하는데 글쎄 나도 진짜는 아몰랑 아는 사람있으면 적어줘''' ㄴ맞다. 포인터가 단순한 주소값 그 이상의 의미를 갖게 되는 핵심 개념이다. ㄴ메모리 주소 자체가 4바이트(32비트)이며 이는 시스템에 따라 달라짐 가감도 이와같이 1을 뺄경우 4씩 내려가는데 n을 더하거나 뺀다고 하면 n<nowiki>*</nowiki>sizeof(int) 이런식으로 깎인다고 생각하면 된다. (int형이 아닐경우 자료형의 크기를 맞게 고쳐주고 곱하면 맞다) 근데 포인터 변수에 <nowiki>*</nowiki> 나 <nowiki> / </nowiki>를 적진 마라 C2296오류 난다 배열을 설명할때 <nowiki>arr[0]</nowiki> 이런식으로 대괄호 안에 숫자를 넣어 해당 원소에 접근했었는데 배열의 이름이 시작주소라는 점을 이용해 포인터를 이용해 각 원소에 접근 할 수 있다. int arr[10]; for(int i=0;i<10;i++) { arr[i] = i; } 가 아닌 int arr[10]; for(int i=0;i<10;i++) { *(arr+i) = i; } 이 가능하단 소리 ㅎㅎ ===구조체=== 구조체는 쉽게 말하면 너가 원하는 새로운 자료형을 만드는 것 과 같다고 보면된다. 예를들어 학생의 이름과 수학,과학,영어 점수를 저장해야 할 때, 단순히 구조체를 모르는 상황에선 이런식으로 변수를 선언할 것이다. <source lang="cpp"> #include <stdio.h> #define STUDENTS 5 int main() { char s_name[STUDENTS][12]; int s_math[STUDENTS]; int s_science[STUDENTS]; int s_english[STUDENTS]; for (int i = 0; i<STUDENTS; i++) { printf("%d번의 학생의 이름은? : ",(i+1)); scanf("%s", s_name[i]); printf("%s 학생의 수학점수는? : ",s_name[i]); scanf("%d", &s_math[i]); printf("%s 학생의 과학점수는? : ",s_name[i]); scanf("%d", &s_science[i]); printf("%s 학생의 영어점수는? : ",s_name[i]); scanf("%d", &s_english[i]); } for (int i = 0; i<STUDENTS; i++) { printf("%s\n", s_name[i]); printf("수학 : %d\n",s_math[i]); printf("과학 : %d\n",s_science[i]); printf("영어 : %d\n",s_english[i]); } return 0; } </source> 구조체를 안쓴상태에서 간단히 짜보았을때 이런 형태를 띌 것이다. 간단한 예제이므로 단순히 입력받고 출력할 뿐인 형태지만 변수 배열 4개를 선언하고 각각에 집어넣는 식이다. 배열첨자를 학생번호로 사용하여 접근할 수 있는데 구조체를 사용하면 좀 더 간결하고 관련된 변수들을 모아 하나의 자료형을 만들어준다 예를들어 위의 예제를 구조체로 만든다면 <source lang="cpp"> #include <stdio.h> #define STUDENTS 5 typedef struct _STUDENT { char name[12]; int math; int science; int english; } Student; int main() { Student students[STUDENTS]; for (int i = 0; i<STUDENTS; i++) { printf("%d번의 학생의 이름은? : ", (i + 1)); scanf("%s", students[i].name); printf("%s 학생의 수학점수는? : ", students[i].name); scanf("%d", &students[i].math); printf("%s 학생의 과학점수는? : ", students[i].name); scanf("%d", &students[i].science); printf("%s 학생의 영어점수는? : ", students[i].name); scanf("%d", &students[i].english); } for (int i = 0; i<STUDENTS; i++) { printf("%s\n", students[i].name); printf("수학 : %d\n", students[i].math); printf("과학 : %d\n", students[i].science); printf("영어 : %d\n", students[i].english); } return 0; } </source> 이렇게 할 수 있다 뭔소린지 몰라도 괜찮다 일단 밑의 설명을 보고 다시 위 예제를 보면된다. 일단 단순히 아까 4개의 배열이 students 라는 하나의 배열로 묶여진 것만 확인하면 된다. 구조체는 여러 변수들을 하나로 모아 새로운 자료형을 만들 수 있는데 그냥 구조체 자료형이라고 해도 되지만 몇몇 사람들은 사용자 정의 자료형이라고도 부른다. 선언 방법은 struct 키워드로 시작하는데 <source lang="cpp"> struct 구조체이름 { 저장할 변수; }; </source> 로 만들 수 있다 이렇게 만든 구조체로 변수 선언을 할때는 struct 구조체이름 변수이름; 원래는 이렇게 선언하고 struct를 붙이는게 싫다면 구조체 선언시 typedef를 쓰면 되는데 VC컴파일러로 cpp확장자 써놓고 걍 struct 무시하고 쓰더라 요즘 표준은 모르겠는데 일단 struct 안붙이면 에러나는 컴파일러도 있으니 확실히 명시해주는편이 좋다. 참고로 typedef란 자료형의 이름을 새로 정의할 수 있다. (새로 정의한다고 기존 이름을 지울 수 있는건 당연히 아니다) typedef 기존자료형이름 새로운이름 이렇게 쓴다면 새로운 자료형 이름으로 변수를 선언할 수 있다. <source lang="cpp"> #include <stdio.h> typedef int Google; int main() { Google fantasy=3; return 0; } </source> int를 Google이라는 이름으로 새로 정의하고 변수를 하나 선언하고 3을 대입했다 이때 Google은 int형의 새로운 이름이니 정수대입이 가능한것이다. define 전처리기 처럼 단순히 치환하는 방식인지는 따져본건 아니니 자세한건 못알려준다. 다만 typedef는 전처리문이 아니므로 치환은 아닐거라고 본다. 그리고 전처리문이 아니며, 함수 블록 내에서도 쓸 수 있으며 순서에 영향을 받는다. <source lang="cpp"> #include <stdio.h> int main() { Google fantasy=3; // 오류! typedef int Google; Google fantasy1 = 2; // 오류 아님 return 0; } </source> 이렇게 문장의 위치에 따라 문제가 생길수도 있다. 근데 보통은 그냥 전처리문 밑에 적는다. 돌아가서 구조체에 대한 설명을 계속하자면 구조체안에 선언한 변수들은 . 으로 접근할 수 있다. 소스로 보자면 <source lang="cpp"> #include <stdio.h> typedef struct _MAN { int hungry; float temperature; } Man; int main() { Man who; who.temperature = 36.5; return 0; } </source> 위의 예제에서는 _MAN이라는 구조체를 만들고 hungry 변수와 temperature 변수를 기술하고 typedef로 Man이라는 이름으로 바꿨다. main함수에서 Man 구조체 자료형의 변수인 who를 선언하고 who 에 . 을 붙여 temperature 라는 아까 기술한 변수이름을 적어 해당 변수에 접근하고 있다. 마찬가지로 who.hungry = 100; 이라고 쓸 수 도 있다. 참고로 구조체는 C++에 가서도 사용이 가능하나 클래스를 쓰기에 그리 많이 사용되진 않는다. ===공용체=== 공용체란 단어의 의미를 생각하면 쉽게 알듯이 다른 자료형의 변수들과 메모리 영역을 공유한다. 뭔 지랄맞은 소리냐 하면, 예를들어 변수 3개가 선언된 구조체는 해당 변수의 자료형을 토대로 3개의 구분된 메모리 공간을 할당한다. 허나 공용체는 3개의 변수를 선언했을 때 제일 큰 자료형의 크기만큼 1개의 메모리 공간할당이 된후 그 메모리 공간을 3개의 변수가 같이 쓴다. 구조체와 쓰는법은 흡사하다. <pre> #include <stdio.h> struct st { char sc; int si; double sd; }; union un { char uc; int ui; double ud; }; int main() { struct st s; union un u; printf("구조체 크기 : %d, 공용체 크기 : %d \n",sizeof(s),sizeof(u)); printf("sc=%p si=%p sd=%p\n",&s.sc,&s.si,&s.sd); printf("uc=%p ui=%p ud=%p\n",&u.uc,&u.ui,&u.ud); } 실행 결과 구조체 크기 : 16, 공용체 크기 : 8 sc=0012FF70 si=0012FF74 sd=0012FF78 uc=0012FF68 ui=0012FF68 ud=0012FF68 </pre> 출처 : 제대로 배우는 C언어 프로그래밍 이해가 쉽도록 예제를 찾아 가져왔다. 구조체와 공용체 변수를 만들고, 크기와 해당 변수들의 주소를 반환하는 것을 출력하는 예제이다. 여기서 눈여겨 볼 점은 구조체 크기는 단순히 생각하면 char, int, double 자료형의 변수 3개가 있으니 크기가 13 아닌가 할 수도 있지만, Struct Alignment라는 Visual C/C++의 내부 기능에 의해 16바이트로 맞춰진 것이니 그리 상관하지 않아도 좋다. (프로세스 성능 저하 방지, 좀더 따지자면 해당 변수에 접근할 때 변수가 메모리상의 2의 n승 단위의 값에 위치한 메모리에 있으면 효율적으로 메모리를 읽을 수 있기에 사용하는 것이다) (사실 좆도 몰라도 보통은 상관 안하더라) 아무튼 공용체의 크기는 8이라는 점이다 이는 제일 큰 자료형의 크기를 가진 double형의 변수가 있기에 8로 만들어진 것이다 그리고 주소도 구조체는 각 변수의 주소가 나눠져 있는데 비해, 공용체는 주소가 나눠지지 않고 하나의 시작주소로 시작한다. 이걸로 암호화하거나 몇몇 알고리즘에 이용할 수 있다. 근데 난 게임개발 하면서 과제를 하려든 대회를 나가든 뭐하든 별로 안써봤다. 내가 병신인지 원래 게임에서 사용빈도가 높지 않은건지는 글쎄~ ===가변인자=== printf 함수를 쓰다보면 요녀석 인자가 여러개 들어가는게 신통방통하네 한 경우가 있거나 나도 이런 함수 만들고 싶다 라고 생각하는 때가 있다. 도움이 될 것같아 이걸 먼저 쓰도록 한다. 이러한 가변인자를 사용하는 함수를 만들려면 ellpsis를 사용해야 한다 ... <- 요거 점3개가 ellpsis(생략 부호) 이다 그리고 stdarg 헤더파일을 포함해줘야 한다. 개귀차나서 간단히 쓰는법만 설명한다 <source lang="cpp"> #include <stdio.h> #include <stdarg.h> // stdarg 헤더파일을 포함해준다. void fun(int a, ...); // 여기서 ellpsis 즉 생략부호를 넣어준다. int main(int argc, char *argv[]){ fun(1, 10); // 인자는 여러개 fun(2, 10,20); // 넣어줘도 된다. 대신 맨 앞 인자는 뒷 인자들의 갯수를 적어줘야한다. return 0; } void fun(int a, ...){ va_list vl; // vl은 ...에 들어간 인자들에 접근하기 위해 만든 변수이다. int i = 0; va_start(vl, a); // va_start는 가변인자를 읽기 시작하는 동시에 몇개까지 읽을 것인가를 설정하는 함수라 보면된다. for (i = 0; i < a;i++) printf("[%d 번 인자] %d\n",i+1, va_arg(vl, int)); // va_arg에 vl과 int 를 넣어줬는데 눈치깠겠지만 int는 읽을 인자의 자료형이다 va_end(vl); // 끝낸다. } </source> 사실 더 설명을 써야하는데 너무 졸리니까 그만씀 ㄴ좆같은 C 쓰지말고 C++17로 갈아타라 <source lang="cpp"> #include <iostream> template<class...T> // 가변 인자 템플릿 void fun(T...arg) { (std::cout << arg, ...); // C++17 의 fold-expression } int main(int argc, char *argv[]){ fun(1, 2, 3, 4, 5, 6, 7, 8); fun(1, 2.3, "asdf"); // 다른 타입들도 가능 return 0; } </source> [[분류:프로그래밍 언어]]
요약:
조무위키에서의 모든 기여는 CC BY-SA 4.0 라이선스로 배포된다는 점을 유의해 주세요(자세한 내용에 대해서는
조무위키:저작권
문서를 읽어주세요). 만약 여기에 동의하지 않는다면 문서를 저장하지 말아 주세요.
또한, 직접 작성했거나 퍼블릭 도메인과 같은 자유 문서에서 가져왔다는 것을 보증해야 합니다.
저작권이 있는 내용을 허가 없이 저장하지 마세요!
취소
편집 도움말
(새 창에서 열림)