void 포인터


void 포인터??

이런 포인터는 처음 들어봤네요~ 분명 void라는 것은 함수 앞에서 반환형이 없을 때 쓰이는 키워드로 아는뎁;

void main은 많이 봤는데...


지금부터 이야기할 주제가 바로 void포인터라고 합니다. 우리는 이제껏 자료형이 정해져있는 포인터 예를 들면


int *a


라는 형태는 봐왔잖아요?


그리고 그 포인터에 주소를 할당하는 방법은 이런 것이죠.


int a=100;

int *b=&a; 


이걸 말로 풀어서 설명한다고 하면

b는 a의 주소를 가지고 있고, b를 통해 a를 참조할 수 있는데 그곳에는 int형 데이터가 있다!

라고 말이에요.


우리는 이 말속에서 힌트를 얻을 수 있습니다. 

그곳에는 int(정수형)형 데이터가 있다 라는 말을 집중해주세요.


위의 int *를 void *로만 바꾸어 써보고 읽어볼게요.


int a=100;

void *b=&a;


b는 a의 주소를 가지고 있고, b를 통해 a를 참조할 수 있는데 그곳에는 void형 데이터가 있다.




void 형 데이터가 있다....(?) void는 "빈공간"이라는 뜻을 내포하고 있는데요. 컴퓨터는 자료형을 모르기 때문에 빈공간처럼 보는 겁니다.

그러기 때문에 앞으로 우리는 이렇게 읽어야 할 겁니다.


b는 a의 주소를 가지고 있고, b를 통해 a를 참조할 수 있는데 그곳에는 알 수 없는 자료형 데이터가 있다.


이 형태 그대로 데이터를 참조하면 컴퓨터는 "아 몰랑!" 합니다.


printf("%d\n", *b);     //오류

 

얼마나 참조해야하는지 알 수 없기 때문입니다. void는 단순히 주소값만을 가지고 있습니다.


우리는 void가 가리키고 있는 데이터의 형태를 알고 있습니다. 우리는 똑똑하니까요(?). 그래서 우리는 *b가 무엇이냐 라고 질문할때 100이라고 대답할 수 있습니다. 우리는 똑똑하기 때문이죠.


하지만 단순히 주소값만!알고 있는 우리 void형은 그 형태가 int형이든 char 형이든 구조체든 문자열을 가리키고 있는 포인터이든 상관없이 단순히 주소값만이요. 그러니 void포인터는 자료형이 무엇이든 간에 주소값만 바라봅니다.


주소만 갖으면 되기 때문에 포인터의 크기(4바이트)만 갖고 있고, 포인터 연산조차 할 수 없습니다.


정말 읽을 수 없는 지 한번 코드로 살펴봅시다.

#include <stdio.h>

int main() {
	int a = 10;
	void *b = &a;
	printf("%d\n", *b);
}
	


실행시킬 수 조차없이 빨간줄로 


Error:Expression must be a pointer to a complete object type


라는 에러를 보게 됩니다. 

완전한 형태의 포인터로 바뀌어야한다 라고요.




우리는 컴퓨터에게 "너가 가리키고 있는 데이터 자료형은 int형이야" 라고 명확히 알려주어야합니다. "그러니까 넌 묻지도 따지지도 말고 4바이트만 읽으면 돼!" 라고요.

어떻게 알려줄까요??


우리는 형변환을 알고 있습니다. 그걸 사용하는 것이죠.


printf("%d\n", *(int*)b); 


이렇게 하면 void*는 int형을 읽을 수 있는 int*로 변환되게 됩니다.

정말 빨간 줄 없이 int형 데이터를 읽을 수 있는 지 코드로 볼까요?

#include <stdio.h>

int main() {
	int a = 10;
	void *b = &a;
	printf("%d\n", *(int*)b);
}


빨간 밑줄은 없어졌군요~ 





실행도 정상적으로 되는 것을 확인할 수 있고, 10을 정확히 읽는 것도 확인할 수 있습니다.


char형은 어떻게 변환할까요? 그것도 간단합니다. 바로 char*로만 바꾸어주면 됩니다.

#include <stdio.h>

int main() {
	char a= 'c';
	void *b = &a;
	printf("%c\n", *(char*)b);
}





네, 'c'라는 문자를 제대로 읽을 수 있군요.




문자열 역시 다르지 않습니다.

#include <stdio.h>

int main() {
	char *str= "문자열";
	void *b = str;
	printf("%s\n", (char*)b);
}

str자체가 문자열을 가리키고 있는 포인터이기 때문에 str변수 앞에 &(amp)를 붙여주지 않습니다. 이해하죠?




문자열도 잘 읽어오는군요.



이렇게 void포인터는 만능입니다. 어떤 자료형이건 바로 참조할 수있죠. 알맞은 자료형으로만 변경한다면 말이죠.


우리는 void포인터를 동적할당할때 유용하게 사용합니다. 동적할당에 사용하는 malloc과 같은 함수들이 void*로 반환하기 때문입니다.

malloc은 특정한 size의 크기로 메모리를 할당하고나서 우리들에게 알맞게 변환해서 써라 라는 의미로 void*를 내뱉게 됩니다.


어차피 나중에 배울 malloc함수의 원형 한번 볼까요?


void *malloc(size_t size)


앞서 말한대로 void*를 토하고 있습니다. 너네가 맘대로 바꾸라고 말이죠.


한번 보세요. 아~ 이런 변태같은 함수도 있구나~ 라고 기억하시기 바랍니다.



이것으로 void포인터에 대해 설명해보았습니다. 감사합니다.

반응형
블로그 이미지

REAKWON

와나진짜

,

Fragment


프래그먼트(Fragment)는 안드로이드 3.0부터 지원되었다고 하는데요. 프래그먼트란 무엇일까요..?

간단하게 말하자면 여러가지 화면을 유연하게 지원하기 위한 것이라고 할 수 있겠습니다. 


어플리케이션의 단위는 Activity이고, 그 Activity도 여러 Fragment로 구성될 수가 있습니다. 


예를 들면 카카오톡의 친구목록, 채팅목록 같은 화면은 프래그먼트이고 이 두 프래그먼트는 같은 Activity에 있는 것과 같아요. 맞나? 제가 카톡 개발자가 아니어서


Activity에 Fragment가 포함되는 것이기 때문에 Fragment간의 직접적인 통신은 막고 있습니다. 반드시 Activity를 통해서 Fragment간의 통신을 해야합니다.






프래그먼트도 Activity와 같이 여러 단계의 주기를 거치게 됩니다. 그 중 눈여겨 볼 메소드는 onCreateonCreateView,그리고 onPause랍니다.




onCreate()

안드로이드 시스템은 프래그먼트가 실행될때 이 메소드를 호출하게 됩니다. 컴포넌트를 초기화하고 싶다면 이곳에서 하세요. 매개변수인 Bundle 객체를 통해서 Activity에서 데이터를 전달받을 수도 있습니다.


onCreateView()

이 메소드는 UI를 생성하는 메소드입니다. UI를 제공하지 않는다면 null을 반한 할 거구요. 대개 전달받은 LayoutInflater로 inflate메소드를 통하여 View를 반환합니다.


프래그먼트는 Activity에서 FragmentManager 객체를 통해서 관리되어 집니다.  MainActivity에서 getSupportFragmentManager메소드를 통해 객체를 생성할 수 있습니다.




프래그먼트를 사용한 어플리케이션 예제

이제 프래그먼트를 사용해서 간단한 어플을 만들어볼텐데요, 우선 Empty Activity로 프로젝트를 구성해주세요.


두 개의 프래그먼트를 구성 할 두 개의 레이아웃 파일을 만들어 주겠습니다.


fragment1.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffee11">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Fragment1"
android:textSize="30dp"/>
</RelativeLayout>

fragment2.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#22ff33">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="Fragment2"
android:textSize="30dp"/>
</RelativeLayout>


두개의 레이아웃 파일은 별거 없습니다. 그냥 현재 어떤 프래그먼트인가를 표시하는 텍스트뷰를 하나 포함하고 있고, 더 구별하기 쉽도록 배경색을 다르게 지정했습니다.


우리는 이 레이아웃 파일을 프래그먼트와 연결해야합니다. 바로 UI를 구성해야하는 것이죠. 

그 메소드가 onCreateView라는 메소드라고 했습니다.


Fragment1.java


import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment1 extends Fragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment1,container,false);
}
}


Fragment2.java

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class Fragment2 extends Fragment {

@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment2,container,false);
}

}


Fragment1 클래스는 fragment1 레이아웃을 UI로 삼고 있고, Fragment2 클래스는 fragment2 레이아웃을 UI로 가지고 있습니다.


이것을 View 객체로 가져오기 위해서는 LayoutInflater로 inflate메소드를 통해 가져올 수 있습니다. 


이제 여기까지 프래그먼트와 레이아웃간의 연결을 끝났습니다. 이제 프래그먼트를 포함할 activity_main.xml을 구성해야합니다.


activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="프래그먼트1"
android:textSize="25dp"
android:onClick="changeFragment"
android:id="@+id/fragment1"/>

<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="프래그먼트2"
android:textSize="25dp"
android:onClick="changeFragment"
android:id="@+id/fragment2"/>

</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/container"
android:orientation="vertical"/>
</LinearLayout>

id가 container인 LinearLayout이 보이시나요? 여기가 프래그먼트를 포함하는 container 역할을 하게 됩니다.


각 버튼은 changeFragment 메소드를 통해서 프래그먼트를 바꾸는 역할을 하게 됩니다.




MainActivity.java

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

FragmentManager fm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

Fragment fragment=new Fragment1();
fm=getSupportFragmentManager();
fm.beginTransaction().add(R.id.container,fragment).commit();
}

public void changeFragment(View view){
int id=view.getId();
Fragment fragment=null;
switch(id){
case R.id.fragment1:
fragment=new Fragment1();
break;
case R.id.fragment2:
fragment=new Fragment2();
break;
}

fm.beginTransaction().replace(R.id.container,fragment).commit();
}
}

Activity는 FragmentManager를 통해서 Fragment를 관리하게 됩니다. FragmentManager를 통해서 FragmentTransaction객체를 가져옵니다. 


FragmentTransaction객체는 프래그먼트를 추가, 교환, 제거할 수 있습니다. replace메소드로 container의 프래그먼트를 교체하고 있는 걸 확인할 수 있습니다. 마지막에는 반드시 commit메소드를 호출해야 replace가 반영됩니다.




결과


프래그먼트1을 버튼을 누른 경우(또는 앱이 실행된 첫 화면)




프래그먼트2를 버튼을 누른 경우




프래그먼트를 사용해서 어플리케이션을 만들어보았습니다. 처음에만 조금 헷갈리지 나중에는 익숙해져... 요... 

반응형
블로그 이미지

REAKWON

와나진짜

,

함수포인터

우리는 이제까지 어떤 변수를 가리키는 포인터, 배열을 가리키는 포인터를 사용해보았어요. 물론 이정도를 알아도 상당히 훌륭하다고 생각합니다.

그렇지만 변수뿐만 아니라 함수도 포인터로 가리킬 수 있다는 것을 알게 된다면 조금 더 간지나는 프로그래밍을 시도해 볼 수 있겠습니다. 이제부터 설명할 것이 무엇이냐, 그 이름하여 함수포인터랍니다. 

함수포인터라... 그냥 포인터도 극혐인데 말이죠

함수포인터란 함수를 가리킬 수 있는 포인터를 의미합니다. 아니, 근데 그러면 주소를 알아야하는데, 함수에도 주소가 있나? 네, 있습니다. 함수가 주소를 가지고 있다구요? 제말이 구라가 아님을 보여드리겠습니다.

#include <stdio.h>
int sum(int a, int b) { 
        return a + b;
} 

int main() { 
        printf("함수 sum의 주소 : %p\n", &sum);
}


이 코드의 결과는 어떻게 될까요? 우리는 변수명 앞에 &(amp)를 붙여 변수의 주소를 알게 됩니다. 그렇다면 &함수명은 무엇을 의미하게 될까요? 아무리 눈치없어도 진짜 이정도면 눈치채야지~

신기하네요. 16진수로 어떤 수가 나오네요. 눈치채셨겠지만 함수의 주소를 의미하게 됩니다. 바늘가는데 실간다는데 주소가 있으면 포인터가 있겠죠. 그렇다면 함수포인터를 선언하는 방법을 알아보도록 하겠습니다. 간단합니다.

①int ②(*ptrSum)③(int a,int b)

 

일반 포인터와 마찬가지로 주소를 가리킬때는 *을 사용해서 포인터라고 알려줍니다.

①은 함수의 반환형을 의미합니다.

②는 함수포인터의 이름을 의미합니다. (변수명과 같이 임의로 정해줍니다.)

③은 매개변수를 의미합니다. 매개변수가 없을 때는 빈 괄호나 void를 사용합니다.

 

네, 위 세가지만 지켜주면 됩니다.

그러니까 ptrSum이라는 함수포인터는 반환형이 int형이고 매개변수 2개를 갖는데, 둘 다 int형 매개변수인 함수포인터가 되겠습니다.

그렇다면 다음과 같은 함수포인터는 무엇을 의미할까요?

void (*ptrFunc)()

함수명이 ptrFunc인데, 반환값이 없고(void), 매개변수도 없는 함수포인터를 의미합니다. 이제 응용가능하시겠죠? 함수포인터에서 반환형과 매개변수가 일치하는 함수만 함수포인터에 할당이 가능합니다. 기억하세요.

함수포인터를 선언하는 방법은 알게 되었으니, 다음 코드를 통해서 함수포인터의 값이 바로 그 함수의 주소인지 확인도 해보고, 사용도 해봅시다.

#include <stdio.h>
int sum(int a, int b) { 
        return a + b;
}

int main() {
        int(*ptrSum)(int a,int b); 
        ptrSum = sum; 
        printf("sum의 주소: %p\n", &sum); //&sum은 sum과 같음 
        printf("ptrSum의 값: %p\n", ptrSum);
        printf("ptrSum의 주소: %p\n", &ptrSum);
        printf("ptrSum(%d, %d) = %d\n", 3, 4, ptrSum(3, 4));
}

 

우리는 sum이라는 함수의 주소가 0x00081023번지라는 것과 ptrSum이 0x00081023을 가리키는 것을 확인할 수 있습니다(실제 sum도 함수의 주소, &sum도 함수의 주소입니다).

그리고 그 함수포인터의 주소는 0x0026FB04번지네요.

함수포인터를 통해서 호출(ptrSum(3,4)) 확인해볼 수 있군요. 

(코드에서 나오지는 않았지만 함수포인터의 크기는 역시 4바이트입니다. 포인터이기 때문에요.)

조금 더 쉽게 그림을 통해서 보도록할게요.

 

포인터를 설명할때 아주 많이 사용하는 그림이죠? 지겹죠?

0x0026FB04번지에 있는 ptrSum이라는 함수포인터는 0x00081023번지에 있는 함수 sum의 시작주소를 가리키게 됩니다.

sum은 함수이기 때문에 시작주소를 갖고 있고, 그 주소를 기점으로 매개변수를 할당, 그리고 함수 내부의 변수들을 스택에 따라 쌓아올립니다. 그러니까 함수의 시작주소를 통해 변수의 주소를 알게 됩니다. 함수의 시작주소는 중요하단 거죠. (더 자세하게 함수호출과정을 아는 것도 도움이 됩니다. 구글형님께 물어보세요.)

그런 sum의 시작주소를 ptrSum은 알고 있기 때문에 sum함수를 호출할 수 있는 겁니다.

아니, 그렇다면 sum함수만 호출하면 되지, 왜 굳이 포인터를 써서 호출하는 거야?

저도 무척이나 이런 생각을 하고 하고 또 하면서 그냥 함수포인터를 흘려 넘겼었죠.

근데 쓰임새는 꽤나 많습니다. 예를 들면 함수 자체를 매개변수로 받고 싶을때가 있을 겁니다.

프로젝트를 진행할때 한 사람만이 진행한다면 물론 상관이 없지만, 여러 사람과 협업을 해야하거나, 라이브러리를 제공할때, 함수에 함수자체를 매개변수로 받아야할 때가 있습니다. 누가 어떤 함수를 필요로 할지 모르니, 어떤 형식으로 함수를 정의해서 매개변수로 전달하게 되면 그 함수를 내부에서는 호출하게 되는 식으로 말이죠.

void func(void (*ptr)()) {
...      ptr();       ... 
}

그리고 또 구조체에서 멤버함수를 커스터마이징할때도 사용할 수가 있습니다. 

struct animal{     
...       void (*walk)();       ... 
}

 

위에서 walk라는 멤버함수를 우리가 원하는 대로 기능을 정의해줄 수 있습니다. 물론, 함수포인터의 형식과 맞는다면 말이죠. 이런 방식은 자바에서 interface라고 하여 메소드를 구현하는 방식과 비슷해보입니다. 함수포인터를 더 연습해보고 개념을 확실히 이해하세요.

이것으로 함수포인터에 대해 알아보았습니다.

반응형
블로그 이미지

REAKWON

와나진짜

,