数组的定义与使用
类型说明符 数组名[常量表达式][常量表达式]…
int a[5][3]
从0开始数的
#include <iostream>
using namespace std;
int main(){
int a[10],b[10];
for(int i = 0;i<10;i++){
a[i]=i*2-1;
b[10-i-1]=a[i];
}
for(int i = 0;i < 10;i++){
cout<<"a["<<i<<"] = "<<a[i]<<" ";
cout<<"b["<<I<<"] = "<<b[i]<<endl;
}
return 0;
}
数组的储存与初始化
一维数据的存储
数组名字是数组的首元素的内存地址
数组名是一个常量,不能被赋值
一维数组初始化
二维数据的存储
按行存放
a00 a01 a02 a03 a10…
二维数据的初始化
例:求Fibonacci数列的前20项,将结果存放于数组中
#include <iostream>
using namespace std;
int main(){
int f[20] = {1,1}; //初始化第0、1个数
for (int i = 2;i < 20;i++)//求第2~19个数
f[i] = f[i-2]+f[i-1];
for (i=0;i<20;i++){ //输出,每行5个数
if(i%5==0)cout<<endl;
cout.width(12); //设置输出宽度为12
cout<<f[i];
}
return 0;
}
一维数组应用举例
统计用户答题的结果,给出每一组答案的正确率
#include <iostream>
using namespace std;
int main(){
const char key[] = {'a','c','b','a','d'};
const int NUM_QUES = 5;
char c;
int ques = 0,numCorrect = 0;
cout<<"Enter the "<<NUM_QUES<<"question tests:"<<endl;
while(cin.get(c)){
if(c != '\n'){
if(c == key[ques]){
numCorrect++;cout<<" ";
}else
cout<<"*";
ques++;
}else{
cout<<" Score " <<static_cast<float>(numCorrect)/NUM-QUES*100<<"%";
ques = 0; numCorrect = 0;cout<<endl;
}
}
return 0;
}
数组作为函数的参数
数组元素作实参,与单个变量一样。
数组名作参数,形、实参数都应该是数组名,类型要一样,传送的是数组首地址。对形参数组的改变会直接影响到实参数组。
例6-2 使用数组名作为函数参数
主函数中初始化一个二维数组,表示一个矩阵,矩阵,并将每个元素输出,然后调用子函数,分别计算每一行的元素之和,将和直接存放在每行的第一个元素中,返回主函数之后输出各行元素的和。
//6_2.cpp
#include <iostream>
using namespace std;
void rowSum(int a[][4], int nRow){
for (int i = 0;i < nRow; i++){
for (int j = 1;j < 4; j++)
a[i][0] += a[i][j];
}
}
int main(){ //主函数
int table[3][4] = {{1, 2, 3, 4},{2, 3, 4, 5},{3, 4, 5, 6}};
for (int i = 0; i < 3; i++){ //输出数组元素
for (int j = 0; j < 4; j=++)
cout << table[i][j]<<" ";
cout << endl;
}
rowSum(table,3)
for (int i = 0; i < 3; i++)//输出计算结果
cout << "Sum of row" << i <<" is "<< table[i][0] << endl;
return 0;
}
对象数组
对象:类名 数组名[元素个数]
访问: 数组名[下标].成员名
对象数组初始化:Point a[2] = {Point(1,2),Point(3,4)};
例6-3 对象数组应用举例
//6_3.cpp
#include "Point.h"
#include <iostream>
using namespace std;
int main(){
cout<<"Entering main ..."<<endl;
Point a[2];
for(int i = 0; i < 2;i++)
a[i].move(i + 10,i + 20);
cout<<"Exiting main ..."<<endl;
return 0;
}
基于范围的for循环
指针的概念、定义与指针运算
内存空间的访问方式:通过变量名访问/通过地址访问
不仅要用*说明它是指针,还要说明它指向的是什么类型的变量
指针运算:寻指的过程
与地址相关的运算——”*“和”&”
指针运算符:*
地址运算符:&
互为逆运算
指针的初始化和赋值
指针变量的初始化
语法形式
储存类型 数据类型 *指针名 = 初始地址;
例: int *pa = &a;
注意事项
用变量地址作为初值时,该变量必须在指针初始化之前已声明过,且变量类型应与指针类型一致。
可以用一个已有合法值的指针去初始化另一个指针变量。
指针变量的赋值运算
语法形式 指针名=地址
注意:“地址”中存放的数据类型与指针类型必须相符,向指针变量赋的值必须是地址常量或变量,不能是普通整数。
例如:通过
地址运算“&”求得
已定义的变量和对象的起始地址、
动态内存分配成功时返回的地址。
整数0可以赋给指针,表示空指针
允许定义或声明指向void类型的指针。该指针可以被赋予任何类型对象的地址
指针空值nullptr
C++11使用nullptr关键字,是表达更准确,类型安全的空指针
例6-5 指针的定义、赋值与使用
//6_5.cpp
#include <iostream>
using namespace std;
int main(){
int i; //定义int类型数i
int *ptr = &i;//取i的地址赋给ptr
i = 10; //int型数赋初值
cout<<"i="<<i<<endl;//输出int型数的值
cout<<"*ptr = "<<*ptr<<endl;//输出int型指针所指地址的内容
return 0;
}
例6-6 void类型指针的使用
#include <iostream>
using namespace std;
int main(){
//!void voidObject;错,不能声明void类型的变量
void *pv;//对,可以声明void类型的指针,只是指向的对象不确定。目前只存放了地址,不知道访问什么。
int i = 5;
pv = &i; //void类型指针指向整型变量
int *pint = static_cast<int *>(pv);//void指针转换为int指针
cout<<"*pint = "<<*pint<<endl;
return 0;
}
指向常量的指针
const指针
不能通过指向常量的指针改变所指对象的值,但指针本身可以改变,可以指向另外的对象。
int a;
const int *p1 = &a;//p1是指向常量的指针
int b;
p1 = &b;//正确,p1本身的值可以改变
*p1 = 1;//编译错误,不能通过p1改变所指的对象
将指针本身定义成常量,则指针本身的值不能被改变(但是并不是只读的指针可以访问对象)
int a;
int * const p2 = &a;
p2 = &b; //错误,p2是指针常量,值不能改变
指针的算式运算、关系运算
指针类型的运算
指针与整数的加减运算
指针++,–运算
- 意义是指向下一个或前一个完整数据的起始。
指针类型的算术运算
指针p加上或减去n
- 其意义是指针当前指向位置的前方或后方第n个数据的起始位置。
指针类型的关系运算
- 指向相同类型数据的指针之间可以进行各种关系运算
- 指向不同数据类型的指针,以及指针与一般整数变量之间的关系运算是无意义的。
- 例外:指针可以和零之间进行等于或不等于的关系运算,例如p==0或p!=0
综合实例单独写
用指针访问数组元素
定义指向数组元素的指针
定义与赋值
int a[10], *pa;
pa=&a[0];或pa=a;
等效形式
*pa就是a[0]
*(pa+1)就是a[1]
a[i],
*(pa+i),
*(a+i),
pa[i]都是等效的
指针数组
数组的元素是指针类型
Point *pa[2];
//由pa[0]\pa[1]两个指针组成
以指针作为函数参数
为什么需要用指针做参数?
需要数据双向传递时(引用也可以达到此效果)
需要传递一组数据,只传首地址运行效率比较高
例6-10
读入三个浮点数
将整数部分和小数部分分别输出。
//6_10.cpp
#include <iostream>
using namespace std;
//将实数x分成整数部分和小数部分,形参intpart、fracpart是指针
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 <isotream>
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;
}
指针类型的函数
若函数的返回值是指针,该函数就是指针类型的函数。
指针函数的定义形式:
存储类型 数据类型 *函数名()
{//函数体语句
}
不要将非静态局部地址(离开函数就失效了)用作函数的返回值。
错误的例子
int main(){
int* functino();
int* ptr = function();//离开function,laocal被释放,原来的地址就是无效地址了
*ptr = 5;//危险的访问!现在拿着无效地址写信息
}
int* function(){
int local=0;//非静态局部变量作用域和寿命都仅限于本函数体内
return &local;
}//函数运行结束时,变量local被释放
正确的例子:主函数中定义数组
#include <iostream>
using namespace std;
int main(){
int array[10];//主函数中定义的数组
int* search(int* a,int num);
for(int i=0li<10;i++)
cin>>array[i];
int* zeroptr = rearch(array,10);//将主函数中数组的首地址传给子函数
return 0;
}
int* search(itn* a,int num){ //指针a指向主函数中定义的数组
for(int i=0;i<num;i++)
if(a[i]==0)
return &a[i];//返回的地址指向的元素是在主函数中定义的
}//函数运行结束,a[i]的地址仍有效
正确的例子2
在子函数中通过动态内存分配new操作取得的内存地址返回给主函数是合法有效的,但是内存分配和释放不在同一级别,要注意不能忘记释放,避免内存泄漏。
#include <iostream>
using namespace std;
int main(){
int* newintvar();
int* intptr = newintvar();
*intptr=5;//访问的是合法有效的地址
delete intptr;//如果忘记在这里释放,会造成内存泄漏
return 0;
}
int* newintvar(){
int* p=new int9);
return p;//返回的地址指向的是动态分配的空间
}//函数运行结束时,p中的地址仍有效
指向函数的指针
容纳的是函数代码的起始地址
存储类型 数据类型 (*函数指针名)();
#include <iostream>
using namespace std;
int compute(int a,int b,int(*func)(int,int))
{return func(a,b);}
int max(int a,int b)//求最大值
{return ((a>b)?a:b);}
int min(int a,int b)//求最小值
{return ((a<b)?a:b);}
int sum(int a,int b)//求和
{return a + b;}
int main()
{
int a,b,res;
cout<<"请输入整数a: "; cin>>a;
cout<<"请输入整数b: "; cin>>b;
res = compute(a,b, &max);
cout<<"Max of"<<a<<"and"<<b<<"is"<<res<<endl;
res = compute(a,b, &min);
cout<<"Max of"<<a<<"and"<<b<<"is"<<res<<endl;
res = compute(a,b, &sum);
cout<<"Max of"<<a<<"and"<<b<<"is"<<res<<endl;
}
对象指针
对象指针定义形式
类名 *对象指针名;
Point a(5,10);
Point *ptr;
ptr=&a;
通过指针访问对象成员
对象指针名->成员名
例:ptr-getx()相当于(*ptr).getx();
例6-12 使用指针来访问Point类的成员
//6_12.cpp
#include <iostream>
using namespace std;
class Point { //类的定义
public: //外部接口
Point(int x = 0, int y = 0) : x(x), y(y) { } //构造函数
int getX() const { return x; } //返回x
int getY() const { return y; } //返回y
private: //私有数据
int x, y;
};
int main() { //主函数
Point a(4, 5); //定义并初始化对象a
Point *p1 = &a; //定义对象指针,用a的地址将其初始化
cout << p1->getX() << endl; //利用指针访问对象成员
cout << a.getX() << endl; //利用对象名访问对象成员
return 0;
}
this指针
隐含于类的每一个非静态成员函数中
指出成员函数所操作的对象
![]()
曾经出现过的错误例子
class Fred;//前向引用声明
class Barney{
Fred *x; //错误:类Fred的声明尚不完善.修正变成指向Fred的指针
};
class Fred{
Barney y;
};
动态分配与释放内存
不知道数组开多大合适
要程序运行起来才知道需要多大数组
动态申请内存操作符new
new 类型名T(初始化参数列表)
释放内存操作符delete
delete 指针p
例6-16 动态创建对象举例
#include <isdtream>
using namespace std;
class PointP{
public:
Point():x(0)y(0){
cout<<"Default Constructor called"<<endl;
}
Point(int x,int y):x(x),y(y){
cout<<"Constructor called."<<endl;
}
~Point(){ cout<<"Destructor called."<<endl;}
int getX() const{ return x; }
int grtY() const{ return y; }
void move(int newX,int newY){
x=newX;
y=newY;
}
private:
int x,y;
};
int main()[
cout<<"Step one: "<<endl;
Point *ptr1 = new Point;//调用默认构造函数
delete ptr1;//删除对象,自动调用析构函数
cout<<"Step two:"<<endl;
ptr1 = new Point(1,2);
delete ptr1;
return 0;
}
申请和释放动态数组
分配:new 类型名T [数组长度]
数组长度可以说任何整数类型表达式
释放:delete[] 数组名p
释放指针p所指向的数组,必须要有方括号才释放整个数组
p必须是用new分配得到的数组首地址
例6-17 动态创建对象数组举例
#include <iostream>
using namespace std;
class Point{//类的声明同6-16,略};
int main(){
Point*ptr = new Point[2];//创建对象数组
ptr[0].move(5,10);//通过指针访问数组元素的成员
ptr[1].move(15,20);//通过指针访问数组元素的成员
cout<<"Deleting..."<<endl;
delete[] ptr;//删除整个对象数组
return 0;
}
动态创建多维数组
new 类型名T[第一位长度][第二维长度]…;
char (*fp)[3];
fp = new char[2][3]
将动态数组封装成类
更加简洁,便于管理
可以在访问数组元素前检查下标是否越界
#include <iostream>
#include <cassert>
using namespace std;
class Point{//声明同例6-16...};
class ArrayOfPoints{
public:
ArrayOfPoints(int size):size(size){
points = new Points[size];
}
~ArrayOfPoints(){
cout<<"Deleting..."<<endl;
delete[] points;
}
Point& element(int index){//取数组元素的函数,返回的是引用类型
assert(index >= 0 && index < size);
return points[index];
}
private:
Point *points;//指向动态数组首地址
int size; //数组大小
};
int main(){
int count;
cout<<"Please enter the count of points:"
cin>>count;
ArrayOfPoints points(count);//创建数组对象
points.element(0).move(5,0);//访问数组元素的成员
points.element(1).move(15,20);//访问数组元素的成员,通过引用可修改
return 0;
}
智能指针
vector对象
vector对象的定义
vector<元素类型> 数组对象名(数组长度);
vector<int> arr(5)
//建立大小为5的int数组
int main(){
unsigend n;
cout<<"n = ";
cin>>n;
vector<double> arr(n); //创建数组对象
cout<<"Please input"<<n<<"real numbers:"<<endl;
for (unsigned i=0;i<n;i++)
cin>>arr[i];
cout<<"Average = "<<average(arr)<<endl;
}
深层复制与浅层复制
移动构造
C风格字符串
string类