ผลต่างระหว่างรุ่นของ "การโปรแกรมภาษาซี สำหรับโปรแกรมเมอร์จาวาและซีชาร์ป"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
แถว 125: แถว 125:
 
== การคอมไพล์และลิงก์โปรแกรมที่อยู่ในหลายแฟ้ม ==
 
== การคอมไพล์และลิงก์โปรแกรมที่อยู่ในหลายแฟ้ม ==
 
วิธีการคลาสสิกในการจัดการงานที่ใหญ่ ก็คือการแบ่งงานดังกล่าวเป็นส่วนย่อย ๆ  การพัฒนาโปรแกรมก็เช่นเดียวกัน ถ้าโปรแกรมที่เราต้องการพัฒนามีขนาดใหญ่ การแบ่งโปรแกรมดังกล่าวออกเป็นส่วนย่อย ๆ ที่มีขนาดเล็กลงมักช่วยให้พัฒนาและทดสอบความถูกต้องได้ง่ายขึ้น  นอกจากนี้ยังมีประโยชน์ที่ช่วยลดเวลาในการคอมไพล์อีกด้วย (โปรแกรมใหญ่ ๆ ถ้าคอมไพล์ทั้งหมดอาจใช้เวลานานมาก)
 
วิธีการคลาสสิกในการจัดการงานที่ใหญ่ ก็คือการแบ่งงานดังกล่าวเป็นส่วนย่อย ๆ  การพัฒนาโปรแกรมก็เช่นเดียวกัน ถ้าโปรแกรมที่เราต้องการพัฒนามีขนาดใหญ่ การแบ่งโปรแกรมดังกล่าวออกเป็นส่วนย่อย ๆ ที่มีขนาดเล็กลงมักช่วยให้พัฒนาและทดสอบความถูกต้องได้ง่ายขึ้น  นอกจากนี้ยังมีประโยชน์ที่ช่วยลดเวลาในการคอมไพล์อีกด้วย (โปรแกรมใหญ่ ๆ ถ้าคอมไพล์ทั้งหมดอาจใช้เวลานานมาก)
 +
 +
ในการนำแฟ้มโปรแกรมต้นฉบับหลาย ๆ แฟ้มมารวมกัน เราจำเป็นจะต้องเข้าใจการทำงานพื้นฐานของการคอมไพล์และลิงก์เสียก่อน
 +
 +
พิจารณาโปรแกรมตัวอย่างด้านล่าง สมมติว่าชื่อ <tt>square.c</tt>
 +
 +
<geshi lang="c">
 +
#include <stdio.h>
 +
 +
main()
 +
{
 +
  int a;
 +
  scanf("%d",&a);
 +
  printf("The square of %d is %d\n", a, a*a);
 +
}
 +
</geshi>
 +
 +
เมื่อเราสั่ง
 +
 +
gcc square.c -o square
 +
 +
สิ่งที่เกิดขึ้นระหว่างการคอมไพล์เพื่อสร้างแฟ้ม <tt>square</tt> ที่ทำงานได้ คือคอมไพเลอร์จะแปลแฟ้ม <tt>square.c</tt> ให้เป็นแฟ้มเป้าหมาย (object file) ก่อน ในแฟ้มดังกล่าวจะประกอบไปด้วยรหัสภาษาเครื่องของคำสั่งที่เขียนใน <tt>square.c</tt> ทั้งหมด อย่างไรก็ตาม สังเกตว่า โปรแกรมดังกล่าวมีการใช้งานฟังก์ชัน <tt>scanf</tt> และ <tt>printf</tt> ซึ่งไม่ได้ถูกนิยามไว้ในแฟ้มดังกล่าว (แม้มีการประกาศฟังก์ชันในแฟ้ม <tt>stdio.h</tt> แต่ไม่มีการระบุนิยามของฟังก์ชันดังกล่าว)
 +
 +
ในขั้นตอนของการสร้างแฟ้มเป้าหมาย ถ้าโปรแกรมมีการอ้างอิงถึงฟังก์ชันที่อยู่นอกขอบเขตแฟ้ม การอ้างอิงดังกล่าวจะถูก "จด" เอาไว้ เพื่อนำไปค้นหาและเชื่อมโยงต่อในขั้นตอนการลิงก์
 +
 +
เราสามารถสั่งให้ <tt>gcc</tt> ทำงานเฉพาะขั้นตอนแรกได้โดยสั่ง
 +
 +
gcc -c square.c
 +
 +
เราจะได้แฟ้มผลลัพธ์ชื่อ <tt>square.o</tt> แฟ้มดังกล่าวจะระบุข้อมูลดังนี้
 +
 +
square.o
 +
--------
 +
มี: main
 +
ต้องการ: scanf, printf

รุ่นแก้ไขเมื่อ 11:42, 12 กรกฎาคม 2552

เอกสารนี้เกี่ยวข้องกับการโปรแกรมภาษาซี โดยออกแบบสำหรับผู้มีความรู้พื้นฐานการโปรแกรมในภาษาตระกูล java และ c# มาแล้ว

<geshi lang="c">

  1. include <stdio.h>

main() {

 printf("Hello, world.\n");

} </geshi>

พอยน์เตอร์ (Pointers)

โปรแกรมภาษาซีมองหน่วยความจำเป็นตาราง แต่ละหน่วยย่อยของหน่วยความจำจะมีตำแหน่งระบุอยู่ ไล่เรียงกันไป หน่วยย่อยสุดของการอ้างถึงหน่วยความจำคือไบต์

พอยน์เตอร์เป็นตัวแปรที่ใช้เก็บตำแหน่งในหน่วยความจำ หรือเรียกว่าตัวแปรพอยน์เตอร์ ชี้ ไปยังตำแหน่งที่มันเก็บอยู่

อย่างไรก็ตามเนื่องจากการชี้ไปยังหน่วยความจำตำแหน่งใด ๆ โดยไม่ระบุประเภทข้อมูลที่เก็บอยู่ที่จุดนั้นไม่เพียงพอในการประมวลผล โดยทั่วไปแล้วการประกาศพอยน์เตอร์จำเป็นจะต้องระบุประเภทข้อมูลที่ตัวแปรนั้นชี้ไปด้วย

การประกาศตัวแปรแบบพอยน์เตอร์ทำได้โดยการใส่ * หน้าชื่อตัวแปร เช่นการเขียน int *a; คือการประกาศให้ตัวแปร a เป็นตัวแปรพอยน์เตอร์ไปยังตำแหน่งข้อมูลที่เก็บข้อมูลประเภท int

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

<geshi lang="c">

  1. include <stdio.h>

main() {

 int a = 10;
 int b = 20;
 int *c;
 printf("%d, %d\n",a,b);
 c = &a;
 *c = 30;
 printf("%d, %d\n",a,b);
 c = &b;
 *c = 40;
 printf("%d, %d\n",a,b);  

} </geshi>

ให้ผลลัพธ์เป็น

10, 20
30, 20
30, 40

พอยน์เตอร์มีประโยชน์มากในการเขียนฟังก์ชันให้มีผลข้างเคียง (side effect) ตัวอย่างเช่นฟังก์ชัน swap ด้านล่าง

<geshi lang="c"> void swap(int *a, int *b) {

 int tmp = *a;
 *a = *b;
 *b = tmp;

} </geshi>

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

<geshi lang="c">

 int a = 10, b = 20;
 int *c = &a;
 int **d = &c;
 *c = 30;   // ตัวแปร a เปลี่ยนค่าเป็น 30
 *d = &b;   // ตอนนี้ c ชี้ไปที่ b
 *c = 100;  // ตัวแปร b เปลี่ยนค่าเป็น 100
 **d = 200;  // **d = *(*d) = *c นั่นคือ หลังคำสั่งนี้ ตัวแปร b เปลี่ยนค่าเป็น 200

</geshi>

การอ่านและเขียนผลลัพธ์

สำหรับการอ่านสตริง ดูในส่วนของการประมวลผลสตริง (ด้านล่าง)

คำสั่งพื้นฐานของภาษา C ในการอ่านและเขียนผลลัพธ์คือ scanf และ printf ซึ่งประกาศอยู่ในไฟล์หัว stdio.h

ในการอ่านและแสดงผลลัพธ์นั้น ฟังก์ชันทั้งสองจะพิจารณาข้อมูลและตัวแปรที่รับมาตามประเภทข้อมูลที่ระบุใน format string (ซึ่งระบุเป็นอาร์กิวเมนต์แรก)

พิจารณาส่วนของโปรแกรมต่อไปนี้

<geshi lang="c">

 int a;
 float b;
 scanf("%d %f", &a, &b);
 printf("a plus 100 is %d.  b divided by 2 is %f.\n", a+100, b/2);

</geshi>

สังเกตว่าในตัวอย่างข้างต้นฟังก์ชัน scanf นั้นรับอาร์กิวเมนต์เป็นตำแหน่งของตัวแปร a และ b อย่างที่เกริ่นไว้ ถ้าเราไม่ได้ระบุชนิดข้อมูล (%d สำหรับ int หรือ %d สำหรับ float) ฟังก์ชัน scanf ก็จะไม่ทราบว่าจะจัดการกับข้อมูลที่อ่านเข้ามาอย่างไร

ในการอ่านข้อมูลด้วยฟังก์ชัน scanf นั้น ฟังก์ชันดังกล่าวจะอ่านข้ามช่องว่างและบรรทัดใหม่ให้เสมอทำให้สะดวกเวลาอ่านข้อมูลหลาย ๆ ตัว (แต่อาจมีปัญหาบ้างถ้าต้องการอ่าน string ที่มีช่องว่างอยู่ด้วย)

ความผิดพลาดที่เกิดขึ้นบ่อยเวลาใช้ฟังก์ชัน scanf ก็คือการลืมใส่ & เพื่อระบุตำแหน่ง เช่นดังตัวอย่างด้านล่าง

<geshi lang="c">

 int a;
 scanf("%d", a);   // ถ้า a มีค่า 0, scanf จะพยายามเขียนข้อมูลลงในตำแหน่ง 0

</geshi>

อย่างไรก็ตาม ไม่ใช่ว่าเราจะใส่ & เสมอไป ตัวอย่างเช่น ส่วนของโปรแกรมด้านล่างอ่านข้อมูลเข้าไปที่ตัวแปร a ได้อย่างถูกต้อง

<geshi lang="c">

 int a; int *b = &a;
 scanf("%d", b);     // b ชี้ไปที่ a

</geshi>

ด้านล่างเป็นตารางของ format ที่ใช้บ่อย ๆ (รวมทั้งตัวอย่างการระบุการเว้นและทศนิยมสำหรับใช้ในคำสั่ง printf)

formatประเภทข้อมูล
%dint
%5dแสดง int แบบ 5 หลัก ชิดขวา
%ffloat
%5.2fแสดง float แบบ 5 หลักมีทศนิยม 2 ตำแหน่ง
%lfdouble
%sstring, เมื่อใช้กับฟังก์ชัน scanf จะถือว่า string ดังกล่าวมีขอบเขตอยู่ที่ช่องว่างหรือบรรทัดใหม่

อาร์เรย์และพอยน์เตอร์

โครงสร้าง (struct)

อาร์กิวเมนต์จาก command line

การประมวลผลสตริง

การคอมไพล์และลิงก์โปรแกรมที่อยู่ในหลายแฟ้ม

วิธีการคลาสสิกในการจัดการงานที่ใหญ่ ก็คือการแบ่งงานดังกล่าวเป็นส่วนย่อย ๆ การพัฒนาโปรแกรมก็เช่นเดียวกัน ถ้าโปรแกรมที่เราต้องการพัฒนามีขนาดใหญ่ การแบ่งโปรแกรมดังกล่าวออกเป็นส่วนย่อย ๆ ที่มีขนาดเล็กลงมักช่วยให้พัฒนาและทดสอบความถูกต้องได้ง่ายขึ้น นอกจากนี้ยังมีประโยชน์ที่ช่วยลดเวลาในการคอมไพล์อีกด้วย (โปรแกรมใหญ่ ๆ ถ้าคอมไพล์ทั้งหมดอาจใช้เวลานานมาก)

ในการนำแฟ้มโปรแกรมต้นฉบับหลาย ๆ แฟ้มมารวมกัน เราจำเป็นจะต้องเข้าใจการทำงานพื้นฐานของการคอมไพล์และลิงก์เสียก่อน

พิจารณาโปรแกรมตัวอย่างด้านล่าง สมมติว่าชื่อ square.c

<geshi lang="c">

  1. include <stdio.h>

main() {

 int a;
 scanf("%d",&a);
 printf("The square of %d is %d\n", a, a*a);

} </geshi>

เมื่อเราสั่ง

gcc square.c -o square

สิ่งที่เกิดขึ้นระหว่างการคอมไพล์เพื่อสร้างแฟ้ม square ที่ทำงานได้ คือคอมไพเลอร์จะแปลแฟ้ม square.c ให้เป็นแฟ้มเป้าหมาย (object file) ก่อน ในแฟ้มดังกล่าวจะประกอบไปด้วยรหัสภาษาเครื่องของคำสั่งที่เขียนใน square.c ทั้งหมด อย่างไรก็ตาม สังเกตว่า โปรแกรมดังกล่าวมีการใช้งานฟังก์ชัน scanf และ printf ซึ่งไม่ได้ถูกนิยามไว้ในแฟ้มดังกล่าว (แม้มีการประกาศฟังก์ชันในแฟ้ม stdio.h แต่ไม่มีการระบุนิยามของฟังก์ชันดังกล่าว)

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

เราสามารถสั่งให้ gcc ทำงานเฉพาะขั้นตอนแรกได้โดยสั่ง

gcc -c square.c

เราจะได้แฟ้มผลลัพธ์ชื่อ square.o แฟ้มดังกล่าวจะระบุข้อมูลดังนี้

square.o
--------
มี: main
ต้องการ: scanf, printf