深入了解c++数组与指针

 更新时间:2020年8月14日 20:57  点击:1524

1.数组

数组大小(元素个数)一般在编译时决定,也有少部分编译器可以运行时动态决定数组大小,比如icpc(Intel C++编译器)。

1.1数组名的意义

数组名的本质是一个文字常量,代表数组第一个元素的地址和数组的首地址。数组名本身不是一个变量,不可以寻址,且不允许为数组名赋值。假设定义数组:

int A[10];

那么再定义一个引用:

int* &r=A;

这是错误的写法,因为变量A是一个文字常量,不可寻址。如果要建立数组A的引用,应该这样定义:

int* const &r=A;

此时,现在数据区开辟一个无名临时变量,将数组A代表的地址常量拷贝到该变量中,再将常引用r与此变量进行绑定。此外,定义一个数组A,则A、&A[0]、A+0是等价的。

在sizeof()运算中,数组名代表的是全体数组元素,而不是某个单个元素。例如,定义int A[5],生成Win32的程序,sizeof(A)就等于5*sizeof(int)=5*4=20。示例程序如下。

#include <iostream>
using namespace std;

int main()
{
int A[4]={1,2,3,4};
int B[4]={5,6,7,8};
int (&rA)[4]=A; //建立数组A的引用

cout<<"A:"<<A<<endl;
cout<<"&A:"<<&A<<endl;
cout<<"A+1:"<<A+1<<endl;
cout<<"&A+1:"<<&A+1<<endl;
cout<<"B:"<<B<<endl;
cout<<"rA:"<<rA<<endl;
cout<<"&rA:"<<&rA<<endl;
}

运行结果:

A:0013F76C
&A:0013F76C
A+1:0013F770
&A+1:0013F77C
B:0013F754
rA:0013F76C
&rA:0013F76C

阅读以上程序,注意如下几点。

(1)A与&A的结果在数值上是一样的,但是A与&A的数据类型却不同。A的类型是int[4],&A的类型则是int(*)[4]。它们在概念上是不一样的,这就直接导致A+1与&A+1的结果完全不一样。

(2)为变量建立引用的语法格式是type& ref,因为数组A的类型是int[4],因此为A建立引用的是int (&rA)[4]=A;

1.2数组的初始化

定义数组的时候,为数组元素赋初值,叫作数组的初始化。可以为一维数组指定初值,也可以为多维数组指定初值。例如。

Int A[5]={}; //定义长度为5的数组,所有数组元素的值都为0
int A[]={1,2,3}; //定义长度为3的数组,数组元素分别为1,2,3
int A[5]={1,2}; //定义长度为5的数组,A[0],A[1]分别为1,2,其他值均为0
int A[][2]={{1,2},{3,4},{5,6}}; //定义一个类型为int[3][2]的二维数组
int A[][2]={{1},{1},{1}}; //定义一个类型为int[3][2]的二维数组,A[0][0]、A[1][0]、A[2][0]三个元素的值为1,其他元素的值均为0

//以下是几种错误的初始化方法
int A[3]={1,2,3,4}; //初始化项的个数超过数组的长度 
int A[3]={1,,3}; //不允许中间跳过某项

2.指针

2.1指针的定义

指针是用来存放地址值的变量,相应的数据类型成为指针类型。在32位平台上,任何指针类型所占用的空间都是都是4字节。比如sizeof(int*)、sizeof(double*)、sizeof(float*)等的值都为4。

2.2定义指针的形式

定义指针的形式是:type* p,其中type是指针所指向对象的数据类型,而*则是指针的标志,p是指针变量的名字。由于C++中允许定义复合数据类型,因此指向复合数据类型对象的指针的定义方式可能较为复杂。理解指针,关键是理解指针的类型和指针所指向数据的类型。例如:

int (*p)[5]; //指针p的类型是int(*)[5],指针所指向的数据类型是int[5]
int* p[5]; //p是有5个分量的指针数组,每个分量的类型都是int*(指向int的指针)
int** p; //指针p的类型是int**,p指向的类型是int*,p是指向指针的指针

2.3指针的初始化

定义指针变量之后,指针变量的值一般是随机值,这样的值不是合法访问的地址。指针变量值的合法化途径通常有两个,
一是显示置空,二是让指针指向一个已经存在的变量,三是为指针动态申请内存空间。如下:

//显示置空
int *p=NULL;

//将指针指向某个变量
int i;
int *p=&i;

//动态申请内存空间
int* p=new int[10];

2.4指针可以参与的运算

由于指针是一个变量,所以指针可以参与一些运算。假设定义指针int* p,指针p能够参与的运算有:
(1)解引用运算,即获取指针所指的内存地址处的数据,表达式为*p,如果指针指向的是一个结构或者类的对象,那么访问对象成员有两种方式:(*p).mem或p->mem。

(2)取地址运算,即获取指针变量的地址,表达式为&p,其数据类型为int**;

(3)指针与整数相加减。表达式p+i(或者p-i),实际上是让指针递增或递减地移动i个int型变量的距离。

(4)两个指针相减,如p-q,其结果是两个指针所存储的地址之间的int型数据的个数。

2.5注意指针的有效性

使用指针的关键就是让指针变量指向一个它可以合法访问的内存地址,如果不知道它指向何处,请置为空指针NULL或者((void*)0)。

在某些情况下,指针的值开始是合法的,以后随着某些操作的进行,它变成了非法的值。考察如下程序。

#include <iostream>
using namespace std;

int* pPointer;
void SomeFunction()
{
int nNumber=25;
pPointer=&nNumber; //将指针pPointer指向nNumber
}

void UseStack()
{
int arr[100]={};
}

int main()
{
SomeFunction();
UseStack();
cout<<"value of *pPointer:"<<*pPointer<<endl;
}

输出结果是0,并非想象中的25。原因是函数SomeFunction()运行结束之后,局部变量nNumber已经被清空,其占有的空间在离开函数后归还给系统,之后又分配给函数UseStack()中的局部变量arr。因此指针pNumber的解引用后的值变成了0。所以,要想正确的使用指针,必须保证指针所指向单元的有效性。

3.数组与指针的关系

数组名代表数组的首地址,而数组A的某个元素A[i]可以解释成*(A+i),所以数组名本身可以理解为一个指针(地址),一个指针常量。所以,在很多情况下,数组与指针的用法是相同的,但是数组与指针本质上存在一些重要的区别。

(1)数组空间是静态分配的,编译时决定大小。而指针在定义时,可以没有合法访问的地址空间,也就是野指针。

(2)数组名代表一个指针常量,企图改变数组名所代表的地址的操作都是非法的。例如如下代码:

int arr[5]={0,1,2,3,4};
arr++; //编译错误

(3)函数形参中的数组被解释为指针。考察如下程序:

void show0(int A[])
{
A++;
cout<<A[0]<<endl;
}

void show1(int A[5])
{
A++;
cout<<A[0]<<endl;
}

int main()
{
int d[5]={1,2,3,4,5};
show0(d);
show1(d);
}

以上程序编译通过并输出2和2。程序中形参数组A可以进行自增运算,改变了自身的值,这个说明了形参数组A被当作指针看待。之所以这样处理,原因有两个,一是C++语言不对数组的下标作越界检查,因此可以忽略形参数组的长度;二是数组作整体进行传递时,会有较大的运行时开销,为了提高程序运行效率,将数组退化成了指针。

(4)如果函数的形参是数组的引用,那么数组的长度将被作为类型的一部分。实际上,对数组建立引用,就是对数组的首地址建立一个常引用。由于引用是C++引入的新机制,所以在处理引用时使用了一些与传统C语言不同的规范。在传统的C语言中,对数组的下标是不做越界检查,因此在函数的参数说明中,int[5]和int[6]都被理解为int[](也就是int*),C++语言也沿用了这种处理方式。但是,int(&)[5]与int(&)[6]被认为是不同的数据类型,在实参与形参的匹配过程作严格检查。考察如下程序。

#include <iostream>
using namespace std;

void show(int(&A)[5])
{
cout<<"type is int(&)[5]"<<endl;
}

void show(int(&A)[6])
{
cout<<"type is int(&)[5]"<<endl;
}

int main()
{
int d[5]={1,2,3,4,5};
show(d);
}

程序结果:

type is int(&)[5]

(5)在概念上,指针同一维数组相对应。多维数组是存储在连续的存储空间,而将多维数组当做一维数据看待时,可以有不同的分解方式。考察如下程序。

#include <iostream>
using namespace std;

void show1(int A[],int n)
{
for(int i=0;i<n;++i)
cout<<A[i]<<" ";
}

void show2(int A[][5],int n)
{
for(int i=0;i<n;++i)
show1(A[i],5);
}

void show3(int A[][4][5],int n)
{
for(int i=0;i<n;++i)
show2(A[i],4);
}

int main()
{
int d[3][4][5];
int i,j,k,m=0;
for(int i=0;i<3;++i)
for(int j=0;j<4;++j)
for(int k=0;k<5;++k)
d[i][j][k]=m++;

show1((int*)d,3*4*5);
cout<<endl;
show2((int(*)[5])d,3*4);
cout<<endl;
show3(d,3);
}

程序运行结果可以看出,以下三条输出语句的数据结果是相同的。

show1((int *)d,3*4*5);
show2((int(*)[5])d,3*4);
show3(d,3);

它们的输出结果完全一样,即从0到59。这说明把3维数组d当作一维数组看待,至少可以有以下3中不同的分解方式:

a.数据类型为int,元素个数为3*4*5=60;
b.数据类型为int[5],元素个数为3*4=12;
c.数据类型为int[4][5],元素个数为3。

所以,可以将多维数组看做“数组的数组”。在将多为数组转换为指针的时候,一定要注意多为数组的分解方式,以便进行正确的类型转换。

(6)字符数组与字符指针的区别。
字符数组字符指针在形式上很接近,但在内存空间的分配和使用上还是有重大的差别。如前所述,数组名并不是一个运行实体,它本身不能被寻址。而指针是一个变量(运行时实体),可以被寻址,它所指向的空间是否合法要在运行时决定。错误地使用指针将导致对内存空间的非法访问。考察如下程序。

#include <iostream>
using namespace std;
int main()
{
char s[]="abc"; //s是字符数组,空间分配在栈上。对字符数组元素的修改是合法的
char *p="abc"; 
s[0]='x';
cout<<s<<endl;
//p[0]='x'; //此句编译出错,指针指向常量区的字符串,对字符串常量的修改是非法的
cout<<p<<endl;
}

程序输出结果:

xbc
abc

以上就是深入了解c++数组与指针的详细内容,更多关于c++数组与指针的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • php中eval()函数操作数组的方法

    在php中eval是一个函数并且不能直接禁用了,但eval函数又相当的危险了经常会出现一些问题了,今天我们就一起来看看eval函数对数组的操作 例子, <?php $data="array...2016-11-25
  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • Python 图片转数组,二进制互转操作

    这篇文章主要介绍了Python 图片转数组,二进制互转操作,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-09
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • php数组操作 键名比较 差集 交集赋值

    本文章提供在量的数据中级操作实例有如对键名比较计算数组的差集 计算差集 给指定数组中插入一个元素 反转数组 交集赋值新的数组实例。 //定义回调函数 funct...2016-11-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C#二维数组基本用法实例

    这篇文章主要介绍了C#二维数组基本用法,以实例形式分析了C#中二维数组的定义、初始化、遍历及打印等用法,具有一定参考借鉴价值,需要的朋友可以参考下...2020-06-25
  • C#数组的常用操作方法小结

    Array数组在C#中同样是最基本的数据结构,下面为大家C#数组的常用操作方法小结,皆为细小的代码段,欢迎收看收藏...2020-06-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • php curl模拟post请求和提交多维数组的示例代码

    下面一段代码给大家介绍php curl模拟post请求的示例代码,具体代码如下: <&#63;php$uri = "http://www.cnblogs.com/test.php";//这里换成自己的服务器的地址// 参数数组$data = array ( 'name' => 'tanteng'// 'passwor...2015-11-24
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • C# 拷贝数组的几种方法(总结)

    下面小编就为大家带来一篇C# 拷贝数组的几种方法(总结)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧...2020-06-25
  • PHP 二维数组根据某个字段排序的具体实现

    本文记录的要实现的功能类似于 MySQL 中的 ORDER BY,上个项目中有遇到这样的一个需求。 要求:从两个不同的表中获取各自的4条数据,然后整合(array_merge)成一个数组,再根据数据的创建时间降序排序取前4条。 遇到这个...2014-06-07
  • C#实现字符串转换成字节数组的简单实现方法

    这篇文章主要介绍了C#实现字符串转换成字节数组的简单实现方法,仅一行代码即可搞定,非常简单实用,需要的朋友可以参考下...2020-06-25
  • c#将字节数组转成易读的字符串的实现

    这篇文章主要介绍了c#将字节数组转成易读的字符串的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-06-25
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25