C++学习笔记——02 怎样使用类和对象


🌿前言:本系列笔记是主要是对于谭浩强的c++程序设计的学习笔记再加上一些我自己的理解。如果有误欢迎大家指出。

建议是有基础的同学快速入门,或者复习用

一、 类对象的初始化

1.需要对类对象进行初始化
  • 不能在声明类时对数据成员初始化

  • 如果一个类中所有成员都是公有的,则可以在定义对象时对数据成员初始化

    class Time
    {
        public:
        hour;
        minute;
        sec;
    };
    Time t1={14,56,30}; 
    

    (类似于结构体变量的初始化)

2. 构造函数——实现数据成员的初始化
  • 构造函数无需用户调用,在建立对象时会自动执行

  • 构造函数的名字必须与类同名。它没有任何类型,不返回任何值

    #include<iostream>
    using namespace std;
    class Time
    {
        public:
        Time()
        {
            hour=0;
            minute=0;
            sec=0;
        }
        void set_time();
        void show_time();
        private:
        int hour;
        int minute;
        int sec;
    };
    void Time::set_time()
    {
        cin>>hour;
        cin>>minute;
        cin>>sec;
    }
    void Time::show_time()
    {
        cout<<hour<<":"<<minute<<":"<<sec<<endl;
    }
    int main()
    {
        Time t1;
        t1.set_time();
        t1.show_time();
        Time t2;
        t2.show_time();
        return 0;
    }
    
  • 也可以只在类内声明,而在类外定义构造函数。不过要加上类名Time和域限定符“::”

    #include<iostream>
    using namespace std;
    class Time
    {
        public:
        Time();
        void set_time();
        void show_time();
        private:
        int hour;
        int minute;
        int sec;
    };
    Time::Time()
    {
        hour=0;
        minute=0;
        sec=0;
    }
    
  • 可以用类对象初始化另一个类对象

    Time t1;
    Time t2=t1;
    
  • 构造函数里也可以包含其他语句但是一般不提倡。

  • 如果用户没有定义构造函数,系统会自动生成一个空的构造函数,它没有参数,不执行初始化操作。

3.带参数的构造函数——对不同对象初始化
  • 带参数的构造函数的一般形式:

    构造函数名(类型1 形参1,类型2 形参2,·····)

  • 相应的定义对象的格式就换为:

    类名 对象名(实参1,实参2,·····)

    #include<iostream>
    using namespace std;
    class Time
    {
        public:
        Time(int,int,int);
        private:
        int hour;
        int minute;
        int sec;
    };
    Time::Time(int h,int m,int s)
    {
        hour=h;
        minute=m;
        sec=s;
    }
    int main()
    {
        Time t1(12,13,14);
        Time t2(13,14,15);
        //略
        return 0;
    }
    
4.参数初始化表
  • 参数初始化表,可以在函数首部实现初始化。

  • 带参数初始化表的构造函数的一般形式

    类名::构造函数名([参数表])[:成员初始化表]

    {

    [构造函数体]

    }

    Time::Time(int h,int m,int s):hour(h),minute(m),sec(s){}
    

    方括号内的东西可有可无

    即在原来函数首部的末尾加一个冒号,然后列出初始化表。

  • 如果数据成员是数组,则应当在构造函数的函数体内进行赋值,不能在参数初始化表中初始化。

    class Student
    {
        public:
        student(int n,char s,name[]):num(n),sex(s)
        {
            strcpy(name,nam);
        }
        private:
        int num;
        char sex;
        char name[20];
    };
    
5.对构造函数重载
  • 作用:以便为对象提供不同的初始化方法

  • 在建立对象时不必给出实参的构造函数,称为默认构造函数。无参函数属于默认构造函数。一个类只能有一个默认构造函数。

  • 如果在建立对象时选用的是无参构造函数,要注意是

    Time t1;
    Time t1(); //这是错误的
    
6. 默认参数

构造函数中参数的值既可以通过实参传递,也可以指定为某些默认值,也就是如果用户不指定实参值,编译系统就让形参的值为默认值。

#include<iostream>
using namespace std;
class Time
{
    public:
    Time(int h=10,int m=10,int s=10);//在声明时指定默认构造函数
    //也可以写成
    //Time(int=10,int=10,int=10);
    private:
    int hour;
    int minute;
    int sec;
};
Time::Time(int h,int m,int s)//在定义时可以不指定默认参数
{
    hour=h;
    minute=m;
    sec=s;
}
int main()
{
    Time t1(12,13,14);
    Time t2(13,14,15);
    //略
    return 0;
}
  • 定义了全是默认参数的构造函数后,不能定义重载构造函数。
7. 利用析构函数进行清理工作
  • 析构函数是在撤销对象占用的内存之前完成一些清理工作,使这部分内存可以被程序分配给新对象使用。它还可以被用来执行“用户希望在最后一次使用对象之后所执行的任何操作。

  • 析构函数的名字是类名之前加上一个波浪线

  • 当对象的生命期结束时,会自动执行析构函数

  • 一个类只能有一个析构函数。因为它没有函数参数,所以不能被重载,也就只能有一个析构函数

  • 应该在声明类的同时定义析构函数

    #include<iostream>
    using namespace std;
    class Time
    {
        public:
        Time(int,int,int);
        ~Time()
        {
            cout<<"Constructor called."<<endl;
        }
        private:
        int hour;
        int minute;
        int sec;
    };
    Time::Time(int h,int m,int s)
    {
        hour=h;
        minute=m;
        sec=s;
    }
    int main()
    {
        Time t1(12,13,14);
        Time t2(13,14,15);
        //略
        return 0;
    }
    
8. 调用构造函数和析构函数的顺序
  • 先构造的后析构,后构造的先析构

  • 系统在什么时候调用构造和析构:

    • (1)如果在全局范围中定义对象(即在所有函数之外定义的对象),那么它的构造函数在本文件模块中的所有函数(包括main函数)执行之前调用。但如果一个程序包含多个文件,而在不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。但是当main函数执行完毕或调用exit函数时(此时程序终止),会调用析构函数。
    • (2)如果定义的是局部自动对象(例如在函数中定义对象),则在建立对象时调用其构造函数。如果对象所在的函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。
    • (3)如果在函数中定义静态(static)局部对象,则只在程序第1次调用此函数定义对象时调用构造函数一次,在调用函数结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

二、对象数组

  • 对象数组的每一个元素都是同类对象

  • 建立数组时同样要调用构造函数

  • 在需要时可以在定义数组时提供实参进行初始化。如果构造函数只有一个参数,在定义数组时可以直接在等号后面的花括号内提供实参。如果构造函数有多个参数这不能使用该方法。

    Student stud[3]={60,70,80};//3个实参分别传递给3个数组元素的构造函数
    
  • 构造函数有多个参数的初始化方法:在建立对象数组时,分别调用构造函数,对每个元素初始化

    Student stud[3]={
        Student(1001,18,87),
        Student(1002,19,66),
        Student(1003,18,94)
    };
    

三、对象指针

1. 指向对象的指针
  • 一个对象的起始地址就是对象的指针。可以定义一个指针变量,来存放对象的地址,这就是指向对象的指针变量

  • 定义指向类对象的指针变量的一般形式:

    类名 *对象指针名;

2.指向对象成员的指针
  • 存放对象的起始地址的指针变量是指向对象的指针变量;存放对象成员地址的指针变量是指向对象成员的指针变量;

  • 指向对象数据成员的指针:(定义方法同指向普通变量的指针变量)

    数据类型名 *指针变量名;

  • 指向对象成员函数的指针:

    • 定义指向普通函数的指针变量的一般形式:

      类型名 (*指针变量名)(参数表列);

    • 定义指向公用成员函数的指针变量的一般形式:

      数据类型名 (类名::*指针变量名)(参数表列);

    • 使指针变量指向一个公用成员函数的一般形式:

      指针变量名=&类名::成员函数名

3.指向当前对象的this指针
  • 在每一个成员函数中都包含一个特殊的指针,它的名字是固定的,叫做this。它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。

  • this指针是隐式使用(第三行)的,需要时也可以显式使用(第8行)如

    int Box::volume()
    {
        return(height*width*length);
    }
    //c++处理为
    //int Box::volume()
    //{
    //    return(this->height*this->width*this->length);
    //}
    
  • 可以用*this表示被调用的成员函数所在对象

     return((*this).height*(*this).width*(*this).length);//*this两边的括号不能省略
    

四、共用数据的保护

1.定义常对象
  • 定义常对象的一般形式:

    类名 const 对象名 [(实参表)];

    const 类名 对象名 [(实参表)];

  • 常对象必须要有初值,初始化之后不能再改变

  • 如果一个对象被声明为常对象,则通过该对象只能调用它的常成员函数,而不能调用该对象的普通成员函数(除了系统自动调用的隐式的构造函数和析构函数)。常成员函数是常对象对外的唯一接口。

  • 引用常对象中的数据成员,只需将该成员函数声明为const即可。

  • 常成员函数可以访问常对象中的数据成员,但不允许修改常对象中数据成员的值

  • 有时在编程时有要求,需要修改常对象中某个数据成员的值,只需将该成员声明为mutable,作为可变的数据成员即可。

    mutable int count;
    
2.定义常对象成员
  • 常数据成员

    只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对其赋值

  • 常成员函数

    • 如果将成员函数声明为常成员函数,则只能引用本类中的数据成员,而不能修改

    • 声明常成员函数的一般格式:

      类型名 函数名(参数表) const

  • 对数据成员的引用

数据成员非const的普通成员函数const成员函数
非const的普通数据成员可引用,可改变值可引用,不可改变值
const数据成员可引用,不可改变值可引用,不可改变值
const对象不允许引用可引用,不可改变值
  • 对常成员函数的利用:

    • 如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。

    • 如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用const成员函数引用数据成员,这样起到“双保险”的作用,切实保证了数据成员不被修改。

    • 如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问常对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,并确保在函数中不修改对象中的数据成员。

    • 不要误认为常对象中的成员函数都是常成员函数。常对象只保证其数据成员是常数据成员,其值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。

    • 常成员函数不能调用另一个非const成员函数。

    • 如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用const成员函数引用数据成员,这样起到“双保险”的作用,切实保证了数据成员不被修改。

    • (3)如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问常对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,并确保在函数中不修改对象中的数据成员。

    • 不要误认为常对象中的成员函数都是常成员函数。常对象只保证其数据成员是常数据成员,其值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。

    • 还有一点要指出:常成员函数不能调用另一个非const成员函数。

    • 3.4.3 指向对象的常指针

    • 将指针变量声明为const型,这样指针变量始终保持为初值,不能改变,即其指向不变。如

    • Time t1 ( 10,12,15) ,t2; //定义对象

    • Time *const ptr1; //const位置在指针变量名前面,指定ptr1是常指针变量

    • ptr1 =&t1; //ptr1指向对象t1,此后不能再改变指向

    • ptr1=&t2; //错误,ptr1不能改变指向

    • 定义指向对象的常指针变量的一般形式为

    • 类名*const指针变量名;

  • (1)如果在一个类中,有些数据成员的值允许改变,另一些数据成员的值不允许改变,则可以将一部分数据成员声明为const,以保证其值不被改变,可以用非const的成员函数引用这些数据成员的值,并修改非const数据成员的值。

  • (2)如果要求所有的数据成员的值都不允许改变,则可以将所有的数据成员声明为const,或将对象声明为const(常对象),然后用const成员函数引用数据成员,这样起到“双保险”的作用,切实保证了数据成员不被修改。

  • (3)如果已定义了一个常对象,只能调用其中的const成员函数,而不能调用非const成员函数(不论这些函数是否会修改对象中的数据)。这是为了保证数据的安全。如果需要访问常对象中的数据成员,可将常对象中所有成员函数都声明为const成员函数,并确保在函数中不修改对象中的数据成员。

  • 不要误认为常对象中的成员函数都是常成员函数。常对象只保证其数据成员是常数据成员,其值不被修改。如果在常对象中的成员函数未加const声明,编译系统把它作为非const成员函数处理。

  • 还有一点要指出:常成员函数不能调用另一个非const成员函数。

  • 3.4.3 指向对象的常指针

  • 将指针变量声明为const型,这样指针变量始终保持为初值,不能改变,即其指向不变。如

  • Time t1 ( 10,12,15) ,t2; //定义对象

  • Time *const ptr1; //const位置在指针变量名前面,指定ptr1是常指针变量

  • ptr1 =&t1; //ptr1指向对象t1,此后不能再改变指向

  • ptr1=&t2; //错误,ptr1不能改变指向

  • 定义指向对象的常指针变量的一般形式为

  • 类名*const指针变量名;

3 指向对象的常指针
  • 如果想要指针变量始终指向一个对象,可以将它指定为const型指针变量。

  • 将指针变量声明为const型,这样指针变量始终保持为初值,不能改变,即其指向不变,但可以改变其所指向对象(如t1)的值

    Time t1(10,12,15),t2; //定义对象
    Time *const ptr1; //const位置在指针变量名前面,指定ptr1是常指针变量
    ptr1=&t1; //ptr1指向对象t1,此后不能再改变指向
    ptr1=&t2; //错误,ptr1不能改变指向
    
  • 定义指向对象的常指针变量的一般形式为

    类名 *const 指针变量名;

  • 往往用常指针作为函数的形参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象

4.指向常对象的指针变量
  • 指向常变量的指针变量

    • 定义指向常变量的指针变量的一般形式:

      const 类型名 *指针变量名;

    • 如果一个变量已经被声明为常变量,则只能用指向常变量的指针指向它

    • 指向常变量的指针变量除了可以指向常变量外,还可以指向未被声明为const的变量,可以访问,但不能通过该指针变量改变该变量的值。

    • 如果函数的形参是指向普通(非const)变量的指针变量,实参只能用指向普通(非const)变量的指针,而不能用指向const变量的指针,这样,在执行函数的过程中可以改变形参指针变量所指向的变量(也就是实参指针所指向的变量)的值。

  • 用指针变量作形参时形参和实参的对应关系

形参实参合法不改变指针所指向的变量的值
指向非const型变量的指针非const变量的地址合法可以
指向非const型变量的指针const变量的地址非法——
指向const型变量的指针const变量的地址合法可以
指向const型变量的指针非const变量的地址合法可以
  • 指向常对象的指针变量的概念和使用是与此类似的,只要将“变量”换成“对象”即可

    • 如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的(指向非const型对象的)指针变量去指向它。

    • 如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过该指针变量来改变的。如

      Time t1(10,12,15); //定义Time类对象t1,它是非const型对象
      const Time *p=&t1; //定义p是指向常对象的指针变量,并指向t1
      t1.hour=18; //合法,t1不是常变量
      (*p).hour=18; //非法,不能通过指针变量改变t1的值
      
    • 如果希望在任何情况下t1的值都不能改变,则应把它定义为const型,如

      const Time t1(10,12,15);
      
      • 请注意指向常对象的指针变量与3.6.3节中介绍的指向对象的常指针变量在形式上和作用上的区别。

        Time *const p; //指向对象的常指针变量
        const Time *p; //指向常对象的指针变量
        
    • 指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。当希望在调用函数时对象的值不被修改,就应当把形参定义为指向常对象的指针变量,同时用对象的地址作实参(对象可以是const或非const型)。如果要求该对象不仅在调用函数过程中不被改变,而且要求它在程序执行过程中都不改,则应把它定义为const型。

    • 如果定义了一个指向常对象的指针变量,是不能通过它改变所指向的对象的值的,但是指针变量本身的值是可以改变的。如

      const Time *p=&t1; //定义指向常对象的指针变量p,并指向对象t1
      p=&t2; //p改为指向t2,合法.这时,同样不能通过指针变量p改变t2的值。
      
  • (1)如果一个对象已被声明为常对象,只能用指向常对象的指针变量指向它,而不能用一般的(指向非const型对象的)指针变量去指向它。(2)如果定义了一个指向常对象的指针变量,并使它指向一个非const的对象,则其指向的对象是不能通过该指针变量来改变的。如
      Time t1 ( 10,12,15) ; //定义Time类对象t1,它是非const型对象
      const Time *p=&t1; //定义p是指向常对象的指针变量,并指向t1
      t1. hour=18; //合法,t1不是常变量
      ( *p) . hour=18; //非法,不能通过指针变量改变t1的值
    如果希望在任何情况下t1的值都不能改变,则应把它定义为const型,如
      const Time t1(10,12,15);
    请注意指向常对象的指针变量与3.6.3节中介绍的指向对象的常指针变量在形式上和作用上的区别。
      Time *const p; //指向对象的常指针变量
      const Time *p; //指向常对象的指针变量
      (3)指向常对象的指针最常用于函数的形参,目的是在保护形参指针所指向的对象,使它在函数执行过程中不被修改。如

5.对象的常引用
  • 一个变量的引用就是变量的别名。对象的引用也是与此类似的,也可以把引用声明为const,即常引用。
#include<iostream>
using namespace std;
class Time
{
	public:
		Time(int,int,int);
		int hour;
		int minute;
		int sec;
};
Time::Time(int h,int m,int s)
{
	hour=h;
	minute=m;
	sec=s;
}
void fun(Time &t)
{
	t.hour=18;
}
int main()
{
	Time t1(10,13,56);
	fun(t1);
	cout<<t1.hour<<endl;
	return 0;
}


如果不希望在函数中修改实参t1的值,可以把fun函数的形参t声明为const(常引用),函数原型为

void fun(const Time &t);

则在函数中不能改变t的值,也就是不能改变其对应的实参t1的值。

在C++面向对象程序设计中,经常用常指针和常引用作函数参数。这样既能保证数据安全,使数据不能被随意修改,在调用函数时又不必建立实参的拷贝。

6.const型数据的小结

const型数据的含义

形式含义
Time const t1;t1是常对象,其值在任何情况下都不能改变
void Time::fun()const;fun是Time类中的常成员函数,可以引用,但不能修改本类中的数据成员
Time*const p;p是指向Time类对象的常指针变量,p的值(p的指向)不能改变
const Time*p;p是指向Time类常对象的指针变量,p指向的类对象的值不能通过p来改变
const Time&t1=t;t1是Time类对象t的引用,二者指向同一存储空间,t的值不能改变

五、对象的动态建立和释放

  • 可以用new运算符动态的分配内存,用delete运算符释放这些内存

  • 动态的建立对象:(如果我们已经定义了一个Time类)

    new Time;
    

    此时用户还无法访问这个对象,因为该对象没有对象名,用户也不知道它的地址,这种对象称为无名对象,它确实存在,但没有名字。

  • 用new运算符动态地分配内存后,将返回一个指向新对象的指针,即所分配的内存空间的起始地址。用户可通过这个地址来访问这个对象。这样就需要定义一个指向本类对象的指针变量来存放该地址,如

    Time *pt;      //定义一个指向Time类对象的指针变量pt
    pt=new Time;   //在pt中存放了新建对象的地址
    

    在程序中就可以通过pt访问这个新建的对象

    cout<<pt->hour;
    cout<<pt->fun;
    
  • C++还允许在执行new时对新建的对象初始化

    Time *pt=new Time(12,15,18);
    
  • 调用对象既可以通过对象名,也可以通过指针。用new建立的动态对象一般是用指针访问的,它主要应用于动态的数据结构,如链表。

  • 大多数C++编译系统都使new返回一个0指针值(NULL)。只要检测返回值是否为零,就可判断分配内存是否成功。C++标准提出,在执行new故障时,就抛出一个异常,用户可根据异常进行相应处理。

  • 不需要时,可以用delete运算符予以释放

    delete pt;
    

六、对象的赋值和复制

1.对象的赋值
  • 同样是使用赋值运算符”=“。

  • 一般形式

    对象名1=对象名2;

    (二者必须属于同一类)

  • 只对数据成员赋值,不对成员函数赋值

  • 类的数据成员中不能包括动态分配的数据

2.对象的复制
  • 一般形式:

    类名 对象2(对象1);

    类名 对象名1=对象名2;

    (第二种可以在一个语句中进行多个对象的复制)

    Time t2=t1,t3=t2;
    
  • 复制构造函数;

    //The copy constructor definition
    Box::Box(const Box& b)
    {
        height=b.height;
        width=b.width;
        length=b.length;
    }
    

    它也是构造函数,但只有一个参数,是本类的对象,而且采用对象的引用的形式(一般约定加const声明,使参数值不能改变。

    如果用户没有自己定义复制构造函数,系统会自动提供一个默认的只会简单复制类中每个数据成员的复制构造函数

  • 赋值和复制的区别

    前者是对已经存在的对象赋值,要先定义对象。后者是从无到有地建立一个对象,并使它与一个已有的对象完全相同。

  • 普通构造函数与复制构造函数的区别

    1.在形式上
    类名(形参表列); //普通构造函数的声明,如Box(int h,int w,int len);
    类名(类名&对象名);//复制构造函数的声明,如Box(Box &b);
    2.在建立对象时,实参类型不同。系统会根据实参的类型决定调用普通构造函数或复制构造函数。如

    Box box1(12,15,16);//实参为整数,调用普通构造函数
    Box box2(box1); //实参是对象名,调用复制构造函数
    

    3.在什么情况下被调用

    普通构造函数在程序中建立对象时被调用

    复制构造函数在用已有对象复制一个新对象时被调用,在以下3种情况下需要复制对象:

    ​ ①程序中需要新建立一个对象,并用另一个同类的对象对它初始化,如前面介绍的那样。
      ②当函数的参数为类的对象时。在调用函数时需要将实参对象完整地传递给形参,也就是需要建立一个实参的拷贝,这就是按实参复制一个形参,系统是通过调用复制构造函数来实现的,这样能保证形参具有和实参完全相同的值。如

    void fun(Box b) //形参是类的对象
      { }
    int main()
    {
        Box box1(12,15,18);
        fun(box1); //实参是类的对象,调用函数时将复制一个新对象b
        return 0;
    } 
    

    ​ ③函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时。此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。如

    Box f( ) //函数f的类型为Box类类型
      {Box box1 ( 12,15,18) ;
      return box1; //返回值是Box类的对象
      }
    int main()
    {
        Box box2;
        box2=f();
        return 0;
    }
    

七、不同对象间实现数据共享

如果想在同类的多个对象之间实现数据共享,也不要用全局对象,可以用静态的数据成员。

1 把数据成员定义为静态
  • 静态数据成员是一种特殊的数据成员。它以关键字static开头。例如

    class Box
    {
        public:
        int volume();
        private:
        static int height; //把height定义为静态的数据成员
        int width;
        int length;
    };
    

    如果希望同类的各对象中的数据成员的值是一样的,就可以把它定义为静态数据成员,它就可以为各对象所共用,所有对象都可以引用它。静态数据成员的值对所有对象都是一样的,如果改变它的值,则各对象最后这个数据成员的值都变了。

  • 静态数据成员不属于某一个对象,在为对象分配内存空间时不包括静态数据成员所占的空间,静态数据成员是在所有对象之外独立开辟空间,只要在类中指定了静态数据成员,即使不定义对象,也为其分配空间,它可以被引用。(如果只声明类而未定义对象,则类的一般数据成员是不占用内存空间的,只有在定义对象时,才为对象的数据成员分配空间)。在一个类可以有一个或多个静态数据成员。

  • 静态数据成员是在程序编译时被分配并预备空间的,在程序开始运行时就占分配的内存,到程序结束时才释放,所以静态数据成员不随对象的建立和撤销而变化。

  • 公用的静态数据成员可以进行初始化,但只能在类体外进行初始化

    int Box::height=10;
    

    一般形式:

    数据类型 类名::静态数据成员名=初值;

    (只在类体中声明静态数据成员时加static,不必在初始化语句中加。)

  • 静态数据成员既可通过对象名引用,也可通过类名引用。

    #include<iostream>
    using namespace std;
    class Box
    {
    	public:
    		Box(int,int);
    		int volume();
    		static int height;
    		int width;
    		int length;
    };
    Box::Box(int w,int len)//通过构造函数对width和length赋初值
    {
    	width=w;
    	length=len;
    }
    int Box::height=10;
    int Box::volume()
    {
    	return(height*width*length);
    }
    int main()
    {
    	Box a(15,20),b(20,30);//建立两个对象 
    	cout<<a.height<<endl;//通过对象a引用静态数据成员 
    	cout<<b.height<<endl;//通过对象b引用静态数据成员
    	cout<<Box::height<<endl;//通过类名引用静态数据成员
    	cout<<a.volume()<<endl;//
    	return 0;
    }
    

    如果静态数据成员被定义为私有的,则不能在类外直接引用,而必须通过公用的成员函数

2.用静态成员函数访问静态数据成员
  • 成员函数也可以定义为静态的,在类中声明函数的前面加static就成了静态成员函数。如

    static int volume();
    
  • 和静态数据成员一样,静态成员函数是类的一部分而不是对象的一部分。如果要在类外调用公用的静态成员函数,要用类名和域运算符“::”。如

    Box::volume();
    

    实际上也允许通过对象名调用静态成员函数,如

    a. volume();
    

    但这并不意味着此函数是属于对象a的,而只是用a的类型而已。

  • 与静态数据成员不同,静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员。前面曾指出:当调用一个对象的成员函数(非静态成员函数)时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数并不属于某一对象,它与任何对象都无关,因此静态成员函数没有this指针。既然它没有指向某一对象,就无法对一个对象中的非静态成员进行默认访问(即在引用数据成员时不指定对象名)。

  • 静态成员函数可以直接引用本类中的静态成员,因为静态成员同样是属于类的,可以直接引用。在C++程序中,静态成员函数主要用来访问静态数据成员,而不访问非静态成员。
    但是,并不是绝对不能引用本类中的非静态成员,只是不能进行默认访问,因为无法知道应该去找哪个对象。如果一定要引用本类的非静态成员,应该加对象名和成员运算符“.”。如

    cout<<a. width<<endl;//引用本类对象a中的非静态成员
    

    假设a已定义为Box类对象,且在当前作用域内有效,则此语句合法。

    #include<iostream>
    using namespace std;
    class Student //定义Student类
    {
    	public:
    		Student(int n,int a,float s):num(n),age(a),score(s){}//定义构造函数
    		void total();//声明成员函数
    		static float average(); //声明静态成员函数
    	private:
    		int num;
    		int age;
    		float score;
    		static float sum;//静态数据成员sum(总分)
    		static int count;//静态数据成员count(计数)
    };
    void Student::total() //定义非静态成员函数
    {
    	sum+=score;//累加总分
    	count++;//累计已统计的人数
    }
    float Student::average()//定义静态成员函数
    {
    	return(sum/count);
    }
    float Student::sum=0; //对静态数据成员初始化
    int Student::count=0; //对静态数据成员初始化
    int main()
    {
    	Student stud[3]={//定义对象数组并初始化
    		Student(1001,18,70),
    		Student(1002,19,78),
    		Student(1005,20,98)
    	} ;
    	int n;
    	cout<<"please input the numder of students:";
    	cin>>n;//输入需要求前面多少名学生的平均成绩 
    	for(int i=0;i<n;i++)//调用3次total函数
    	   stud[i].total();
    	cout<<"the average score of "<<n<<" students is "<<Student::average()<<endl;//调用静态成员函数
    	return 0;
    }
    

    公用的成员函数可以引用本对象中的一般数据成员(非静态数据成员),也可以引用类中的静态数据成员。一般只用静态成员函数引用静态数据成员,而不非引用静态数据成员。

八、允许访问私有数据的“朋友”

1.可以访问私有数据的友元函数

如果在类外定义了一个函数,在类体中用friend进行声明,此函数就称为本类的友元函数。友元函数可以访问这个类的私有成员。

(1)将普通函数声明为友元函数
#include<iostream>
using namespace std;
class Time
{
	public:
		Time(int,int,int);
		friend void display(Time&);
	private:
		int hour;
		int minute;
		int sec;
};
Time::Time(int h,int m,int s)
{
	hour=h;
	minute=m;
	sec=s;
}
void display(Time& t)
{
	cout<<t.hour<<":"<<t.minute<<":"<<t.sec<<endl;
}
int main()
{
	Time t1(10,13,56);
	display(t1);
	return 0;
}

引用私有数据时记得加上对象名。

(2)用友元函数访问私有数据

友元函数还可以是另一个类中的成员函数

#include<iostream>
using namespace std;
class Date;
class Time
{
	public:
		Time(int,int,int);
		void display(Date&);
	private:
		int hour;
		int minute;
		int sec;
};
class Date
{
	public:
		Date(int,int,int);
		friend void Time::display(Date&);
	private:
		int month;
		int day;
		int year;
};

Time::Time(int h,int m,int s)
{
	hour=h;
	minute=m;
	sec=s;
}
void Time::display(Date& d)
{
	cout<<d.month<<"/"<<d.day<<"/"<<d.year<<endl;
	cout<<hour<<":"<<minute<<":"<<sec<<endl;
}
Date::Date(int m,int d,int y)
{
	month=m;
	day=d;
	year=y;
}
int main()
{
	Time t1(10,13,56);
	Date d1(12,25,2004);
	t1.display(d1);
	return 0;
}

C++允许对类作提前引用声明,即在正式声明一个类之前,先声明一个类名,表示此类在稍后声明。只有在正式声明一个类之后才能用它去定义类对象。在对一个类作了提前引用声明后,可以用该类的名字去定义指向该类型对象的指针变量或对象的引用。

一个函数可以被多个类声明为”朋友“。

2.可以访问私有数据的友元类

声明友元类的一般形式:

friend 类名;

  • 友元类的关系是单向的,不能相互访问对方的私有数据。
  • 友元的关系不能传递

九、类模板

class Compare_int
{
    public:
        Compare_int(int a,int b){x=a;y=b;}
        int max(){return (x>y)?x:y;}
        int min(){return (x<y)?x:y;}
    private:
        int x,y;
};
class Compare_float
{
    public:
        Compare_float(float a,float b){x=a;y=b;}
        float max(){return (x>y)?x:y;}
        float min(){return (x<y)?x:y;}
    private:
        float x,y;
};
template<class numtype>
class Compare
{
    public:
        Compare(numtype a,numtype b){x=a;y=b;}
        numtype max(){return (x>y)?x:y;}
        numtype min(){return (x<y)?x:y;}
    private:
        numtype x,y;
};
#include<iostream>
using namespace std;
template<class numtype>
class Compare
{
    public:
        Compare(numtype a,numtype b){x=a;y=b;}
        numtype max();
        numtype min(){return (x<y)?x:y;}
    private:
        numtype x,y;
};
template<class numtype>
numtype Compare<numtype>::max()
{return (x>y)?x:y;}
int main()
{
	Compare<int>cmp1(3,7);
	cout<<cmp1.max()<<endl;
	cout<<cmp1.min()<<endl;
	Compare<float>cmp2(45.78,93.6);
	cout<<cmp2.max()<<endl;
	cout<<cmp2.min()<<endl;
	Compare<char>cmp3('a','A');
	cout<<cmp3.max()<<endl;
	cout<<cmp3.min()<<endl;
	return 0;
}
  • 声明类模板时要加一行

    template< class 类型参数名>

  • 用类模板去定义对象的方法

    类模板名<实际类型名>对象名(参数表);

  • 在类模板外定义定义成员函数

    template<class 虚拟类型参数>

    函数类型 类模板名<虚拟类型参数>::成员函数名(函数形参表){……}

  • 类模板的类型参数可以有一个或多个,每个类型前都要加class

  • 模板可以有层次,一个类模板可以作为基类,派生出派生类模板。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值