指针、数组、字符串的恩怨,这有你想知道的一切
内存组成
为了讲明白不同方式下数组、字符串定义时在内存中的存放方式,需要先对计算机内存分区组成有所了解:
堆区
堆区 (Heap):由程序员手动申请释放的内存空间。
- C中:
malloc()
和colloc()
函数申请,用free()
释放
若不用
free()
释放,容易造成内存泄露(即内存被浪费、耗尽)。
ptr = (castType*) malloc(size);
传入参数为内存的字节数,内存未被初始化。
ptr = (castType*)calloc(n, size);
存入参数为内存块数与每块字节数,内存初始化为0
。
free(ptr);
释放申请的内存。
- C++中:
new
申请,delete
释放。new
和delete
都是操作符
int *arr = new int[10];
delete[] arr;
栈区
栈区 (Stack):由系统管理,存放函数参数与局部变量。函数完成执行,系统自行释放栈区内存。
静态存储区
静态存储区 (Static Storage Area):在编译阶段分配好内存空间并初始化。
其中全局区存放静态变量(static
修饰的变量)、全局变量(具有全局作用域的变量);常量区存放常量(又称为字面量)。
常量可分为整数常量(如1000L)、浮点常量(如314158E-5L)、字符常量(如'A'、'\n')和字符串常量(如"Hello")。
const
关键字修饰的的变量无法修改,但存放的位置取决于变量本身是全局变量还是局部变量。当修饰的变量是全局变量,则放在全局区,否则依然在栈区分配。
static
关键字修饰的变量存在全局区的静态变量区。
常变量与宏定义的概念不同。
常变量存储在静态存储区,初始化后无法修改。
宏定义在预处理阶段就被替换。不存在与任何内存区域。
代码区
代码区 (Code Segment):存放程序体的二进制代码。
代码示例
/*示例代码*/
int a = 0; //静态全局变量区
char *p1; //编译器默认初始化为NULL,存在静态全局变量区
void main()
{
int b; //栈
char s[] = "abc"; //栈
char *p1 = "123"; //"123"在字符串常量区,p1在栈区
p2 = (char *)malloc(10); //堆区
strcpy(p2, "123"); //"123"放在字符串常量区
const int d = 0; //栈
static int c = 0; //c在静态变量区,0为文字常量,在代码区
static const int d; //静态常量区
}
字符串定义 - 一维
方法一
char s[10] = "Hello"
内存:静态存储区上的字面量"Hello"
被复制到栈区,数组在栈区上的存储方式为'H''e''l''l''o''\0'
,可以通过s[i]
修改。但这不会影响到静态存储区上的"Hello"
。
定义与使用:
#include <stdio.h>
void f(char s[10]) { //等价于char *s
printf("%s\n", s);
}
int main() {
char s[10] = "LeeHero";
s[3] = 'Z';
printf("%s\n", s); //输出:LeeZero
printf("%s\n", s+1); //输出:eeZero
printf("%c\n", s[3]);//输出:Z
f(s); //数组名作为函数参数传递时,会退化成指向数组首元素的指针 !IMPORTANT
return 0;
}
格式控制符
%s
跟随一个地址,并当做是字符串第一个元素对应的地址.从该首地址开始解析,直到
'\0'
结束。在这里指的是
s[0] = 'H'
的地址。
方法二
char *s = "Hello"
// 等价于const char *s = "Hello"
内存:s是指向字面量"Hello"
的指针,字面量在静态内存区,因此该字符串不可被修改。
定义与使用:
#include <stdio.h>
void f(char s[10]) { //等价于char *s
printf("%s\n", s);
}
int main() {
char *s = "LeeHero";
//s[3] = 'Z'; //无法执行
printf("%s\n", s); //输出:LeeHero
printf("%s\n", s+1); //输出:eeHero
printf("%c\n", s[3]); //输出:H
f(s);
return 0;
}
字符串定义 - 二维
方法一
char s[10][10] = {"Hello","World"}
内存:静态存储区上的字面量"Hello"
,"World"
被拷贝在栈区,与一维定义方式同理,可以通过语法糖s[i][j]
修改字符。
定义与使用:
#include <stdio.h>
void f(char (*s)[10]) { //形参s是个指针,指向有10个元素的字符数组
//把(*s)[10] 改成 s[][10] ,其他不变,最后效果相同
printf("%s\n", s[1]); //输出:Zero
s[1][0] = 'H'; //通过语法糖s[i][j]修改字符
printf("%s\n", s[1]); //输出:Hero
printf("%c\n", s[0][1]); //输出:e
}
int main() {
char s[10][10] = {"Lee","Hero"};
//s[1] = "Hey"; //无法执行,这种赋值方式仅在初始化时可用
s[1][0] = 'Z';
printf("%s\n", s); //输出:Lee
printf("%s\n", *s+1); //输出:ee
printf("%s\n", s[0]+1); //输出:ee
printf("%c\n", *(s[0]+1)); //输出:e
printf("%c\n", s[0][1]); //输出:e
printf("%s\n", s+1); //输出:Zero
printf("%s\n", s[1]); //输出:Zero
f(s);
printf("%s\n", s[1]); //输出:Hero 这意味着函数内部的修改不是局部生效的
return 0;
}
对于打印结果