掰扯掰扯面向对象的类和默认成员函数

来先上车了解一下面向对象的概念吧:对于对于单身党来说对象这个词应该是可望而不可即的词吧,而面向对象更难。下面我们来掰扯掰扯面向对象。

面向对象:它的思想是把整个世界看成具有行为活动的各种对象的组成。比如,当你谈对象时,你是不是要了解人家的性格,爱好,年龄等特征啊。当你买水果时你是不是要看看水果的价格和新鲜程度,这些都是对象的特征,万事万物都是由对象组成的。
OOAD(面向对象与设计)称一个对象的特征叫做属性,把其行为称为服务或方法
当我们向一个对象传递参数并调用对应的函数,这就是在请求其提供服务,而对象之间可以通过它们能够提供的服务来交流,进而合作完成特定的任务。

面向对象中的类是很重要的,具体来说对象是类的实例。比如:把衣服比作 对象,那么衣服的设计图纸就是类,对象是将类实例化了。
先了解类的特性:

  1. 封装性:将数据和函数捆绑在一起,其中数据表示类的属性,函数表示类的行为。而类中有关键字public,private,protected用于声明那些数据和函数是公开可以访问的,私有的,受保护的,这样就达到隐藏的目的,更加安全,但切记不要乱用封装特性,别把不相干的函数和数据都放在类中。
  2. 继承性:继承是一个进程,通过继承,一个对象可以获得另一个对象的属性(包括函数),并可向其中加入属于自己的一些特征。
    尝尝这个栗子:A是基类,B是A的派生类,则B类会继承A类的数据和函数
class A
{
publicvoid Func1(void);
};
class B : public A
{
public:
    void Func2(void);
};

int main()
{
    B b;
    b.Func1();//B从A中继承了函数Func1
    b.Func2();//B调用自己的函数Func2
}

这个栗子说明c++提高了程序的可复用性,是不是感觉这个继承性太有用了,所以因为它太有用所以用的时候就要小心了,不能乱用,各种继承,那样代码就乱套了。所以我们用继承时要注意下面的规则:
规则1:如果类A和类B没有关系,则不要让类B去继承类A的功能和属性。
规则2:如果在逻辑上B是A的一种,并且A的功能和属性对B都有用,则允许B继承A的功能和属性。
3.类的组合特性:组合也是一种类的复用技术,用于表示类的“整体与部分”关系,组合要区分继承,继承表示类的一种“一般和特殊”关系。
eg:
眼、鼻、口、耳、嘴、是头的一部分,所以头是由它们组合起来的,而不是由它们派生的
4. 类的作用域:
4.1. 每个类都有自己的作用域,在类中的对象可以任意访问类中的所有的其它成员变量和成员函数
4.2.在类外定义的成员,需要用”::“作用域解析符来指明成员属于哪个类域。

类中所有成员变量的大小就是对象的大小,并且要遵循内存对齐原则。

那么空类的大小怎么计算?

egclass A
{

}
class B
{
    void Func(void);
}
sizeof(A)大小为1 
sizeof(B)大小也为1
为什么B也是1呢,因为成员函数不占用对象空间,它只用公共的成员函数的空间。
空类表示它在程序中存在过,所以要占用一个字节,占用4个字节就浪费了。

为什么要内存对齐?看图
这里写图片描述
那么怎么计算内存对齐?
eg:

typedef struct A
{
    char _c1;
    int _i;
}A;
typedef struct B
{
    char _c2;
    struct A a;
    double _d;
}B;
那么sizeof(A)为多少,sizeof(B)为多少?
在vs编译器下:sizeof(A) = 8;sizeof(B) = 24;

那么怎么计算出来的,先了解一下内存对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处对齐。
2.其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数: 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8
gcc中的默认值为4
3.结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
先来看sizeof(A):char _c1先占用一个字节,int _i大小为4 ,看规则2,与默认对齐数相比取小的,所以对齐到地址为4出,占用4~7,char _c1占用0~3,所以sizeof(A)的大小为8。
sizeof(B):char _c2先占用一个字节,接下来是结构体,所以看规则3和4,结构体A大小为8,最大对齐数为4,所以在地址4出对齐,占用8字节的大小,此时占用12个字节,double大小为8,对其数为8,看规则2,要对齐到它的对其数的整数倍,所以原来12字节要再浪费4个字节,8对齐到16的位置,再占用8个字节,此时一共占用24个字节,这就是sizeof(B)的大小。

5.默认成员函数解析:

5.1、构造函数:它的主要功能是对象的初始化,并且是任何一个对象创建时自动调用的第一个成员函数,并且只能调用一次。
特征如下:
1、函数名和类名相同
2、没有返回值类型
3、对象构造好时系统会自动调用
4、构造函数可以重载
5、如果类中没有构造函数,系统会自动产生一个缺省的构造函数,但如果定义了,系统就不会产生。
6、无参的构造函数和全缺省的构造函数都认为是缺省构造函数,并且缺省构造函数只能有一个。
7、不要在构造函数中做与初始化无关的事情,否则会降低效率,让人误解。

既然说构造函数是为了初始化对象的,那么初始化意义你了解?
初始化:初始化就是在对象创建时给对象填充一个内存单元,不会有数据类型转换和中间过程,也不会产生临时对象。
赋值:在对象创建好后的任何时候都可以多次调用,并且它调用的是“=”运算符,因此类型可能不匹配,会产生临时对象。

构造函数的初始化:
1、可以用初始化列表来初始化
2、可以在函数体内赋值
eg:
class Date
{ public :
// 2.带参构造函数
Date (int year, int month , int day )//在函数体内赋值
{
    _year = year ;
    _month = month ;
    _day = day ;
}
Date (int year, int month , int day )//初始化列表来初始化
    :_year(year)
    ,_month(month)
    ,_day(day)
{
}
private :
    int _year ;
    int _month ;
    int _day ;
};
初始化列表的使用规则:
1、类的非静态const数据成员和引用成员只能在初始化列表中初始化
因为它存在初始化语义,不存在赋值语义。
2、类的数据成员初始化可以采用初始化列表或者函数体内赋值两种方式
但是初始化列表比函数体内赋值效率高。

5.2析构函数:当一个对象生命周期结束时,C++编译系统会自动调用一个成员函数,这就是析构函数,主要功能是做清理工作。
它的特征如下:
1. 析构函数在类名加上字符~。
2. 析构函数无参数无返回值。
3. 一个类有且只有一个析构函数。如果没有显示定义,系统会自动生成缺省的析构函数。
4. 对象生命周期结束时,C++编译系统会自动调用析构函数。
5. 注意析构函数体内并不是删除对象,而是做一些清理工作。(比如你在构造函数中给指针动态开辟内存,那么你必须在析构函数中free掉指针,并置空,否则就会出现野指针问题)
还有一点:在对象引用的初始化和销毁都不会调用构造函数和析构函数,所以引用传递比值传递效率高。

5.3、拷贝构造函数:
创建对象时使用另一个存在的对象进行初始化,这就是拷贝拷贝构造函数

class Date
{ public :
    Date()
    {}
    // 拷贝构造函数
    Date (const Date& d)
    {
        _year = d ._year;
        _month = d ._month;
        _day = d ._day;
    }
    private :
        int _year ;
        int _month ;
        int _day ;
};
特征:
1.它的第一个参数必须是本类对象的引用、const的引用、volatile引用
或者const volatile引用,并且没有其他参数。
2.若未定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数
会依次拷贝类成员进行初始化

5.4、赋值运算符重载
定义:拷贝构造函数是创建的对象,使用一个已有对象来初始化这
赋值运算符的重载是对一个已存在的对象进行拷贝赋值。

eg:
class Date
{ public :
    Date()
    {}
    // 拷贝构造函数
    Date (const Date& d)
    : _year(d._year)
    , _month(d._month)
    , _day(d._day)
    {}
    // 赋值操作符的重载
    Date& operator = (const Date& d)
    {

        this->_year = d. _year;
        this->_month = d. _month;
        this->_day = d. _day;
    }
private:
    int _year ;
    int _month ;
    int _day ;
};
void Test ()
{
Date d1 ;
Date d2 = d1; // 调用拷贝构造函数
Date d3 ;
d3 = d1 ; // 调用赋值运算符的重载
}

从上面立即你能看出拷贝构造函数和赋值运算符重载的区别吗?
它们的区别是:
拷贝构造函数是在一个对象正在创建时用另一个已经存在的对象来初始化它。
赋值运算符的重载是把一个对象赋值给另一个存在的对象,使那个已经存在的对象具有和源对象相同的状态。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值