类是c++最核心的特性之一,是c++面面对对象的编程基础。c++中围绕类会衍生出一大堆内容,需要学习和掌握的内容很多,需要一点点的总结。这次来学习关于类的一些基础知识

类与对象:面向对象的核心概念

在现实生活中,我们周围存在着各种各样的事物,比如汽车、动物、电子产品等。每种事物都有自己的属性和行为,例如汽车有颜色、品牌、速度等属性,有行驶、刹车、转弯等行为。在 C++ 中,我们用来描述这些事物的共同特征,而对象则是类的具体实例。

类就像是一个模板或蓝图,定义了某类事物的属性和行为;对象则是根据这个模板创建出来的具体个体。比如,我们可以定义一个 “汽车” 类,然后根据这个类创建出 “我的红色轿车”“他的黑色 SUV” 等具体对象。

在 C++ 中,定义类的基本语法如下:



class 类名 {    访问修饰符:  // public(公开)、private(私有)、protected(保护)        成员变量;  // 属性:比如姓名、年龄        成员函数;  // 行为:比如说话、吃饭};  // 注意分号结尾!

类的成员包括成员变量和成员函数。成员变量用于描述类的属性,成员函数用于描述类的行为。其实c++的类是从c语言结构体的概念扩展而来的,在c++中也对结构体做了扩展,在结构体中也可以定义普通变量和函数,基本上与类的结构没有什么区别了。他们之间唯一的区别就是,类的默认访问类型是private,而结构体为public

类的访问控制权限

C++ 提供了三种访问控制权限,用于控制类成员的访问范围:

  • public(公有):公有成员可以在类的内部和外部访问。
  • private(私有):私有成员只能在类的内部访问,不能在类的外部直接访问。
  • protected(保护):保护成员与私有成员类似,但在涉及到类继承时有区别,后面再讨论

为了实现数据的私密性和安全性,一般会把成员变量设置成私有属性,对外界隐藏。只将需要暴露给外部的成员函数设置成公有属性,让外界调用,用来间接访问类的私有成员。这种方法在面对对象编程(OOP)里面被称为数据隐藏。而c++使用类对事物进行抽象,将数据和操作数据的函数封装在一个类中,这种方式称为数据封装

#include <iostream>#include <string>using namespace std;// 定义Student类class Student {private:  // 私有成员:只能被类内函数访问    string name;  // 姓名    int id;       // 学号    float score;  // 成绩public:   // 公开成员:类外可以访问    // 显示学生信息的函数    void showInfo() {        cout << "姓名:" << name << endl;        cout << "学号:" << id << endl;        cout << "成绩:" << score << endl;    }    // 设置学生信息的函数(因为name/id/score是private,需要通过public函数修改)    void setInfo(string n, int i, float s) {        name = n;        id = i;        score = s;    }};

我们可以在类内部定义成员函数,也可以在类外部定义成员函数。在类外部定义成员函数时,需要使用作用域解析运算符:: 来指明函数属于哪个类。一般来说,我们会将类的声明和定义分开,除了个别实现很简单的函数,最好在类外定义成员函数,这样使得代码更清晰易读

例如,我们可以将 Student 类的成员函数在类外部定义:


/ 类外实现showInfo()void Student::showInfo() {    cout << "姓名:" << name << endl;    cout << "学号:" << id << endl;    cout << "成绩:" << score << endl;}// 类外实现setInfo()void Student::setInfo(string n, int i, float s) {    name = n;    id = i;    score = s;}

通常我们会在头文件(.h)中声明类,在源文件(.cpp)中实现类的成员函数。

 const 成员函数

如果成员函数不会修改类的成员变量,可以将其声明为const成员函数,这样可以在语法上保证函数不会修改成员变量,进一步加强了数据安全性

class Student {    // ... 其他成员 ...public:    // const成员函数:保证不修改成员变量    float getScore() const {        // score = 100;  // 编译错误!const函数不能修改成员变量        return score;    }};

构造函数与析构函数

在创建对象时,我们通常需要初始化对象的成员变量;在销毁对象时,可能需要释放对象占用的资源。C++ 提供了构造函数和析构函数来完成这些工作。

构造函数

构造函数是一种特殊的成员函数,用于初始化对象。构造函数具有以下特点:

  • 构造函数的名称与类名相同。

  • 构造函数没有返回值,也不需要写 void。(这个很特殊)

  • 构造函数在创建对象时自动调用。

  • 构造函数可以重载,即一个类可以有多个构造函数,只要它们的参数列表不同。

另外如果我们没有定义构造函数,编译器会自动生成一个默认构造函数,默认构造函数没有参数,仅会帮我们创建一个对象,不会作其他操作

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

class Student {private:    string name;    int id;    float score;public:    // 无参构造函数:初始化默认值    Student() {        name = "未知";        id = 0;        score = 0.0;        cout << "无参构造函数被调用!" << endl;    }    void showInfo() {        cout << "姓名:" << name << ", 学号:" << id << ", 成绩:" << score << endl;    }};

这样在创建对象时会自动调用默认构造函数


int main() {    Student stu2;  // 创建对象时,自动调用Student()    stu2.showInfo();  // 输出:姓名:未知, 学号:0, 成绩:0    return 0;}

需要注意的是,默认构造函数只有在没有显式定义构造函数时才会存在,如果定义了带参数的构造函数,编译器则不再生成默认构造函数,因此如下用法会报错

class Student {private:    string name;    int id;    float score;public:    // 带参数构造函数    Student(string _name, int _id, float _score) {        name = _name;        id = _id;        score = _score;    }    void showInfo() {        cout << "姓名:" << name << ", 学号:" << id << ", 成绩:" << score << endl;    }};
int main(){    Student stu1;  //编译出错,不存在默认构造函数    return 0;}

正确用法如下

    Student stu1 = Student("张三", 1, 80.5);    Student stu2("张三", 2, 84.5);    Student stu3{"张三", 3, 86.5};

C++11 还引入了成员初始化列表的语法,用于更高效地初始化成员变量:

// 初始化列表初始化成员变量Student(string n, int i, float s) : name(n), id(i), score(s) {    //等价于name = n;  id = i; score = s;    cout << "带参构造函数(初始化列表)被调用!" << endl;}

成员初始化列表在初始化成员变量时比在构造函数体内赋值更高效,尤其是对于类类型的成员变量。

拷贝构造函数

一种特殊的构造函数:在同一个类中用一个已存在的对象初始化新对象,形式如下:

classname(const classname &obj){// 构造函数的主体}

比如用stu3初始化stu4

class Student {    // ... 其他成员 ...public:    // 拷贝构造函数:用other对象初始化新对象    Student(const Student& other) : name(other.name), id(other.id), score(other.score) {        cout << "拷贝构造函数被调用!" << endl;    }};// 使用:用stu3初始化stu4Student stu4 = stu3;  // 调用拷贝构造函数stu4.showInfo();  // 输出和stu3一样:姓名:李四, 学号:2023002, 成绩:88.5

拷贝构造函数一般用于以下几个场景

  • 通过使用另一个同类型的对象来初始化新创建的对象。

  • 复制对象把它作为参数传递给函数。

  • 复制对象,并从函数返回这个对象。

同样的,如果没有显式定义拷贝构造函数,编译器会生成一个默认拷贝构造函数,直接对成员变量的值进行复制,这里存在深拷贝和浅拷贝的相关知识,以后深入了再学习记录

析构函数

析构函数也是一种特殊的成员函数,用于在对象销毁时释放资源。析构函数具有以下特点:

  • 析构函数的名称是在类名前加上波浪号~。

  • 析构函数没有返回值,也没有参数。

  • 析构函数在对象销毁时自动调用。

  • 一个类只能有一个析构函数,不能重载。

如果我们没有定义析构函数,C++ 会自动生成一个默认析构函数,默认析构函数也不做任何操作。

Student 类的析构函数:

class Student {    // ... 其他成员 ...public:    // 析构函数    ~Student() {        cout << "析构函数被调用:" << name << "的对象销毁了!" << endl;    }};

当对象生命周期结束时,析构函数会自动被调用:

void test() {    Student stu5("王五", 23, 92.0);  // 创建对象,调用构造函数}  // test()函数结束,stu5销毁,调用析构函数int main() {    test();  // 输出:                      // 析构函数被调用:王五的对象销毁了!    return 0;}

析构函数通常用于释放对象在生命周期内申请的资源,比如动态分配的内存、打开的文件等,避免资源泄漏。

这里只是介绍了类的基本知识,后面围绕类再来展开深入学习

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐