c++ 吕凤翥 第六章 类和对象(二)
指针 引用 和数组
一:对象指针和对象引用
1.指向类的成员的指针
分为指向成员变量和指向成员函数两种指针
成员变量的格式: 类型说明符 类名:: * 指针名
成员函数的格式: 类型说明符 (类名::* 指针名)(参数表)
class A
{
public:
int fun(int b){return ...}
A(int i){a=i;}
int c;
private:
int a;
}
定义指向类A 的数据成员c的指针pc : int A::*PC=&A::c; 此时c为公有成员
定义指向类A的成员函数fun的指针pfun:int (A::*pfun)(int)=A::fun; 函数名为地址 所以未用取地址符号 fun也是公有成员函数 所以可以直接指向
先创建对象,然后通过对象来引用指针指向的成员
A a;
a.* pc=8;
使用指向对象的指针通过指向类成员的指针对该成员进行操作时,可以使用运算符->* 运算符前面为指向对象的指针,后面为指向类成员的指针
A * p=&a; //a 为A类的对象 p是指向对象a的指针
p->*pc=8; //pc为指向A类的成员c的指针
指向普通函数的指针的声明格式:(非类中的成员函数)
类型说明符 * 指针名 (参数表)
赋值格式: 指针名 =函数名 即指针变量的值为函数名即地址
调用格式: (* 指针名)(实参表)
如果是指向类的成员函数的指针 则应加上相应的对象名或指向对象的指针名以及对象成员运算符。
案例:
//吕 example 6.1
#include <iostream>
using namespace std;
class A
{
public:
A(int i){
a=i;
}
int fun(int b)
{
return a*c+b;
}
int c;
private:
int a;
};
int main()
{
A x(8); //构造
int A::*pc;// pc指向类A
pc=&A::c;//pc指针初始化 为公有成员c
x.*pc=3; //成员变量赋值
int(A::*pfun)(int);//指向成员函数的指针声明
pfun=A::fun;//初始化指向成员函数的指针
A*p=&x; // 声明 初始化指向对象的指针
cout<<(p->*pfun)(5)<<"\n";
}
上例中 指针的性质是不同的 p指向对象 pc和pfun是指向类中的成员
2.对象指针和对象引用作为函数参数
使用对象指针做函数参数更加普遍
特点:
1.传地址调用 可改变实参的值(被调函数中改变调用函数中的参数值),实现函数之间的信息传递
2.使用对象指针作为形参仅将对象的地址值传给形参,而不进行对象副本的复制(函数传值的方式 ) 提高运行效率,减少开销
传递的实参必须是对象的地址
案例2:
//吕6.2 example
#include <iostream>
using namespace std;
class M
{
public:
M() //default c
{
x=y=0;
}
M(int _x,int _y) //c
{
x=_x;
y=_y;
}
void copy(M * m); //member #1
void setxy(int _x,int _y) //member #2
{
x=_x;y=_y;
}
void print() //member #3
{
cout<<x<<","<<y<<"\n";
}
private:
int x,y;
};
void M::copy(M * m) //implementation m为指向M对象的指针
{
x=m->x;
y=m->y;
}
void fun(M m1,M * m2); //function prototype 非类中的函数
int main()
{
M p(5,7),q;//两个对象 一个非默认 一个默认
q.copy(&p);
fun(p,&q);//p的值不变 传递的是副本 q的值变化 传递的是对象的地址
p.print(); //5 7
q.print(); //22 25
return 0;
}
void fun(M m1,M * m2)//使用对象指针作为参数 传递
{
m1.setxy(12,15);
m2->setxy(22,25);
}
2.对象引用作为函数参数
使用对象引用做参数比指针更加的普遍 用引用更加方便 更加直接
example 6.3
与6.2的区别
void copy(M &m); //&取代了*
void fun(M m1,M & m2);
3.this 指针
this 是一个由系统自动提供的指向对象的特殊指针。 该镇值是一个指向 正在对某个成员函数操作的对象的指针。
当对一个对象调用成员函数时,编译器先将该对象的地址赋值给系统创建的this指针。然后再调用成员函数,每次成员函数存取数据成员时,都会隐含着使用this指针。同样适用*this 来标识 调用该成员函数的对象。
案例:6.4
//example 6.4 吕
#include <iostream>
using namespace std;
class A
{
public:
A(){
a=b=0;
}
A(int i,int j)
{
a=i;b=j;
}
void copy(A &aa);//其中的&为引用
void print()
{
cout<<a<<","<<b<<"\n";
}
private:
int a,b;
};
void A::copy(A &aa)
{
if(this==&aa) return; //其中的&为取地址运算符 成员函数中的this的值为aa对象的地址
* this=aa; //将aa对象中的成员值 赋值给this指向的对象 ,对象间的赋值
}
int main()
{
A a1,a2(3,4); //初始化两个对象
a1.copy(a2); //用a2来赋值a1
a1.print();
return 0;
}
6.4对象数组和对象指针数组
1.对象数组
数组中元素为对象的数组 需要都是同一个类的对象
类名 数组名 [大小]
DATE dates [10]; 一维数组
DATE dates [10][5]; 二维数组
2.对象数组的赋值
对象数组可以赋初值 也可以赋值
DATE(int m,int d,int y), //构造函数
DATE dates[3]={DATE(1,2,3),DATE(4,5,6),DATE(7,8,9)}; 初始化
dates[0]=DATE(7,22,1998); 对数组中的某个成员赋值
案例:
//example 6.5 吕
#include <iostream>
using namespace std;
class DATE
{
public:
DATE()
{
month=day=year=0;
cout<<"default called.\n";
}
DATE(int m,int d,int y)
{
month=m;
day=d;
year=y;
cout<<"constructor called\n";
}
~DATE() //析构函数
{
cout<<"destructor called.\n";
}
void print()
{
cout<<"month="<<month<<";day="<<day<<";year="<<year<<"\n";
}
private:
int month,day,year;
};
int main()
{
DATE dates[5]={DATE(7,22,1998),DATE(7,23,1998),DATE(20,11,2003)}; //头三个元素用非默认构造函数初始化;后两个用默认的构造函数初始化
dates[3]=DATE(7,25,1998); //非默认构造函数来更改数组元素的值
dates[4]=DATE(1,7,2003); //同上
for(int i(0);i<5;i++)
dates[i].print();
return 0;
}
上面的dates[3] 先用构造函数创建一个无名的对象,然后将它赋值给数组元素 ,然后再将无名对象通过调用析构函数来实现。
6.2指向数组的指针和指针数组
1.指向数组的指针
类型说明符 (* 指针名) [大小]
int (* P)[3]; // int型 3个元素组成的数组 p为指针 指向这个数组
类名 (* 指针名) [大小]
类名 (* PL)[4]; //PL为指针名 指向包含4个对象元素的数组
//example 6.6
#include <iostream>
using namespace std;
int a[][3]={1,2,3,4,5,6,7,8,9};//列表初始化
int main()
{
int (*pa)[3](a);//声明数组包含3个元素 每个元素为指向整型的指针类型 初始化pa为a a为二维数组的地址
for(int i=0;i<3;i++)
{
cout<<"\n";
for(int j=0;j<3;j++)
cout<<*(*(pa+i)+j)<<""; //*pa 第一次解引用 从二维数组降到一维数组 *(*pa) 从一维降到数组中的单个元素 所以输出的是元素的值
}
cout<<"\n";
return 0;
}
pa为指向数组a的第0行一维数组的指针
注意:指向一维数组的指针都用二维数组的某个行地址(即该行首地址)赋值。
6.7 指向对象数组的指针
//exmaple 6.7
#include <iostream>
using namespace std;
class M
{
public:
M(){
a=b=0;
}
M(int i,int j)
{
a=i;b=j;
}
void print()
{
cout<<a<<","<<b<<'\t';
}
private:
int a,b;
};
int main()
{
M m[2][4]; //二维数组 元素为M类对象 数组名为m
int x=10,y=10;
for(int i=0;i<2;i++)
for(int j=0;j<4;j++)
m[i][j]=M(x+=2,y+=10); //初始化 对象数组
M(*pm)[4](m);// //声明 指向数组的指针 指针指向的数组元素为M类的对象 二维数组名代表地址 赋值给pm[0]
//定义一个指针 指向对象数组的指针pm 用二维m的数组名来初始化 pm指向二维数组m的首行
for(int i=0;i<2;i++)
{
cout<<"\n";
for(int j=0;j<4;j++)
(*(*(pm+i)+j)).print(); //两次解引用 从二维变为一维 再从一维变为数组中的元素 M类的对象
}
cout<<"\n";
return 0;
}
2.指针数组
数组元素为指针的数组称为指针数组。一个数组的元素可以是指向同一个类型的一般指针,也可以是指向同一类类型的对象指针
类型名 * 数组名 [大小]
表示该数组名中的元素为指针
int * pa[3]; //包含3个元素 每个元素是一个int型的指针
char * pa[2][5]; //包含10个元素 每个元素是一个char型的指针
//example 6.8 吕 指针数组 元素为指针
#include <iostream>
#include <string.h> //此时需要加.h 没有h 编译器找不到函数
using namespace std;
const int N=5; //全局常量
int main()
{
char * strings[N]; //声明一个指针数组 元素为指向字符串的指针
char str[80]; //声明一个字符串数组
cout<<"At each prompt,enter a string:\n";
for(int i=0;i<N;i++)
{
cout<<"Enter a string #"<<i<<":";
cin.getline(str,sizeof(str)); //获取一行输入中 实际输入长度字符串的首地址 赋值给str
strings[i]=new char[strlen(str)+1]; //开辟一个长度比输入的字符串长度+1的数组 将首地址赋值给指针数组的对应元素(字符串指针)
strcpy(strings[i],str); //将输入进来的字符串赋值拷贝到新开辟的位置的数组,并且通过字符串指针来访问呢
}
cout<<endl;
for(int i=0;i<N;i++)
cout<<"string #"<<i<<":"<<strings[i]<<endl; //输出指针数组中各个元素指向的字符串
return 0;
}
所有数组元素都是指向同一个类类型的对象的指针
类名 * 数组名 [大小]
//example 6.9 吕
#include <iostream>
using namespace std;
class A
{
public:
A(int i=0,int j=0){
a=i;b=j;
}
void print();//声明带分号 实现不用分号
private:
int a,b;
};//类的声明 加上分号
void A::print()
{
cout<<"a="<<a<<";"<<"b="<<b<<"\n";
}
int main()
{
A a1(7,8),a2,a3(5,7); //调用不同的构造函数 初始化
A *b[3]={&a3,&a2,&a1}; //列表初始化指针 指针数组
for(int i=0;i<3;i++) //
b[i]->print();
}
6.2 带参数的main函数
void main(int argc,char * argv[])
其中 argc是代表命令行参数的个数 (包括命令本身也计数) argv 数组包含参数的实际内容 命令本身也包含在内
argv[0] 命令字
argv[1] 命令行第一个参数
argv[2] 命令行第二个参数
...
//example 6.10
#include <iostream>
using namespace std;
int main(int argc,char * argv[])
{
cout<<"the number of command line arguments is"<<argc<<endl;
cout<<"the program name is "<<argv[0]<<endl; //打印程序名
if(argc>1)
{
cout<<"the command line arguments:\n"; //
for(int i=0;i<argc;i++)
cout<<argv[i]<<endl; // 打印从1开始的参数内容
}
return 0;
}
6.3常类型
用const来说明 对象或者变量的值是不能被更新的 所以定义或说明常类型量时必须进行初始化
int const m=15;
m=18; //此语句错误 因为前面已经初始化了m值 不能再修改
6.3.1 一般常量和对象常量
int const x=2; //const 可以如此 也可以位于int之前
类型说明符 cosnt 数组名 [大小]=初始值
const 类型说明符 数组名 [大小]=初始值
int const a[5]={1,2,3,4,5};//数组元素的值是不能被更新的。
2.常对象
常对象是指对象常量,定义格式:
类名 const 对象名 (初始值)
初始化后对象不能被更新 同样const的位置可以变化
6.3.2 常指针和常引用
1.常指针
常指针使用修饰符const说明 一种表示指针的地址值是常量 另一种表示指针所指向的是常量 格式不同
类型 * const 指针名=初始值 表示指针是常量 即 指针的值 (实际的地址) 不可以被修改 ;但是指向(实际内存地址处)的值可以被修改。
char * const ps1=s1; ps1的值是不变的 始终为s1 *ps1 可以改变
ps1=s2 是非法的
* ps1=“char” 指针所指向的值是可以改变的 将char 字符串存入ps1的地址处
2.所指向的值是常量的常指针
这是一种指向某类型常量的指针 它定义的格式如下:
const 类型 * 指针名=初始值
const char * ps2=s2;
ps2 是一个常指针 指向的量是常量 不能改变 但是该指针的地址值是可以改变的
3.常引用
使用const修饰符可以说明引用 被说明的引用为常引用 该引用所引用的对象不能被更新。
const 类型说明符 & 引用名= 初始值
double b(1,2);
const double & v=b;
v=a; //此语句非法
c++中 常指针和常引用往往用来作为函数的形参,这样的参数称为常参数。使用const修饰的常指针和常引用更多
好处是参数传递过程中不必执行复制构造函数 会改善程序的运行效率
//6.11 example const pointer as argument
#include <iostream>
using namespace std;
const int N=5; //const修饰的常量
void print(const int * p,int n);//函数原型
int main(int argc,char * argv[])
{
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<<endl; //打印数组中的第0个元素
for(int i=1;i<n;i++)
cout<<","<<*(p+i)<<endl;//打印数组中其他的元素
cout<<"}"<<"\n";
}
//6.12 常引用作为函数的参数