类与对象

2019-04-09  本文已影响0人  漫游之光

面向对象程序设计

程序是完成一定功能的一系列有序指令的集合。我想,对于这个定义,汇编程序员的理解是最深的。汇编语言用助记符代替机器指令,用地址符号或标号代替指令或操作数的地址。特定的汇编语言和特定的机器语言指令集是一一对应的,不同平台之间不可直接移植。汇编语言的抽象层次不高,大部分时候我们还是需要以机器的思维来考虑问题,如需要考虑寻址方式,并且代码和数据之间的界限不明确。现在我们已经很少使用汇编语言了,大部分时候都是使用高级语言。

程序 = 数据结构 + 算法,这是结构化程序设计的核心思想。它将程序定义为处理数据的一系列过程。这种设计方法的着眼点是面向过程的,特点是数据与程序分离,即数据与数据处理分离。结构化程序设计的基本思想是采用自顶向下、逐步细化的设计方法和单入单出的控制结构。其理念是将大型程序分解成小型、便于管理的任务。如果其中的一项仍然过大,则将它分解为更小的任务。这一过程将一直持续下去,知道将程序划分为小型的,易于编写的模块。结构化程序设计为处理复杂问题提供了有力手段,但到80年代末,这种设计方法逐渐暴露出以下缺陷:程序难以管理、数据修改存在问题、程序的可重用性差。面向过程程序设计缺点的根源在于数据与数据处理分离。

程序 = 对象 + 对象 + ... ... + 对象;对象 = 数据结构 + 算法。这是面向对象程序设计的主要思想。面向对象程序设计模拟自然界认识和处理事务的方法,将数据和对数据的操作方法放在一起,形成一个相对独立的整体——对象,同类对象还可抽象出共性,形成类。一个类中的数据通常只能通过本类提供的方法进行处理,这些方法成为该类与外部的接口。对象之间通过消息进行通讯。结构化设计方法应用的是过程抽象。过程抽象是将问题域中具有明确功能定义的操作抽取出来,并将其作为一个实体看待。面向对象设计方法应用的是数据抽象。数据抽象是将描述客体的属性和行为绑定在一起,实现统一的抽象,从而达到对现实世界客体的真正模拟。

面向对象的基本特征

面向对象编程方法的特性

类的定义

类是一种用户自定义类型,类中可以定义数据和函数,数据可以是基本数据类型,也可以是类类型,函数可以带缺省参数,用inline修饰,函数还可以重载。其声明形式如下。

class 类名称
{
    public:
        //公有成员(外部接口)
    private:
        //私有成员
    protected:
        //保护成员
};

在C++中,struct也可以定义类,它和class关键字的区别是,在未指定访问权限时,class默认是私有的,而struct默认是公有的。

this指针和static关键字

先看这样一段代码:

#include <iostream>
using namespace std;

class A{
public:
    int val;
    void print(){
        cout<<"val = "<<val<<endl;
    }
};

int main(int argc, char const *argv[])
{
    A a1,a2;
    a1.val = 1;
    a2.val = 2;
    a1.print();
    a2.print();
    return 0;
}

仔细想想这段代码,就会发现一个问题,print怎么知道是哪个对象调用了它,它的参数里面没有指明对象啊?其实因为绝大多数的情况下,我们使用类中的函数都需要使用对象的值,为了简单起见,在函数调用的时候,会自动传入一个对象的指针,也就是说,成员函数有一个隐含的附加形参,即指向该对象的指针,这个隐含的形参叫做this指针。使用this指针保证了每个对象可以拥有不同的数据成员,但处理这些成员的代码可以被所有对象共享。

普通成员函数必须使用对象进行调用,并且由于this指针的缘故,处理的数据都是对象自己的。现在有这样一个问题,如果在一个类中,需要有所有对象共享的数据,怎么办?这就引入了static关键字,使用static关键字声明的数据是独立于对象之外的,并且只有一份,是所有对象共享的。同时,static不仅可以声明数据,还可以声明函数,static声明的函数是独立于对象的,也就是说,使用类作用域符就可以调用,但是,同时,static声明的函数不能使用非static的数据成员。

前向声明

C++中类必须先定义,才能够实例化。两个类需要相互引用形成一个“环形”引用时,无法先定义使用。这时候需要用到前向声明。前向声明的类不能实例化,因为前面声明仅仅是声明了类的名称,仅仅知道类的名称,编译器无法构造出具体的对象。

嵌套类

局部类

构造函数

在C语言中,是没有构造函数的,但是C语言提供了一种相对简单的构造方法。

struct Student{
    char *name;
    int age;
};
Student st = {name,age};

但对于C++来说,初始化要复杂一些,所以C++提供了构造函数来对对象进行初始化。构造函数是特殊的成员函数,创建类类型的新对象,系统自动会调用构造函数,也就是说,构造函数是为了保证对象的每个数据成员都被正确初始化。构造函数有如下的一些特点。

C++提供了初始化列表,可以方便的对类中的数据成员进行初始化,它的一个例子如下。

A(int val1, int val2):val1_(val1),val2_(val2){}

在很多类中,初始化和赋值的区别事关底层效率问题:前者字节初始化数据成员,后者先初始化再赋值。此外,一些数据成员必须被初始化,只能使用初始化列表进行初始化,如引用和const常量。

构造函数可以重载,根据参数的不同,有如下一些特殊的构造函数。

默认构造函数(无参构造函数)

没有参数的构造函数称为默认构造函数,或者无参构造函数,如果程序中未声明其他构造函数,则系统自动产生出一个默认构造函数。值得注意的是,在《C++primer》中,说定义在函数外的变量会执行默认初始化,但实际的效果是,在有的编译器中并不会执行。看下面的代码。

class A{
public:
    int x;
    int y = 0;
};

按照《C++primer》的说法,A中的x会被设置为0,但实际上,g++中x还是一个随机值,但y的值会被正确初始化为0。

转换构造函数及explicit关键字

转换构造函数,也就是单个参数的构造函数,之所以叫转换构造函数,是因为它可以将其它类型转换为该类类型。类的构造函数只有一个参数是非常危险的,因为编译器可以使用这种构造函数把参数的类型隐式转换为类类型。如果想要隐式类型转换,可以使用explicit关键字,explicit是只提供给类的构造函数使用的关键字,编译器不会把声明为explicit的构造函数用于隐式转换,它只能在程序代码中显示创建对象。

还有一点值得注意的地方,就是赋值和初始化有一些不同,初始化的时候,等号是特殊处理的,不能看做是等号运算符。请看下面的代码。

class A{
public:
    int val_;
    A(int val):val_(val){cout<<"A() "<<val_<<endl;} 
    ~A(){cout<<"~A() "<<val_<<endl;}
    A& operator=(const A& a){
        cout<<"operator="<<endl;
        val_ = a.val_;
        return *this;
    }
};

int main(int argc, char const *argv[])
{  
    /* 如果用explicit声明A,后面两个语句都无法通过编译 */
    A a1 = 10; /* 初始化,不会调用等号运算符 */
    a1 = 20; /* 先把20转化为一个类A,然后使用等号运算符进行赋值 */
    return 0;
}

拷贝构造函数及深浅拷贝

只有一个参数并且参数为该类对象的引用的构造函数称为拷贝构造函数。拷贝构造函数主要作用是使用一个已经存在的对象来初始化一个新的同一类型的对象。如果类中没有说明拷贝构造函数,则系统自动生成一个缺省复制构造函数,作为该类的公有成员。拷贝函数主要在下列两个场景中调用:

默认的拷贝构造函数是逐值拷贝,在有些情况下是不适用的。看下面的代码。

#include <iostream>
#include <stdlib.h>
using namespace std;

class A{
public:
    int *val_;
    A(){
        val_ = (int *)malloc(sizeof(int));
        *val_ = 1;
    }
};

int main(int argc, char const *argv[])
{  
    A a1;
    A a2(a1);
    a2.val_[0] = 0;
    cout<<a1.val_[0]<<endl;
    return 0;
}

因为是逐值拷贝,所以a2和a1的val_指向了同一块内存,则显然是有点不合理的。因为改变了a2的值,a1的值就会跟着改变。

析构函数

析构函数和构造函数恰好相反,析构函数是对象销毁是调用的。析构函数有如下一些特点。

new和delete

new和delete是C++新增的关键字,和malloc和free的功能相似,只不过new和delete除了分配空间外,还会调用对象的构造析构函数。new即可以创建对象也可以创建数组,而delete只能删除对象,如果要删除数组,需要使用delete[]。

上一篇下一篇

猜你喜欢

热点阅读