c++面对对象的基石--类
类是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, 成绩:0return 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;}
析构函数通常用于释放对象在生命周期内申请的资源,比如动态分配的内存、打开的文件等,避免资源泄漏。
这里只是介绍了类的基本知识,后面围绕类再来展开深入学习
更多推荐


所有评论(0)