首页 > 编程语言 >从Python语言的角度看C++的指针

从Python语言的角度看C++的指针

时间:2024-03-05 11:02:35浏览次数:29  
标签:Python C++ int grid var new main 指针

技术背景

从一个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++,int,grid,var,new,main,指针
From: https://www.cnblogs.com/dechinphy/p/18047405/pointer

相关文章

  • python打包exe
    参考:https://blog.csdn.net/zkkkkkkkkkkkkk/article/details/125082767一、为什么要打包    就比如你在具有Python环境的A电脑上开发了一个程序,现在A电脑坏掉了,你只能在B电脑上去执行你开发的程序。但是呢B电脑又没有Python环境,短时间又不能解决环境问题。这时候就需要用到......
  • python元类
    python元类classCar(object):def__init__(self,name):print('init')self.name=namedef__new__(cls,*args,**kwargs):print('new')returnsuper().__new__(cls)obj=Car('雪佛兰')#输......
  • Python工具箱系列(五十)
    使用PIL加工图片 常见的图片操作包括但不限于:•大小进行变化•旋转•翻转•使用滤镜进行处理•剪切   以下python代码演示了如何将一幅美女图进行多种加工处理,并且汇集在一起,形成一个类似于照片墙的相关操作。fromPILimportImagefromPILimportImageFilterf......
  • python与人工智能
    由于无文化是机器学习学是大模型一些就用开发,大部份教学都是python方便实验,就导致了要对python第二遍在过一下相关知识,虽然10年前学一遍python和web开发,估记也忘记差不多了,主要是针对ES文件检索,向量数据库,字符串处理,ESP32 fask这些也不太熟了,所以整理一下pythonAI应用开发......
  • C++面试,实现memcpy,strcpy这2个函数的功能
    `strcpy`和`memcpy`都是用于内存复制的函数,但它们之间有几个关键的区别:1.**复制的对象**:-`strcpy`主要用于复制字符串,它将从源字符串的起始位置开始复制字符,直到遇到源字符串的空字符('\0'),然后将空字符也复制到目标字符串中,表示字符串的结束。-`memcpy`则是通用的内存复......
  • python 语法之 print 函数和 input 函数
    print函数input函数案例一:圆的周长importmaths=input("圆的半径:\n")s=float(s)c=2*math.pi*sprint(f"圆的周长,{c:.2f}")w=input("请输入天气情况(可选项:晴、阴):")ifw=="晴天":print("play")else:print(f"天气{w}不玩")......
  • python益智游戏五子棋的二次创新
    五子棋是一种源自中国的传统棋类游戏,起源可以追溯到古代。它是一种两人对弈的游戏,使用棋盘和棋子进行。棋盘通常是一个15×15的网格,棋子分为黑白两色,双方轮流在棋盘上落子。游戏的目标是通过在棋盘上落子,使自己的五个棋子在横向、纵向或斜向形成连续的线路,从而获胜。五子棋被认......
  • WSGI介绍:Python 首先了解
    1.1什么是WSGI首先介绍几个关于WSGI相关的概念WSGI:全称是WebServerGatewayInterface,WSGI不是服务器,python模块,框架,API或者任何软件,只是一种规范,描述webserver如何与webapplication通信的规范。server和application的规范在PEP3333中有具体描述。要实现WSGI协议,必须同时实......
  • python-pip更改下载路径,解决超时问题
    有时pip安装包时,会提示pip._vendor.urllib3.exceptions.ReadTimeoutError:HTTPSConnectionPool(host='files.pythonhosted.org',port=443):Readtimedout.原因跟解决方式PyPI镜像:考虑使用PyPI的镜像站点。中国用户经常遇到与files.pythonhosted.org的连接问题,因此他们经常......
  • python内置方法(重点)
    方法作用示例输出upper全部大写"hello".upper()"HELLO"lower全部小写"Hello".lower()"hello"startswith()是否以a开头"Yuan".startswith("Yu")Trueendswith()是否以a结尾"Yuan".endswith("a&qu......