11. 指针
补充知识:
存储器和存储地址空间
- 存储器:计算机的组成中,用来存储程序和数据,辅助CPU进行运算处理的重要部分。
- 内存:内部存储器,暂存程序/数据,掉电丢失。SRAM、DRAM、DDR3、DDR4
- 外存:外部存储器,长时间保存程序/数据,掉电不丢失。ROM、FLASH、硬盘、光盘等。
内存是沟通CPU与硬盘的桥梁:
- 暂时存放CPU中的运算数据
- 暂时存放与硬盘等外部存储器交换是数据
存储地址空间:对存储器编码的范围
- 编码:对每个物流存储单元(一个字节)分配一个号码
- 寻址:根据分配的号码找到相应的存储单元,完成数据的读写
内存地址:
- 将内存抽象成一个很大的一维字符数组
- 编码就是对内存的每一个字节分配一个32位或64位的编号,这个内存编号称为内存地址(唯一)
- 内存中的每一个数据都会分配相应的地址
- 对变量应用地址运算符(&),可以获取它在内存中的地址
11. 1 指针和指针变量
指针:是一种独立的数据类型,这种类型的变量存储的值就是内存地址。
指针的作用:通过指针间接访问内存
- 内存编号从0开始记录,一般用16进制数字表示
指针变量:保存内存地址
不同类型的指针变量所占用的存储单元长度是相同的。
11.1.1 声明和初始化指针变量
指针变量的声明:
声明格式:
- 数据类型 * 变量名:
int * ptr;
- 数据类型 *变量名:
int *ptr;
- 数据类型* 变量名:
int* ptr;
注意:
- 对每个指针变量名,都需要使用一个
*
int* p1,p2;
//声明创建一个指针变量p1和一个int
类型变量p2;int *p1, *p2;
//声明创建一个指针变量p1和一个指针变量p2;
//在声明语句中初始化指针变量
int num=5;
int *pt_num=#//让指针pt_num记录变量num的地址
//声明指针变量后对其进行初始化
double d=2.0;
double *pt_d;
pt_d=&d;
11. 1. 2 使用指针变量(解引用)
可以通过解引用的方式来找到指针指向的内存。
指针前加 *
代表解引用,找到指针指向的内存中数据。
*p
间接的访问内存,并对内存进行读写操作。
int main() {
int a = 10;
int* p = &a;//定义指针并赋值
cout << "a的地址为" << &a << endl;//000000E75DCFFBA4
cout << "指针p为" << p << endl;//000000E75DCFFBA4
//使用指针
*p = 1000;
cout << "a=" << a << endl;//1000
cout << "*p=" << *p << endl;//1000
int num = 10;
int* p;
p = #
cout << p << endl;
cout << &num << endl;
//对p解除引用运算符
//*p == num;
cout << *p << endl;
*p = 100;
cout << *p << endl;//100
cout << num << endl;//100
(*p)++;//++优先级高于*
cout << *p << endl;//101
cout << num << endl;//101
cin >> *p;
cout << *p << endl;//300
cout << num << endl;//300
return 0;
}
解除引用
- 运算符称为间接值或接触引用运算符,将其应用于指针变量,可以得到该地址处存储的值。
- 例如
ptr
是一个指针变量,则ptr
存储的是一个地址,而* ptr
表示存储在该地址处的值,*ptr
与常规变量等效。- 一定要在对指针变量解除引用之前,将指针变量初始化为一个确定的,适当的地址
- 不能简单的将整数值赋值给指针变量
11. 2 指针所占内存空间
为什么要使用指针类型来保存地址的值?
- 编译时类型检查
- 指明一个内存地址所保存的二进制数据该怎么解释
#include<iostream>
using namespace std;
int main() {
int num = 10;
int *p=#
cout << &num << endl;
cout << sizeof(&num) << endl;
cout << sizeof(int *) << endl;
//在同一个平台上,所有的地址所占的空间大小是一样的
//32位系统分配32位编码,4个字节
//64位系统分配64位编码,8个字节
return 0;
}
11. 3 空指针和野指针
空指针:指针变量指向内存中编号为0
的空间
- 在C++中值为0的指针变量称为空指针(null pointer)。空指针不会指向有效的数据。
- C++中,空指针的值可以使用
0
、NULL
、nullptr
来表示
用途:初始化指针变量
注意:空指针指向的内存是不可以访问的
#include<iostream>
using namespace std;
int main() {
//空指针
//对指针变量进行初始化
int* p = NULL;
cout << p << endl;//0000000000000000
int* ptr = nullptr;
cout << ptr << endl;//0000000000000000
//空指针不可进行访问
// 0~255之间的内存编号是系统占用的,因此不可进行访问
//*p = 100;//报错
return 0;
}
野指针:指针变量指向非法的内存空间
- 任意数值赋值给指针变量没有意义,这样的指针变量就是野指针,野指针指向的区域是未知的
- 野指针不会直接引发错误,操作野指针指向的内存区域可能会出现问题。
#include<iostream>
using namespace std;
int main() {
//野指针
int* ptr = (int*)0x787878;
*ptr = 10;//写入权限冲突,因为地址不存在
cout << *ptr << endl;//访问野指针报错
return 0;
}
注意: 在程序中尽量避免出现野指针
取地址与取值的关系
//在使用中,*(取值)和 &(取地址)运算符之间的关系
int num=0;
int *p;
//p=# num是int类型 &num是int*类型
//总结:对变量取地址 &,整个表达式类型加上一个 *
int *p=#//int *p; p=#
p=#// p是 int* 类型 *p==num是int类型
//总结:在使用中,对指针变量使用 * ,整个表达式类型上减少一个 *
//注意:在使用中,当*和&同时出现时,从左往右,依次抵消
//&*&*&num
11. 4 指针变量的宽度和跨度
指针变量的两种类型:
-
自身类型:去掉变量名,剩余的部分就是指针变量的自身类型
int * ptr;//指针变量 ptr 的自身类型是 int * int ** ptr;//指针变量 ptr 的自身类型是 int **
-
指向类型:去掉变量名以及离它最近的一个
*
,剩余的部分就是指针变量的指向的类型
int * ptr;//指针变量 ptr 指向的类型是 int
int ** ptr;//指针变量 ptr 指向的类型是 int *
int *p;
short *p1;
指针变量所取内容的宽度
- 指针变量所取内容的宽度是由指针变量所指向的类型长度决定的。
指针变量+1
的跨度
- 指针变量+1的跨度是由指针变量所指向的类型的大小决定。
#include<iostream>
using namespace std;
//演示指针变量取值的宽度和+1的跨度
int main() {
//宽度
int num = 0x01020304;
int* p = #
cout << hex<<*p << endl;//hex 表示取 十六进制,即取num的十六进制格式:01020304,第一个0省略 1020304
short* s = (short*)#
cout << hex << *s << endl;//取前两个宽度,0304,第一个0省略,304
//跨度
short* s1 = nullptr;
cout << "s1:" << s1 << endl;//s1:0000000000000000
cout << "s1+1:" << s1 + 1 << endl;//s1 + 1:0000000000000002 short的跨度是2
int* p1 = nullptr;
cout << "p1:" << p1 << endl;// p1 : 0000000000000000
cout << "p1+1:" << p1 + 1 << endl;// p1 + 1 : 0000000000000004 int的跨度是4
return 0;
}
//获取第二和第三个字节
char* c = (char *) & num;
short* s2 = (short*)(c + 1);
cout << hex <<*s2 << endl;//0203,省略第一个0,203
11. 5 void
类型指针
void*
为“无类型指针”
void*
可以保存任意类型的地址(万能指针)- 主要作为函数的参数
void*
指针变量可以指向任意变量的内存空间,任何类型指针变量都可以转为void*
int num=10;
void *p=#
int * p_num=#
void *p1=p_num;//任何类型指针变量都可以转为void*
不要对void*
指针变量做解除引用与算数运算,如果想通过void*
指针变量取出内容,必须进行强制类型转换。
因为void*
是不完整的数据类型,系统不知道指针变量所指向的类型,所以不知道具体要取多少宽度,会报错
不允许使用void
定义普通变量
11. 6 const
和指针
const
修饰指针变量,const修饰谁,谁就只读
#include<iostream>
using namespace std;
//const 和 指针
int main() {
int num = 100;
//第一种情况:const 在 * 的左边,修饰的是 * (*p1只读,p1可读可写)
const int* p1 = #
//*p1=20;报错,因为*p1不能修改
int data = 20;
p1 = &data;//p1可修改
cout << *p1 << endl;//20,p1已被修改
//第二种情况:const在*的右边,修饰的是p2,(*p2可读可写,p2只读)
int* const p2 = #
*p2 = 200;
cout << *p2 << endl;//200
//第三种情况:const在*的左右两边,既修饰*也修饰指针变量(*p3只读,p3只读)
const int* const p3 = #
return 0;
}
11. 7 指针和数组
作用:利用指针访问数组中的元素
#include<iostream>
using namespace std;
int main() {
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
cout << "数组第一个元素为:" << arr[0] << endl;//1
int* p = arr;
cout << "利用指针访问第一个元素:" << *p << endl;//1
p++;
cout << "利用指针访问第二个元素:" << *p << endl;//2
return 0;
}
11. 8 指针和函数
作用:利用指针作函数参数,可以修改实参的值
#include<iostream>
using namespace std;
void swap01(int a, int b) {
int temp = a;
a = b;
b = temp;
}
void swap02(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
int main() {
int a = 10;
int b = 20;
//值传递 不会改变实参
swap01(a, b);
cout << "a=" << a << endl;//10
cout << "b=" << b << endl;//20
//地址传递 会改变实参
swap02(&a, &b);
cout << "a=" << a << endl;//20
cout << "b=" << b << endl;//10
return 0;
}
11. 9 二级指针
二级指针:一个指针指向的是另外一个指针,或者指向指针的指针
即一个指针变量保存的是另外一个指针变量的地址值
int num=10;
int *p=#
int **q=&p;//二级指针
//*p : 获取的是 num 的值
//*q:获取的是num的地址值
//**q:获取的是 num 的值