当前课程知识点:基于Linux的C++ > 第六讲 复合数据类型 > 6.3 数组(一) > LinuxCPP0603
第二个方面就是我们的数组
这一节的内容有比较多
包括数组的意义和性质
包括数组在我们的C++的代码里边
怎么样存储表示
我们怎么访问这些数组的元素
和数组与函数之间的关系
最后我们还有一节
专门来讨论多维数组
我们首先来看数组的意义和性质
首先我们要搞清楚
在我们C++程序中怎么来定义数组
它的定义的方式格式是很简单的
就是一个int后面a[8]
这一句就定义了一个变量a
它是包含8个整数的整数数组
所以它基本的定义格式就是元素类型
后面跟着数组的名称
然后是常数表达式
表示数组的元素的个数
特别需要注意
当我们定义这个数组的时候
它这个常数表达式
必须是一个常数或常量
你不能够使用变量来定义它
因为编译器需要根据这个表达式的值
来去算元素的个数
然后要为它分配空间
那么这个计算的任务
是在什么时候做呢
是在编译的时候做的
所以如果你给它一个变量
那么它就没有办法在编译期间
算出它的结果来
为这个数组分配恰好那么多的
内存空间
它不能为它分配一个合适的内存空间
那么这个编译器它就会拒绝分配
所以这个常数表达式
它里面必须是一个常数或者常量
这两种方式在C++代码里边都可以
你如果定义变量int count 初始化为8
然后int c[count] 这就错了
在C++里边如果你把count定义成const int count
初始化8 然后int c[count]
那么在大多数编译器里边
这个写法就是对的
但是需要特别注意
如果你写的是C程序
常量 也是不允许的
你只能使用常数
数组的元素下标
它是从0开始计数的
也就是说数组的首元素
就是它的0号位元素
次元素是它的1号位元素
不是从1开始计数的
所以如果我定义了这个数组
包含了8个元素
那么它的下标就是从0到7
绝没有8号元
还需要说明的是
数组的操作不允许对数组进行整体赋值
你只能使用循环
一个一个地操作
这个数组中的某一个单一的元素
你比如讲int a[8], b[8]
然后我把b赋值给a
我这个数组一口气就全赋过去了
这个在C/C++里边是不可以的
特别注意这一条
我们定义数组的最大的目的
也就是它的意义和性质
就是为了将性质相同的
这样的数据元素组织成一个整体
构成单一纬度上面的数据序列
本来嘛 我这儿有一个数据对象
这一个数据对象包括一系列的数据
这些数据本身的性质是一模一样的
如果你使用了n多个变量
那么你就割裂了这些数据对象从属于
同一个集合的性质 它是一个整体
你把整体的这个性质给它取消了
那么在编程过程中它就有可能
给我们带来一些难以预见的结果
所以我们使用数组
来把具有相同性质的数据元素
组织成一个整体
然后构成一个单一纬度上的数据序列
有了数组的定义
具有相同性的这些数据
就可以很自如地组织在一块
在实际存储这个数组的时候
这个数组的元素依次连续地存放
中间是没有空闲的空间的
你比如说我刚才这个例子int a[8]
先放a[0] 然后放a[1] 然后放a[2]
这就成了0号元 1号元 2号元
然后就按照这个顺序往后放
一直放到7号元
前边没有任何控制这个数组开始的
特殊的标记
后面没有控制这个数组结束的
特殊的标记
前面没有前导标记 后边没有结束标记
中间绝不会出现空洞
0号元放完
紧跟着就放1号元 紧跟着就放2号元
这就是数组的存储表示
在我们计算机内存里边
它就按照这个方式来保存
这样紧密排布的数组元素
它有什么好处呢
一旦知道这个数组的基地址
再知道这个数组的
每个元素的存储空间大小之后
我们很容易地就能够把它计算出来
对于一个数组基地址来讲
它指的就是数组开始存储的
这个物理位置
对于这个数组首元素的地址呢
那就是这个数组的0号元的开始位置
因为数组的每一个元素
存储的那个地址空间啊
它不一定是单一的一个字节
它可能是很多个字节
我们往往说的就是它的基地址
就是它的起始位置那个地址编号
这个数组首元素的基地址呢
它也就是这个数组的基地址
因为我们刚才讲
这个数组实际存的时候
它前面没有前导标记的
后边没有结束标记的
中间是紧挨着连续放的
所以这个数组首元素的基地址
和数组的基地址
其实在数值是上是相等的
我们怎么取这个数组的元素
或这个数组的基地址呢
我们有一个特别的操作符
就叫“&”操作符
使用“&”来获得一个特定的量的基地址
&a就表示获得这个数组的基地址
&a[0]就表示获得这个数组a的
0号元的基地址
实际上这两者是等价的
在数值上是相同的
有一点需要特别注意
当单独出现这个数组的名字的时候
它往往就意味着
我们取这个数组的基地址
所以实际上前面那个“&”符号
大部分情况下是可以不写的 就写a
它其实取的就是这个数组a的基地址
它就是&a[0]
那么对于这个数组第I号元
我们怎么知道它的地址在哪里呢
如果我这个数组中的每个元素
的存储空间是m个字节
再加上这个数组的起始位置的地址是p
那么这个第i号元的地址
当然就是p+mi
这个式子非常非常重要
我们后边会看到怎么去计算
这个第i号元的基地址
事实上 在C++代码中
为了方便我们的编程
它的地址计算比我们这个方式还简单
在定义这个数组的时候
我们可以对它进行初始化
它的标准初始化格式就是int a[8]
后边跟着一个初始化的符号
然后跟着一个花括号对
把这个数组的元素一个一个往里塞
就按照这个方式定义就可以了
一个值、一个值、一个值
都放在花括号对里面
中间用分号来分割开
当你在对这个数组的全部元素
进行初始化的时候
中括号里边的那个数组的元素个数
可以不写
编译器知道有多少个元素
它会数后面你初始化了多少个数
前面它自动地就给你填几个
你自己就可以不写
如果你真不写了怎么办
你程序中想知道这个数组的元素个数呀
你就应该这个操作符sizeof
看上去像一个函数一样
但实际上它是一个操作符
它后面跟着一个数据类型
或者跟着一个变量
用来表达求取这个类型
或这个变量的存储空间的大小
如果跟着类型必须加小括号对
如果跟着变量加不加小括号对都可以
它这个值(得到的结果)
是以字节为单位的
如果你给我一个sizeof(int)
那么它的结果就是4个字节
如果你给我一个sizeof(a)
看这个数组的尺寸
那么它会告诉你
它是32个字节
因为这里面其实是8个元素
但我怎么知道呢 就这么去算
int num_of_elements元素的个数
我把它初始化成sizeof(a)/sizeof(a[0])
整个数组的尺寸
除上数组的0号元的尺寸
那就是这个数组的元素个数
因为数组的每个元素的尺寸是相同的嘛
实际上在对这个数组进行初始化的时候
这个8个元素的数组
你说我只想把它的前4位给初始化
后4个元素我不初始化
那就可以这么写int a[8]然后初始化
花括号对里边写成“1 ,2,3,4”
后面不要了 它就不初始化了
那你可能还会说
我想把后4个元素初始化
前4个我不初始化
你得要跟刚才那个写法区分开
你得写“int a[8] = {, , , , 5, 6, 7, 8}”
也就是说你前面1、2、3、4可以不要
但是四个逗号你不能省略
这样的话 才能知道
0号元你没写 1号元你没写
2号元你没写 3号元你没写
写的第一个事实上是4号元的
也就是第五个元素
我们来看一个数组的基本操作例子
我们写一个程序 使用一个数组
存储用户输入的5个整数 然后求和
就这一道题来讲
一维数组使用方式实际上
是非常非常简单的
它就像一个普通的变量一样使用
就可以了
我们定义了一个数组int a[5]
然后要有一个result来求和
定义了一个循环变量i
来操纵这个数组的每个元素
我们这个数组
同学们一定要记住
你是不能够对它进行整体赋值的
所以想要对这个数组进行赋值
那就只能一个一个来
所以我要写一个for循环
for(i=0;i<5;i++)
然后for循环里边
cout << “Integer No. ”然后cin >> a[i]
就按这个格式
我一个元素、一个元素、一个元素地
把这五个元素全部输入进去
你看a[i]是不是就像普通的变量
那计算的时候呢 一样的
一个for循环
然后我用result += a[i]
把a[i]给累加上去
所以你看到这个数组的某一个元素a[i]
它事实上就像普通的变量一样
可以被赋值 可以赋值给别人
这就是我们数组基本操作
接下来我们来看数组和函数的关系
当使用一个数组的元素
作为函数的实际参数的时候
它的使用和普通的变量没有什么差别
先定义这个Add函数
然后int a[2] 初始化成{1, 2}
有个整型变量sum
然后sum赋值为Add
函数调用a[0]、a[1]
作为Add这个函数的实际参数
就像普通的变量一样
第二个 数组整体作为函数的形式参数
这个函数书写的基本格式
应该是这个样子
void GenerateIntegers( int a[], unsigned int n)
我们要传两个参数
第一个参数是这个数组的名称
第二个参数是这个数组的元素个数
元素个数类型是unsigned int
你写个int也行
这个关系不是特别大
特别需要注意的是
数组名称后的那个中括号内
不需要写元素的个数
必须使用单独的参数
来传递元素的个数信息
我们来看一个实际的例子
我们编写一个函数
随机生成n个位于lower和upper
这样一个区间里的整数
并保存到数组里
void GenerateIntegers
( int a[], unsigned int n, int lower, int upper)
四个参数
我们随机化 Randomize
随机化之后
用一个for循环来生成n个随机数
生成这个随机的整数之后
我就把它赋值给a[i]
一个for循环做完
这些整数就全部被我们生成了
当数组作为函数参数的时候
它有一个巨大的优势
就是能够把函数内部
对这个数组元素的修改带出去
调用这个函数
那个实际数组就会被你改变
也就是说
当这个数组作为函数参数的时候
它不仅仅是这个函数输入集的一部分
也是这个函数的输出集的一部分
它是能够把结果带回去的
它和普通的量
作为函数参数是不一样的
当数组作为函数参数的时候
你直接书写这个常数
它会有一些小问题
就是你如果写int a[8]
后面跟着int lower, int upper
那个元素个数n 那个参数我们不传了
那现在你这个函数里面怎么写呢
没传n值啊 那没辙
我们只能写for(i=0;i<8;i++)
你只能按照这个方式那么写
没别的方案
这意味着我们写的这个函数
GenerateIntegers只能处理
8个元素的一个整数数组
如果这个数组是9个元素呢
7个元素呢
这个函数不能用
那你说我这个程序
得写多少个这样的函数啊
元素个数为两个的得写一个
为三个的得写一个
四个的得写一个
五个、六个、七个……没完没了了
所以这样的函数你写出来
它是对的 但是实现上来讲
它是极其不科学的
你这么写 它只能处理8个元素啊
太特殊了 那你说我不写
我不管这个元素有多少个
我就写int a[]后面跟着int lower, int upper
n值我也不传了 肯定不成啊
那这个地方写啥啊
i小于几啊
for循环你怎么写啊
你生硬在里面写个8写个9
写个10 写个n
这都会出问题的呀
这个方案可以这么实现
但是它非常非常容易出错
因为你不知道它的元素个数
在C++的编译器里边
数组的下标是不是越界这个问题
编译器是不带你检查的
你访问这样一个数组
元素下标是不是越界了
这是你——程序员的责任
你必须确保元素下标没有越界
如果越界了
这个程序十之八九有可能崩溃
程序压根就运行不下去
那你直接写变量写int a[n]
成不成呢 不能这么写呀
C++里边它必须是个常数和常量
在C里面它必须是个常数
连常量都不成
所以我们想来想去
对我们一维数组
它最好的一个书写方式是什么呢
我写int a[] 面跟着unsigned int n
第二个参数作为一个
附加的参数把元素的个数
传进去算了
这样的话我写的程序就相当一般化了
调用它蛮简单的
我们使用一个单独的数组名字
作为函数的实际参数
这个时候我们传的
将是这个数组的基础地址
而不是数组的本身 在这种情况下面
我们这个函数的形式参数和实际参数
实际上对应着同一片存储区
也只有对应着同一片的存储区
在你的这个函数的内部
对这个形式参数所做的任何改变
才能立即反映到它的实际参数里边去
也就是说 它才能够把这个结果带出去
我定义一个宏#define NUMBER_OF_ELEMENTS 8
然后我定义一个const int lower_bound
初始化成10
然后const int upper_bound初始化成99
下界初始化成10 上界初始化成99
然后定义这个数组
int a[NUMBER_OF_ELEMENTS]
然后调用我们的函数GenerateIntegers
你看我传的参数
第一个参数传这个数组的名字a
第二个参数传NUMBER_OF_ELEMENTS
第三个和第四个
是lower_bound和upper_bound——上界和下界
这就完成了我们标准的函数调用
这个函数一做完
这个数组int a[NUMBER_OF_ELEMENTS]里边
就会保存它给我们随机生成的
8个10到99之间的整数
-1.1 提纲
-1.2 程序设计的基本概念
-1.3 简单C/C++程序介绍
-1.4 程序设计的基本流程
-1.5 基本语法元素
-1.6 程序设计风格
-1.7 编程实践
-第一讲 C/C++基本语法元素--编程实践提交入口
-2.1 提纲
-2.2 结构化程序设计基础
-2.3 布尔数据
-2.4 分支结构
-2.5 break语句
-2.6 循环结构
-2.7 编程实践
-第二讲 程序控制结构--编程实践提交入口
-3.1 提纲
-3.2 函数声明、调用与定义
-3.3 函数调用栈框架
-3.4 编程实践
-第三讲 函数--编程实践提交入口
-4.1 提纲
-4.2 算法概念与特征
-4.3 算法描述
-4.4 算法设计与实现
-4.5 递归算法(一)
-4.6 递归算法(二)
-4.7 容错与计算复杂度
-4.8 编程实践
-第四讲 算法--编程实践提交入口
-5.1 提纲
-5.2 库与接口
-5.3 随机数库(一)
-5.4 随机数库(二)
-5.5 作用域与生存期
-5.6 典型软件开发流程(一)
-5.7 典型软件开发流程(二)
-5.8 编程实践
-第五讲 程序组织与开发方法--编程实践提交入口
-6.1 提纲
-6.2 字符
-6.3 数组(一)
-6.4 数组(二)
-6.5 结构体
-6.6 编程实践
-第六讲 复合数据类型--编程实践提交入口
-7.1 提纲
-7.2 指针基本概念
-7.3 指针与函数
-7.4 指针与复合数据类型(一)
-7.5 指针与复合数据类型(二)
-7.6 字符串
-7.7 动态存储管理(一)
-7.8 动态存储管理(二)
-7.9 引用
-7.10 编程实践
-第七讲 指针与引用--编程实践提交入口
-8.1 提纲
-8.2 数据抽象(一)
-8.3 数据抽象(二)
-8.4 链表(一)
-8.5 链表(二)
-8.6 链表(三)
-8.7 链表(四)
-8.8 函数指针(一)
-8.9 函数指针(二)
-8.10 抽象链表(一)
-8.11 抽象链表(二)
-8.12 编程实践
-第八讲 链表与程序抽象--编程实践提交入口
-9.1 提纲
-9.2 程序抽象与面向对象
-9.3 类类型
-9.4 对象(一)
-9.5 对象(二)
-9.6 类与对象的成员(一)
-9.7 类与对象的成员(二)
-9.8 类与对象的成员(三)
-9.9 继承(一)
-9.10 继承(二)
-9.11 继承(三)
-9.12 多态(一)
-9.13 多态(二)
-9.14 编程实践
-第九讲 类与对象--编程实践提交入口
-10.1 提纲
-10.2 四则运算符重载(一)
-10.3 四则运算符重载(二)
-10.4 关系与下标操作符重载
-10.5 赋值操作符重载(一)
-10.6 赋值操作符重载(二)
-10.7 赋值操作符重载(三)
-10.8 赋值操作符重载(四)
-10.9 赋值操作符重载(五)
-10.10 流操作符重载(一)
-10.11 流操作符重载(二)
-10.12 流操作符重载(三)
-10.13 操作符重载总结
-10.14 编程实践
-第十讲 操作符重载--编程实践提交入口
-11.1 提纲
-11.2 泛型编程概览
-11.3 异常处理机制(一)
-11.4 异常处理机制(二)
-11.5 运行期型式信息(一)
-11.6 运行期型式信息(二)
-11.7 模板与型式参数化
-11.8 题外话:术语翻译
-11.9 泛型编程实践(一)
-11.10 泛型编程实践(二)
-11.11 泛型编程实践(三)
-11.12 泛型编程实践(四)
-11.13 泛型编程实践(五)
-11.14 泛型编程实践(六)
-11.15 泛型编程实践(七)
-11.16 泛型编程实践(八)
-11.17 泛型编程实践(九)
-11.18 泛型编程实践(十)
-11.19 编程实践
-第十一讲 泛型编程--编程实践提交入口
-12.1 提纲
-12.2 程序执行环境(一)
-12.3 程序执行环境(二)
-12.4 程序执行环境(三)
-12.5 程序执行环境(四)
-12.6 输入输出(一)
-12.7 输入输出(二)
-12.8 文件系统
-12.9 设备
-12.10 库(一)
-12.11 库(二)
-12.12 makefile文件(一)
-12.13 makefile文件(二)
-12.14 makefile文件(三)
-12.15 编程实践
-第十二讲 Linux系统编程基础--编程实践提交入口
-13.01 提纲
-13.02 进程基本概念
-13.03 信号
-13.04 进程管理(一)
-13.05 进程管理(二)
-13.06 进程管理(三)
-13.07 进程间通信(一)
-13.08 进程间通信(二)
-13.09 进程间通信(三)
-13.10 进程间通信(四)
-13.11 进程池
-13.12 编程实践
-第十三讲 进程编程--编程实践提交入口
-14.1 提纲
-14.2 线程基本概念
-14.3 线程管理(一)
-14.4 线程管理(二)
-14.5 线程管理(三)
-14.6 线程管理(四)
-14.7 线程同步机制(一)
-14.8 线程同步机制(二)
-14.9 C++11线程库(一)
-14.10 C++11线程库(二)
-14.11 C++11线程库(三)
-14.12 C++11线程库(四)
-14.13 C++11线程库(五)
-14.14 编程实践
-第十四讲 线程编程--编程实践提交入口
-15.1 提纲
-15.2 Internet网络协议
-15.3 套接字(一)
-15.4 套接字(二)
-15.5 编程实践
-第十五讲 网络编程--编程实践提交入口