c++ 吕凤翥 第六章 类和对象(二)

时间:2023-03-08 16:07:40

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  常引用作为函数的参数