-
数组:
- 当你声明一个字符数组并用字符串字面量初始化时,例如
char array[] = "zhanghonghua";
,数组会存储整个字符串,包括结尾的空字符'\0'
。数组的大小是根据初始化的字符串自动确定的。
- 当你声明一个字符数组并用字符串字面量初始化时,例如
-
指针:
- 当你声明一个字符指针并用字符串字面量初始化时,例如
char *ptr = "zhanghonghua";
,指针存储的是字符串首字符的地址。指针本身不存储整个字符串,它只是指向存储字符串的内存位置。
- 当你声明一个字符指针并用字符串字面量初始化时,例如
这里有几个关键点需要注意:
- 数组名在大多数表达式中会被解释为指向数组首元素的指针,但在函数参数传递时,数组名会退化为指向数组首元素的指针。
- 字符串字面量通常存储在程序的只读数据段中,因此通过指针引用它们是安全的,但尝试修改它们将导致编译时或运行时错误。
- 字符数组可以存储可修改的字符串,因为它们通常在栈上或通过动态内存分配在堆上分配,这些内存区域是可写的。(下面有详细解释)
下面是一个简单的例子,展示了数组和指针在存储字符串时的不同:
// 使用数组存储字符串
char array[] = "Hello, World!";
array[0] = 'h'; // 合法操作,修改第一个字符为小写的 'h'
// 使用指针存储字符串的首地址
char *ptr = "Hello, World!";
// ptr[0] = 'h'; // 非法操作,因为字符串字面量存储在只读内存区域
在这个例子中,修改数组 array
是合法的,但尝试修改通过指针 ptr
引用的字符串将导致编译错误,因为字符串字面量是不可修改的。
当然,让我进一步解释一下字符数组和它们是如何存储可修改字符串的。
1. **字符数组在栈上分配**:
当你在函数内部定义一个字符数组时,这个数组是在栈上分配的。栈是用于存储局部变量的内存区域,它在函数调用时自动分配,在函数返回时自动释放。栈上分配的内存是可写的。
void function() {
char charArray[50] = "initial string"; // 在栈上分配的字符数组
charArray[0] = 'A'; // 可以修改数组中的字符
}
在这个例子中,`charArray` 是一个局部变量,它在函数的栈帧上分配。你可以修改这个数组中的字符,因为栈内存是可写的。
2. **字符数组通过动态内存分配在堆上**:
使用 `new` 或 `malloc` 可以在堆上分配内存。堆是用于动态内存分配的内存区域,它需要程序员手动管理(分配和释放)。堆上分配的内存也是可写的。
char* dynamicCharArray = new char[50]; // 在堆上使用 new 分配的字符数组
strcpy(dynamicCharArray, "another string"); // 可以复制字符串到这个数组
dynamicCharArray[0] = 'A'; // 可以修改数组中的字符
delete[] dynamicCharArray; // 释放堆上分配的内存
在这个例子中,`dynamicCharArray` 是一个指针,指向在堆上分配的内存。你可以使用 `strcpy` 函数复制一个字符串到这个数组,也可以直接修改数组中的字符。使用完毕后,需要使用 `delete[]` 来释放分配的内存,防止内存泄漏。
3. **为什么数组可以存储可修改的字符串**:
- 字符数组存储的是实际的字符数据,而不是字符数据的引用。因此,当你修改数组中的字符时,你是在修改实际存储在内存中的字符。
- 由于栈和堆上的内存都是可写的,字符数组可以存储可修改的字符串。
4. **与指针的区别**:
- 指针本身不存储字符串,它只存储一个地址,指向存储字符串的内存位置。如果指针指向的是字符串字面量,那么这个字符串是存储在只读内存区域的,不能被修改。
- 当你使用字符数组时,你实际上是在操作一个存储实际字符数据的内存区域,因此可以修改它。
总结来说,
字符数组可以存储可修改的字符串,因为它们直接在可写的内存区域(栈或堆)上存储字符数据。
而指针只是存储地址,是否可修改取决于它指向的内存区域的可写性。
这里进一步明确一下:
-
栈(Stack):
- 栈是用于存储函数调用时的局部变量和返回地址等信息的内存区域。
- 栈上分配的内存是可写的。当你在函数内部定义一个局部变量时,这个变量存储在栈上,你可以修改它的值。
-
堆(Heap):
- 堆是用于动态内存分配的内存区域,程序员可以通过
malloc
、calloc
、realloc
或 C++ 中的new
和new[]
等操作在堆上分配内存。 - 堆上分配的内存同样是可写的。程序员需要负责管理这部分内存,使用完毕后应通过
free
或delete
释放。
- 堆是用于动态内存分配的内存区域,程序员可以通过
-
字符串常量(String Literals):
- 字符串常量(如
"Hello, World!"
)通常存储在只读数据段中,如程序的文本段(.text
)或只读数据段(.rodata
)。 - 这些内存区域是不可写的,因为它们包含了程序运行时需要的固定数据,修改这些数据可能会导致程序行为不稳定或崩溃。
- 字符串常量(如
下面是一个简单的例子,展示如何在栈和堆上存储可修改的字符串,以及字符串常量的区别:
#include <cstring>
int main() {
// 在栈上存储可修改的字符串
char stackString[20] = "initial";
stackString[0] = 'I'; // 修改第一个字符为大写的 'I'
// 在堆上存储可修改的字符串
char* heapString = new char[20];
strcpy(heapString, "initial");
heapString[0] = 'I'; // 修改第一个字符为大写的 'I'
delete[] heapString; // 释放堆内存
// 字符串常量是不可修改的
const char* literal = "Hello, World!";
// literal[0] = 'h'; // 这将导致编译错误,因为字符串常量是只读的
return 0;
}
在这个例子中:
stackString
是在栈上分配的字符数组,可以修改。heapString
是在堆上分配的字符数组,也可以修改。literal
是一个指向字符串常量的指针,指向的字符串是不可修改的。尝试修改它将导致编译错误。
下面链接是整理的各个存储区的生存周期和是否支持数据的读写