关闭

类的成员函数

230人阅读 评论(0) 收藏 举报
分类:
类的默认成员函数:
构造函数,拷贝构造函数,析构函数,赋值操作符重载,取地址操作符重载,const修饰的取地址操作符重载
1.构造函数:
一个特殊的成员函数,名字与类名相同,创建类类型对象时,由编译   
自动调用,在对象的生命周期内只且只调用一次,以保证每个数据成员都有一个合适的初始值。
class Time
{
public:
                 //类Time的构造函数
                 //函数名为类名,无返回值
                Time( int hour,int minute,int second)
                {//函数体可以为空}
private:
                 //数据成员
                 int _hour;
                 int _minute;
                 int _second;
};
构造函数特性:
 1、函数名与类名相同。
  2、没有返回值。
  3、有初始化列表(可以不用)。
  4、新对象被创建,由编译器自动调用且在对象的生命期内仅调用一次。
  5、构造函数可以重载,实参决定了调用那个构造函数。但是不同的构造函数之间必须在参数的数量或参数的类型上有所区别。
  6、如果没有显示定义时,编译器会提供一个默认的构造函数。
  7、无参构造函数和带有缺省值得构造函数都认为是缺省构造函数,并且缺省构造函数只能有一个。
  8、构造函数不能用const来修饰,为什么呢?
因为当我们创建一个类的const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性,这样的话,构造函数在const对象的构造过程中可以向其写值。
构造函数的作用:1.构建对象
               2.初始化对象的数据成员 
               3.类型转换

Date(int year, int month)
 {
}
 Date( int year, int month, int day)
{
}
这两个函数构成重载,函数参数也不一样。
当对象被默认初始化或值初始化时自动执行默认构造函数。默认构造函数在以下情况下发生:
  • 当我们在块作用域内不使用任何初始值定义一个非静态变量
  • 当一个类的本身含有类类型的成员且使用合成的默认构造函数时
  • 当类类型的成员没有在构造函数初始值列表中显式地初始化时
初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个数据成员后面跟一个放在园括号中的初始化式。
每个成员在初始化列表中只能出现一次。
初始化列表仅用于初始化数据成员,并不指定这些数据成员的初始化顺序,
     数据成员在类中定义顺序就是在参数列表中的初始化顺序
尽量避免使用成员初始化成员,成员的初始化顺序最好和成员的定义顺序保持一致
构造函数的初始值在某些情况下必不可少:如果成员是const、引用、或者属于某种未提供默认构造函数的类类型
初始化列表:
Date(int year, int month, int day)
          :_year(year)
           , _month(month)
           , _day(day)
构造函数初始值的顺序与成员声明的顺序保持一致。
默认构造函数】
   类如果没有显式定义构造函数时,编译器会合成一个默认的构造函数,该构造函数中什么工作都不做只要显式定义了,即使该构造函数什么也不做,编译器也不会为该类合成默认的构造函数。编译器生成的默认构造函数使用与变量初始化相同的规则来初始化成员,具有类类型的成员通过运行各自的默认构造函数来进行初始化。内置和符合类型的成员如指针、数组,只对定义在全局作用域中的对象初始化,当对象定义在局部作用域时,内置和符合类型的成员不进行初始化。在某些情况下,默认构造函数是由编译器隐式使用的。
explicit:
  1. explicit只对一个实参的构造函数有效,需要多个实参的构造函数不能用于执行隐式转换,所以无须将这些构造函数指定为explicit的
  2. 只能在类内声明构造函数时使用explicit关键字
  3. explicit构造函数只能用于直接初始化。
explicit只允许出现在类内的构造函数声明处。
当我们执行拷贝形式的初始化时,会发生隐式转换,此时我们只能使用直接初始化而不能使用explicit构造函数。

2.拷贝构造函数:第一个参数必须是一个引用类型(自身类类型的引用),额外参数都有默认值。
只有单个形参,而且该形参是对本类类型对象的引用(常用const修饰),这样的构造函数称为拷贝构造函数。
拷贝构造函数是特殊的构造函数创建对象时使用已存在的同类对象来进行初始化,由编译器自动调用。
              Date(int year, int month, int day)
                                :_year(year)
                                , _month(month)
                                , _day(day)
                {
                                cout << "Date():" << this << endl;
                                 this->_year = year;
                                 this->_month = month;
                                 this->_day = day;
                }

                Date( const Date& d)//拷贝构造函数
                                :_year(d._year)
                                , _month(d._month)
                                , _day(d._day)
                {
                                cout << "Date(const Date& d)" << this << endl;
                }
大多数类应该定义了拷贝构造函数和拷贝复制运算符,对于某些类来说,这些操作没有合理意义。定义类时必须采取某些机制阻止拷贝或赋值。
  【特征】
   1、它是构造函数的重载。
   2、它的参数必须使用同类型对象的引用传递。
   3、如果没有显式定义,系统会自动合成一个默认的拷贝构造函数。默认的拷贝构造函数会依次拷贝类的数据成员完成初始化。
即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数。合成拷贝构造函数用来阻止 我们拷贝该类类型的对象。
如何拷贝:
对类类型的成员,使用拷贝构造函数拷贝,内置类型的成员直接拷贝。合成拷贝构造函数会逐元素地拷贝一个数组的成员。如果数组元素是类类型,则使用元素的拷贝构造函数进行拷贝。
拷贝初始化:
1.直接初始化,实际上要求编译器使用普通的函数匹配来选择与我们提供的参数最匹配的构造函数。
2.拷贝初始化,我们要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要还需要进行类型转化。拷贝初始化使用拷贝构造函数完成。
拷贝初始化过程中,编译器可以(但不是必须)跳过拷贝构造函数,直接创建对象。
string null_book="9-999-99999-9";//拷贝初始化
string null_book("9-999-99999-9");//编译器略过拷贝构造函数

3.析构函数
与构造函数功能相反,在对象被销毁时,由编译器自动调用,完成类的一些资源清理和收尾工作。
析构函数是类的一个成员函数,无返回值,不接受参数。不能重载,对一个给定类,有且只有一个析构函数。
对象生命周期结束时,C++编译系统系统自动调用析构函数。
注意析构函数体内并不是删除对象,而是做一些清理工作。
class Date
{
public:
   ~Date();//析构函数,无参,无返回值


};
析构函数的调用:
class CArray
{
public:
        CArray(size_t capacity)
                : _capacity(capacity)
        {
                _pData = ( int*)malloc(capacity*sizeof (int));
                _size = 0;
        }

        ~CArray()
        {
               //按成员初始化逆序销毁,在栈上创建,先入后出
                 if (NULL != _pData)
                {
                        free(_pData);
                        _pData = NULL;
                }
                _size = 0;
                _capacity = 0;
        }

private:
         int* _pData;
        size_t _size;
        size_t _capacity;
};
构造函数有一个初始化部分和一个函数体,析构函数也有一个函数体和析构部分
析构函数释放对象生存期分配的所有资源。
内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。
合成析构函数:
当一个类未定义自己的析构函数时,编译器会为他定义一个合成析构函数。
析构函数体自身不直接销毁成员,成员在析构函数体后隐含的析构阶段销毁,整个对象销毁阶段,函数体作为成员销毁步骤之外的另一部分而进行的。
4.赋值操作符重载
int i=0,j=0;//初始化而非赋值
const int ci=i;//初始化而非赋值
【操作符重载】

重载操作符是具有特殊函数名的函数,关键字operator后面接需要定义的操作符符号。
操作符重载也是一个函数,具有返回值和形参表。它的形参数目与操作符的操作数目相同,一元运算符有一个参数,二元有两个参数。二元运算符,左侧运算对象传递给第一个参数,右侧运算对象传递给第二个参数。
函数调用操作符可以接受任意数目的操作数。
   
注意:
1、不能通过连接其他符号来创建新的操作符:比如operator@;
   void operator @(){}
只能重载已有的运算符,不能自己发明新的运算符号。operator**不能用来执行幂操作。
2、重载操作符必须有一个类类型或者枚举类型的操作数
   
    int operator +(const int _iNum1 , const int _iNum2 )   // 报错,不能为int重定义内置的运算符
   {
       return ( _iNum1 + _iNum2);
   }
   typedef enum TEST {one ,two ,three };
   int operator+(const int _iNum1 , const TEST _test )
   {
        return _iNum1;
   }
3、用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  逻辑运算符和关系运算符应该返回bool,算数运算符应该返回一个类类型的值,赋值运算符和复合运算符则应返回运算对象的一个引用。
5、不在具备短求值特性
   重载操作符不能保证操作符的求值顺序,在重载&&和||中,对每个操作数
   都要进行求值,而且对操作数的求值顺序不能做规定,因此:重载&&、
   ||和逗号操作符不是好的做法。
6.作为类成员的重载函数,其形参看起来比操作数数目少1
   成员函数的操作符有一个默认的形参this,限定为第一个形参。
7、一般将算术操作符定义为非成员函数,将赋值运算符定义成员函数
8、操作符定义为非类的成员函数时,一般将其定义为类的友元
9、== 和 != 操作符一般要成对重载
10、下标操作符[]一个非const成员并返回引用,一个是const成员并返回引用
11、解引用操作符*和->操作符,不显示任何参数
13、自增自减操作符
    前置式++/--必须返回被增量或者减量的引用
    后缀式操作符必须返回旧值,并且应该是值返回而不是引用返回
                 //前置++
                
                 Complex& operator++()
                {
                       _real++;
                       _image++;
                       return *this ;
                }

                 //后置++
                 Complex operator++(int )
                {
                       Complex temp(*this );
                        _real++;
                        _image++;
                       return temp;
                       //_real++;
                       //_image++;
                       //return *this ;

                }
14、输入操作符>>和输出操作符<<必须定义为类的友元函数
           重载输出操作符<<:第一个形参是非常量ostream对象的引用,非常量因为向流写入内容会改变状态,该形参是引用因为无法直接复制ostream对象;第二个参数常量引用,我们打印的类类型,该形参是引用我们希望避免复制实参,是常量因为打印不会改变对象内容。
     ostream &operator<<(ostream &os,const Date& d)
    {
        os<< d._year <<"-"<<d._month<<"-"<<d._day;
        return os;
    }
    重载输入操作符>>:第一个形参读取流的引用,第二个形参是读取(非常量)对象的引用,非常量是因为我们将数据读入对象中。返回给定流的引用。
   istream &operator>>(istream &is,Date &d)
  {
     is>>d._year>>"-">>d._month>>"-">>d._day;
     return is;
  }
>>流含有错误类型的数据时读取操作可能失败。
>>当读取操作到达文件末尾时或者遇到输入流其他错误也会失败。
*运算符定义为成员函数还是普通的非成员函数区别:
1.赋值(=),下标([ ]),调用(())和成员访问箭头(->)运算符必须是成员。
2.复合赋值运算符一般来说是成员,但非必需,与赋值运算符不同。
3.改变对象状态的运算符或者给定类型密切相关的运算符,递增,递减,解引用运算符,应该是成员。
4.具有对称性的运算符可能转换任意一端的对象,算术,相等性 ,关系,位运算符等,普通的非成员函数。
【建议】
   使用重载操作符,可以令程序更自然、更直观,而滥用操作符重载会使得类难以理解,在实践中很少发生明显的操作符重载滥用。但有些程序员会定义operator+来执行减法操作,当一个重载操作符不明确时,给操作符取一个名字更好,对于很少用的操作,使用命名函数通常比用操作符好,如果不是普通操作,没有必要为简洁而用操作符。
类的静态成员(static)
静态成员函数(static)不与任何对象绑定在一起,不包含this指针。作为结果,静态成员函数不能声明成const,也不能在static函数体内使用this指针。
static不属于具体对象,但是使用类的对象、引用或者指针来访问静态成员。
定义静态成员:
可以在内部定义也可以在外部定义,外部定义时不能重复static关键字,只出现在内部声明语句。
必须在类的外部定义和初始化每个静态成员。一个静态数据成员只能定义一次。
定义静态数据成员的方式和类外部定义成员函数差不多。指明类型名、类名、作用域运算符和成员自己的名字。
class Date
{
  public:
      static int _count;
};
int Date::_count = 0;
静态成员类内初始化:
通常,类的静态成员不应该在类的内部初始化。即使一个常量静态数据成员在类内部被初始化,通常情况下也应该在类外部定义一下该成员。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:4329次
    • 积分:267
    • 等级:
    • 排名:千里之外
    • 原创:23篇
    • 转载:3篇
    • 译文:0篇
    • 评论:0条
    文章分类