从Python语言的角度看C++的指针
技术背景
从一个Python Coder的角度来说,其实很羡慕C++里面指针类型的用法,即时指针这种用法有可能会给程序带来众多的不稳定因素(据C++老Coder所说)。本文主要站在一个C++初学者的角度来学习一下指针的用法,当然,最好是带着一定的Python基础再去学习C++的逻辑,会更容易一些。
内存地址赋值
一般认为,指针就是一个内存地址。其实Python中也可以获取内存地址,但如果直接使用Python的内存地址作为指针,那会是一个非常hacky的操作。使用内存地址有一个非常重要的好处,就是可以在不改动指针的情况下,直接在其他函数内修改指针对应的数据,直接避免了非必要的传参。比如下面这个示例:
// g++ main.cpp -o main && ./main
#include <iostream>
class Check{
public:
int* p;
void func_1(int* p);
};
void Check::func_1(int* p){
printf("%d\n", *p);
}
int main(){
int var = 1;
Check new_check;
new_check.p = &var;
new_check.func_1(new_check.p);
var++;
new_check.func_1(new_check.p);
}
在这个示例中,我们把var这个变量的内存地址作为new_check的一个属性值,然后在不改变new_check对象本身的情况下,我们在外部修改了var的值。那么在修改var前后,同样使用new_check的一个打印函数去打印指针所指向的内容,我们发现指针指向的内容已经被改变了:
$ g++ main.cpp -o main && ./main
1
2
在Python的常规编程中,如果不直接对new_check.p进行修改或者重新复制,我们是没办法改变new_check.p的值的,这是使用C++指针的好处之一。
多重索引
多重的指针,有点类似于一个链表的数据结构,在Python中必须要实现一个链表或者使用多层赋值的NamedTuple,但是在C++里面用起来就非常的自然:
// g++ main.cpp -o main && ./main
#include <iostream>
class Check{
public:
int** p;
void func_1(int** p);
};
void Check::func_1(int** p){
printf("%d\n", **p);
}
int main(){
int var = 1;
int num = 2;
int* p_out = &var;
Check new_check;
new_check.p = &p_out;
new_check.func_1(new_check.p);
p_out = #
new_check.func_1(new_check.p);
}
这里我们修改的是第二重指针指向的变量,从原来的指向var,变成了指向num的一个指针。由于我们把这个第二重的指针赋值给了第一重指针的索引,所以这里我们改变第二重指针指向的变量之后,第一重指针指向的最终变量也会发生变化:
$ g++ main.cpp -o main && ./main
1
2
数组指针
C++中可以用一个指针ptr指向数组的第一个元素,然后通过迭代指针的方法,例如使用ptr++来指向数组的下一个元素。
// g++ main.cpp -o main && ./main
#include <iostream>
#include <vector>
using namespace std;
int main(){
int var[] = {1, 2, 3, 4, 5};
vector<int> g{-1}, l{-1};
int *t = nullptr;
int len = sizeof(var) / sizeof(var[0]);
for (int i=0; i<len; i++){
if (var[i] <= 2){
l.push_back(var[i]);
}
else{
g.push_back(var[i]);
}
}
g.push_back(6);
t = &g[0];
for (int i=0; i<g.size()-1; i++){
t++;
printf("%d\n", *t);
}
}
输出的结果为:
$ g++ main.cpp -o main && ./main
3
4
5
6
这里需要注意的一点是,在这个代码中把数组的第一个元素赋值给指针是在数组完成更新之后操作的。如果在这之前操作,会因为push_back的操作导致指针移位,使得定义好的指针不再有意义,输出的结果也会是不可预测的随机值。只有这种原位替换,才不会影响到指针的指向:
// g++ main.cpp -o main && ./main
#include <iostream>
#include <vector>
using namespace std;
int main(){
int var[] = {1, 2, 3, 4, 5};
int *t = nullptr;
int len = sizeof(var) / sizeof(var[0]);
t = &var[0];
var[1] *= -1;
for (int i=0; i<len; i++){
printf("%d\n", *t);
t++;
}
}
这个案例中我们在定义了数组指针之后,又修改了数组var的第二个元素,输出结果如下:
1
-2
3
4
5
这里我们就可以看到,第二个元素被成功修改,但通过指针还是可以正常的索引到这个数组。
指针应用
这里我们用指针来完成一个“打格点的任务”。简单描述就是,将三维空间划分成若干个网格,然后将处于同一个网格的原子序号输出出来。这里使用的空间坐标,我们用c++的随机数生成器产生一个均匀分布的随机二维矩阵:
#include <vector>
#include <random>
using namespace std;
vector<vector<float>> random_crd(int random_seed, int n_atoms, int dimensions){
std::default_random_engine e;
// 产生[-0.5, 0.5]之间的均匀分布随机数
std::uniform_real_distribution<double> u(-0.5, 0.5);
e.seed(random_seed);
// 初始化一个shape为(n_atoms, dimensions)的矩阵,所有的元素初始化为0
vector<vector<float>> crd(n_atoms, vector<float>(dimensions, 0));
for (int i=0; i<n_atoms; i++){
for (int j=0; j<dimensions; j++){
crd[i][j] = u(e);
}
}
return crd;
}
这个产生的向量的形式大致是这样的:
// g++ main.cpp -o main && ./main
#include <iostream>
#include <vector>
#include <random>
using namespace std;
vector<vector<float>> random_crd(int random_seed, int n_atoms, int dimensions){
std::default_random_engine e;
std::uniform_real_distribution<double> u(-0.5, 0.5);
e.seed(random_seed);
vector<vector<float>> crd(n_atoms, vector<float>(dimensions, 0));
for (int i=0; i<n_atoms; i++){
for (int j=0; j<dimensions; j++){
crd[i][j] = u(e);
}
}
return crd;
}
int main(){
int N = 10;
int D = 3;
vector<vector<float>> crd = random_crd(0, N, D);
for (int i=0; i<N; i++){
for (int j=0; j<D; j++){
printf("%f,", crd[i][j]);
}
printf("\n");
}
}
打印输出结果为:
-0.368462,-0.041350,-0.281041,
0.178865,0.434693,0.019416,
-0.465428,0.029700,-0.492302,
-0.433158,0.186773,0.430436,
0.026929,0.153919,0.201191,
0.262198,-0.452535,-0.171766,
0.256410,-0.134661,0.482550,
0.253356,-0.427314,0.384707,
-0.063589,-0.022268,-0.225093,
-0.333493,0.397656,-0.439436,
我们可以先简单的假设把这个-0.5到0.5的范围切成8个正方体,或者叫8个Grid。总粒子数为N,然后可以假设每个Grid中的粒子数有一个上限M。这样一来我们可以构造一个(8,M)的矩阵用于存储每一个Grid中的原子序号,然后用一个大小为N的指针数组来定位每一个Grid中当前的索引记录。
// g++ main.cpp -o main && ./main
#include <iostream>
#include <vector>
#include <random>
using namespace std;
// 产生一个随机初始化的空间坐标
vector<vector<float>> random_crd(int random_seed, int n_atoms, int dimensions){
std::default_random_engine e;
std::uniform_real_distribution<double> u(-0.5, 0.5);
e.seed(random_seed);
vector<vector<float>> crd(n_atoms, vector<float>(dimensions, 0));
for (int i=0; i<n_atoms; i++){
for (int j=0; j<dimensions; j++){
crd[i][j] = u(e);
}
}
return crd;
}
// 将空间格点化,输出位于每一个格点中的原子序号
vector<vector<int>> grids(vector<vector<float>> crd, int *grid_ptr[8], int max_atoms){
int grid_index = 0;
vector<vector<int>> grid_atoms(8, vector<int>(max_atoms, -1));
for (int i=0; i<crd.size(); i++){
// 计算当前原子的坐标所处的格点序号
grid_index += 4 * static_cast<int>(crd[i][0] > 0);
grid_index += 2 * static_cast<int>(crd[i][1] > 0);
grid_index += 1 * static_cast<int>(crd[i][2] > 0);
// 向对应格点矩阵中添加原子序号
if (grid_ptr[grid_index] == nullptr){
grid_atoms[grid_index][0] = i;
grid_ptr[grid_index] = &grid_atoms[grid_index][0];
}
else{
// 指针移位
grid_ptr[grid_index]++;
*(grid_ptr[grid_index]) = i;
}
grid_index = 0;
}
return grid_atoms;
}
int main(){
int N = 10;
int D = 3;
int M = 4;
vector<vector<float>> crd = random_crd(0, N, D);
// 初始化一个nullptr空指针
int *grid_ptr[8];
for (int i=0; i<8; i++){
grid_ptr[i] = nullptr;
}
// 计算格点化
vector<vector<int>> grid_atoms = grids(crd, grid_ptr, M);
// 打印输出
for (int i=0; i<8; i++){
for (int j=0; j<M; j++){
printf("%d,", grid_atoms[i][j]);
}
printf("\n");
}
return 0;
}
上述代码的运行结果为:
$ g++ main.cpp -o main && ./main
0,8,-1,-1,
-1,-1,-1,-1,
2,9,-1,-1,
3,-1,-1,-1,
5,-1,-1,-1,
6,7,-1,-1,
-1,-1,-1,-1,
1,4,-1,-1,
如果把参数改为:20个原子、单格点最大原子数为5,得到的输出结果为:
0,8,11,-1,-1,
-1,-1,-1,-1,-1,
2,9,17,-1,-1,
3,15,18,-1,-1,
5,10,12,16,19,
6,7,13,-1,-1,
-1,-1,-1,-1,-1,
1,4,14,-1,-1,
整体来说这个实现方法用起来还是比较灵活的。
总结概要
本文主要是站在一个有一定的Python经验的C++新手的角度,学习一下C++中的指针使用方法。指针其实就是一个内存地址的标记,同时在用法上也跟Python中的迭代器很相似,可以通过指针移位来标记下一个需要读取或者更新的位置。通过这一篇文章,可以掌握指针对象的赋值、多重指针的使用和数组指针的使用,以及最后我们介绍了一个基于指针数组来实现的空间格点划分算法。
版权声明
本文首发链接为:https://www.cnblogs.com/dechinphy/p/pointer.html
作者ID:DechinPhy
更多原著文章:https://www.cnblogs.com/dechinphy/
请博主喝咖啡:https://www.cnblogs.com/dechinphy/gallery/image/379634.html
从Python语言的角度看C++的指针的更多相关文章
- Python语言初学总结
课程名称:程序设计方法学 实验1:程序设计语言工具 时间:2015年10月21日星期三,第3.4节 地点:理工楼1#208 一.实验目的 1.深入理解程序设计语言及其几种常见的编程范型: 2.激发学生 ...
- 【阿里云产品公测】以开发者角度看ACE服务『ACE应用构建指南』
作者:阿里云用户mr_wid ,z)NKt# @I6A9do 如果感觉该评测对您有所帮助, 欢迎投票给本文: UO<claV RsfTUb)< 投票标题: 28.[阿里云 ...
- C、C++、C#、Java、php、python语言的内在特性及区别
C.C++.C#.Java.PHP.Python语言的内在特性及区别: C语言,它既有高级语言的特点,又具有汇编语言的特点,它是结构式语言.C语言应用指针:可以直接进行靠近硬件的操作,但是C的指针操作 ...
- sklearn:Python语言开发的通用机器学习库
引言:深入理解机器学习并全然看懂sklearn文档,须要较深厚的理论基础.可是.要将sklearn应用于实际的项目中,仅仅须要对机器学习理论有一个主要的掌握,就能够直接调用其API来完毕各种机器学习问 ...
- 强者联盟——Python语言结合Spark框架
引言:Spark由AMPLab实验室开发,其本质是基于内存的高速迭代框架,"迭代"是机器学习最大的特点,因此很适合做机器学习. 得益于在数据科学中强大的表现,Python语言的粉丝 ...
- Python语言 介绍
一.python介绍python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语 ...
- 从Go语言编码角度解释实现简易区块链
区块链技术 人们可以用许多不同的方式解释区块链技术,其中通过加密货币来看区块链一直是主流.大多数人接触区块链技术都是从比特币谈起,但比特币仅仅是众多加密货币的一种. 到底什么是区块链技术? 从金融学相 ...
- 【学习笔记】PYTHON语言程序设计(北理工 嵩天)
1 Python基本语法元素 1.1 程序设计基本方法 计算机发展历史上最重要的预测法则 摩尔定律:单位面积集成电路上可容纳晶体管数量约2年翻倍 cpu/gpu.内存.硬盘.电子产品价格等都遵 ...
- Python基础之Python语言类型
编程语言主要从以下几个角度进行分类: 编译型和解释型 静态语言和动态语言 强类型定义语言和弱类型定义语言 编译和解释的区别是什么? 编译器把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样 ...
- 关于《selenium2自动测试实战--基于Python语言》
关于本书的类型: 首先在我看来技术书分为两类,一类是“思想”,一类是“操作手册”. 对于思想类的书,一般作者有很多年经验积累,这类书需要细读与品位.高手读了会深有体会,豁然开朗.新手读了不止所云,甚至 ...
随机推荐
- Ant Design Vue封装a-drawer
1.创建子组件 <template> <a-drawer :title="drawerInfo.customTitle" :placement="pla ...
- 手写promise实现自定义封装多个回调函数的执行
自定义封装多个回调函数的执行 <script src="./Promise.js"></script> let p = new Promise((resol ...
- python.exe和pythonw.exe的区别(区分.py、.pyw、.pyc、.pyo文件)
python和pythonw 在Windows系统搭建好Python的环境后,进入Python的安装目录,大家会发现目录中有python.exe和pythonw.exe两个程序.如下图所示: 它们到底 ...
- Prompt learning 教学基础篇:prompt基本原则以及使用场景技巧助力你更好使用chatgpt,得到你想要的答案
Prompt learning 教学[基础篇]:prompt基本原则以及使用场景技巧助力你更好使用chatgpt,得到你想要的答案 如果你想系统学习 如果你对 AI 和 Prompt Engineer ...
- C++ 基于Boost.Asio实现端口映射器
Boost.Asio 是一个功能强大的 C++ 库,用于异步编程和网络编程,它提供了跨平台的异步 I/O 操作.在这篇文章中,我们将深入分析一个使用 Boost.Asio 实现的简单端口映射服务器,该 ...
- 多路io复用pool [补档-2023-07-19]
多路IO- poll 3.1简介 poll的机制与select类似,他们都是让内核在以线性的方法对文件描述符集合进行检测,根据描述符的状态进行具体的操作.并且poll和select在检测描述符集合 ...
- vue+elementui批量上传下载注意事项
批量手动上传文件,和表单数据一起提交 1.在el-upload组件关键的钩子,其它省略 multiple :auto-upload = "false" :file-list = & ...
- Arduino语言基础(萌新)
Arduino语言基础(萌新) Arduino语言注解Arduino语言是建立在C/C++基础上的,其实也就是基础的C语言,Arduino语言只不过把AVR单片机(微控制器)相关的一些参数设置都函数化 ...
- ICLR 2024 | Mol-Instructions: 面向大模型的大规模生物分子指令数据集
Mol-Instructions: 面向大模型的大规模生物分子指令数据集 发表会议:ICLR 2024 论文标题:Mol-Instructions: A Large-Scale Biomolecula ...
- MarkDown文件插入公式(常用格式)
1.插入公式 markdown支持插入公式,书写公式需要按照特定格式来写,涉及到希腊字母.符号.角标.基本语法等内容需要熟悉, 1.1 句中插入公式 表达式前后插入$即可 ,比如$\alpha$,显示 ...