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

在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. Newbe.Claptrap 框架入门,第二步 —— 简单业务,清空购物车

    接上一篇 Newbe.Claptrap 框架入门,第一步 —— 创建项目,实现简易购物车 ,我们继续要了解一下如何使用 Newbe.Claptrap 框架开发业务.通过本篇阅读,您便可以开始尝试使用 ...

  2. 程序员肺被切掉一块还得去加班... 再谈“工作996,生病ICU”

    如题,为什么要说再谈“工作996,生病ICU”,因为996问题早已不是一个新问题,在我最近刚出版的新书<SOD框架“企业级”应用数据架构实战>写作期间,爆发了一次程序员“起义”,出现了一个 ...

  3. http安全

    https介绍  因为HTTP是明文传输,所以不安全,容易被黑客窃听或窜改: 通信安全必须同时具备机密性.完整性,身份认证和不可否认这四个特性 HTTPS的语法.语义仍然是HTTP,但把下层的协议由T ...

  4. zabbix修改默认密码

    1.mysql -u root -p 2.desc users; 3.select userid,alias,passwd from users; 4.update users set passwd= ...

  5. Java 添加、删除、替换、格式化Word中的文本(基于Spire.Cloud.SDK for Java)

    Spire.Cloud.SDK for Java提供了TextRangesApi接口可通过addTextRange()添加文本.deleteTextRange()删除文本.updateTextRang ...

  6. 花生壳内网穿透vue项目错误

    原因:新版的webpack-dev-server出于安全考虑,默认检查hostname,如果hostname不是配置内的,将中断访问. 解决:webpack.dev.conf.js添加配置 disab ...

  7. Python Cookbook(第3版) 中文版 pdf完整版|网盘下载内附提取码

    Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码 ...

  8. Android JNI之动态注册

    所谓动态注册,就是不用像静态注册那样按规则严格的命名native方法,而是在加载so库的时候完成这个从Java方法到native方法的匹配工作,而这个匹配工作,需要我们写native代码来完成.下面直 ...

  9. intel:spectre&Meltdown侧信道攻击(三)—— raw hammer

    今天介绍raw hammer攻击的原理:这次有点“标题党”了.事实上,raw hammer是基于DRAM内存的攻击:所以理论上,只要是用了DRAM内存的设备,不论是什么cpu(intel.amd,或则 ...

  10. 简单的vector--- 2

    如何重载operator[]   及其相关细节 如何使用 const_cast<>(  )  和 static_cast<>( ) 模板类 如何内部声明,外部定义友元函数 使用 ...