Giskard

(三)使用类和对象

2019-01-27

当对象被创建时,构造函数被自动执行,当对象消亡前,析构函数被自动执行。

构造函数的功能是在定义对象时被编译系统自动调用来创建对象并初始化对象。

Time();           //无参构造函数的声明
Time::Time()     //无参构造函数的实现
{
      Hour=0;
    Minute=0;
    Second=0;
}

事实上,如果在类中没有显式定义构造函数,那么编译系统就会自动生成一个默认形式的构造函数,这个构造函数的功能仅用于创建对象

在定义类时,只要显式定义了一个类的构造函数,则编译器就不产生默认的构造函数

不存在没有构造函数的对象!

class A
{
    float x,y;
public:
    A(……)     {     ……      }  //显式定义了带参数的构造函数,不产生默认的构造函数
    void Print( ){   cout<<x<<'\t'<<y<<endl;  }
};
int main( )
{      
    A  a1;                 //错误!!定义时,没有构造函数可供调用
    return 0;
}

用初始化参数列表

Box::Box(int h,int w,int len):height(h),width(w),length(len)
{

}

带默认参数的构造函数

Box(int w=10,int h=10,int len=10); 
Box::Box(int w,int h,int len)
{
    height=h;
    width=w;
    length=len;
}

在声明构造函数时,形参名可以省略为Box(int,int,int)

~Box();        //析构函数的声明
Box::~Box()    //析构函数的实现
{
    cout<<“This is destructor!<<endl;
}

析构函数没有参数,因此不能重载。一个类中只能定义一个析构函数。

构造析构

复制(拷贝)构造函数

对象的赋值是对一个已经存在的对象赋值,因此必须先定义被赋值的对象,才能进行赋值。

对象的复制是从无到有地建立一个新对象,并使它与一个已有的对象完全相同

1、对象的赋值

a2=a1;   //同类型的对象之间可以整体赋值,相当于数据成员间相互赋值

2、对象的复制

#include<iostream> 
using namespace std;
class Object
{
public:
    Object(int a,int b)  //构造函数
    {
        x=a;y=b;
        cout<<"constructor."<<endl;
    }
    Object(const Object& p) //复制构造函数
    {
        x=p.x;y=p.y;
        cout<<"copy constructor."<<endl;
    }
    ~Object()             //析构函数
    {
        cout<<"destructor."<<endl;
    }
    int add(){return x+y;} //成员函数
private:
    int x,y;
};

int main()
{
    Object p1(2,3);
    //系统调用构造函数创建并初始化对象p1
    Object p2(p1);
    //系统调用复制构造函数创建对象p2
    //并用对象p1初始化对象p2
    cout<<"in p2,x+y="<<p2.add()<<endl;    
    //对象p2调用公有成员函数
    return 0;
}

如果程序员没有显式定义复制构造函数,编译系统就会自动生成一个默认形式的复制构造函数.

三种构造函数
Box();           //无参构造函数
Box(int h,int w ,int len):height(h),width(w),length(len){}
                //带参构造函数
Box(const Box& b);//复制构造函数


Box box1(15,30,25);//实参为整数            
Box box2(box1);//实参是对象名

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

复制构造函数

1、在用已有对象复制一个新对象时被调用

2、当函数的参数为类对象时,在调用函数时需要将实参对象完整地传递给形参:

void fun(Box b) //形参是类的对象
{}
int main( )
{
    Box box1(12,15,18);
    fun(box1);           
    return 0;
}

3、当函数的返回值是类的对象Box,在函数调用完毕将返回值带回函数调用处时:

Box f( )
{   
    Box box1(10,20,30);
    return box1; 
}
int main( )
{
    Box box2;
    box2=f( ); 
    return 0;
}

4、动态创建对象,使其为a

Object a;
Object* p = new Object(a);

每个类都必须有一个复制构造函数。如果类中没有显式定义复制构造函数,则编译系统自动生成一个默认形式的复制构造函数,作为该类的公有成员。

静态成员

静态成员是C++提供的解决同一个类的不同对象之间数据和函数共享问题的机制。

类的静态成员分为静态数据成员和静态成员函数。

静态成员是类的所有对象共享的成员,而不是某个对象的成员,它在对象中不占存储空间,是属于整个类的成员。

静态数据成员不随对象的建立而分配空间,也不随对象的撤销而释放。它是在程序编译时分配空间,到程序结束时才释放空间。

静态成员

静态数据成员是在所有对象之外单独开辟空间。

只要在类中定义了静态数据成员,即使不定义对象,也为静态数据成员分配空间,它可以被访问。

在一个类中可以有一个或多个静态数据成员,所有的对象共享这些静态数据成员,都可以访问它。

不能用参数初始化表对静态数据成员初始化,也不能在构造函数体内初始化,静态数据成员只能在类体外进行初始化。

int A::x=0; //在A类外给静态数据成员分配空间和初始化   

静态成员2

在类的成员函数中可以直接访问该类的静态数据成员,而不必使用成员访问运算符或作用域运算符。

在类外必须使用成员访问运算符或作用域运算符访问公有静态数据成员。

Student stud1(“Wang ling”);
cout <<stud1.total<<endl;

Student stud2(“Liu xiao”);
cout <<Student::total<<endl;

静态数据成员与全局变量一样都是静态分配存储空间的,但全局变量在程序中的任何位置都可以访问它,而静态数据成员受到访问权限的约束。必须是public权限时,才可能在类外进行访问。

静态成员函数就是使用static关键字声明的成员函数。

和静态数据成员一样,静态成员函数是类的一部分,而不是对象的一部分。

静态成员函数的作用是为了能处理静态数据成员。

静态成员函数没有this 指针。

静态成员函数可以直接访问该类的静态成员,但不能直接访问类中的非静态成员。

如果静态成员函数中要使用非静态成员时,必须通过参数传递方式得到对象名,然后可以通过对象名来访问非静态成员

静态成员3

静态成员4

对象指针

与基本数据类型的变量一样,每一个对象在创建之后都会在内存中占有一定的空间。

因此,既可以通过对象名访问对象,也可以通过对象的起始地址来访问一个对象,即对象指针。

对象指针就是用于存放对象数据起始地址的变量。

Student s1;
Student *p=&s1;
p->display();
(*p).display();

动态创建对象

Box *p1=new Box;                  //定义指向Box堆对象的指针变量p1
delete p1;  

this指针是一个特殊的隐含指针,它隐含于每一个成员函数(静态成员函数除外)中,也就是说,每个成员函数都有一个this指针参数。

this指针指向调用该函数的对象,即this指针的值是当前被调用的成员函数所在的对象的起始地址。

int Box::volume()
{
      return(height*width*length);
}

隐含调用,相当于都加上this

this指针一般用于返回当前对象自身。

同样也可以使用*this来标识调用该成员函数的当前对象。

静态成员中不能访问this指针,因为静态成员函数不从属于任何对象。

this指针

类的成员可能是一些变量、函数或者对象等,因此可以将它们的地址存放到一个指针变量中。这样,就可以使指针直接指向对象的成员,进而可以通过这些指针访问对象的成员。

这样的指针称为成员指针,或者说是指向对象的成员的指针变量。

指向非静态成员函数的指针
void (*p) ();
#include <iostream>    
using namespace std;
class Point 
{
public: 
    Point(int xx=0,int yy=0)
    { X=xx; Y=yy; }
    int GetX(){return X;}
    int GetY(){return Y;}
private:
    int X,Y;
};

int main()
{
    Point A(4,5);//声明对象A
    Point *p1=&A;
    //声明对象指针并初始化
    int (Point::*p)()= Point::GetX;
    //声明成员函数指针并初始化
    cout<<(A.*p)(); 
    //使用成员函数指针访问成员函数
    return 0;
}
指向类的静态成员的指针

对类的静态成员的访问是不依赖于对象的,因此可以用普通的指针来指向和访问静态成员。

int *p=&Point::count;

void (*p)()=Point::GetC;
p();

对象的引用

避免通过值来传递对象,而是通过引用来传递。

Student& returnS(const Student& s)
{return s;}

参数传递的是引用,没有构造函数或析构函数被调用,节约了系统资源,提高了运行效率。

常对象是其数据成员值在对象的整个生存期间内不能被改变的对象。即被const修饰

Time const t1(1,2);   //也可以const Time t1(1,2)]

常对象的所有数据成员都是常量,不能改变。因此,常对象必须初始化。

不能通过常对象调用普通的成员函数,可以调用常成员函数。

void get_time() const;

常成员函数只能访问数据成员,不能修改数据成员的值。

如果要修改常对象中某个数据成员的值,可以将数据成员声明为mutable,这样就可以用声明为const的成员函数来修改它的值。mutable int count

常数据成员

在任何函数中都不能对常数据成员赋值。

只能通过构造函数的参数初始化表对常数据成员进行初始化。

const int Hour;
Time::Time(int h):Hour(h){}
常成员函数

常成员函数不能更新对象的数据成员,也不能调用该类中的非const成员函数。

通过常对象只能调用它的常成员函数,而不能调用其他成员函数。

常对象中的成员函数不是常成员函数,除非成员函数有const修饰。

const关键字可以用于对重载函数的区分。

const成员

指向对象的常指针:指针变量声明为const型并初始化,指针本身的值不能改变,即其指向不能改变。

Time t1(10,12,15),t2;
Time *const ptr1=&t1;//ptr1是指向t1对象的常指针
ptr1=&t2;   //错误,ptr1不能改变指向

指向常对象的指针:可以指向非const型的对象,此时不能通过指针改变该对象的值;但是指针本身的值可以改变。

Time t1(10,12,15);
const Time *p=&t1;//p是指向常对象的指针,并指向t1对象
(*p).hour=18;   //错误,不能通过指针改变t1的值
t1.hour=18;//正确,t1不是常对象
p=&t2;//正确,p改为指向t2

指向常对象的指针可以指向const和非const型的对象,而指向非const型对象的指针只能指向非const的对象。

常引用所引用的对象不能被更新。

void fun(const Time &t);

对象数组:在建立数组时,需要调用构造函数。数组中有多少个元素,就调用多少次构造函数。

用已存在的类对象作为另一个类的成员,这个成员成为对象成员或者子对象。

class A
{
    int i;
};
class B
{
    int j;
    A a;  //对象成员
};

对象成员的初始化有两种情况,一种是在构造函数成员初始化表中被初始化,一种是在函数体内被初始化。

class B
{ 
public:
     B(const A &a);
private:
    A m_a;
};

B::B(const A &a):m_a(a)
{}

B::B(const A &a)
{
    m_a=a;}

一般来说,在类中出现了对象成员时,创建本类对象既要对本类的数据成员进行初始化,又要对对象成员进行初始化。

先调用对象成员的构造函数,再调用本类的构造函数。析构函数的调用顺序刚好相反。

如果调用本类默认形式的构造函数,那么也只能调用对象成员的默认形式的构造函数。

类模板

template<class T>
class Compare
{ 
public:
    Compare(T a, T b)
    {x=a;y=b;}
    T max()
    {return(x>y)?x:y;}
private:
    T x,y;
};

int main()
{   
    Compare<int> cmp1(3,7); 
// int Compare类,cmp1为该类的一个对象
    cout << cmp1.max()<<endl; 
    Compare<double> cmp2(4.3,9.6); 
// double Compare类,cmp2为该类的一个对象
    cout << cmp2.max()<<endl;
    Compare<char> cmp3(‘a’,’A’); 
// char Compare类,cmp3为该类的一个对象
    cout << cmp3.max()<<endl;    
    return 0;
}

如果说类是对象的抽象,对象是类的实例,则类模板是类的抽象,类是类模板的实例。

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

template<class T1,class T2>
class A{};

在定义对象时分别代入实际的类型名

A<int,double> obj;

使用默认参数的类模板

template <class T=int>
Tags: C/C++