相关基础概念
OOP:面向对象编程。OOP语言的四大特征是抽象、封装/隐藏、继承、多态。
实体:比如人,一个人就是一个实体,比如表格,一张表格也可以是一个实体。
属性、行为:比如人的属性有年龄、身高、体重等,人的行为有吃喝拉撒等。
类:实体的抽象类型,比如一个人的类,就是先把人的属性、行为等抽象出来,虚虚地放在代码段上,仅仅是一个模板,类型是不占空间的。当要描述某个人的这个实体的时候,把这个类填充上属性即可,但是多个实体公用一套行为。实体的属性的抽象对应类的成员变量,实体的行为对应类的成员方法。属性一般是私有的,方法一般是公有的。
成员方法的实现方法:有类体内实现和类体外实现两种方法。类体内实现的方法自动转为inline函数。类体外实现需要在函数名称前面加上作用域和作用域解析符。
实例化:把类填充上属性,也叫类的实例化。
对象:类实例化的产物叫做对象。
This指针
类的成员方法一经编译,所有的方法参数,都会加一个this指针,接收调用该方法的对象的地址。
所以C++的good1.init(“面包”)实际上是C语言中的init(&good1,”面包”)。但是在C++中,当你调用一个成员函数时,编译器会自动传递调用该函数的对象的地址作为函数的隐含参数(即 this 指针),因此你无需手动传递对象的地址。通俗来说就是,C++的good1.init(“面包”),看起来没有传入&good1,但是在这个方法内部只要用了this这个关键字,就相当于在用&good1。
构造函数与析构函数
初始化和释放不一定要通过构造和析构函数完成,但是C++中为了保证代码的安全性,设置了构造和析构函数,析构函数能自行执行,构造函数必须自动或显式地执行以此保证我们不会忘记初始化。
构造函数:一般用于初始化对象成员变量。
析构函数:一般用于释放对象成员变量所占的外部堆内存。
构造函数的特点:1、不带返回值。2、与类同名。3、可以有多个形参列表不同的构造函数。4、构造函数会根据我们的传参自行选择是哪个函数。5、对象的创建必定经过构造函数。
析构函数的特点:1、不带返回值。2、~+类名。3、只能有一个。4、不能带参数。5、析构函数可以自行调用(不建议)。5、如果对象创建在堆上,需要自己手动删除,删除的时候回自动调用析构函数。6、一个程序可能会有多个对象需要析构,按反入栈的顺序进行析构。7、一个对象销毁前必定经过析构函数。
拷贝构造函数与深浅拷贝
形参类型是同类(比如const CClass &c)的构造函数就是拷贝构造函数。
CClass c2 = c1;
CClass c3(c1);
这两句代码都是在调用拷贝构造函数,是等价的。
默认情况下拷贝构造函数是把成员变量一一复制,如果有成员变量是地址,指向了堆内的空间,那么复制过来的成员变量指向了同一块堆内存空间,这是浅拷贝。
如果需要把堆内存空间也放到新开辟的堆空间里,我们需要在拷贝构造函数里自定义这个堆空间的开辟和复制过程,也就是深拷贝。
运算符重载
运算符重载(Operator Overloading)是 C++ 中的一种特性,允许你重新定义或扩展已有的运算符的行为,使其适用于自定义的类或数据类型。
比如赋值运算符重载,operator=有点类似一个被定义的函数。
class MyClass { public: int value; MyClass& operator=(const MyClass& other) { if (this != &other) { value = other.value; } return *this; } };
返回引用是支持连续的operator=赋值操作。
返回引用意味着是除了把*this作为返回值之外,operator=的左边如果是引用类型,那么operator=的左边的变量也是*this的引用。如果不是引用类型,那么返回值被当做右值使用。
构造函数的初始化列表
成员对象:B类变量是A类的成员变量之一,那么可以叫这个B类变量为A类的成员对象。
如果B类自定义了构造函数,那么默认构造函数会被覆盖掉,那么A类对象构造的时候无法自动调用B类对象的构造函数,就需要手动调用。
初始化列表的语法如下:
ClassName::ClassName(Type1 arg1, Type2 arg2, ...) : member1(arg1), member2(arg2), ... {
// 构造函数的主体
}
初始化列表会省略类型。
假设menber1是int age,在初始化列表中member1(arg1)其实就是int age = arg1;
假设member2是CClass c,member2(arg2)其实就是CClass c(arg2);
注意:成员变量的初始化和他们定义顺序有关,和构造函数初始化列表中出现的先后顺序无关。
类的各种成员、方法
普通成员方法
- 属于类的作用域。
- 调用该方法的时候,需要依赖一个对象。
- 可以任意访问对象的私有成员变量。
- 默认传入了this指针。
Static成员变量
- 所有同类对象共享,所有对象都能操作它。
- 需要类外定义并且初始化。初始化方法:类型 作用域::变量名 = 值。
- 它不属于对象,属于类级别。本质是全局变量,但是作用域被限制在了类中。
Static成员方法
1、可以直接用类名::static方法的方式调用。如果每次访问static变量都需要通过某个对象,显然很别扭,因此诞生了static方法,可以不通过对象,而是通过类直接访问static变量。
2、因为调用它不依赖对象,所以它没有传入this指针,不方便访问普通的变量。
Const成员方法
如果创建对象的时候创建成了const类型的对象,那么普通的成员方法因为默认是非const的this指针,无法接受const类型的对象的指针。这时候就需要在普通方法的声明的右括号之后加const修饰,表示this指针形参被改成了const this指针形参。
建议如果是只涉及读操作的成员方法,都要加const修饰,这样const对象也能调用,常对象也能调用。
Const方法只能读私有成员,不能修改。
总结
普通成员方法、const成员方法、static成员方法,差别主要是玩的是不同this指针,const玩的是const this指针,static干脆不玩指针。
指向类的成员的指针
可以在类的作用域下定义指针,指向public的类的成员方法或成员变量。
调用这个指针的时候需要依赖具体的对象。
#include <iostream> class MyClass { public: void memberFunction(int x) { std::cout << "Member function called with argument: " << x << std::endl; } int memberVariable = 42; }; int main() { // 声明指向成员函数的指针 void (MyClass::*functionPtr)(int) = &MyClass::memberFunction; // 声明指向成员变量的指针 int MyClass::*variablePtr = &MyClass::memberVariable; MyClass obj; // 通过对象调用成员函数 (obj.*functionPtr)(10); // 通过对象调用成员变量 std::cout << "Member variable value: " << obj.*variablePtr << std::endl; return 0; }