虽然学习了一段时间的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++中使用任何东西都是要先定义的,类也一样,我们要在使用类的之前对类进行定义。我们以水果为例:
- #include<string>
- #include<iostream>
- #include<conio.h>
- using namespace std;
- class Fruit //定义一个类,类的名字是Fruit
- {
- public: string name;
- };
- int main()
- {
- Fruit apple = {"apple"}; //定义一个Fruit的类对象apple
- cout << apple.name << endl; //使用apple的数据成员name
- _getch();
- return 0;
- }
程序的说明就省略了.不过要提的一点是,类中不仅可以有数据成员,也可以有函数成员。另外类的默认标号是private类型的。
例如将上边程序稍作修改:
- #include <string>
- #include <iostream>
- using namespace std;
- class Fruit //定义一个类,名字叫Fruit
- {
- public: //标号,表示这个类成员可以在外部访问
- string name; //定义一个name成员 void print() //定义一个输出名字的成员print()
- {
- cout<< name<<endl;
- }
- };
- int main()
- {
- Fruit apple = {"apple"}; //定义一个Fruit类对象apple apple.print(); //使用apple的成员print
- return 0;
- }
可以发现与C的不同了,你可以在class中添加成员函数,让C++有了面向对象的特征,而C是结构化的编程。另外C++类中还有一个构造函数的概念。先看例子:
- #include <string>
- #include <iostream>
- using namespace std;
- class Fruit //定义一个类,名字叫Fruit
- {
- public: //标号,表示这个类成员可以在外部访问
- string name; //定义一个name成员 void print() //定义一个输出名字的成员print()
- {
- cout<< name<<endl;
- }
- Fruit(const string &st) //定义一个函数名等于类名的函数成员
- {
- name = st;
- }
- };
- int main()
- {
- Fruit apple = Fruit("apple"); //定义一个Fruit类对象apple
- Fruit apple = Fruit("orange");
- apple.print(); //使用apple的成员print
- orange.print();
- return 0;
- }
例子中的函数名就等于类名的函数成员,就是构造函数,在每一次定义一个新的对象时,程序自动调用。程序中我们定义了两个对象,一个apple,一个orange,分别用了两种不同的方法,你会发现构造函数的作用。另外对象只能等于对象,不能等于某个字符串等等。此时就不能直接Fruit bananer了,因为已经没有可用的构造函数了,而没有构造函数之前,可以这样做:直接Fruit banner,在使banner.name = “banner”;
- #include <string>
- #include <iostream>
- using namespace std;
- class Fruit //定义一个类,名字叫Fruit
- {
- public: //标号,表示这个类成员可以在外部访问
- string name; //定义一个name成员
- void print() //定义一个输出名字的成员print()
- {
- cout<< name<<endl;
- }
- };
- int main()
- {
- Fruit apple; //定义一个Fruit类对象apple
- apple.name ="apple"; //这时候才初始化apple的成员name apple.print(); //使用apple的成员print
- return 0; }
而有了构造函数之后就不能这样做了,有两种方法可以实现:第一种,就是重载一个空的构造函数。那么什么是重载呢?我们看一个例子:
- #include <string>
- #include <iostream>
- using namespace std;
- class Fruit //定义一个类,名字叫Fruit
- {
- public: //标号,表示这个类成员可以在外部访问
- string name; //定义一个name成员 void print() //定义一个输出名字的成员print()
- {
- cout<< name<<endl;
- }
- Fruit(const string &st)
- { name = st;
- }
- Fruit(){} //重载一个空构造函数
- };
- int main()
- {
- Fruit apple; //定义一个Fruit类对象apple,这时是允许的了,自动调用第2个构造函数
- apple.name ="apple"; //这时候才初始化apple的成员name apple.print(); //使用apple的成员print
- return 0; }
第二种方法就是使用构造函数默认实参:
#include <string> #include <iostream> using namespace std; class Fruit //定义一个类,名字叫Fruit { public: //标号,表示这个类成员可以在外部访问 string name; //定义一个name成员 void print() //定义一个输出名字的成员print() { cout<< name<<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 <string> #include <iostream> using namespace std; class Fruit //定义一个类,名字叫Fruit { //没有标号了,表示这个类成员不可以在外部访问,class默认为private哦 string name; //定义一个name私有成员 public: void print() //定义一个输出名字的成员print() { cout<< name<<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 <string> #include <iostream> using namespace std; class Fruit //定义一个类,名字叫Fruit { string name; //定义一个name成员 public: void print() //定义一个输出名字的成员print() { cout<< name<<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 <string> #include <iostream> using namespace std; class Fruit //定义一个类,名字叫Fruit { string name; //定义一个name成员 const string colour; public: void print() //定义一个输出名字的成员print() { cout<<colour<<" "<<name<<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)",一样的效果。另外,初始化列表在定义时就自动调用了,所以在构造函数{}之前使用,你可以看看这个例子:
- #include <string>
- #include <iostream>
- using namespace std;
- class Fruit //定义一个类,名字叫Fruit
- {
- string name; //定义一个name成员 const string colour;
- public:
- void print() //定义一个输出名字的成员print()
- { cout<<colour<<" "<<name<<endl; }
- Fruit(const string &nst = "apple",const string &cst = "green"):name(nst),colour(cst)
- {
- name +="s"; //这时name已经等于"apple“了
- }
- };
- int main()
- {
- Fruit apple("apple","red"); //定义一个Fruit类对象apple apple.print();
- return 0;
- }
最后输出 red apples。
好了,针对构造函数做了一点点的总结归纳,知识还不是那么有条理性,很多地方还不是很完善,敬请指正!