小结 | C++(二)| 默认成员函数、this指针

来源:互联网 发布:php 协程 异步 编辑:程序博客网 时间:2024/05/29 03:00

一、类和对象

1、面向对象程序设计
概念:(Object Oriented Programming,缩写:OOP)是一种程序设计范型,同时也是一种程序开发的方法。
对象指的是类的实例,将对象作为程序的基本单元,将程序和数据封装其中,以提高软件的重用性、灵活性和扩展性。
举个例子:人是一个类,其中的每个人都有着自己的角色:孩子、老师、司机……他们都属于人这一个类。一个类可以实例化很多的对象。

2、C++是一个基于面向对象的语言,并不是纯粹的面向对象语言,这是因为C++向前兼容C,而C是面向过程的语言

3、面向对象有三大特性:封装、继承、多态。
封装:将方法和数据放在一个类里面。
继承:子类可以通过不同的继承方式从父类中获得不同类型的成员和方法。是复用的手段
多态:父子类中相同的函数,不再是通过类型来判断,而是通过不同的对象来调用函数。

一个类的例子:

class Person//类名字{public://访问限定符    void show()//成员函数    {}private:    char* _name;//成员    char* _sex;    int _age;};

4、类外面定义成员函数
通过域访问符来实现

class Person{public:    //类里面进行成员函数的定义    void show()    {        cout << "show()" << endl;    }    //类里面进行声明    void Display();private:    char* _name;    char* _sex;    int _age;};//在类外面进行定义void Person::Display(){    cout << "Display()" << endl;}

5、类的访问限定符
(1)public:公有访问。该限定符下,类外面的对象可以任意访问
(2)protected:保护访问。在继承的时候与private不同,其他情况类外面的对象不可以访问
(3)private:私有访问。类外面的对象不可以访问,是该类私有。
(4)在不声明访问限定符的情况下,默认是私有访问。
(5)每个限定符在类体中可使用多次,它的作用域是从该限定符出现开始到下一个限定符之前或类体结束前。
(6)类的访问限定符体现了访问限定符的封装性。

6、类的大小

#include <iostream>#include <stdlib.h>using namespace std;class person{public:    void show()    {}private:    char* _name;    char* _sex;    int _age;};int main(){    cout << sizeof(person) << endl;    system("pause");    return 0;}

这个类的大小是12,类在计算类的大小的时候,并不是将类的成员函数计算在内的。
解释:由于一个类可以创建很多的对象,这个时候类的成员函数是一样的,为了节省空间,类的成员函数是不创建在栈中,而是将成员函数存放在常量区中。在计算大小的时候,就不包含成员函数。

计算类的大小:
(1)成员函数是存放在常量区,计算的时候不占用大小。
(2)成员大小的计算规则与C中结构体的大小计算方法一致,需要准守对齐规则。
这里写图片描述

(3)空类的大小是一个字节,是用于占位。

二、隐藏的this指针

每一个类的成员函数有一个隐藏的this指针,这个指针会作为成员函数的第一个参数,它会将对象的地址作为实参进行传入。这个this指针是固定的,名字确定唯一,所以我们不能够显示的定义this,并且不能够在成员函数中调用。编译器会自动处理调用。

以Data类为例子

class Data{public:    void Show()    {        cout << "Show()" << endl;    }private:    int _year;    int _month;    int _day;};

这里写图片描述

this指针可以很好的解释,在共用一个成员函数的情况下,编译器可以在没有传参的时候,准确的知道是哪一个对象需要调用这个函数。
这里写图片描述

三、默认成员函数

默认成员函数在没有显示的创建的时候,编译器会自动的创建相应的成员函数。一般,我们都会自定义成员函数。此外,默认的成员函数有一个隐藏的this指针。

1)构造函数

函数形式:

class Data{public:    Data()    {        cout << "Data()" << endl;    }private:    int _year;    int _month;    int _day;};

此时的函数person()便是构造函数。

作用:
构造函数用于将对象在创建的时候进行初始化,这样当对象被创建出来就会被初始化

特点:

  1. 函数名与类的名字一样
  2. 构造函数没有返回值
  3. 在实例化对象的时候,构造函数被系统自动调用
  4. 构造函数可以被重载,就像拷贝构造的时候
  5. 可以在类中定义,也可以在类外面定义
  6. 如果没有自定义一个构造函数,系统将自动创建一个缺省的构造函数,是无参的
  7. 无参的构造函数和全缺省的构造函数只能存在一个。两者同时存在的时候,如果没有给构造函数进行传参时,不知道调用哪一个。

构造函数的例子

class Data{public:    //无参    Data()    {        cout << "Data()" << endl;    }    //全缺省    Data(int year = 2000, int month = 1, int day = 1)        //参数列表,用于赋值        :_year(year)        , _month(month)        , _day(day)    {        cout << "Data(int year = 2000, int month = 1, int day = 1)" << endl;    }    //以上两者只能存在一个private:    int _year;    int _month;    int _day;};int main(){    Data d1;//无参的构造函数的创建对象    Data d2(2001, 1, 1);//传参和全缺省的构造函数的创建对象    //事实上,以上两者不能一起存在    //错误的调用构造函数 无参的时候,不能够带上()    Data d3();    return 0;}

2)拷贝构造函数

函数形式

    Data(const Data&d)        :_year(d._year)        , _month(d._month)        , _day(d._day)    {        cout << "Data(const Data&d)" << endl;    }

作用:
拷贝构造函数是利用已经创建的同类对象来初始化当前对象的一种方式,使当前对象与参照的对象初始化一致

特点:

  1. 拷贝构造是构造函数的重载
  2. 拷贝构造函数的参数必须用引用
  3. 如果没有显示的创建拷贝构造函数,系统将默认的创建一个缺省的函数,但是这个时候进行的是值拷贝。一般来说我们都不喜欢值拷贝,在有指针的时候,会出现大问题。
  4. 如果定义了拷贝构造函数,那么就不会有缺省的构造函数生成,因为拷贝构造就是构造函数的重载。

为什么拷贝构造一定要进行引用传参:
形参是需要临时拷贝的,如果不是进行引用传参的时候,那么在创建临时变量时,又需要调用拷贝构造进行临时变量的创建,这样就会陷入一个死循环。
这里写图片描述

拷贝构造的时候,第一个参数就是this指针,这样函数才知道应该参考哪一个对象。此时this指针指向d2的地址,d1是引用。这样拷贝构造的时候,d1中的每一个成员值,都相应的赋给了d2中的成员。
这里写图片描述

3)析构函数
函数形式:

    ~Data()    {        cout << "~Data()" << endl;    }

作用:
析构函数是用来在程序结束的时候,进行一些清理工作的函数。

特点:

  1. 析构函数的函数形式就是在类名之前加上‘~’
  2. 析构函数没参数,没有返回值,但其中还是隐藏了this指针
  3. 一个类有且只有一个析构函数
  4. 生命周期结束的时候,系统会自动调用析构函数
  5. 析构函数不是对空间的销毁,而是进行一些清理工作,例如将动态申请的空间返回,对打开的文件进行关闭
  6. 析构函数先对后调用的函数进行使用,因为这个是栈的特性,后进先出

析构函数不一定要进行操作,但是对于函数有进行了动态申请的时候或者打开了文件,就需要一定的清理工作。

一个注意的点:
拷贝构造如果不进行自定义的创建的时候,系统会自动的创建缺省的拷贝构造函数。但是这个时候进行的是浅拷贝,如果这个时候,原拷贝对象包含了动态申请的空间,浅拷贝会导致两个指针指向了同一块空间。当析构函数进行清理,就会因为对同一块空间进行了两次释放出现崩溃。

这里写图片描述

如图:因为没有显示的调用拷贝构造函数,程序利用的是系统调用的拷贝构造函数,这样_a指针,指向了同一块空间,不仅在析构的时候会出现问题,而且在s1或者s2其中之一的_a进行了修改,会影响各自对象的值。

4)运算符的重载
函数形式:
由关键返回类型+operator+重载的运算符(参数)构成

作用:
运算符的重载是为了增强函数的可读性

特点:

  1. 运算符的重载是为了使自定义类型,也可以像内置类型一样可以使用运算符,这样会增强函数的可读性。
  2. 当运算符的重载函数定义为成员函数,这个时候,左操作数自动被隐藏的this指针所占。
  3. 运算符的重载,不能够改变其优先级、结合性、操作数个数

一个例子:
在日期类内重载了日期了的大小比较运算符

//在Data()中定义的日期大小比较bool operator <(const Data& d)    {        if (_year < d._year)        {            return true;        }        else if (_year == d._year)        {            if (_month == d._month)            {                return true;            }            else if (_month == d._month)            {                if (_day == d._day)                {                    return true;                }            }        }        return false;    }//在main()中具体的调用,以下两种都是正确的,明显我们是为了第一种表达。    d1 < d2;    d1.operator<(d2);

这里写图片描述

阅读全文
0 0
原创粉丝点击