感谢哔哩哔哩UP"开发者LaoJ",以下是学习记录~
1、类是属性、行为的封装,将数据的表示和数据的处理集中到一起
2、类内成员的访问权限为:public、protected、private
- public,可以被外界访问
- protected,不可被外界访问,可以被派生类访问
- private,不可以被外界访问,也不可以被派生类访问
3、类的相关成员函数的定义
cpp
/*类的相关成员函数的定义*/
//1、成员函数内联定义
//2、成员函数在类内声明,在类外定义
//3、构造函数,允许重载
//3.1使用默认构造函数(=default)
myclass() = default;
//3.2、带参数
myclass(int val) : data(val) {......}
//3.3、动态内存分配(会调用默认构造函数)
myclass *objptr = new myclass();
......
delete objptr;
//3.4、复制构造函数
myclass obj1;
myclass obj2 = obj1;
//4、析构函数
//没有参数,没有返回值,不可以重载
//自定义的析构函数,一般用来释放动态内存、关闭文件、释放资源等
//当对象超出作用域时,析构函数会被调用
//5、成员初始化列表
myclass(string str, int val) : name(str), data(val) {......}
浅拷贝:
- 浅拷贝是默认的拷贝行为,它只是简单地复制对象的成员变量的值。如果对象中有指针成员,浅拷贝只会复制指针的值,而不会复制指针指向的内容
- 浅拷贝会导致多个对象的指针成员指向同一块内存。当其中一个对象释放了这块内存后,其他对象的指针就会变成悬空指针
深拷贝:
- 深拷贝会复制对象的所有成员变量,包括指针指向的内容。对于动态分配的资源,深拷贝会为新对象分配新的内存,并将原对象的内容复制到新内存中
- 需要手动实现拷贝构造函数和赋值运算符重载,以确保深拷贝的正确性
4、this指针
this指针指向类的实例化对象,是一个顶层const,即不可以修改指向,但是可以借助this指针来修改类内的成员
5、静态数据成员
- 静态成员是类级别的,不是对象级别的,被类的所有对象共享
- 静态成员变量在类内声明,类外定义(必须在类外定义),在编译时,由编译器分配内存
- 静态成员函数只能访问静态变量
- 可以通过类名和类的对象访问静态成员
6、虚函数指针和虚函数表
当一个类中有一个或多个虚函数时,编译时会为其生成一张虚函数表(vftable),在类的对象中,有一个虚函数指针(vfptr),指向该虚函数表
如果父类中有虚函数,无论子类是否有虚函数,均会有一张虚函数表
cpp
#include<iostream>
#include<vector>
using namespace std;
class Device {
public:
virtual void fetchData() {
cout << "device fetchData" << endl;
}
virtual void performAction() {
cout << "device performAction" << endl;
}
virtual ~Device() = default;
};
class LightController : public Device {
public:
void fetchData() override {
cout << "LightController fetchData" << endl;
}
/*void performAction() {
* cout << "LightController performAction" << endl;
}*/
};
int main(void)
{
Device objD, objD2;
LightController objL;
objL.fetchData();
objL.performAction();
return 0;
}

根据上图可知:
- 父类对象和子类对象的虚函数指针的指向不同。其中,因为子类的performAction函数没有实现,所以在虚函数表中,子类的performAction函数的地址与父类中同名函数的地址一样
- 定义了两个基类的对象,对象内均有一个vfptr指针,指向的地址相同,说明一个基类的多个对象共享一份虚函数表
一、封装
封装简化了代码的复杂性,隐藏类的内部的实现细节,只是向外提供接口以供调用,起到了保护其内部成员的作用
二、继承
继承允许创建一个新的类,从一个或多个现有的类中继承属性和行为。新类称为派生类,被继承类称为基类
一般而言,共性的东西会被设计在基类中,个性化的东西被设计在派生类中
在构造派生类对象时,会先调用基类的构造函数,然后调用派生类的构造函数;在派生类对象作用域结束时,先调用派生类的析构函数,然后调用基类的析构函数
2.1、三种继承形式
继承的三种形式:public、protected和private
- public:
类外:对于继承的成员,保持与父类的对外可见性一致
类内:可以处理父类的public、protected成员,不可以处理父类的private成员
- protected:
类外:对于继承的成员,除了private成员外,其它成员对外均为protected
类内:可以处理父类的public、protected成员,不可以处理父类的private成员
- private:
类外:对于继承的成员,对外均为private
类内:可以处理父类的public、protected成员,不可以处理父类的private成员
- 总结:
派生类内是否可以访问父类的成员,需要看父类成员的访问权限。类外是否可访问父类成员,需要看继承形式
不管什么继承方式,也不管父类成员本身的访问权限修饰符是什么,子类对象已完全继承父类的所有属性和行为,只是对外不一定可见,在类内不一定可以处理
2.2、同名问题
对于普通成员,只能通过类的对象进行访问
对于静态成员,可以通过类的对象和类访问,静态成员属于类,类的所有对象共享静态变量。故而,静态变量必须在类内声明,类外定义
2.2.1、子类与父类普通成员名称相同
cpp
#include<iostream>
using namespace std;
class Base
{
public:
int a = 0;
void myfun() {
cout << "I am parent class's function" << endl;
}
};
class A : public Base
{
public:
int a = 1;
void myfun() {
cout << "I am child class's function" << endl;
}
};
int main(void)
{
/*使用对象访问静态成员*/
A obj;
cout << obj.a << endl;
cout << obj.Base::a << endl;
obj.myfun();
obj.Base::myfun();
}
2.2.2、子类与父类静态成员名称相同
cpp
#include<iostream>
using namespace std;
class Base
{
public:
static int a;
static void myfun() {
cout << "I am parent class's function" << endl;
}
};
int Base::a = 0;
class A : public Base
{
public:
static int a;
static void myfun() {
cout << "I am child class's function" << endl;
}
};
int A::a = 1;
int main(void)
{
/*使用对象访问静态成员*/
A obj;
cout << obj.a << endl;
cout << obj.Base::a << endl;
obj.myfun();
obj.Base::myfun();
/*使用类访问静态成员*/
cout << A::a << endl;
cout << A::Base::a << endl;
A::myfun();
A::Base::myfun();
return 0;
}
2.2.3、派生类的多个父类中有同名成员
cpp
#include <iostream>
using namespace std;
class parent1
{
public:
int a = 6;
};
class parent2
{
public:
int a = 9;
};
class child : public parent1, public parent2
{};
int main(void)
{
child obj;
//cout << obj.a << endl; //错误,需要指明是哪个父类中的成员
/*当多个父类中有同名成员时
*需要指明该同名成员是来自哪个父类*/
cout << obj.parent1::a << endl;
cout << obj.parent2::a << endl;
}
2.2.4、钻石继承问题--虚继承
cpp
#include<iostream>
using namespace std;
class base
{
public:
base() {
cout << "create base" << endl;
basea = 0;
}
~base() {
cout << "delete base" << endl;
}
int basea;
};
class A : virtual public base
{
public:
A() {
cout << "create A" << endl;
a = 1;
}
~A() {
cout << "delete A" << endl;
}
int a;
};
class B : virtual public base
{
public:
B() {
cout << "create B" << endl;
b = 2;
}
~B() {
cout << "delete B" << endl;
}
int b;
};
class C : public A, public B
{
public:
C() {
cout << "create C" << endl;
c = 3;
}
~C() {
cout << "delete C" << endl;
}
int c;
};
/*通过使用virtual,可以使得A和B中的basea指向同一块内存
*如果没有使用该关键字,那么A和B会有basea各自的副本*/
int main(void)
{
C c1;
cout << "通过C访问A类的a:" << c1.a << endl;
cout << "通过C访问B类的b:" << c1.b << endl;
/*第一个问题:不明确的问题
*如果不使用virtual关键字,需要指明访问哪个父类的同名成员
*使用virtual关键字后,同名成员指向同一内存,不确定来源亦可
*以下三个语句均正确*/
cout << "通过C访问base类的basea:" << c1.basea << endl;
cout << "通过C访问base类的basea:" << c1.A::basea << endl;
cout << "通过C访问base类的basea:" << c1.B::basea << endl;
/*第二个问题:多份同名,不同值的问题*/
c1.A::basea = 5;
c1.B::basea = 6;
cout << "c的basea的值:" << c1.basea << endl;
cout << "sizeof(C) is: " << sizeof(C) << endl;
return 0;
}

三、多态
多态允许一个类的对象被视为其他类的对象
多态允许以一致的方式使用不同的类型的对象
多态通过虚函数、指针/引用来实现
多态分为静态多态(函数重载、运算符重载)和动态多态(虚函数、继承对象间的指针/引用)
3.1、静态多态
3.1.1、函数重载
函数名必须相同,参数列表必须不同,返回值可以相同也可以不同
3.1.2、运算符重载
对于我们自定义的类,系统的内置运算符并不适用。因此需要重载运算符
重载运算符:
- 返回值类型 operator 运算符 (操作数列表) {相关操作}
重载运算符的形式:
- 使用内联函数:此时,函数为类的成员函数
- 类内声明类外定义:此时,函数仅可以访问类的公有成员
- 友元函数实现:此方式比较通用
cpp#include<iostream> #include<string> using namespace std; class student { public: /*使用成员函数实现 加号运算符 重载*/ student& operator+ (student& st) { this->age += st.age; return *this; } /*使用友元函数实现 输出运算符 重载 *必须返回一个输出流对象的引用*/ friend ostream& operator<< (ostream& os, student& st); /*使用类内声明类外定义的方式实现 前置++和后置++运算符 重载 *此时,仅可访问类的公有成员*/ student& operator++ (); student operator++ (int); void print() { cout << "age is: " << age << endl; } int score = 60; private: string name = "lisi"; int age = 23; }; ostream& operator<< (ostream& os, student& st) { os << "name:" << st.name << " " << "age:" << st.age << "score:" << st.score << endl; return os; } student& student::operator++ () { ++score; return *this; } student student::operator++ (int) { student tmp(*this); ++score; return tmp; } int main(void) { student st1, st2; st1.print(); st1 + st2; st1.print(); cout << st1; st2 = st1++; cout << st1.score << endl; cout << st2.score << endl; cout << st1 << endl; cout << st2 << endl; return 0; }
3.2、动态多态
动态多态支持向上转型而不支持向下转型。即一个派生类对象可以赋给一个基类的指针,但一个派生类指针不可以指向一个派生类对象
重写是派生类重新实现(覆盖)基类中已有的虚函数(若不是虚函数,派生类无法覆盖基类的实现),发生在继承关系中。重写会带来一定的内存开销和寻址延迟,但是相对于现在的计算机来说,并不是多大问题(虚函数本身是一个指针)
当基类指针指向派生类对象时,如果基类的析构函数不是虚函数,编译器会根据指针的类型(基类)调用析构函数,而不会根据实际对象的类型(派生类)调用析构函数
cpp
#include<iostream>
#include<vector>
using namespace std;
/*fetchData和performAction是纯虚函数,所以Device是抽象类,该类不可以实例化对象
*纯虚函数必须在派生类中实现
*Device应该提供虚析构函数,否则,不会调用子类的析构函数*/
class Device {
public:
virtual void fetchData() const = 0;
virtual void performAction() const = 0;
virtual ~Device() = default;
};
class TemperatureSensor : public Device {
public:
void fetchData() const override {
cout << "Fetching temperature data." << endl;
}
void performAction() const override {
}
~TemperatureSensor() {
cout << "delete T" << endl;
}
};
class LightController : public Device {
public:
void fetchData() const override {
cout << "Fetching light data." << endl;
}
void performAction() const override {
cout << "Adjusting light intensity" << endl;
}
~LightController() {
cout << "delete L" << endl;
}
};
class SmartSocket : public Device {
public:
void fetchData() const override {
cout << "Fetching power consumption data." << endl;
}
void performAction() const override {
cout << "Turning on/off the smart socket" << endl;
}
~SmartSocket() {
cout << "delete S" << endl;
}
};
int main(void)
{
vector<Device*> devices;
/*devices中有多个基类Device的指针,分别指向不同的派生类
*当使用基类的指针指向派生类时 并且基类中的函数使用virtual进行修饰时
*使用该指针调用函数时,派生类中的同名函数可以覆盖掉基类中的该函数*/
devices.push_back(new TemperatureSensor());
devices.push_back(new LightController());
devices.push_back(new SmartSocket());
for (const auto& device : devices) {
device->fetchData();
device->performAction();
}
/*如果基类Device不定义虚析构函数,并且子类对象是通过new(动态申请内存)出来的
*清空Device类型的指针,并不会调用子类的析构函数
*如果派生类中有动态分配的内存或其他资源(如文件句柄、网络连接等),将会导致内存泄漏
*此处,将调用deveces.size()次基类的析构函数*/
for (const auto& device : devices) {
delete device;
}
return 0;
}
四、友元
友元会破坏封装性,不能滥用
友元不受类内访问权限的限制
友元关系不具有传递性,并且是单向的。即,A是B的友元,B是C的友元,A不一定是C的友元,B不一定是A的友元
4.1、友元函数
友元函数通常声明在类中,定义在类外(也可以定义在类内,此时就是隐式的内联函数)
友元函数不是类的成员函数,但是可以访问类的私有成员
友元函数可以是全局函数,也可以是其他类的成员函数
cpp
#include <iostream>
using namespace std;
class MyClass {
private:
int secretValue;
public:
MyClass(int value) : secretValue(value) {}
friend void showSecret(const MyClass& obj);
};
void showSecret(const MyClass& obj) {
cout << "Secret value: " << obj.secretValue << endl;
}
int main() {
MyClass obj(42);
showSecret(obj);
return 0;
}
4.2、友元类
cpp
#include <iostream>
using namespace std;
class MyClass {
private:
int secretValue;
public:
MyClass(int value) : secretValue(value) {}
friend class FriendClass;
};
class FriendClass {
public:
void showSecret(const MyClass& obj) {
cout << "Secret value: " << obj.secretValue << endl;
}
};
int main() {
MyClass obj(42);
FriendClass friendObj;
friendObj.showSecret(obj);
return 0;
}