技术背景

从一个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 = &num;
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++的指针的更多相关文章

  1. Python语言初学总结

    课程名称:程序设计方法学 实验1:程序设计语言工具 时间:2015年10月21日星期三,第3.4节 地点:理工楼1#208 一.实验目的 1.深入理解程序设计语言及其几种常见的编程范型: 2.激发学生 ...

  2. 【阿里云产品公测】以开发者角度看ACE服务『ACE应用构建指南』

    作者:阿里云用户mr_wid ,z)NKt#   @I6A9do   如果感觉该评测对您有所帮助, 欢迎投票给本文: UO<claV   RsfTUb)<   投票标题:  28.[阿里云 ...

  3. C、C++、C#、Java、php、python语言的内在特性及区别

    C.C++.C#.Java.PHP.Python语言的内在特性及区别: C语言,它既有高级语言的特点,又具有汇编语言的特点,它是结构式语言.C语言应用指针:可以直接进行靠近硬件的操作,但是C的指针操作 ...

  4. sklearn:Python语言开发的通用机器学习库

    引言:深入理解机器学习并全然看懂sklearn文档,须要较深厚的理论基础.可是.要将sklearn应用于实际的项目中,仅仅须要对机器学习理论有一个主要的掌握,就能够直接调用其API来完毕各种机器学习问 ...

  5. 强者联盟——Python语言结合Spark框架

    引言:Spark由AMPLab实验室开发,其本质是基于内存的高速迭代框架,"迭代"是机器学习最大的特点,因此很适合做机器学习. 得益于在数据科学中强大的表现,Python语言的粉丝 ...

  6. Python语言 介绍

    一.python介绍python的创始人为吉多·范罗苏姆(Guido van Rossum).1989年的圣诞节期间,吉多·范罗苏姆为了在阿姆斯特丹打发时间,决心开发一个新的脚本解释程序,作为ABC语 ...

  7. 从Go语言编码角度解释实现简易区块链

    区块链技术 人们可以用许多不同的方式解释区块链技术,其中通过加密货币来看区块链一直是主流.大多数人接触区块链技术都是从比特币谈起,但比特币仅仅是众多加密货币的一种. 到底什么是区块链技术? 从金融学相 ...

  8. 【学习笔记】PYTHON语言程序设计(北理工 嵩天)

    1 Python基本语法元素 1.1 程序设计基本方法 计算机发展历史上最重要的预测法则     摩尔定律:单位面积集成电路上可容纳晶体管数量约2年翻倍 cpu/gpu.内存.硬盘.电子产品价格等都遵 ...

  9. Python基础之Python语言类型

    编程语言主要从以下几个角度进行分类: 编译型和解释型 静态语言和动态语言 强类型定义语言和弱类型定义语言 编译和解释的区别是什么? 编译器把源程序的每一条语句都编译成机器语言,并保存成二进制文件,这样 ...

  10. 关于《selenium2自动测试实战--基于Python语言》

    关于本书的类型: 首先在我看来技术书分为两类,一类是“思想”,一类是“操作手册”. 对于思想类的书,一般作者有很多年经验积累,这类书需要细读与品位.高手读了会深有体会,豁然开朗.新手读了不止所云,甚至 ...

随机推荐

  1. 【JS 逆向百例】网洛者反爬练习平台第一题:JS 混淆加密,反 Hook 操作

    关注微信公众号:K哥爬虫,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后 ...

  2. windowsbat删除命令

    widnwosbat命令 DEL /F /A /Q \?%1 用于删除指定路径下的文件,参数含义如下: /F: Force delete,即强制删除: /A: 用于指定文件属性,A代表存档,D代表目录 ...

  3. 安装Visual Studio的详细流程

      本文介绍Visual Studio 2022软件Community(社区版)的下载.安装.运行与使用方法.   首先需要提一句,本文介绍的是Visual Studio 2022软件的下载:而其它版 ...

  4. 从零开始配置vim(30)——DAP的其他配置

    很抱歉这么久才来更新这一系列,主要是来新公司还在试用期,我希望在试用期干出点事来,所以摸鱼的时间就少了.加上前面自己阳了休息了一段时间.在想起来更新就过去一个多月了.废话不多说了,让我们开始进入正题. ...

  5. c++基础之语句

    上一次总结了一下c++中表达式的相关内容,这篇博文主要总结语句的基础内容 简单语句 c++ 中语句主要是以分号作为结束符的,最简单的语句是一个空语句,空语句主要用于,语法上需要某个地方,但是逻辑上不需 ...

  6. 随机 Transformer

    在这篇博客中,我们将通过一个端到端的示例来讲解 Transformer 模型中的数学原理.我们的目标是对模型的工作原理有一个良好的理解.为了使内容易于理解,我们会进行大量简化.我们将减少模型的维度,以 ...

  7. C# 字符串转码后操作二进制文件

    String转码后写入二进制文件,读二进制文件进行解码返回. public class BinaryClass { /// <summary> /// 写二进制文件 /// </su ...

  8. java在服务器上创建文件(以shell脚本为例)并执行

    java在服务器上创建文件(以shell脚本为例)并执行 1️⃣ 首先写个方法,来在服务器上创建脚本 package com.preciouslove.xinxin_emo.controller; i ...

  9. git常用命令(企业级)

    一: 常用git命令 # 初始化,将已有的文件初始化为git仓库 git init # 查询文件状态[绿色暂存区,红色表示工作区更改了,没有提交到暂存区] git status git status ...

  10. jetson nano ssh远程连接控制

    jetson orin nano ssh远程连接 准备:好用的网线一根,jetson orin nano一台,将网线两端连接nano的网口以及当作主机的笔记本的网口 PS:确保双方网线连接成功,网线设 ...