一:数组
数组是具有一定关系若干对象的集合体,组成数组的对象称为该数组的元素。
1.数组的声明
- (1)确定数组的名称。
- (2)确定数组元素的类型。
- (3)确定数组的结构(包括数组的组维数,每一维的大小)。
一般形式:
数组类型 标识符[常量表达式1][常量表达式2]……;
注意:数组的元素都是从0开始的,假设一个数组的元素由n个,则最大下标为n-1。
int a[3][4];
2.数组的使用
int a[3] = {1, 2, 3};
for (int i = 0; i < 3; i++)
{
cout << a[i] << ends;
}
- 使用数组时,只能分别对数组各个元素进行操作,不能整个对数组进行赋值。
- 数组访问不能越界,否则会出现越界错误。
- 数组下标表达式必须是合法的表达式,其结果必须是整数。
3.数组的储存和初始化
(1)数组的储存:
数组元素在内存中是顺序、连续储存的。
- 一位数组可以看作是数学上的一个列向量。
- 二维数组可以看作是数学上的一个矩阵。
(2)数组的初始化
数组的初始化就是在声明数组时给部分或全部元素赋初值。
//一维数组的初始化
int a1[3] = {1, 2, 3}; //最基本的赋值
int a2[5] = {1, 2, 3}; //给部分元素赋值,对于静态生存期的数组,剩下的元素系统默认为0;动态生存期的数组,每个元素的初值不确定
int a3[] = {1, 2, 3, 4, 5, 6}; //可以不用说明数组的大小
//二维数组的初始化
int b1[2][2] = {1, 2, 3, 4};
int b2[2][3] = {{1,2,3}, {4,5,6}};
int b3[][3] = {1, 2, 3}; //第一维的下标个数可以省略不写
const float b4[5] = {1, 2, 3, 4, 5}; //对于声明为常量的数组,必须给定初值
4.数组作为函数参数
#include<iostream>
using namespace std;
void fun1(int a[]) {}//使用数组名传递数据,传递的时地址,一般不指定数组第一维的大小,如果指定,也会被编译器忽略
void fun2(int *a) {}
int main()
{
int a[3] = {1,2,3};
fun1(a); //数组名表示数组的首地址,将a的首地址传给fun1函数
fun1&a[0]);
fun1(&a[1]); //此时数组的大小发生变化,传进去的数组时从a数组的第二个元素开始的数组
//fun2函数同理
}
5.对象数组
#include<iostream>
using namespace std;
//学生类
class Student
{
int stuID;
public:
Student(int i = 0):stuID(i) {}
Student() {}
}
int main()
{
//主函数中创建对象数组 创建形式:对象名 数组名[N],数组中储存的是对象
Student stu1[3] = {Student(1), Student(2), Student(3)}; //第一种初始化方式,相当于对象的拷贝
Student stu2[3] = {{1}, {2}, {3}}; //第二种初始化方式,直接分别给出初值
}
二:指针
1.指针变量的声明
指针变量是用于存放内存单元地址的。
int *p;
char *c;
2. & 和 *
- 在C++中&既表示引用,又表示对一个变量进行取址操作,注意区分使用场合。
- * 称为指针操作符,也称解析符,表示获取指针所指向对象的值。
3.指针的赋值
int a[3] = {1, 2, 3};
int *pa = a; //数组名代表数组的首地址,是一个不能被赋值的指针,即指针常量
int b = 3, *p;
p = &b; //&表示取址符,p指向b
- 可以声明指向常量的指针,此时不能修改指针所指向常量的值,但指针本身可以改变,可以指向别的对象。
int a;
const int *p = &a;
int b;
*p = b; //编译器报错,因为常量指针所指向的值不可以改变
p = &b; //正确,常量指针可指向别的对象
- 可以声明指针类型的常量,此时指针本身的值不可以改变。
int *const p = &a;
p = &b; //编译器会报错,指针常量的值不可以改变
- void指针可以储存任何类型的指针。经过使用类型显示转换,void指针可以访问任何类型的数据。
4.指针的运算
指针是一种数据类型,和其他数据类型一样,可以参与部分运算,包括算数运算、关系运算和赋值运算。
- 设有指针p1和整数n1,p1+n1表示指针p1所指位置后方第n1个数的地址,p1-n1表示指针p1所指位置前方第n1个数的地址。
- *(p1+n1) 表示p1当前位置后方第n1个数的内容,也可以写作p1[n1],*(p1-n1)表示p1当前位置前方第n1个数的内容,也可以写作p1[-n1]。
- 指针的逻辑运算指的是相同类型的指针进行的关系运算,不同类型的指针或指针与非0整数之间的运算是毫无意义的。
- 指针可以和零进行逻辑运算,0专门用于表示空指针,即不指向任何有效地址的指针。空指针也可用NULL表示,在编译器的头文件中有定义NULL的宏,即#define NULL 0
5.用指针处理数组元素
数组为一段连续的空间,恰好契合了指针的特点,因此可以用指针来方便的处理数组。
- 用指针处理一维数组
int a[6] = {1, 2, 3, 4, 5, 6};
int *pa = a;
for (int i = 0; i < 3; i++)
{
cout << a[i] << ends; //直接用数组名来访问数组
}
for (int i = 0; i < 3; i++)
{
cout << *(pa+i) << ends; //*(pa+i)表示数组下标为i的元素
}
for (int i = 0; i < 3; i++)
{
cout << pa[i] << ends; //pa[i]与*(pa+i)等价
}
- 用指针处理二维数组
int b[2][3] = {{1,2,3}, {4,5,6}};
int *pb = b; //二维数组名表示二维数组一维下标为0所在行的地址
int i = 1, j = 2;
//b数组的数组名表示b[0],即指向二维数组第一行的地址,地址与第一行第一个元素相同,但是类型不同,类型为int (*p)[3],这里可以理解为从行的角度看待数列
//*b表示b[0]首元素的地址,即b[0][0]的首地址,相当于转化成了一位数组,这里可以理解为具体的从列的角度看待数列
cout << *(*(pb + i) + j) << ends; //等价于b[i][j]
cout << *(pb[i] + j) << ends; //等价于b[i][j]
6.用指针作为函数参数
当需要在不同的函数之间传输大量数据时,程序执行时调用函数的开销就会很大。这时如果需要传递的数据是一段连续的内存区域,就可以只传递数据的起始地址,而不必传数据的所有值,这样就会减少开销,提高效率。
用指针作为函数参数有三个作用:
- 使实参参与形参指针指向共同的内存空间。
- 减少函数调用时数据传递的开销。
- 通过指向函数的指针传递函数代码的首地址。
6.指针型函数
若一个函数的返回值为指针类型,则这个函数就是指针型函数。
int* func(int a[], int length)
{
……
return a; //这时返回数组的首地址,而不是某个变量或者对象,相当于传递了大量数据
}
7.指向函数的指针
未完待续……