代码编译运行环境:VS2017+Debug+Win32

文章目录

  • 1.数组
    • 1.1数组名的意义
    • 1.2数组的初始化
  • 2.指针
    • 2.1指针的定义
    • 2.2定义指针的形式
    • 2.3指针的初始化
    • 2.4指针可以参与的运算
    • 2.5注意指针的有效性
  • 3.数组与指针的关系
  • 参考文献

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。因此指针pPointer的解引用后的值变成了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++高级进阶教程[M].武汉:武汉大学出版社,2008[5.1(P186-P195)]

C++ 数组与指针详解之终极无惑相关推荐

  1. C printf() 详解之终极无惑

    FBI WARNING 鄙人首个开源电子书 <Go 编码建议>已经上线啦,欢迎各位大佬斧正指导,协同共建 编译环境:Linux 环境以 g++ 4.4.6 编译成 64 位程序 文章目录 ...

  2. 配置文件格式详解之终极无惑

    文章目录 1.键值对 2.JSON 2.1 JSON 语法 2.2 JSON 实例 2.3 JSON 解析 3.XML 3.1 XML 语法 3.2 XML 实例 3.3 XML 解析 4.YAML ...

  3. C语言printf() 详解之终极无惑

    关注.星标公众号,直达精彩内容 来源:CSDN - 恋猫大鲤鱼 编译环境:Linux 环境以 g++ 4.4.6 编译成 64 位程序 1.printf() 简介 printf() 是 C 语言标准库 ...

  4. printf()详解之终极无惑

    1.printf()简介 printf()是C语言标准库函数,用于将格式化后的字符串输出到标准输出.标准输出,即标准输出文件,对应终端的屏幕.printf()申明于头文件stdio.h. 函数原型: ...

  5. C++ cin 详解之终极无惑

    代码编译运行环境:VS2017+Win64+Debug. 文章目录 1.简介 2.常用输入方法 2.1 cin>> 的用法 2.2 cin.get() 的用法 2.2.1 cin.get( ...

  6. C++ 数据类型转换详解之终极无惑

    程序开发环境:VS2017+Win32+Debug 文章目录 1. 隐式数据类型转换 2. 显示数据类型转换 3.C++ 新式类型转换 3.1 const_cast 3.2 static_cast 3 ...

  7. printf()详解之终极无惑-转载

    https://blog.csdn.net/k346k346/article/details/52252626 http://www.cplusplus.com/reference/cstdio/pr ...

  8. Go Context 详解之终极无惑

    文章目录 1.什么是 Context 2.为什么要有 Context 3.context 包源码一览 3.1 Context 3.2 CancelFunc 3.3 canceler 3.4 Conte ...

  9. C语言:数组与指针详解

    文章概述 本文章讲述关于C语言的主要内容如下:   1.字符数组.2.一维数组与指针.3.二维数组与指针.4.字符串与指针.5.字符串指针数组 一.字符数组   定义一维数组时,系统会在内存中为其分配 ...

最新文章

  1. Oculus软件完全支持三传感器室内跟踪
  2. bp 字母识别 java_Matlab实现BP网络识别字母
  3. 2017各银行贷款利息表及P2P平台贷款利率比较
  4. matlab错误:Variable 'a' cannot be saved to a MAT-file whose version is older than 7.3.
  5. 模板模式在源码中的体现
  6. 基于SOA的银行系统架构
  7. threejs精灵(Sprite)
  8. 【转】js版的俄罗斯方块!!
  9. vscode找不到config_vscode中的 jsconfig.json
  10. 几米的一段话,献给所有小郁闷的人
  11. 软件公司多注重开发不注重管理
  12. java中this关键字的基本使用
  13. 学python有前途吗-我们能用Python做什么?学Python有前途吗?
  14. JS中时间格式化常用方法
  15. fabric1.0 java sdk_运行 fabric-sdk-java 官方示例
  16. 加速nginx: 开启gzip
  17. 第一冲刺阶段(第四天)
  18. python转txt到xml并编译为exe
  19. linux字体不识别不了怎么办,Docker容器不识别宋体等字体怎么办
  20. 中级微观经济学 复习框架思维导图-平狄克

热门文章

  1. Python排序算法[二]:测试数据的迷雾散去
  2. BZOJ 1041 圆上的整点 数学
  3. 测试工程师的核心竞争力----打卡第九天
  4. lvs之 lvs+nginx+tomcat_1、tomcat_2+redis(lvs dr 模式)
  5. 关于Oracle针对中文的md5加密
  6. 开源大数据周刊-第21期
  7. mysql 常用配置
  8. Python抓取小说
  9. ImageView.ScaleType 属性值
  10. Linux/Unix服务端和客户端Socket编程入门实例(含源码下载)