跳至主要內容

【专题】静态、动态与指针、数组、字符串

LincZero大约 8 分钟

【专题】静态、动态与指针、数组、字符串

【字符串比较】C-风格、string类、cstring类

  • C-风格字符串
    • 提供新特性:使用字符串方式快速初始化变量,而无需数组
  • C++ String类(比起cstring库更加方便)
    • 头文件string,位于名称空间std
    • 提供新特性:简单变量的声明赋值方式,字符串的简便拼接附加功能
  • C语言cstring库(旧)
    • 头文件cstringstring.h
    • 提供新特性:strcpystrncpystrcatstrncat方法

【数组比较】数组、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增加整个数组长度个地址)
  • 数组指针等价性

    • 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解释说明
值(真实内存)——12345678901981919219819192存储的是变量中的值
值(打印值)00EFFB601234567890(任意值)(任意值)
变量状态有值不存储已赋值已赋值定义未赋值越界
地址(真实值)012E6D08012E6D08012E6D0C012E6D10012E6D14这里的地址是虚拟地址,汇编代码是寄存器地址?
地址(打印值)00EFFB60012E6D08012E6D0C012E6D10012E6D14编译器只把输出字符数组的地址解释为打印字符串
变量名pnpn[0]pn[1]pn[2]pn[3]汇编代码不存在变量名,编译时会检查以防止出错
变量类型int_pointerintintintint汇编代码不存在变量类型
变量名解释首值的地址指针名 == &指针名[0]
变量打印解释打印值(地址)打印值打印值打印值打印值当打印的值为地址时,会输出
变量地址打印解释
程序跟踪量new分配的内存大小

int [3] arn; arn[0] = 12345; arn[1] = 67890;为例

指针和数组具有等价性

参数数组名内存1内存2内存3内存4解释说明
值(真实内存)——1234567890198655791114008320存储的是变量中的值
值(打印值)00EFFAA41234567890(任意值)(任意值)汇编代码中:数组变量名被解释为地址
变量状态有值不存储已赋值已赋值定义未赋值越界
地址(真实值)(&arn不是)00EFFB6C00EFFB7000EFFB7400EFFB78这里的地址是虚拟地址,汇编代码是寄存器地址?
地址(打印值)00EFFB6C00EFFB6C00EFFB7000EFFB7400EFFB78编译器只把输出字符数组的地址解释为打印字符串
变量名arnarn[0]arn[1]arn[2]arn[3]汇编代码不存在变量名,编译时会检查以防止出错
变量类型int_arrayintintintint汇编代码不存在变量类型
变量名解释首值的地址数组名 == &数组名[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解释说明
值(真实内存)——971140???存储的是变量中的值
值(打印值)00EFFB64ar\0(任意值)汇编代码中:数组变量名被解释为地址
变量状态有值不存储已赋值已赋值定义未赋值越界
地址(真实值)(&arc不是)00EFFB6400EFFB6500EFFB6600EFFB67这里的地址是虚拟地址,汇编代码是寄存器地址?
地址(打印值)arar(空字符)烫烫烫烫?0编译器只把输出字符数组的地址解释为打印字符串
(强制转换打印)00EFFB6400EFFB6400EFFB6500EFFB6600EFFB67
变量名arcarc[0]arc[1]arc[2]arc[3]汇编代码不存在变量名,编译时会检查以防止出错
变量类型char_arraycharcharcharchar汇编代码不存在变量类型
变量名解释首值的地址数组名 == &数组名[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;
}