`
mmdev
  • 浏览: 12916257 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

浅谈C++类 - 概念和构造函数

 
阅读更多

虽然学习了一段时间的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. usingnamespacestd;
  5. classFruit//定义一个类,类的名字是Fruit
  6. {
  7. public:stringname;
  8. };
  9. intmain()
  10. {
  11. Fruitapple={"apple"};//定义一个Fruit的类对象apple
  12. cout<<apple.name<<endl;//使用apple的数据成员name
  13. _getch();
  14. return0;
  15. }

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

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

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

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

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

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

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

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

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

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


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

构造函数的操作见下例:


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


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

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

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

最后输出 red apples。

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

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics