虽然学习了一段时间的C++了,可是针对一些难以理解的东西,我还是习惯于梳理记录下来,一方面是自己知识的一种条理化,另一方面是能够帮助到需要的人。

类是C++中的重中之重,几乎在C++的每一本书里面都会有很大的篇幅来讲解类。那么究竟什么是类?对于有C基础的人来说,一看概念就基本了解了,可是真正的类的理解却还要下一番功夫的。C++提供了很好的STL,因此我们在学习类的时候可以有更大的函数库和代码可以使用。

首先,我们来看一下概念。很多C++教科书上都是这样讲的:类是具体对象(实例)的抽象。那么究竟如何抽象?就是把一个实例的特征提取出来,比如,水果是一个类,苹果是水果的一个实例,苹果有苹果的特征,其他水果有其他水果的特征,比如香蕉。我们只要从苹果香蕉中把特征提取出来进行统一管理就可以,这样就形成了水果类class Fruits()。然后“Fruites  apple”,表示苹果是水果的一个实例。就像人如果是一个类的话,我们每一个人都是这个类的实例。Bjarne Stroustup来自于Simula的概念(The Design and Evolution of C++),一个class其实就是一个用户定义的新的Type,这点和结构体(struct)没有本质上的区别,只是使用上的区别而已。

概念就是概念,看完之后没有太多的印象,我们只有在概念的基础上编程、编程、再编程才是最快的。

1、一个简单的类

C++中使用任何东西都是要先定义的,类也一样,我们要在使用类的之前对类进行定义。我们以水果为例:

 
   
  1. #include<string>  
  2. #include<iostream>   
  3. #include<conio.h>   
  4.  
  5. using namespace std;   
  6.  
  7. class Fruit //定义一个类,类的名字是Fruit   
  8. {   
  9. public: string name;   
  10. };   
  11.  
  12. int main()   
  13. {   
  14. Fruit apple = {"apple"}; //定义一个Fruit的类对象apple   
  15. cout <&lt; apple.name &lt;&lt; endl; //使用apple的数据成员name  
  16.  _getch();   
  17. return 0;   
  18. }  

 

程序的说明就省略了.不过要提的一点是,类中不仅可以有数据成员,也可以有函数成员。另外类的默认标号是private类型的。

 

例如将上边程序稍作修改:

 

 
  
  1. #include &lt;string>   
  2. #include <iostream>   
  3. using namespace std;   
  4.  
  5. class Fruit       //定义一个类,名字叫Fruit   
  6. {   
  7. public:  //标号,表示这个类成员可以在外部访问    
  8.  
  9. string name;    //定义一个name成员            void print()     //定义一个输出名字的成员print()    
  10. {     
  11. cout<&lt; name&lt;&lt;endl;    
  12. }   
  13. };  
  14.  
  15.  int main()   
  16. {    
  17. Fruit apple = {"apple"};  //定义一个Fruit类对象apple  apple.print();  //使用apple的成员print    
  18. return 0;   
  19. }  
  20.  

可以发现与C的不同了,你可以在class中添加成员函数,让C++有了面向对象的特征,而C是结构化的编程。另外C++类中还有一个构造函数的概念。先看例子:

 
  
  1. #include &lt;string>   
  2. #include <iostream>   
  3.  
  4. using namespace std;   
  5.  
  6. class Fruit     //定义一个类,名字叫Fruit   
  7. {   
  8. public:      //标号,表示这个类成员可以在外部访问    
  9. string name;   //定义一个name成员            void print()     //定义一个输出名字的成员print()    
  10. {     
  11. cout<&lt; name&lt;&lt;endl;    
  12. }    
  13. Fruit(const string &st) //定义一个函数名等于类名的函数成员    
  14. {     
  15.    name = st;    
  16. }   
  17. };  
  18.  
  19.  int main()  
  20.  {    
  21. Fruit apple = Fruit("apple");  //定义一个Fruit类对象apple  
  22. Fruit apple = Fruit("orange");    
  23. apple.print();  //使用apple的成员print    
  24. orange.print();       
  25. return 0;   
  26. }  
  27.  

例子中的函数名就等于类名的函数成员,就是构造函数,在每一次定义一个新的对象时,程序自动调用。程序中我们定义了两个对象,一个apple,一个orange,分别用了两种不同的方法,你会发现构造函数的作用。另外对象只能等于对象,不能等于某个字符串等等。此时就不能直接Fruit  bananer了,因为已经没有可用的构造函数了,而没有构造函数之前,可以这样做:直接Fruit  banner,在使banner.name  =  “banner”;

 

 
  
  1. #include &lt;string>   
  2. #include <iostream>   
  3.  
  4. using namespace std;   
  5.  
  6. class Fruit     //定义一个类,名字叫Fruit  
  7. {   
  8. public:    //标号,表示这个类成员可以在外部访问    
  9. string name;            //定义一个name成员              
  10. void print()    //定义一个输出名字的成员print()    
  11. {     
  12. cout<&lt; name&lt;&lt;endl;    
  13. }   
  14. };   
  15.  
  16. int main()   
  17. {    
  18. Fruit apple;  //定义一个Fruit类对象apple    
  19. apple.name ="apple";  //这时候才初始化apple的成员name  apple.print();  //使用apple的成员print      
  20. return 0; }  
  21.  

而有了构造函数之后就不能这样做了,有两种方法可以实现:第一种,就是重载一个空的构造函数。那么什么是重载呢?我们看一个例子:

 

 
  
  1. #include &lt;string>   
  2. #include <iostream>   
  3.  
  4. using namespace std;   
  5. class Fruit      //定义一个类,名字叫Fruit   
  6. {   
  7. public:      //标号,表示这个类成员可以在外部访问    
  8. string name;    //定义一个name成员            void print()     //定义一个输出名字的成员print()   
  9.  {     
  10. cout<&lt; name&lt;&lt;endl;    
  11. }    
  12. Fruit(const string &st)    
  13. {   name = st;    
  14. }    
  15. Fruit(){}    //重载一个空构造函数   
  16. };   
  17.  
  18. int main()  
  19.  {    
  20. Fruit apple;  //定义一个Fruit类对象apple,这时是允许的了,自动调用第2个构造函数    
  21. apple.name ="apple";  //这时候才初始化apple的成员name  apple.print();  //使用apple的成员print      
  22. return 0; }  
  23.  

第二种方法就是使用构造函数默认实参:

#include &lt;string> #include <iostream> using namespace std; class Fruit               //定义一个类,名字叫Fruit { public:                //标号,表示这个类成员可以在外部访问 string name;            //定义一个name成员           void print()              //定义一个输出名字的成员print()  {   cout<&lt; name&lt;&lt;endl;  }  Fruit(const string &st = "banana")  {   name = st;  } }; int main() {  Fruit apple;  //定义一个Fruit类对象apple  apple.print();  apple.name ="apple";  //这时候才初始化apple的成员name  apple.print();  //使用apple的成员print    return 0; }

程序中当直接定义一个屋初始值的apple的时候,它就把name表示为banana。前面讲过,不推荐使用Fruit  apple  = { “apple”},在这里可以看到原因,因为这样的初始化,必须保证成员可以被访问,当name为私有成员的时候,这样就不奏效了。当牵扯到类的数据封装的时候,就不能这样进行对象的实例化了。

构造函数的操作见下例:

#include &lt;string> #include <iostream> using namespace std; class Fruit               //定义一个类,名字叫Fruit {                //没有标号了,表示这个类成员不可以在外部访问,class默认为private哦 string name;            //定义一个name私有成员           public:   void print()              //定义一个输出名字的成员print()  {   cout<&lt; name&lt;&lt;endl;  }  Fruit(const string &st = "banana")  {   name = st;  } }; int main() {  Fruit banana;  //定义一个Fruit类对象 banana.print(); // banana.name ="apple";  //这时候才改变banana的成员name已经是不允许的了 //  你要定义一个name等于apple的成员必须这样: Fruit apple("apple");  apple.print();    return 0; }

要说明的是,构造函数必须定义更公用的,因为必须要在外部调用。关于构造函数还有一些特殊的形式,初始化列表。

#include &lt;string> #include <iostream> using namespace std; class Fruit               //定义一个类,名字叫Fruit {   string name;            //定义一个name成员           public:   void print()              //定义一个输出名字的成员print()  {   cout<&lt; name&lt;&lt;endl;  }  Fruit(const string &st = "banana"):name(st){}  //看到不同了吗? }; int main() {  Fruit banana;  //定义一个Fruit类对象 banana.print();    return 0; }

在参数表后,函数实体前,以“:”开头,列出的一个列表,叫初始化列表,这里初始化列表的作用和以前的例子完全一样,就是用st初始化name,问题是,为什么要特别定义这个东西呢?C++ Primer的作者Lippman在书里面声称这时许多相当有经验的C++程序员都没有掌握的一个特性,因为很多时候根本就不需要,用我们以前的形式就够了但有种情况是例外。在说明前我们为我们的Fruit加个固定新成员,而且定义后不希望再改变了,比如颜色。

#include &lt;string> #include <iostream> using namespace std; class Fruit               //定义一个类,名字叫Fruit {   string name;     //定义一个name成员           const string colour; public:   void print()              //定义一个输出名字的成员print()  {   cout<&lt;colour&lt;&lt;" "&lt;&lt;name&lt;&lt;endl;  }  Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst){}  }; int main() {  Fruit apple;  //定义一个Fruit类对象apple  apple.print();    return 0; }

在这里吧colour的初始化放在{}里,用以前的方法会有报错,因为她是const的,而实际上放在{}里面的是个计算阶段,而放在初始化列表里面就可以,因为初始化列表的使用时在数据定义的时候就自动调用了。因为这个原因,数据的调用顺序和初始化列表里面的顺序无关,只和数据定义的顺序有关。

给两个例子,比如你在上面的例子中把初始化列表改为":colour(name),name(nst)"没有任何问题,因为在定义 colour前面,name 就已经定义了,但是":name(colour),colour(cst)"却不行,因为在name定义的时候colour 还没有被定义,而且问题的严重性在于我可以通过编译.........太严重了,所以在C++ Primer不推荐你使用数据成员初始化另外一个数据,有需要的话,可以":name(cst),colour(cst)",一样的效果。另外,初始化列表在定义时就自动调用了,所以在构造函数{}之前使用,你可以看看这个例子:

 

 
  
  1. #include &lt;string>   
  2. #include <iostream>   
  3.  
  4. using namespace std;  
  5.  class Fruit         //定义一个类,名字叫Fruit   
  6. {    
  7. string name;     //定义一个name成员           const string colour;   
  8. public:     
  9. void print()      //定义一个输出名字的成员print()    
  10. {   cout&lt;&lt;colour&lt;&lt;" "&lt;&lt;name&lt;&lt;endl;  }    
  11. Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)    
  12. {    
  13. name +="s";    //这时name已经等于"apple“了    
  14. }   
  15.  };   
  16.  
  17. int main()   
  18. {    
  19. Fruit apple("apple","red");  //定义一个Fruit类对象apple  apple.print();      
  20. return 0;   
  21. }  
  22.  

最后输出   red apples。

 

好了,针对构造函数做了一点点的总结归纳,知识还不是那么有条理性,很多地方还不是很完善,敬请指正!