c++指针和储存空间
本博文为本人阅读c++ primer plus第4章复合类型后做的小笔记,仅记录本人不熟悉或者容易犯错的地方
概述
- 指针是一个变量,储存的是值得地址,而不是值本身
- 地址运算符&,可以获得变量的所在地址
- 指针名表示的是地址,
*
运算符被称为间接值(indirect value)或者解除引用(dereferencing);简单来说,使用*
可以获得指针指向的内存地址的值 - 由指针的说明可以知道,指针的大小和机器的虚拟地址位数相同(因为它的值是地址),例如本人的电脑为64位,那么指针大小为8个字节(可用sizeof自己验证,顺便多说一句,内存中可寻址的最小单位为字节,sizeof显示的值单位为字节);
声明和初始化指针
-
声明(int类型指针):
int* ptr;
易错:int* ptr1, ptr2;
创建一个指针ptr1和一个int变量ptr2 -
初始化
int val = 2; ptr = &val;
指针的危险
-
在c++中创建指针,计算机会分配用来储存地址的内存,但不会分配用来储存指针所指的数据的内存
long * fellow; *fellow = 233; // a problem
-
问题原因
- 没有给fellow赋值一个地址,那么我们是不知道它现在指向哪个地方的,然后我们立刻将指向内存的位置赋值为233,那么233究竟在哪里呢?不知道,可以肯定的是这个位置已经储存了数据233
- 假设fellow的值为1200,那么233会储存在内存地址为1200的位置,但是这个1200的位置很可能不是要储存233的内存位置
-
启示1:一定要在指针使用
*
前将指针初始化为一个确定的、适当的地址
使用new动态分配内存
-
指针的真正用武之地就是在运行时分配未命名的内存来储存值;c语言中使用
malloc
来分配,c++使用new
运算符 -
事例:
int * pn = new int
- new运算符根据类型确定需要多少字节的内存(此例子为4字节),然后找到这样的内存,返回地址
- 地址被赋给pn,pn是被声明为指向int的指针
-
使用new分配的内存块通常和常规变量声明分配的内存块不同
- 常规变量(如int val):储存在栈(stack)中
- new分配的变量(如int* p = new int):储存在称为堆(heap)或自由储存区(free store)中
-
c++中,值为0的指针称为空指针(null pointer),c++确保空指针不会指向有效的数据,因此可以用于表示运算符或函数失败(如内存耗尽,无法再使用new分配内存)
使用delete释放内存
-
使用delete,后面加上指向内存块的指针(这些内存块最初由new分配);如下代码所示,这只会释放指针指向的内存,但是不会删除指针ps本身
int * ps = new int; ... delete ps;
-
注意
- 一定要配对使用new和delete,否则会发生内存泄漏(memory leak),即被分配的内存再也不能使用了
- 不要尝试释放已经释放的内存块,这样做带来的结果是不确定的
- 只能用delete释放使用new分配的内存,但是对空指针使用delete是安全的
使用new来创建动态数组
-
在编译时给数组分配内存称为静态联编(static binding),意味着数组是在编译时加入到程序的
-
在程序运行时选择数组的长度,这称为动态联编(dynamic binding),意味着数组是在程序运行时创建的,这种数组称为动态数组
-
创建动态数组例子:
int * ps = new int[10];
- new运算符返回第一个元素的地址,该地址被赋给ps
-
释放数组:
delete [] ps;
[]
告诉程序应该释放整个数组,不仅仅是指针指向的元素
-
以上面的例子来说,编译器不会对ps指针指向10个整数中的第一个这种情况进行跟踪,我们需要跟踪内存块中的元素个数
-
不能使用sizeof运算符确定动态分配的数组包含的字节数
-
动态数组的使用和静态联编情况下的数组使用是相似的,用下标即可,例如
ps[1]
,指针和数组这种奇妙的关系下面来学习一下
指针、数组和指针算术
-
指针变量增加1,增加的量等于它指向的类型的字节数,如指向double的指针增加1,那么数值增加8(如果系统使用8个字节储存double),这种结果是很好理解的。
-
c++将数组名解释为地址
- c++将数组名解释为数组第一个元素的地址
- 数组存在这个等式:
foobar = &foobar[0]
- 形如
foobar[n]
的表达式(n为常量),c++解释为*(foobar + n)
,意味着先找到第n个元素的地址,再找到储存在那里的值;同理,对于int * ps = new int[10]
,ps[n]
也会解释为*(ps + n)
.(0 <= n <= 9)
-
虽然指针和数组名都可以表示地址,但是它们是有区别的
- 指针的值可以修改,但是数组名是常量不能修改
- 对数组使用sizeof运算符得到的是整个数组的大小,但是对指针应用sizeof得到的是指针的大小
-
数组名被解释为其第一个元素的地址,但是对数组名用
&
运算符,得到的是整个数组的地址,可以尝试一下例子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数组的第一个元素
指针和字符串
-
先看简单的例子
char f[10] = "rose"; cout << f << "s are red\n";
-
可以知道:
- f是一个char的地址。这也意味着可以将指向char的指针变量作为cout的参数,因为它也是地址
- c++中用括号括起来的字符串像数组名一样,也是第一个元素的地址,此代码不会将整个字符串发送到cout,只会发送该字符串的地址
- 因此:数组的字符串、用括号括起来的字符串常量、指针所描述的字符串的处理方式是一样的,都会传递它们的地址
-
字符串输入:应使用已经分配的内存地址,可以是数组名,也可以是new初始化过的指针
-
显示字符串地址
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创建动态结构
-
创建:
person * p = new person;
-
访问结构成员(如name):
p->name
(*p).name
因为如果p是指向结构的指针,那么*p
就是被指向的值,结构本身
自动存储、静态存储、动态存储
-
c++管理内存的方式
- 自动存储
- 静态存储
- 动态存储(自由存储空间或堆)
- 线程存储(不作介绍)
下面作简单的说明,详细内容不在本章内容
自动存储
- 函数内存定义的常规变量使用自动存储空间,称为自动变量(automatic variable),它们在函数调用时自动产生,函数结束时消亡
- 自动变量是局部变量,作用域为代码块
- 自动变量通常存储在栈中,LIFO(last in first out)的方式,程序执行过程中,栈不断增大或者减少
静态存储
-
静态存储整个程序执行期间都存在
-
声明方式
- 函数外定义
- 声明变量时使用static关键字:
static double fee = 1.2;
动态存储
- new和delete的方式更加灵活,它们管理一个内存池,在c++中称为自由存储区(free store)或者堆(heap),该内存池中用于静态变量和自动变量的内存是分开的
- 使用new的数据的生命周期不受程序或者函数的生存时间控制
指针数组
-
创建并初始化:
person * class[3] = {&p1, &p2, &p3};
- 上述创建了一个person类型的指针数组class,每一个元素都是指向一个person结构体的指针
-
访问成员:
class[n]->name;
(0 <= n <= 2) -
还可以创建指向上述数组的指针:
person ** t = class;
- 由于class是一个数组,因此它的数组名是数组第一个元素的地址,而这个元素是一个指针,它指向一个指向person类型的指针
- t的值就是class数组一个元素的地址
-
访问成员(以name为例子):
(*(t+n))->name;
-
如果不理解上面的内容,个人认为可以大致画出一下几种情况在内存中的情况,然后就能很好理解了(可以先用代码输出某一个变量的地址或者值)
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];