C++面向对象程序设计(上)

C++面向对象程序设计(上)

C语言是一种基于过程的编程语言,C++在此基础上发展而成,保留了C的绝大部分的功能和运行机制。同时增加了面向对象的机制。C++面向对象的程序设计,除了主函数,其他的函数基本都在类中,只有通过类才能调用类中的函数。程序的基本单元是类,程序面对的是一个个类和对象。

☆面向过程的程序设计(Process oriented programming),也称为结构化程序设计(Structured programming),有时会被视为是指令式编程(Imperative programming)的同义语。。编写程序可以说是这样一个过程:从系统要实现的功能入手把复杂的任务分解成子任务,把子任务再分解成更简单的任务,层层分解来完成。可以采用函数(function)或过程(procedure)一步步细化调用实现。程序流程结构可分为顺序(sequence)、选择(selection)及重复(repetition)或循环(loop)。

☆面向对象程序设计(Object Oriented Programming),是围绕着问题域中的对象(Object)来设计,从同种类型的对象抽象出类,但在程序中,必须要事先定义类(Class),然后再调用类产生对象(实例化)。对象是类的实例(instance)。

类的成员包含属性——在C++中称为数据成员(data member)和方法——在C++中称为成员函数(Member Function)。对象则指的是类的实例。

【对象(Object)与类(Class)的关系

类是对象的抽象,而对象是类的具体实例。类与对象的关系是抽象与类的实例化就是对象,对象的共性特征抽象出来就是类。

比如车是类,具体的车就是对象 如F1赛车。

比如你要买一台PC,你在订单上列出了这台PC的CPU和显卡型号、显示屏的大小、键盘是104还是87位、PC颜色,这所有信息组成在一起就是类,当服务员拿出了一台符合这个订单的具体PC时,这个PC就是那个类的具体对象。

从同种类型的对象抽象出类,但在程序中,必须要事先定义类,然后再调用类产生对象(实例化)。有了类的好处是:我们可以把同一类对象相同的数据与功能/操作存放到类里,而无需每个对象都重复存一份,这样每个对象里只需存自己独有的数据即可,极大地节省了空间。所以,如果说对象是用来存放数据与功能的容器,那么类则是用来存放多个对象相同的数据与功能/操作的容器。

实例(instance)和对象(object)

实例和对象基本上是同义词,它们常常可以互换使用。对象就是类的实例,所有的对象都是实例,但并不是所有的实例都是对象。除了类的实例外的实例都不是对象。我们常见的实例都是类的实例,此时二者没有区别。】

C++中的类

C++中使用关键字 class 来定义类, 其基本形式如下:

class 类名
{
       public或private:数据成员(data member)
       public或private:成员函数(Member Function)
};

说明:

1)类名 需要遵循一般的命名规则;

2)public 与 private 是限制的关键字, private 表示该部分内容是私密的, 不能被外部所访问或调用, 只能被本类内部访问; 而 public 表示公开的, 外界可以直接访问或者调用。

3)结束部分的有分号。

、定义一个点(Point)类, 其
数据成员: x坐标, y坐标
成员函数: 1.设置x,y的坐标值; 2.输出坐标的信息。
class Point
    {
        public:
            void setPoint(int x, int y);
            void printPoint();

        private:
            int xPos;
            int yPos;
};

C++类成员函数的定义有两种方式:
1、在类定义时定义成员函数
成员函数的实现可以在类定义时同时完成, 如代码:
#include <iostream>
using namespace std;
   class Point
   {
       public:
           void setPoint(int x, int y) //实现setPoint函数
           {
               xPos = x;
               yPos = y;
            }

           void printPoint()       //实现printPoint函数
           {
                cout<< "x = " << xPos << endl;
                cout<< "y = " << yPos << endl;
            }

        private:
            int xPos;
            int yPos;
    };

    int main()
    {
        Point M;        //用定义好的类创建一个对象 点M
        M.setPoint(10, 20); //设置 M点 的x,y值
        M.printPoint();     //输出 M点 的信息

        return 0;
     }

运行之,参见下图:

2、在类外定义成员函数
在类外定义成员函数通过在类内进行声明, 然后在类外通过作用域操作符 :: 进行实现, 形式如下:
返回类型 类名::成员函数名(参数列表)
    {
     //函数体
     }
       
将上例代码改用在类外定义成员函数的代码:
#include <iostream>
using namespace std;
class Point
{
    public:
        void setPoint(int x, int y); //在类内对成员函数进行声明
        void printPoint();

    private:
        int xPos;
        int yPos;
};

void Point::setPoint(int x, int y) //通过作用域操作符 '::' 实现setPoint函数
{
    xPos = x;
    yPos = y;
}

void Point::printPoint()       //实现printPoint函数
{
    cout<< "x = " << xPos << endl;
    cout<< "y = " << yPos << endl;
}

int main()
{
    Point M;//用定义好的类创建一个对象 点M
    M.setPoint(10, 20); //设置 M点 的x,y值
    M.printPoint();     //输出 M点 的信息

    return 0;
}

运行之,参见下图:

类对象的创建(类的实例化)
将一个类定义并实现后, 就可以用该类来创建对象了, 创建的过程如同 int、char 等基本数据类型声明一个变量一样简单,创建一个类的对象称为该类的实例化,格式:
类名 对象名;
如上面的例子中的:Point M;  

类对象成员的使用 
通过 对象名.公有函数名(参数列表); 的形式就可以调用该类对象所具有的方法——成员函数, 通过 对象名.公有数据成员; 的形式可以访问对象中的数据成员。
如上面的例子中的:M.setPoint(10, 20);

C++构造函数与析构函数
1、构造函数(Constructor)

构造函数是一种特殊的成员函数,与类名相同且没有返回类型。它在创建对象时由编译器自动调用一次,用于初始化对象的数据成员。

构造函数可以有多个重载版本,每个版本具有不同的参数列表。根据提供的参数,编译器将选择合适的构造函数来创建对象。

构造函数的作用
一般来说, 构造函数有以下三个方面的作用:
①给创建的对象建立一个标识符;
②为对象数据成员开辟内存空间;
③完成对象数据成员的初始化。
        
默认构造函数
当用户没有显式的去定义构造函数时, 编译器会为类生成一个默认的构造函数, 称为 "默认构造函数", 默认构造函数不能完成对象数据成员的初始化, 只能给对象创建一标识符, 并为对象中的数据成员开辟一定的内存空间。
        
构造函数的特点
无论是用户自定义的构造函数还是默认构造函数都主要有以下特点:
①在对象被创建时自动执行;
②构造函数的函数名与类名相同;
③没有返回值类型、也没有返回值;
④构造函数不能被显式调用。

构造函数的显式定义
若默认构造函数无法满足我们的要求, 所以我们需要显式定义一个构造函数来覆盖掉默认构造函数以便来完成必要的初始化工作, 当用户自定义构造函数后编译器就不会再为对象生成默认构造函数。
    
在构造函数的特点中我们提到, 构造函数的名称必须与类名相同, 并且没有返回值类型和返回值, 看一个构造函数的定义:
#include <iostream>

using namespace std;

class Point
    {
        public:
            Point()     //声明并定义构造函数
            {
                cout<<"自定义的构造函数被调用...\n";
                xPos = 100;         //利用构造函数对数据成员 xPos, yPos进行初始化
                yPos = 100;
            }
            void printPoint()
            {
                cout<<"xPos = " << xPos <<endl;
                cout<<"yPos = " << yPos <<endl;
            }

        private:
            int xPos;
            int yPos;
    };

    int main()
    {
        Point M;    //创建对象M
        M.printPoint();

        return 0;
    }

运行之,参见下图:

说明:

在Point类的 public 成员中我们定义了一个构造函数 Point() , 可以看到这个Point构造函数并不像 printPoint 函数有个void类型的返回值, 这正是构造函数的一特点。在构造函数中, 我们输出了一句提示信息, "自定义的构造函数被调用...", 并且将对象中的数据成员xPos和yPos初始化为100。
在 main 函数中, 使用 Point 类创建了一个对象 M, 并调用M对象的方法 printPoint 输出M的数据成员信息, 根据输出结果看到, 自定义的构造函数被调用了, 所以 xPos和yPos 的值此时都是100, 而不是一个随机值。
构造函数的定义也可放在类外进行。

带有参数的构造函数
在上个示例中实在构造函数的函数体内直接对数据成员进行赋值以达到初始化的目的, 但是有时候在创建时每个对象的数据成员有可能是不同的, 这种直接赋值的方式显然不合适。不过构造函数是支持向函数中传入参数的, 所以可以使用带参数的构造函数来解决该问题。
#include <iostream>
using namespace std;

class Point
    {
        public:
            Point(int x = 0, int y = 0)     //带有默认参数的构造函数
            {
                cout<<"自定义的构造函数被调用...\n";
                xPos = x;         //利用传入的参数值对数据成员进行初始化
                yPos = y;
            }
            void printPoint()
            {
                cout<<"xPos = " << xPos <<endl;
                cout<<"yPos = " << yPos <<endl;
            }

        private:
            int xPos;
            int yPos;
    };

    int main()
    {
        Point M(10, 20);    //创建对象M并初始化xPos,yPos为10和20
        M.printPoint();

        Point N(200);       //创建对象N并初始化xPos为200, yPos使用参数y的默认值0
        N.printPoint();

        Point P;            //创建对象P使用构造函数的默认参数
        P.printPoint();

        return 0;
    }

运行之,参见下图:

说明:
在这个示例中的构造函数 Point(int x = 0, int y = 0) 使用了参数列表并且对参数进行了默认参数设置为0。在 main 函数中共创建了三个对象 M, N, P。
M对象不使用默认参数将M的坐标初始化10和20;
N对象使用一个默认参数y, xPos初始化为200;
P对象完全使用默认参数将xPos和yPos初始化为0。

对象中的一些数据成员除了在构造函数体中进行初始化外还可以通过调用初始化表来进行完成, 要使用初始化表来对数据成员进行初始化时使用 : 号进行调出, 例如:
Point(int x = 0, int y = 0):xPos(x), yPos(y)  //使用初始化表
    {
        cout<<"调用初始化表对数据成员进行初始化!\n";
    }
在 Point 构造函数头的后面, 通过单个冒号 : 引出的就是初始化表, 初始化的内容为 Point 类中int型的 xPos 成员和 yPos成员, 其效果和 xPos = x; yPos = y; 是相同的。

与在构造函数体内进行初始化不同的是, 使用初始化表进行初始化是在构造函数被调用以前就完成的。每个成员在初始化表中只能出现一次, 并且初始化的顺序不是取决于数据成员在初始化表中出现的顺序, 而是取决于在类中声明的顺序。
 一些通过构造函数无法进行初始化的数据类型可以使用初始化表进行初始化, 如: 常量成员和引用成员, 这部分内容将在后面进行详细说明。
使用初始化表对对象成员进行初始化的例子:
#include <iostream>

using namespace std;

class Point
{
    public:
        Point(int x = 0, int y = 0):xPos(x), yPos(y)
        {
             cout<<"调用初始化表对数据成员进行初始化!\n";
        }

        void printPoint()
        {
             cout<<"xPos = " << xPos <<endl;
             cout<<"yPos = " << yPos <<endl;
        }

    private:
        int xPos;
        int yPos;
};

int main()
{
    Point M(10, 20);    //创建对象M并初始化xPos,yPos为10和20
    M.printPoint();
 
    return 0;
}
 

运行之,参见下图:

2、析构函数(Destructor)

析构函数是一种特殊的成员函数,与类名相同但在前面加上波浪号(~),没有返回类型。它在对象销毁时自动调用,用于清理对象占用的资源。

析构函数在对象销毁时调用的顺序与对象的创建顺序相反。即最后创建的对象最先销毁,最先创建的对象最后销毁。

与构造函数相反, 析构函数是在对象被撤销时被自动调用, 用于对成员撤销时的一些清理工作, 例如在前面提到的手动释放使用 new 或 malloc 进行申请的内存空间。析构函数具有以下特点:
析构函数函数名与类名相同, 紧贴在名称前面用波浪号 ~ 与构造函数进行区分, 如: ~Point();
构造函数没有返回类型, 也不能指定参数, 因此析构函数只能有一个, 也不能被重载;
当对象被撤销时析构函数被自动调用, 与构造函数不同的是, 析构函数可以被显式的调用, 以释放对象中动态申请的内存。
对象生命周期结束时, C++ 编译系统系统自动调用析构函数。
当用户没有显式定义析构函数时, 编译器同样会为对象生成一个默认的析构函数, 但默认生成的析构函数只能释放类的普通数据成员所占用的空间, 无法释放通过 new 或 malloc 进行申请的空间, 因此有时我们需要自己显式的定义析构函数对这些申请的空间进行释放, 避免造成内存泄露。
析构函数的例子
#include <iostream>
#include <cstring>

using namespace std;

class Book
{
    public:
        Book( const char *name )  //构造函数
        {
            bookName = new char[strlen(name)+1];
            strcpy(bookName, name);
        }
        ~Book()       //析构函数
        {
            cout<<"析构函数被调用...\n";
            delete []bookName;  //释放通过new申请的空间
        }
        void showName() { cout<<"Book name: "<< bookName <<endl; }

    private:
        char *bookName;
};

int main()
{
    Book CPP("C++ Primer");
    CPP.showName();

    return 0;

}

运行之,参见下图:

说明:
代码中创建了一个 Book 类, 类的数据成员只有一个字符指针型的 bookName, 在创建对象时系统会为该指针变量分配它所需内存, 但是此时该指针并没有被初始化所以不会再为其分配其他多余的内存单元。在构造函数中, 我们使用 new 申请了一块 strlen(name)+1 大小的空间, 也就是比传入进来的字符串长度多1的空间, 目的是让字符指针 bookName 指向它, 这样才能正常保存传入的字符串。
在 main 函数中使用 Book 类创建了一个对象 CPP, 初始化 bookName 为 "C++ Primer"。从运行结果可以看到, 析构函数被调用了, 这时使用 new 所申请的空间就会被正常释放。
        
自然状态下对象何时将被销毁取决于对象的生存周期, 例如全局对象是在程序运行结束时被销毁, 自动对象是在离开其作用域时被销毁。
        
如果需要显式调用析构函数来释放对象中动态申请的空间只需要使用 对象名.析构函数名(); 即可, 例如上例中要显式调用析构函数来释放 bookName 所指向的空间,只要: CPP.~Book();


C++的的继承( Inheritance])、封装(Encapsulation)、多态(Polymorphism)可见下一讲https://blog.csdn.net/cnds123/article/details/109602105

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习&实践爱好者

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值