A first way of declaring a C-string is this:
char s = "Nothing more than a simple string";Here we are using the array notation. So we are saying to the compiler we won't use the name "s" to refer to anything else to that memory location initialized to contain the specified text (and a '\0' to mark its end). Sure we can change the memory itself, for instance we could change the string first letter:
s = 'M';But we can't reuse "s", this line won't even compile:
s = "Another string"; // compiler error
If we want to reuse the variable name to point to different strings in memory, we should use instead the pointer notation:
char* s2 = "Nothing more than a simple string";In this case is legal assign a new string to our variable:
s2 = "Another string";But we can't modify the string itself. If we try to do something like this:
s2 = 'B'; // access violationwe are about to crash our application.
If we are using Visual C, we can use a Microsoft extension to try-catch the code:
char* s2 = "Nothing more than a simple string";
__try // 1.
s2 = 'B'; // access violation
__except(EXCEPTION_EXECUTE_HANDLER) // 2.
if(GetExceptionCode() == 0xc0000005) // 3.
// access violation
1. The __try keyword introduces something very similar to the standard C++ try block.
2. And __except is something like the catch C++ clause, but it expects and integer defining what to do. The define EXCEPTION_EXECUTE_HANDLER specifies to stop the current execution flow, passing to the associated block, and then to the first line after the __except block.
3. GetExceptionCode() returns the code associated with the reason for the code to generate an exception. The exception code for an access violation is defined in standard Windows include file as EXCEPTION_ACCESS_VIOLATION and STATUS_ACCESS_VIOLATION.
If you are writing code that could generated such a problem in C++ (again, Visual C++) you can use the standard try/catch mechanism, but you should tell to the compiler that you want to use the asynchronous exception handling model (option /EHa - and not the standard /EHs). This leads to a code that is bigger and slower - but safer.
We have seen that we have two choices: having a variable bound to a block of memory that we can modify as we please (array notation), or using a pointer, that we can freely associate to different strings that couldn't be changed.
But using pointers we could easily get much more freedom:
char* p = s; // 1.
*p = 'B'; // 2.
p = s + 10; // 3.
1. We say that p points to the same chunk of memory pointed by s, that has been initialized to be modifiable.
2. Being the string pointed by p modifiable, well, we can modify it.
3. And it is a pointer, free as air, so we can reuse it to point so something else (here: the substring starting 10 characters to the left of the original s).
Lot of freedom indeed. Someone could think too much freedom. Sometimes we want to put a limit to what one should do with a string.
If we don't want the memory be changed, we could say that our pointer is a const one:
const char* r = s;
*r = 'S'; // compiler error
r = s + 15;
A "const char*" is a pointer to a string that won't change. If we try to modify the associated memory using this pointer, we'll get a compiler error. On the other side, we could reuse the pointer to refer to another memory location.
If we want the pointer never change its associated block of memory we write:
char* const t = s;
*t = 'S';
t = s + 12; // compiler error
A "char* const" allows us to modify the memory it points to, but if we try to reuse it for another memory location we have a compiler error.
An sure we can combine the constantness:
const char* const z = s;
*z = 'K'; // compiler error
z = s + 10; // compiler error