第九章 面向对象程序设计

第九章 面向对象程序设计

面向过程和面向对象编程思想

编程思想与语言无关,无论是学习Java,Python,C语言都会涉及编程思想这个问题。

在这里插入图片描述
 
面向过程功能上的封装,比如之前学习过的函数,编写函数就是实现功能的封装。所以实际上编写的函数采用的就是面向过程的编程思想,其典型代表就是C语言。

面向对象属性和行为上的封装,也就是类和对象。Python语言是面向对象的编程语言,Java语言也是面向对象的编程语言。

面向过程的编程思想和面向对象的编程思想并不是相互独立的,而是相辅相成的。

面向过程与面向对象的异同点

面向过程面向对象
区别事物比较简单,可以用线性的思维去解决事物比较复杂,使用简单的线性思维无法解决
共同点面向过程和面向对象都是解决实际问题的一种思维方式
二者相辅相成,并不是对立的
解决复杂问题,通过面向对象方式便于我们从宏观上把握事物之间复杂的关系,方便我们分析整个系统,具体到微观操作,仍然使用面向过程方式来处理

自定义类和创建自定义类的对象

类是怎么来的?
类是由N多个对象抽取出“”的属性行为从而归纳总结出来的一种类别

在Python中一切皆对象

在这里插入图片描述

a = 10
b = 9.8
c = 'hello'
print(type(a))
print(type(b))
print(type(c))

在这里插入图片描述
 

在这里插入图片描述

 
自定义数据类型的语法结构为:【注意类名的首字母要大写,小括号()可写可省略】

class 类名():
  pass

创建对象的语法格式为:

对象名 = 类名()

# 编写一个Person类
class Person():
    pass


# 编写一个Cat类:
class Cat():
    pass


# 编写一个Dog类(这里省略小括号)
class Dog:
    pass


# 编写一个Student类(这里省略小括号)
class Student:
    pass


# 创建类的对象
# 对象名=类名()
per = Person()  # per就是Person类型的对象
c = Cat()  # c就是Cat类型的对象
d = Dog()  # d就是Dog类型的对象
stu = Student()  # stu就是Student类型的对象
print(type(per))
print(type(c))
print(type(d))
print(type(stu))

在这里插入图片描述

类的组成

类的组成:

  • 类属性:直接定义在类中,方法外的变量
  • 实例属性:定义在__init__方法中,使用self打点的变量
  • 实例方法:定义在类中的函数,而且自带参数self
  • 静态方法:使用装饰器@staticmethod修饰的方法
  • 类方法:使用装饰器@classmethod修饰的方法

 
注意:

  • 类方法、类属性、静态方法都是使用类名打点调用。
  • 跟实例有关的:实例属性、实例方法都是使用对象名打点调用。
  • 静态方法不能调用实例属性,也不能调用实例方法。
  • 类方法不能调用实例属性,也不能调用实例方法。
class Student:
    # 类属性:直接定义在类中,方法外的变量
    school = '北京大学'

    # 实例属性:定义在__init__方法中,使用self打点的变量
    # 初始化方法
    def __init__(self, xm, age):  # xm,age是方法的参数,是局部变量,xm,age的作用域是整个__init__方法
        self.name = xm  # = 左侧是实例属性,xm是局部变量,将局部变量的值xm赋值给实例属性self.name
        self.age = age  # 实例的名称和局部变量的名称可以相同,只不过左侧是实例属性,右侧是局部变量

    # 实例方法:定义在类中的函数,而且自带参数self
    def show(self):
        print(f'我叫{self.name},今年{self.age}岁')

    # 静态方法:使用装饰器@staticmethod修饰的方法
    # 注意:静态方法不能调用实例属性,也不能调用实例方法
    @staticmethod
    def sm():
        # self.name,不能调用实例属性
        # self.show(),不能调用实例方法
        print('这是一个静态方法,不能调用实例属性,也不能调用实例方法')

    # 类方法:使用装饰器@classmethod修饰的方法
    # 注意:类方法不能调用实例属性,也不能调用实例方法
    @classmethod
    def cm(cls):  # cls-->class的简写
        # self.name,不能调用实例属性
        # self.show(),不能调用实例方法
        print('这是一个类方法,不能调用实例属性,也不能调用实例方法')


# 创建类的对象
stu = Student('lxl', 24)  # 为什么传了两个参数,因为__init__方法中,有两个形参,self是自带的参数,无需手动传入
# 实例属性,使用对象名进行打点调用
print(stu.name, stu.age)
# 类属性,直接使用类名打点调用
print(Student.school)
# 实例方法,使用对象名进行打点调用
stu.show()
# 静态方法,@staticmethod进行修饰的方法,直接使用类名打点调用
Student.sm()
# 类方法,@classmethod进行修饰的方法,直接使用类名打点调用
Student.cm()

'''
类方法、类属性、静态方法都是使用类名打点调用
跟实例有关的:实例属性、实例方法都是使用对象名打点调用
'''

在这里插入图片描述

使用类模版创建多个对象

class Student:
    # 类属性:直接定义在类中,方法外的变量
    school = '北京大学'

    # 实例属性:定义在__init__方法中,使用self打点的变量
    # 初始化方法
    def __init__(self, xm, age):  # xm,age是方法的参数,是局部变量,xm,age的作用域是整个__init__方法
        self.name = xm  # = 左侧是实例属性,xm是局部变量,将局部变量的值xm赋值给实例属性self.name
        self.age = age  # 实例的名称和局部变量的名称可以相同,只不过左侧是实例属性,右侧是局部变量

    # 实例方法:定义在类中的函数,而且自带参数self
    def show(self):
        print(f'我叫{self.name},今年{self.age}岁')


# 根据“图纸”可以创建出N多个对象
stu = Student('lxl', 24)
stu2 = Student('mdm', 25)
stu3 = Student('xl', 25)
stu4 = Student('hmm', 25)

print(type(stu))

Student.school = '清华大学'  # 给类的类属性赋值

# 将学生对象存储到列表中
lst = [stu, stu2, stu3, stu4]  # 列表中的元素是Student类型的对象
for item in lst:  # item是列表中的元素,是Student类型的对象
    item.show()  # 对象名打点调用实例方法

在这里插入图片描述

动态绑定属性和方法

类是模版,可以创建N多个同类型的对象,由于类型是相同的,所以每个对象的属性名称都是相同的,但是属性值却可以不相同。

Python是一门动态语言,可以在创建对象后再去绑定它独有的属性。

可以为某个对象绑定独有属性方法

在这里插入图片描述

class Student:
    # 类属性:直接定义在类中,方法外的变量
    school = '北京大学'

    # 实例属性:定义在__init__方法中,使用self打点的变量
    # 初始化方法
    def __init__(self, xm, age):  # xm,age是方法的参数,是局部变量,xm,age的作用域是整个__init__方法
        self.name = xm  # = 左侧是实例属性,xm是局部变量,将局部变量的值xm赋值给实例属性self.name
        self.age = age  # 实例的名称和局部变量的名称可以相同,只不过左侧是实例属性,右侧是局部变量

    # 实例方法:定义在类中的函数,而且自带参数self
    def show(self):
        print(f'我叫{self.name},今年{self.age}岁')


# 创建两个Student类型的对象
stu = Student('lxl', 24)
stu2 = Student('mdm', 25)
print(stu.name, stu.age)
print(stu2.name, stu2.age)

# 为stu2动态绑定一个实例属性
stu2.gender = '男'
print(stu2.name, stu2.age, stu2.gender)
# print(stu.gender)  # AttributeError: 'Student' object has no attribute 'gender'

# 动态绑定方法
def introduce():
    print('我是一个普通的函数,我被动态绑定成了stu2对象的方法')


stu2.fun = introduce  # 函数的赋值,不能加()。若加了()则为调用
# fun就是stu2对象的方法
stu2.fun()

在这里插入图片描述

面向对象三大基本特征

面向对象程序设计的三大基本特征:封装、继承、多态

  • 封装:隐藏内部细节,提高程序的安全性(健壮性)。
  • 继承:实现代码的复用(重复使用),通过继承可以理顺类与类之间的关系。
  • 多态:不关心对象的数据类型,不关心是否具有继承关系,只关心对象的行为(方法),提高程序的可扩展性和可维护性。

Python中的权限控制

封装:隐藏内部细节,对外提供访问属性和方法的访问方式,其目的是保证数据的安全性

在Python中没有明确的权限访问修饰符,对于权限的访问控制是通过对属性或方法添加下划线实现的。

权限控制:是通过对属性或方法添加单下划线、双下划线以及首尾双下划线来实现。

  • 单下划线开头:以单下划线开头的属性或方法表示protected受保护的成员,这类成员被视为仅供内部使用,允许类本身和子类进行访问,但实际上它可以被外部代码访问。
  • 双下划线开头:表示private私有的成员,这类成员只允许定义该属性或方法的类本身进行访问。
  • 首尾双下划线:一般表示特殊的方法。
class Student():
    # 首尾双下划线
    def __init__(self, name, age, gender):
        self._name = name  # self._name,单下划线开头表示受保护的,只能本类和子类访问
        self.__age = age  # self.__age,双下划线开头表示私有的,只能类本身去访问
        self.gender = gender  # self.gender普通的实例属性,类的内部、外部及子类都可以访问

    def _fun1(self):  # 单下划线开头,受保护的,
        print('本身及子类可以访问')

    def __fun2(self):  # 双下划线开头,私有的,
        print('只有类本身可以访问')

    def show(self):  # 普通的实例方法
        self._fun1()  # 类本身访问受保护的方法
        self.__fun2()  # 类本身访问私有方法
        print(self._name)  # 受保护的实例属性
        print(self.__age)  # 私有的实例属性


# 创建一个学生类的对象
stu = Student('lxl', 20, '女')
# 类的外部
print(stu._name)
# print(stu.__age)  # AttributeError: 'Student' object has no attribute '__age'

# 调用受保护的实例方法
stu._fun1()  # 子类及本身可以访问
# 私有方法
# stu.__fun2()  # AttributeError: 'Student' object has no attribute '__fun2'

# 私有的实例属性和方法是真的不能访问吗?-->用下面的方法就可以访问了
print(stu._Student__age)
stu._Student__fun2()

# dir()可以输出对象中所有的属性和方法
print(dir(stu))
'''
会发现在这里面私有属性age会被定义为_Student__age;
私有方法fun2会被定义为_Student__fun2;
因此可以用上面的方式访问私有的实例属性和方法
'''

在这里插入图片描述

属性的设置

在上述例子中使用对象名._类名.__属性名(stu._Student__age)访问私有属性的方式是非常不推荐的。

在编写程序的过程中可以使用一个装饰器,装饰器是Python中的一个名词(概念),是使用 @ 符号开头,可以使用 @property 将一个方法去转换成属性去使用,就是说它的定义形式是方法,但是在使用的时候是不需要加括号的,将其当做属性使用。使用 @property 去修饰方法就可以将其转换成属性了,在访问时只能访问属性的值,却无法修改属性的值;如果想修改还要再设置一个 @setter ,去修改属性的值。

class Student():
    # 初始化方法
    def __init__(self, name, gender):
        self.name = name
        self.__gender = gender  # self.__gender 私有的实例属性

    # 使用@property修改方法,将方法转成属性使用
    @property
    def gender(self):  # 私有属性叫什么,方法名一般也叫什么
        # 这是一个带返回值的方法,谁调用就将值返回给谁
        return self.__gender

    # 将gender设置为可写属性
    @gender.setter
    def gender(self, value):  # 方法名跟上面的相同,且这是一个赋值方法,所以要有一个参数去进行赋值
        if value != '男' and value != '女':
            print('性别有误,已将性别默认设置为男')
            self.__gender = '男'
        else:
            self.__gender = value


# 创建一个学生类的对象
stu = Student('lxl', '男')
print(stu.name, '的性别是:', stu.gender)  # stu.gender就会去执行stu.gender()

# 修改属性值
stu.gender = '其他'
print(stu.name, '的性别是:', stu.gender)

在这里插入图片描述

继承的概念

继承的概念源于人们的生活,子孙辈会从父辈那里继承一些体貌特征,但是子孙辈又不完全是父辈的翻版,他们还会有一些自己的特征,这就是继承。

在程序设计当中,被继承的类称为父类或基类,新的类称为子类或派生类。子类继承了父类就拥有了父类所有共有成员和受保护的成员

继承

  • 在Python中一个子类可以继承N多个父类
  • 一个父类也可以拥有N多个子类
  • 如果一个类没有继承任何类,那么这个类默认继承的是object类

继承的语法结构:

class 类名(父类1,父类2,…,父类N):
  pass

class Person:  # 默认继承了object
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(f'大家好,我叫:{self.name},我今年:{self.age}岁')


# Student继承Person类
class Student(Person):
    # 编写初始化方法
    def __init__(self, name, age, stuno):
        # name,age是父类中有的,可以直接调用
        super().__init__(name, age)  # 调用父类的初始化方法给name,age赋值
        self.stuno = stuno


# Doctor继承Person类
class Doctor(Person):
    # 编写初始化方法
    def __init__(self, name, age, department):
        super().__init__(name, age)
        self.department = department


# 创建子类对象
stu = Student('lxl', 20, '1001')
stu.show()
doc = Doctor('wz', 20, '外科')
doc.show()

在这里插入图片描述

Python中的多继承

在Python中一个父类可以拥有N多个子类,它与其他编程语言不一样的地方就是在Python中一个子类也可以继承多个父类。继承多个父类后就拥有了多个父类的共有的成员和受保护的成员

class FatherA():
    def __init__(self, name):
        self.name = name

    def showA(self):
        print('父类A中的方法')


class FatherB():
    def __init__(self, age):
        self.age = age

    def showB(self):
        print('父类B中的方法')


# 多继承
class Son(FatherA, FatherB):
    def __init__(self, name, age, gender):
        # 需要调用两个父类的初始化方法
        # 当一个类继承多个父类时不能再使用super(),而应该用类名进行区分
        FatherA.__init__(self, name)
        FatherB.__init__(self, age)
        self.gender = gender


son = Son('mdm', 20, '女')  # 调用Son类中__inti__执行
son.showA()
son.showB()

在这里插入图片描述

方法重写

  • 子类继承了父类就拥有了父类中共有成员和受保护的成员
  • 父类的方法并不能完全适合子类的需求,这个时候子类就可以重写父类的方法
  • 子类在重写父类的方法时,要求方法的名称必须与父类的方法名称相同,在子类重写后的方法中可以通过super().xxx()调用父类中的方法
class Person:  # 默认继承了object
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(f'大家好,我叫:{self.name},我今年:{self.age}岁')


# Student继承Person类
class Student(Person):
    # 编写初始化方法
    def __init__(self, name, age, stuno):
        # name,age是父类中有的,可以直接调用
        super().__init__(name, age)  # 调用父类的初始化方法给name,age赋值
        self.stuno = stuno

    # 重写父类中的方法,方法名要与父类中的方法名相同
    def show(self):
        # 调用父类中的方法
        super().show()
        print(f'我来自北京大学,我的学号是:{self.stuno}')


# Doctor继承Person类
class Doctor(Person):
    # 编写初始化方法
    def __init__(self, name, age, department):
        super().__init__(name, age)
        self.department = department

    # 重写父类中的方法,方法名要与父类中的方法名相同
    def show(self):
        print(f'大家好,我叫{self.name},今年{self.age},我的工作科室是{self.department}')


# 创建子类对象
stu = Student('lxl', 20, '1001')
stu.show()  # 调用子类自己的show()方法。子类中没有的才再去父类中找

doc = Doctor('wz', 20, '外科')
doc.show()  # 调用子类自己的show()方法

在这里插入图片描述

多态

多态

  • 指的就是“多种形态”,即便不知道一个变量所引用的对象到底是什么类型,仍然可以通过这个变量调用对象的方法
  • 在程序运行过程中根据变量所引用对象的数据类型,动态决定调用哪个对象中的方法
  • Python语言中的多态,根本不关心对象的数据类型,也不关心类之间是否存在继承关系,只关心对象的行为(方法)。只要不同的类中有同名方法,即可实现多态

在程序运行过程中根据变量所引用对象的数据类型,动态决定调用哪个对象中的方法。也就是说在编写方法的时候,参数类型不知道,只有在调用的时候传入具体的参数后才能知道是谁,但是这个参数可以很确定它是一个对象,可以调用这个对象的方法,只有这个对象有这个方法就可以了。

所以说Python中的多态不关心对象的数据类型,也不关心是否存在继承关系,它只关心是否具有同名的方法,只要具有同名的方法就可以实现多态。这点也是Python语言和其他语言当中不一样的地方,在Java语言中封装、继承、多态是必不可少的,要想实现多态必须得有继承去做前提;但是在Python中则不一样,Python中只关心方法,不关心是否具有继承关系。再次强调Python中的多态只关心方法

class Person():
    # 这里没有写初始化方法__init__()。
    # 当有实例属性时在初始化方法中赋值,没有实例属性时可以不写初始化方法
    def eat(self):
        print('人,吃五谷杂粮')


class Cat():
    def eat(self):
        print('猫,喜欢吃鱼')


class Dog():
    def eat(self):
        print('狗,喜欢啃骨头')


# 这三个类是并列关系,没有继承关系。这三个类中都有一个同名方法,eat
# 编写函数
def fun(obj):  # obj是函数的形式参数,在定义处知道这个形参的数据类型吗?-->不知道
    # 虽然不知道形参的数据类型,但仍然可以调用对象的方法
    obj.eat()  # 通过变量obj(对象)调用eat方法


# 创建三个类的对象
per = Person()  # 类中没有初始化方法,因此不用传参
cat = Cat()
dog = Dog()

# 调用fun()函数
fun(per)  # Python中的多态,不关心对象的数据类型,只关心对象是否具有同名方法
fun(cat)
fun(dog)

在这里插入图片描述

通过上面的例子可以发现实现多态的好处:可以在程序运行的过程当中动态的决定调用哪个对象当中的方法,可以实现程序的可扩展性

面向对象三大特征的作用

  • 封装的作用:保护程序的安全。
  • 继承的作用:程序的方法的重写,代码的复用。
  • 多态的作用:实现程序的可扩展性。

object类

在Python中如果一个类没有继承任何类的话,那么这个类默认继承的就是object类。所以object类是所有类的直接或间接父类,因此所有类都拥有object类的属性和方法。

object类

  • 所有类直接或间接的父类
  • 所有类都拥有object类的属性和方法
object类中特殊的方法功能描述
__new__()由系统调用,用于创建对象
__init__()创建对象时手动调用,用于初始化对象属性值
__str__()对象的描述,返回值是str类型,默认输出对象的内存地址
class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def show(self):
        print(f'大家好,我叫{self.name},今年{self.age}')


# 创建Person类的对象
per = Person('zhangsan', 21)  # 创建对象的时候会自动调用__init__()方法
print(dir(per))  # dir(对象) 会返回该对象所具有的所有属性和方法

print(per)  # 自动调用了__str__(),输出对象的描述信息,默认输出对象的内存地址

在这里插入图片描述

是__new__()先执行还是__init__()先执行呢?————会先执行父类中的__new__()去创建对象开辟内存空间,创建完成之后再由__init__()去给实例属性进行赋值。
 

重写__str__():

class Person(object):
    def __init__(self, name, age):
        self.name = name
        self.age = age

    # 方法重写,方法名要与父类方法名相同
    def __str__(self):  # 返回str类型
        return '这是一个人类,具有name和age两个实例属性'  # 返回值是一个字符串


# 创建Person类的对象
per = Person('zhangsan', 21)  # 创建对象的时候会自动调用__init__()方法

# 直接输出对象名,实际上是调用__str__()方法(即默认调用)
print(per)  # 重写__str__()后这里便不再是内存地址了,而是重写后__str__()里的内容
# 亦可手动调用
print(per.__str__())

在这里插入图片描述

对象的特殊方法

在Python中首尾双下划线的方法表示的是特殊方法,比如__init__()。实际上Python中运算符也是通过调用特殊方法来实现的。

运算符特殊方法功能描述
+__add__()执行加法运算
-__sub__()执行减法运算
<、<=、==__lt__()、__le__()、__eq__()执行比较运算
>、>=、!=__gt__()、__ge__()、__ne__()执行比较运算
*、/__mul__()、__truediv__()执行乘法运算、非整除运算
%、//__mod__()、__floordiv__()执行取余运算、整除运算
**__pow__()执行幂运算
a = 10
b = 20
print(dir(a))  # Python中一切皆对象
print(a + b)
print(a.__add__(b))  # 执行加法运算
print(a.__sub__(b))  # 执行减法运算
print(f'{a}<{b}吗?', a.__lt__(b))
print(f'{a}<={b}吗?', a.__le__(b))
print(f'{a}=={b}吗?', a.__eq__(b))
print('-' * 30)
print(f'{a}>{b}吗?', a.__gt__(b))
print(f'{a}>={b}吗?', a.__ge__(b))
print(f'{a}!={b}吗?', a.__ne__(b))
print('-' * 30)
print(a.__mul__(b))  # 乘法
print(a.__truediv__(b))  # 除法
print(a.__floordiv__(b))  # 整除
print(a.__mod__(b))  # 取余
print(a.__pow__(2))  # 幂运算

在这里插入图片描述

Python中的特殊属性

在Python中首尾双下划线的方法表示的是特殊方法首尾双下划线的属性表示的是特殊属性

特殊属性功能描述
obj.__dict__对象的属性字典
obj.__class__对象所属的类
obj.__bases__类的父类元组
obj.__base__类的父类
obj.__mro__类的层次结构
obj.__subclasses__()类的子类列表
class A:
    pass


class B:
    pass


class C(A, B):
    def __init__(self, name, age):
        self.name = name
        self.age = age


# 创建类的对象
a = A()
b = B()
# 创建C类的对象
c = C('lxl', 20)

print('对象a的属性字典:', a.__dict__)  # 对象的属性字典(实例属性)
print('对象b的属性字典:', b.__dict__)
print('对象c的属性字典:', c.__dict__)

print('对象a所属的类:', a.__class__)  # 对象所属的类
print('对象b所属的类:', b.__class__)
print('对象c所属的类:', c.__class__)

print('A类的父类元组:', A.__bases__)  # 类的父类元组(一个类可以继承N多个父类)
print('B类的父类元组:', B.__bases__)
print('C类的父类元组:', C.__bases__)

print('A类的父类:', A.__base__)  # 类的父类
print('B类的父类:', B.__base__)
print('C类的父类:', C.__base__)  # 如果继承了N多个父类,则类的父类结果只显示第一个父类

# 如果继承了N多个父类,其中有同名方法,那么默认调用的是第一个父类的方法

# 类的层次结构--->都继承了谁
print('A类的层次结构:', A.__mro__)
print('B类的层次结构:', B.__mro__)
print('C类的层次结构:', C.__mro__)  # C类继承了A类、B类,间接继承了object类

# 类的子类列表
print('A类的子类列表:', A.__subclasses__())
print('B类的子类列表:', B.__subclasses__())
print('C类的子类列表:', C.__subclasses__())

在这里插入图片描述

类的深拷贝与浅拷贝

  • 变量的赋值:只是形成两个变量,实际上还是指向同一个对象
  • 浅拷贝:拷贝时,对象包含的子对象内容不拷贝,因此,源对象与拷贝对象会引用同一个子对象
  • 深拷贝:使用copy模块的deepcopy函数,递归拷贝对象中包含的子对象,源对象和拷贝对象所有的子对象也不相同

变量(对象)的赋值

class CPU():
    pass


class Disk():
    pass


class Computer():
    # 计算机由CPU和硬盘组成
    def __init__(self, cpu, disk):
        self.cpu = cpu
        self.disk = disk


cpu = CPU()  # 创建了一个CPU对象
disk = Disk()  # 创建了一个硬盘对象

# 创建一个计算机对象
com = Computer(cpu, disk)

# 变量(对象)的赋值
com1 = com
# 变量的赋值:只是形成两个变量,实际上还是指向同一个对象
print(com)  # 两者内存地址相同,说明是同一个对象
print(com1)
print(com, '子对象的内存地址:', com.cpu, com.disk)  # 同一个对象,两者子对象内存地址也相同,在内存中实际上只有一个对象
print(com1, '子对象的内存地址:', com1.cpu, com1.disk)

在这里插入图片描述

画内存分析图:
在这里插入图片描述

类对象的浅拷贝

浅拷贝会产生一个新的对象,但是子对象不会产生新的。

class CPU():
    pass


class Disk():
    pass


class Computer():
    # 计算机由CPU和硬盘组成
    def __init__(self, cpu, disk):
        self.cpu = cpu
        self.disk = disk


cpu = CPU()  # 创建了一个CPU对象
disk = Disk()  # 创建了一个硬盘对象

# 创建一个计算机对象
com = Computer(cpu, disk)

# 变量(对象)的赋值
com1 = com
# 变量的赋值:只是形成两个变量,实际上还是指向同一个对象
print(com)  # 两者内存地址相同,说明是同一个对象
print(com1)
print(com, '子对象的内存地址:', com.cpu, com.disk)  # 同一个对象,两者子对象内存地址也相同,在内存中实际上只有一个对象
print(com1, '子对象的内存地址:', com1.cpu, com1.disk)

print('-' * 30)

# 类对象的浅拷贝
import copy

com2 = copy.copy(com)  # com2是新产生的对象,com2的子对象cpu,disk不变
print(com, '子对象的内存地址:', com.cpu, com.disk)
print(com2, '子对象的内存地址:', com2.cpu, com2.disk)

在这里插入图片描述

画内存分析图:
在这里插入图片描述

类对象的深拷贝

深拷贝是连子对象也会重新创建。

class CPU():
    pass


class Disk():
    pass


class Computer():
    # 计算机由CPU和硬盘组成
    def __init__(self, cpu, disk):
        self.cpu = cpu
        self.disk = disk


cpu = CPU()  # 创建了一个CPU对象
disk = Disk()  # 创建了一个硬盘对象

# 创建一个计算机对象
com = Computer(cpu, disk)

# 变量(对象)的赋值
com1 = com
# 变量的赋值:只是形成两个变量,实际上还是指向同一个对象
print(com)  # 两者内存地址相同,说明是同一个对象
print(com1)
print(com, '子对象的内存地址:', com.cpu, com.disk)  # 同一个对象,两者子对象内存地址也相同,在内存中实际上只有一个对象
print(com1, '子对象的内存地址:', com1.cpu, com1.disk)

print('-' * 30)

# 类对象的浅拷贝
import copy

com2 = copy.copy(com)  # com2是新产生的对象,com2的子对象cpu,disk不变
print(com, '子对象的内存地址:', com.cpu, com.disk)
print(com2, '子对象的内存地址:', com2.cpu, com2.disk)

print('-' * 30)

# 类对象的深拷贝
com3 = copy.deepcopy(com)  # com3是新产生的对象,com3的子对象cpu,disk也会重新创建
print(com, '子对象的内存地址:', com.cpu, com.disk)
print(com3, '子对象的内存地址:', com3.cpu, com3.disk)

在这里插入图片描述
画内存分析图:
在这里插入图片描述

章节习题

object类中特殊的方法功能描述
__new__()由系统调用,用于创建对象
__init__()创建对象时手动调用,用于初始化对象属性值
__str__()对象的描述,返回值是str类型,默认输出对象的内存地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

练习题

练习一

定义一个圆的类计算面积和周长。

需求:定义一个圆类——Circle,提供一个属性 r (半径),提供两个方法:计算圆的面积 get_area(self) 和计算圆的周长 get_perimeter(self),通过两个方法计算圆的周长和面积并且对计算结果进行输出,最后从键盘录入半径,创建圆类的对象,并调用计算面积和周长的方法输出面积和周长。

from math import pi


class Circle():
    def __init__(self, r):
        self.radius = r

    # 计算面积的方法
    def get_area(self):
        # return pi * self.radius * self.radius
        return pi * pow(self.radius, 2)  # 幂运算

    # 计算周长的方法
    def get_perimeter(self):
        return 2 * pi * self.radius


# 创建对象
r = eval(input('请输入圆的半径:'))
cir = Circle(r)
# 调用方法
area = cir.get_area()  # 调用计算面积的方法
perimeter = cir.get_perimeter()  # 调用计算周长的方法
print('圆的面积为:', area)
print('圆的周长为:', perimeter)
print('圆的面积为:{0:.2f};圆的周长为:{1:.2f}'.format(area, perimeter))

在这里插入图片描述

练习二

定义学生类录入5个学生信息存储到列表中。

需求:定义学生类Student,包含姓名,年龄,性别,分数四个属性,提供一个用于学员信息输出的方法 info(self)。编写测试代码,使用循环录入5位学生的信息,由于录入的学生信息中间使用 “#” 进行分隔,所以需要使用字符串的 split( ) 方法进行劈分(该方法会将劈分后的结果构成一个列表),使用劈分的信息创建学生对象,使用列表存储学生信息,最后使用循环遍历列表,调用对象的 info( ) 方法输出学员信息。

class Student(object):
    def __init__(self, name, age, gender, score):
        self.name = name
        self.age = age
        self.gender = gender
        self.score = score

    # 实例方法
    def info(self):
        print(self.name, self.age, self.gender, self.score)


print('请输入5位学生信息:(姓名#年龄#性别#成绩)')
lst = []  # 用于存储5个学生对象
for i in range(1, 6):
    s = input(f'请输入第{i}位学生信息及成绩:')
    s_lst = s.split('#')  # 劈分后的结果返回一个列表。索引为0的是姓名,索引为1的是年龄,索引为2的是性别,索引为3的是成绩
    # 创建学生对象
    stu = Student(s_lst[0], s_lst[1], s_lst[2], s_lst[3])
    # 将学生对象添加到列表中
    lst.append(stu)

# 遍历列表,调用学生对象的info()方法
for item in lst:  # item的数据类型是Student类型
    item.info()  # 对象名.方法名()

在这里插入图片描述

本题考察到了面向对象,字符串的分割方法。

练习三

使用面向对象思想(封装、继承、多态)实现乐器演奏。

需求:乐手可以弹奏不同的乐器从而发出不同的声音。可以弹奏的乐器包括二胡、钢琴和小提琴。定义乐器类 Instrument ,包括方法 make_sound( ) 。定义乐器类的子类:二胡 Erhu、钢琴 Piano 和小提琴 Violin ,定义一个函数可以弹奏各种乐器 play(instrument),测试给乐手不同的乐器让他弹奏。

class Instrument():  # 父类
    def make_sound(self):
        pass


class Erhu(Instrument):
    def make_sound(self):
        print('二胡在弹奏')


class Piano(Instrument):
    def make_sound(self):
        print('钢琴在弹奏')


class Violin(Instrument):
    def make_sound(self):
        print('小提琴在弹奏')


# 事实上在Python中多态并不关心类之间是否有继承关系,只需要有同名方法即可。
# 题目要求有继承关系,因此这里进行了继承

# 编写一个函数
def play(obj):
    obj.make_sound()


# 创建对象
erhu = Erhu()
piano = Piano()
violin = Violin()

# 调用方法
play(erhu)
play(piano)
play(violin)

在这里插入图片描述

本题考察了方法重写和多态。

练习四

使用面向对象的思想(封装、继承、多态),设计自定义类,描述出租车和家用轿车的信息。

  • 出租车类:
    • 属性:车型、车牌、所属出租公司
    • 方法:启动、停止
  • 家用轿车类:
    • 属性:车型、车牌、车主姓名
    • 方法:启动、停止

需求
(1)分析出租车和家用轿车的公共成员,提取出父类——汽车类
(2)利用继承机制,实现出租车类和家用轿车类
(3)最后,分别测试出租车类和家用轿车类对象的相关方法

class Car():
    def __init__(self, type, no):
        self.type = type
        self.no = no

    def start(self):
        print('我是车,我能启动')

    def stop(self):
        print('我是车,我可以停止')


# 出租车
class Taxi(Car):
    def __init__(self, type, no, company):
        super().__init__(type, no)
        self.company = company

    # 重写父类的启动和停止的方法
    def start(self):
        print('乘客您好!')
        print(f'我是{self.company}出租车公司的,我的车牌是:{self.no},您要去哪里?')

    def stop(self):
        print('目的地到了,请您扫码付款,欢迎下次乘坐')


# 家用轿车
class FamilyCar(Car):
    def __init__(self, type, no, name):
        super().__init__(type, no)
        self.name = name

    # 重写父类的启动和停止的方法
    def start(self):
        print(f'我是{self.name},我的汽车我做主')

    def stop(self):
        print('目的地到了,我们去玩吧!')


# 测试
taxi = Taxi('北汽', '京A888888', '长城')
taxi.start()
taxi.stop()
print('-' * 30)
family_car = FamilyCar('问界', '京A666666', 'lxl')
family_car.start()
family_car.stop()

在这里插入图片描述

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: 《C面向对象程序设计第三版答案》是由谭浩强编写的一本与C语言相关的教材辅导答案。C面向对象程序设计是计算机科学中的一门重要课程,谭浩强作为资深教授和编程专家,他撰写的书籍在编程领域拥有很高的权威性。 这本教材答案为学习者提供了对应教材《C面向对象程序设计第三版》的习题答案和思考指导。习题是帮助学生巩固所学知识和提升编程能力的重要方式,通过对答案的学习,学生可以更好地理解并运用相关知识。学习者可以通过对比答案,分析解题思路、吸收优秀的编程风格和技巧,从而提高编程水平。 《C面向对象程序设计第三版答案》按照教材的章节顺序,详细解答了各个章节的习题,包括程序设计题、思考题、应用题等,涵盖了从基础的语法使用到复杂的程序设计技巧,旨在帮助学生全面理解并掌握C语言的面向对象编程思想和方法。 除了提供答案,这本教材还包括了一些习题的思考指导,指导学生如何分析问题、拆解问题、确定解决步骤等。这些思考指导帮助学生培养编程思维和解决问题的能力,使他们能够独立思考和解决实际编程任务。 总之,《C面向对象程序设计第三版答案》是一本为学习C语言面向对象程序设计的学生提供的辅助资料,它不仅提供了习题答案,还包括了思考指导,帮助学生提高编程水平和解决实际问题的能力。 ### 回答2: 《C++面向对象程序设计(第3版)》是计算机科学与技术专业学生的主要教材之一,由谭浩强编写。这本书全面介绍了C++编程语言的面向对象编程思想和相关的概念、原则与技巧。 该教材内容分为15章,首先从C++的基本概念和语法入手,然后逐渐介绍了面向对象编程的思想和实现。每章的结尾都提供了习题和答案,帮助学生巩固所学知识。 《C++面向对象程序设计(第3版)》的答案是谭浩强根据书中习题所提供的参考答案。这些答案精确明确,清晰易懂,配有详细的解释和示范代码。通过阅读和理解这些答案,学生可以加深对所学知识的理解,提高自己的编程技能。 同时,这本书还提供了大量的示例代码和实践案例,帮助学生将理论知识应用于实际的编程项目中。通过实践,学生可以更好地理解面向对象编程的思想和方法,并培养自己的分析和解决问题的能力。 总之,《C++面向对象程序设计(第3版)》是一本权威性、系统性和实用性并存的教材。通过学习这本书,学生可以全面掌握C++编程语言和面向对象编程的相关知识,提高自己的编程能力,并为将来的实际工作打下坚实的基础。 ### 回答3: 《C++面向对象程序设计》(第三版)是谭浩强所著的一本教材,该教材主要介绍了C++面向对象程序设计的基本概念、语法和技巧。全书共分为10个章节,涵盖了面向对象程序设计的各个方面。 第一章介绍了C++的发展历程以及面向对象程序设计的基本概念和特点。第二章详细讲解了C++的基本语法和常用数据类型。第三章重点介绍了C++中的类和对象的概念,并通过具体的示例演示了如何定义和使用类。 第四章讲解了C++的继承和派生,介绍了单继承和多继承的概念以及如何定义和使用派生类。第五章介绍了C++中的多态性,包括静态多态和动态多态的概念以及如何使用虚函数实现动态绑定。 第六章讲解了C++中的运算符重载和类型转换,通过实例说明了如何重载运算符和类型转换函数。第七章介绍了C++中的异常处理机制,讲解了异常的概念和处理方法。 第八章讲解了C++中的文件操作,包括输入输出流、文件读写以及文件指针的相关知识。第九章介绍了C++的模板和泛型编程,包括函数模板和类模板的定义和使用。 第十章介绍了C++中的标准模板库(STL),包括容器、迭代器、算法和函数对象等的使用方法。 《C++面向对象程序设计》(第三版)通过简明扼要的语言和生动具体的示例,全面而深入地介绍了C++面向对象程序设计的基本概念和技巧,适合初学者学习和参考。同时,该教材也提供了丰富的练习题和案例,供读者巩固和应用所学知识。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值