一、类的意义,以及类与对象:
类的内容比较多,但类是面向对象的基础,所谓面向对象(object),其实就是一种更高层次的模块化,也称为封装。C语言中,稍微复杂点的数据结构都是用结构体来写的,结构体本质上就是把一些相关的信息打包存储,比如猫:
struct Cat
{
char name[20];
int age;
char sex;
};
这样我们通过键盘输入很多猫的信息就可以用结构体来存储:
Cat cats[100];
for(int i=0;iscanf("%s %d %c",name,age,sex);
strcpy(cats[i].name,name);
cats[i].age=age;
cats[i].sex=sex;
}
如果没有结构体,那我们可能就要用三个数组来存放这些数据,然后用他们的下标来一一对应联系,这是可行的,但对于写程序的人来说负担是很大的,比如:
//拙劣的代码:
char name[100][100];
int age[100];
char sex[100];
//然后name[i][100],age[i],sex[i]是相关联的一组数,描述同一个猫的特征。(累得很)
结构体将属性打包,而类不仅打包属性,还打包操作这些属性的方法(函数)。
还用猫来打比方:
class Cat
{
public: //类的权限系统,后面再说,现在暂时都用public
char name[100];
int age;
char sex;
public:
void printInfo()
{
cout<<"This cat's name is "<cout<<"This cat's age is "<
这个时候我们不仅像结构体那样定义了属性,还定义了一个输出属性信息的函数方法。
而此时如果运行这个类的定义,内存里发生了什么呢?
回答是:什么也没发生,因为现在只是定义了这个类,而没有定义一个类的实例,也叫对象,对象和实例是一回事。
猫是一个类,我俩的宠物猫“天天”则是猫这个类的一个实例;
车是一个类,我的奔驰车是车这个类的一个对象;
书是一个类,《c++ primer》是书的一个对象。
所以,类只是一个模板,是抽象的,而对象是具体的。问一个类的属性具体是多少一般是不可以的,而要问一个对象的属性是多少才是正确的。
比如:
车是个类,问:车的颜色是什么? 答不出来。
猫是个类,问:猫的名字是什么?天下猫的名字千千万,答不上来。
但问:我的奔驰车(对象)是什么颜色?可以答:黑色。
再回到刚刚那个问题,Cat这个类运行了之后内存里什么都没发生,因为类是抽象的,计算机并没有给它分配任何存储的空间,只有当类的对象定义了之后内存才会根据这个模板,给他分配空间。
Cat mypet;
这句话出来的一瞬间,系统才会分配一个Cat类型大小的空间,这个时候mypet.name, mypet.age, mypet.sex都是有了自己对应大小的空间了,有了空间,我们就可以对他们的值进行初始化:
strcpy(mypet.name,"tiantian");
mypet.age=1;
mypet.sex='F';
然后可以调用类的函数来输出这个猫的信息:
mypet.printInfo();
注意这里是mypet.printInfo()
,而不是Cat.printInfo()
因为前者的意思是打印我的宠物信息,后面那个是打印猫的信息,那就相当于在问系统,猫的名字,猫的性别,系统答不上来的。
现在如果我又定义了一只猫:
Cat anotherCat;
然后系统又会根据模板,造出另一个猫的对象,这两个对象的空间是互相独立的,互不干扰。
二、类的初始化
所谓初始化,就是把一个刚创建的数据设置成我想要的值,而不是一个我不能掌控的随机数。
前面我们的Cat类型,初始化用的就是一般的赋值,这种方法是可行的,但当属性很多,而大部分属性都是默认的情况下,这种方法会有些让人烦躁,比如:
定义一个二叉树节点:
class Node
{
public:
int index;
Node* left;
Node* right;
};
此例中,初始化left和right是指针,指针初始化时默认写0一般,用上面的代码,每一次新建一个Node都要写
Node node;
node.index=123;
node.left=0;
node.right=0;
后面两行写多了就烦了。
c++为类提供了初始化函数,这个函数在对象被创建时有系统自动调用,是创建对象的最后一步。也就是说,初始化函数是创建对象的一部分,初始化函数退出之后,该对象才完成了创建。
初始化函数和类名要保持一致,且没有返回值,连void都没有!!!
class Cat
{
public: //类的权限系统,后面再说,现在暂时都用public
char name[100];
int age;
char sex;
public:
Cat(char* name,int age,char sex) //初始化函数
{
strcpy(this->name,name);
this->age=age;
this->sex=sex;
}
void printInfo()
{
cout<<"This cat's name is "<cout<<"This cat's age is "<
至此我们来看如何用这个初始化函数,
比如刚刚我们的代码,创建一个对象myCat;
Cat myCat;
这么写,首先声明它是错的,我们想想前面的创建对象的步骤,分配空间没问题,我类模板放在那边呢,属性都写着呢,要多少空间就定下来了(注意啊,函数不占空间啊,对象里面占空间的只有属性啊),但是初始化的时候,初始化函数问我要三个参数,我给了吗?没给!没给就报错啊,编译都通不过。
之前我这么定义为啥没问题,难道我就不能先这么定义着,然后再通过前面那种方式给他赋值吗?答案:不能!
为什么?因为之前我没给初始化函数,没给的话,c++默认会自动给一个参数表为空且什么都不做的初始化函数,也就是像下面那样子:
class Cat
{
public: //类的权限系统,后面再说,现在暂时都用public
char name[100];
int age;
char sex;
public:
Cat() //初始化函数,没写的话,系统就自动给一个这样的初始化函数,什么都没做。
{
}
void printInfo()
{
cout<<"This cat's name is "<cout<<"This cat's age is "<
而当你给了一个初始化函数时,系统就不给默认的了,所以原先那种就不行了,那么怎么办?
看下面的代码:
scanf("%s %d %c",name,age,sex);
Cat myCat(name,age,sex);
通过myCat后面跟一个括号,把参数传给初始化函数。括号里面的东西要和初始化函数的参数表一一对应。没有第二种写法!
再来看刚刚二叉树那个例子,给他写一个初始化函数:
class Node
{
public:
int index;
Node* left;
Node* right;
public:
Node(int index)
{
this->index=index; //this指针应该没啥问题的吧
left=0;
right=0;
}
};
然后我初始化时候参数表只要一个index数值就好,所以它的对象这么创建:
Node t(10);
//此时: t.index==10, t.left==0, t.right==0
三、类的对象的使用
定义好了一个类,此时它就和基础类型无异了,可以定义该类的普通对象,也可以定义指向该类的指针对象。假设我们所有的程序在64位机子上跑,首先64位机和32位机区别在哪?这个你现在应该知道的,你告诉我吧。
然后我定义一个普通对象和指针对象(还用上面的自定义了初始化函数的Cat类):
Cat myCat("TianTian",1,'F');
Cat *p;
题外话1:
两个问题,myCat 变量占多大的空间,p指针又占多大空间?这两个问题是理解指针变量和普通变量很关键的问题。
答:
myCat占 sizeof(Cat) 这么大的空间,而p占8字节。
为什么?因为普通变量大小很直接,但指针存的是地址,64位机地址就是64bits,也就是8字节,是定的。所以说不管是: int*, double*,bool*, Cat* 这些指针,或者是int**,char**,这种指向指针的指针,他们占的大小都是8字节,因为存的都是地址。
那么现在在理解下下面这个类定义为什么不对:
class Node
{
public:
int index;
Node next;
public:
......
}
答:暂时先略,你先思考哦
回到上面,Cat* p;
是一个指针,但目前是一个危险的悬空指针,使用它操作类如下:
p=&myCat;
p->name; //=="TianTian"
p->age; //==1
p->sex; //=='F'
p->printInfo();
然后通过上面的方法可以操作对象的属性和方法。
或者也可以写成这样(对是对,不过没人这样用):
(*p).name;
(*p).printInfo();
//因为*p自然就是取所指空间的值,和myCat是等价的,上面的方法方便些,注意两者的区别。
//不要混用,比如p.name是错的,因为p是指针,不是一个类,没有name属性
//(*p)->name,也是错的(*p)不是一个指向类的指针,它只是一个普通对象空间myCat。
这一篇的内容很基础,是能用好c++类的前提,另外从我曾经刚开始接触类和对象的体验来说,初学时上面所举的例子可能容易接受,但可能想象不出在计算机编程领域类能表示什么,或者一些奇奇怪怪的东西用类怎么描述,比如描述一个类:圆,或者磁盘,或者CPU,或者一个多边形复杂体的样子。有些东西光是人用语言来描述就都很困难了,用一个类就能描述出来吗?
回答是:当然。。。不行!!!
那这玩意儿有啥用呢?有的书上会说万物皆对象,这又算啥?
实际上,在工程中,我们描述或者定义一个类型时,只定义我们感兴趣的部分,属性和操作都是这样的。比如圆我们只关心圆心和半径:
class Circle
{
public:
int x;
int y;
int r;
};
题外话2:
如果我还有点Point这个对象我可以这么写:
class Point
{
public:
int x;
int y;
public:
Point(int x,int y)
{
this->x=x;
this->y=y;
}
};
class Circle
{
public:
Point center;
int r;
public:
Circle(int x,int y, int r): center(x,y)
{
this->r=r;
}
};
int main()
{
Circle p(1,2,3);
}
这时候main函数中要构造Circle对象p,要构造p就要先构造p里面那个Point对象center,而Point是没有默认构造函数的,因为我自定义了一个,所以只能通过上面那种方式给center的构造函数传值,如果有多个属性是没有默认构造函数的对象的话,都写在后面,用逗号隔开。
这里面要注意这些变量的构造顺序:
开始构造 p(1,2,3)
……开始构造p.Center(1,2)
……p.Center构造完成
p构造完成
它的顺序是像递归一样,先把基础的构造好了,才会去构造上层的东西。
然后要用数据的时候就是p.center.x
这种写法。
继续回到上面,刚刚我们在讨论我们用类描述东西时候是描述我们感兴趣的部分,比如剪刀,我们如果只关心大小,我们就:
class Scissor
{
public:
int size;
//Color color;
};
如果还关心颜色那就加个Color color;
属性,并定义Color类:
class Color
{
public:
int r;
int g;
int b;
}
如果有人非要关心什么形状,这种难以定量描述的属性,直接点,我们可以在电脑上存个图片,然后加一个属性char shape_url[255];
初始化的时候把图片地址赋过来,也是可以的!
总之,无论什么属性都要转变成数值可表示的东西才行,实在不好表示的,像某人的声音,这种东西就跟刚刚的图片一样,弄个链接作为属性就好。