C++类的理解(一):类的初识

一、类的意义,以及类与对象:
类的内容比较多,但类是面向对象的基础,所谓面向对象(object),其实就是一种更高层次的模块化,也称为封装。C语言中,稍微复杂点的数据结构都是用结构体来写的,结构体本质上就是把一些相关的信息打包存储,比如猫:

struct Cat
{
    char name[20];
    int age;
    char sex;
};

这样我们通过键盘输入很多猫的信息就可以用结构体来存储:

Cat cats[100];
for(int i=0;i<n;++i)
{
    scanf("%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 "<<name<<endl;
        cout<<"This cat's age is "<<age<<endl;
    }
};

这个时候我们不仅像结构体那样定义了属性,还定义了一个输出属性信息的函数方法。
而此时如果运行这个类的定义,内存里发生了什么呢?
回答是:什么也没发生,因为现在只是定义了这个类,而没有定义一个类的实例,也叫对象,对象和实例是一回事。

猫是一个类,我俩的宠物猫“天天”则是猫这个类的一个实例;
车是一个类,我的奔驰车是车这个类的一个对象;
书是一个类,《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 "<<name<<endl;
        cout<<"This cat's age is "<<age<<endl;
    }
};

至此我们来看如何用这个初始化函数,
比如刚刚我们的代码,创建一个对象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 "<<name<<endl;
        cout<<"This cat's age is "<<age<<endl;
    }
};

而当你给了一个初始化函数时,系统就不给默认的了,所以原先那种就不行了,那么怎么办?
看下面的代码:

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]; 初始化的时候把图片地址赋过来,也是可以的!
总之,无论什么属性都要转变成数值可表示的东西才行,实在不好表示的,像某人的声音,这种东西就跟刚刚的图片一样,弄个链接作为属性就好。

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值