精华内容
下载资源
问答
  • Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。 如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些...

    27Python面向对象(Python2)

    Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的。本章节我们将详细介绍Python的面向对象编程。
    如果你以前没有接触过面向对象的编程语言,那你可能需要先了解一些面向对象语言的一些基本特征,在头脑里头形成一个基本的面向对象的概念,这样有助于你更容易的学习Python的面向对象编程。
    接下来我们先来简单的了解下面向对象的一些基本特征。

    27.1面向对象技术简介

    类(Class): 用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。
    类变量:类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。
    数据成员:类变量或者实例变量, 用于处理类及其实例对象的相关的数据。
    方法重写:如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。
    局部变量:定义在方法中的变量,只作用于当前实例的类。
    实例变量:在类的声明中,属性是用变量来表示的。这种变量就称为实例变量,是在类声明的内部但是在类的其他成员方法之外声明的。
    继承:即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个Dog类型的对象派生自Animal类,这是模拟"是一个(is-a)"关系(例图,Dog是一个Animal)。
    实例化:创建一个类的实例,类的具体对象。
    方法:类中定义的函数。
    对象:通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法。

    27.2创建类

    使用class语句来创建一个新类,class之后为类的名称并以冒号结尾:

    class ClassName:
        ‘类的帮助信息’               #类文档字符串
        class_suite                   #类体
    

    类的帮助信息可以通过ClassName.__doc__查看。
    class_suite由类成员,方法,数据属性组成。

    27.2.1实例

    以下是一个简单的Python类的例子:

    # -*- coding: UTF-8 -*-
    
    class Employee:
        '所有员工的基类'
        empCount = 0
    
        def __init__(self, name, salary):
            self.name = name
            self.salary = salary
            Employee.empCount += 1
    
        def displayCount(self):
            print("Total Employee %d" % Employee.empCount)
    
        def displayEmployee(self):
            print("Name:",self.name,"Salary:",self.salary)
    

    empCount变量是一个类变量,它的值将在这个类的所有实例之间共享。你可以在内部类或外部使用Employee.empCount访问。
    第一种方法__init__()方法是一种特殊的方法,被称为类的构造函数或初始化方法,当创建了这个类的实例时就会调用该方法。
    self代表类的实例,self在定义类的方法时是必须有的,虽然在调用时不必传入相应的参数。

    27.2.2self代表类的实例,而非类

    类的方法与普通的函数只有一个特别的区别----它们必须有一个额外的第一个参数名称,按照惯例它的名称是self。

    # -*- coding: UTF-8 -*-
    
    class Test:
        def prt(self):
            print(self)
            print(self.__class__)
    
    
    t = Test
    t.prt(t)
    

    运行结果:

    <class '__main__.Test'>
    <class 'type'>
    

    从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。
    self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的:

    # -*- coding: UTF-8 -*-
    
    class Test:
        def prt(runoob):
            print(runoob)
            print(runoob.__class__)
    
    
    t = Test
    t.prt(t)
    

    运行结果和上一个实例的结果一样,都是:

    <class '__main__.Test'>
    <class 'type'>
    

    27.3创建实例对象

    实例化类其他编程语言中一般用关键字 new,但是在 Python 中并没有这个关键字,类的实例化类似函数调用方式。
    以下使用类的名称Employee来实例化,并通过__init__方法接收参数。

    “创建Employee类的第一个对象”
    emp1 = Employee(“Zara”,2000)
    “创建Employee 类的第二个对象”
    emp2 = Employee(“Manni”,5000) 
    

    27.4访问属性

    您可以使用点号 . 来访问对象的属性。使用如下类的名称访问类变量:

    emp1.displayEmployee()
    emp2.displayEmployee()
    print "Total Employee %d" % Employee.empCount
    

    完整实例:

    # -*- coding: UTF-8 -*-
    
    class Employee:
        '所有员工的基类'
        empCount = 0
    
        def __init__(self,name,salary):
            self.name = name
            self.salary = salary
            Employee.empCount += 1
    
        def displayCount(self):
            print("Total Employee %d" % Employee.empCount)
    
        def displayEmployee(self):
            print("Name: ",self.name,",Salary:",self.salary)
    
    "创建 Employee 类的第一个对象"
    emp1 = Employee("Zara", 2000)
    "创建 Employee 类的第二个对象"
    emp2 = Employee("Manni", 5000)
    emp1.displayEmployee()
    emp2.displayEmployee()
    print("Total Employee %d" % Employee.empCount)
    

    运行结果:

    Name:  Zara ,Salary: 2000
    Name:  Manni ,Salary: 5000
    Total Employee 2
    

    你可以添加,删除,修改类的属性,如下所示(看案例的最后几段):

    # -*- coding: UTF-8 -*-
    
    class Employee:
        '所有员工的基类'
        empCount = 0
    
        def __init__(self,name,salary):
            self.name = name
            self.salary = salary
            Employee.empCount += 1
    
        def displayCount(self):
            print("Total Employee %d" % Employee.empCount)
    
        def displayEmployee(self):
            print("Name: ",self.name,",Salary:",self.salary)
    
    "创建 Employee 类的第一个对象"
    emp1 = Employee("Zara", 2000)
    "创建 Employee 类的第二个对象"
    emp2 = Employee("Manni", 5000)
    emp1.displayEmployee()
    emp2.displayEmployee()
    print("Total Employee %d" % Employee.empCount)
    
    # 可以添加,删除,修改类的属性,如下所示:
    emp1.age = 7    #添加一个‘age’属性
    emp1.age = 8    #修改'age'属性
    print(emp1.age)
    del emp1.age    #删除'age'属性
    

    运行结果:

    Name:  Zara ,Salary: 2000
    Name:  Manni ,Salary: 5000
    Total Employee 2
    8
    

    你也可以使用以下函数的方式来访问属性:
    getattr(obj,name[,default]):访问对象的属性
    hasattr(obj,name):检查是否存在一个属性
    setattr(obj,name,value):设置一个属性,如果属性不存在,会创建一个新属性。
    delattr(obj,name):删除属性

    查看以下案例,最后几行:

    # -*- coding: UTF-8 -*-
    
    class Employee:
        '所有员工的基类'
        empCount = 0
    
        def __init__(self,name,salary):
            self.name = name
            self.salary = salary
            Employee.empCount += 1
    
        def displayCount(self):
            print("Total Employee %d" % Employee.empCount)
    
        def displayEmployee(self):
            print("Name: ",self.name,",Salary:",self.salary)
    
    "创建 Employee 类的第一个对象"
    emp1 = Employee("Zara", 2000)
    "创建 Employee 类的第二个对象"
    emp2 = Employee("Manni", 5000)
    emp1.displayEmployee()
    emp2.displayEmployee()
    print("Total Employee %d" % Employee.empCount)
    
    # 可以添加,删除,修改类的属性,如下所示:
    # emp1.age = 7    #添加一个‘age’属性
    # emp1.age = 8    #修改'age'属性
    # print(emp1.age)
    # del emp1.age    #删除'age'属性
    
    setattr(emp1,'age',18)           #添加属性'age'值为8
    hasattr(emp1,'gender')           #如果存在‘gender’属性返回True
    print(getattr(emp1,'age'))       #返回‘age’属性的值
    setattr(emp1,'age',20)           #添加属性'age'值为8
    print(emp1.age)
    #delattr(emp1,'age')
    

    运行结果:

    Name:  Zara ,Salary: 2000
    Name:  Manni ,Salary: 5000
    Total Employee 2
    18
    20
    

    27.5Python内置类属性

    __dict__ : 类的属性(包含一个字典,由类的数据属性组成)
    __doc__ :类的文档字符串
    __name__: 类名
    __module__: 类定义所在的模块(类的全名是’main.className’,如果类位于一个导入模块mymod中,那么className.module 等于 mymod)
    __bases__ : 类的所有父类构成元素(包含了一个由所有父类组成的元组)

    Python内置类属性调用实例如下:

    # -*- coding: UTF-8 -*-
    
    class Employee:
        '所有员工的基类'
        empCount = 0
    
        def __init__(self,name,salary):
            self.name = name
            self.salary = salary
            Employee.empCount += 1
    
        def displayCount(self):
            print("Total Employee %d" % Employee.empCount)
    
        def displayEmployee(self):
            print("Name : ", self.name,  ", Salary: ", self.salary)
    
    
    print("Employee.__doc__",Employee.__doc__)
    print("Employee.__name__",Employee.__name__)
    print("Employee.__module__",Employee.__module__)
    print("Employee.__bases__",Employee.__bases__)
    print("Employ.__dict__:",Employee.__dict__)
    

    运行结果:

    Employee.__doc__ 所有员工的基类
    Employee.__name__ Employee
    Employee.__module__ __main__
    Employee.__bases__ (<class 'object'>,)
    Employ.__dict__: {'__module__': '__main__', '__doc__': '所有员工的基类', 'empCount': 0, '__init__': <function Employee.__init__ at 0x00000225B41765E8>, 'displayCount': <function Employee.displayCount at 0x00000225B4176558>, 'displayEmployee': <function Employee.displayEmployee at 0x00000225B41764C8>, '__dict__': <attribute '__dict__' of 'Employee' objects>, '__weakref__': <attribute '__weakref__' of 'Employee' objects>}
    

    27.6Python对象销毁(垃圾回收)

    Python 使用了引用计数这一简单技术来跟踪和回收垃圾。

    在 Python 内部记录着所有使用中的对象各有多少引用。
    一个内部跟踪变量,称为一个引用计数器。

    当对象被创建时, 就创建了一个引用计数, 当这个对象不再需要时, 也就是说, 这个对象的引用计数变为0 时, 它被垃圾回收。但是回收不是"立即"的, 由解释器在适当的时机,将垃圾对象占用的内存空间回收。

    a = 40     #创建对象   <40>
    b = a      #增加引用   <40>的计数
    c = [b]     #增加引用.  <40>的计数
    
    del a      #减少引用 <40>的计数
    b = 100    #减少引用 <40>的计数
    c[0] = -1   #减少引用 <40>的计数
    

    垃圾回收机制不仅针对引用计数为0的对象,同样也可以处理循环引用的情况。循环引用指的是,两个对象相互引用,但是没有其他变量引用他们。这种情况下,仅使用引用计数是不够的。Python 的垃圾收集器实际上是一个引用计数器和一个循环垃圾收集器。作为引用计数的补充, 垃圾收集器也会留心被分配的总量很大(及未通过引用计数销毁的那些)的对象。 在这种情况下, 解释器会暂停下来, 试图清理所有未引用的循环。

    实例:
    析构函数__del__,__del__在对象销毁的时候被调用,当对象不再被使用时,__del__方法运行:

    # -*- coding: UTF-8 -*-
    
    class Point:
        def __init__(self, x=0, y=0):
            self.x = x
            self.y = y
    
        def __del__(self):
            class_name = self.__class__
            print(class_name, "销毁")
    
    
    pt1 = Point()
    pt2 = pt1
    pt3 = pt1
    print(id(pt1), id(pt2), id(pt3))
    del pt1
    del pt2
    del pt3
    

    运行结果:

    2489690751816 2489690751816 2489690751816
    <class '__main__.Point'> 销毁
    

    注意:通常你需要在单独的文件中定义一个类。

    27.7类的继承

    面向对象的编程带来的主要好处之一是代码的重用,实现这种重用的方法之一是通过继承机制。
    通过继承创建的新类称为子类或派生类,被继承的类称为基类、父类或超类。
    继承语法

    class 派生类别(基类名)
        ...
    

    在python中继承中的一些特点:
    1、如果在子类中需要父类的构造方法就需要显示的调用父类的构造方法,或者不重写父类的构造方法。详细说明查看:Python子类继承父类构造函数说明。(https://www.runoob.com/w3cnote/python-extends-init.html)
    2、在调用基类的方法时,需要加上基类的类名前缀,且需要带上self参数变量。区别在于类中调用普通函数时并不需要上self参数。
    3、Python总是首先查找对应类型的方法,如果它不能在派生类中找到对应的方法,它才开始到基类中逐个查找。(先在本类中查找调用的方法,找不到才去基类中找)。

    如果在继承元组中列了一个以上的类,那么它就被称作”多重继承”。
    语法:
    派生类的声明,与他们的父类类似,继承的基类列表跟在类名之后,如下所示:

    class SubClassName (ParentClass1[,ParentClass2,...]):
        ...
    

    实例:

    # -*- coding: UTF-8 -*-
    
    class Parent:  # 定义父类
        parentAttr = 100
    
        def __init__(self):
            print("调用父类构造函数")
    
        def parentMethod(self):
            print("调用父类方法")
    
        def setAttr(self, attr):
            Parent.parentAttr = attr
    
        def getAttr(self):
            print("父类属性:", Parent.parentAttr)
    
    
    class Child(Parent):  # 定义子类
        def __init__(self):
            print("调用子类构造方法")
    
        def childMethod(self):
            print('调用子类方法')
    
    
    c = Child()  # 实例化子类
    c.childMethod()  # 调用子类的方法
    c.parentMethod()  # 调用父类方法
    c.setAttr(200)  # 再次调用父类的方法 - 设置属性值
    c.getAttr()  # 再次调用父类的方法 - 获取属性值
    

    运行结果:

    调用子类构造方法
    调用子类方法
    调用父类方法
    父类属性: 200
    

    你可以继承多个类

    class A:      #定义类A
    ......
    
    class B       #定义类B
    ......
    
    class C(A,B):   #继承类A和B
    

    你可以使用issubclass()或者isinstance()方法来检测。
    issubclass() - 布尔函数判断一个类是另一个类的子类或者子孙类,语法:issubclass(sub,sup)
    Isinstance(obj,Class)布尔函数如果obj是Class类的实例对象或者是一个Class子类的实例对象则返回true。

    27.8方法重写

    如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法:
    实例:

    # -*- coding: UTF-8 -*-
    
    class Parent:  # 定义子类
        def myMethod(self):
            print("调用父类方法")
    
    
    class Child(Parent):  # 定义子类
        def myMethod(self):
            print("调用子类方法")
    
    
    c = Child()  # 子类实例
    c.myMethod()  # 子类调用重写方法
    

    运行结果:

    调用子类方法
    

    27.9基础重载方法

    下表列出了一些通用的功能,你可以在自己的类重写:
    在这里插入图片描述

    27.10运算符重载

    Python同样支持运算符重载,实例如下:

    # -*- coding: UTF-8 -*-
    
    class Vector:
        def __init__(self, a, b):
            self.a = a
            self.b = b
    
        def __str__(self):
            return 'Vector (%d, %d)' % (self.a, self.b)
    
        def __add__(self, other):
            return Vector(self.a + other.a, self.b + other.b)
    
    
    v1 = Vector(2, 10)
    v2 = Vector(5, -2)
    print(v1 + v2)
    以上代码执行结果如下所示:
    Vector (7, 8)
    

    27.11类属性与方法

    27.11.1类的私有属性

    __private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直观访问。在类内部的方法中使用时self.__private_attrs。

    27.11.2类的方法

    在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数

    27.11.3类的私有方法

    __private_method:两个下划线开头,声明该方法为私有方法,不能在类的外部调用。在类的内部调用 self.__private_methods
    实例:

    # -*- coding: UTF-8 -*-
    
    class JustCounter:
        __secretCount = 0  #私有变量
        publicCount = 0    #公开变量
    
        def count(self):
            self.__secretCount += 1
            self.publicCount += 1
            print(self.__secretCount)
    
    
    counter = JustCounter()
    counter.count()
    counter.count()
    print(counter.publicCount)
    #print(counter.__secretCount)  #报错,实例不能访问私有变量
    

    运算结果:

    1
    2
    2
    

    Python不允许实例化的类访问私有数据,但你可以使用object._className__attrName(对象名._类名__私有属性名)访问属性,参考以下实例:

    # -*- coding: UTF-8 -*-
    
    class Runoob:
        __site = "www.runoob.com"
    
    
    runoob = Runoob()
    print(runoob._Runoob__site)
    

    运行结果:

    www.runoob.com
    

    27.12单下划线、双下划线、头尾双下划线说明

    __foo__: 定义的是特殊方法,一般是系统定义名字 ,类似 init() 之类的。
    _foo: 以单下划线开头的表示的是 protected 类型的变量,即保护类型只能允许其本身与子类进行访问,不能用于 from module import *
    __foo: 双下划线的表示的是私有类型(private)的变量, 只能是允许这个类本身进行访问了。

    展开全文
  • 类和对象类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance),拥有类的成员变量和成员函数。与结构体一样,类...

    类和对象

    类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance),拥有类的成员变量和成员函数。

    与结构体一样,类只是一种复杂数据类型的声明,不占用内存空间。而对象是类这种数据类型的一个变量,或者说是通过类这种数据类型创建出来的一份实实在在的数据,所以占用内存空间。
    类的定义
    类是用户自定义的类型,如果程序中要用到类,必须提前说明,或者使用已存在的类(别人写好的类、标准库中的类等),C++语法本身并不提供现成的类的名称、结构和内容。

    一个简单的类的定义:

    #include<iostream>
    using namespace std;
    class Student{
    private:
        //成员变量
        char *name;
        int age;
        float score;
    public:
        //成员函数
        void say(){
            cout << name << "的年龄是" << age << ",成绩是" << score << endl;
        }
    };

    class是 C++ 中新增的关键字,专门用来定义类。Student是类的名称;类名的首字母一般大写,以和其他的标识符区分开。{ }内部是类所包含的成员变量和成员函数,它们统称为类的成员(Member);由{ }包围起来的部分有时也称为类体,和函数体的概念类似。
    访问限定符:public也是 C++ 的新增关键字,它只能用在类的定义中,表示类的成员变量或成员函数具有“公开”的访问权限。
    与之对应的是private关键字,有“保护”权限。
    注意在类定义的最后有一个分号;,它是类定义的一部分,表示类定义结束了,不能省略。
    整体上讲,上面的代码创建了一个 Student 类,它包含了 3 个成员变量和 1 个成员函数。

    类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了。

    类可以理解为一种新的数据类型,该数据类型的名称是 Student。与 char、int、float 等基本数据类型不同的是,Student 是一种复杂数据类型,可以包含基本类型,而且还有很多基本类型中没有的特性,以后大家会见到。
    创建对象

    有了 Student 类后,就可以通过它来创建对象了,例如:

    Student liLei;  //创建对象

    Student是类名,liLei是对象名。这和使用基本类型定义变量的形式类似:

    int a;  //定义整型变量

    从这个角度考虑,我们可以把 Student 看做一种新的数据类型,把 liLei 看做一个变量。

    在创建对象时,class 关键字可要可不要,但是出于习惯我们通常会省略掉 class 关键字,例如:

    class Student LiLei;  //正确
    Student LiLei;  //同样正确

    除了创建单个对象,还可以创建对象数组:

    Student allStu[100];

    该语句创建了一个 allStu 数组,它拥有100个元素,每个元素都是 Student 类型的对象。
    访问类的成员

    创建对象以后,可以使用点号.来访问成员变量和成员函数,这和通过结构体变量来访问它的成员类似,如下所示:

    #include<iostream>
    using namespace std;
    //类通常定义在函数外面
    class Student{
    public:
        //成员变量
        char *name;
        int age;
        float score;
    public:
        //成员函数
        void say(){
            cout << name << "的年龄是" << age << ",成绩是" << score << endl;
        }
    };
    int main(){
        //创建对象
        Student stu;
        stu.name = "小明";
        stu.age = 15;
        stu.score = 92.5f;
        stu.say();
        return 0;
    }
    

    运行结果:
    小明的年龄是15,成绩是92.5

    stu 是一个对象,占用内存空间,可以对它的成员变量赋值,也可以读取它的成员变量。
    面现对象封装性
    封装是实现信息隐蔽的一种技术,其目的是使类的定义和实现分离。C++的一个特性,相信很多的人自认为理解,完全的理解,至少我是这么认为的,在三个特性中我认为封装性最容易理解,但是随着对面向对象的了解和认识,我对封装性有了新的认识,感觉以前的认识十分的片面。正如上面所说封装性是c++语言的特性,而语言是用来约束程序员的,以前的理解就是类像一个包,而里面有一些数据,程序员的代码不能随便访问。然而这种封装是比较片面的。

    Everything has a purpose,而封装存在的理由就是重用,重用就是写的一个代码库可以在很多地方重用,而且易于扩充。在一块内存中可以被许多应用程序运用,从开发的角度这样十分的省事,不必做重复的工作,在使用的角度,十分的节约内存,以前一个程序要加载一个库,现在几个程序只需加载一个库就可以了。这就是重用,使用以前的概念是无法实现这个目的的。聪明的程序员解决了这个问题。使用com组件技术,做到了二进制级别的代码重用。相当于扩充了操作系统,因为操作系统就是一个开放的接口,供应用程序调用功能。这就需要二进制级别的封装,

    而由于一样的c++代码由不同的编译器编译出来的代码不一样,造成了代码之间的兼容问题。因为你封装起来的东西给了使用不同编译器的程序员获得的代码是不一样的,这样就不具备扩充性和灵活性。不能实现代码重用。

    所以c++语言级别的封装性是十分狭隘的。

    各种角度看封装

    用户:我只需要个能用的,能升级的产品。

    客户程序员:我需要的是可扩展的,封装的,插件式的库,这个库的东西不能影响到程序框架中的其他部分

    库程序员:给客户程序员一个接口和一个二进制级别的代码。以实现可扩充行和可重用性。

    内存:对我而言没有封装,计算机的一个基本概念,越往底层限制的东西越少。

    编译器:我编译出来的代码要具有封装性,必须处理我和同行之间的差异。

    C++语言:无辜的我是用来限制程序员的。但是也可以使用我的巧妙规则开提高你的水平,来实现你所需要的重用和扩充性。

    对象的大小、计算
    对象的大小计算依旧遵守内存对齐的原则
    结构体内存对其规则:
    1.第一个成员在与结构体变量偏移量为0的地址处。
    2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    //对齐数=编译器默认的一个对齐数与该成员大小的较小值。
    VS中默认的值为8
    gcc中的默认值为4
    3.结构体总大小为最大对齐数(每个成员变量除了第一个成员都有一个对齐数)的整数倍。
    4.如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体
    的对齐数)的整数倍。

    空类对象(无成员变量的类)的大小也不一样,VC中为1。
    实例化的原因(空类同样可以被实例化),每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址,所以大小为1。
     当然在不同的编译器上得到的结果可能不同,但是这个实验告诉我们,不管类是否为空类,是否有成员变量,这个类在创建对象的时候都是需要分配空间的。

    四个默认成员函数

    1.构造函数
    成员变量为私有的,要对它们进行初始化,必须用一个公有成员函数来进行。同时这个函数应该有且仅在定义对象时自动执行一次,这时
    调用的函数称为构造函数(constructor)。
    构造函数是特殊的成员函数,其特征如下:
    1. 函数名与类名相同。
    2. 无返回值。
    3. 对象构造(对象实例化)时系统自动调用对应的构造函数。
    4. 构造函数可以重载。
    5. 构造函数可以在类中定义,也可以在类外定义。
    6. 如果类定义中没有给出构造函数,则C++编译器自动产生一个缺省的构造函数,但只要我们定义了一个构造函数,系统就不会自动
    生成缺省的构造函数。
    7. 无参的构造函数和全缺省值的构造函数都认为是缺省构造函数,并且缺省的构造函数只能有一个。
    2.拷贝构造函数
    创建对象时使用同类对象来进行初始化,这时所用的构造函数称为拷贝构造函数(Copy Constructor),拷贝构造函数是特殊的构造函
    数。
    特征:
    1. 拷贝构造函数其实是一个构造函数的重载。
    2. 拷贝构造函数的参数必须使用引用传参,使用传值方式会引发无穷递归调用。(思考为什么?)
    先从一个小例子开始:(自己测试一下自己看看这个程序的输出是什么?)

    #include<iostream>  
    using namespace std;  
    
    class CExample  
    {  
    private:  
        int m_nTest;  
    
    public:  
        CExample(int x) : m_nTest(x)      //带参数构造函数  
        {   
            cout << "constructor with argument"<<endl;  
        }  
    
        // 拷贝构造函数,参数中的const不是严格必须的,但引用符号是必须的  
        CExample(const CExample & ex)     //拷贝构造函数  
        {  
            m_nTest = ex.m_nTest;  
            cout << "copy constructor"<<endl;  
        }  
    
        CExample& operator = (const CExample &ex)//赋值函数(赋值运算符重载)  
        {     
            cout << "assignment operator"<<endl;  
            m_nTest = ex.m_nTest;  
            return *this;  
        }  
    
        void myTestFunc(CExample ex)  
        {  
        }  
    };  
    
    int main(void)  
    {  
        CExample aaa(2);  
        CExample bbb(3);  
        bbb = aaa;  
        CExample ccc = aaa;  
        bbb.myTestFunc(aaa);  
    
        return 0;     
    }  

    这个例子的输出结果是:
    [cpp] view plain copy
    constructor with argument // CExample aaa(2);
    constructor with argument // CExample bbb(3);
    assignment operator // bbb = aaa;
    copy constructor // CExample ccc = aaa;
    copy constructor // bbb.myTestFunc(aaa);
    如果你能一眼看出就是这个结果的话, 恭喜你,可以站起来扭扭屁股,不用再往下看了。

    如果你的结果和输出结果有误差, 那拜托你谦虚的看完。

    第一个输出: constructor with argument // CExample aaa(2);

    如果你不理解的话, 找个人把你拖出去痛打一顿,然后嘴里还喊着“我是二师兄,我是二师兄…….”

    第二个输出:constructor with argument // CExample bbb(3);

    分析同第一个

    第三个输出: assignment operator // bbb = aaa;

    第四个输出: copy constructor // CExample ccc = aaa;

    这两个得放到一块说。 肯定会有人问为什么两个不一致。原因是, bbb对象已经实例化了,不需要构造,此时只是将aaa赋值给bbb,只会调用赋值函数,就这么简单,还不懂的话,撞墙去! 但是ccc还没有实例化,因此调用的是拷贝构造函数,构造出ccc,而不是赋值函数,还不懂的话,我撞墙去!!

    第五个输出: copy constructor // bbb.myTestFunc(aaa);

    实际上是aaa作为参数传递给bbb.myTestFunc(CExample ex), 即CExample ex = aaa;和第四个一致的, 所以还是拷贝构造函数,而不是赋值函数, 如果仍然不懂, 我的头刚才已经流血了,不要再让我撞了,你就自己使劲的再装一次吧。

    通过这个例子, 我们来分析一下为什么拷贝构造函数的参数只能使用引用类型。

    看第四个输出: copy constructor // CExample ccc = aaa;

    构造ccc,实质上是ccc.CExample(aaa); 我们假如拷贝构造函数参数不是引用类型的话, 那么将使得 ccc.CExample(aaa)变成aaa传值ccc.CExample(CExample ex),即CExample ex = aaa,因为 ex 没有被初始化, 所以 CExample ex = aaa 继续调用拷贝构造函数,接下来的是构造ex,也就是 ex.CExample(aaa),必然又会有aaa传给CExample(CExample ex), 即 CExample ex = aaa;那么又会触发拷贝构造函数,就这下永远的递归下去。

    所以绕了那么大的弯子,就是想说明拷贝构造函数的参数使用引用类型不是为了减少一次内存拷贝, 而是避免拷贝构造函数无限制的递归下去。
    3. 若未显示定义,系统会默认缺省的拷贝构造函数。缺省的拷贝构造函数会,依次拷贝类成员进行初始化。
    3.析构函数
    当一个对象的生命周期结束时,C++编译系统会自动调用一个成员函数,这个特殊的成员函数即析构函数(destructor)
    构造函数是特殊的成员函数,其特征如下:
    1. 析构函数在类名加上字符~。
    2. 析构函数无参数无返回值。
    3. 一个类有且只有一个析构函数。若未显示定义,系统会自动生成缺省的析构函数。
    4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。
    5. 注意析构函数体内并不是删除对象,而是做一些清理工作。
    4.赋值运算符重载
    拷贝构造函数是创建的对象,使用一个已有对象来初始化这个准备创建的对象。
    赋值运算符的重载是对一个已存在的对象进行拷贝赋值。

    运算符重载

    为了增强程序的可读性,C++支持运算符重载。
    运算符重载特征:
    1. operator+合法的运算符构成函数名(重载<运算符的函数名:operator<)。
    2. 重载运算符以后,不能改变运算符的优先级/结合性/操作数个数。
    5个C++不能重载的运算符: .* /:: /sizeof /?: /.

    隐含的this指针

    1. 每个成员函数都有一个指针形参,它的名字是固定的,称为this指针,this指针是隐式的。(构造函数比较特殊,没有这个隐含this形参)
    2. 编译器会对成员函数进行处理,在对象调用成员函数时,对象地址作实参传递给成员函数的第一个形参this指针。
    3. this指针是成员函数隐含指针形参,是编译器自己处理的,我们不能在成员函数的形参中添加this指针的参数定义,也不能在调用时显示传递对象的地址给this指针。
    展开全文
  • 类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance) ,拥有类的成员变量和成员函数。class Person { public: ...

    一.类
    1.类的定义
    类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance)
    ,拥有类的成员变量和成员函数。

    class Person
    {
    public:
        void  Display()
        {
            cout << _name << ":" << _age << ":"
                 << _sex << ":" << _id << endl;
    
        }
        char* _name;
        int _age;
        char* _sex;
        int _id; 
    };
    void main()
    {
        Person p;
        p._name = "Peter";
        p._age = 18;
        p._sex = "boy";
        p._id = 001;
        p.Display();//不能直接调用Display,在使用前要加对象。

    2.访问限定符
    public,private,protected
    a.public成员可以从类外直接访问,private和protected成员不能从类外部直接访问到
    b.每个限定符可以在类中多次使用,它的作用域是从该限定符出现到下一个限定符之前或类体结束之前
    c.类体中如果没有定义限定符,则默认为私有的
    d.类的访问限定符体现了面向对象的封装性
    3.对象的大小的计算
    所有变量之和(不算成员函数,成员函数存在于公共代码区)
    在计算时遵从内存对齐:
    内存对齐可以提高效率

    class BB
    {
    
    };
    

    输出时

    cout<<sizeof(BB)<<endl;
    

    结果为1。起到占位作用,表示这个类定义的对象是存在的
    二.类的6个默认成员函数

    1.构造函数
    定义:要对私有的成员函数进行初始化,必须用一个公有的成员函数来进行。并且只在定义对象是自动执行一次。
    特征:a.函数名和类名相同
    b.无返回值
    c.对象构造时系统自动调用对应的构造函数
    d.可以重载
    e.可以在类外定义,也可以在类体里定义
    f.如果类定义中没有给出构造函数,则会自动生成一个缺省的构造函数(不传参数)
    g.无参的构造函数和全缺省的构造函数都认为是缺省的构造函数,缺省的构造函数只能有一个

    class Date
    {
    public:
        void Display()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
        //无参的构造函数
        Date()
        {}
        //有参的构造函数
        Date(int year, int month, int day)
        {
            _year = year;
            _month = month;
            _day = day;
    
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
    

    2.拷贝构造函数
    定义:创建对象时用同类对象初始化
    特征:a.是构造函数的重载
    b.参数必须引用传参,否则会无穷递归
    c.没有显示定义,系统会自动生成缺省的拷贝构造,依次拷贝类成员进行初始化

    class Date
    {
    public:
        void Display()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
        Date(int year = 1990,int month = 1 ,int day = 1)
        {
            _year = year;
            _month = month;
            _day = day;
        }
        Date(const Date& d)
        {
            _year = d._year;
            _month = d._month;
            _day = d._day;
        }//拷贝构造函数
    private:
        int _year;
        int _month;
        int _day;
    };
    
    

    3.析构函数
    定义:当一个对象的生命周期结束后,C++自动调用一个成员函数
    特征:a.析构函数在类名前加~
    b.无参无返回值
    c.一个类有且只有一个析构函数,未定义时系统会自动生成缺省的析构函数
    d.对象生命周期结束时,C++自动调用
    e.进行清理工作

    class Date
    {
    public:
        ~Date()
        {}
    private:
        int _year;
        int _month;
        int _day;
    };
    
    

    4.赋值操作符重载
    运算符重载:operator+合法的运算符构成函数名
    不能改变优先级/结合性/操作数个数
    5个不能重载的运算符: .* :: sizeof ?: .
    赋值操作符重载定义:对一个已存在的对象进行拷贝赋值

    class Date
    {
    public:
        Date(int year = 1990,int month = 1 ,int day = 1)
        {
            _year = year;
            _month = month;
            _day = day;
        }
    
        void Display()
        {
            cout << _year << "-" << _month << "-" << _day << endl;
        }
            //bool operator == (Date* this,const Date& d)
        bool operator == (const Date& d)//隐含一个this指针
        {
            return _year == d._year
                && _month == d._month
                && _day == d._day;
        }
    private:
        int _year;
        int _month;
        int _day;
    };
    
        Date operator = (const Date& d)
        {
            if (this != &d)
            {
                this->_year = d._year;
                _month = d._month;
                _day = d._day;
            }
            return *this;
        }
    
    

    这里写图片描述

    d1 = d2 = d3;
    Date& operator = (const Date& d)
    //编译器系统会对以上代码做什么样的处理?
    
    
    //d2.operator = (&d2,d3) =>return *this(d2)
    //d1.operator = (&d1,d2.operator = (&d2,d3)) => return *this(d1)
    
    展开全文
  • 设计一个类 只能在堆上创建? 只能在栈上创建? 只能创建一个对象?(单例模式**) C++中创建对象的方法有两种,静态创建和动态创建。 1.静态创建 静态创建是由编译器为对象在栈空间中分配一块内存,通过...

    设计一个类
    只能在堆上创建?
    只能在栈上创建?
    只能创建一个对象?(单例模式**

    C++中创建对象的方法有两种,静态创建和动态创建。

    1.静态创建

    静态创建是由编译器为对象在栈空间中分配一块内存,通过直接移动栈顶指针,取出需要多大的空间,然后再这块内存上调用构造函数形成一个栈对象。直接调用类的构造函数。

    2.动态创建

    通过new运算符将对象建立在堆空间中。首先,调用operator new()函数,在堆空间中找适合的内存并进行分配;然后,调用构造函数构造对象,初始化这块内存空间。间接调用类的构造函数。

    限制对象只能在什么地方建立?

    1.只能在堆上创建?

    意思是不能静态的创建对象,即不能直接调用类的构造函数。只能动态创建对象,使用new操作符完成。

    方法一:将构造函数设为私有。

    想到这种办法的原因是,将构造函数设为私有后,无法再类外调用构造函数来创建对象,只能使用new运算符来建立对象。
    但是这种方法是不可行的,因为new运算符执行的过程中有两步,调用operator new()函数,在堆空间中找适合的内存并进行分配。然后,调用构造函数构造对象,初始化这块内存空间。C++提供new运算符的重载,其实只允许重载operator new()函数,而operator函数用于分配内存,无法提供构造功能。

    方法二:将析构函数声明为私有

    将析构函数设置为私有的,就可以保证只能在堆上创建一个新的类对象,这样可以自由的控制对象的生命周期,但是徐亚提供创建和撤销的公共接口。

    class B
    {
    public:
       B() {}
        void Destroy()
        {
            delete this; 
        }
    private:
           ~B() {}
    };
    int main()
    {
         B *p = new B;
         p ->Destroy(); // 如果类中没有定义Destroy()函数, 而在这里用"delete            p;"代替"p->Destroy();", 则会报错. 因为"delete p;"会去调用类的析构函数, 而在类域外调用类的private成员函数必然会报错.
        return 0;
    }

    C++是一个静态绑定的语言,在编译的过程中,所有的非虚函数调用都必须分析完成。即使是虚函数,也要检查可访问性。在栈上生成的对象,编译器会帮我们析构,所以析构函数需要访问,但是在堆上生成的对象,由于析构是由程序员自己决定的,所以不一定需要析构函数。

    但是问题又出来了,将析构函数设置为私有,不仅仅会限制栈对象生成外,还回限制继承。如果一个类不作为基类,通常会将其构造函数声明为私有的。

    方法三:为了限制栈对象,但不想限制继承,可以将析构函数声明为protected。

    class B
    {
    protected:
      ~B() { }
    public:
      void destroy()
      {
       delete this ;//调用保护析构函数
      }
    };

    但是,用new创建的一个对象,但不使用delete释放,而是用destory释放,不符合用户习惯,所以将构造函数也设置为私有或保护,让类提供一个静态成员函数专门用于生产该类型的堆对象。

    即使用静态成员函数创建对象和销毁,将构造函数和析构函数声明为protected。(设计模式中的单例模式就是这么实现的)

    class B{
    protected:
        B(){}
        ~B(){}
    public:
        static B* create(){
            return new A();
        }
        static void destory(B* p){
            delete p;    
            p = NULL;
        }
    };

    2.只在栈上生成对象?

    不能动态创建对象,即不能调用new操作符,可以将opterator new和operator delete设置为私有的。

    //设计一个类只能在栈上创建对象, //不能调用new 操作符,重载operator new() 和 operator delete() class B { 
    private: 
        void * operator new(size_t size){} 
        void operator delete(void *ptr){}
    public: 
        B(){}
        ~B(){}
    };
    展开全文
  • 在C++中,类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* ptr=new A;这两种方式是有区别的。 静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出...
  • 本文实例讲述了Python面向对象封装操作。分享给大家供大家参考,具体如下:目标士兵突击案例身份...都被 封装 在 类的内部一个对象的 属性 可以是 另外一个类创建的对象01. 士兵突击需求士兵 许三多 有一把 AK47士兵...
  • 如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?并不是,任何一个类在我们不写情况下,都会自动生成下面6个默认成员函数 二、构造函数 2.1 构造函数概念 构造函数是一个特殊成员函数,名字与...
  • /* * instanceof运算符用法 ...* 说明:一个类的实例是这个类本身的实例,也是他父类,父类的父类的实例,也是实现的接口的实例 * (2)instanceof左边操作元显式声明的类型与右边操作元必须是同种类或右边是
  • 对任一X,如果用户没有自定义赋值运算符函数,那么编译系统将自动为其生成一个默认赋值运算符函数,默认赋值运算符函数重载形式如下: X&X::operator =(const X & source) { 成员间赋值 } 若...
  • 在C++中,类的对象建立分为两种,一种是静态建立,如A a;另一种是动态建立,如A* ptr=new A;这两种方式是有区别的。  静态建立一个类对象,是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出...
  • 这里的对象,指的是像字符串、列表、元祖这样的序列对象和像字典的序列。切片就像下标可以从字符串、列表和元祖中取得单个值一样,“切片”可以从字符串或列表、元祖中取得多值。切片输入在对方括号中,像下标...
  • Python对象以及运算符

    2020-08-14 15:17:02
    python是一种面向对象的语言,类则是所有数据类型的基础。 标识符、对象和赋值语句 在Python语言的所有语句...创建一个类的新实例的过程被称为实例化。例如我们可以用w=Widget()这样的语句来创建这个类的实例。 许多py
  • 类是创建对象的模板,一个类可以创建多个对象,每个对象都是类类型的一个变量;创建对象的过程也叫类的实例化。每个对象都是类的一个具体实例(Instance),拥有类的成员变量和成员函数。 与结构体一样,类只是一...
  • 为什么书上的代码 要将这种一元的运算符重载(+除外)用新创建一个类对象 再按值进行返回的方式来重载呢,是为了使用临时对象语法吗还是其他的原因呢? 还有就是可不可以简单的理解为如果是创建一个新的对象来返回...
  • 1、静态建立类对象:是由编译器为对象在栈空间中分配内存,是通过直接移动栈顶指针,挪出适当的空间,然后在这片内存空间上调用构造函数形成一个对象。使用这种方法,直接调用类的构造函数。2、动态建立类对象,是...
  • 禁用new运算符实现只能在栈上创建对象的类 其实只要不...为了保证一个类的对象都是在栈上创建,那么,最有效的方式就是在代码中禁止使用new操作符。怎么实现禁用new操作符呢?其实很简单,将new和delete进行重载,...
  • 当* 左面的对象是右面的类创建的对象时,该运算符运算的结果是true,否则是false** 说明:一个类的实例是这个类本身的实例,也是他父类,父类的父类的实例,也是实现的接口的实例* (2)instanceof左边操作元显式声明的...
  • 类和对象2 ( 类的成员函数、构造函数、析构函数、拷贝构造函数、赋值运算符重载、const成员) 类的6个默认成员函数 如果一个类中什么成员都没有,简称为空类。空类中什么都没有吗?不是的。任何一个类在我们不写的...
  • C++的运算符重载

    2021-02-07 01:12:15
    目录运算符重载函数调用运算符重载(仿函数)左移(右移)运算符重载 运算符重载 运算符重载顾名思义,就是对已知的运算符进行重新定义,除了相同名称的声明,...作用:创建一个可以传递任意数目参数的运算符函数 clas
  • 1.类的默认成员函数:⑴构造函数 ⑵析构函数 ⑶拷贝构造函数 ⑷赋值函数 ⑴构造函数是种特殊的函数,它不需要用户来调用它,在创建对象时构造函数会自动执行。 注意:构造函数的函数名必须与类名相同,不能...
  • 2、拷贝复制,拷贝构造函数是使用同类对象初始化创建对象,赋值运算符重载主要是把一个对象赋值给另一个对象。 3、取地址运算符重载,包含取地址运算符重载和const取地址运算符重载。 1、构造函数 1.1、构造函数...
  • 对象:const常量哪怕栈空间数据被改 被访问时依然是使用存于符号表数据构造函数形式: 拷贝构造 Test(Test &t... //只要类型不同,会创建一个中间无名变量来赋值; //实际上是调用了构造函数中 Test(200)
  • String 中重写了 equals() 方法用于比较两字符串的内容是否相等。 语法 public boolean equals(Object anObject) 参数 anObject-- 与字符串进行比较的对象。 返回值 如果给定对象与字符串相等,则返回...
  • 我用一个String字段构造了一个类。 然后,我创建了两个对象,还必须使用==运算符和.equals()比较它们。 这是我所做:public class MyClass {String a;public MyClass(String ab) {a = ab;}public boolean equals...
  • javainstanceof运算符

    2019-03-19 19:41:04
    instanceof是二目运算符,左边的操作元是一个对象,右边是一个类。当左边的对象是右边的类或子类创建的对象,该运算符的结果为true,反之为false。 例子 public class Father { public void show(){ System.out....
  • C++创建一个类

    2020-08-23 22:39:43
    类的默认访问属性是private,一般我们将数据部分定义为private,将成员函数定义为public(成员函数也可以定义为private,此时,只有类的成员函数可以调用,对象不能直接调用)。 实现类成员函数:定义成员函数时,...
  • 1.类是创建一个对象的蓝图 2.要实现一个类,就要创建一个对象 3.对象是类的实例 4.一个类可以有很多实例,每个实例可以有自己的数据 5.运算符new用来在运行时动态的为对象分配内存(dynamic memory allocation) ...
  • //后缀形式 返回改变前的对象 创建一个独立的类类返回先前的值 所以采用的是按值进行返回 } const Byte operator--(int) { Byte before(b); b--; return before; } ``` 为什么返回this指针的解...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,741
精华内容 696
关键字:

创建一个类的对象的运算符是