指针和引用

内存地址和间接访问
内存地址
程序运行时,代码和需要的数据都被存储在内存中
内存是有序的字节序列,每个字节都有唯一的地址,使用该地址可以确定字节的位置,用以存储和获取数据
直接访问和间接访问
通过变量的名字直接访问为程序中定义的变量分配的内存单元,存取变量的值
使用变量的内存地址找到存放数据的单元,间接访问其中的内容
指针
指针的特点:
指针持有一个对象的地址,称为指针“指向”该对象
通过指针可以间接操纵它指向的对象
定义指针变量的语法
每个指针都有相关的类型,要在定义指针时指出
类型 *指针变量;

int  *pi;
int* pi; 
char *pc1, *pc2;
char* pc1, pc2; 
char *pc1, pc2; 
char* pc1, *pc2;

取地址运算符“&”
指针存放指定类型对象的地址,要获取对象的地址,使用取地址运算符“&”

int ival = 120;
int  *pi = &ival; 
  // pi存放int变量ival的地址
  // 或者说pi指向ival
char ch = 'a', *pc = &ch;
  // pc指向字符型变量ch

指针解引用运算符“*”
如果指针指向一个对象,则可以通过指针间接访该对象,使用指针解引用运算符“*”

int x = 100, y = 20;
int *pi = &x;
*pi = y;  
  // 间接操作pi指向的x,即x = y

指向对象的指针
指向一个对象的指针有两个存储单元与之相关
一个是指针自己的存储单元,里面存放着所指对象的地址;
另一个就是指针指向的对象的存储单元,里面存放该对象的值。
可以定义存放指针对象的地址的指针

int ival = 1024;
int *pi = &ival;
int **ppi = π  //ppi是指针的指针,存放pi的地址

指针的类型
指针的类型即指针指向的类型
每个指针都有相关的类型,需要在定义指针时指出
type* pointer
指针的类型指出了如何解释该内存地址保存的内容,以及该内存区域应该有多大
不同类型指针的表示方法和保存的地址值并没有分别,区别只是指针指向的对象类型不同

int *pi;  
char *pc;

指针的值
指针不能保存非地址值,也不能被赋值或初始化为不同类型的地址值

int ival = 100;
int *pi = &ival;   // pi 被初始化为ival的地址
int *pi2 = ival;       // 编译错误,ival不是地址
double dval = 1.5;
pi = &dval;   // 编译错误
pi2 = 0;        // 正确:pi2是空指针

空指针
指针值为0时是一个空指针,即不指向任何对象的指针
表示空指针的2种方法:
0
预处理常量NULL

// 生成空指针的2种方法 
int *p1 = 0;       
int *p2 = NULL; 

//不能写成下面的样子:
int zero = 0;
int *p4 = zero;

使用指针的注意事项
定义指针时,应该对指针进行初始化
指针的运算:
同类型的指针可以进行相等(==)或不相等(!=)的比较操作,比较的结果是布尔类型
自增、自减运算适用于指向数组元素的指针
指针可以进行加或减整数值的算术运算

通用指针void*
void指针*
可以持有任何类型的地址值,即通用指针
相关的值是个地址,但是该地址保存的对象类型不知道
不能操纵void指针指向的对象,只能传送该地址值或者和其他地 址值进行比较
不允许void指针到其他类型指针的直接赋值

int a = 10;
char ch = 'k';
void* pv = &a;  //是否正确?
pv = &ch;   //是否正确?
int* pi = pv;   //是否正确?

指针的应用
指针的典型用法包括:

  1. 构建链式的数据结构,如链表和树;
  2. 管理程序运行时动态分配的对象;
  3. 作为函数的参数。

存储空间分配策略
静态(编译时)分配空间
编译器在处理程序源代码时分配内存;
效率高,灵活性差,运行前就要知道程序需要的内存大小和类型
动态(运行时)分配空间
程序运行时调用运行时刻库函数来分配内存;
占用程序运行时间,更灵活
静态和动态内存分配在语法上的主要区别:

  1. 静态对象是有名字的变量,可以直接对其进行操作;动态对象没有名字,要通过指针间接地对它进行操作。
  2. 静态对象的空间分配与释放由编译器自动处理,动态对象的空间分配与释放必须由程序员显式地管理。
    程序使用动态内存的原因:
    程序不知道自己需要使用多少对象
    程序不知道所需对象的准确类型
    程序需要在多个对象间共享数据

动态存储空间管理
堆(heap)、自由存储区、动态存储区
系统为所有程序提供了一个运行时可用的内存池,这个内存池被称为程序的自由存储区或堆
动态内存管理方法
C++通过new和delete运算符进行动态存储空间的管理
new运算符
在堆上动态分配空间,创建对象,并返回对象的地址
一般将new返回的地址保存在指针变量中,以便间接访问堆上的对象。
new表达式的三种形式
分配单个对象:new 类型 或者 new 类型(初始值)
在堆上分配特定类型的单个对象,并返回其地址

int* ip1 = new int; //在堆上分配一个int类型的对象,返回它的地址
*ip1 = 512;  //堆上分配的这个对象只能通过指针间接操作
int* ip2 = new int(100);  //在堆上分配一个int对象,初始化为100,返回其地址

分配多个连续存储的对象:new 类型[数组大小]
在堆上分配指定类型和大小的数组(连续内存空间),返回数组首地址

int* ipa = new int[100];
//在堆上分配一个大小为100的int数组并返回数组的首地址

不能对数组进行显式的初始化
数组大小不必是常量,是数组元素的个数,不是字节数
用new分配数组时,并未得到一个数组类型的对象,而是得到一个数组元素类型的指针

定位new,在指定位置分配空间:new (指针) 类型;
程序员可以预先分配大量的内存,以后通过定位new表达式在这段内存中创建对象
使用定位new,必须包含标准库头文件

#include <new>
char* buf = new char [1000]; 
  //预分配一段空间,首地址在buf中保存
int main(){
int* pi = new (buf) int; 
  //在buf中创建一个int对象,此时不再重新从堆上分配空间
}

delete运算符
堆上的空间在使用后必须释放,否则会造成内存泄漏,即动态内存空间使用后未回收,导致一直被占据。
new运算符分配的空间用delete运算符释放
释放new分配的单个对象的delete形式 delete 指针;
释放new分配的数组的delete形式
定位new没有对应的delete表达式 delete[] 指针;

释放new分配的单个对象 delete 指针;

int* ip = new int; ... //不再使用这个int对象时,释放内存
delete ip; //释放指针指向的int对象,将空间归还给动态存储区

释放new分配的数组 delete[] 指针;

int* pa = new int[100];  ... //不再使用这个数组时,释放内存
delete[] pa;  //释放指针pa指向的数组,将空间归还给动态存储区

空悬指针
执行delete运算后,指针ip指向的空间被释放,不能再使用ip指向的内存,但是ip这个指针变量自己的存储空间不受影响
delete后的ip不是空指针,而是“空悬指针”,即指向不确定的单元
delete之后,继续通过ip间接使用这个单元是非法的,会引起不可预料的运行错误

引用
引用类型
引用又称为别名,它可以作为对象的另一个名字;
通过引用可以间接地操纵对象;
在程序中,引用主要用作函数的参数。

引用的定义和初始化
引用由类型标识符和一个说明符(&)来定义
type& refVariable = leftValue;
引用必须被初始化,初始值是一个有内存地址的对象

int ival = 100;
int &refVal = ival; 
int &refVal2;    //是否正确?
int &refVal3 = &ival;  //是否正确?
int &refVal4 = 10;  //是否正确?

引用的初始化和一般变量的初始化不同
一般在初始化变量时,初始值会被复制到新建的对象中
定义引用时,程序把引用和它的初始值绑定在一起,而不是将初始值拷贝给引用

引用的初始化和一般变量的初始化不同
一旦初始化完成,引用将和它的初始值对象一直绑定在一起
因为无法令引用重新绑定到另外一个对象,所以引用必须初始化

引用关系
引用一旦初始化,就不能再指向其他的对象,对引用的所有操作都会被应用在它所指向的对象上
引用的初始化和赋值不同
初始化时引用被“绑定到”一个对象;
赋值时,引用被作为所绑定对象的别名

int x = 100, y = 20;
int &r = x; // r是x的引用
r = y;  // r不会变成y的引用,而是x = y

使用左值引用时注意
引用并非对象,是为已存在的对象所起的另一个名字
引用只能绑定到对象(有内存地址)上,不能与字面值或某个表达式的计算结果绑定在一起。

引用vs指针(1)
指针的定义形式
类型 *指针变量;
指针保存指定类型的对象的地址,一个指针可以指向同类型的不同对象

int x = 10, y = 20;
int *pi; //未初始化
pi = &x; 
pi = &y; 

引用的定义和初始化
类型 &引用名 = 初始值;
引用是一个对象的别名,定义引用时必须用有内存地址的对象初始化
引用在初始化之后,一直指向该对象

int a = 10, b=20;
int &ri = a; 
ri = b;        

引用vs指针(2)
使用方式
指针通过解引用(*)运算间接访问指向的对象
引用作为对象的别名,可以直接访问对象

int x, a, *pi;
pi = &x;
*pi = 30;  // x=30
int &ri = a;
ri = 40;  //a=40 

引用vs指针(3)
有空指针
指针可以不指向任何对象,其值为0,表示空指针
没有空引用
引用必须指向一个对象,而且一直指向该对象,不存在“空”引用。
引用的值如果为0,表示它指向的单元值为0

int a, *pi;
pi = 0;  // pi是空指针,不指向任何对象
int &ri = a;
ri = 0;  //a=0

引用vs指针(4)
赋值
指针之间的相互赋值会改变指向关系
引用之间的相互赋值是它们指向的对象之间的赋值,引用关系本身并不改变

int x = 100, y = 20;
int *p1 = &x, *p2 = &y;
p1 = p2;  // p1=&y, p1和p2都指向y
int &r1 = x, &r2 = y; 
r1 = r2;  // x = y, r1仍是x的引用

const限定指针
指向const对象的指针(非const )
const type cp; 或者type const cp;
cp是指向常量的指针,它所指向的内存中的内容不可以改变,即
cp的值不能改变
指向非const对象的const指针
type
const cp = initAddressValue;
cp是常量指针,初始化后值不能改变,指向固定的单元
指向const对象的const指针
const type* const cp = initAddressValue;

const int ival = 1024;
int *pi = &ival;  
 //错误:试图将一个const地址赋值给一个非const指针
const int ival = 1024;
const int *pi = &ival;   //OK
//或者这样写:
int const *pi = &ival;   //OK
*pi = 500;  
 //错误:因为*pi是一个const int

C++允许将一个非const地址赋值给const指针

int ival =1024;
const int *pi = &ival;
ival = 500;  //OK,ival没有被限定为const,可以改变
*pi = 500;  //错误: 不可以通过pi改变ival,因为pi是const int*

指向const对象的const指针

const int ival = 5;
const int* const pi = &ival; //pi是一个指向const对象的const指针

在pi的定义中
第一个const限定int,表示指针指向的单元是常量;
第二个const限定pi,表示指针的值也是一个常量
指针pi所在内存的值不允许改变,它所指向内存的值也不能改变

const限定引用
const限定的左值引用不可修改
const引用可以绑定到const对象
不能用非const引用指向const对象

const int ival = 5;
const int &r1 = ival;  //正确:引用和所引用的对象都是const int
r1 = 10;  //错误:r1是const的引用,不能修改
int &r2 = ival;  //错误:不能用非const引用指向const对象

const左值引用可以绑定到非const对象,但是const引用不能用来修改它所绑定的对象。

int ival = 10;   
const int &r1 = ival;//正确:允许将const引用绑定到非const对象上
ival = 20; //正确
r1 = 20; //错误:r1是const引用,不能修改

const左值引用可以绑定到生成右值的表达式,可以用任意表达式初始化const引用,只要表达式的结果能转换成引用的类型即可。

const int &r2=r1*2; //正确:r2是const引用
int &r3 = r1 * 2;  //错误:r3是普通非const引用
int& r4 = 10; //错误:r4是普通非const引用,字面值10不可寻址
const int &r5 = 10;  //正确:r5是const引用,编译器生成一个值为10的临时对象,r5指向这个对象

const限定引用的含义
const引用仅对引用自己可参与的操作进行了限定,对所指向的对象本身是不是常量未作限定。因为指向的对象也可能不是const,所以允许通过其他途径改变它的值

int ival = 5;
const int &r1 = ival; //正确:r1可以绑定ival,但不能通过r1修改ival
int &r2 = ival;
r1 = 0; //错误:r1是const引用,不能修改
r2 = 0;  // r2并非常量,可以修改ival为0

数组与指针
使用数组时一般会转换为指针
ia是一个int*类型的指针常量int ia[5];
ia和&ia[0]都表示数组第一个元素的地址,可以使用指针对数组进行访问 。

int a[10];
//指针访问数组元素
for(int *p = a; p < a+10; p++)
 cout << *p;

一维数组元素在内存中按下标顺序依次存放,一维数组a[n]的元素a[i]在内存中地址是a+i。
多维数组在内存中按行序存储,二维数组a[m][n]的元素a[i][j] 在内存中的地址是a+(i*n+j),使用指针访问数组时需要控制指针的范围,确保指针指向数组的元素

for(int *p = a; p < a+10; p++)
 ......

begin()和end()
库函数begin()和end()
让指针在数组上的使用更简单更安全
在头文件中定义
用法
begin(数组名),返回指向数组第一个元素的指针
end(数组名),返回指向数组最后一个元素的下一个位置的指针

//在数组arr 中查找第一个负数:
int *pb = begin(arr), *pe = end(arr);
while(pb != pe && *pb >= 0)
++pb;
//逐个输出数组元素的循环
for(int p = begin(arr); p!= end(arr); ++p)
cout << *p << "\t";
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值