引入类之前,首先引入一个古老的话题:类别,比如int ,char ,double;这些基本的类型方便了我们描述数据(请注意,这句话很重要),类型的存在就是为了方便我们描述数据的。而c++中的类其实作用也是:方便我们描述数据。因此,我们可以这么认为:引入类的作用,就是为了让我们可以像使用int 这些类型一样,来描述我们想要表达的数据。尽管很多时候我们需要把我们想描述的数据抽象化。因此,我们有理由将类和int,char这些基本类型一视同仁(这个思想是很重要的!!!,但要在编程的时候认识到这点并不容易)。而实质上,引入类的初衷:就是让我们能够像使用基本类型使用我们定义的类,并能够像基本类型那样操作自如。
再看一个基本的话题:比如定义了一个变量 ,比如int x;很不幸的是,我们已经养成了习惯,习惯把x叫作“变量”,同样的把int叫作类型,这对我们认识和使用类造成了很大的不便。在类中,也会定义类变量,但我们把它叫对象。比如,我们定义猫科动物为一类:class Cat{};通常类型名大写,请注意,这里的Cat是我们定义的类,或者说是类型。即Cat和int,char等并无差异性。比如我们可以通过这个Cat类型,定义一些猫科动物。比如Cat cat(就是定义了猫),Cat tiger(定义了老虎)。我们发现:猫和老虎都是猫科动物(就像1和2都是整数一样)。这里的cat,tiger,我们称之为对象。其实想叫作变量也可以,但还是跟着叫对象吧。
接下来,通过一段代码初步认识类:
1 # ifndef STOCK00_H 2 #define STOCK00_H 3 # include "string" 4 class Stock 5 { 6 private: 7 std::string company; 8 long shares; 9 double share_val; 10 double total_val; 11 void set_tot()//思考这个函数定义的意义何在 12 { 13 total_val = shares*share_val; 14 } 15 public: 16 Stock::Stock(); 17 void acquire(const std::string &co, long n, double pr); 18 void buy(long num, double price); 19 void sell(long num, double price); 20 void updata(double price); 21 void show(); 22 }; 23 #endif //ifbdef...endif成对出现
这段代码定义了一个名叫Stock的类。首先,我们在这个代码中看到的是:public和private,中文意思公共和私有(这种描述和其功能很吻合)。共有的意思是对外公开的,客户可以对其访问的。我们也把这个叫做接口。私有的意思是用户对其不可直接访问的。上面代码可以看到:我们在private中定义了很多基本类型变量。我们把这些基本类型变量叫做数据成员。而public中定义的是函数成员,我们也叫做方法。当然不一定非要把数据成员都放在private中,而把函数成员都放在public中,取决于你想让用户访问什么。关于这方面,请具体参考c++ prime,并通过自己的实战体会。
在此,仅仅总结这么个东西(很重要):我们定义了一个类,比如猫科动物。我们首先想到的肯定是:既然是猫科动物,首先肯定要有一些描述它的信息:比如体重啊,毛色啊,名字啊(假如有),甚至眼睛大小啊等等。而这些其实本质上,就是我们说的数据成员,即我们需要定义一些基本类型变量来描述我们的类具有哪些特征。但是现在,我们是面向对象编程:我们需要封装。假如现在有一个用户手上有一只具有这些特征的值的猫,他想根据这些数据知道这个猫几岁了???。这便引入了这样一个问题:我们类的存在不只是为了存储一只猫的各类类型的值。(那样的话我们直接用结构体就好了,为何要使用类呢???),我们是想根据这些值能 做些什么比较方便的事,而这些事最终被反馈到用户。那么成员函数的存在就是:利用这些基本类型变量,完成某项工作。但用户并不需要知道怎么完成,我只需要知道猫有几岁???知道猫有几岁这个工作毫无疑问是通过函数完成的,或者说我们用什么方法(函数)来知道猫有几岁(过度很自然)。比如有一个函数indicate_age()可以帮我们做这件事,则用户可以通过调用类——猫中的indicate_age()方法来实现这个愿望。这一点也表明了,这个indicate_age()方法要放在public,这样用户才有访问使用它的权限。(假如用户不想让人知道名字,可以放在private中)
(如果清闲,可以思考Python中定义类有何异同)
请注意,上面那段代码中public中只是声明了函数原型,但是并没定义这些函数,当然我们可以在public中直接定义,但没必要,显得很啰嗦。下面给出这些函数的定义:
1 # include "iostream" 2 # include "stock00.h" 3 4 void Stock::acquire(const std::string &co, long n, double pr) 5 { 6 company = co; 7 if (n < 0) 8 { 9 std::cout << "Number of shares can't be negative;" 10 << company << "shares set to 0. "; 11 shares = 0; 12 } 13 else 14 shares = n; 15 share_val = pr; 16 set_tot(); 17 } 18 19 void Stock::buy(long num, double price) 20 { 21 if (num < 0) 22 { 23 std::cout << "Number of shares can't be negative." 24 << "Transaction is aborted. "; 25 } 26 else 27 { 28 shares += num; 29 share_val = price; 30 set_tot(); 31 } 32 } 33 34 void Stock::sell(long num, double price) 35 { 36 using std::cout; 37 if (num < 0) 38 { 39 cout << "Number of shares can't be negative." 40 << "Transaction is aborted. "; 41 } 42 else if (num>shares) 43 { 44 cout << "You can't sell more than you have!" 45 << "Transaction is aborted. "; 46 } 47 else 48 { 49 shares -= num; 50 share_val = price; 51 set_tot(); 52 } 53 } 54 55 void Stock::updata(double price) 56 { 57 share_val = price; 58 set_tot(); 59 } 60 61 void Stock::show() 62 { 63 std::cout << "Company:" << company 64 << "Shares:" << shares << " " 65 << "Shares Price:$" << share_val 66 << "Total worth:$" << total_val << std::endl; 67 } 68 69 Stock::Stock() 70 { 71 company = "no name"; 72 shares = 0; 73 share_val = 0.0; 74 }
我们重新用一个文件来定义这些函数:具体的细节不再赘述,想描述的是每个函数的抬头,都采用了Stock::,这其实是表明这些函数的作用域是类作用域,比如我们外面也声明了acquire()函数,那么两者是可以区分的。这个类作用域十分重要,具体的可以参考c++ prime,
在这里,我们发现,给出的第一个代码中,有这样一个函数:Stock::Stock(),这个函数对类的数据成员进行了赋值(实际上就是初始化)。这个函数有两个特点:1 没有返回类型;2 名字和类的名字要一致。我们把这种函数叫构造函数。实际编写代码中,我们有时候没有写构造函数,但是貌似并不是报错,但这并不意味着构造函数是非必须的。实际上,构造函数是必须的。我们在创建类 对象的时候(就是定义类 变量的时候)将先自动调用构造函数(即由系统自动调用),然后才能生成我们的类 对象。也就是构造函数是用来被创建对象的,从而构造函数是不能通过对象去调用的。这一是十分重要的。关于构造函数耳朵显示,和隐式初始化可以参考c++ primer。
上面完成了对一个类的定义。下面给出调用它的代码:
1 # include"iostream" 2 # include"stock00.h" 3 int main() 4 { 5 Stock fluffy_the_car; 6 fluffy_the_car.acquire("NanoSmart", 20, 12.50); 7 fluffy_the_car.show(); 8 fluffy_the_car.buy(15, 18.125); 9 fluffy_the_car.show(); 10 fluffy_the_car.sell(400, 20.00); 11 fluffy_the_car.show(); 12 fluffy_the_car.buy(300000, 40.125); 13 fluffy_the_car.show(); 14 system("pause"); 15 //int i; 16 //std::cout << "i is:" << i << std::endl;//c++中未初始化造成报错,不同于C语言. 17 return 0; 18 }
上面的代码是多么的简洁,假如我们是客户,我们只需要知道类方法怎么使用即可,即怎么调用接口,就能知道我们的猫有几岁。站在用户的角度讲,这段程序很简单!!!而这,就是类产生的目的!!!
C++中类的诞生是一种很重要的思想,而所有关于C++类相关的,最重要的思想在于:能够让我们像使用其他类一样方便的使用我们定义的自己数据类型。认识到这一点的重要性在于:当我们思想中认识到我们自己创建的类和int,char并无区别的时候,就意味着,int,char ,double这些基本类型所有的操作,定义的类对象也应该有:比如,初始化,赋值,甚至比较大小。具有指针,具有数据(类数组),可以将类作为函数的返回值和参数。所以,我们思考类的角度,应该从数据和结构的角度去思考!
关于类:还想说明两点很重要的地方:
1 声明类只是创建了对象的形式,而非创建对象本身。因此,在创建对象之前,我们所定义的东西,是不被分配内存空间的,比如当我们声明int类型的时候,只有定义了 int i,i才有内存空间
2 关于类的作用域。在很多地方,都讨论过作用域。作用域到底意味着什么???作用域意味着变量的生存周期,意味着作用域与作用域之间的独立性。而类的作用域,一个最明显的特征是,当我们定义类方法的时候,采用的是:classname ::funcname 这样的限定方式,这样即使两种方法使用了同一个名字,但由于处于不同的类作用域中,两者的同时存在并不冲突!