【专题】静态、动态与指针、数组、字符串
大约 8 分钟
【专题】静态、动态与指针、数组、字符串
【字符串比较】C-风格、string类、cstring类
- C-风格字符串
- 提供新特性:使用字符串方式快速
初始化
变量,而无需数组
- 提供新特性:使用字符串方式快速
- C++ String类(比起cstring库更加方便)
- 头文件
string
,位于名称空间std
中 - 提供新特性:简单变量的
声明
、赋值
方式,字符串的简便拼接
、附加
功能
- 头文件
- C语言cstring库(旧)
- 头文件
cstring
或string.h
- 提供新特性:
strcpy
、strncpy
、strcat
、strncat
方法
- 头文件
【数组比较】数组、vector、array
比较类型 | 特点 | 优点 | 缺点 |
---|---|---|---|
数组 | 长度固定,使用栈 | 性能好 | 不安全、不方便 |
模板类 array(C++11) | 长度固定,使用栈,可使用成员函数at() 、begin() 、end() | 更安全 | |
动态数组 | 长度可变,使用堆 | 更方便 | 不安全 |
模板类 vector | 长度可变,使用堆,可使用成员函数at() 、begin() 、end() | 更方便、更安全 | 效率稍低 |
注意:
- array类和vector类的成员函数
at()
与中括号表示法类似,但会在运行期间
捕获非法索引,中断程序 - array类和vector类的成员函数
begin()
、end()
则可以检查边界,以免无意间越界。而==C/C++不检查数组越界错误==(安全性的体现) - 额外检查的代价是运行时间更长
【比较】字符串、数组
参数 | 数组 | 字符串 | 区别 |
---|---|---|---|
变量名 | C/C++将数组名解释为第一个元素的地址 | C/C++将字符串名解释为第一个字符的地址 | 无区别 |
定义方式 | 不可以使用C-风格字符串 方式 | 可以使用C-风格字符串 方式 | 有区别 |
输出方式 | 当给cout 提供一个元素的地址时则它只打印地址本身 | 当给cout 提供一个字符的地址时则它将从该字符开始打印,直到遇到空字符为止 | 有区别 |
【比较】指针、数组/字符串
指针、数组(符号操作:指针算术)
原理概述
- 指针和数组基本等价的原因在于指针算术(pointer arithmetic),指针变量加1后,增加的量等于它指向的类型的字节数
数组名
- C/C++将
数组名
解释为第一个元素的地址(长度为元素类型长度,地址+1只增加元素类型长度个地址) - 注意:
对数组名取的地址
则被解释为整个数组的地址(长度为数组长度,地址+1增加整个数组长度个地址)
- C/C++将
数组指针等价性
arrayname[i] == *(arrayname+i)
~~(becomes是“变成”的意思)~~前者是数组表示法,后者是指针表示法arrayname[r][c] == *(*(arrayname+r)+c)
指针、字符串
字符串与数组类似,但有一点不同:
- 当给
cout
提供一个字符的地址时,则它将从该字符开始打印,直到遇到空字符为止 - 用引号括起的字符串也会像数组名一样处理,也是第一个元素的地址(但不建议用这种方式处理,不安全)
- C++中字符串字面值被视为常量,修改会导致运行阶段错误
- C++不能保证字符串字面值被唯一地存储
这种设计的好处:不需要逐个传递字符串中所有字符,工作量减少
【比较】静态数组、动态数组
动态数组、静态数组
- 静态数组:编译时给数组分配内存,称为静态联编(static binding)
- 动态数组:运行时给数组分配内存,称为动态联编(dynamic binding),这种数组叫动态数组(dynamic array)
内存块
- 需要注意的是new分配的内存块通常与常规变量声明分配的内存块不同
- 常规变量声明的值存储在:栈(stack)的内存区域中
- new方式分配的内存在:堆(heap)或自由存储区(free store)的内存区域
【比较】大杂烩(实验与底层原理总结)
指针、动态数组、静态数组、字符串
变量在内存中的情况(Release x86)
以int * pn = new int [3]; pn[0] = 12345; arn[1] = 67890;
为例
参数 | 指针名 | 内存1 | 内存2 | 内存3 | 内存4 | 解释说明 |
---|---|---|---|---|---|---|
值(真实内存) | —— | 12345 | 67890 | 19819192 | 19819192 | 存储的是变量中的值 |
值(打印值) | 00EFFB60 | 12345 | 67890 | (任意值) | (任意值) | |
变量状态 | 有值不存储 | 已赋值 | 已赋值 | 定义未赋值 | 越界 | |
地址(真实值) | 012E6D08 | 012E6D08 | 012E6D0C | 012E6D10 | 012E6D14 | 这里的地址是虚拟地址,汇编代码是寄存器地址? |
地址(打印值) | 00EFFB60 | 012E6D08 | 012E6D0C | 012E6D10 | 012E6D14 | 编译器只把输出字符数组的地址解释为打印字符串 |
变量名 | pn | pn[0] | pn[1] | pn[2] | pn[3] | 汇编代码不存在变量名,编译时会检查以防止出错 |
变量类型 | int_pointer | int | int | int | int | 汇编代码不存在变量类型 |
变量名解释 | 首值的地址 | 值 | 值 | 值 | 值 | 指针名 == &指针名[0] |
变量打印解释 | 打印值(地址) | 打印值 | 打印值 | 打印值 | 打印值 | 当打印的值为地址时,会输出 |
变量地址打印解释 | ||||||
程序跟踪量 | new分配的内存大小 |
以int [3] arn; arn[0] = 12345; arn[1] = 67890;
为例
指针和数组具有等价性
参数 | 数组名 | 内存1 | 内存2 | 内存3 | 内存4 | 解释说明 |
---|---|---|---|---|---|---|
值(真实内存) | —— | 12345 | 67890 | 1986557911 | 14008320 | 存储的是变量中的值 |
值(打印值) | 00EFFAA4 | 12345 | 67890 | (任意值) | (任意值) | 汇编代码中:数组变量名被解释为地址 |
变量状态 | 有值不存储 | 已赋值 | 已赋值 | 定义未赋值 | 越界 | |
地址(真实值) | (&arn不是) | 00EFFB6C | 00EFFB70 | 00EFFB74 | 00EFFB78 | 这里的地址是虚拟地址,汇编代码是寄存器地址? |
地址(打印值) | 00EFFB6C | 00EFFB6C | 00EFFB70 | 00EFFB74 | 00EFFB78 | 编译器只把输出字符数组的地址解释为打印字符串 |
变量名 | arn | arn[0] | arn[1] | arn[2] | arn[3] | 汇编代码不存在变量名,编译时会检查以防止出错 |
变量类型 | int_array | int | int | int | int | 汇编代码不存在变量类型 |
变量名解释 | 首值的地址 | 值 | 值 | 值 | 值 | 数组名 == &数组名[0] |
变量打印解释 | 打印值(地址) | 打印值 | 打印值 | 打印值 | 打印值 | |
变量地址打印解释 | ||||||
程序跟踪量 | 数组长度 |
以 char arc[3] = { 'a', 'r' };
为例
地址打印纸和强制转换打印值的分析:同一个地址,相同的值,即可以用字符来解释他也能用字符数组来解释他,两种解释的结果不同。这也表明了内存中并不会存储变量值的变量类型、汇编代码中不存在变量名和变量类型的概念,只会麻木地进行各种运算
注意:
数组名
被解释为第一个元素的地址(长度为元素类型长度,地址+1只增加元素类型长度个地址)对数组名取的地址
则被解释为整个数组的地址(长度为数组长度,地址+1增加整个数组长度个地址)可以理解为被解释为多维数组,取地址后该数组被视作上一层数组的元素(这个元素是数组,而第一个元素就是当前数组)
a[0][0]
中,a[0] == &a[0][0]
,a == &a[0]
(下面的"烫"是Debug下运行的结果,而不是Release)
参数 | 数组名 | 内存1 | 内存2 | 内存3 | 内存4 | 解释说明 |
---|---|---|---|---|---|---|
值(真实内存) | —— | 97 | 114 | 0 | ??? | 存储的是变量中的值 |
值(打印值) | 00EFFB64 | a | r | \0 | (任意值) | 汇编代码中:数组变量名被解释为地址 |
变量状态 | 有值不存储 | 已赋值 | 已赋值 | 定义未赋值 | 越界 | |
地址(真实值) | (&arc不是) | 00EFFB64 | 00EFFB65 | 00EFFB66 | 00EFFB67 | 这里的地址是虚拟地址,汇编代码是寄存器地址? |
地址(打印值) | ar | a | r | (空字符) | 烫烫烫烫?0 | 编译器只把输出字符数组的地址解释为打印字符串 |
(强制转换打印) | 00EFFB64 | 00EFFB64 | 00EFFB65 | 00EFFB66 | 00EFFB67 | |
变量名 | arc | arc[0] | arc[1] | arc[2] | arc[3] | 汇编代码不存在变量名,编译时会检查以防止出错 |
变量类型 | char_array | char | char | char | char | 汇编代码不存在变量类型 |
变量名解释 | 首值的地址 | 值 | 值 | 值 | 值 | 数组名 == &数组名[0] |
变量打印解释 | 打印值(地址) | 打印值 | 打印值 | 打印值 | 打印值 | |
变量地址打印解释 | ||||||
程序跟踪量 | 字符数组长度 |
连续定义下的地址总结
00EFFB60~00EFFB63:栈,指针,占4字节
00EFFB64~00EFFB67:栈,字符串,占1*4字节
00EFFB68~00EFFB6B:空白,占4字节
00EFFB6C~00EFFB7B:栈,数组,占4*4字节
012E6D08~012E6D17:堆,数组,占4*4字节
实验代码
#include <iostream>
using namespace std;
int main()
{
/*内存情况*/
int* pn = new int[3];
pn[0] = 12345;
pn[1] = 67890;
cout << pn << endl;
cout << pn[0] << endl;
cout << pn[1] << endl;
cout << pn[2] << endl;
cout << pn[3] << endl;
cout << &pn << endl;
cout << &pn[0] << endl;
cout << &pn[1] << endl;
cout << &pn[2] << endl;
cout << &pn[3] << endl;
cout << "————————" << endl;
int arn[3];
arn[0] = 12345;
arn[1] = 67890;
cout << arn << endl;
cout << arn[0] << endl;
cout << arn[1] << endl;
cout << arn[2] << endl;
cout << arn[3] << endl;
cout << &arn << endl;
cout << &arn[0] << endl;
cout << &arn[1] << endl;
cout << &arn[2] << endl;
cout << &arn[3] << endl;
cout << "————————" << endl;
char arc[3] = { 'a', 'r' };
cout << arc << endl;
cout << arc[0] << endl;
cout << arc[1] << endl;
cout << arc[2] << endl;
cout << arc[3] << endl;
cout << &arc[0] << endl;
cout << &arc[1] << endl;
cout << &arc[2] << endl;
cout << &arc[3] << endl;
cout << (int*)&arc[0] << endl; // 转指针类型技巧
cout << (int*)&arc[1] << endl;
cout << (int*)&arc[2] << endl;
cout << (int*)&arc[3] << endl;
// 旧方法cout << (long)&arc << endl; // 但会忽略前面的零而且不是16进制
cout << "————————" << endl;
char arc2[4] = { 'a', 'r','c'};
cout << (arc2 == &arc2[0]) << endl;
cout << arc2 << endl;
cout << &arc2[0] << endl;
cout << &arc2[1] << endl;
cout << &arc2[2] << endl;
}