【学习C++】学习C++ -> 类的特殊数据成员

来源:互联网 发布:2017淘宝网店数量 编辑:程序博客网 时间:2024/06/01 15:54

学习C++ -> 类的特殊数据成员

转载 http://www.cnblogs.com/mr-wid/archive/2013/02/23/2923842.html

    在构造函数一节的介绍中, 我们已经提到了在C++中有几类特殊的数据成员不能使用构造函数进行初始化, 他们有自己的初始化方式, 下面就具体介绍下这几种数据成员。
    
    
一、const 数据成员
    const 类型的数据成员具有只读属性, 在构造函数内进行初始化是不允许的, 例如以下代码:
    

复制代码
 1     #include <iostream> 2  3     using namespace std; 4  5     class A 6     { 7         public: 8             A(int n = 0) { num = n; }       //在构造函数内使用 n 初始化const型数据成员 num 9             void showNum() { cout<<"num = "<<num<<endl; }10         private:11             const int num;            //const 型数据成员12     };13 14     int main()15     {16         A a(10);17         a.showNum();18 19         return 0;20     }
复制代码


    尝试编译运行时报错:

        error: uninitialized member 'A::num' with 'const' type 'const int'        error: assignment of read-only data-member 'A::num'

   
    要初始化 const 型的数据成员, 必须通过初始化表达式来进行初始化, 一经初始化, 其值确定, 不能再被修改, 通过初始化表达式形式如下:

复制代码
 1     #include <iostream> 2  3     using namespace std; 4  5     class A 6     { 7         public: 8             A(int n = 0):num(n) { }       //使用初始化表达式对const型数据成员进行初始化 9             void showNum() { cout<<"num = "<<num<<endl; }10         private:11             const int num;12     };13 14     int main()15     {16         A a(10);17         a.showNum();18 19         return 0;20     }
复制代码

 

 



    

二、引用类型的成员
    对于引用类型的成员, 同样只能通过初始化表达式进行初始化, 错误的示例这里不再举出, 一个通过初始化表对引用类型初始化的示例:
    

复制代码
 1     #include <iostream> 2  3     using namespace std; 4  5     class A 6     { 7         public: 8             A(int &rn):rNum(rn) { }       //使用初始化表达式对引用型数据成员进行初始化 9             void showNum() { cout<<"num = "<<rNum<<endl; }10         private:11             int &rNum;      //引用类型12     };13 14     int main()15     {16         int x = 10, &rx = x;17         A a(rx);        //传递引用类型18         a.showNum();19 20         return 0;21     }
复制代码

 

 


    
    
    
三、类对象成员
    类的对象可以作为另一个类的数据成员, 在定义类时, 与普通的数据成员一样, 在声明时无法进行初始化, 例如有个Line类中有两个 Point 类的对象作为数据成员, 在声明时

    private:        int xPos(10, 20);        int yPos(100, 200);


    这样是错误的, 所以在初始化类对象成员时也需要一定的方法。
    
    情况一: 
        还以 Line 类和 Point 类为例, 当 Point 类中的数据成员全部为 public 时, 可以在 Line 类的构造函数内完成初始化, 像以下示例的情况:

复制代码
 1         #include <iostream> 2  3         using namespace std; 4  5         class Point 6         { 7             public: 8                 void printPoint() { cout<<"("<<xPos<<", "<<yPos<<")\n"; } 9                 int xPos;   //public类型的 xPos 和 yPos10                 int yPos;11         };12 13         class Line14         {15             public:16                 Line(int x1, int y1, int x2, int y2)17                 {18                     M.xPos = x1; M.yPos = y1;       //在构造函数内完成类对象的初始化19                     N.xPos = x2; N.yPos = y2;20                 }21                 void printLine() { M.printPoint(); N.printPoint(); }22             private:23                 Point M;        //类对象成员, M和N24                 Point N;25         };26 27         int main()28         {29             Line L(10, 20, 100, 200);30             L.printLine();31 32             return 0;33         }
复制代码


        编译运行的结果:

            (10, 20)            (100, 200)            Process returned 0 (0x0)   execution time : 0.500 s            Press any key to continue.


        代码说明:
            由于 Point 类中的数据成员全为 public 的, 所以可以通过 对象名.数据成员名 的方式直接进行访问。 可以看到, Line 类中有两个 Point 类型的数据成员, M和N, 然后通过 对象名.数据成员名 的方式在 Line 类的构造函数中完成初始化。
            


    情况二:
        由于类的数据成员一般都为 private 型的, 再使用上面的方式进行初始化就不适合了, 因为 private 数据是不允许外部直接访问的, 将 Point 类中的数据成员改为 private 后再编译运行代码便会报出error: 'int Point::xPos' is private 这样的错误。
    
        要在 Line 类中对 Point 的 private 数据成员进行正常的初始化同样需要借助初始化表达式来完成, 修改后的示例:

复制代码
 1         #include <iostream> 2  3         using namespace std; 4  5         class Point 6         { 7             public: 8                 Point(int x, int y) { xPos = x; yPos = y; } 9                 void printPoint() { cout<<"("<<xPos<<", "<<yPos<<")\n"; }10             private:11                 int xPos;   //private类型的 xPos 和 yPos12                 int yPos;13         };14 15         class Line16         {17             public:18                 Line(int x1, int y1, int x2, int y2):M(10, 20), N(100, 200) { ; }   //借助初始化表达式完成对M,N的初始化19                 void printLine() { M.printPoint(); N.printPoint(); }20             private:21                 Point M;        //类对象成员, M和N22                 Point N;23         };24 25         int main()26         {27             Line L(10, 20, 100, 200);28             L.printLine();29 30             return 0;31         }
复制代码

        
        运行输出后的结果与上例中是相同的。
        
        代码说明:
            通过初始化表达式对象 L 中的 M、N 数据成员的初始化顺序如下: 首先 M 对象的构造函数被调用, 接着调用 N 对象的构造函数, 最后 Line 类的构造函数被调用, 这样便完成了类对象成员的初始化。
            

 


    
    
四、static类型数据成员
    static 类型的数据成员为静态成员, 他的特点是: 无论对象创建了多少个, 该数据成员的实例只有一个, 会被该类所创建的所有对象共享, 其中任何一个对象对其操作都会影响到其他对象。该类型的数据初始化是放在类外进行的, 其基本格式如下:
        类型 类名::成员变量名 = 初始化值/表达式;
        
    一个示例: 统计一共创建了多少个对象, 并且在一番销毁后还剩多少。
    

复制代码
 1     #include <iostream> 2  3     using namespace std; 4  5     class Book 6     { 7         public: 8             Book() { ibookNumber++; }           //通过构造函数访问static型数据ibookNumber并使其自增1 9             ~Book() { ibookNumber--; }          //对象在被撤销时将static型数据ibookNumber并使其自减110             void showNum() { cout<<"Book number = "<<ibookNumber<<endl; }11         private:12             static int ibookNumber;             //static类型数据成员 ibookNumber13     };14 15     int Book::ibookNumber = 0;        //在类外对static类型的数据成员进行初始化16 17     int main()18     {19         Book A; Book B; Book C; Book D; Book E;     //创建一些对象20         A.showNum();        //使用对象A查看当前对象个数21         B.showNum();        //使用对象B查看当前对象个数22         cout<<"销毁一些对象...\n";23         B.~Book(); C.~Book();   //将B、C对象进行撤销24         D.showNum();            //使用D对象查看剩余对象个数25         E.showNum();            //使用E对象查看剩余对象个数26 27         return 0;28     }
复制代码

    
    编译运行的结果:

复制代码
        Book number = 5        Book number = 5        销毁一些对象...        Book number = 3        Book number = 3        Process returned 0 (0x0)   execution time : 0.266 s        Press any key to continue.
复制代码

        
    代码说明:
        在上面代码的类Book中, 我们声明了一个 static 类型的ibookNumber 作为创建多少对象的统计变量, Book的构造函数的作用是当对象创建时将 ibookNumber 的值自增1, 而析构函数的则负责将 ibookNumber 自减1。

        main函数中, 创建了 A-E共5个对象, 在创建完成后, 通过 A 对象输出对象的总数和 B 对象输出的结果是相同的, 都是5个, 然后销毁 B、C 对象, 用D、E对象查看剩余对象个数, 结果都为3, 可以看出, static型的数据成员任何对象都可以进行访问, 并且在创建后所产生的实例是唯一的。

 

 

 附注:

C/C++ 通过初始化列表和构造函数内赋值初始化成员变量的区别


一般我们进行成员变量初始化用两种方法
第一种是通过在构造函数内赋值
class Point
{
public:
Point(){ _x = 0; _y = 0;};
Point( int x, int y ){ _x = 0; _y = 0; }
private:
int _x, _y;
};
第二种是使用初始化列表
class Point
{
public:
Point():_x(0),_y(0){};
Point( int x, int y ):_x(x),_y(y){}
private:
int _x, _y;
};

这两种用法是有区别的
一、在有些情况下,必须使用初始化列表。特别是const和引用数据成员被初始化时。
class Point
{
// 这个类的定义就要求使用初始化成员列表,因为const成员只能被初始化,不能被赋值
public:
Point():_x(0),_y(0){};
Point( int x, int y ):_x(x),_y(y){}
//Point(){ _x = 0; _y = 0;}
//Point( int x, int y ){ _x = 0; _y = 0; }
private:
const int _x, _y;
};

二、是从效率方面来说的,对于内置类型或复合类型,差异不会太大,但对于非内置数据类型,差异还是很明显的
如我们再给Point类添加一个新的string类型的成员变量
class Point
{
const int _x, _y;
string _name;
};

构造函数内赋值进行初始化
Point( int x, int y, string name ){ _x = 0; _y = 0; _name = name; }

_name = name 这个表达式会调用string类的缺省构造函数一次,再调用Operator=函数进行赋值一次。所以需调用两次函数:一次构造,一次赋值

用初始化列表进行初始化
Point( int x, int y, string name ):_x(x),_y(y), _name(name){}
_name会通过拷贝构造函数仅以一个函数调用的代码完成初始化
即使是一个很简单的string类型,不必要的函数调用也会造成很高的代价。随着类越来越大,越来越复杂,它们的构造函数也越来越大而复杂,那么对象创建的代价也越来越高,所以一般情况下建议使用初始化列表进行初始化,不但可以满足const和引用成员的初始化要求,还可以避免低效的初始化数据成员。