C++基础04(类)

文章目录

    • 类的定义与声明
        • 类的定义
        • **类方法**
        • this指针
        • **类成员权限限定符**
    • 构造函数
    • 析构函数
        • 对象的生存期
    • 堆内存管理

类的定义与声明

类的定义

​ 在C++中,⽤户⾃定义数据类型的⽅式主要有两种:结构体类型和类类型。其中类类型是根据结构体类型演化过来的⼀种类型,可以说是结构体的增强版。类是⽤户⾃⼰指定的类型。如果程序中要⽤到类类型,必须⾃⼰根据需要进⾏声明,或者使⽤别⼈已设计好的类。C++标准本身并不提供现成的类的名称、结构和内容。在C++中声明⼀个类类型和声明⼀个结构体类型是相似的

类(class)是C++有别于C语言的最重要的概念之一,正是因为有了类的概念,C++才拥有了所谓面向对象(OOP,即Object Original Programming)的编程模式,那么类究竟是什么呢?

单从语法层面讲,类其实就是增加了函数的结构体。而从逻辑层面讲,类是增加了数据行为的结构体。类跟结构体一样,先要自定义类类型,然后再使用类类型去定义变量(对象)。以下是示例代码:

#include 
#include 
using namespace std;


class stu
{
public:
    string name;
    int age;
    void printf_info()
    {
        cout << name << age << endl;
    }
};





int main(int argc, char const *argv[])
{
    class stu s1;
    s1.name = "dad";
    s1.age = 20;
    s1.printf_info();
    
    return 0;
}

以上定义中可以看到,跟C语言的结构体定义是非常类似的,它们的唯二区别是:

  • 出现了作用域限定符public,该关键字用来指明类成员可以在类外任意地方访问。
  • 出现了函数 void showInfo(),这代表类不仅有数据,还有自身的行为。

​ 在类中,如果对成员不加任何访问修饰,系统则默认该成员是私有的。在C++中,对成员的访问限制,系统提供了三个关键字:public公共成员,protected受保护成员,private私有成员。访问修饰符的作用范围:是从修饰符位置开始到下一个修饰符结束或来类的结束符为止。在一个类中可以存在多个访问修饰符,也可以重复,一般建议先写public,再写protected,最后写私有的。

小贴士:

  • 空类或只有普通函数成员的类一般占用1个字节内存,这个字节是用来区分该类创建的不同对象用的。

  • 在一个类中,函数成员一般不占对象的内存,类中的函数成员在内存中只存一份,供该类创建的所有对象所共享。一个对象分配的内存主要是类中的数据成员,和普通函数没关系。如果类型中存在虚函数成员,一般会多占用对象的一个指针大小的内存。一个类中无论存在多少个虚成员函数,对于该类的对象来说,只会多占用一个指针大小的内存。

  • 对于类中的静态数据成员,不占用对象的内存,因为静态数据成员属于类不属于对象

  • 采用结构体初始化类中的数据成员是有局限性,对共公有成员是可以的,但对类中的非公有成员是失效的。在C++中初始化数据成员的方式:

    • 1.采用类外逐成员进行初始化;
    • 2.可以通过构造函数来初始化类中的数据成员(更通用)
  • 类中普通成员和函数成员只能被该类的对象所调用

  • 类中的函数成员有两种定义方式:

    • 1.在类中定义;
    • 2.在类内声明,在类外定义。函数成员类外定义的格式:返回类型 类名::成员函数名(形参列表){}
  • 类中的成员函数可以实现对当前类内的数据成员和函数成员进行访问,类内成员函数访问内部成员不受访问修饰符约束

  • 受保护成员,可以在当前的类内和当前类的派生类内部调用。私有成员,外部不可以直接访问,但是可以简介访问:比如通过公共成员函数去获取私有成员进行返回

类方法

所谓类方法,就是类里面的函数接口,这是与结构体有重大区别的地方之一。类方法实际上表达的是一个对象的行为属性,比如小猫类 Kitty 应该要提供如下类方法,来表达一只小猫的正常的行为:

class Kitty
{
public:
    void eat(){cout<< "进食" <

调用这些类方法的形式也非常简单,跟普通的函数调用没有什么区别,只不过类方法一般是通过类的对象来调用的(静态对象可以通过对象调用也可以通过类调用),例如:

int main(void)
{
    Kitty myCat;

    // 通过对象myCat调用各个类方法
    myCat.eat();    // 进食
    myCat.sleep();  // 睡觉
    myCat.actCute();// 卖萌
}
this指针
class BMP
{
    string fileName;
    int width;
    int height;

    char *RGB;

public:
    // 构造函数:
    BMP(string fileName)
    {
        fileName = fileName; // 对象名字与参数名字相同了
    }
};

上述代码显然有误,由于类成员 fileName 恰好与构造函数参数同名,因此其赋值语句发生了名字冲突,无法正常赋值,此时除了修改它们的名字外,通常也可以用 this 指针来指明变量的所属,比如:

// 构造函数
BMP(string fileName)
{
    this->fileName = fileName; 
}

重点:

  • this 指针是所有类方法(包括构造函数和析构函数)的隐藏参数
  • this 指向调用了该类方法的类对象
类成员权限限定符

类与结构体有许多不同,其中之一是结构体中的所有数据默认都是公有的(public),而类中的成员(不管是数据还是方法)会被分成三种不同的访问权限,它们分别是:

  • 公有(public)成员:任何地方都可以访问。
  • 保护(protected)成员:只能在本类域和及其后代类域中访问。
  • 私有(private)成员:只能在本类域内部访问。

一般而言,对于类成员的权限限定的基本原则是:

  • 对于类数据属性,一般会设定为私有
  • 对于类方法,一般会设定为公有

构造函数

  • 概念:类的特殊成员,专门用于初始化类的各项数据。
  • 特点:
    1. 任何一个类都必然有至少一个构造函数。
    2. 如果一个类没有显式定义构造函数,那么系统会自动添加一个隐藏的无参空构造函数。
    3. 如果一个类显式定义了构造函数,那么隐藏的无参空构造函数将被取消。
    4. 每当分配一个类对象时,构造函数就会被自动调用。
    5. 构造函数没有返回值类型,且名字必须与类名一致。
    6. 构造函数跟普通函数一样,可以重载。
    7. 如果类中一旦存在常数据成员,此时对其初始化需要通过初始化列表形式来初始化;初始化列表可以对类中的普通数据 成员进行初始化,也可以混合。
    8. 当类中存在引用数据成员时,此时必须使用初始化列表来对其初始化。
    9. 用另一个类的对象当作当前类的数据成员时,如果该类没有默认构造函数,此时必须用初始化列表来初始化该成员。
    10. 调用构造函数时,先执行初始化列表中的初始化语句,然后再执行构造函数体中的初始化语句。
    11. 初始化列表在初始化数据成员时,与写的先后顺序没有关系,和数据成员在类中定义的先后顺序有关。

一个类中可以存在多个构造函数,意味着构造函数也可以支持函数重载。在函数重载中,要警惕带默认参数的函数对重载的影响。

析构函数

  • 概念:类的特殊成员,专门用于处理类对象被释放时的收尾工作。析构函数是⼀个特殊的由⽤户定义的成员函数 ,当该类的对象离开了它的域(对象的⽣⽣命期结束时) 或者 delete表达式应⽤到⼀个该类的对象的指针上时 析构函数会⾃动被调⽤。析构函数的名字是在类名前加上波浪线 ~ ,它不返回任何值也没有任何参数。尽管我们可以为⼀个类定义多个构造函数 但是我们只能提供⼀个析构函数, 它将被应⽤在类的所有对象上。
  • 特点:
    1. 任何一个类有且仅有一个析构函数。
    2. 如果一个类没有显式定义任何析构函数,那么系统会自动添加一个隐藏的空析构函数。
    3. 如果一个类显式定义了析构函数,那么隐藏的空析构函数将被取消。
    4. 每当释放一个类对象时,析构函数就会被自动调用。
    5. 析构函数没有返回值类型,也没有参数,名字与类名一致,且在前面多一个波浪号。
    6. 不能重载。
    7. 如果用户在类中没有提供析构函数,则系统在编译程序时,会自动生成一个。

以下代码展示了 BMP 的析构函数的实现:

class BMP
{
    string fileName;
    int width;
    int height;

    char *RGB;

public:
    // 析构函数
    ~BMP()
    {
        // 释放各种资源等
    }
};
对象的生存期

⼀个对象从创建到释放为⽌的时间段称为对象的⽣存期,⼀个对象的⽣存期主要

包括:

1.给对象分配内存空间

2.由构造函数初始化

3.通过调⽤对象的成员函数实现对象的功能

4.由析构函数清理现场(⽐如new申请内容,delete释放)

5.销毁内部对象(释放对象内部创建的⼦对象或数据成员)

6.从内存中清除该对象(系统回收其分配的内存)

堆内存管理

在C语言中,使用 malloc()free() 来分配和释放堆内存,这两个函数在C++中也一样可用,但C++有更好的API来实现这一功能,它们是:

  • 分配堆内存:new
  • 释放堆内存:delete

以下是一个简单的示例代码:

int main(void)
{
    // 申请一块int型数据大小的堆内存
    int *p1 = new int;

    // 申请一块可以存储100个int型数据大小的堆内存
    int *p2 = new int[100];

    // 申请一块35个字节大小的堆内存
    char *p3 = new char[35];

    // 释放所有的堆内存资源
    delete p1;
    delete [] p2;
    delete [] p3;
}

以上代码,完全等价于:

int main(void)
{
   // 申请一块int型数据大小的堆内存
    int *p1 = (int *)malloc(sizeof(int));

    // 申请一块可以存储100个int型数据大小的堆内存
    int *p2 = (int *)malloc(sizeof(int) * 100);

    // 申请一块35个字节大小的堆内存
    char *p3 = (char *)malloc(35);

    // 释放所有的堆内存资源
    free(p1);
    free(p2);
    free(p3);
}

可以看到,在操作基本数据类型的时候,这两组堆内存操作函数是没有区别的,它们真正的区别在给类对象分配内存的时候,具体而言指的是:

  • 申请类类型的内存时,new 会自动调用类的构造函数,而 malloc() / calloc() 不会。
  • 释放类类型的内存时,delete 会自动调用类的析构函数,而 free() 不会。

你可能感兴趣的:(c++,开发语言)