Dart: String เบื้องต้น

การจัดการ String หรือข้อมูลประเภทข้อความต่าง ๆ ที่อยู่ใน dart:core → String class ตัว Dart มีรูปแบบการจัดการที่ค่อนข้างยืดหยุ่น มีรูปแบบในการประกาศได้หลายแบบ ช่วยอำนวยความสะดวกให้กับผู้ที่เขียนโปรแกรมเป็นอย่างดี

การเก็บข้อมูล String จะเป็นการเอาข้อมูลตัวอักษรแต่ละตัวมาเรียงกันตามการเข้ารหัสแบบ UTF-16 ซึ่งจะไม่เหมือนกับที่นิยมใช้กันบนเว็บไซต์หรือ source code โปรแกรมที่ใช้ UTF-8

Dsmurat, penubag, Jelican9, CC BY-SA 4.0

String class เป็น immutable นั้นคือ ไม่สามารถแก้ไขข้อมูลได้หลังจากสร้าง object เรียบร้อยแล้ว หากมีการแก้ไขข้อมูลใน string ข้อมูลใหม่ที่แก้ไขจะถูกสร้างเป็น object ตัวใหม่ โดย string ตัวเดิมจะถูกปล่อยไว้อย่างนั้น รอจนตัว Garbage Collection มาเก็บกวาดภายหลัง ดังนั้นโปรดระวังการเขียนโปรแกรมที่มีการสร้างข้อมูลใหม่ที่ซ้ำซ้อนเป็นจำนวนมาก เพราะจะทำให้ประสิทธิภาพของแอปแย่ลง

การประกาศตัวแปรแบบ String

ข้อมูลที่เป็น String สามารถเขียนได้หลายแบบ โดยจะคล้ายกับภาษาอื่น ๆ ตัว String จะอยู่ในเครื่องหมาย ' หรือ "

ประกาศ String แบบบรรทัดเดียว

การประกาศแบบข้อมูลอยู่ในบรรทัดเดียวก็ไม่มีอะไร ตรงไปตรงมา เริ่มต้นด้วย ' ก็จบด้วย ' หรือเริ่มต้นด้วย " ก็จบด้วย " แค่นั้น จะมีตัวอักษรพิเศษที่จะใช้สำหรับ escape string คือ \

'Single quotes';
"Double quotes";
'Double quotes in "single" quotes';
"Single quotes in 'double' quotes";

ประกาศ String แบบหลายบรรทัด

ในการประกาศข้อมูล String แบบครั้งเดียวมีหลายบรรทัด ให้ใช้เครื่องหมาย ''' หรือ """ ในการเริ่มต้นและสิ้นสุดข้อความ โดยตัวอักษรตัวแรกจะอยู่ต่อเครื่องหมายทันทีหรือขึ้นบรรทัดก็ได้ มีความหมายเหมือนกัน

void main() {
  var test1 = '''A
multiline
string''';

  var test2 = """
Another
multiline
string""";

  print(test1);
  print("------------");
  print(test2);
}

ผลที่ได้

A
multiline
string
------------
Another
multiline
string

การประกาศแบบหลายบรรทัดตามตัวอย่างข้างบนจะมีค่าเท่ากับการใส่ \n เป็น escape string ที่แทนการขึ้นบรรทัดใหม่ดังนี้


void main() {
  var test1 = 'A\nmultiline\nstring';
  var test2 = "Another\nmultiline\nstring";

  print(test1);
  print("------------");
  print(test2);
}

ประกาศ String แบบตัดเป็นข้อความยาว ๆ เป็น หลาย ๆ ชิ้น เพื่อไม่ให้เกินขอบขวา

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

void main() {
  var test1 = 'ดาร์ต (Dart) เป็นภาษาโปรแกรมที่ออกแบบโดย Lars Bak และ Kasper Lund '
      'และพัฒนาโดยกูเกิล สามารถใช้ในการพัฒนาโปรแกรมประยุกต์บนเว็บ และ แอปมือถือ '
      'ตลอดจนแอปพลิเคชันเซิร์ฟเวอร์ และ แอปพลิเคชันเดสก์ท็อป';
  print(test1);
}

หรือใช้เครื่องหมาย + เพื่อเชื่อมกันข้อความก็ได้เช่นกัน (แต่อาจโดนตัว editor แจ้งแนะนำให้ทำเป็น interpolation หรือเอาเครื่องหมาย + ออกไป เพราะไม่จำเป็น)

void main() {
  var test1 = 'ดาร์ต (Dart) เป็นภาษาโปรแกรมที่ออกแบบโดย Lars Bak และ Kasper Lund ' +
      'และพัฒนาโดยกูเกิล สามารถใช้ในการพัฒนาโปรแกรมประยุกต์บนเว็บ และ แอปมือถือ ' +
      'ตลอดจนแอปพลิเคชันเซิร์ฟเวอร์ และ แอปพลิเคชันเดสก์ท็อป';
  print(test1);
}

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

void main() {
  print('I don\'t want to go "CafeCat".');
  print("I don't " 'want to go "CafeCat".');
}

ผลที่ได้จะเหมือนกันทั้ง 2 คำสั่ง

I don't want to go "CafeCat".
I don't want to go "CafeCat".

Dsmurat, penubag, Jelican9, CC BY-SA 4.0

ควรใช้การประกาศแบบนี้เมื่อจำเป็นจริง ๆ และไม่ทำให้การกลับมาแก้ไขนั้นอ่านยาก หรือทำให้ลำบากในการทำความเข้าใจ

escape string การใช้อักขระพิเศษในข้อความ

การ escape string ก็คล้ายกับภาษาอื่น ๆ คือใช้ \ นำหน้าอักขระที่ต้องการ escape เช่น


  var test1 = 'I don\'t want to sleep'; // OK  escape ' inside string
  var test2 = 'I don't want to sleep';  // compile error → Error: String starting with ' must end with '

นอกจากนี้ยังมีอักขระพิเศษต่าง ๆ ที่สามารถใช้งานในข้อความ string ได้ ที่ใช้กันบ่อย ๆ เช่น

  • \t → Tab
  • \n → Newline
  • \r → Carriage return
  • \" → Double quote
  • \' → Single quote
  • \\ → Backslash
  • \xnn → ตัวอักษรตามตาราง Ascii แบบเลขฐาน 16 ค่า 00-FF (0 ถึง 255 ฐาน 10)
  • \unnnn → ตัวอักษร Unicode ลำดับที่ nnnn โดย n แทนเลขฐาน 16 เช่น \u263A = ☺
  • \u{nnnnn...} → ตัวอักษร Unicode ลำดับที่ nnnnn... โดย n แทนเลขฐาน 16 เช่น \u{1F436} = 🐶

การสร้าง raw string

raw string เป็นการระบุว่าข้อความจะไม่ทำการ escape string ตัวอักษร \ จะมีผลเหมือนตัวอักษรปกติ วิธีการประกาศ raw string ให้ใส่ r ไว้ด้านหน้าข้อความ ก่อน " หรือ '

void main() {
  var test1 = r"this is raw string → \n doesn't covert to newline";
  print(test1);
}

ผลที่ได้

this is raw string → \n doesn't covert to newline

การใช้ raw string ช่วยให้ผู้เขียนโปรแกรมไม่ต้องมาคอยใส่ \\ เพื่อให้แสดง \ ได้ถูกต้องเช่น


  var test1 = r"D:\My Data\hello.text";
  var test2 = "D:\\My Data\\hello.text"; // same as test1

Dsmurat, penubag, Jelican9, CC BY-SA 4.0

ไม่ใช่แค่เครื่องหมาย \ ที่จะถูกละเว้น การใช้ interpolation เพื่อแสดงค่าตัวแปรในก็จะถูกละเว้นด้วย โปรดใช้ raw string อย่างระมัดระวัง

ข้อจำกัดของ raw string เนื่องจากมันไม่สามารถ escape string อะไรได้เลย ทำให้ไม่สามารถใช้เครื่องหมาย " ในข้อความ "text" หรือ ' ใน 'text' ได้

  print(r"I don't want to go anywhere"); // OK
  print('I don\'t want to go anywhere'); // OK

  print(r'I don't want to go anywhere'); // compile error
  print(r'I don\'t want to go anywhere'); // compile error

String interpolation การประมวลผลประโยคภาษา Dart ที่อยู่ใน String

ความสามารถที่ภาษาใหม่ ๆ มีกันคือ ผู้เขียนโปรแกรมสามารถใส่คำสั่งหรือตัวแปรที่จะให้ประมวลผลด้วย .toString() ใส่ไว้ใน string ที่ต้องการได้เลย

void main() {
  var num1 = 100, num2 = 200;
  print(num2.toString() + ' is twice as much as ' + num1.toString() + ".");
  print('$num2 is twice as much as $num1.');
}

ผลที่ได้

200 is twice as much as 100.
200 is twice as much as 100.

ในตัวอย่าง print() ตัวที่สองใช้วิธี interpolation โดยการใส่ num1 และ num2 เข้าไปใน string ได้เลย

การระบุว่าตัวไหนเป็นคำสั่งหรือตัวแปรใน Dart ที่จะเอาไปแทรกในข้อความ ให้ใส่ $ ไว้ด้านหน้าตัวแปรที่จะแทรก แต่หากเป็นประโยคคำสั่งจะใช้ ${...} ครอบคำสั่งเอาไว้

void main() {
  var num1 = 100, num2 = 200;
  print('$num2 is twice as much as $num1.');
  print('$num2 + $num1 = ${num2 + num1}');
  print('$num2 in heximal number is ${num2.toRadixString(16).toUpperCase()}');
}

ผลที่ได้

200 is twice as much as 100.
200 + 100 = 300
200 in heximal number is C8

เนื่องจากการใช้ String interpolation จะทำการเรียกใช้ .toString() ให้อัตโนมัติหากไม่ระบุ ดังนั้นอาจเกิด bug ขึ้นโดยไม่ตั้งใจได้ เช่น

void main() {
  String? userName;
  print('Your username is ' + userName); // Compile error: The argument type 'String?' can't be assigned to the parameter type 'String'.
  print('Your username is $userName'); // output → Your username is null
}

จะเห็นว่าในแถวที่ 4 จะพิมพ์คำว่า null ออกมา เพราะ userName ยังไม่ได้กำหนดค่าใด ๆ จึงมีค่าเริ่มต้นเป็น null และเมื่อทำการ interpolation จะกลายเป็นการเรียกคำสั่ง null.toString() ทำให้พิมพ์คำว่า null ในข้อความให้ผู้ใช้งานเห็น ซึ่งเป็นเรื่องที่ไม่สมเหตุสมผล

Dsmurat, penubag, Jelican9, CC BY-SA 4.0

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

การใช้ operator กับ String

String class รองรับตัว operator 4 ตัวด้วยกันคือ + * == []

การเชื่อม string เข้าด้วยกันด้วย +

ในการเชื่อม string มากกว่า 1 ชิ้นเข้ารวมกัน สามารถใช้ + ในการเชื่อมได้เลย

void main() {
  String text1 = "My name is";
  String text2 = " David";
  String result = text1 + text2;
  print(result); // output → My name is David
}

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

void main() {
  String text1 = "Odd number from 1 to 100 are";

  for (var i = 1; i <= 100; ++i) {
    if (i % 2 != 0) {
      text1 = text1 + " " +$i;
    }
  }

  print(text1); // → Odd number from 1 to 100 are 1 3 5 7 9 11 13 15 17 19 21 ... 93 95 97 99
}

จากตัวอย่างจะเป็นการสร้างข้อความใหม่ ที่เกิดจากข้อความเก่า ตามด้วยตัวเลขคี่ตัวใหม่ในแต่ละรอบของการวนลูป for() เนื่องจากตัว String class เป็น immutable ทำให้โปรแกรมข้างต้นสร้างข้อความใหม่ขึ้นมาในแต่ละรอบดังนี้

  • Odd number from 1 to 100 are 1
  • Odd number from 1 to 100 are 1 3
  • Odd number from 1 to 100 are 1 3 5
  • Odd number from 1 to 100 are 1 3 5 7
  • Odd number from 1 to 100 are 1 3 5 7 9
  • ...
  • Odd number from 1 to 100 are 1 3 5 7 9 ... 99

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

void main() {
  StringBuffer buffer = StringBuffer();
  buffer.write("Odd number from 1 to 100 are ");

  for (var i = 1; i <= 100; ++i) {
    if (i % 2 != 0) {
      buffer.write(" $i");
    }
  }

  print(buffer); // → Odd number from 1 to 100 are 1 3 5 7 9 11 13 15 17 19 21 ... 93 95 97 99
}

การทำซ้ำข้อความด้วย *

ในการทำซ้ำข้อความตามจำนวนครั้งที่กำหนด สามารถใช้ * กับตัวเลขจำนวนที่การทำซ้ำดังนี้

void main() {
  String oneTime = 'time ';
  String threeTime = oneTime * 3;
  print(threeTime); // output → time time time 
}

การเปรียบเทียบข้อความที่เหมือนกันด้วย ==

การเปรียบเทียบข้อความจากสองแหล่งมีตัวอักษรตามลำดับตรงกันทุกลำดับหรือไม่ สามารถใช้ == เพื่อเปรียบเทียบได้ โดยผลจะคืนค่าเป็น true หากข้อความที่เปรียบเทียบเหมือนกันทุกตัวอักษร

การเปรียบเทียบสามารถเปรียบระหว่างตัวแปร ข้อความ และยังสามารถใช้ .hashCode ในการเปรียบเทียบก็ได้ โดย .hashCode ของ string ที่มีลำดับตัวอักษรเหมือนกันจะมีค่าตัวเลขเท่ากัน

void main() {
  String text1 = "text1";
  String text2 = "text2";

  print(text1 == text2); // false
  print(text1 == "text1"); // true
  print(text1.hashCode == "text1".hashCode); //true
}

การเข้าถึงตัวอักษรในลำดับที่ต้องการด้วย []

ในการเข้าถึงตัวอักษรในลำดับที่ต้องการในข้อความ สามารถใช้ [] เพื่อระบุตำแหน่ง index ที่ต้องการได้ โดยตัวอักษรตัวแรก index=0 ตัวสุดท้ายคือ index=.length - 1

void main() {
  String text1 = "01234"; // text1.length → 5
  print(text1[0]);
  print(text1[1]);
  print(text1[2]);
  print(text1[3]);
  print(text1[4]);
  print(text1[5]);
}

ผลที่ได้คือ

0
1
2
3
4
Unhandled exception:
RangeError (index): Invalid value: Not in inclusive range 0..4: 5
#0 _StringBase.[] (dart:core-patch/string_patch.dart:274:41)
#1 main (file:///D:/My%20Data/dart/dart_tester/bin/dart_string.dart:8:14) dart_string.dart:8
#2 _delayEntrypointInvocation. (dart:isolate-patch/isolate_patch.dart:297:19)
#3 _RawReceivePort._handleMessage (dart:isolate-patch/isolate_patch.dart:184:12)

จากตัวอย่างหากอ้างถึงตำแหน่งที่เกินตัวอักษรลำดับสุดท้าย จะเกิด RangeError ดังนั้นการใช้งานควรตรวจสอบเสมอว่าค่าตำแหน่งที่อ้าง เท่ากับหรือมากกว่า .length หรือไม่

อ่านเพิ่มที่ไหนต่อ

นอกจากนี้ยังมีส่วนที่นำเอา String ไปใช้งานในส่วนอื่น ๆ อีก

  • Strings and regular expressions ที่ใช้สำหรับค้นหาข้อความที่ตรงกับรูปแบบที่กำหนด และแทนที่ด้วยข้อความใหม่ที่ต้องการได้ ช่วยลดความซับซ้อนในการเขียนโปรแกรมได้มากขึ้น
  • RegExp class
  • การ convert ข้อมูล string เพื่อนำไปใช้งานอื่น ๆ ใน dart:convert library เช่น เข้ารหัสแบบ base64 JSON หรือแปลง UTF-16 ↔ UTF-8