C++面向对象总结——类和对象

引言

一直以来,对于编程都是零零散散的在学,本篇对于C++的学习做一个系统全面的回顾总结。

在这里也反思一下,在学校信心满满自以为什么都懂,实践下来,才发现编程的学习需要系统的构架和扎实的底蕴。


一,C++中的类和对象

1️⃣类的定义

定义一个类,本质上是定义一个数据类型的蓝图。它定义了类的对象包括了什么(成员变量或者说是属性),以及可以在这个对象上执行哪些操作(成员函数或者说是方法)。

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

class Point
{
public://方法(成员函数)
     void setPoint(int x, int y);
     void printPoint();
 
private://属性(成员变量)
     int xPos;
     int yPos;
};
代码说明:
     上段代码中定义了一个名为 Point 的类, 具有两个私密属性, int型的xPos和yPos, 分别用来表示x点和y点。
     在方法上, setPoint 用来设置属性, 也就是 xPos 和 yPos 的值; printPoint 用来输出点的信息。 
2️⃣类的对象

类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。下面的语句声明了类 Point的两个对象:

Point center;//定义一个中心点对象(Point类)
Point conner;//定义一个角点对象(Point类)

为了更清晰的理解,看一个实例:

#include <iostream>
using namespace std;

class Point
{
public://成员函数(方法)
    void setPoint(int X,int Y);
    void printPoint()//成员函数的内部定义
    {
        cout << "点的x坐标:" << xPos << endl;
        cout << "点的y坐标:" << yPos << endl;
    }
private://成员变量(属性)
    int xPos;
    int yPos;
};
//成员函数的外部定义
void Point::setPoint(int X, int Y)
{
    xPos = X;
    yPos = Y;
}

//主函数调用Point类
int main(void)
{
    Point center;
    center.setPoint(20, 20);
    center.printPoint();
    return 0;
}

结果:

需要注意的是:我们定义的成员变量是私有的,因此不能对center的xPos 和yPos直接赋值(将成员变量改为公有即可赋值)

center.xPos = 20;//报错
center.yPos = 20;//报错

3️⃣类的构造函数

创建完类之后,我们在使用类之前,需要对类进行初始化。定义如何对类进行初始化的成员函数就称为构造函数构造函数在创建类类型的对象时被执行。它的工作是保证每个对象的数据成员具有合适的初始值。

构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。

class Counter
{
public:
         // 类Counter的构造函数
         // 特点:以类名作为函数名,无返回类型
         Counter()
         {
                m_value = 0;
         }
private:
          // 数据成员
         int m_value;
}

 当该类对象被创建时,会自动调用该构造函数(由构造函数完成成员的初始化工作)

eg:    Counter c1;
编译系统为对象c1的每个数据成员(m_value)分配内存空间,并调用构造函数Counter( )自动地初始化对象c1的m_value值设置为0。

构造函数的种类

C++中的构造函数可以分为4类(以Student类为例):
(1)默认构造函数。默认构造函数的原型为Student();//没有参数
(2)构造函数初始化列表  Student(int num,int age);//有参数
(3)复制(拷贝)构造函数Student(Student&);//形参是本类对象的引用
(4)转换构造函数Student(int r) ;//形参时其他类型变量,且只有一个形参

🧡默认构造函数

数列表为空的构造函数称为默认构造函数。(所有参数都指定了默认值的带参数的构造函数也是默认构造函数)

class Student
{
public:
    //默认构造函数
    Student()
    {
       num=1001;
       age=18;       
    }
private:
    int num;
    int age;
};
int main()
{
    //用默认构造函数初始化对象S1
    Student s1;
    return 0;
}

💙构造函数初始化列表

构造函数化初始化列表形式如下:

    class Student
{
public:
    //第一种形式
    Student(int n, int a) :num(n), age(a) {}
    //第二种形式
    Student() :num(1002), age(18) {}
private:
    int num;
    int age;
};
    int main()
    {
        Student s2(1002, 18); //调用第一种形式的构造函数创建对象
        Student s3;//调用第二种形式的构造函数创建对象
        return 0;
    }

值得一提的是,下面这句话的声明是正确的,可以通过编译

Student s3();

但此时我们并不是声明了一个对象,而是声明了类的一个函数。

💛复制(拷贝)构造函数

复制构造函数参数为类对象本身的引用,用于根据一个已存在的对象复制出一个新的该类的对象,一般在函数中会将已存在对象的数据成员的值复制一份到新创建的对象中。

 class Student
{
public:
 Student(const Student &C)
{
 //复制对象C中的数据成员
   num=C.num;
   age=C.age;
}
private:
    int num;
    int age;
};

如代码所示,它的作用是将一个已存在的对象C,复制给调用该复制构造函数的对象

为什么函数中可以直接访问对象c的私有成员?
答:(网上)因为拷贝构造函数是放在本身这个类里的,而类中的函数可以访问这个类的对象的所有成员,当然包括私有成员了。

💙转换构造函数

  • 转换构造函数用于将其他类型的变量,隐式转换为本类对象。
  • 下面的转换构造函数,将int类型的r转换为Student类型的对象,对象的age为r,num为1004.
 Student(int r)
 {
     int num=1004int age= r;
 }

4️⃣类的析构函数

 析构函数为成员函数的一种,名字与类名相同,在前面加‘~’没有参数和返回值在C++中“~”是位取反运算符。

一个类最多只能有一个析构函数析构函数不返回任何值,没有函数类型,也没有函数参数,因此它不能被重载。

构造函数可能有多个,但析构函数只能有一个,就像人来到人世间,可能出生的环境家庭不同(重载构造函数),但最终都会死亡(析构函数)。

class C
{
public:
    ~C ( ) { }
    ~C (int i) { }    // error C2524: “C”: 析构函数 必须有“void”参数列表
    // warning C4523: “C”: 指定了多个析构函数
};

析构函数对象消亡时即自动被调用。可以定义析构函数来在对象消亡前做善后工作,比如释放分配的空间等。
如果定义类时没写析构函数,则编译器生成缺省析构函数。缺省析构函数什么也不做。如果定义了析构函数,则编译器不生成缺省析构函数。
析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作。

class A
{
private :
    char * p;
public:
    A ( )
    {
        p = new char[10];
    }
    ~ A ( )
    {
        delete [] p;
    }
};

若A类没写析构函数,则在生成A对象后,new出来的内存空间未清除,可能造成内存泄露。


 知识点C++ delete与delete[]的区别

  • delete 释放new分配的单个对象指针指向的内存
  • delete[] 释放new分配的对象数组指针指向的内存

总结下就是,如果ptr代表一个用new申请的内存返回的内存空间地址,即所谓的指针,那么:
delete   ptr   代表用来释放内存,且只用来释放ptr指向的内存。
delete[]   rg   用来释放rg指向的内存,!!还逐一调用数组中每个对象的destructor!!
对于像int/char/long/int*/struct等等简单数据类型,由于对象没有destructor,所以用delete 和delete [] 是一样的!但是如果是C++对象数组就不同了!

具体详看C++ delete 和 delete []的区别 - 王陸 - 博客园 (cnblogs.com)


 在创建一类的对象数组时,对于每一个数组元素,都会执行缺省的构造函数。同样,对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用。

#include<iostream>
using namespace std;
unsigned count = 0;
class A
{
public:
    A ( )
    {
        i = ++count;
        cout << "Creating A " << i <<endl;
    }
    ~A ( )
    {
        cout << "A Destructor called " << i <<endl;
    }
private :
    int i;
};
int main( )
{
    A ar[3];  // 对象数组
    return 0;
}

这类似于栈的后进先出

 🧡那么析构函数什么情况下被调用呢?

如果出现以下几种情况,程序就会执行析构函数:
(1)如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。
(2)static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。
(3)如果定义了一个全局对象,则在程序的流程离开其作用域时(如main函数结束或调用exit函数) 时,调用该全局对象的析构函数。
(4)如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。
(5)调用复制构造函数后。 

二,友元函数

类的友元函数是定义在类外部(如需要在类定义中该函数原型前使用关键字 friend,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

如果要声明函数为一个类的友元,如下所示:

class Box
{
   double width;
public:
   double length;
   friend void printWidth( Box box );
   void setWidth( double wid );
};

实例

#include <iostream>
using namespace std;

class Box
{
    double width;
public:
    friend void printWidth(Box box);
    void setWidth(double wid);
};

// 成员函数定义
void Box::setWidth(double wid)
{
    width = wid;
}

// 请注意:printWidth() 不是任何类的成员函数(因此不用写friend和类::)
void printWidth(Box box)
{
    // 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 
    cout << "Width of box : " << box.width << endl;
}

// 程序的主函数
int main()
{
    Box box;

    // 使用成员函数设置宽度
    box.setWidth(10.0);

    // 使用友元函数输出宽度
    printWidth(box);

    return 0;
}

为什么要使用友元函数?

友元提供了不同类的成员函数之间、类的成员函数与一般函数之间进行数据共享的机制。通过友元,允许外面的类或函数去访问类的私有变量和保护变量,从而使两个类共享同一函数 。c++中的友元为封装隐藏这堵不透明的墙开了一个小孔,外界可以通过这个小孔窥视内部的秘密。

优点:能够提高效率,表达简单、清晰

缺点:友元函数破环了封装机制,尽量不使用成员函数,除非不得已的情况下才使用友元函数。

什么时候使用友元函数?

  1. 运算符重载的某些场合需要使用友元。
  2. 两个类要共享数据的时候

关于友元,有两点需要说明:

  • 友元的关系是单向的而不是双向的。如果声明了类 B 是类 A 的友元类,不等于类 A 是类 B 的友元类,类 A 中的成员函数不能访问类 B 中的 private 成员。
  • 友元的关系不能传递。如果类 B 是类 A 的友元类,类 C 是类 B 的友元类,不等于类 C 是类 A 的友元类。

除非有必要,一般不建议把整个类声明为友元类,而只将某些成员函数声明为友元函数,这样更安全一些。

 三,指向类的指针

指向 C++ 类的指针与指向结构的指针类似,访问指向类的指针的成员,需要使用成员访问运算符 ->,就像访问指向结构的指针一样。与所有的指针一样,您必须在使用指针之前,对指针进行初始化

下面的实例有助于更好地理解指向类的指针的概念:

#include <iostream>
using namespace std;

class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};

int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // 声明对象Box1
   Box *ptrBox;                // 声明一个指针(指针初始化)
   // 保存第一个对象的地址
   ptrBox = &Box1;
   // 现在尝试使用成员访问运算符来访问成员
   cout << "Volume of Box1: " << ptrBox->Volume() << endl;return 0;
}

四,C++的this 指针

在 C++ 中,每一个对象都能通过 this 指针来访问自己的地址。this 指针是所有成员函数的隐含参数。因此,在成员函数内部,它可以用来指向调用对象。

友元函数没有 this 指针,因为友元不是类的成员。只有成员函数才有 this 指针。

下面的实例有助于更好地理解 this 指针的概念:

#include <iostream>
using namespace std;
 
class Box
{
   public:
      // 构造函数定义
      Box(double l=2.0, double b=2.0, double h=2.0)
      {
         length = l;
         breadth = b;
         height = h;
      }
      double Volume()
      {
         return length * breadth * height;
      }
      int compare(Box box)
      {
         return this->Volume() > box.Volume();
      }
   private:
      double length;     // Length of a box
      double breadth;    // Breadth of a box
      double height;     // Height of a box
};
 
int main(void)
{
   Box Box1(3.3, 1.2, 1.5);    // Declare box1
   Box Box2(8.5, 6.0, 2.0);    // Declare box2
 
   if(Box1.compare(Box2))
   {
      cout << "Box2 is smaller than Box1" <<endl;
   }
   else
   {
      cout << "Box2 is equal to or larger than Box1" <<endl;
   }
   return 0;
}

说明:

表达式Box1.compare(Box2),Box1调用了compare函数,compare函数中的this指针此时指向的是Box1,因此compare函数中的表达式return this->Volume() > box.Volume()变成了return Box1->Volume() > box.Volume()

 

   

posted @ 2021-07-15 14:02  唯有自己强大  阅读(752)  评论(0编辑  收藏  举报