第六章 数组,指针与字符串(一)
数组的定义与初始化
数组的定义与使用
如:int a[10];
int a[5][3];
二维数组
a[0] = a[5] + a[7];
数组的存储与初始化
元素间物理地址上的相邻,对应着逻辑次序上的相邻。
数组名是数组首元素的内存地址。是常量;
一维数组的初始化:
列出全部元素的初始值
`static int a[10] = {0,1, 2, 3, 4, 5, 6, 7, 8, 9};`
只给部分元素指定初始值
`static int a[10] = {0, 1, 2, 3, 4};`
还可以不指定长度
`static int a[] = {0, 1, 2, 3,4, 5}`
二维数组的存储
按行列存储
-
初始化
将所有初始值写在一个{}内,按顺序初始化; 分行列出二维数组元素的初值; `static int a[3][4] = {{1, 3, 4, 5}, {1,2}, {2213,43,3}}`
第一维下标个数可省略
二维数组的初始化
如果不做初始化,局部作用域的非静态数组中会存在垃圾数据,static数组中的数据默认初始化为0;
如果只对部分元素初始化,剩下的未显示初始化的元素,将自动被初始化为零;
数组作为函数的参数
数组元素做不了形参;
数组名做参数,形,实参数都应该是数组名,类型要一样,传送的是数组首地址。对形参数组的改变会直接影响到实参数组。
将数组做函数参数时,在函数体内对形参数组数据的修改将会直接影响实参数组
对象数组
只要对象都属于同一种类型就可以了
数组元素的构造和析构
构造数组时,元素所属的类未声明构造函数就用默认构造函数;
各元素对象的初值要求为相同的值时,可以声明具有默认形参值的构造函数。
各元素对象的初值要求为不同值时,需要声明带形参的构造函数。
当数组中每一个对象被删除时,系统要调用一次析构函数。
基于范围的for循环
int main()
{
int array[3] = {1, 3, 4};
for(int& e:array)
{
e+=2;
cout << e << endl;
}
return 0;
}
指针的概念、定义和指针运算
内存空间的访问方式:
通过变量名访问;
通过地址访问;
指针的概念
指针:内存地址,用于简介访问内存单元
指针变量:用于存放地址的变量
指针运算过程就是寻址过程;
指针变量的初始化和赋值
如int* pa = &a;
可以用一个已有合法值的指针去初始化另一个指针变量。
不要用一个内部非静态变量去初始化static指针。
指针变量的赋值运算
指针名=地址;
地址如:
通过地址运算“&”求得已定义的变量和对象的起始地址
动态内存分配成功时返回的地址;
整数0可以赋值给指针,表示空指针 。
允许定义或声明指向void类型的指针。该指针可以被赋予任何类型对象的地址;
但是,只能存放地址而不能用它访问它所指向的对象(内存空间)
int *pint = static_cast<int *>(pv); //void指针转换为int指针
指针空值nullptr
c++11中,使用nullptr关键字,使得表达更准确,指针运算更安全;
指向常量的指针
const指针;
int a;
const int* p = &a; //p是指向常量的指针
int b;
p = &b; //正确,p本身的值可以改变
*p = 1; //编译时出错,不能通过p改变所指的对象;
也可以将指针本身定义成常量;
int a; int* const p = &a;
指针的算术运算、关系运算
指针类型的算术运算
指针p加上或减去n
意义是指针当前指向位置的前方或后方第n个数据的起始位置。
比如一个int对象占4个字节,它就应当指向首字节;
++, --运算也同样是完整数据的移动;
当指针指向连续存储的同类型数据时,指针与整数的加减运算和自增自减才有意义;
short a[4];
short *pa = a;
// *pa等同于a[0]
// *(pa+n)等同于a[n],
// 一次进两个字节
指针类型的关系运算
指向同类型数据的指针之间可以进行各种关系运算。
指向不同数据类型的指针,以及指针与一般整数变量之间的关系预算是无意义的;
指针可以和零之间进行等于或不等于的关系运算:
例如:p==0或p!=0;
指针与数组
指针访问数组元素
pa=&a[0]; 或 pa=a;
pa就是a[0],(pa+1)就是a[1],… ,*(pa+i)就是a[i].
a[i], *(pa+i), *(a+i), pa[i]都是等效的。
不能写a++,因为a是数组首地址、是常量。
指针数组:数组元素是指针类型
利用指针数组存放矩阵:
#include
using namespace std;
int main() {
int line1[] = { 1, 0, 0 }; //矩阵的第一行
int line2[] = { 0, 1, 0 }; //矩阵的第二行
int line3[] = { 0, 0, 1 }; //矩阵的第三行
//定义整型指针数组并初始化
int *pLine[3] = { line1, line2, line3 };
cout << "Matrix test:" << endl;
//输出矩阵
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++)
cout << pLine[i][j] << " ";
cout << endl;
}
return 0;
}
输出结果为:
Matrix test:
1,0,0
0,1,0
0,0,1
指针与函数
传指针也是引用传递的一种;
为什么要用?
需要数据双向传递时(引用也可以达到此效果)
需要传递一组数据,只传首地址运行效率比较高;
例:
#include <iostream>
using namespace std;
void splitFloat(float x, int *intPart, float *fracPart) {
*intPart = static_cast<int>(x); //取x的整数部分
*fracPart = x - *intPart; //取x的小数部分
}
int main() {
cout << "Enter 3 float point numbers:" << endl;
for(int i = 0; i < 3; i++) {
float x, f;
int n;
cin >> x;
splitFloat(x, &n, &f); //变量地址作为实参
cout << "Integer Part = " << n << " Fraction Part = " << f << endl;
}
return 0;
}
指向常量的指针做形参
#include <iostream>using namespace std;
const int N = 6;
void print(const int *p, int n);
int main() {
int array[N];
for (int i = 0; i < N; i++)
cin>>array[i];
print(array, N);
return 0;
}
void print(const int *p, int n) {
cout << "{ " << *p;
for (int i = 1; i < n; i++)
cout << ", " << *(p+i);
cout << " }" << endl;
}
指针类型的函数
返回值是指针;
存储类型 数据类型 *函数名()
{ //函数体语句
}
注意事项:
不要将非静态局部地址用作函数的返回值。
错误的例子:
在子函数中定义局部变量后将其地址返回给主函数,就是非法地址。
正确的例子:
主函数中定义的数组,在子函数中对该数组元素进行某种操作后,
返回其中一个元素的地址,这就是合法有效的地址。
在子函数中通过动态内存分配new操作取得的内存地址返回给主函数
是合法有效的,但是内存分配和释放不在同一级别,要注意不能忘记
释放,避免内存泄漏。
指向函数的指针
为什么需要?
通过函数指针调用的函数
例如将函数的指针作为参数传递给一个函数,使得在处理相似事件的时候可以灵活的使用不同的方法。
调用者不关心谁是被调用者
需知道存在一个具有特定原型和限制条件的被调用函数。