指针变量与应用——动态数组

在C++中,有一种神奇的变量,它不可以表示一个值,但是可以表示某个元素的地址,通过地址来访问这个元素。

打个比方:你有一张地图和一个坐标,你就可以通过访问这个坐标来达到你访问坐标所表示的元素的目的。指针变量就是这个“坐标”。

下面我们来具体看看指针变量的应用。

1、指针变量的性质

正如上面所说,指针变量不可以表示一个值,但是可以指向别的元素的地址,通过这个地址来间接访问这个元素的值。

由于它的性质,指针变量不可以直接=一个元素,赋值时要注意。

具体操作下面会讲到。

2、指针变量的声明

如何声明一个指针变量? 有如下表达式:

数据类型+“*”+指针名

通常我们这样赋值:

int main()
{
int *p=NULL;
return ;
}

这样我们就定义了一个指针类型的变量p,NULL是空内存,这个内存里什么元素都没有,你可以之后再给p赋一个元素的地址。(可以不用=NULL,但是这是个人习惯,类似于return 0好习惯这种……)

这个语句的意义是:定义一个int类型的指针p,指向空地址。

那么怎么把一个元素的地址赋给一个指针变量呢?

有如下语句:

#include<cstdio>
using namespace std;
int main()
{
int a;
int *p=NULL;
p=&a;
return ;
}
/*int main()
{
int a;
int *p=&a;
return 0;
}*/

上面两个主函数的效果是一样的。

我们说说这两段代码的意义:

相信大家都用过scanf( ),在我们输入变量名前要加一个符号“&”,这就是地址符,表示变量名的地址。

我们的指针要指向一个地址,当然就是:指针名=&变量名啦!

3、用指针变量调用元素值

既然我们会赋值了,下一步就是调用元素值,但是指针指向的是一个地址,不能直接参与运算,这时候我们要用到间接运算符“*”。(就是定义的时候那个星号)

如果我们有一个元素a,需要用指针来输出它,怎么操作?

对于这个问题,有如下程序:

#include<cstdio>
using namespace std;
int main()
{
int a;
scanf("%d",&a);
int *p=&a;//定义一个指针变量p指向元素a
printf("%d",*p);//间接运算符+指针名表示指针所指元素
return ;
}

代码注释已经很详尽了,我们的指针指向一个元素,用“*”+指针名即可访问指针所指元素

注意:通过指针操作元素值和直接操作元素值是一样的,比如下面这段代码:

#include<cstdio>
using namespace std;
int main()
{
int a,b,s,t,*pa=NULL,*pb=NULL;
pa=&a,pb=&b;
a=,b=;
s=*pa+*pb;
t=*pa**pb;
printf("%d %d",s,t);
return ;
}

程序给出的结果是30 200。

4、指针的+、-运算

首先我们给出一个基本的定义:

当我们定义数组的时候,系统会给出连续的地址,比如a[5],假设a[0]的地址是0,那么a[1]的地址就是1……以此类推。

此时,我们直接把地址+1(指针+1),就可以访问数组的下一个元素。

#include<cstdio>
using namespace std;
int main()
{
int a[],*p=&a[];
for(int i=;i<;i++)
scanf("%d",&a[i]);
for(int i=;i<;i++)
{
printf("%d ",*p);
p++;
}
return ;
}

对于p--,同理。这个语句输出了a[ ]中的所有变量。

5、指针与数组

指向数组的指针叫数组指针,众所周知,一个数组的地址是连续的,首地址就是他所占有的几个单元的首地址,我们可以把数组名赋给指针,也可以把数组中某个元素的地址赋给它。

有以下语句:

int a[],*p=a;

则以下三个语句

&a[0],a,*p,均指向同一个单元——数组的首地址。

那么可以推导:&a[i]、a+i、p+i,均指向数组a中a[i]的地址。

有如下代码:

#include<cstdio>
using namespace std;
int main()
{
int a[],*pa=a;
for(int i=;i<;i++)
scanf("%d",&a[i]);
for(int i=;i<;i++)
printf("%d %d %d %d\n",*(pa+i),pa[i],a[i],*(a+i));
return ;
}

我们输如5个数:1 2 3 4 5

系统给出了5行:

1 1 1 1

2 2 2 2

3 3 3 3

4 4 4 4

5 5 5 5

这说明上面4个语句:*(pa+i),pa[i],a[i],*(a+i)是等价的。

代码说明和注意事项:

1、a(数组名)可以加“*”变为常量指针,a是开始元素,根据指针的加减原理,a+i是第i个元素的地址。

2、a是常量名,不能直接进行指针的+、-操作(这里指的是p++、p--这种赋值操作非法,但是a+i这种是合法的),但是pa是指针变量,可以进行加减操作。

6、指针申请系统空间

我们用如下语句可以申请一个系统空间给指针p:

int *p=new(int);

此时*p的内容不确定。

这个语句是动态数组的基础。

7、用指针当数组名

1、原理:之前说过,如果我们一次申请多个空间,系统会发给我们连续的新地址,可以当做数组用。

2、具体操作

有如下代码:

#include<cstdio>
using namespace std;
int main()
{
int n,*p;
scanf("%d",&n);
p=new int[n+];//申请连续的n+1个空间给指针p
for(int i=;i<=n;i++)
scanf("%d",&p[i]);
for(int i=;i<=n;i++)
printf("%d ",p[i]);
return ;
}

如果我们输入:

5

1 2 3 4 5

系统给出

1 2 3 4 5

上面的代码你可以理解有一个数组,数组名就是指针名,其余操作和第5个板块中提到的一样。(通过数组名+下标访问)

我们还可以改成这个样子:

#include<cstdio>
using namespace std;
int main()
{
int n,*p;
scanf("%d",&n);
p=new int[n+];//申请连续的n+1个空间给指针a
for(int i=;i<=n;i++)
scanf("%d",&p[i]);
for(int i=;i<=n;i++)
{
p++;//由于p默认指向第0个元素,所以先++
printf("%d ",*p);
}
return ;
}

这里使用指针访问而不使用数组名访问,和上面的代码是等价的。当然你也可以写成这样:printf("%d ",*(p+i));在上面提到过,这几种写法是等价的。

8、动态数组与空间复杂度优化

前面扯了那么多指针的基本定义和写法,终于到了今天的正题了——利用指针建立动态数组。

我们给出一个情景:现在有一个巨大(行列<=10000000)但是稀疏(大部分元素是0)的矩阵,我们要对这个矩阵进行操作,怎么办呢?

显然,这样的代码是绝对行不通的。

#include<cstdio>
#define N 10000100
using namespace std;
int n[N][N];

如果这么写,你的空间复杂度是绝对过不了的。

我们要进行优化才行。

记得指针可以申请空间吗?我们可以利用这个特性,避免存储无效数据(0),我们为每一次输入的有效数据开一个新的内存单元,这样就不会爆内存啦!

我们看下面这个例题:

一本通例题8.7:

【问题描述】

矩阵可以认为是N*M的二维数组。现在有一个巨大但稀疏的矩阵。

N,M的范围是1<=N,M<=100000,有K个位置有数据,1<=K<=100000。

矩阵输入的方式是从上到下(第1行到第N行),从左到右(从第1列到第M列)扫描,记录有数据的坐标位置(x,y)和值(v)。这是按照行优先的方式保存数据的。

现在要求按照列优先的数据,即从左到右,从上到下扫描,输出有数据的坐标和位置。

【输入格式】

第1行:3个整数N,M,K,其中1<=N,M,K<=100000;下面有K行,每行三个整数:a,b,c,表示第a行第b列有数据c。数据在int范围内,保证是行优先的次序。

【输出格式】

1行,K个整数,是按照列优先次序输出的数

【样例输入】

4 5 9

1 2 12

1 4 23

2 2 56

2 5 78

3 2 100

3 4 56

4 1 73

4 3 34

4 5 55

【样例输出】

73 12 56 100 34 23 56 78 55

【样例解释】

0 12 0 23 0
0 56 0 0 78
0 100 0 56 0
73 0 34 0 55

对于这个矩阵,我们可以这样存:

73 12 34 23 78
—— 56 —— 56 55
—— 100 —— —— ——
—— —— —— —— ——

注:标记“------”的都是没有使用的内存,这样我们就节省了11个内存单元,对于大数据的时候,我们还能节省更多的内存,保证不会超出空间限制。

这个思路的大体意思就是:忽略x的值,把第y列第一次输入的数据当做第y列的第一个数据,然后是第二个……

下面来看代码实现:

#include<cstdio>
using namespace std;
const int LP=;
int n,m,k;
int x[LP],y[LP],d[LP],c[LP];//记录数据,记录第n个数据在x行,y列,c是第y列的数据总数。
int *a[LP];//最多有LP列,所以我们开LP长度的指针数组
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=;i<=k;i++)
{
scanf("%d%d%d",&x[i],&y[i],&d[i]);//输入x,y,d
c[y[i]]++;//第y[i]列的数据个数++
}
for(int i=;i<=m;i++)
a[i]=new int[c[i]];//为每一列申请空间来存数据
for(int i=;i<=k;i++)
{
*a[y[i]]=d[i];//收集数据到第y列中
a[y[i]]++;//第y列的指针指向下一个地址,准备下一次收集
}
for(int i=;i<=m;i++)//列优先
{
a[i]-=c[i];//因为前面收集数据的时候每一列的指针都指向了该列的最后一个元素,所以要先减去该列的元素数,让它指向第一个元素
for(int j=;j<=c[i];j++,a[i]++)//从第1列开始输出,j用来统计输出到第i列第几个元素,如果输出到最后一个元素,跳出循环
printf("%d ",*a[i]);//指针每次+1,指向下一个元素并输出它
}
return ;
}

a[i]=new int c[[i]];这一句的意思是给a[i]这个指针新申请c[i]个空间,等同于我们开了LP个一维的指针数组,这些数组每一个都有一个专用的指针a[i],每个数组有c[i]个元素。

到这里,我们已经讲完了利用指针开动态数组数组的具体做法,这样可以很有效率的优化你的程序,赶紧用起来吧!!!

C++指针变量的基本写法的更多相关文章

  1. C语言指针变量作为函数参数

    0x01 指针变量作为函数参数的作用是:将一个变量的地址传送到另一个函数中. 0x02 简单的例子:虽然都能实现功能,但意义不同. 正确的写法: #include <stdio.h> vo ...

  2. 分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var)

    BOOL型变量:if(!var)    int型变量: if(var==0)    float型变量:    const float EPSINON = 0.00001;    if ((x > ...

  3. C语言之指针变量

    菜单导航 1.指针变量 2.指针和数组 3.常量指针和指向常量的指针 4.指针和字符串的关系 5.数组越界造成的访问不属于自己的内存空间现象 6.引用数据类型和基本数据类型,形参和实参 7.字符串和字 ...

  4. 【转】C语言中,为什么字符串可以赋值给字符指针变量

    本文是通过几篇转帖的文章整理而成的,内容稍有修改: 一. C语言中,为什么字符串可以赋值给字符指针变量 char *p,a='5';p=&a;                     //显然 ...

  5. C语言中,为什么字符串可以赋值给字符指针变量

    转载于:http://www.cnblogs.com/KingOfFreedom/archive/2012/12/07/2807223.html 本文是通过几篇转帖的文章整理而成的,内容稍有修改: 一 ...

  6. C语言指针篇(一)指针与指针变量

    指针 1. 什么是指针?    2. 指针可不可怕? 3. 指针好不好玩? 4. 怎么学好指针?     C语言是跟内存打交道的语言,指针就是内存地址.指针无处不在,指针并不可怕,相反,等你学到一定程 ...

  7. BOOL,int,float,指针变量 与“零值”比较的if语句

    分别给出BOOL,int,float,指针变量 与“零值”比较的 if 语句(假设变量名为var) 解答: BOOL型变量:if(!var) int型变量: if(var==0) float型变量: ...

  8. C/C++中的 if(指针变量) 和 if(!指针变量)

    目录 if(指针变量) 代码演示 if(指针变量) 解读代码 if(!指针变量) 解读代码 总结 替代方案.推荐写法!!!!! if(指针变量) 当把一个指针作为条件表达式时,所要判断的条件实际上就是 ...

  9. 全面总结sizeof的用法(定义、语法、指针变量、数组、结构体、类、联合体、位域位段)

    一.前言 编译环境是vs2010(32位). <span style="font-size:18px;">#include<iostream> #inclu ...

随机推荐

  1. element-ui设置级联选择器表单验证

    data(){<el-form :model="ruleForm" :rules="rules" ref="ruleForm" lab ...

  2. NIO入门之多路复用选择器Selector

    简介 Selector 是 java.nio.channels 包下的重要组件,阅读本文可以带你了解常用的 API.本文中把 Channel 翻译成信道,按照个人习惯也可以称作是通道.管道. Sele ...

  3. O、Θ、Ω、o、ω,别再傻傻分不清了!

    前言 本篇文章收录于专辑:http://dwz.win/HjK,点击解锁更多数据结构与算法的知识. 你好,我是彤哥,一个每天爬二十六层楼还不忘读源码的硬核男人. 前面几节,我们一起学习了算法的复杂度如 ...

  4. python为什么这么火?里面肯定是有原因的

    因为人生苦短要用python啊! 看完本文,你将在结尾得到本文的一个福利彩蛋 你瞧瞧其他语言之父... Java之父——James Gosling PHP之父 ——Rasmus Lerdorf Obj ...

  5. Linux企业运维人员最常用命令汇总

    本文目录 线上查询及帮助命令 文件和目录操作命令 查看文件及内容处理命令 文件压缩及解压缩命令 信息显示命令 搜索文件命令 用户管理命令 基础网络操作命令 深入网络操作命令 有关磁盘与文件系统的命令 ...

  6. Redis的持久化之RDB

    1.什么是Redis的持久化 Redis是一种高级key-value数据库,是一个高性能的(key/value)分布式内存数据库,基于内存运行并支持持久化的NoSQL数据库,所以Redis的所有数据都 ...

  7. python 安装 0x000007b错误解决及VC++ 安装第三方库报红

    dll 版本不对 dll 可能是 32 位和 64 位的 ,安装的可能不对 下载 DirectX_DLL修复工具v3.5增强版 进行修复 VC++ 安装第三方库报红问题 使用 VS 2017 或者 V ...

  8. TCP 服务器端

    """ 建立tcp服务器 绑定本地服务器信息(ip地址,端口号) 进行监听 获取监听数据(监听到的客户端和地址) 使用监听到的客户端client_socket获取数据 输 ...

  9. Python os.pipe() 方法

    概述 os.pipe() 方法用于创建一个管道, 返回一对文件描述符(r, w) 分别为读和写.高佣联盟 www.cgewang.com 语法 pipe()方法语法格式如下: os.pipe() 参数 ...

  10. PHP array_intersect_assoc() 函数

    实例 比较两个数组的键名和键值,并返回交集: <?php$a1=array("a"=>"red","b"=>"g ...