รับทําเว็บไซต์ รับทําseo
 
รับทําเว็บไซต์ รับทําseo
บทความที่น่าสนใจ

บทความ ที่น่าสนใจ

Objective-C : Function , Pointer (ตอนที่2)

    Functions with Variable Argument Lists

    ในบางครั้ง ฟังก์ชันที่เราต้องการจะเขียนไม่มีรูปแบบพารามิเตอร์ที่ตายตัว อาจจะมี 2-3 พารามิเตอร์หรือเราไม่ทราบจำนวนที่แน่ชัดของพารามิเตอร์ ยกตัวอย่างเช่นฟังก์ชัน NSLog จะเห็นว่าจำนวนของพารามิเตอร์ไม่แน่นอน

     

            NSLog(@"%d" , x  );
            NSLog(@"%d %d %d" , x , y ,z );

     

    การแก้ปัญหาวิธีการแรกที่สามารถทำได้ก็คือใช้ array เป็นพารามิเตอร์ นอกจากวิธีนี้ยังมีอีกหนึ่งวิธีนั่นก็คือการใช้ variable list  การประกาศให้ฟังก์ชันรับพารามิเตอร์แบบ variable list สามารถทำได้ด้วยการประกาศพารามิเตอร์ลำดับรองสุดท้ายให้เป็น int และตัวสุดท้ายให้แทนด้วย …  ( 3 จุด ) ดังเช่นตัวอย่าง

     

    int printList(int argc, ...);
    int printList(int param, int argc, ...);

     

    เราจะเขียนโปรแกรมหาค่าเฉลี่ยจากพารามิเตอร์ที่รับเข้ามา ซึ่งมีโค้ดดังนี้

     

    Program 6.5
    main.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
    #import <Foundation/Foundation.h>
     
    double average ( int fisrtNum, ... )
    {
        va_list arguments;
        double value = 0;
        double sum = fisrtNum;
        int counter = 1;
     
        // init arguments to store all values after fisrtNum
        va_start ( arguments, fisrtNum );
     
        while (true)
        {
            value = va_arg (arguments, int);
            if( value != -1)
                sum += value;
            else
                break;
     
            counter++;
        }
     
        // cleans up
        va_end ( arguments );                  
     
        return sum / counter;
    }
     
    int main(int argc, const char * argv[])
    {
     
        @autoreleasepool {    
     
            NSLog(@"Average %f ", (double)average(5, 4 , 6 , 7 , 9 , -1));
     
        }
        return 0;
    }

     

    Program 6.5 Output
    Average 6.20


    จากโค้ดของฟังก์ชัน average เราเริ่มต้นด้วยการประกาศตัวแปรต่างๆพร้อมกับตัวแปร arguments ซึ่งเป็น va_list ( variable list ) ต่อมาบรรทัดที่ 11 เป็นการเก็บค่าพารามิเตอร์หลังจาก firstNum ที่เหลือทั้งหมดมาไว้ที่ตัวแปร arguments นั่นคือค่า 4, 6 , 7 , 9 และ -1 จากนั้นโปรแกรมก็จะเริ่มเข้าสู่ while loop ซึ่งในลูปนี้ เราจะเริ่มนำค่าพารามิเตอร์ที่ได้เก็บไว้ยังตัวแปร arguments ออกมาทีละค่าด้วยฟังก์ชัน va_arg โดยต้องระบุด้วยว่าพารามิเตอร์นั้นเป็นชนิดใด จากตัวอย่างของโปรแกรมค่าที่รับเข้ามาเป็น int ทั้งหมด ดังนั้นเราจึงกำหนดให้เป็น int เช่นกัน (ในกรณีที่พารามิเตอร์เป็นหลายชนิด เราต้องกำหนดชนิดของพารามิเตอร์ให้ถูกต้องด้วย) เนื่องจากเราไม่สามารถรู้ได้ว่าฟังชั่นมีพารามิเตอร์จำนวนเท่าใด ดังนั้นเราจึงกำหนดว่า เมื่อไหร่ก็ตามที่พารามิเตอร์มีค่าเป็น -1 ก็ให้หยุดการทำงานของลูปนั่นเอง

     

    C Standard Library

    โดยปกติแล้วไม่ว่าจะเขียนโปรแกรมด้วยภาษาใดๆก็ตาม มักจะมีฟังก์ชันมาตรฐานให้ใช้งานเสมอ ในภาษา C ก็มีฟังก์ชันต่างๆมากมายให้ใช้เช่นกัน เรียกว่า C Standard Library ซึ่งมีฟังก์ชันต่างๆมากมาย ไม่สามารถอธิบายได้หมด ฟังก์ชันที่ควรรู้เบื้องต้นได้แก่ ฟังก์ชันและมาโครที่เกี่ยวกับคณิตศาสตร์ เช่น


    t

     

    Program 6.6

    main.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    
    #import <Foundation/Foundation.h>
     
    int main(int argc, const char * argv[])
    {    
        @autoreleasepool {
     
            int degree = 30;
            double radian = degree * 0.0174532925;
     
            NSLog(@"cos 30 degree = %f" ,cos(radian) );
            NSLog(@"sin 30 degree = %f" ,sin(radian) );
            NSLog(@"tan 30 degree = %f" ,tan(radian) );
            NSLog(@"exp(10) = %f" ,exp(10) );
            NSLog(@"log(8) = %f" ,log(8) );
            NSLog(@"pow(10,2) = %f" ,pow(10,2) );
            NSLog(@"sqrt(10) = %f" ,sqrt(10) );
            NSLog(@"ceil(6.3) = %f" ,ceil(6.3) );
            NSLog(@"floor(6.3) = %f" ,floor(6.3) );
            NSLog(@"fabs(-6.3) = %f" ,fabs(-6.3) );
            NSLog(@"INT_MAX = %d",INT_MAX);
            NSLog(@"INT_MIN = %d",INT_MIN);
            NSLog(@"UINT_MAX = %ud",UINT_MAX);
            NSLog(@"arc4random = %d",arc4random());
     
        }
        return 0;
    }

     

    Program 6.6 Output
    cos 30 degree = 0.866025
    sin 30 degree = 0.500000
    tan 30 degree = 0.577350
    exp(10) = 20.085537
    log(8) = 2.079442
    pow(10,2) = 100.000000
    sqrt(10) = 3.162278
    ceil(6.3) = 7.000000
    floor(6.3) = 6.000000
    fabs(-6.3) = 6.300000
    INT_MAX = 2147483647
    INT_MIN = -2147483648
    UINT_MAX = 4294967295d
    arc4random = 445927451
    เราจะหยุดเรื่องของฟังก์ชันเอาไว้เท่านี้ก่อน เพราะยังมีเรื่องที่ต้องทำความเข้าใจเพิ่มเติมนั่นก็คือ pointer

     

    Pointer

    เราได้พูดถึงเรื่องโครงสร้างของ หน่วยความจำ และการบริหารหน่วยความจำ ถ้าจะไม่กล่าวถึงเรื่อง pointer ก็คงจะไม่ครบสมบูรณ์ ในภาษา C มีตัวแปรที่สามารถเก็บตำแหน่งของหน่วยจำได้โดยเรียกว่าตัวแปรแบบนี้ว่า Pointer ซึ่งหมายถึงตัวชี้ตำแหน่งหน่วยความจำ

     

    Pointer definition and initialization

    การประกาศตัวแปรแบบ pointer นี้สามารถทำได้โดยการเพิ่มเครื่องหมาย * (ดอกจันทร์) ด้านหน้าของชื่อตัวแปร ดังเช่นตัวอย่าง

     

            int *ptrX;
            double *y;

     

    เริ่มคุ้นๆแล้วใช่ไหมว่าเคยเห็นการประกาศตัวแปรรูปแบบนี้ที่ไหนมาก่อน ถูกต้องแล้ว เราได้ประกาศใช้ตัวแปรแบบ pointer มาตั้งแต่บทแรกแล้ว นั่นก็คือ

     

            NSAutoreleasePool *pool;

     

    รวมไปถึงการประกาศ instance ของ object ต่างๆ ก็ประกาศให้เป็นตัวแปรแบบ pointer ทั้งสิ้น

     

            Demo *demo

     

    เมื่อเราประกาศตัวแปรแบบ pointer นี้แล้วสิ่งที่ต้องทำต่อมาก็คือการกำหนดค่า memory address ให้กับตัวแปร แต่เราไม่สามารถจะกำหนด memory address แบบตรงๆได้ ต้องใช้เครื่องหมาย & ( address operator ) ดังเช่นตัวอย่าง

     

            int x = 4;
            int *xPtr = &x;

     

    โดยปกติแล้วเมื่อประกาศตัวแปรเช่น int หรือ double ตัวแปรเหล่านี้จะเก็บค่าโดยตรงหรือเรียกว่า directly references a value เป็นต้นว่าประกาศตัวแปร int x = 4 ก็คือการกำหนด x ให้เท่ากับค่า 4 โดยตรง แต่การกำหนดค่าให้กับตัวแปรแบบ pointer จะเป็นแบบ indirectly refrence a value คือไม่ได้ให้ค่ากับตัวแปรนั้นตรงๆแต่เป็นอ้างอิงไปยังที่อื่น เช่น xPtr = &x หมายถึงการกำหนดให้ xPrt เก็บค่าตำแหน่งของ x

     

    t2

     

    สมมติว่าตัวแปร x อยู่บนตำแหน่ง 0x0A0 ส่วน xPrt อยู่ตำแหน่ง 0x0A8 ดังตาราง จะเห็นว่าค่าที่ตัวแปร x เก็บไว้คือเลข 4 และตัวแปร xPrt เก็บค่า 0x0A0 ซึ่งเป็นตำแหน่งของ x นั่นเอง หรือพูดอีกอย่างว่า xPrt ชี้ไปยังตำแหน่งของตัวแปร x และการจะให้ pointer ชี้ไปยังตัวแปรใดๆ เราจำเป็นต้องประกาศ pointer ให้เป็นชนิดเดียวกับตัวแปรนั้น เช่น สมมติว่าตัวแปรนั้นเป็น int ก็ต้องประกาศ pointer ให้เป็น int เช่นกัน

     

    จากรูปเมื่อ xPrt ชี้ไปยังตำแหน่ง x ได้นั่นก็หมายถึงว่า ตัวแปร xPrt เปรียบเสมือนหนทางไปสู่ข้อมูลของตัวแปร x อีกทางหนึ่งได้เช่นกัน เราสามารถที่จะเข้าใช้ข้อมูลในตำแหน่งนั้นด้วยใช้เครื่องหมาย * นำหน้า pointer เพื่อเป็นการ derefence pointer ดังเช่นตัวอย่างโปรแกรมง่ายๆต่อไปนี้

     

    Program 6.7
    main.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    #import <Foundation/Foundation.h>
     
    int main(int argc, const char * argv[])
    {
     
        @autoreleasepool {
     
            int x = 10;
            int *xPtr;
            xPtr = &x;
     
            NSLog(@"Address x = %p", &x);
            NSLog(@"Value x = %d", x);
     
            NSLog(@"Address xPtr = %p", &xPtr);
            NSLog(@"Value xPtr = %p", xPtr);
     
            *xPtr = 20;
     
            NSLog(@"New Value x = %d", x);        
     
        }
        return 0;
    }

     

    Program 6.6 Output

    Address x = 0x7fff5fbff89c
    Value x = 10
    Address xPtr = 0x7fff5fbff890
    Value xPtr = 0x7fff5fbff89c
    New Value x = 20

     

    โปรแกรมได้กำหนดค่า x เท่ากับ 10 และ xPtr ให้ชี้ไปยังตำแหน่ง x จากนั้นก็แสดงค่า memory adress และค่าที่เก็บไว้ทั้งตัวแปร x และ xPrt ซึ่งจะเห็นว่าค่า Value xPrt นั้นเท่ากับค่า Address x และเมื่อโปรแกรมทำงานมาถึงบรรทัดที่ 18 เราได้ใช้เครื่องหมาย * เพื่อเป็นการ derefence pointer หรือการเข้าถึงค่าที่ xPrt ได้ชี้ไป นั่นก็ตำแหน่งของตัวแปร x หรืออาจจะพูดง่ายๆว่าเราได้กำหนดตัวแปร x ให้มีค่าเท่ากับ 20 นั่นเอง

     

    Function and Pointer

    จากโปรแกรม 6.4 เมื่อฟังก์ชัน factorial ถูกเรียกใช้งาน ค่าตัวแปรที่จากผู้เรียกฟังก์ชัน ( ตัวแปร num ใน main ) จะถูกคัดลอกไปยังตัวแปรที่เป็นพารามิเตอร์ของฟังก์ชัน ( ตัวแปร n ของฟังก์ชัน factorial ) นั่นหมาย num และ n เป็นคนละตัวแปรกัน ถ้าเราแก้ไขค่า n ก็จะไม่เกี่ยวกับค่า num เลย ตัวอย่างโปรแกรมง่ายๆต่อไปนี้ แสดงให้เห็นว่าค่า x ทั้งสองเป็นคนละตัวแปรกัน

     

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    #import <Foundation/Foundation.h>
     
    void mul ( int x)
    {
        x *= 2;
        NSLog(@"x mul: %d",x);
    }
     
    int main(int argc, const char * argv[])
    {
     
        @autoreleasepool {
     
            int x = 10;
            mul(x);
     
            NSLog(@"x main: %d",x);
     
        }
        return 0;
    }

     

    Program Output

    x mul: 20
    x main: 10

     

    ในกรณีที่เราอยากจะให้ฟังก์ชันสามารถเปลี่ยนแปลงค่าตัวแปรที่รับมาเป็น พารามิเตอร์ เช่นสมมติว่า ต้องเขียน function สลับค่าระหว่างตัวแปร 2 ตัวแปร ย่อมไม่สามารถทำได้ เพราะถ้าหากจะใช้การ return จากฟังก์ชันก็ทำได้เพียงแค่ 1 ค่าเท่านั่น จากที่เราได้เรียนรู้ในหัวข้อที่ผ่านมาว่า pointer คือตัวแปรที่ชี้ไปยังตำแหน่งของหน่วยความจำ เราสามารถที่จะประยุกต์ pointer กับฟังก์ชันเพื่อที่จะเข้าถึงตัวแปรที่เป็นพารามิเตอร์ได้โดยตรง เราจะเขียนฟังก์ชันสลับค่าระหว่างตัวแปรด้วยพ้อยเตอร์ เช่นโปรแกรมต่อไปนี้

     

    Program 6.8
    main.m

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    #import <Foundation/Foundation.h>
     
    void swap ( int *x , int *y)
    {
        int temp = *x;
        *x = *y;
        *y = temp;
    }
     
    int main(int argc, const char * argv[])
    {
     
        @autoreleasepool {
     
            int a = 10;
            int b = 30;
     
            NSLog(@"a = %d , b = %d", a, b);
     
            swap(&a, &b);
     
            NSLog(@"a = %d , b = %d", a, b);
     
        }
        return 0;
    }

     

    Program 6.8 Output

    a = 10 , b = 20
    a = 20 , b = 10

     

    จากโปรแกรม 6.8 เราได้ส่งค่า &a และ &b ซึ่งหมายถึง memory address ของ a และ b ให้กับฟังก์ชัน swap ซึ่งรับพารามิเตอร์เป็น pointer ดังนั้นแล้วฟังก์ชันก็สามารถที่จะเข้าถึงตัวแปร a และ b ใน main ได้โดยตรง หลังจากฟังก์ชันสลับค่าระหว่างตัวแปรแล้ว เมื่อใช้ NSLog เพื่อแสดงผลลัพธ์ก็จะเห็นว่าค่า a และ b ได้สลับกัน ในบทต่อไปเมธอดของคลาสที่เราจะได้เขียนก็จะมีพารามิเตอร์เป็น pointer ลักษณะเช่นเดียวกัน หรืออย่างเมธอดของคลาสในบทที่ผ่านมาเช่น

     

    -(NSObject*) sampleObject
    {
        NSObject* sample = [[NSObject alloc] init];
        [sample release];
        return sample;
    }

     

    ก็จะเห็นว่าฟังก์ชัน sampleObject ส่งค่ากลับมาเป็น pointer หรือ NSObject* และค่าที่ส่งกลับเป็นตำแหน่งของอ็อบเจ็กที่ได้สร้างขึ้นมาในฟังก์ชั่นนั่น เอง

     

    Pointer and Object

    มาถึงตรงนี้อาจจะสงสัยว่าเมื่อเราประกาศ instance ของ class อย่าง

     

    แต่เรากลับไม่เห็นเครื่องหมาย & นั่นก็เพราะว่า เมธอด alloc นั้นจะส่งค่ากลับมาเป็นตำแหน่งหน่วยความจำที่ได้จากการสร้าง อ็อบเจ็กอยู่แล้ว ดังนั้นจึงไม่จำเป็นต้องใช้ & นั่นเองนำหน้านั่นเอง

     

    เมื่อเขียนแผนภาพแสดงข้อมูลของตัวแปรจะเห็นว่าในความเป็นจริงแล้ว ตัวแปร manee นั้นไม่ได้เก็บค่า Student โดยตรง แต่เป็นตัวชี้ไปยังตำแหน่งหน่วยความจำที่เก็บค่า Student อีกที และหากเราสังเกตดูจะพบว่าเมธอดและฟังก์ชันที่มีพารามิเตอร์เป็นอ็อบเจ็กนั้น ประกาศเป็นแบบ pointer ทั้งหมดนั่นก็เพราะว่า เมื่อโปรแกรมเรียกฟังก์ชันหรือเมธอด จะสร้างสิ่งที่เรียกว่า Frame ซึ่งเป็นข้อมูลที่ประกอบไปด้วย ตำแหน่งที่ต้องส่งค่ากลับ ตำแหน่งของโปรแกรมที่ต้องกลับไปเมื่อทำงานเสร็จ พารามิเตอร์ต่างๆที่ฟังก์ชันต้องการใช้งานและค่าอื่นๆ หลังจากสร้างเฟรมเสร็จก็จะถูกใส่เข้ามายัง Stack Frame เพื่อทำการประมวลผล และ Stack Frame นี้มีการทำงานในลักษณะเข้าก่อนออกทีหลัง เช่น factorial(5) ถูกเรียกก่อน แต่จะทำงานเสร็จทีหลัง factorial(0) ดังรูป

     

    เนื่องจาก Stack Frame นั้นมีจำกัด สิ่งที่เกิดขึ้นตามมาถ้าหากเราเขียนโปรแกรมได้ไม่ดีนั่นก็คือ Stack Frame เต็มหรือเรียกว่า Stack Overflow นั่นเอง นอกจากนี้ในการดีบักโปรแกรมคำที่จะคุ้นหูกันมากก็คือ call stack, stack trace ซึ่งหมายถึงการติดตามว่าตอนนี้ Stack Frame มีฟังก์ชันหรือเมธอดได้ทำงานนั่นเอง


    ในการสร้างเฟรมนั้นค่าของตัวแปรที่เป็นพารามิเตอร์จะถูกคัดลอกไปยังตัวแปรใน เฟรม ซึ่งนั่นหมายความว่ายิ่งมีพารามิเตอร์มากและขนาดใหญ่เท่าไหร่ Stack Frame ก็จะใหญ่ขึ้นไปด้วย ขนาดของ Stack Frame ส่งผลกระทบโดยตรงกับประสิทธิภาพของโปรแกรม ถึงแม้เราจะมี ram ขนาดหลาย GB. แต่ Stack Frame มันมีจำกัด นั่นก็เพราะว่าอันที่จริงแล้วหน่วยความจำในระบบคอมพิวเตอร์นั้นถูกแบ่งออก เป็นหลายระดับ โดยทั่วๆไปจะมีทั้งหมด 3 ระดับนั่นก็คือ register ซึ่งเป็นหน่วยความจำที่มาพร้อมกับ cpu ทำงานด้วยความเร็วเท่ากับ cpu แต่มีขนาดเล็กมากๆอย่าง cpu x86-64 bit ก็มีรีจิสเตอร์ 16 ตัวเท่านั้น ลำดับต่อมาคือ cpu cache เป็นหน่วยความจำที่อยู่ภายใน cpu เช่นเดียวกัน ทำงานช้ากว่า register  แต่มีขนาดใหญ่กว่า register อย่าง intel i7 ก็มีขนาดประมาณ 4-8 MB. และสุดท้ายคือ ram ซึ่งเป็นหน่วยความจำภายนอก cpu มีขนาดใหญ่ๆมาก และทำงานได้ช้ากว่า cache ถ้าเทียบความเร็วระหว่าง register , cache , ram ให้พอเห็นภาพง่ายๆก็คือ สมมติว่า register ทำงานเสร็จภายใน 1 วินาที แคชจะใช้เวลา 100 วินาที ส่วนแรมจะใช้เวลา 1000  วินาที ดังนั้นแล้วถ้าหากเราอยากจะให้โปรแกรมทำงานได้เร็วๆก็ต้องเอาโหลดโปรแกรมไป ไว้ที่ register หรือ cache แต่ก็อย่างที่ทราบกันไปว่า cache มีขนาดจำกัดมากๆ จะเอาทั้งโปรแกรมไปไว้ก็ไม่ได้ ระบบคอมพิวเตอร์จึงออกแบบให้บางส่วนของโปรแกรมเช่น Stack Frame ไปไว้ในหน่วยความจำภายใน cpu และเพื่อลดขนาดของ stack frame เราจึงต้องเอาตัวแปรที่เป็นตัวชี้ไปยังตำแหน่งหน่วยความจำเช่น ram ไปไว้ที่ frame เพื่อที่ว่าเวลาที่จำเป็นต้องใช้ object นั้นจริงๆถึงจะไปโหลดจาก ram อีกครั้ง แบบนี้ประสิทธิภาพโดยรวมของโปรแกรมก็จะทำงานได้เร็วขึ้นนั่นเอง

บทความที่น่าสนใจ

บทความ ล่าสุด

บทความ ความรู้ด้านไอที, คอมพิวเตอร์ Techonlogy, Gadget, ความรู้เกี่ยวกับคอมพิวเตอร์ กับทาง SoftMelt.com