c++指针和自由储存空间

c++指针和储存空间

本博文为本人阅读c++ primer plus第4章复合类型后做的小笔记,仅记录本人不熟悉或者容易犯错的地方

概述

  1. 指针是一个变量,储存的是值得地址,而不是值本身
  2. 地址运算符&,可以获得变量的所在地址
  3. 指针名表示的是地址,*运算符被称为间接值(indirect value)或者解除引用(dereferencing);简单来说,使用*可以获得指针指向的内存地址的值
  4. 由指针的说明可以知道,指针的大小和机器的虚拟地址位数相同(因为它的值是地址),例如本人的电脑为64位,那么指针大小为8个字节(可用sizeof自己验证,顺便多说一句,内存中可寻址的最小单位为字节,sizeof显示的值单位为字节);

声明和初始化指针

  1. 声明(int类型指针): int* ptr;易错:int* ptr1, ptr2;创建一个指针ptr1和一个int变量ptr2

  2. 初始化

     int val = 2;
     ptr = &val;
    

指针的危险

  1. 在c++中创建指针,计算机会分配用来储存地址的内存,但不会分配用来储存指针所指的数据的内存

     long * fellow;
     *fellow = 233; // a problem
    
  2. 问题原因

    • 没有给fellow赋值一个地址,那么我们是不知道它现在指向哪个地方的,然后我们立刻将指向内存的位置赋值为233,那么233究竟在哪里呢?不知道,可以肯定的是这个位置已经储存了数据233
    • 假设fellow的值为1200,那么233会储存在内存地址为1200的位置,但是这个1200的位置很可能不是要储存233的内存位置
  3. 启示1:一定要在指针使用*前将指针初始化为一个确定的、适当的地址

使用new动态分配内存

  1. 指针的真正用武之地就是在运行时分配未命名的内存来储存值;c语言中使用malloc来分配,c++使用new运算符

  2. 事例:int * pn = new int

    • new运算符根据类型确定需要多少字节的内存(此例子为4字节),然后找到这样的内存,返回地址
    • 地址被赋给pn,pn是被声明为指向int的指针
  3. 使用new分配的内存块通常和常规变量声明分配的内存块不同

    • 常规变量(如int val):储存在栈(stack)中
    • new分配的变量(如int* p = new int):储存在称为堆(heap)或自由储存区(free store)中
  4. c++中,值为0的指针称为空指针(null pointer),c++确保空指针不会指向有效的数据,因此可以用于表示运算符或函数失败(如内存耗尽,无法再使用new分配内存)

使用delete释放内存

  1. 使用delete,后面加上指向内存块的指针(这些内存块最初由new分配);如下代码所示,这只会释放指针指向的内存,但是不会删除指针ps本身

     int * ps = new int;
     ...
     delete ps;
    
  2. 注意

    • 一定要配对使用new和delete,否则会发生内存泄漏(memory leak),即被分配的内存再也不能使用了
    • 不要尝试释放已经释放的内存块,这样做带来的结果是不确定的
    • 只能用delete释放使用new分配的内存,但是对空指针使用delete是安全的

使用new来创建动态数组

  1. 在编译时给数组分配内存称为静态联编(static binding),意味着数组是在编译时加入到程序的

  2. 在程序运行时选择数组的长度,这称为动态联编(dynamic binding),意味着数组是在程序运行时创建的,这种数组称为动态数组

  3. 创建动态数组例子:int * ps = new int[10];

    • new运算符返回第一个元素的地址,该地址被赋给ps
  4. 释放数组:delete [] ps;

    • []告诉程序应该释放整个数组,不仅仅是指针指向的元素
  5. 以上面的例子来说,编译器不会对ps指针指向10个整数中的第一个这种情况进行跟踪,我们需要跟踪内存块中的元素个数

  6. 不能使用sizeof运算符确定动态分配的数组包含的字节数

  7. 动态数组的使用和静态联编情况下的数组使用是相似的,用下标即可,例如ps[1],指针和数组这种奇妙的关系下面来学习一下

指针、数组和指针算术

  1. 指针变量增加1,增加的量等于它指向的类型的字节数,如指向double的指针增加1,那么数值增加8(如果系统使用8个字节储存double),这种结果是很好理解的。

  2. c++将数组名解释为地址

    • c++将数组名解释为数组第一个元素的地址
    • 数组存在这个等式: foobar = &foobar[0]
    • 形如foobar[n]的表达式(n为常量),c++解释为*(foobar + n),意味着先找到第n个元素的地址,再找到储存在那里的值;同理,对于int * ps = new int[10]ps[n]也会解释为*(ps + n).(0 <= n <= 9)
  3. 虽然指针和数组名都可以表示地址,但是它们是有区别的

    • 指针的值可以修改,但是数组名是常量不能修改
    • 对数组使用sizeof运算符得到的是整个数组的大小,但是对指针应用sizeof得到的是指针的大小
  4. 数组名被解释为其第一个元素的地址,但是对数组名用&运算符,得到的是整个数组的地址,可以尝试一下例子

     int a[3];
     cout << a << endl;
     cout << &a << endl;
     cout << a + 1 << endl;
     cout << &a + 1 << endl;
    

实际上,a是一个int指针(*int),但是&a是一个指向包含3个元素的int数组(int(*)a[3])
5. 声明指向整个数组的指针:int (*ps)[3] = &a;

- `()`不可省略,如果省略,由优先级规则将使得ps先和`[3]`结合,导致ps是一个int指针数组,含3个元素
- 要描述类型,可以去掉声明中的变量名,如ps的类型是`int(*)[20]`
- 由于ps被设置为`&a`,故`*ps`和a等价,`(*ps)[0]`为a数组的第一个元素

指针和字符串

  1. 先看简单的例子

     char f[10] = "rose";
     cout << f << "s are red\n";
    
  2. 可以知道:

    • f是一个char的地址。这也意味着可以将指向char的指针变量作为cout的参数,因为它也是地址
    • c++中用括号括起来的字符串像数组名一样,也是第一个元素的地址,此代码不会将整个字符串发送到cout,只会发送该字符串的地址
    • 因此:数组的字符串、用括号括起来的字符串常量、指针所描述的字符串的处理方式是一样的,都会传递它们的地址
  3. 字符串输入:应使用已经分配的内存地址,可以是数组名,也可以是new初始化过的指针

  4. 显示字符串地址

     char a[] = "test";
     cout << (int *) a << endl; // show address
     char* b = a;
    
    • 需要强制类型转换为int指针类型,否则会打印出字符串的值

    • 将a赋给b并不会赋值字符串,而只是复制地址,两个指针都是指向了同一个内存单元

    • 如果像得到自己的一份副本,可以

        b = new char[strlen(a)+1];
        strcpy(b, a);
      

使用new创建动态结构

  1. 创建:person * p = new person;

  2. 访问结构成员(如name):

    • p->name
    • (*p).name 因为如果p是指向结构的指针,那么*p就是被指向的值,结构本身

自动存储、静态存储、动态存储

  1. c++管理内存的方式

    • 自动存储
    • 静态存储
    • 动态存储(自由存储空间或堆)
    • 线程存储(不作介绍)

下面作简单的说明,详细内容不在本章内容

自动存储

  1. 函数内存定义的常规变量使用自动存储空间,称为自动变量(automatic variable),它们在函数调用时自动产生,函数结束时消亡
  2. 自动变量是局部变量,作用域为代码块
  3. 自动变量通常存储在栈中,LIFO(last in first out)的方式,程序执行过程中,栈不断增大或者减少

静态存储

  1. 静态存储整个程序执行期间都存在

  2. 声明方式

    • 函数外定义
    • 声明变量时使用static关键字:static double fee = 1.2;

动态存储

  1. new和delete的方式更加灵活,它们管理一个内存池,在c++中称为自由存储区(free store)或者堆(heap),该内存池中用于静态变量和自动变量的内存是分开的
  2. 使用new的数据的生命周期不受程序或者函数的生存时间控制

指针数组

  1. 创建并初始化:person * class[3] = {&p1, &p2, &p3};

    • 上述创建了一个person类型的指针数组class,每一个元素都是指向一个person结构体的指针
  2. 访问成员:class[n]->name;(0 <= n <= 2)

  3. 还可以创建指向上述数组的指针:person ** t = class;

    • 由于class是一个数组,因此它的数组名是数组第一个元素的地址,而这个元素是一个指针,它指向一个指向person类型的指针
    • t的值就是class数组一个元素的地址
  4. 访问成员(以name为例子):(*(t+n))->name;

  5. 如果不理解上面的内容,个人认为可以大致画出一下几种情况在内存中的情况,然后就能很好理解了(可以先用代码输出某一个变量的地址或者值)

     int a[2];
     int * b = new int[2];
     int ** c = new int * [2];
     for (int i = 0; i < 2; i++) c[i] = new int[2];
     int d[2][2];
    

你可能感兴趣的:(c++,计算机基础)