c++ 指针详解

article/2025/10/21 17:57:09

文章目录

    • 指针
      • 1、 使用指针遍历[数组](https://blog.csdn.net/m0_62870588/article/details/123787052)
      • 2、指针的概念与理解
      • 3、指针的创建与初始化
      • 4、指针的基本操作
      • 5、指针的算数操作
      • 6、[const](https://blog.csdn.net/m0_62870588/article/details/123399735)指针
      • 7、指针的[数组和数组](https://blog.csdn.net/m0_62870588/article/details/123787052)的指针
      • 8、指针的指针
      • 9、const_cast 与reinterpret_cast

指针

指针是c++中的一个核心概念,是一名c++程序员可以直接对内存进行操作的一种工具,这样的工具就是一把双刃剑,一面可以实现一些非常优化的程序,另一方面也会导致一些难以调试的错误。


1、 使用指针遍历数组

示例:

#include<iostream>
using namespace std;//使用指针遍历数组int main()
{int arr[5] = { 0, 1, 2, 3, 4 };int *ptr = arr;for (int i = 0;i < 5 ;i++){cout << *ptr << " ";ptr++;//也可以直接写成 cout << *(ptr++) << " ";}cout << endl;return 0 ;
}

运行结果:
在这里插入图片描述
示例中涉及指针的初始化、解引用操作以及自增,看一个有趣的语句“int *ptr = arr;”,该语句用数组名初始化指针。在这里数组名代表的是数组第一个元素的地址,之后在循环内程序会递增指针以指向数组的后面几个元素。


2、指针的概念与理解

指针(Pointer),从其英文字面上来理解就是一个指向某一物件的东西,在程序中就是指向数据的地址(Address)。计算机的内存可以看作是一个紧密排列的数据序列,每一小块数据序列,每一小块数据(也就是字节)的旁边都有一个编号代表数据地址。这在现实中可以用房屋的地址来理解,我们可以说这一栋房子是小李家,也可以说一栋房子是xx路xxx号(指针表示)。对于上面的示例可以用一个图来理解ptr 和 arr 到底指的是什么。

arrptr
203204205206207
01234
假设arr的地址是203,那么数组后面几个元素的地址依次递增(这个例子中因为数组的类型为 int ,所以 其实真是的地址需要依次增加4个字节)。指针实际上就是内存地址,所以arr的值就是203,而当ptr指向数组最后一个元素的时候,它的值为207.如果我们想要获取某一个地址下存储的数据,就可以使用ptr来获得。

指针的含义:

#include <iostream>
using namespace std;//指针的含义int main()
{int arr[5] = { 0, 1, 2, 3, 4 };int *ptr = arr;for (int i = 0; i < 5 ; i++){cout << *ptr << " ";cout << "地址:" << ptr <<endl;ptr++;}return 0 ;
}

运行结果:
 指针代表地址
可以看到,数组的第一个元素的十六进制地址是:0x7ffd83fdbcd0
(不同计算机以及同一计算机每次运行该程序得到的地址都有可能与示例中的地址不同),第二个元素的地址是:0x7ffd83fdbcd4
,每个元素之间的距离正好是 int 的大小-----4 字节。


3、指针的创建与初始化

指针的创建与初始化示例:

#include<iostream>
using namespace std;//指针的创建和初始化int main()
{float *floatPtr = NULL;string *strPtr;int *intPtr1, *intPtr2;int* intPtr3,  intPtr4; //intPtr4只是一个整数return 0 ;
}

通过示例可以看到,指针的声明就是在变量类型名和变量名之间加上星号(*),并可以并可以任意选择让星号紧贴类型名
(int * intPtr3,)或者变量名
(float *floatPtr = NULL; string *strPtr; int *intPtr1, *intPtr2;)的代码风格。然而紧贴类型名的代码风格会给人造成“int *”是一个整体的感觉,初学者很容易在声明多个指针的时候遗漏后面变量名前的星号,就像intPtr4一样,感觉像是定义了一个指针,其实只是一个整型。正确的语法是像intPtr2那样在前面加上一个星号,不管与星号之间有没有空格。

此外,示例中只有第一样的floatPtr初始化了,但是实际编程中我们一定要初始化所有指针,就跟变量一样。floatPtr的初始值NULL是一个宏定义,它的实际数值是0,也就是地址:0x00000000.一般我们都会把指针初始化为NULL,也叫做空指针,这给我们一个统一可管理的异常值。在程序中,我们只要检查指针是否为空就知道指针是否指向有效数据了。

**提示:**如果指针没有初始化,它可能指向一个未知的地址,那么我们在尝试读取数据的时候就可能造成程序崩溃。此外,在指针初始化的时候,不能使用0以外的整型给指针赋值。

除了例子中的指针类型外,c++还有一种通用的void*指针。我们知道指针就是地址,指针的类型不过表示了地址指向的位置所存储的数据类型。如果我们将int * 指针转换为float * 指针,那么程序也只是将数据重新解读为浮点类型。所以这里void * 只是代表了一个地址,而我们不知道它所指向的数据类型,但我们也可以重新定义它所指向的数据类型。void * 一般会在一些内存处理的系统函数时候使用


4、指针的基本操作

对于指针来说,解引用和取地址是最重要的两个操作符。
指针的基本操作

#include<iostream>
using namespace std;//指针的基本操作int main()
{int num = 4 ;int *intPtr = &num;cout << "num的地址是:" << &num << endl;cout << "指针的值是:" << intPtr << endl;if ( intPtr ){//检查指针是否为空cout << "指针所指的数字是:" << *intPtr << endl;}return 0 ;
}

运算结果:
在这里插入图片描述
可以看到,符号“&”表示了取地址的操作,它可以获得变量的内存地址。将其赋值给我们的指针intPtr后,打印&num和intPtr将同时获得num的地址。而当我们使用解引用操作符“ * ”的时候,* intPtr将会得到intPtr所指向的地址中的数据,也就是num的值。

在示例中,还加上了一个条件来检查指针是否为NULL,以此保证对intPtr解引用一定是安全的。这里利用了数值与布尔值之间的隐性转换,只写了intPtr作为条件,因为intPtr为空的话值会转化为false。条件intPtr也可以写成“intPtr != NULL”。
例子中用解引用操作符读取了指针指向的数据,而解引用操作符也可以用来作为赋值语句的左值以修改为数据。

左值解引用:

#include <iostream>
using namespace std;//左值解引用int main()
{int num = 4 ;int *intPtr = &num;if ( intPtr ){//检查指针是否为空cout << "指针所指的数字是:" << *intPtr << endl;cout << "num的值是: " << num << endl;*intPtr = 3;cout << "修改后,指针所指的数字是: " << *intPtr << endl;cout << "num的值是:" << num << endl;}return 0 ;
}

运行结果:
在这里插入图片描述
示例中,使用了左值解引用操作将指向num的指针中的数据修改为3,由于指针与num的地址相同,因此num也会变成3.指针的这一种行为可能会让初学者感到困惑,接下来我们是用图片来直观地解释指针解引用、取地址和左值解引用的行为。


5、指针的算数操作

指针可以像整形那样进行一部分算数操作,我还可以对地址进行修改。因为计算后的指针不一定会指向具有有效数据的地址,所以在进行指针算数操作的时候需要格外小心。

指针与整形的算数操作:

#include<iostream>
using namespace std;//指针与整型的算数操作int main()
{int arr[5] = { 0, 1, 2, 3, 4 };int *ptr = arr;cout << "arr + 4:" << *(arr + 4 ) << endl;cout << "ptr + 4:" << *(ptr + 4 ) << endl;cout << "ptr: " << ptr + 2 << endl;cout << "++ptr: " << ++ptr << endl;cout << "ptr - 2: " << ptr - 2 << endl;cout << " --ptr:" << --ptr << endl;return 0;
}

运行结果:
在这里插入图片描述
可以看到,指针与整型的算数操作不同于一般的数字加减,而是与指针类型绑定的。由于一个int 的大小是4字节,那么ptr+2 会将地址加上8,在组中就指向第三个元素。在示例中,除了指针ptr,我们也对数组arr做了加法,得到的结果都是第五元素的值。此外,示例末尾的ox7ffd4207fe80 已经比数组的第一个元素地址还小了,如果对这个地址进行解引用,可能会使程序崩溃。

提示数组名其实可以看做是指向数组第一个元素的指针。指针的各种操作都适用于数组名但只有一点区别,那就是数组名不能被重新赋值。这样是很容易理解的,因为数组是静态的,数组名代表了当前作用域唯一的一个数组,不可能先指针那样指向其他地址。

指针除了与整型的算数操作以外,还可以进行指针相减
指针相减

#include <iostream>
using namespace std;
int main()
{int arr[5] = { 0, 1, 2, 3, 4 };int *ptr1 = arr + 1;int *ptr2 = arr + 3;cout << "ptr1:" << ptr1 << endl;cout << "ptr2:" << ptr2 << endl;cout << "ptr2 - ptr1: " << ptr2 - ptr1 << endl;cout << "ptr1 - ptr2: " << ptr1 - ptr2 << endl;return 0 ;
}

运行结果:
在这里插入图片描述
指针相减返回的是指针地址之间的距离,并且是分正负的。这个距离也与类型绑定,单位是该类型数据的个数。指针之间不存在加法,每个指针代表的地址在计算机中都是唯一确定的,相加没有任何意义。这就好像是门牌号32减掉30得到2,表示他们之间,隔着两户,而32加上30却不能表示什么。


6、const指针

之前有使用左值解引用来修改指针指向的原变量的例子,但如果变量是const,值是不能被修改的,因此我们也需要有一种特殊的指针来保证原变量不会被修改,这就是指向const对象的指针。

指向const对象的指针

#include<iostream>
using namespace std;//指向const对象的指针int main()
{const int num = 3;//普通指针不能指向const变量//int *ptr1 = &num;const int *ptr2 = &num;cout << "*ptr2:" << *ptr2 << endl;//指向const对象的指针不能修改解引用后的值//*ptr2 = 4;//指向const对象的指针可以修改指向的地址const int num1 = 4;ptr2 = &num1;cout << "*ptr2: " << *ptr2 << endl;//指向const对象的指针也可以指向普通变量int num2 = 5;ptr2 = &num2;cout << "*ptr2 : " << *ptr2 << endl;return 0 ;
}

运行结果:

要定义一个指向const对象的指针,就要在const对象类型名后加上星号。“int* ptr1 = &num;”这一行如果去掉注释,编译器就会报错,因为普通指针不能指向const对象。“ * ptr2 =4;”这一行如果去掉注释相当于修改const对象的值,编译器也会报错。

注意虽然ptr2指向的地址不能修改,但是它本身指向的地址可以修改。示例中,我们先后又让它指向了另外两个变量,其中也有一个非const的变量,指向非const变量的这一种指针也不能修改解引用后的值。
既然指向const对象的指针还可以修改地址的,那么应该也有另外一种不能修改地址的指针,也就是const指针。

const指针

#include<iostream>
using namespace std;//const指针int main()
{int num1 = 3 ;int num2 = 4 ;int *const ptr1 = &num1;//const指针不能修改指向地址ptr1 = &num2 ;const int num3 = 5 ;const int num4 = 6 ;//指向const对象的const指针既不能修改地址,也不能修改值const int *const ptr2 = num3;ptr2 = num4;return 0 ;
}

示例展示了尝试修改const指针而导致的编译错误。const指针的创建语法是将const移动到了星号后面,一开始ptr1指向num1,而当我们尝试把num2的地址赋值给ptr1的时候编译器报错。一个指向const对象的const指针ptr2 ,这个指针只能指向const int变量,它指向的地址也不能改变。


7、指针的数组和数组的指针

标题看似是两个类似的概念,二者却截然不同。指针作为一种变量类型,当然可以被声明为数组,而数组作为一种变量类型,也可以有指向它的指针。指针的数组是一种数组,而数组的指针则是一种指针。

指针的数组和数组的指针

#include<iostream>
using namespace std;//指针的数组和数组的指针int main()
{int arr[5] = { 0, 1, 2, 3, 4 };//数组的指针int (*arrPtr)[5] = &arr;//指针的数组int *ptrArr[5] = { &arr[0], &arr[1], &arr[2], &arr[3], &arr[4] };cout << "arrPtr: " << arrPtr << endl;cout << "*arrPtr:" << *arrPtr << endl;for (int i = 0; i < 5;i++){cout << ( *arrPtr ) [i] << " ";cout << ptrArr[i] << " ";cout << *(ptrArr[i] ) << " "<< endl;}return 0 ;
}

运行结果:
在这里插入图片描述
可以看到,数组的指针和指针的数组的语法区别在于:数组的指针需要在星号和变量名外面加一个括号,而指针的数组却没有。这一点其实很好理解,因为声明数组的时候元素类型名int和数组大小[5] 就是被变量名隔开的,在这里我们添加一个星号,并用括号括起来,表示这个指针int( * arrPtr)[5]是指向整个数组的;如果不加括号,编译器就只会将星号联系到前面的类型名int,所以ptrArr就只是声明了一个数组,数组的元素类型就是int*。

在声明arrPtr的时候,我们把数组的地址赋值给他作为初值,由于数组的指针解引用以后就相当于数组,我们可以用( * arrPtr)[i]来读取数组的元素。
ptr Arr是一个指针的数组,它的每一个元素都是一个指针,这里就将数组的每个元素的地址分别赋值,而在遍历的时候使用 * (ptrArr[i])来读取数组中某一个指针指向的元素值。
由于这里比较不直观的一点arrPtr和*arrPtr代表的地址完全一样,为了解释这一点,再看一个示例

#include<iostream>
using namespace std;//数组的指针的地址int main()
{int arr1[5] = { 0, 1, 2, 3, 4 };//数组的指针int (*arrPtr)[5] = &arr1;cout <<"arrPtr: " << arrPtr << endl;cout <<"*arrPtr:" << *arrPtr << endl;int arr2[5] = { 0, 1, 2, 3, 6 };//数组的指针必须指向大小相同的数组arrPtr = &arr2;cout << "arrPtr: " << arrPtr << endl;cout << "*arrPtr:" << *arrPtr << endl;//数组的指针指向数组,而数组是不可修改的//*arrPtr = arr1return 0 ;
}

运算结果:
在这里插入图片描述
我们可以看到,数组的指针必须指向相同大小的数组,如果arr2只有4个元素,“ arrPtr = &arr2; ”的赋值机会产生编译错误,并且由于数组的指针指向是不可修改的数组,我们不能把arrPtr作为左值修改。

至于为什么arrPtr和 * arrPtr的地址一样,可以看做是编译器不得已的安排。数组名arr1代表着数组首元素的地址,而一般的变化量比如int1就放着一个数值,而&int1才放着int1 的地址。由于数组的这一特殊性,导致了&arr得到的数组地址与arr代表的数组地址是一样的,因此相应的arrPtr和 * arrPtr的地址也只能是一样的,* arrPtr也要搭配下标操作符才能取得数组的对应元素。


8、指针的指针

指针可以指向任何变量或者对象,所以也可以指向指针。

#include <iostream>
using namespace std;//指针的指针int main()
{int num = 3 ;int *numPtr = &num;int **numPtrPtr = &numPtr;cout << "num: " << num << endl;cout << "*numPtr: " << *numPtr << endl;cout << "numPtr: " << numPtr << endl;cout << "*numPtrPtr: " << *numPtrPtr << endl;cout << "numPtrPtr: " << numPtrPtr << endl;return 0;
}

运行结果:
在这里插入图片描述
可以看出,指针的指针声明就多加了一个星号,以表示指针指向的指针类型,因此将numPtr的地址赋值给它。
指针的指针一般用于函数传参数时修改传入的指针。


9、const_cast 与reinterpret_cast

const_cast的作用是将一个变量转换成const限定的常量。

const_cast

#include<iostream>
using namespace std;//const_castint main()
{int intNum = 2 ;intNum = 3;//const_cast后面必须跟引用或指针类型const int &consIntNum = const_cast<int&>(intNum);//转换以后数字不能在修改//constIntNum = 2;return 0 ;
}

示例中,可以看到intNum在转换前是可以修改的变量,在转换以后就变成常量,不能再进行修改了。

reinterpret_cast 比较特殊。reinterpret 的意思是重新解读,而reinterpret_cast 就是将一段数据按照二进制表示重新解读成另一种数据,所以他并没有对数据做任何改变,只是改变了类型。

reinterpret_cast

#include<iostream>
using namespace std;//reinterpret_castint main()
{int intNum = 0x00646362;int *intPtr = &intNum;char *str = reinterpret_cast<char *>(intPtr);cout << "str的值为:" << str << endl;return 0;
}

运行结果:
在这里插入图片描述
在示例中,可以看到热interpret_cast将一个指向整数的指针转换成了指向字符的指针,也就是c风格的字符串,十六进制的62、63、64在ASCLL码中分别代表b、c、d,所以打印了“bcd”。
reinterpret_cast 的作用就是将一个类型的指针转换成另一个类型的指针,而指针指向的内存将被原封不动地重新解读。当然这也是一种比较危险的举动。

如果本文对您有帮助,请点赞支持一下~


http://chatgpt.dhexx.cn/article/p288bvie.shtml

相关文章

深入理解C语言指针

一、指针的概念 要知道指针的概念&#xff0c;要先了解变量在内存中如何存储的。在存储时&#xff0c;内存被分为一块一块的。每一块都有一个特有的编号。而这个编号可以暂时理解为指针&#xff0c;就像酒店的门牌号一样。 1.1、变量和地址 先写一段简单的代码&#xff1a; …

c语言指针的指针

1、情况 c语言指针的指针&#xff0c;还是比较常用的一个功能&#xff1b;当然&#xff0c;我也相信&#xff0c;一些用C语言很长时间的人&#xff0c;也没大用过&#xff0c;因为用不到&#xff0c;这是工作需求决定的&#xff0c;但总体来说&#xff0c;还是经常用的。 理解…

十四、C指针详解(四):指针的指针

文章目录 一、指针的指针 一、指针的指针 指针用来存放变量的地址&#xff0c;同时&#xff0c;指针也有自己的地址&#xff0c;因此&#xff0c;就可以设置一个指针变量&#xff0c;用来存放指针的地址&#xff0c;也就是指针的指针&#xff0c;他存放的是一个地址&#xff0…

指针的指针、字符串和指针、数组指针(详)

一、指针的指针 指针的指针&#xff0c;即指针的地址 定义了一个指针变量&#xff0c;指针变量本身占4个字节&#xff0c;指针变量也有地址编号 例&#xff1a; int a0x12345678; 假设a的地址为&#xff1a;0x0000 2000 int *p; p&a; 则p中存放的是a的地址编号为0x0000 20…

指针的指针(简单易懂)

int a 12&#xff1b; int *b &a; 内存的分配如下 这时再来一个变量 c &b; 问题来了? c 是什么类型? b 是指向整型的指针 ,c 是指向整形指针的指针&#xff1f; 是的 c 是指向指针的指针 声明如下 int ** c; int a 12; int *b &a; int **c &b…

Nginx Rewrite规则详解

Nginx Rewrite 规则相关指令 相关指令有if,rewrite,set,return,break等&#xff0c;其中最关键的就是rewrite.一个简单的Nginx Rewrite规则语法如下&#xff1a;rewrite ^/b/(.*)\.html /play.php?video$1 break; 1.break指令 默认值&#xff1a;none ;使用环境&#xff1a…

nginx配置文件rewrite规则

nginx配置文件rewrite规则 文章目录 nginx配置文件rewrite规则[toc]ifRewite 规则介绍flag标志位配置rewrite规则last二次转发 if 语法&#xff1a;if (condition) {…} 应用场景&#xff1a; server段 location段 常见的condition 变量名&#xff08;变量值为空串&#xf…

nginx Rewrite 规则

一&#xff1a;nginx Rewrite 规则 1&#xff1a;rewrite的概念&#xff1a; Nginx Rewrite功能是使用nginx提供的全局变量或自己设置的变量&#xff0c;结合正则表达式和标志位实现URL重写以及重定向功能。Rewrite指令只能放在server {}&#xff0c;location {}&#xff0c;…

Nginx高级之Rewrite规则

进阶阶段的回顾: Nginx进阶之静态Web资源服务 Nginx进阶之代理服务 Nginx进阶之负载均衡服务 Nginx进阶之缓存服务和动静分离 作用及应用场景 作用: 实现对URL的重写以及对匹配(正则表达式)的url的重定向 场景: 1. URL访问跳转, 支持开发设计 ① 页面跳转 ② 兼容…

Nginx配置请求转发location及rewrite规则

location / {# 精确匹配 / &#xff0c;主机名后面不能带任何字符串[ configuration A ] }location / {# 因为所有的地址都以 / 开头&#xff0c;所以这条规则将匹配到所有请求# 但是正则和最长字符串会优先匹配[ configuration B ] }location /documents/ {# 匹配任何以 …

Rewrite规则简介

Rewirte主要的功能就是实现URL的跳转&#xff0c;它的正则表达式是基于Perl语言。可基于服务器级的(httpd.conf)和目录级的(.htaccess)两种方式。如果要想用到rewrite模块&#xff0c;必须先安装或加载rewrite模块。方法有两种一种是编译apache的时候就直接安装rewrite模块&…

rewrite详解

rewrite模块 URI跟URL介绍 什么是uri&#xff1f;统一标识符&#xff0c;拿www.abc.com/aw/wd/举例&#xff0c;那么rui就是/aw/wd/这部分数据(也有可能是图片&#xff0c;html网页,如果是伪静态的话,那就得看配置是啥玩意了 什么是url? 统一定位符&#xff…

Nginx基础——Rewrite规则

点击上方“芋道源码”&#xff0c;选择“置顶公众号” 技术文章第一时间送达&#xff01; 源码精品专栏 精尽 Dubbo 原理与源码专栏( 已经完成 69 篇&#xff0c;预计总共 75 篇 )中文详细注释的开源项目Java 并发源码合集RocketMQ 源码合集Sharding-JDBC 源码解析合集Spring …

F280049C Crossbar X-BAR

文章目录 X-BAR9.1 输入X-BAR9.2 ePWM、CLB和GPIO输出X-BAR9.2.1 ePWM X-BAR9.2.1.1 ePWM X-BAR架构 9.2.2 CLB X-BAR9.2.2.1 CLB X-BAR架构 9.2.3 GPIO输出X-BAR9.2.3.1 GPIO输出X-BAR架构9.2.4 X-BAR标志 总结 X-BAR 交叉开关&#xff08;在本章中称为X-BAR&#xff09;提供…

BCGControlBar Pro 31.2 正式版-Key

什么是 MFC 的 BCGControlBar Pro&#xff1f; BCGControlBar&#xff08;“Business Components Gallery ControlBar”&#xff09;是一个 MFC 扩展库&#xff0c;企鹅180846090允许您创建具有完全自定义选项&#xff08;功能区、可自定义工具栏、菜单等&#xff09;和一组丰富…

BCGControlBar Library for .NET 7.1.1 Crack

什么是 BCGControlBar Library for .NET&#xff1f; BCGControlBar Library for .NET 是 100% 托管代码工具包&#xff0c;用 C/CLI 编写&#xff0c;面向 Microsoft .NET Framework 2.0 或更高版本。该库包含许多高度可定制、完全可设计的组件&#xff0c;使您能够创建最复杂…

BCGControlBar v12的向导使用图解

BCGControlBar专业版是MFC的一个扩展库&#xff0c;您可以用来构建类似于Microsoft Office 2000/XP/2003/2007/2010、Microsoft Visual Studio&#xff08;打印、用户定制工具栏、菜单等&#xff09;和其他一些知名产品的高级用户界面。 首先从网上下载BCGControlBar v12资源 &…

MFC界面控件BCGControlBar v33.4 - 日历、属性网格组件升级

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版和BCGSuite for MFC v33.4已正式发布了&#xff0c;该版本包含了对Windows 11 Mica materi…

BCG学习(一)——BCGControlBar安装与配置

最近工作中需要用到BCG相关的知识&#xff0c;趁着全民防疫、居家隔离这段时间正好学习一下&#xff0c;作此笔记&#xff0c;记录学习过程和心得体会。话不多说&#xff0c;开整&#xff01; 简介 下载、安装与配置 例程编译与运行 简介 BCG是MFC的一个扩展库&#xff0c;可以…

MFC扩展库BCGControlBar Pro v33.5新版亮点 - 控件、脚本管理增强

BCGControlBar库拥有500多个经过全面设计、测试和充分记录的MFC扩展类。 我们的组件可以轻松地集成到您的应用程序中&#xff0c;并为您节省数百个开发和调试时间。 BCGControlBar专业版 v33.5已正式发布了&#xff0c;此版本包含了Ribbon&#xff08;功能区&#xff09;自定义…