C++程序设计案例教程(学习笔记)——Cha5类和对象

  • 5.1 类的定义

类的定义格式存在两部分:分别是声明和实现。
声明部分:声明类中的成员(数据成员和成员函数)
在类体内定义的成员函数称为内联函数。
5.1.1 类的定义格式
class <类名> //声明部分、
{
public:
<公有数据成员和成员函数的声明或实现>
protected:
<保护数据成员和成员函数的声明或实现>
private:
<私有数据成员和成员函数的声明或实现>

};
<函数类型> <类名> ::<成员函数名> (<参数表>) //实现部分
{
<函数体>
}
通常来说,类成员包含数据成员和函数成员,数据成员通常是变量或对象的声明语句,
函数成员指函数的定义或声明语句
public:关键字,说明其后的成员为公有成员。通常将类的成员函数全部或部分定义为公有成员。
private:关键字,可默认,说明其后的成员为私有成员。通常将类的数据成员定义为私有成员,是被隐藏的部分。
protected:关键字,说明其后的成员为保护成员。
三个关键字称为访问权限控制符或访问权限修饰符,它们在类体中出现的顺序和次数没
有限制,用来说明类成员的访问权限。
成员函数的定义既可以在类体内,也可以在类体外实现,在类体内是正常定义,在类体
外定义时使用“::”说明该函数属于哪个类,该符号称为作用域运算符。
例:void Point::Setvalue(int x, int y)
{
<(函数实现部分)>

}
定义类注意:
1)在类体内定义数据成员时,不允许初始化。
2)类中的数据成员可以是任意类型的数据,但不能是自身类的对象
3)在类体外定义函数时,必须在类体内有声明语句
4)一般在类中,先声明公有成员,后声明私有成员,在声明数据成员时,一般按数据成员类型的大小,有小到大进行声明,这样以便提高时空利用率。
5)通常将类的定义放在一个用户自定义的头文件中,方便程序使用。

5.1.2 类成员的访问控制
访问控制属性:public private protected
1)public:公有属性定义了类的外部接口,共用成员既可以在类体内引用,也可以在类外程序引用,共有的成员函数或数据成员可以被程序中的任何函数或语句访问(静态成员除外)。
2)private:私有属性的成员不允许类的外部程序代码对其进行直接访问,只能被本类的成员函数或特殊说明的函数引用,私有成员被隐藏在类中,对外界不可见,外部程序代码访问私有成员只能通过类内定义的公有成员函数或友元函数。
3)protected:保护属性对于本类相当于私有成员,对于该类的派生类属于相当于公有成员,保护属性的成员不能被类外的程序代码访问,只有类的成员函数和类的派生类才可以访问。

5.1.3成员函数的特性
成员函数具有一般函数的特性,包括重载,可定义为内联函数,可以设置函数参数的默认值。
1)成员函数的重载性
注:一般成员函数和构造函数可以重载,但是析构函数不可以重载
2) 成员函数可以定义为内联函数
成员函数如果在类体内定义,则为内联函数,如果在类外定义的函数也成为内联函数,则在定义时在函数头加上关键字inline,例:inline void Point :: Move( int x,int y);
3)成员函数的参数可以设置默认值
在程序中既有函数声明又有定义时,默认参数只能在函数声明中给出,而定义中不允许设置默认参数。但是当程序中没有函数声明时,则默认参数必须在函数定义中出现。

  • 5.2 对象的定义和使用

5.2.1对象的定义方法
类是一种类型,不占用存储空间;对象是类的实例,是实体,定义对象时,系统会分配相应存储空间,对象包含其所属类的所有成员。对象中只存放数据成员,成员函数不在每个对象中,而是存放在一个可被对象访问的公共区域中。
对象定义格式: <类名> <对象名表>;
例: Point p1,p2 *p,&rp,p[10];
5.2.2对象成员的表示方法
1.一般对象的成员表示
<对象名>.<数据成员>
<对象名>.<成员函数名>(<参数表>)
例:Point p1;
2.指向对象的指针的成员表示
<对象名>-><数据成员名>
<对象名>-><成员函数名>(<参数表>)
3.对象引用的成员表示
<对象引用名>.<数据成员名>
<对象引用名>.<成员函数名>(<参数表>)
4.对象数组元素的成员表示
<数组名>[<下标>].<数据成员名>
<数组名>[<下标>].<成员函数名>(<参数表>)

  • 5.3 构造函数和析构函数

C++在创建对象时,系统会自动调用相应的构造函数对所创建对象进行初始化,即完成对象的数据成员初始化的过程中,当一个对象生存期结束,系统自动调用析构函数来释放这个对象。
5.3.1 构造函数和拷贝构造函数

  • 构造函数的特点
    (1)构造函数的名字与类名相同
    (2)构造函数没有返回值,不允许定义构造函数的返回值类型,包括void型
    (3)构造函数可以有一个,多个或没有参数,分别称为有参构造函数和默认构造函数
    (4)构造函数可以重载,重载构造函数由参数个数和参数类型来区分
    (5)程序中一般不直接调用构造函数,而是由系统在创建对象时自动调用

  • 默认(无参)构造函数
    当创建不带任何实参的对象时,通常调用默认构造函数。
    默认构造函数:有两种,一种是由用户定义,另一种是系统自动提供。当用户在类体中没有定义任何构造函数,系统会自动创建一个默认的构造函数,其函数体为空,用来对创建的对象进行初始化,使对象中的不同类型的数据成员具有默认值或无意义值
    默认(无参)构造函数定义格式:
    <类名>::<默认构造函数名>()
    {
    }

    注:class A
    {
    Public:
    A();
    A(int,int);
    A(double,double);
    //…
    }
    int main()
    {
    A a1(10,10);
    A a2; //产生二义性,创建对象a3时,系统无法确定是调用误无参的构造函数还是调
    //用有默认参数的构造函数来初始化对象。
    }

  • 有参构造函数
    如果被创建对象带有实参,系统会根据实参的个数调用相应的有参构造函数对对象进行初始化。
    注:若类中的已定义有参构造函数,则在使用无参构造函数时,必须由用户自定义无参构造函数,因为系统默认的无参构造函数已经失去作用。

  • 拷贝构造函数
    具有一般构造函数的功能,其作用是使用已知对象给所创建对象进行初始化。拷贝构造函数只有一个参数,且是对某一个对象的引用。
    定义格式:
    <构造函数名> (<类名> &<对象引用名>)
    如果一个类中没有定义拷贝构造函数,则系统会自动提供一个默认的拷贝构造函数,拷贝构造函数在以下三种情况被调用:
    1)由一个对象初始化同类的另一个对象
    2)当对象做为函数实参传递给函数形参时 例:void Fun(p1); //实参p1传递给形参p
    3)当函数返回值为对象时 例:系统调用构造函数p去创建并初始化一个无名对象,函数返回时将无名对象的值赋给对象p1,然后释放无名对象。
    Poin Fun()
    {

    return p;
    }
    int main()
    {

    p1=Fun()
    }
    5.3.2 析构函数
    一种特殊的成员函数,功能是用来释放所创建的对象,一个对象在其生命周期将要结束时由系统自动调用析构函数将其从内存中清除,释放由构造函数分配的内存
    析构函数的特点:
     1) 析构函数与构造函数名同类名,但要在析构函数名前加~。
     2)析构函数定义时不能给出类型,也无返回值,并且无参数。
     3)析构函数不能重载,一个类中只能定义一个析构函数
     4)析构函数通常被系统自动调用,用户不能调用
     5)如果一个类中没有定义析构函数,则系统提供一个默认的析构函数,其格式如下:
    <类名>::<默认析构函数名>
    { } //默认析构函数体为空
    析构函数在以下两种情况下被调用:
    1)如果在函数体内定义一个对象,当函数结束时,系统自动调用析构函数释放该对象
    2)如果一个对象是使用new运算符动态创建时,则在delete运算符释放时,会自动调用
    析构函数
    注:先创建对象后析构,后创建对象先析构。

  • 5.4 静态成员

    静态成员,实现了数据共享又不影响数据的封装性。静态成员用static来修饰,不管有多少个属于类的对象,其静态成员在内存中只有一个拷贝,为类中所以对象共享。
    5.4.1 静态数据成员

     静态数据成员是该类所有对象共有的数据成员,实现同类对象的数据传递,它的值对每个对象都是一样的。
静态数据成员的定义格式:
    在类中定义:static <类型说明符> <数据成员名>;
     在类中定义,在类外进行初始化:<数据类型> <类名>::<数据成员>=<初值>;
注: 静态数据成员只能在类外进行初始化,静态数据成员独立于所有对象,占据的存储空间是系统为其开辟的一个单独的空间,可以通过类来引用也可通过对象来引用。引用格式如下:<类名>::<静态数据成员>;
5.4.2静态成员函数
     静态成员函数的实现既可以在类体内也可以在类体外,静态成员函数是类中所有对象所共享的成员函数。
1) 静态成员函数,声明格式:
Static <类型> <成员函数>(<参数表>)
2)静态成员函数引用
<类名>::<静态成员函数名>(<参数表>)

<对象名>.<静态成员函数>(<参数表>)
注:静态成员函数属于类,而不属于某个特定的对象,因此不能像一般的成员函数那样随便访问对象中的非静态数据成员,只能引用类中声明的静态数据成员,如果要引用非静态数据成员则通过对象来引用。

  • 5.5 常对象和常成员

    5.5.1 常对象
    常对象定义格式:
    Const <类名> <对象名> (<初值>);
    或 <类名> const <对象名> (<初值>);
    注:常对象的值不能改变,常对象只能调用类中的常成员函数。
    5.5.2 常数据成员
    const <类型> <常数据成员>;
    注:常数据成员的值必须初始化,且不能改变,故在类中声明常数据成员时,只能通过构造函数成员初始化列表来实现。
    <构造函数名>(<参数表>):<成员初始化列表>
    {
    <函数体>
    }
    5.5.3 常成员函数
    <类型> <成员函数名>(<参数表>) const;
    在函数声明和函数定义部分都要有关键字。const用于判断函数重载条件, 常成员函数可以被一般对象调用。

  • 5.6 对象指针和对象引用

5.6.1 对象指针
1.指向对象的指针
<类名> *<对象指针名>;
对象指针在定义过程中可以赋值也可以不赋值,
对象指针可以被赋值,可以使用同类对象的地址值,也可以使用new运算符为对象指针赋值
指向类的数据成员的指针
该指针可以可访问所指向类的数据成员,但该数据成员必须具有公有属性
<类型> <类名>:: *<指针变量名;>
<类数据成员指针变量名>=&<类名>::<类数据成员名>;

例: int A :: *p=&A :: y //定义一个指向类A的数据成员y的指针p

指向类成员函数的指针该指针所指向类中的成员函数(必须具有公有属性)
定义格式如下:
<类型> (<类名> ::*<指针变量名>)(形参表); //类型是成员函数的返回值类型
<指向类成员函数的指针变量名>=<类名>::<类成员函数名>;
通过类成员函数指针调用该成员函数:
(<对象名> . *<指向类成员函数的指针变量>)(实参表)

5.6.2 this指针
this指针是一个隐含于每个成员函数中的特殊指针,是类的指针类型,存储该类的某个对象的地址,this指针由系统创建,this指针明确指出成员函数当前操作的对象,当通过一个对象调用其成员函数时,系统先将对象的地址赋给this指针,然后调用成员函数,成员函数执行时隐含使用了this指针。也可以使用*this来表示或调用该成员函数的对象。
5.6.3 对象的引用
对象引用常用作函数的形参,定义格式式如下:
<类名> &<对象引用名>=<对象名>;
引用调用:对象的引用在做函数形参时,要求实参为对象名,对象引用做函数参数要比指针做参数更普通,更加简单和直接。

  • 5.7 对象数组

       对象数组是指元素为类的对象的数组,对象数组的定义、赋值和引用与普通数组一样,故有指向对象数组的指针和对象指针数组。
    5.7.1 对象数组的定义和使用
    对象数组的定义格式如下:
    <类名> <数组名>[ <大小>]…;
    对象数组中的元素必须是同一个类的对象,生成对象数组时,将针对每个数组元素按其下标的排列顺序依次调用一个构造函数,调用的次数是等于数组的元素个数。
    定义对象数组时可以直接初始化,也可以通过赋值语句进行赋值。
    对象数组在定义过程中进行元素初始化时调用有参构造函数,如果只定义而不进行初始化,则调用无参构造函数。
    利用赋值语句对数组元素进行赋值时,系统先调用有参构造函数来创建无名对象,并将其值赋给相应对象数组元素,然后再将无名对象释放,通过析构函数来实现。在程序结束前,调用析构函数将对象数组元素释放,释放顺序与创建顺序刚好相反。
    5.7.2 对象指针数组
    对象指针数组是指数组元素都是指向对象的指针,且所有数组元素都是指向同类对象的指针。定义格式如下:
    <类名> *<对象指针数组名>[<大小>]…;
    类名是对象指针数组中指针所指向的类,对象指针数组可以初始化也可以被赋值。
    5.7.3 指向对象数组的指针
    指向对象数组的指针可以指向一维数组,也可以指向多维数组。

  • 5.8 子对象和堆对象

子对象:当一个类的成员是另一个类的对象时,该成员对象称为子对象。
堆对象:也称动态对象,是一种在程序运行过程中根据需要随时间创建的对象。
5.8.1 子对象
体现了定义的嵌套,当一个类中出现子对象,该类的构造函数要负责子对象的初始化,子对象的初始化应该通过构造函数的初始化列表完成。
5.8.2 堆对象
堆对象在程序运行过成当中,可已根据需要随时创建和删除。堆对象存储在内存的一个特殊区域。这种特殊的存储单元成为堆对象。
创建堆对象:new 删除堆对象: delete
new创建一个对象或一个对象数组,定义格式如下:
new <类名>(<初值>)
或者
new <类名> [<大小>]
注:new返回一个地址值,通常将其赋给一个同类型的指针,<初值>指的是创建对象时,用来给创建的对象初始化的值,若省略,则得到默认值。
例:Point *p;
P=new Point (10,20);
//new用来创建一个可存放类point大小的存储空间,并将其首地址返回给指针p,new运算符同时自动调用该类的构造函数将其创建的堆对象进行初始化。将数据或成员的值放在开辟的内存单元中。
例:Point *q;
P=new Point [10];
// new 用来创建一个可存放10个类为point的存储空间,并将其第一元素的首地址返回给P,new运算符同时自动调用无参构造函数给对向数组元素进行初始化。

delete 运算符可以释放堆对象,其格式如下:
delete <指针名>;

delete [ ]<指针名>;
例如:
delete p; // 将指针名所指向的对象释放
delete [ ] q; // 将创建的对象数组释放

  • 5.9 友元

类具有封装性,类中的私有成员只能通过对象调用公有成员函数访问或者利用有友元函数。
5.9.1 友元函数
在一个类体内声明的友元函数不是该类的成员函数,只是独立于该类的一般函数,但是它具有类成员函数可访问该类所有成员的功能。声明格式如下:
friend <类型> <函数名>(<参数表>);
注:友元函数破坏了类的封装性,使用时应特别慎重
友元函数可在类体内任何位置声明,但其函数定义一般在类体外。友元函数可以访问类中的私有成员,但是不可以直接引用数据成员,而必须通过对象来引用,通常友元函数的参数是对象的引用
5.9.2 友元类
友元不仅可以是函数,还可以是个类,即一个类可以做另一个类的友元,该类即为友元。当一个类是另一个的友元类时,该类中的所有成员函数都是这个类的友元函数。
注:使用友元类时,注意友元类不可逆,同时友元类是不可传递的。

  • 5.10 类的作用域和对象的生存期

5.10.1 类的作用域
每个类都有自己的作用域,范围就是所定义的类体内的成员。一个文件可以包含若干个类,而一个类又可以定义多个成员函数,类的作用域介于文件域和函数域之间。
注:类作用域中定义的数据成员不能使用register、extern等修饰,类的作用域介于文件域和函数域之间。
类成员的作用域在下列情况下具有类作用域(假设类A中的成员M)
(1)类的成员函数中出现了该类的成员M,并且该成员函数中没有定以同名的标识符。
(2)在该类的某个对象的该成员表达式中,例如A a;a.M中。
(3)在该类的某个指向对象指针的该成员表达式中,例如A a, *pa;则在pa->M中。
(4)在使用域运算符所限定的该成员中,例如:A::M 。
5.10.2 对象的生存期
对象生存期是指一个对象从创建开始到被释放为止存在时间。
对象分为三种:全局对象、局部对象和静态对象。

  1. 全局对象
    作用域是整个程序,程序结束时才被释放。全局对象的缺点是安全性较差,因为在整程序的各个文件可以改变它。
    全局对象定义在函数体外,定义时不加修饰符。在某个文件中定义的全局对象,在其他文件使用时应该加extern

  2. 局部对象
    定义在函数体内或程序块内,作用域是该函数体或程序块内。局部对象是在对,象声明时创建的,而在退出定义该对象所在函数或块时,系统自动调用析构函数将其释放。函数的参数也是局部对象。

  3. 静态对象
    程序运行结束时才被释放。根据作用域的的不同分为内部静态对象和外部静态对象。函数体内或程序块内的静态对象称为内部静态对象,作用域为函数体或程序块;定义在函数体外的静态对象称为外部静态对象,作用域为从定义位置开始知道文件结束。静态对象存放在静态存储区中。
    静态对象定义时需要前面加上关键字static。

综上:全局对象和静态对象的生产期最长,全局对象在程序开始时即被创建,两者都是在程序结束时,调用析构函数来释放。

猜你喜欢

转载自blog.csdn.net/qq_40719550/article/details/83447640
今日推荐