指针和引用
指针
每个指针都有相关的类型,要在定义指针时指出
类型 *指针变量;
取地址运算符“&”
指针存放指定类型对象的地址,要获取对象的地址,使用取地址运算符“&”
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
#include <iostream>
using namespace std;
int main( )
{
int ival=1024;
int *pi=&ival;
cout << " sizeof(pi):" << sizeof(pi) << endl; //指针在内存中所占大小
cout << " sizeof(ival):" << sizeof(ival) << endl; //ival在内存中所占大小
cout << " &pi:" << &pi << endl; //指针在内存中的地址
cout << " pi:" << pi << endl; //指针中存放的内容,即ival的地址
cout << " &ival:" << &ival << endl; //ival的地址
cout << " *pi:" << *pi << endl; //指针所指内存中存放的内容,即ival的值
cout << " ival:" << ival << endl; //ival的值
空指针
// 生成空指针的2种方法
int *p1 = 0;
int *p2 = NULL;
//不能写成下面的样子:
int zero = 0;
int *p4 = zero;
使用指针的注意事项
- 定义指针时,应该对指针进行初始化
- 指针的运算
- 同类型的指针可以进行相等(==)或不相等(!=)的比较操作,比较的结果是布尔类型
- 指针可以进行加或减整数值的算术运算
- 自增、自减运算适用于指向数组元素的指针
存储空间分配策略
静态(编译时)分配空间
编译器在处理程序源代码时分配内存;
效率高,灵活性差,运行前就要知道程序需要的内存大小和类型
动态(运行时)分配空间
程序运行时调用运行时刻库函数来分配内存;
占用程序运行时间,更灵活
动态存储空间管理
new运算符
- new表达式的三种形式
-
分配单个对象:
new 类型
或者new 类型(初始值)
-
分配多个连续存储的对象:
new 类型[数组大小]
-
定位new,在指定位置分配空间:
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形式
delete[] 指针;
-
定位new没有对应的delete表达式
-
空悬指针
- 执行delete运算后,指针ip指向的空间被释放,不能再使用ip指向的内存,但是ip这个指针变量自己的存储空间不受影响
- delete后的ip不是空指针,而是“空悬指针”,即指向不确定的单元
- delete之后,继续通过ip间接使用这个单元是非法的,会引起不可预料的运行错误
引用
在程序中,引用主要用作函数的参数。
引用的定义和初始化
- 引用由类型标识符和一个说明符(&)来定义
type& refVariable = leftValue;
- 引用必须被初始化,初始值是一个有内存地址的对象
引用关系
- 引用一旦初始化,就不能再指向其他的对象,对引用的所有操作都会被应用在它所指向的对象上
- 引用的初始化和赋值不同
- 初始化时引用被“绑定到”一个对象;
- 赋值时,引用被作为所绑定对象的别名
- 引用只能绑定到对象(有内存地址)上,不能与字面值或某个表达式的计算结果绑定在一起。
- 引用并非对象,是为已存在的对象所起的另一个名字
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对象的指针
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指针
int ival = 1024;
int* const pi = &ival;
pi所在内存中的值不能改变
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
begin()和end()
- 库函数begin()和end()
- 让指针在数组上的使用更简单更安全
- 在头文件
<iterator>
中定义
用法:
-
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";