Article Structure Diagram:
std::string vs. const char*
const char*const char* is the traditional way of handling strings in C. The key to understanding it lies in recognizing its essence.Essence: It is not a string; it is merely an address.The const char* type itself is a pointer. It does not store any character data; it merely stores a memory address that points to a character in memory (usually the first character of a string).The convention: the “gentleman’s agreement” relying on .Since the pointer itself only knows a starting address, how do we know where the string ends? C establishes a simple convention: at the end of the string content, a special null character is placed.All functions that handle C-style strings (such as strlen, strcpy) rely on this convention; they read from the starting address until they encounter .Responsibility: only “points to”, does not “own”.This is the most critical point. The const char* pointer is not responsible for the lifecycle of the memory it points to. It is merely a “signpost” indicating that there is some data, but it has no knowledge of who allocated this data or when it will be released. This lack of ownership means that developers need to manage memory manually, which is prone to errors.
As can be seen above, const char* can point to memory areas with different lifecycles, making management very cumbersome.stringTo solve the management problem of C-style strings, C++ introduced the std::string class.Essence: a class object, a collection of data and operations. std::string is a class.When you create a std::string variable, you get a fully functional object. This object not only stores the string data but also bundles a series of methods (member functions) for manipulating this data, such as append(), find(), substr(), etc.Internal structure: an intelligent memory manager.As discussed earlier, a std::string object typically contains three core members:(1) a pointer to heap memory (char* _data) for storing the actual character data.(2) a variable (size_t _size) that records the current length.(3) a variable (size_t _capacity) that records the allocated capacity.This design makes obtaining the string length operation extremely efficient (O(1)) and can optimize append operations through pre-allocated capacity.Responsibility: follows RAII’s “owner”.std::string is the sole owner of the heap memory data it manages. It adheres to one of the most important design principles in C++—RAII, which stands for “Resource Acquisition Is Initialization”.When constructed: when the object is created, it automatically allocates the required memory on the heap.When destructed: when the object’s lifecycle ends (e.g., when it goes out of scope), its destructor is automatically called to release the heap memory it owns.This makes memory management automated and safe, greatly reducing the risk of memory leaks.Bridge and Compatibility: .c_str()Since std::string and const char* are two different types, what should we do when we need to pass a modern std::string object to a function that only accepts the “old” const char*? This is the purpose of the c_str() member function.const char* c_str() const;Function: this function returns a const char* pointer to the internal character buffer of std::string. This pointer points to memory that is null-terminated, fully compliant with C-style string requirements.const keyword: Note that the return is const char*. const means you can only read data through this pointer and cannot modify it. This is to protect the internal state of the std::string object from being arbitrarily modified by external sources.Which functions require const char*?Most library functions derived from C require it.For example:File operations: fopen(“filename.txt”, “r”), the filename requires const char*.String processing in <cstring> header file functions, such as strlen(), strcmp(), strstr(), etc.System calls and third-party libraries: many low-level APIs or interfaces of old third-party libraries.
Summary: std::string is the preferred way to manage strings in modern C++, as it is safe, convenient, and powerful.const char* represents a lower-level, manually managed C language tradition. Understanding the differences between the two and learning to convert through the .c_str() bridge when necessary.strlen vs. sizeof
In C++, “length” and “size” are often two completely different concepts.strlen and sizeof are typical representatives of these two concepts: one dynamically calculates content length at runtime, while the other statically obtains memory size at compile time. strlenstrlen is a function, declared in the <cstring> header file.Timing: at runtime. strlen must be executed when the program is actually running to calculate based on the data in memory.Working principle: its core is “traverse and count, stop at “. strlen takes a pointer to a character as a parameter, starts checking byte by byte from that address while counting, until it finds the first null character . It returns the actual length of the content, excluding .Bottom-level implementation pseudocode:We can easily write a simulation of strlen to understand its working principle:
From the pseudocode, we can see that this is a runtime loop dependent on actual memory content.sizeofsizeof is an operator, built into the C++ language, just like +, -, *, /.Timing: at compile time. The compiler analyzes the definitions of variables or types during compilation and directly replaces sizeof(…) with a specific constant value. By the time the program runs, the calculation of sizeof has already been completed.Working principle: its core is “look up type definition, return memory usage”. sizeof calculates the total number of bytes occupied by a variable or a type in memory. It does not care what is stored in memory; it only cares about how big this “container” is. Comparative ExperimentLet’s use a piece of code to call strlen and sizeof on three common string forms: char*, char[], and std::string, and observe and analyze the differences in their outputs.
Output results (on x64 system):
Analysis Case One:
Analysis Case Two:
Analysis Case Three:
SummaryThe difference between strlen and sizeof is not merely whether or not to include .They are two tools that operate in completely different dimensions:(1) strlen is a runtime content length calculator.(2) sizeof is a compile-time memory usage calculator. Using sizeof on different types (pointers, arrays, classes) will yield completely different results, as it reflects the static memory definition of that type.strcpy, memcpy vs. memmove
strcpychar* strcpy(char* dest, const char* src);strcpy is a copy function specifically designed for C-style strings.Working method: it starts copying characters from the source address (src) to the destination address (dest) and continues until it encounters the character in src. Finally, it also copies to dest.Core flaw: does not perform boundary checks.This is the “original sin” of strcpy. It completely trusts the programmer, assuming that dest always has enough space to accommodate all of src’s content. If this assumption is incorrect, a buffer overflow will occur, leading to program crashes or more serious security vulnerabilities.Bottom-level implementation pseudocode:
From the implementation, we can see that the only stopping condition for the loop is *src equals , with no checks on the capacity of dest.Example of incorrect usage of strcpy:
Therefore, due to its inherent insecurity, strcpy should always be avoided in modern C++ code. You can use the assignment operation of std::string or the safer C11 standard function strcpy_s.memcpyvoid* memcpy(void* dest, const void* src, size_t count);Note the difference here with strcpy; the return value and parameters are void* types, while strcpy’s return value and parameters are char* types, indicating that memcpy is more general.If using a string type variable, the difference between memcpy(s1, s2, 10) and strcpy(s1.c_str(), s2.c_str()).memcpy is a more general memory copy function that does not care whether the content is a string.Working method: it copies a specified number (n) of bytes from the src address to the dest address. It does not care about or any other special characters; it simply performs byte transfer.Advantages:Relatively safe: because it requires the programmer to provide a clear length n, this forces us to consider whether the target buffer is large enough before calling it, thus avoiding the blind copying of strcpy (i.e., relying on the programmer’s judgment, but the system does not enforce checks).
Efficient: since the length n is known, the compiler and library functions can perform the copy in a highly optimized manner, such as using special CPU instruction sets to move large blocks of data at once, rather than checking byte by byte.Core flaw: undefined behavior with overlapping memory.The standard for memcpy states that if the memory areas pointed to by src and dest overlap, its behavior is undefined. This means the program may crash, produce incorrect results, or “happen to” work normally.Bottom-level pseudocode implementation:
Note that this simple implementation from front to back will have issues with memory overlap.memmovevoid* memmove(void* dest, const void* src, size_t count);memmove exists to solve the memory overlap problem.Working method: similar to memcpy, it copies n bytes. But it adds a crucial safety check internally.Advantages: absolutely safe. Regardless of whether the memory areas of src and dest overlap, memmove will always ensure correct results.Principle: intelligently determines the copy direction.Before executing the copy, memmove compares the addresses of src and dest to determine whether there is overlap and the type of overlap:(1) No overlap or dest is before src: in this case, copying from front to back is safe, and memmove behaves like memcpy.(2) dest is behind src and there is overlap: in this case, if copying from front to back, the latter half of src’s data will be overwritten prematurely. memmove will intelligently switch to copying from back to front, thus perfectly avoiding this issue.Bottom-level implementation of memmove:
Comparison of the three:
strlen vs. sizeof
std::string The efficiency issue of std::string stems from its core design philosophy: it is a resource owner.A std::string object typically consists of the following three core members:A pointer (char*): pointing to memory dynamically allocated on the heap for storing the actual character sequence.A length (size_t): records the number of valid characters in the current string.A capacity (size_t): records how many characters the currently allocated heap memory can hold.This design means that the std::string object itself (usually on the stack) is merely a “remote control”; it owns and manages a separate, potentially very large heap memory.For a detailed explanation of string, refer to my article: 【C++】High-Frequency Interview Questions: Stringstd::string_viewTo solve the unnecessary copy problem of string, C++17 introduced string_view. It adopts a completely different strategy: it gives up ownership and only acts as an observer.1. Internal structure: a “lightweight” viewstd::string_view’s internal structure is extremely simple, typically containing two members:(1) a constant pointer (const char*): pointing to the starting position of external string data.(2) a length (size_t): records the number of characters observed by this view.It does not own any character data and does not perform any memory allocation. It is merely a “window” through which we can “see” a segment of already existing string data.Below is a simple implementation of the six member functions of string_view:
Below is the test call:
Running results:
1. Core members: the code clearly shows that MyStringView only has data_ and size_ as members, with no dynamically allocated resources.2. Copy constructor and assignment: MyStringView(const MyStringView& other) and operator= implementations are extremely simple, just copying the values of the two member variables.3. Destructor: ~MyStringView() is an empty function. Because it does not own any resources, it does not need to release any resources. This sharply contrasts with std::string, which must call delete[] upon destruction.4. Move constructor and assignment: for MyStringView, which is a very simple type with low copy cost (Plain Old Data, POD-like), the move operation and copy operation are almost indistinguishable at the assembly level.5. In the implementation, we see that the logic of move construction and copy construction is identical. This also explains why in the test output, even when using std::move, the original object sv1 remains valid and unchanged.6. Main function test: process_view(sv1) and process_view(s2) demonstrate the power of MyStringView as a function parameter. Whether passing from std::string or const char*, it will only trigger a single cheap MyStringView copy constructor, and will not incur any deep copy of std::string.When to use string and string_viewRecommended scenarios for using std::string: when you need a variable that truly owns and manages string data, use std::string.1.When you need to modify the string content: std::string_view is read-only.2.When you need to store data passed from outside the function: if a function needs to keep the passed string for future use (beyond its lifecycle), it must copy it into a std::string.3.As a function return value (usually): when a function needs to create a new string and return it, std::string is a safe choice.Recommended scenarios for using std::string_view: when you only need a temporary, read-only “window” to observe a string, use std::string_view.1.As a function parameter: this is the most core and widespread use of std::string_view. It can accept std::string, const char*, string literals, and other types of arguments without producing any copies.2.Extracting multiple substrings from a large string for processing: using sv.substr() will not incur new memory allocation, making it very efficient.SummaryThis article first discusses the differences between const char* (or char*) and string:(1)const char* is a pointer that points to the address of char type; it does not store any content itself, and its size is also the size of a pointer, ending with ‘/0’.(2)string is a class object that has member variables/functions, etc.; the member variables in string include const char* data_, size_t size_, size_t capacity.(3)When you want to use a string class object to call a function with const char* type parameters, use string.c_str().The second part explains the difference between strlen and sizeof:(1)strlen is a function that works at runtime, calculating the actual size of the variable, ending with ‘/0’, and does not include ‘/0’.(2)sizeof is a keyword determined at compile time; it does not calculate the size of the variable’s content but rather the actual size of the variable.Using sizeof on different types (pointers, arrays, classes) will yield completely different results, as it reflects the static memory definition of that type.The third part explains the differences in memory operation functions, including strcpy, memcpy, and memmove:(1)strcpy has parameters of const char* type, so when using string, it needs to be converted using c_str(). It is also extremely unsafe, as it does not check the size of the target address; if used incorrectly, exceptions may occur.(2)memcpy has parameters of void* type, so string can be used directly. It has three parameters, with the third parameter indicating the length of characters to copy. Compared to strcpy, it is somewhat safer, but it still relies on the programmer’s judgment, as the compiler does not perform checks.(3)memmove has the same parameters as memcpy. Compared to memcpy, it has a check that examines whether there is overlap; if there is, it copies from back to front, avoiding undefined errors.The fourth part discusses string and string_view:(1)string is an “owner” type; its member variables include a pointer pointing to an address on the heap. Its member functions, including constructors/copy constructors/assignment constructors, all perform deep copies, which may not be efficient.(2)string_view, introduced in C++17, is a “view” type that does not own. Its member variables only include a pointer and a length. Its member functions do not perform deep copies; they only copy pointers and lengths, making it very lightweight.