ความแตกต่างระหว่างประเภทสตริงและ char [] ใน C ++


126

ฉันรู้ C นิดหน่อยและตอนนี้ฉันกำลังดู C ++ ฉันเคยใช้ถ่านอาร์เรย์สำหรับจัดการกับสตริง C แต่ในขณะที่ฉันดูรหัส C ++ ฉันเห็นมีตัวอย่างที่ใช้ทั้งประเภทสตริงและอาร์เรย์ถ่าน:

#include <iostream>
#include <string>
using namespace std;

int main () {
  string mystr;
  cout << "What's your name? ";
  getline (cin, mystr);
  cout << "Hello " << mystr << ".\n";
  cout << "What is your favorite team? ";
  getline (cin, mystr);
  cout << "I like " << mystr << " too!\n";
  return 0;
}

และ

#include <iostream>
using namespace std;

int main () {
  char name[256], title[256];

  cout << "Enter your name: ";
  cin.getline (name,256);

  cout << "Enter your favourite movie: ";
  cin.getline (title,256);

  cout << name << "'s favourite movie is " << title;

  return 0;
}

(ทั้งสองตัวอย่างจากhttp://www.cplusplus.com )

ฉันคิดว่านี่เป็นคำถามที่ถามและตอบกันอย่างแพร่หลาย (ชัดเจน?) แต่จะดีถ้ามีคนบอกฉันได้ว่าอะไรคือความแตกต่างระหว่างสองวิธีในการจัดการกับสตริงใน C ++ (ประสิทธิภาพการรวม API วิธีที่แต่ละคนเป็น ดีกว่า, ... ).

ขอบคุณ.


สิ่งนี้อาจช่วยได้: C ++ char * vs std :: string
Wael Dalloul

คำตอบ:


187

อาร์เรย์ถ่านก็แค่นั้น - อาร์เรย์ของอักขระ:

  • หากจัดสรรบนสแต็ก (เช่นในตัวอย่างของคุณ) มันจะครอบครองเช่นเสมอ 256 ไบต์ไม่ว่าข้อความจะมีความยาวเท่าใดก็ตาม
  • หากจัดสรรบนฮีป (โดยใช้ malloc () หรือถ่านใหม่ []) คุณจะต้องรับผิดชอบในการปล่อยหน่วยความจำในภายหลังและคุณจะมีค่าใช้จ่ายในการจัดสรรฮีปเสมอ
  • หากคุณคัดลอกข้อความที่มีมากกว่า 256 ตัวอักษรลงในอาร์เรย์อาจทำให้เกิดข้อผิดพลาดสร้างข้อความยืนยันที่น่าเกลียดหรือทำให้เกิดพฤติกรรมที่ไม่สามารถอธิบายได้ (ผิดพลาด) ที่อื่นในโปรแกรมของคุณ
  • ในการกำหนดความยาวของข้อความอาร์เรย์จะต้องถูกสแกนทีละอักขระสำหรับอักขระ \ 0

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

คุณสามารถเข้าถึงอาร์เรย์ของสตริงได้ดังนี้:

std::string myString = "Hello World";
const char *myStringChars = myString.c_str();

สตริง C ++ สามารถมีอักขระ \ 0 ฝังอยู่รู้ความยาวโดยไม่ต้องนับเร็วกว่าอาร์เรย์ถ่านที่จัดสรรแบบฮีปสำหรับข้อความสั้น ๆ และปกป้องคุณจากการโอเวอร์รันบัฟเฟอร์ นอกจากนี้ยังอ่านง่ายขึ้นและใช้งานง่ายขึ้น


อย่างไรก็ตามสตริง C ++ ไม่ (มาก) เหมาะสำหรับการใช้งานข้ามขอบเขต DLL เนื่องจากจะต้องมีผู้ใช้ฟังก์ชัน DLL ดังกล่าวเพื่อให้แน่ใจว่าเขาใช้คอมไพเลอร์และการใช้รันไทม์ C ++ เดียวกันเพื่อมิให้เขาเสี่ยงต่อคลาสสตริงของเขาที่ทำงานแตกต่างกัน

โดยปกติคลาสสตริงจะปล่อยหน่วยความจำฮีปในฮีปการโทรด้วยดังนั้นจึงจะสามารถเพิ่มหน่วยความจำได้อีกครั้งหากคุณใช้รันไทม์เวอร์ชันแชร์ (.dll หรือ. so)

กล่าวโดยย่อ: ใช้สตริง C ++ ในฟังก์ชันและวิธีการภายในทั้งหมดของคุณ หากคุณเคยเขียน. dll หรือ. so ให้ใช้สตริง C ในฟังก์ชันสาธารณะของคุณ (dll / so-exposed)


4
นอกจากนี้สตริงยังมีฟังก์ชันตัวช่วยมากมายที่สามารถทำได้อย่างเรียบร้อย
Håkon

1
ฉันไม่เชื่อเล็กน้อยเกี่ยวกับขอบเขตของ DLL ภายใต้เงื่อนไขที่พิเศษมากอาจทำให้เกิดการแตกหักได้ ((หนึ่ง DLL เชื่อมโยงแบบคงที่กับรันไทม์เวอร์ชันที่แตกต่างจากที่ใช้โดย DLL อื่น ๆ ) และสิ่งที่แย่กว่านั้นอาจเกิดขึ้นก่อนในสถานการณ์เหล่านี้) แต่ในกรณีทั่วไปที่ทุกคนใช้ค่าเริ่มต้น เวอร์ชันที่ใช้ร่วมกันของรันไทม์มาตรฐาน (ค่าเริ่มต้น) สิ่งนี้จะไม่เกิดขึ้น
Martin York

2
ตัวอย่าง: คุณแจกจ่ายไบนารีที่คอมไพล์ VC2008SP1 ของไลบรารีสาธารณะชื่อ libfoo ซึ่งมี std :: string & ใน API สาธารณะ ตอนนี้มีคนดาวน์โหลด libfoo.dll ของคุณและทำการแก้ไขข้อบกพร่อง สตริง std :: ของเขาสามารถมีฟิลด์ดีบักเพิ่มเติมอยู่ในนั้นได้เป็นอย่างดีทำให้ออฟเซ็ตของตัวชี้สำหรับสตริงไดนามิกที่จะย้าย
Cygon

2
ตัวอย่างที่ 2: ในปี 2010 มีคนดาวน์โหลด libfoo.dll ของคุณและใช้ในแอปพลิเคชันที่สร้าง VC2010 ของเขา รหัสของเขาโหลด MSVCP100.dll และ libfoo.dll ของคุณยังคงโหลด MSVCP90.dll -> คุณได้รับสองฮีป -> ไม่สามารถปลดปล่อยหน่วยความจำได้ข้อผิดพลาดในการยืนยันในโหมดดีบักหาก libfoo แก้ไขการอ้างอิงสตริงและส่ง std :: string ใหม่ ตัวชี้กลับ
Cygon

1
ฉันจะใช้ "ในระยะสั้น: ใช้สตริง C ++ ในฟังก์ชันและวิธีการภายในทั้งหมดของคุณ" พยายามทำความเข้าใจตัวอย่างของคุณทำให้สมองของฉันป๊อป
Stephen

12

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

ในทางกลับกันchar[]สัญกรณ์ในกรณีข้างต้นได้ จำกัด บัฟเฟอร์อักขระไว้ที่ 256 อักขระ หากคุณพยายามเขียนอักขระมากกว่า 256 ตัวลงในบัฟเฟอร์นั้นอย่างดีที่สุดคุณจะเขียนทับหน่วยความจำอื่นที่โปรแกรมของคุณ "เป็นเจ้าของ" ที่แย่ที่สุดคือคุณจะพยายามเขียนทับหน่วยความจำที่คุณไม่ได้เป็นเจ้าของและระบบปฏิบัติการของคุณจะฆ่าโปรแกรมของคุณทันที

บรรทัดล่าง? สตริงเป็นมิตรกับโปรแกรมเมอร์มากขึ้นถ่าน [] มีประสิทธิภาพมากกว่าสำหรับคอมพิวเตอร์มาก


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

6

ประเภทสตริงเป็นคลาสที่ได้รับการจัดการอย่างสมบูรณ์สำหรับสตริงอักขระในขณะที่ char [] ยังคงเป็นสิ่งที่อยู่ใน C ซึ่งเป็นอาร์เรย์ไบต์ที่แสดงสตริงอักขระสำหรับคุณ

ในแง่ของ API และไลบรารีมาตรฐานทุกอย่างถูกนำไปใช้ในรูปแบบของสตริงไม่ใช่ถ่าน [] แต่ยังมีฟังก์ชันมากมายจาก libc ที่รับถ่าน [] ดังนั้นคุณอาจต้องใช้มันสำหรับสิ่งเหล่านั้นนอกเหนือจากที่ฉันต้องการ ใช้ std :: string เสมอ

ในแง่ของประสิทธิภาพแน่นอนว่าบัฟเฟอร์ดิบของหน่วยความจำที่ไม่มีการจัดการมักจะเร็วกว่าสำหรับสิ่งต่างๆมากมาย แต่ให้คำนึงถึงการเปรียบเทียบสตริงเช่น std :: string มีขนาดที่จะตรวจสอบก่อนเสมอในขณะที่ใช้ char [] คุณ ต้องเปรียบเทียบตัวละครทีละตัวละคร


5

โดยส่วนตัวแล้วฉันไม่เห็นเหตุผลใด ๆ ว่าทำไมเราถึงต้องการใช้ถ่าน * หรือถ่าน [] ยกเว้นเพื่อความเข้ากันได้กับรหัสเก่า std :: string ไม่ช้าไปกว่าการใช้ c-string ยกเว้นว่าจะจัดการการจัดสรรซ้ำให้คุณ คุณสามารถกำหนดขนาดได้เมื่อคุณสร้างและหลีกเลี่ยงการจัดสรรใหม่หากคุณต้องการ เป็นตัวดำเนินการจัดทำดัชนี ([]) ให้การเข้าถึงเวลาคงที่ (และในทุกความหมายของคำนั้นเหมือนกับการใช้ตัวทำดัชนี c-string) การใช้วิธีการที่ช่วยให้คุณได้รับการตรวจสอบความปลอดภัยเช่นกันสิ่งที่คุณไม่ได้รับจากสตริง c เว้นแต่คุณจะเขียนมัน คอมไพเลอร์ของคุณส่วนใหญ่มักจะเพิ่มประสิทธิภาพการใช้ดัชนีในโหมดรีลีส มันง่ายที่จะยุ่งกับ c-strings; สิ่งต่างๆเช่น delete vs delete [] ความปลอดภัยของข้อยกเว้นแม้กระทั่งวิธีการจัดสรร c-string ใหม่

และเมื่อคุณต้องจัดการกับแนวคิดขั้นสูงเช่นการมีสตริง COW และไม่ใช่ COW สำหรับ MT เป็นต้นคุณจะต้องใช้ std :: string

หากคุณกังวลเกี่ยวกับสำเนาตราบใดที่คุณใช้การอ้างอิงและการอ้างอิง const ไม่ว่าคุณจะทำได้คุณจะไม่มีค่าใช้จ่ายใด ๆ เนื่องจากสำเนาและเป็นสิ่งเดียวกับที่คุณทำกับ c-string


+1 แม้ว่าคุณจะไม่ได้พิจารณาปัญหาการใช้งานเช่นความเข้ากันได้ของ DLL แต่คุณก็มี COW

แล้วฉันจะรู้ได้อย่างไรว่าอาร์เรย์ถ่านของฉันมีขนาด 12 ไบต์ ถ้าฉันสร้างอินสแตนซ์สตริงเพราะมันอาจไม่มีประสิทธิภาพจริงๆใช่ไหม
David 天宇 Wong

@ เดวิด: หากคุณมีรหัสที่ละเอียดอ่อนมากใช่ คุณอาจพิจารณาการเรียก std :: string ctor เป็นค่าใช้จ่ายนอกเหนือจากการเริ่มต้นของสมาชิก std :: string แต่โปรดจำไว้ว่าการเพิ่มประสิทธิภาพก่อนกำหนดได้สร้างฐานรหัสจำนวนมากโดยไม่จำเป็นต้องใช้รูปแบบ C ดังนั้นโปรดใช้ความระมัดระวัง
Abhay

1

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


0

คิดว่า (char *) เป็น string.begin () ความแตกต่างที่สำคัญคือ (char *) เป็นตัววนซ้ำและ std :: string เป็นคอนเทนเนอร์ หากคุณยึดติดกับสตริงพื้นฐาน a (char *) จะให้สิ่งที่ std :: string :: iterator ทำ คุณสามารถใช้ (ถ่าน *) เมื่อคุณต้องการประโยชน์ของตัววนซ้ำและความเข้ากันได้กับ C แต่นั่นเป็นข้อยกเว้นไม่ใช่กฎ เช่นเคยโปรดระวังตัวทำซ้ำที่ไม่ถูกต้อง เมื่อมีคนพูดว่า (ถ่าน *) ไม่ปลอดภัยนี่คือสิ่งที่พวกเขาหมายถึง ปลอดภัยพอ ๆ กับตัวทำซ้ำ C ++ อื่น ๆ


0

ความแตกต่างอย่างหนึ่งคือการยกเลิก Null (\ 0)

ในภาษา C และ C ++ ถ่าน * หรือถ่าน [] จะนำตัวชี้ไปยังถ่านตัวเดียวเป็นพารามิเตอร์และจะติดตามไปตามหน่วยความจำจนกว่าจะถึงค่าหน่วยความจำ 0 (มักเรียกว่าเทอร์มิเนเตอร์ null)

สตริง C ++ สามารถมีอักขระ \ 0 ฝังอยู่รู้ความยาวโดยไม่ต้องนับ

#include<stdio.h>
#include<string.h>
#include<iostream>

using namespace std;

void NullTerminatedString(string str){
   int NUll_term = 3;
   str[NUll_term] = '\0';       // specific character is kept as NULL in string
   cout << str << endl <<endl <<endl;
}

void NullTerminatedChar(char *str){
   int NUll_term = 3;
   str[NUll_term] = 0;     // from specific, all the character are removed 
   cout << str << endl;
}

int main(){
  string str = "Feels Happy";
  printf("string = %s\n", str.c_str());
  printf("strlen = %d\n", strlen(str.c_str()));  
  printf("size = %d\n", str.size());  
  printf("sizeof = %d\n", sizeof(str)); // sizeof std::string class  and compiler dependent
  NullTerminatedString(str);


  char str1[12] = "Feels Happy";
  printf("char[] = %s\n", str1);
  printf("strlen = %d\n", strlen(str1));
  printf("sizeof = %d\n", sizeof(str1));    // sizeof char array
  NullTerminatedChar(str1);
  return 0;
}

เอาท์พุท:

strlen = 11
size = 11
sizeof = 32  
Fee s Happy


strlen = 11
sizeof = 12
Fee

"จากที่เฉพาะเจาะจงอักขระทั้งหมดจะถูกลบออก" ไม่ แต่จะไม่ถูก "ลบออก" การพิมพ์ตัวชี้ถ่านจะพิมพ์ได้ไม่เกินเทอร์มิเนเตอร์ว่างเท่านั้น (เนื่องจากเป็นวิธีเดียวที่ถ่าน * จะรู้จุดจบ) คลาสสตริงรู้ขนาดเต็มของตัวมันเองจึงใช้แค่นั้น หากคุณทราบขนาดของถ่าน * คุณสามารถพิมพ์ / ใช้ตัวอักษรทั้งหมดด้วยตัวเองได้เช่นกัน
Puddle
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.