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

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++数组与指针的资料请关注我们其它相关文章!

(0)

相关推荐

  • 基于C#调用c++Dll结构体数组指针的问题详解

    C#调用c++dll文件是一件很麻烦的事情,首先面临的是数据类型转换的问题,相信经常做c#开发的都和我一样把学校的那点c++底子都忘光了吧(语言特性类). 网上有一大堆得转换对应表,也有一大堆的转换实例,但是都没有强调一个更重要的问题,就是c#数据类型和c++数据类型占内存长度的对应关系. 如果dll文件中只包含一些基础类型,那这个问题可能可以被忽略,但是如果是组合类型(这个叫法也许不妥),如结构体.类类型等,在其中的成员变量的长度的申明正确与否将决定你对dll文件调用的成败. 如有以下代码,其

  • 浅谈C/C++中指针和数组的不同

    这边先简单介绍一下内存分区. 内存按照用途划分为五个区: 1.栈区:由系统控制分配和回收. 例如定义变量 int x = 0; int *p = NULL; 变量所占的内存都是分配在栈区的. 2.堆区:由程序员管理. 在C语言中由 malloc 申请的内存,或者在C++中,用 new 申请的内存,是在堆区中申请的.用完之后需要程序员自己回收,否则会造成内存泄漏. 3.全局区:存储全局变量及静态变量 4.常量区:存储常量. 5.代码区:存储编译之后的二进制代码. 数组和指针具有很大的相似性,实际上

  • C++中指针指向二维数组实例详解

    C++中指针指向二维数组实例详解 一维指针通常用指针表示,其指向的地址是数组第一元素所在的内存地址,如下 int ary[4][5]; int(*aryp)[5] = ary; 那么ary[4]相当于int(*aryp),以下理解如此,但参数传递需要知道实参所在 的一维个数,所以传递的时候应该传递多一个参数,子数组的引用可以理解 为(*p),那么取元素就是(*p)[i],如下 void printVal(int(*aryp)[5],int irowCount){ for (int(*p)[5]

  • C#访问C++动态分配的数组指针(实例讲解)

    项目中遇到C#调用C++算法库的情况,C++内部运算结果返回矩形坐标数组(事先长度未知且不可预计),下面方法适用于访问C++内部分配的任何结构体类型数组.当时想当然的用ref array[]传递参数,能计算能分配,但是在C#里只得到arr长度是1,无法访问后续数组Item. C++ 接口示例: void Call(int *count, Rect **arr) { //-.. //重新Malloc一段内存,指针复制给入参,外部调用前并不知道长度,另外提供接口Free内存 //-. } 结构体:

  • 详解C++中的对象指针与对象数组

    C++对象指针 指向对象的指针 在建立对象时,编译系统会为每一个对象分配一定的存储空间,以存放其成员.对象空间的起始地址就是对象的指针.可以定义一个指针变量,用来存放对象的指针. 如果有一个类: class Time { public : int hour; int minute; int sec; void get_time( ); }; void Time::get_time( ) { cout<<hour<<":"<<minute<<

  • c++用指针交换数组的实例讲解

    对于指针一直很迷,今天看了一下指针交换数组,知识量很少,希望能帮助到大家. 利用指针来交换数组主要是为了节省时间嘛,有两种交换方式 第一种是写一个函数把数组传过去然后用swap交换,即可 代码如下: #include<iostream> #include<cstdio> #include<ctime> using namespace std; int a[100000050],b[100000050]; void da(int *a,int *b) { swap(a,b

  • 详解C++中的指针结构体数组以及指向结构体变量的指针

    C++结构体数组 一个结构体变量中可以存放一组数据(如一个学生的学号.姓名.成绩等数据).如果有10个学生的数据需要参加运算,显然应该用数组,这就是结构体数组.结构体数组与以前介绍过的数值型数组的不同之处在于:每个数组元素都是一个结构体类型的数据,它们都分别包括各个成员项. 定义结构体数组和定义结构体变量的方法相仿,定义结构体数组时只需声明其为数组即可.如: struct Student //声明结构体类型Student { int num; char name[20]; char sex; i

  • C/C++ 数组和指针及引用的区别

    C/C++ 数组和指针及引用的区别 1.数组和指针的区别 (1)定义 数组是一个符号,不是变量,因而没有自己对应的存储空间.但是,指针是一个变量,里面存储的内容是另外一个变量的地址,因为是变量所以指针有自己的内存空间,只不过里面存储的内容比较特殊. (2)区别 a.对于声明和定义,指针和数组是不相同的,定义为数组,则声明也应该是数组,不可混淆 b.当作下标操作符时,指针和数组是等价的.a[i]会被编译器翻译成*(a+i). c.当数组声明被用作函数形参的时候,数组实际会被当作指针来使用. (3)

  • C语言中的数组和指针汇编代码分析实例

    今天看<程序员面试宝典>时偶然看到讲数组和指针的存取效率,闲着无聊,就自己写了段小代码,简单分析一下C语言背后的汇编,可能很多人只注重C语言,但在实际应用当中,当出现问题时,有时候还是通过分析汇编代码能够解决问题.本文只是为初学者,大牛可以飘过~ C源代码如下: 复制代码 代码如下: #include "stdafx.h" int main(int argc, char* argv[]) {        char a=1;        char c[] = "

  • 约瑟夫环问题的PHP实现 使用PHP数组内部指针操作函数

    来看看这个问题的详细描述: view sourceprint?一群猴子排成一圈,按 1,2,...,n 依次编号.然后从第 1 只开始数,数到第 m 只,把它踢出圈,从它后面再开始数, 再数到第 m 只,在把它踢出去...,如此不停的进行下去, 直到最后只剩下一只猴子为止,那只猴子就叫做大王.要求编程模拟此过程,输入 m.n, 输出最后那个大王的编号. 刚开始构思的时候想使用 PHP 数组来实现(当然最后还是使用的数组),然后模拟一个数组的内部指针,结果发现想模拟一个"数组指针"不是那

  • 详解C语言中的函数、数组与指针

    1.函数:当程序很小的时候,我们可以使用一个main函数就能搞定,但当程序变大的时候,就超出了人的大脑承受范围,逻辑不清了,这时候就需要把一个大程序分成许多小的模块来组织,于是就出现了函数概念:   函数是C语言代码的基本组成部分,它是一个小的模块,整个程序由很多个功能独立的模块(函数)组成.这就是程序设计的基本分化方法: (1) 写一个函数的关键: 函数定义:函数的定义是这个函数的实现,函数定义中包含了函数体,函数体中的代码段决定了这个函数的功能: 函数声明:函数声明也称函数原型声明,函数的原

  • php数组函数序列之end() - 移动数组内部指针到最后一个元素,并返回该元素的值

    定义和用法 end() 函数将数组内部指针指向最后一个元素,并返回该元素的值(如果成功). 语法 end(array)参数 描述 array 必需.规定要使用的数组. 例子 复制代码 代码如下: <?php $people = array("Peter", "Joe", "Glenn", "Cleveland"); echo current($people) . "<br />"; ech

  • 数组和指针的区别深入剖析

    在C/C++中,指针和数组在很多地方可以互换使用,这使得我们产生一种错觉,感觉数组和指针两者是完全等价的,事实上数组和指针是有很大的区别的. 1.两者在含义上的区别. 数组对应着一块内存区域,而指针是指向一块内存区域.其地址和容量在生命期里不会改变,只有数组的内容可以改变:而指针却不同,它指向的内存区域的大小可以随时改变,而且当指针指向常量字符串时,它的内容是不可以被修改的,否则在运行时会报错. 如: 复制代码 代码如下: #include<stdio.h> #include<stdli

  • 举例理解C语言二维数组的指针指向问题

    之前对数组的概念一直没有理解透彻,只觉得数组名就是个常量指针而已,用法和基本的指针差不多.所以当我尝试用二级指针去访问二维数组时,就经常会出错.下面就是刚开始写的一个错误的程序: #include <stdio.h> int main() { int iArray[2][3] = {{1,2,3},{4,5,6}}; int **pArray = NULL; pArray = iArray; printf("array[0][0] = %d\n", pArray[0][0]

  • 直观理解C语言中指向一位数组与二维数组的指针

    一维数组和指针: 对于一位数组和指针是很好理解的: 一维数组名: 对于这样的一维数组:int a[5];  a作为数组名就是我们数组的首地址, a是一个地址常量 . 首先说说常量和变量的关系, 对于变量来说, 用箱子去比喻再好不过了, 声明一个变量就声明一个箱子,比如我们开辟出一个苹果类型的箱子, 给这个变量赋值就是把盛放苹果的箱子中放入一个实实在在的苹果, 这就是变量的赋值.  而对于数组来说, 就是一组类型相同的箱子中,一组苹果箱子, 可以放入不同的苹果. 一维数组空间: 变量被声明后, 我

  • C++中字符串以及数组和指针的互相使用讲解

    C++字符串与指针 在C++中可以用3种方法访问一个字符串(在第5章介绍了前两种方法). 用字符数组存放一个字符串 [例]定义一个字符数组并初始化,然后输出其中的字符串. #include <iostream> using namespace std; int main( ) { char str[]="I love CHINA!"; cout<<str<<endl; return 0; } 运行时输出: I love CHINA! 用字符串变量存放

  • C++中用new创建二维数组和指针数组实例代码

    使用new 创建二维数组方法 #include <iostream> using namespace std; void main() { //用new创建一个二维数组,有两种方法,是等价的 //一: int (*p)[10] = new int[5][10]; //二: int **p = new int* [5]; for(int i=0;i <5;i++) p[i] = new int[10]; //指针数组的创建,也有两种方法 //一: char **pa = new char*

随机推荐