从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语言》
关于本书的类型: 首先在我看来技术书分为两类,一类是“思想”,一类是“操作手册”. 对于思想类的书,一般作者有很多年经验积累,这类书需要细读与品位.高手读了会深有体会,豁然开朗.新手读了不止所云,甚至 ...
随机推荐
- dispaly结合背景图片会提升加载性能
1.display的常见现象 我们很多人都知道,display可以让元素实现隐藏或者显示. 或者让行级元素变成块级元素. 对它的认识也是比较准确的. 如果一个元素使用了display:none; 那么 ...
- Linux挂载新磁盘到根目录
1.添加磁盘到需要挂载的机器上2.lsblk查看硬盘挂载情况,sdb,sdc为我新挂载的磁盘 3.fdisk -l查看挂载之前的分区情况, 4.为新硬盘创建分区 fdisk /dev/sdb,终端会提 ...
- 【8】同步vscode配置和插件【导入导出】、再也不用担心换电脑重新安装插件了
相关文章: [1]VScode中文界面方法-------超简单教程 [2]VScode搭建python和tensorflow环境 [3]VSCode 主题设置推荐,自定义配色方案,修改注释高亮颜色 [ ...
- 19.8 Boost Asio 异或加密传输
异或加密是一种对称加密算法,通常用于加密二进制数据.异或操作的本质是对两个二进制数字进行比较,如果它们相同则返回0,如果不同则返回1.异或加密使用一把密钥将明文与密文进行异或运算,从而产生密文.同时, ...
- centos环境下MySQL8.0.25离线升级至8.0.32
环境 centos7 mysql8.0.25 下载新版本mysql 下载地址:https://dev.mysql.com/downloads/mysql/ 升级 备份数据 先保存原始数据,进入mysq ...
- CH58x/CH57x硬件SPI操作外部flash学习记录
官方提供的58x的spi例程,spi主机模式下的发送方式有三种单字节发送,FIFO连续发送,DMA连续发送.本文分别对SPI0主机模式下三种发送模式进行使用. 本次使用的是CH582m做为主机,W25 ...
- DHCP中继代理配置与管理
实验介绍:DHCP中继存在目的 当一台DHCP需要配置不同网段的IP地址时 一:前期准备 1.在DHCP服务器配置页面 右键ipv4,建立多个作用域. 我这里设置了三个可以分配给服务器端的网段,分别是 ...
- .NET Core开发实战(第13课:配置绑定:使用强类型对象承载配置数据)--学习笔记
13 | 配置绑定:使用强类型对象承载配置数据 要点: 1.支持将配置值绑定到已有对象 2.支持将配置值绑定到私有属性上 继续使用上一节代码 首先定义一个类作为接收配置的实例 class Config ...
- .NET Core开发实战(第12课:配置变更监听)--学习笔记
12 | 配置变更监听:配置热更新能力的核心 这一节讲解如何使用代码来监视配置变化并做出一些动作 当我们需要追踪配置发生的变化,可以在变化发生时执行一些特定的操作 配置主要提供了一个 GetReloa ...
- JS Leetcode 26. 删除有序数组中的重复项 题解分析,字典与快慢双指针
壹 ❀ 引 本题来自LeetCode26. 删除有序数组中的重复项,是一道简单题,题目描述如下: 给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组 ...