美文网首页
类和对象进阶

类和对象进阶

作者: 薛落花随泪绽放 | 来源:发表于2022-09-13 08:50 被阅读0次

一、构造函数

构造函数的作用

构造函数是类中的特殊成员函数,它属于类的一部分。
给出类定义时,由程序员编写构造函数。
构造函数的作用时完成对象的初始化工作,在对象生成时,系统自动调用构造函数,用户在程序中不会直接调用构造函数。

构造函数的定义

定义一个类时,需要为类定义相应的构造函数。构造函数的函数名与类名相同,没有返回值。
一个类的构造函数可以有多个,即构造函数允许重载。

类名(形参1,形参2,...,形参n);

在声明类的构造函数时可以同时给出函数体,这样的构造函数称为内联函数。

假设类的成员变量是x1,x2,...,xn,则在类体外定义构造函数时有如下形式

形式一:
类名::类名(形参1,形参2,...,形参n):x1(形参1),x2(形参2),...,xn(形参n){}
形式二:
类名::类名(形参1,形参2,...,形参n)
{
  x1 = 形参1;
  x2 = 形参2;
  ...
  xn = 形参n;
}
形式三:
类名::类名()
{
  x1 = 初始化表达式1;
  x2 = 初始化表达式2;
  ...
  xn = 初始化表达式n;
}

构造函数参数的排列顺序可以是任意的,只要保证能与类的成员变量相对应即可。
还可以使用默认参数为成员变量赋初值,构造函数中实参的个数可以少于类的成员变量的个数。
例如,程序2-1中为类myDate定义了两个构造函数,

myDate::myDate()
{
    year = 1997,month = 2,day = 12;
 } 
myDate::myDate(int y,int m,int d)
{
    year = y;month = m;day = d;
}

改写,使用初始化列表的形式为类myDate的对象进行初始化。例3-1

myDate::myDate():year(1997),month(02),day(12){}
或是:
myDate::myDate(int y,int m,int d):year(y),month(m),day(d){}

构造函数也可以在类体外定义。假定类myDate中已经声明了下列4个构造函数:

myDate();    //不带参数
myDate(int);    //带1个参数
myDate(int,int);  //带2个参数
mydate(int,int,int);  //带3个参数

则类体外,构造函数的定义如例3-2

myDate::myDate():year(1997),month(02),day(12){}
myDate::myDate(int d):year(1997),month(02)
{
    day = d;
}
myDate::myDate(int m,int d):year(1997)
{
    month = m;
    day = d;
}

myDate::myDate(int y,int m,int d)
{
    year = y;
    month = m;
    day = d;
}

例3-3 错误的构造函数定义

myDate::myDate(int d):year(1997),month(02)
{
    day = d;
}
myDate::myDate(int d):year(1997),day(1)
{
    month = d;
}

两个函数的参数列表是相同的。

构造函数的使用

例3-6 使用构造函数创建类的对象

假设已有例3-2中定义的构造函数,则创建对象时,可以使用下列形式:
myDate d();
myDate d1(25);
myDate d2(10,25);
myDate d3(1997,12,12);

例3-7 使用构造函数的默认参数创建对象

假设myDate类中仅定义了下列构造函数:
myDate::myDate(int y = 1997,int m = 2,int d = 12)
{
    year = y;
    month = m;
    day = d;
}
则创建对象时,可以使用
myDate d0();
myDate d1(1980);
myDate d2(1990,3);
myDate d3(2000,3,12);
输出这4个对象的值,
1997/2/12
1980/2/12
1990/3/12
2000/3/12

例3-9 使用构造函数创建对象指针

假设已有例3-2中定义的构造函数,则创建对象时,可以使用下列形式:
myDate *pd = new myDate();      pd->printDate();
myDate *pd1 = new myDate(55);   pd1->printDate();
myDate *pd2 = new myDate(66,77);pd2->printDate();
myDate *pd3 = new myDate(12,30,50);pd3->printDate();
使用new创建对象时,还可以写成:
myDate *pd = new myDate;  //未加括号

程序3-1 对象数组使用默认构造函数进行初始化
修改程序2-2中定义的类Student,为类添加两个构造函数

class Student
{
    public:
        void setStudent(string,myDate); //设置学生信息
        void setName(string);           //设置学生姓名
        string getName();               //获取姓名
        void setBirthday(myDate);       //设置生日
        myDate getBirthday();           //获取生日
        void printStudent() const;      //打印信息
    private:
        string name;        //姓名
        myDate birthday;        //生日 
};
//在类体外定义成员函数
void Student::setStudent(string s,myDate d)
{
    name = s;
    birthday.setDate(d);
    return;
}
void Student::setName(string n)
{
    name = n;
    return;
}
string Student::getName()
{
    return name; 
}
void Student::setBirthday(myDate d)
{
    birthday.setDate(d);
    return;
}
myDate Student::getBirthday()
{
    return birthday;
}
void Student::printStudent() const
{
    cout<<"姓名:"<<name<<"\t生日:";
    birthday.printDate();   //调用类myDate的成员函数 
    cout<<endl; 
 } 

在主函数main()中,可以使用无参数的构造函数生存Student对象stud,还可以声明Student对象的数组ss,ss[0]和ss[1]也是使用无参构造函数生成的。

 int main() {
    Student stud;
    Student ss[2];  //调用两次默认构造函数
    int y,m,d,i;
    string name_;
    stud.printStudent(); 
    for(i = 0;i<2;i++)
        ss[i].printStudent();   //输出默认值
    for(i = 0;i<2;i++)
    {
        cout<<"请输入学的姓名和生日,生日以\”年 月 日\"的次序输入:";
        cin>>name_>>y>>m>>d;
        ss[i].setStudent(name_,myDate(y,m,d)); 
    }
    for(i = 0;i<2;i++)
        ss[i].printStudent();
    return 0;
}

数组中不仅能保存对象,还可以保存指向对象的指针,从而构成对象指针数组,
例3-11 使用构造函数生成对象指针数组

int main() {
    Student stud;
    stud.printStudent();    //输出默认值
    stud.setName("111");    //姓名改为“111”
    stud.printStudent();
    Student *spointer[2] = {new Student(),&stud};
    Student sy[2] = {Student(),stud};
    for(int i = 0;i<2;i++)
        spointer[i]->printStudent();
    for(int i = 0;i<2;i++)
        sy[i].printStudent();
    return 0;
} 

例3-12

int main() {
    Student stud;
    stud.printStudent();    //输出默认值
    stud.setName("111");    //姓名改为“111”
    stud.printStudent();
    Student *spointer[2] = {new Student(),&stud};
    Student sy[2] = {Student(),stud};
    for(int i = 0;i<2;i++)
        spointer[i]->printStudent();
    for(int i = 0;i<2;i++)
        sy[i].printStudent();
    stud.setName("222");
    for(int i = 0;i<2;i++)
        spointer[i]->printStudent();
        
    spointer[0]->setName("333");
    spointer[1]->setName("444");
    for(int i = 0;i<2;i++)
        spointer[i]->printStudent();
        
    stud.printStudent();
    for(int i = 0;i<2;i++)
        sy[i].printStudent();
    return 0;
}

姓名:Noname    生日:1997/2/12
姓名:111       生日:1997/2/12
姓名:Noname    生日:1997/2/12
姓名:111       生日:1997/2/12
姓名:Noname    生日:1997/2/12
姓名:111       生日:1997/2/12
姓名:Noname    生日:1997/2/12
姓名:222       生日:1997/2/12
姓名:333       生日:1997/2/12
姓名:444       生日:1997/2/12
姓名:444       生日:1997/2/12
姓名:Noname    生日:1997/2/12
姓名:111       生日:1997/2/12

复制构造函数与类型转换构造函数

复制构造函数

复制构造函数是构造函数的一种,也称为拷贝构造函数。他的作用是使用一个已存在的对象去初始化另一个正在创建的对象。
复制构造函数只有一个参数,参数类型是本类的引用。
对于类A而言,复制构造函数的原型如下

A::A(const A &)
或
A::A(A &)

声明和实现复制构造函数的格式

class 类名
{
  public:
    类名(形参表);    //构造函数
    类名(类名 & 对象名);  //复制构造函数
...
};
类名::类名(类名 & 对象名)  //复制构造函数的实现
{
  函数体
}

例3-13 调用复制构造函数
设有程序2-1和程序2-2中定义的类myDate和Student,则主函数中可以有:

Student stud;
Student ss[2] = {stud,Student()};或
Student ss[2];
ss[0]= Student(stud);
ss[1]= Student();

例3-14 自定义复制构造函数
在类Student中声明复制构造函数的原型

Student(const Student &s);

复制构造函数的函数体如下

Student::Student(const Student &s)
 {
    name = s.name;
    birthday = s.birthday;
 }

则在主函数中调用自定义的反正构造函数的语句如下:

Student ss[2] = {stud,Student()};
stud.printStudent();

为了验证效果,将复制构造函数修改如下:

 Student::Student(const Student &s)
 {
    name = "COPY" + s.name;
    birthday = s.birthday;
 }

主函数中添加:

Student ss[2] = {stud,Student()};
    stud.printStudent();
    stud.setName("111");
    ss[0] = Student(stud);
    ss[1] = Student();
    stud.printStudent();
    ss[0].printStudent();
    ss[1].printStudent();

*类型转换构造函数

如果构造函数只有一个参数,则可以看作是类型转换构造函数,他的作用是进行类型的自动转换。
程序3-2 类型转换构造函数的定义及使用

#include <iostream>
using namespace std;
class Demo
{
    int id;
    public:
        Demo(int i) //构造函数
        {
            id = i;
            cout<<"id="<<id<<"构造函数"<<endl;
         }
         void printDemo();
         ~Demo()
         {
            cout<<"id-"<<id<<"析构函数"<<endl;
          } 
};

void Demo::printDemo()
{
    cout<<"id="<<id<<endl;
}

int main()
{
    Demo d4(4);
    d4.printDemo();
    d4 = 6;
    d4.printDemo();
    return 0;
}

id=4构造函数
id=4
id=6构造函数
id-6析构函数
id=6
id-6析构函数

二、析构函数

析构函数也是成员函数的一种,它的名字也与类名相同。但要在类名前加一个“~”字符,以区别于构造函数。析构函数没有参数,也没有返回值。
一个类中有且仅有一个析构函数。不会有重载的析构函数。
例3-17 自定义析构函数

myDate::~myDate()
{
    cout<<"myDate析构函数"<<endl;
}
Student::~Student()
{
    cout<<"Student析构函数"<<endl;
}

当使用new运算符生成对象指针时,自动调用本类的构造函数。
例3-18 使用delete语句
修改类myDate和类Student的无参构造函数和析构函数

myDate::myDate():year(1997),month(2),day(12)
{
    cout<<"myDate构造函数"<<endl;

myDate::~myDate()
{
    cout<<"myDate析构函数"<<endl;
Student::Student():name("Noname"),birthday(myDate())
{
    cout<<"Student构造函数"<<endl;
}
Student::~Student()
{
    cout<<"Student析构函数"<<endl;
}

在主函数中执行

Student *stud = new Student();
delete stud;

myDate构造函数
Student构造函数
Student析构函数
myDate析构函数

例3-19 对象数组与delete语句

Student *ss = new Student[2];
delete []ss;

myDate构造函数
Student构造函数
myDate构造函数
Student构造函数
Student析构函数
myDate析构函数
Student析构函数
myDate析构函数

例3-20 对象指针数组与delete语句

Student *ss[2] = {new Student(),new Student()};
delete ss[0];
delete ss[1];

myDate构造函数
Student构造函数
myDate构造函数
Student构造函数
Student析构函数
myDate析构函数
Student析构函数
myDate析构函数

析构函数的调用执行顺序与构造函数相反。

例3-21

#include <iostream>
using namespace std;
class Samp
{
    public:
        void Setij(int a,int b)
        {
            i = a;
            j = b;
        }
        ~Samp()
        {
            cout<<"析构函数"<<i<<endl;
        }
        int GetMuti()
        {
            return i*j;
         } 
        protected:
            int i;
            int j;
};
int main()
{
    Samp *p;
    p = new Samp[5];
    if(!p)
    {
        cout<<"内存分配错误\n";
        return 1;
    }
    for(int j = 0;j<5;j++)
        p[j].Setij(j,j);
    for(int k = 0;k<5;k++)
        cout<<"Muti["<<k<<"]值是:"<<p[k].GetMuti()<<endl;
    delete []p;
    return 0;
}

Muti[0]值是:0
Muti[1]值是:1
Muti[2]值是:4
Muti[3]值是:9
Muti[4]值是:16
析构函数4
析构函数3
析构函数2
析构函数1
析构函数0

三、类的静态成员

静态变量

在C++中,可以使用static说明自动变量。根据定义的位置不同,分为静态全局变量和静态局部变量。

全局变量是指在所有花括号之外声明的变量,其作用域范围是全局可见的,即在整个项目文件内都有效。

使用static修饰的全局变量是静态全局变量,其作用域有所限制,仅在定义该变量的源文件内有效,项目中的其他源文件中不能使用它。

块内定义的变量是局部变量,从定义之处开始到本块结束处为止是局部变量的作用域。

使用static修饰的局部变量是静态局部变量。

静态局部变量具有局部作用域,但却具有全局生存期。

静态变量均存储在全局数据区,静态局部变量只执行一次初始化。

如果程序未显式给出初始值,则相当于初始化为0;如果显式给出初始值,则在该今天变量所在块的一次执行时完成初始化。
程序3-3 自动变量和静态变量的定义和使用

#include <iostream>
using namespace std;
static int glos = 100;
void f()
{
    int a = 1;  //局部自动变量a
    static int fs = 1;  //静态局部变量fs,完成初始化
    cout<<"在f中:a(自动)="<<a<<"  fs(静态)="<<fs<<"  glos(静态)="<<glos<<endl;
    a += 2;
    fs += 2;
    glos += 10;
    cout<<"在f中:a(自动)="<<a<<"   fs(静态)="<<fs<<" glos(静态)="<<glos<<"\n"<<endl;
//  cout<<"ms="<<ms<<endl; 
}

int main()
{
    static int ms = 10;
    for(int i = 1;i<= 3;i++) f();
//  cout<<"fs="<<fs<<endl;  //错误,变量fs不可见
    cout<<"ms="<<ms<<endl;
    cout<<"glos="<<glos<<endl;
    return 0; 
}

在f中:a(自动)=1  fs(静态)=1  glos(静态)=100
在f中:a(自动)=3   fs(静态)=3 glos(静态)=110

在f中:a(自动)=1  fs(静态)=3  glos(静态)=110
在f中:a(自动)=3   fs(静态)=5 glos(静态)=120

在f中:a(自动)=1  fs(静态)=5  glos(静态)=120
在f中:a(自动)=3   fs(静态)=7 glos(静态)=130

ms=10
glos=130

类的静态成员

类的静态成员有两种:静态成员变量和静态成员函数。类的静态成员被类的所有对象共享,不论有多少对象存在,静态成员都只有一份保存在公用内存中。对于静态成员变量,各对象看到的值是一样的。

给静态成员变量赋初值的格式

类型 类名::静态成员变量=初值;

访问类静态成员的一般格式

类名::静态成员名
或
对象名.静态成员名
或
对象指针->静态成员名

类的静态成员函数没有this指针,不能在静态成员函数内访问非静态的成员。

程序3-4 静态成员的使用

#include <iostream>
using namespace std;
class classA
{
    public:
        double x,y; 
        static int num; //公有静态成员变量——供所有对象“共享”,用于记录通过构造函数已生成的对象个数
        
        classA()
        {
            x = 0;
            y = 0;
            num++;      //每生成一个对象,num加1 
         } 
         classA(double x0,double y0)
         {
            x = x0;
            y = y0;
            num++;
          } 
          static void staticFun()       //静态成员函数,输出静态成员变量num的当前值 
          {
            cout<<"current_num="<<num<<endl;
//          cout<<"x="<<x<<endl;    //错误,在静态函数中不能访问非静态变量 
          }
};

int classA::num = 0;        //必须在类体外(使用类名限定)初始化静态成员变量
int main()
{
    classA obj(1.2,3.4),*p; //调用1次构造函数
    cout<<"classA::num="<<classA::num<<endl;    //使用类名做限定符
    classA::staticFun();
    cout<<"obj.num="<<obj.num<<endl;    //使用对象名做限定符 
    obj.staticFun();
    cout<<endl;
    classA A[3];        //调用3次构造函数 
    cout<<"classA::num="<<classA::num<<endl;
    classA::staticFun();
    cout<<endl;
    p = new classA(5.6,7.8);
    cout<<"classA::num="<<classA::num<<endl;
    classA::staticFun();
    cout<<"p->num="<<p->num<<endl;
    p->staticFun();
    return 0;
 } 

classA::num=1
current_num=1
obj.num=1
current_num=1

classA::num=4
current_num=4

classA::num=5
current_num=5
p->num=5
current_num=5

静态成员变量本质上是全局变量。对于一个类,哪怕它的一个对象都不存在,其静态成员变量也存在。静态成员函数并不需要作用在某个具体的对象上,因此本质上是全局函数。

四、变量即对象的生存期和作用域

变量的生存期和作用域

变量的生存期是指变量所占据的内存空间由分配到释放的时期。

变量有效的范围称为其作用域。

作用域分为全局域和局部域。

全局域包括程序作用域和文件作用域;局部域包括类作用域、函数作用域、块作用域和函数原型作用域等。

程序作用域也称为多文件作用域,属于程序作用域的由通过extern关键字进行说明的外部变量以及外部函数等。

重名标识符指的是在程序中被重复定义的同名标识符。在相同的作用域内,标识符不能被重复定义。重名标识符的作用域遵循如下规则:

  • 没有包含关系的两个不同作用域,在其中说明的标识符尽管名字相同,但二者毫不相干。
  • 具有包含关系的两个不同作用域,将它们看成是互不相同的名字;进入子范围后,将屏蔽其父范围的名字。

程序3-5 变量生存期与作用域

#include <iostream>
using namespace std;
int main()
{
    int i = 10; //整型变量i,具有函数作用域
    char ch = '1'; //字符型变量ch,具有函数作用域
    cout<<"在main中 --i=" <<i<<",\tch="<<ch<<endl;
    {
        int i = 20; //另一个整型变量,外层块作用域
        char ch = '2';
        cout<<"在外层块 --i="<<i<<",\tch="<<ch<<endl;
        if(i>0)
        {
            double i = 30.3;
            int ch = 33;
            cout<<"在内层块 --i="<<i<<",\tch="<<ch<<endl;
         } 
         cout<<"在外层块 --i="<<i<<",\tch="<<ch<<endl;
     } 
     cout<<"在main中 --i=" <<i<<",\tch="<<ch<<endl;
     return 0; 
}
在main中 --i=10,        ch=1
在外层块 --i=20,        ch=2
在内层块 --i=30.3,      ch=33
在外层块 --i=20,        ch=2
在main中 --i=10,        ch=1

程序3-6

#include <iostream>
using namespace std;

int x = 11;
char ch = '1';
void fun1(int ipara1);
void fun2()
{
    int i = 22222;
    double ch = 202.2;
    cout<<"fun2中 --x="<<x<<",\tch="<<ch<<",\ti="<<i<<endl; 
}

int main()
{
    cout<<"在main中 --x="<<x<<",\tch="<<ch<<endl;
    fun1(x);
    fun2();
    return 0; 
}

void fun1(int ii)
{
    int i = 2111;
    int x = 201;
    cout<<"在fun1中 --ii="<<ii<<",\tx="<<x<<",\tch="<<ch<<",\ti="<<i<<endl;
}

在main中 --x=11,        ch=1
在fun1中 --ii=11,       x=201,  ch=1,   i=2111
fun2中 --x=11,  ch=202.2,       i=22222

类对象的生存期和作用域

类的对象在生成时调用构造函数,在消亡时调用析构函数,在这两个函数调用之间即是对象的生存期。
程序3-7 对象的生存期与作用域

#include <iostream>
#include <string>
using namespace std;

class Demo
{
    int id;
    string position;
    public:
        Demo(int i,string pos)      //构造函数 
        {
            id = i;
            position = pos;
            cout<<"生成对象"<<this<<",id="<<id<<"   位置:"<<position<<endl;
        }
        Demo(int i):position("temp")    //类型转换构造函数
        {
            id = i;
            cout<<"生成对象"<<this<<",id="<<id<<"   位置:"<<position<<endl;
         } 
        ~Demo()
        {
            cout<<"消亡对象"<<this<<",id="<<id<<"   位置:"<<position<<endl;
        }
};

Demo d1(1,"全局");
void Func()
{
    cout<<"enter func"<<endl;
    static Demo d2(2,"函数Func内,静态的");
    Demo d3(3,"函数Func内");
    cout<<"exit func"<<endl;
}
int main()
{
    cout<<"enter main"<<endl;
    Demo d4(4,"主函数内");
    d4 = 7;
    cout<<"enter block"<<endl;
    {
        Demo d5(5,"主函数块内"); 
    }
    cout<<"exit block"<<endl;
    static Demo d6(6,"主函数内,静态的");
    Func();
    cout<<"main ends"<<endl;
    return 0;
}

生成对象0x4a9030,id=1   位置:全局
enter main
生成对象0x70fdc0,id=4   位置:主函数内
生成对象0x70fde0,id=7   位置:temp
消亡对象0x70fde0,id=7   位置:temp
enter block
生成对象0x70fdb0,id=5   位置:主函数块内
消亡对象0x70fdb0,id=5   位置:主函数块内
exit block
生成对象0x4a9070,id=6   位置:主函数内,静态的
enter func
生成对象0x4a9060,id=2   位置:函数Func内,静态的
生成对象0x70fd40,id=3   位置:函数Func内
exit func
消亡对象0x70fd40,id=3   位置:函数Func内
main ends
消亡对象0x70fdc0,id=7   位置:temp
消亡对象0x4a9060,id=2   位置:函数Func内,静态的
消亡对象0x4a9070,id=6   位置:主函数内,静态的
消亡对象0x4a9030,id=1   位置:全局

五、常量成员和常引用成员

由关键字const修饰的类成员变量称为类的常量成员变量。必须进行初始化,而且只能通过构造函数的成员初始化列表的方式进行。

使用const修饰的函数称为常量函数。

定义常量对象或常量成员变量的格式

const 数据类型 常量名 = 表达式;

定义常量函数的格式

类型说明符 函数名(参数表) const;

例3-26 使用常量对象不能调用非常量成员函数

class CDemo
{
    public:
        void SetValue(){}
};

如果主函数中有:

class CDemo
{
    public:
        void SetValue(){}
};

int main()
{
    const CDemo Obj;    //Obj是常量对象
    Obj.SetValue(); //错误 
}

程序3-8 调用常量成员函数与普通成员函数

#include <iostream>
#include <string>
using namespace std;

class Sample
{
    public:
        Sample();
        void getValue() const;  //常量成员函数
        void getValue();    //非常量成员函数
        void priValue();    //非常量成员函数
        void priVcon() const;   //常量成员函数 
};

Sample::Sample(){}

void Sample::getValue()const    //常量成员函数
{
    cout<<"常量成员函数"<<endl;
 } 

void Sample::getValue()
{
    cout<<"非常量成员函数"<<endl;
}

void Sample::priValue()
{
    cout<<"非常量成员函数"<<endl;
}

void Sample::priVcon()const
{
    cout<<"常量成员函数"<<endl;
}

int main()
{
    const Sample cono;
    Sample o;
    cout<<"cono\t";
    cono.getValue();
//  cono.priValue();    //错误,不能调用非常量成员函数 
    cout<<"o\t";
    o.getValue();
    cout<<"o\t";
    o.priValue();
    cout<<"o\t";
    o.priVcon();
    return 0;
}

cono    常量成员函数
o       非常量成员函数
o       非常量成员函数
o       常量成员函数

程序3-9 常量成员变量及常量成员函数的使用

#include <iostream>
#include <string>
using namespace std;

class constClass
{
    const int conMbr;
    int Mbr;
    public:
        constClass():conMbr(0),Mbr(100){}
        constClass(int i):conMbr(i)
        {
            Mbr = 200;
        }
        void printConst()
        {
            cout<<"conMbr="<<conMbr<<",Mbr="<<Mbr<<endl;
        }
        int getConst()
        {
            cout<<"调用非常量函数"<<endl;
            return conMbr;
        }
        int getConst() const
        {
            cout<<"调用常量函数"<<endl;
            return conMbr;
        }
        int getValue()
        {
            return Mbr;
        }
        void processConst()
        {
            cout<<"-- 在processConst函数中  非常量 --"<<endl;
            int x = 2*conMbr+1;
            cout<<"x=2*conMbr+1="<<x<<endl; //可以读取conMbr
//          conMbr++;//错误,不能更改常量成员变量conMbr的值
            Mbr++;  //可以修改非常量Mbr的值
            cout<<"Mbr="<<Mbr<<endl; 
        }
        void processConst() const
        {
            cout<<"--在processConst函数中 常量--"<<endl;
            int x = conMbr + 1;
            cout<<"x = conMbr + 1="<<x<<endl;
//          conMbr++;//错误
//          Mbr++;  //错误
            cout<<"Mbr="<<Mbr<<endl;
        }
};

int main()
{
    constClass ob1(123),ob2;
    ob1.printConst();
    cout<<"ob2.getConst()="<<ob2.getConst()<<endl;
    ob2.printConst();
    const constClass ob3(20);
    cout<<"ob3.getConst()="<<ob3.getConst()<<endl;
    ob3.processConst();
    return 0;
}

conMbr=123,Mbr=200
调用非常量函数
ob2.getConst()=0
conMbr=0,Mbr=100
调用常量函数
ob3.getConst()=20
--在processConst函数中 常量--
x = conMbr + 1=21
Mbr=200

程序3-10 常引用型成员变量

#include <iostream>
#include <string>
using namespace std;
int fvalue = 10;
class CDemo
{
    public:
        const int num;
        const int& ref;
        int value;
    public:
        CDemo(int n):num(n),ref(value),value(4){}
};

int main()
{
    cout<<sizeof(CDemo)<<endl;
    CDemo f(100);
//  f.ref = f.value;
    cout<<"f.num="<<f.num<<"\tf.ref="<<f.ref<<",\tf.value="<<f.value<<endl;
    return 0;
}

24
f.num=100       f.ref=4,        f.value=4

六、成员对象和封闭类

一个类的成员变量如果是另一个类的对象,则该成员变量称为成员对象。这两个类为包含关系。包含成员对象的类叫做封闭类。

封闭类构造函数的初始化列表

在封闭类构造函数中添加初始化列表的格式

封闭类名::构造函数名(参数表):成员变量1(参数表),成员变量2(参数表),...
{...}

程序3-11 封闭类的定义

#include <iostream>
#include <string>
using namespace std;
class CTyre     //轮胎类
{
    private:
        int radius; //半径
        int width;      //宽度 
    public:
        CTyre():radius(16),width(185){} //构造函数
        CTyre(int r,int w):radius(r),width(w){} //构造函数
        int getRadius()
        {
            return radius;
         } 
        int getWidth()
        {
            return width;
        }
 };
 
class CCar      //汽车类,封闭类
{
    private:
        int price;  //价格
        CTyre tyre; //成员对象 
    public:
        CCar();
        CCar(int p,int tr, int tw);
        int getPrice()
        {
            return price;
        }
        CTyre getCTyre()
        {
            return tyre;
        }
 };
 
CCar::CCar()
{
    price = 50010;
    CTyre();
 };
 
CCar::CCar(int p,int tr,int tw):price(p),tyre(tr,tw){}; //使用初始化列表 

int main()
{
    CCar car(48900,17,225);
    cout<<"price="<<car.getPrice();
    cout<<"\tCTyre.Radius="<<car.getCTyre().getRadius()<<"\tCTyre.Width="<<car.getCTyre().getWidth()<<endl;
    CCar car1;
    cout<<"price="<<car1.getPrice();
    cout<<"\tCTyre.Radius="<<car1.getCTyre().getRadius()<<"\tCTyre.Width="<<car1.getCTyre().getWidth()<<endl;
}

price=48900     CTyre.Radius=17 CTyre.Width=225
price=50010     CTyre.Radius=16 CTyre.Width=185

程序12 封闭类对象的创建与消亡

#include <iostream>
#include <string>
using namespace std;

class CTyre     //轮胎类
{
    private:
        int radius; //半径
        int width;      //宽度 
    public:
        CTyre():radius(16),width(185)
        {
            cout<<radius<<"\tCTyre 构造函数"<<endl;
        }
        CTyre(int r,int w):radius(r),width(w)
        {
            cout<<radius<<"\tCTyre 构造函数"<<endl;
        }
        ~CTyre()
        {
            cout<<radius<<"\tCTyre 析构函数"<<endl;
        }
        int getRadius()
        {
            return radius;
         } 
        int getWidth()
        {
            return width;
        }
 };
 
class CCar      //汽车类,封闭类
{
    private:
        int price;  //价格
        CTyre tyre; //成员对象 
    public:
        CCar();
        CCar(int p,int tr, int tw);
        ~CCar();
        int getPrice()
        {
            return price;
        }
        CTyre getCTyre()
        {
            return tyre;
        }
 };
 
CCar::CCar()
{
    price = 50010;CTyre();cout<<price<<"\tCCar构造函数"<<endl;
};
CCar::CCar(int p,int tr,int tw):price(p),tyre(tr,tw)
{
    cout<<price<<"\t构造函数"<<endl;
}
CCar::~CCar()
{
    cout<<price<<"\tCCar 析构函数"<<endl;
}

int main()
{
    CCar car(48900,17,225);
    cout<<"price="<<car.getPrice();
    cout<<"\tCTyre.Radius="<<car.getCTyre().getRadius()<<"\tCTyre.Width="<<car.getCTyre().getWidth()<<endl;
    CCar car1;
    cout<<"price="<<car1.getPrice();
    cout<<"\tCTyre.Radius="<<car1.getCTyre().getRadius()<<"\tCTyre.Width="<<car1.getCTyre().getWidth()<<endl;
}

17      CTyre 构造函数
48900   构造函数
price=48900     CTyre.Radius=17 CTyre.Width=225
17      CTyre 析构函数
17      CTyre 析构函数
16      CTyre 构造函数
16      CTyre 构造函数
16      CTyre 析构函数
50010   CCar构造函数
price=50010     CTyre.Radius=16 CTyre.Width=185
16      CTyre 析构函数
16      CTyre 析构函数
50010   CCar 析构函数
16      CTyre 析构函数
48900   CCar 析构函数
17      CTyre 析构函数

*封闭类的复制构造函数

如果封闭类的对象是用默认复制构造函数初始化的,那么它包含的成员对象也会用复制构造函数初始化。

程序3-13 复制构造函数的使用

#include <iostream>
#include <string>
using namespace std;

class A
{
    public:
        A()
        {
            cout<<"default"<<endl;
        }
        A(A &a)
        {
            cout<<"copy"<<endl;
        }
};

class B
{
    A a;
};

int main()
{
    B b1,b2(b1);
    return 0;
}

default
copy

七、友元

友元

友元实际上并不是面向对象的特征,而是为了兼顾C语言程序设计的习惯与C++信息隐藏的特点,而特意增加的功能。

这是一种类成员的访问权限。

友元的概念破坏了类的封装性和信息隐藏,但有助于数据共享,能够提高程序执行的效率。

友元使用关键字friend标识。在类定义中,当friend出现在函数说明语句的前面时,表示该函数为类的友元函数。一个函数可以同时说明为多个类的友元函数,一个类中也可以有多个友元函数。

友元函数

在类定义中,将一个全局函数声明为本类友元函数的格式

friend 返回值类型 函数名(参数表);

当有某类A的定义后,将类A的成员函数说明为本类的友元函数的格式

friend 返回值类型 类A::类A的成员函数名(参数表);

不能把其他类的私有成员函数声明为友元函数。

友元函数不是类的成员函数,但允许访问类中的所有成员。

友元函数不受类中的发访问权限关键字限制,可以把它放在类的公有、私有、保护部分,结果一样。

程序3-14 友元函数的声明

#include <iostream>
#include <cmath>
using namespace std;
class Pixel;    //前向引用声明
class Test
{
    public:
        void printX(Pixel p);   //用到了类Pixel 
 };
 
class Pixel
{
    private:
        int x,y;
    public:
        Pixel(int x0,int y0)
        {
            x = x0;
            y = y0;
        }
        void printxy()
        {
            cout<<"pixel:("<<x<<","<<y<<")"<<endl;
        }
        friend double getDist(Pixel p1,Pixel p2);   //友元函数(原型),全局函数
        friend void Test::printX(Pixel p);      //类Test的成员函数为本类的友元函数 
};
void Test::printX(Pixel p)
{
    cout<<"x="<<p.x<<"\ty="<<p.y<<endl; //访问类Pixel的私有成员 
    return;
}

double getDist(Pixel p1,Pixel p2)   //友元函数在类体外定义 
{
    double xd = double(p1.x-p2.x);
    double yd = double(p1.y-p2.y);
    return sqrt(xd*xd + yd*yd);     //两点间距离 
 } 
 
int main()
{
    Pixel p1(0,0),p2(10,10);
    p1.printxy();
    p2.printxy();
    cout<<"(p1,p2)间距离 = "<<getDist(p1,p2)<<endl;    //直接调用全局函数
    Test t;
    cout<<"从友元函数中输出---"<<endl;
    t.printX(p1);
    t.printX(p2);
    return 0; 
 } 

pixel:(0,0)
pixel:(10,10)
(p1,p2)间距离 = 14.1421
从友元函数中输出---
x=0     y=0
x=10    y=10

程序3-15 类成员函数实现复数类操作

#include <iostream>
using namespace std;

class myComplex     //复数类 
{
    private:
        double real,imag;   //复数的实部和虚部
    public:
        myComplex();
        myComplex(double r,double i);
        myComplex addCom(myComplex c);  //成员函数,调用者对象与参数对象c相加
        void outCom();      //成员函数,输出调用者对象的有关数据 
};

myComplex::myComplex()
{
    real = 0;
    imag = 0;
}

myComplex::myComplex(double r,double i)
{
    real = r;
    imag = i;
}

myComplex myComplex::addCom(myComplex c)
{
    return myComplex(real + c.real,imag + c.imag);
}

void myComplex::outCom()
{
    cout<<"("<<real<<","<<imag<<")";
}

int main()
{
    myComplex c1(1,2),c2(3,4),res;
    res = c1.addCom(c2);    //调用友元函数必须通过类对象 
    c1.outCom();
    cout<<"+";
    c2.outCom();
    cout<<"=";
    res.outCom();
    cout<<endl;
    return 0;
}

(1,2)+(3,4)=(4,6)

程序3-16 友元函数实现复数类操作

#include <iostream>
using namespace std;

class myComplex     //复数类 
{
    private:
        double real,imag;   //复数的实部和虚部
    public:
        myComplex();
        myComplex(double r,double i);
        friend myComplex addCom(myComplex c1,myComplex c2); //友元函数 两个参数对象c1与c2相加 
        friend void outCom(myComplex c);        //友元函数  输出参数对象c的有关数据 
};

myComplex::myComplex()
{
    real = 0;
    imag = 0;
}

myComplex::myComplex(double r,double i)
{
    real = r;
    imag = i;
}

myComplex addCom(myComplex c1,myComplex c2)
{
    return myComplex(c1.real + c2.real,c1.imag + c2.imag);
}

void outCom(myComplex c)
{
    cout<<"("<<c.real<<","<<c.imag<<")";
}

int main()
{
    myComplex c1(1,2),c2(3,4),res;
    res = addCom(c1,c2);    //调用友元函数必须通过类对象 
    outCom(c1);
    cout<<"+";
    outCom(c2);
    cout<<"=";
    outCom(res);
    cout<<endl;
    return 0;
}

(1,2)+(3,4)=(4,6)

友元类

在类定义中声明友元类的格式

friend class 类名;

友元类的关系是单向的。若说明类B是类A的友元类,不等于类A也是类B的友元类。

改写实现复数类的程序3-15和程序3-16,将操作定义为一个操作类oper,然后将类oper说明为Complex的友元类。
程序3-17 友元类示例

#include <iostream>
using namespace std;

class myComplex     //复数类 
{
    private:
        double real,imag;   //复数的实部和虚部
    public:
        myComplex();
        myComplex(double r,double i);
        friend class oper;  //友元类 
};


myComplex::myComplex()
{
    real = 0;
    imag = 0;
}

myComplex::myComplex(double r,double i)
{
    real = r;
    imag = i;
}

class oper
{
    public:
        myComplex addCom(myComplex c1,myComplex c2);        //成员函数,两个参数对象c1与c2相加
        void outCom(myComplex c);   //成员函数,输出参数对象c的有关数据 
};

myComplex oper::addCom(myComplex c1,myComplex c2)
{
    return myComplex(c1.real + c2.real,c1.imag + c2.imag);
}

void oper::outCom(myComplex c)
{
    cout<<"("<<c.real<<","<<c.imag<<")";
}

int main()
{
    myComplex c1(1,2),c2(3,4),res;
    oper o;
    res = o.addCom(c1,c2);  //通过类oper的对象调用操作类的成员函数 
    o.outCom(c1);
    cout<<"+";
    o.outCom(c2);
    cout<<"=";
    o.outCom(res);
    cout<<endl;
    return 0;
}

(1,2)+(3,4)=(4,6)

例3-31 计算两点间的距离

#include <iostream>
#include <cmath>
using namespace std;

class Point //定义的是平面坐标系下的点 
{
    private:
        double x,y;
        friend class Line;
    public:
        Point(double i = 0,double j = 0)
        {
            x = i;
            y = j;
        }
        Point(Point &p)
        {
            x = p.x;
            y = p.y;
        }
};

class Line  //定义的是经过两点的直线 
{
    private:
        Point p1,p2;
    public:
        Line(Point &xp1,Point &xp2):p1(xp1),p2(xp2){}
        double GetLength(); //返回给定两点间的距离 
};

double Line::GetLength()
{
    double dx = p2.x - p1.x;
    double dy = p2.y - p1.y;
    return sqrt(dx*dx + dy*dy);
}

int main()
{
    Point p1,p2(6,8);
    Line L1(p1,p2);
    cout<<L1.GetLength()<<endl;
}

八、this指针

C++语言规定,当调用一个成员函数时,系统自动向它传递一个隐含的参数。该参数是一个指向调用该函数的对象的指针,称为this指针。

this指针是C++实现封装的一种机制,它将对象和该对象调用的成员函数联系在一起,从外部看来,好像每一个对象都拥有自己的成员函数。

C++规定,在非静态成员函数内部可以直接使用this关键字,this就代表指向该函数所作用的对象的指针。

程序3-18 this指针的使用

#include <iostream>
using namespace std;

class myComplex
{
    public:
        double real,imag;
        myComplex():real(0),imag(0){}
        myComplex(double,double);
        myComplex AddRealOne();
        myComplex AddImagOne();
        void outCom();
};

myComplex::myComplex(double real,double imag)
{
    this->real = real;
    this->imag = imag;
}
void myComplex::outCom()
{
    cout<<"("<<real<<","<<imag<<")";
}

myComplex myComplex::AddRealOne()
{
    this->real++;
    return *this;
}

myComplex myComplex::AddImagOne()
{
    this->imag++;
    return *this;
}

int main()
{
    myComplex c1(1,1),c2,c3;
    c1.outCom();
    c2.outCom();
    c3.outCom();
    cout<<endl<<"我是分界线"<<endl;
    c2 = c1.AddRealOne();
    c1.outCom();
    c3 = c1.AddImagOne();
    c2.outCom();
    c3.outCom();
    cout<<endl;
    return 0;
}

(1,1)(0,0)(0,0)
我是分界线
(2,1)(2,1)(2,2)

相关文章

  • 类和对象进阶

    值类型和引用类型 // 值类型:string,number,boolean let num1 = 100 ...

  • 类和对象进阶

    一、构造函数 构造函数的作用 构造函数是类中的特殊成员函数,它属于类的一部分。给出类定义时,由程序员编写构造函数。...

  • Python进阶:类和对象

    .面向对象 (OOP) 面向过程:顺序执行,只有执行,没有返回值,特点是侧重怎么做,就是在主程序中调用不同的函数,...

  • 三、类和对象进阶

    构造函数 变量初始化全局变量如果程序员在声明变量时没有进行初始化,则系统自动为其初始化为0。这个工作在程序启动时完...

  • Python面向对象进阶

    Python 面向对象(进阶篇) 面向对象是一种编程方式,此编程方式的实现是基于对 类 和 对象 的使用类 是一个...

  • python进阶:面向对象是什么意思?

    python是一门面向对象的语言,体现面向对象的技术就是类。想要在python编程上进阶,就必须理解和运用类来解决...

  • [5] - 类和对象之进阶(一)

    继承 只能有一个父类 与其他支持面向对象的语言一样,Scala 也支持继承,并且子类只能有一个父类,不能继承于多个...

  • [6] - 类和对象之进阶(二)

    Scala 中的可见性非常灵活且复杂,这篇文章希望通过大量的示例来说清楚各种情况下的可见性是怎么样的。 默认可见性...

  • Python面向对象之访问控制!

    回顾 在Python进阶记录之基础篇(十五)中,我们介绍了面向对象的基本概念以及Python中类和对象的基础知识,...

  • 对象、类对象和元类对象

    http://www.tuicool.com/articles/mmyuUr http://blog.csdn.n...

网友评论

      本文标题:类和对象进阶

      本文链接:https://www.haomeiwen.com/subject/jvklnrtx.html