目录
1. 概述
指针的作用在于, 可以通过指针 来间接地访问内存.
内存编号是从0开始记录的, 一般用十六进制数来表示.
可以利用指针变量, 来保存一段内存的地址.
2. 指针的定义
语法:
数据类型 * 指针变量名 ;
int * p ;
" * " 就是星号, 在C++中也作乘号使用.
这时, 我们虽然创建了一个指针变量, 但这个指针并没有和任何地址对应起来(没有指向)
所以我们需要为指针变量和指定的内存地址建立联系.
可以用取址符" & " 来获取某个变量的地址. " && " 则表示逻辑的" 与 ", 注意区分.
int a = 0;
int * p = &a;
这样指针变量 p 保存的就是 变量a 的地址.
但是学到此处仍然感觉一头雾水, 于是输出一下这些有关变量的值, 看看它们到底代表什么.
#include <iostream>
using namespace std;
int main()
{
int a = 0;
int * p = &a; //也可以分两行写 int * p ; p = &a ;
cout << " a = : " << a << endl;
cout << " &a = : " << &a << endl;
cout << " p = : " << p << endl;
cout << " *p = : " << *p << endl;
cout << " &p = : " << &p << endl;
return 0;
}
现在都清楚了.
a 就是变量a 所对应的值, 也就是变量a对应的那一段内存中存放的值. 这个是我赋值的, 为0.
&a 和 p 就是变量a 的地址的值, 也就是变量a对应的那一段内存的地址本身, 它是未知的但可以查.
*p 就是 把变量p 对应的值视为一个地址, 然后去找这个地址的内存中存放的值, 也就是a的值.为0.
【C++】*P、P 、&P的区别_*p和&p的区别_赫于富的博客-优快云博客
那为什么会有 int *p = &a 呢? 不是应该*p = a 才对嘛?
因为int *p 只是表示变量p中存放的是地址, 变量p 是一个指针变量, 并不是说 *p = &a .
也就是说 int * 是一个数据类型, 就像int , float , double.
int * 类型的数据, 允许用 *变量名 , 但int 不行.
如果上面 *a 就是错的, 就算a = 0 , 尽管编码为0的这段内存确实存在, 也不允许通过这种方式去访问这段内存, 因为a是 int 类型的数据, 不是指针(int *).
如果分两行写, 意思就对了.在学习后面的内容之前, 要先把这个弯转过来.
int *p ;
p = &a ;
3.使用指针 (解引用)
解引用就是一个将值视为内存, 然后去对应编码的内存中寻找值的过程.
比如 *p ,意思是取出变量 p 中的值, 将它作为地址, 然后去找对应地址内存中的值.
这个值才是 *p 的值.
如上面所说, p = &a , *p = a
那么我们就可以通过操作 *p 来间接地操作a.
int a = 0;
int *p = &a;
*p = 1000;
cout << a << endl;
cout << *p << endl;
运行, 发现a 的值变为了1000. 这个过程就叫做 解引用.
注意: 虽然当 p = &a 后, 有*p = a,
但是 p = &a; 并不等价于 *p = a;
因为赋值号不是等号.
因为赋值号不是等号.
因为赋值号不是等号.
C基础知识(4):指针--p=&a和*p=a的区别详解 - Storm_L - 博客园 (cnblogs.com)
4.指针占用的内存空间
指针数据类型( int *) 在32位操作系统下占据 4 个字节的内存空间, 64位系统占据 8 个字节.
int * 作为一个数据类型同样也可以使用 sizeof 关键字来查询它占据的内存空间大小.
我这里使用的是64位操作系统.
int * p = &a ;
cout << sizeof(int *) << endl;
cout << sizeof(p) << endl;
cout << sizeof(*p) << endl;
结果为8 8 4.
因为 *p 就是a , a 是int 类型的变量, 当然占据4个字节.
5.空指针和野指针
NULL是值吗?为什么=NULL会出错?| 一文讲懂SQL NULL - 知乎 (zhihu.com)
【C++进阶】C++中的空指针和野指针_c++空指针和野指针_CPP攻城师的博客-优快云博客
C++中NULL和nullptr的区别_nullptr和null区别_csu_zhengzy~的博客-优快云博客
空指针的作用浅析_空指针有什么用_Moon_K_H的博客-优快云博客
空指针就是指不指向任何地址的指针.
定义一个指针的时候, 如果还没有想好让它指向哪个地址, 最好将它设为空指针:
int * p = nullptr ;
如果不进行指向, 那么可能会导致随意分配地址, 导致程序崩溃.
这就是空指针的作用.
野指针就是指向非法内存空间的指针.
我们定义一个指针变量的时候, 程序会根据赋值运算符的右值进行赋值, 此时右值必须是一个地址类型的数据.
int a = 0;
int * p = &a ;
我们可以这么做是因为&a 是地址类型的数据, 而且a的内存是由程序来分配的.
如果强行给指针变量赋值一个自定义的地址类型数据, 那么这样的指针是非法的,叫做 野指针.
int * p = (int *)0x1100 ;
因为0x1100 这个内存地址不是程序来分配的, 甚至连编译器可能都没有它的访问权限.
那么一旦尝试对这个指针变量进行访问, 甚至是解引用, 就会出现不可预料的后果.
野指针是非常危险的, 更危险的是, 编译器并不一定会对野指针报错, 可能编程者自己都没有发现.
所以写程序的时候务必注意, 不要写出野指针.
6. const 修饰的指针
const修饰的指针有三种情况:
常量指针, 指针常量, const 既修饰指针又修饰常量.
6.1 常量指针
顾名思义, 常量指针是一个指针, 常量意味着这个指针指向的是一个常量.
也就是 常量的指针.
const int *p = &a ;
常量指针的特点是:
禁止通过常量指针对变量进行赋值(但不禁止直接用变量再赋值);
允许指针的指向发生改变.
为便于记忆, 常量指针的常量在指针之前, 代码中也是 const 在 int * 之前.
但是此处的 a 并不必须定义成常量, a 仍然可以是变量, 并且对 a 进行重新赋值.
这是因为 a 是变量, 允许对 a 重新赋值, 并且这个过程中确实读写了 a 对应的内存
而*p 才是常量 , 整个过程中又没有对 *p 重新赋值, 因此可以通过编译,而且 a 的值确实改变了
所以 const int * p 禁止的是通过对指针变量 p 进行解引用, 间接地更改a的值. (*p = 1000;)
但并不会禁止单纯的访问. 如 cout << *p ; 就是合法的.
也就是说, 常量指针并不能保护常量的值, 但可以防止它通过解引用被修改.
#include <iostream>
using namespace std;
int main()
{
int a = 10;
int b = 20;
const int* p = &a; //const int * 就是常量指针, 这意味着指针变量 p 是一个常量指针
a = 100;
cout << a << endl; //输出100, p 是常量指针, 但 a 是变量, 允许重新赋值.
cout << *p << endl; //输出100, p 是常量指针, 但允许通过 *p 对 a 进行访问, 前提是没有对 a 重新赋值.
p = &b;
cout << *p << endl; //输出20, p 是常量指针, 常量指针允许指针的指向发生改变, 此时 p 指向 b 的地址.
// *p = 1000; 报错, 因为 p 是常量指针, 不允许通过解引用的方式对 a 重新赋值.
return 0;
}
拓展 :
如果不对指针变量进行const修饰, 而是修饰变量a呢?
int const e = 1;
int *p2 = &e;
答案是会报错. 编译器认为 e 是 const int * 类型的数据.
但可以通过前缀强行修改数据类型, 通过编译:
int const e = 1;
int *p2 = (int *) & e;
*p2 = 3;
cout << e << " " << *p2 << endl;
这样输出, e为1 , *p2 为3
这是因为, 编译器对常量是一种粗暴的替换, 在代码中遇到任何 e ,直接替换为1.
在调用 e 的时候, 直接用 1 代替, 并不会真的去 e 对应的内存中取值.
但实际上 e 内存中对应的值已经被指针变量 p2 通过解引用的方式强行修改为3了.
这种方式确实符合常量的概念, 在这个程序中不管再怎么调用 e , e 都没有被修改的风险.
总结起来就是, const 可以很好地保护常量, 但是不会保护常量的内存.
现在, e 是否算真正的被修改了, 并不好说, 但是明白这个原理可以让我们更加灵活地运用.
C/C++可以通过指针修改常量_Echo木的博客-优快云博客
6.2 指针常量
指针常量也是一个指针, 但同时也可以认为是一个常量, 因为指针就是一个地址,
而它指向的地址不变. 也就是 指针是常量.
int c = 0;
int * const p = &c ;
指针常量的特点是:
允许修改指向的变量的值, 也允许通过解引用的方式间接修改变量的值.
禁止通过取址的方式修改指针的指向, 也就是说指针常量的值(p)永远不会变.
int c = 0;
int d = 1;
int * const p = &c ;
// p = &d; 错误,禁止通过取址的方式修改指针的指向(也就是修改指针常量的值.)
看不明白不要紧, 看代码就懂了.
//指针常量
int c = 0;
int d = 50;
int* const p1 = &c; //int * const p1 ,意味着 p1 是一个指针常量.
c = 25;
*p1 = 666;
cout << " 解引用前的*p1 : " << *p1 << endl; //输出666, p1 是指针常量, 允许通过解引用的方式对 c 重新赋值.
//p1 = &d; //报错,因为 p1 是指针常量, p1已经指向 c 的地址, 不允许重新指向 d.
*p1 = d;
d++;
cout << " 解引用后的*p1 : " << *p1 << endl; //输出50, p1 是指针常量,允许对 *p1 赋值.
//但是!仅在赋值*p1的时候访问, 取得d的值作为 *p1的值, 输出*p1的时候不是去d的内存取, 而是去c的内存.
cout << "c的地址 : " << &c << endl;
cout << "d的地址 : " << &d << endl;
cout << "p1 的值 : " << p1 << endl; //特别注意! 虽然 *p1 的值发生了改变 , 但 p1 的值没有发生改变!
*p1 第二次的值为50.
说明程序在调用 *p1 的时候并没有跑到 d 的内存中去取值(因为此时 d 已经变成51).
而是程序在 *p1 = d ; 的这个过程中, 才会从 d 中取值赋值给 *p1 (也就是c).
再检查一下 c 的地址 和 p1 的值, 发现它俩相等.
这就对了.
6.3 const 修饰的指针和常量
这可能有点反直觉:
常量指针,cosnt在 * 左侧,是指 const 修饰指向的常量;
指针常量,const在 * 右侧,是指 const 修饰指针变量本身。
那么当 const 既修饰指针 , 又修饰常量呢?
答案是
//双重const
int num = 0;
const int * const p3 = #
num = 10;
cout << *p3 << endl; //输出10, const修饰指针,并不禁止通过变量赋值
// p3 = & num1 报错,const修饰常量,不允许通过取址方式重新指向.
int num1 = 0;
// *p3 = num1; 报错,const修饰指针,不允许通过解引用方式给变量赋值.
拓展
这一节里面我写了很多的解引用, 但引用又是什么呢, 为了满足好奇心,再贴个链接在这里:
【C++】之引用详解 什么是引用?_c++引用_Brant_zero2022的博客-优快云博客
7. 指针和数组
前面已经说过, 数组在内存空间中是连续的.
那么就可以通过一个自增的指针访问整个数组的元素.
#include <iostream>
using namespace std;
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int * p_arr = arr; //一定不要写成 &arr , 因为程序认为arr就是一个 int * 类型的数据 , 就是地址.
//int 表示这个指针指向的是整型变量, 也就是 arr[0]是整型变量
for (int a = 0; a < 10; a++)
{
cout << *p_arr << " "; //解引用, 去数组arr的首地址读内存
p_arr += 1; //指针指向 下 一 段 内存, 这个长度由指针指向的数据类型决定
//因为指针指向整型变量, 所以虽然指针 +1 , 但实际上指针偏移了4个字节.
cout << "下一段内存的地址:" << p_arr << endl; //验证一下
}
cout << endl;
double arr1[10] = { 1,2,3,4,5,6,7,8,9,10 };
//int* p_arr1 = arr1; //报错, 因为 arr1的首地址是 double * 类型的数据, 不是 int * 类型.
double* p_arr1 = arr1;
for (int a = 0; a < 10; a++)
{
cout << *p_arr1 << " "; //解引用, 去数组arr1的首地址读内存
p_arr1 += 1; //指针指向 下 一 段 内存, 这个长度由指针指向的数据类型决定
//因为指针指向双精度浮点型变量, 所以虽然指针 +1 , 但实际上指针偏移了8个字节.
cout << "下一段内存的地址:" << p_arr1 << endl; //验证一下
}
cout << endl;
return 0;
}
可见结论是正确的.
所以指针类型变量 +1 的时候要注意, 指针变量的值不是 +1, 而是 +n , n为指针指向的变量类型
而 p_arr在此处是 int * 类型的指针变量, 因此 n = siezeof(int) = 4.
同样地, p_prr1在此处是 double * 类型的指针变量, 因此 n = sizeof(double) = 8.
这是在 int * p_arr = arr; 这一行代码时就已经被决定的.