本篇仅对本人C++五月份的学习做一个大致的思维导图和总结,各个详细的知识点在具体文章中都有提到,本文不对所有的知识点做详细的解释,如有需要可以移步具体文章进行阅读理解。
目录
🤔类和对象:
🙂 什么是类?
🙂类的组成:
🙂特殊成员函数:
1.构造函数:
构造函数调用规则:
拷贝构造函数特殊点:
🤔成员函数和运算符重载:
🙂成员函数重载:
🙂运算符重载:
例如:
🤔继承:
🙂继承可以分为以下几种类型:
🙂继承中的对象模型
🤔多态:
🙂动态多态代码:
🤔模板:
🙂函数模板:
🙂类模板:
🤔容器:
🙂string容器:
🙂vector容器:
🙂deque容器:
🙂queue容器:
🙂list容器:
🙂set容器:
🙂map容器:
红黑树:
🤔算法:
🙂遍历算法:
🙂排序算法:
🙂拷贝替换算法:
🙂算数生成算法:
🙂集合算法:
🤔杂项:
🙂1.引用:
🙂2.this指针:
🙂3.友元:
🙂4.虚析构与纯虚析构:
🙂5.谓词与函数对象:
🤔类和对象:
🙂 什么是类?
在C++中,类是一种自定义的数据类型,它定义了一组属性(数据成员)和方法(成员函数),用于描述具有相似特征的对象。类通过封装将数据和行为归为一体,对外隐藏了实现细节,提供了对外的接口,使得使用者可以访问类中的数据和方法,而不用关心内部的实现。
📖通过类,我们可以创建多个对象,每个对象都含有一组独特的属性值和可以调用的方法。类还支持继承和多态等面向对象编程的特性,可以更好地实现代码的复用和扩展。
🙂类的组成:
类由两部分组成:成员变量和成员函数。
📖成员变量:类的成员变量是指类的属性或者状态,它们保存了类的对象的状态信息。成员变量也可以称为数据成员,它们可以是基本数据类型,也可以是对象类型或其他数据类型,例如:
class Person {
private:
string name;
int age;
float height;
public:
// 构造函数
Person(string n, int a, float h) {
name = n;
age = a;
height = h;
}
// 成员函数
string getName() {
return name;
}
int getAge() {
return age;
}
float getHeight() {
return height;
}
};
上面的类定义中,name
、age
、height
三个变量是成员变量,用于保存人的基本信息(姓名、年龄、身高)。
📖成员函数:类的成员函数是指类的方法或操作,它们定义了类的行为。成员函数也可以称为成员方法或方法。成员函数可以操作成员变量,并且可以访问类的其他方法。例如上面的类定义中,getName()
、getAge()
和 getHeight()
都是成员函数。
除了成员变量和成员函数之外,C++ 中的类还可以包含访问控制符、构造函数、析构函数、静态成员变量、静态成员函数、友元函数等。在使用类时,可以通过对象来访问它的成员变量和成员函数。
🙂特殊成员函数:
1.构造函数:
构造函数是一种用于初始化对象的特殊成员函数,当创建对象时,构造函数会自动被调用。在C++中,每个类都可以有一个或多个构造函数,构造函数名称与类名称相同,不带返回类型。通常构造函数用于将对象的数据成员初始化为适当的值。
📖构造函数的主要作用是初始化对象。当我们创建一个对象时,编译器首先为其分配内存地址,然后调用构造函数对对象的各个数据成员进行初始化。
📖构造函数分为普通构造和拷贝构造。
class Person {
private:
string name;
int age;
public:
// 构造函数
Person(string n, int a) {
name = n;
age = a;
}
// 成员函数
string getName() {
return name;
}
int getAge() {
return age;
}
};
int main() {
Person p("Tom", 20);
cout << "Name:" << p.getName() << ", Age:" << p.getAge() << endl;
return 0;
}
上面的例子中,我们定义了一个名为Person
的类,并在其中定义了一个构造函数Person(string n, int a)
,它用来初始化 Person
类的两个数据成员。在主函数中,我们创建了一个名为 p
的 Person
类对象,它的 name
属性被赋值为 "Tom"
,age
属性被赋值为 20
。
需要注意的是,如果我们没有定义构造函数,编译器会自动生成默认构造函数,它不需要参数且什么也不做。但是在大多数情况下,我们需要定义自己的构造函数以满足特定的需求。
构造函数调用规则:
📖1.如果用户自定义了有参构造函数,c++不会再提供默认的无参结构,但是会提供默认拷贝结构
📖2.如果用户自己定义了拷贝构造函数,c++不会再提供其他构造函数
拷贝构造函数特殊点:
拷贝构造函数进行的是浅拷贝,也就是只是把要拷贝的对象的地址赋值拷贝,而并不是重新开辟一块空间存储值。
也就是说如果我们对A进行释放之后,0x23地址的数据就会被释放,如果此时再释放浅拷贝的B,就会崩溃,因为一个地址不能连续被释放两次。
🤔成员函数和运算符重载:
🙂成员函数重载:
在C++中,成员函数重载是指在同一个类中定义多个同名的成员函数,但是这些函数的参数数量或类型不同,以便能够处理不同类型或数量的数据。
📖成员函数重载可以有效地提高程序的可读性和灵活性,它允许使用相同的函数名来访问多个功能不同的函数,以简化代码并提高代码的复用性。
🙂运算符重载:
运算符重载是指自定义类型或类对象上的运算符操作的行为。换句话说,它是在类中定义的将运算符(例如+、-、*等)用于特定类型的方法。
📖C++ 允许运算符重载,它将操作符与具有自定义类型的对象关联起来。使用运算符重载,您可以将运算符的含义扩展到自定义类型。例如,我们可以定义一个 Vector 类,使其支持向量的加法运算。
例如:
class Vector {
private:
double x, y;
public:
Vector(double a, double b) {
x = a;
y = b;
}
Vector operator+ (const Vector& v) const {
return Vector(x+v.x, y+v.y);
}
// 其它成员函数...
};
int main() {
Vector v1(1, 2);
Vector v2(3, 4);
Vector v3 = v1 + v2; // 调用重载的 operator+
return 0;
}
这样我们就通过运算符重载实现了对两个向量的加法运算。
🤔继承:
C++中的继承是一种非常重要的面向对象编程(OOP)概念,它可以让我们基于现有的类创建一个新的类,并使用现有类的成员变量和成员函数,以便扩展或修改其行为。
基类(父类) 派生类(子类)
📖在C++中,使用关键字 class
声明一个新类时,可以通过在类声明中指定一个已存在的类的名称来表示这个新类从特定的已存在类派生而来。这种从已存在类派生新类的过程称为继承。
🙂继承可以分为以下几种类型:
-
公有继承(public inheritance):公有继承用于表示一种“is-a”的关系,即派生类是基类的一种类型。在公有继承中,基类的公有成员和受保护成员都会成为派生类的成员,而基类的私有成员只能在基类内部访问。
-
私有继承(private inheritance):私有继承用于表示基类的实现细节被派生类所共享的情况,它不会对外界暴露任何基类的成员。在私有继承中,基类的所有成员(包括公有、受保护和私有的成员)都会成为派生类的私有成员。
-
受保护继承(protected inheritance):受保护继承用于表示一种中间状态,派生类和基类不完全是“is-a”的关系。在受保护继承中,基类的公有成员和受保护成员都将成为派生类的受保护成员,而基类的私有成员仍然无法被派生类访问。
🙂继承中的对象模型
- 其实私有对象也会被继承,只不过由于父类设置,其被隐藏了,因此无法调用
- 也就是说在父类中的所有非静态成员都会被子类继承,只不过有的子类无权调用,被隐藏起来了。
- 子类继承父类之后,当创建子类对象的时候,也会调用父类的析构函数。
- 顺序是父类先构造,子类在构造,然后是子类再析构,父类再析构
- 当子类中出现和父类同名的成员时,对子类的访问可以直接访问,但是对父类的就需要加上作用域。
🤔多态:
- 静态多态
- 动态多态
C++ 多态关系详解_我是一盘牛肉的博客-CSDN博客
动态多态和静态多态都是多态性的两种表现形式,它们都可以实现相同的目标:为不同类型的对象提供一致的接口。
📖静态多态(Static polymorphism)也称为编译时多态或重载,它是在程序编译时就可以确定的多态形式。静态多态是指在编译时就已经确定了函数的具体调用方式,通常使用函数重载(Function Overloading)和运算符重载(Operator Overloading)来实现。静态多态在编译阶段即可确定对象类型,因此具有一定的效率优势。
📖动态多态(Dynamic polymorphism)也称为运行时多态或覆盖,它是在程序运行时根据对象的实际类型动态判断并调用相应的方法。动态多态通常是通过虚函数(Virtual Function)、函数覆盖(Function Overriding)和抽象类(Abstract Class)等机制实现,它能够实现灵活的多态性,并允许对象根据需要动态地切换类型。
🙂动态多态代码:
class Shape {
public:
virtual void draw() {
cout << "绘制形状" << endl;
}
};
class Circle: public Shape {
public:
virtual void draw() {
cout << "绘制圆形" << endl;
}
};
class Square: public Shape {
public:
virtual void draw() {
cout << "绘制正方形" << endl;
}
};
int main() {
Shape *s;
Circle c;
Square sq;
s = &c;
s->draw(); // 调用 Circle 的 draw
s = &sq;
s->draw(); // 调用 Square 的 draw
return 0;
}
在上面的示例中,我们使用了动态多态,通过使用基类的指针类型来调用不同派生类的同名虚函数,从而实现了根据对象的实际类型来动态调用相应的方法。
📖需要注意的是,静态多态通常使用函数重载和运算符重载等机制实现,而在这种情况下编译器会在编译时对函数进行不同的处理。相比之下,动态多态需要在运行时通过虚函数表进行查找,因此效率上有些损失。但动态多态确实是实现多态性的主要方式,特别是在需要便于扩展和维护代码的情况下。
关于动态多态的底层逻辑,我们可以在这篇文章中阅读:C++ 动态多态实现底层逻辑_我是一盘牛肉的博客-CSDN博客
🤔模板:
- 函数模板
- 类模板
自定义模板的学习主要是为了能更好的理解STL标准库中为我们提供的模板。C++的模板是一种通用的编程工具,它允许开发人员定义一种通用的数据类型或函数,以便在不同的数据类型上进行操作,从而实现代码的复用和泛化。模板可以用于定义类模板和函数模板两种形式。
C++ 自定义模板详解_我是一盘牛肉的博客-CSDN博客
🙂函数模板:
函数模板(Function Template)是使用模板定义的函数。函数模板可以在函数声明中使用类型参数,以便编写通用的函数,可以用于不同的参数类型。例如,我们可以编写一个名为max
的函数模板,用于返回两个值中的最大值,以下是一个max
函数模板的示例代码:
template <class T>
T max(T a, T b) {
return a > b ? a : b;
}
int a = 10, b = 20;
cout << max(a, b) << endl; // 输出 20
double x = 2.5, y = -3.7;
cout << max(x, y) << endl; // 输出 2.5
在上面的代码中,max
函数接受两个参数,这两个参数的类型是 T 类型的。在函数体中,使用三目运算符比较这两个参数的大小,然后返回较大的那个参数。
🙂类模板:
类模板(Class Template)是指模板定义的类,可以在类定义中使用类型参数来定义成员变量和成员函数,以便让这些成员能够用于不同的数据类型。例如,我们可以定义一个名为Stack
的栈模板,它可以用于任何数据类型。以下是一个栈模板的示例代码:
template <class T>
class Stack {
private:
// 栈的实现细节
public:
Stack();
void push(T);
T pop();
bool isEmpty();
};
template <class T>
Stack<T>::Stack() {
// 创建空栈
}
template <class T>
void Stack<T>::push(T element) {
// 将元素压入栈中
}
template <class T>
T Stack<T>::pop() {
// 弹出栈顶元素并返回
}
template <class T>
bool Stack<T>::isEmpty() {
// 判断栈是否为空
}
上面的代码定义了一个通用的栈模板Stack<T>
,其中的 T 是可以替换成任何数据类型的类型参数。在模板定义之后,我们可以通过以下方式创建一个特定类型的栈对象:
Stack<int> intStack; // 整型栈
Stack<string> stringStack; // 字符串栈
通过类模板,我们可以在不复制代码的情况下,让代码使用多个数据类型,提高代码的复用性和可维护性。
🤔容器:
- string容器
- vector容器
- deque容器
- queue容器
- list容器
- set容器
- map容器
🙂string容器:
📖string容器是C++ STL中的一种常用容器,用于存储和操作字符串。string的用法与普通的C风格字符串相似,但它提供了更多的便捷和安全功能。
C++ string类成员函数介绍:_我是一盘牛肉的博客-CSDN博客
🙂vector容器:
📖vector容器是C++ STL中的一个动态数组容器,可以在运行时动态的添加和删除元素,是一个常用的容器类型。
C++ vector类成员函数介绍_c++ vector 成员_我是一盘牛肉的博客-CSDN博客
🙂deque容器:
📖deque容器是C++ STL中的一种双端队列容器,可以在两端进行插入和删除操作。deque是由一组数组构成的,支持高效的随机访问,但它也能够在两端添加和删除元素。
C++ deque类成员函数介绍_我是一盘牛肉的博客-CSDN博客
🙂queue容器:
📖queue容器是C++ STL中的一个队列容器,是一种先进先出(FIFO)的数据结构,支持在末尾添加元素、在队头删除元素。queue容器可以使用vector或deque来实现,使用deque实现时可称为双端队列(dequeued).
C++ queue类成员介绍_我是一盘牛肉的博客-CSDN博客
🙂list容器:
📖list容器是C++ STL中的一种双向链表容器,内部以节点的形式维护元素,每个节点包含了数据和指向前一个和后一个节点的指针。因为双向链表的特点,list容器对于元素的插入、删除和移动操作非常高效。
C++ list类成员函数介绍_我是一盘牛肉的博客-CSDN博客
🙂set容器:
📖set容器是C++ STL中的一个关联容器,它可以保存不重复且有序的元素集合。set容器内部使用一种称为红黑树的数据结构来维护元素,它保证了元素的有序性和快速的插入、删除、查找操作。
C++ set类成员函数介绍 (set和multiset)_我是一盘牛肉的博客-CSDN博客
🙂map容器:
📖map容器是C++ STL中的一个关联容器,它提供了一种以键值对形式存储元素的方式。每一个元素都是一个键值对,其中键是唯一的,而值可以重复。map容器内部使用红黑树来组织元素,可以保证元素按键排序,而且可以快速地进行插入、删除和查找操作。、
C++ map类成员介绍 (map与multimap)_我是一盘牛肉的博客-CSDN博客
红黑树:
红黑树(Red-Black Tree)是一种自平衡二叉搜索树。它是一种复杂的数据结构,但是其在进行插入、删除、查找等操作时,可以保持在最坏情况下的时间复杂度为O(log n),因此被广泛应用于各种编程语言和计算机科学领域。
红黑树的每个节点包含颜色、键和值,其性质如下:
- 节点要么是黑色,要么是红色。
- 根节点为黑色。
- 红色节点必须只能有黑色的子节点(反之则不一定成立)。
- 从根到叶子节点或空子节点的每条路径,必须包含相同数量的黑色节点(即相同的黑高度)。为了保持树高度平衡,红黑树对插入和删除操作进行了限制,所以它是一种自平衡二叉搜索树。特别地,红黑树对于插入、删除操作是基本不用调整的,它可以在常数时间内完成这些操作。
容器的使用为我们节省了大量的时间,因此我们要熟练使用各种容器。
🤔算法:
- 遍历算法
- 查找算法
- 排序算法
- 拷贝替换算法
- 算术生成算法
- 集合算法
🙂遍历算法:
C++ 遍历算法_我是一盘牛肉的博客-CSDN博客
🙂排序算法:
C++ 排序算法_我是一盘牛肉的博客-CSDN博客
🙂拷贝替换算法:
C++ 拷贝替换算法_我是一盘牛肉的博客-CSDN博客
🙂算数生成算法:
C++ 常用算数生成算法_我是一盘牛肉的博客-CSDN博客
🙂集合算法:
C++ 常见集合算法_我是一盘牛肉的博客-CSDN博客
🤔杂项:
🙂1.引用:
📖C++引用是一个别名,它是用另一个变量或对象的名称来引用某个对象或变量。引用是C++中的一种重要特性,因为它们不同于指针,能够更直接而安全地访问内存中的数据。
在C++中,引用通过取地址符&来定义。例如,假设已经定义了一个整数变量a,可以通过如下方式来定义一个指向a的引用:
int a = 100;
int& b = a; // 定义一个引用b,指向a
然后就可以通过b来访问a,如下所示:
b = 200;
cout << a << endl; // 输出200
可以看出,b引用了a,因此对b的任何操作实际上都是在对a进行操作。同时,由于引用是一个别名,因此它不能被重新绑定到其他对象或变量上。
引用还有一个很重要的用途是作为函数参数,通过引用传递参数可以避免产生副本的开销来提高程序的性能。
🙂2.this指针:
📖在C++中,this
指针是一个关键字,指向当前对象的指针。它作为类的非静态成员函数的隐含参数传递给函数,可以用于在一个成员函数中引用当前对象。
在一个成员函数内部,可以使用this
关键字来访问当前对象的成员变量和成员函数。例如,假如一个类中有一个成员变量x,可以用this->x
来访问它。
this
指针是一个常量指针,不能被修改。它的类型是指向当前类的指针类型,例如:
class Test {
int x;
public:
void setX(int x) {
this->x = x;
}
};
在上面的例子中,setX
函数中的this
指针指向当前的Test
对象。通过使用this
指针来访问成员变量x
,可以避免成员变量的名称与局部变量的名称冲突。
另外,对于不使用this
指针的情况下,编译器将自动为成员函数添加this
指针。因此,即使在成员函数中不显式地使用this
指针,也可以访问对象的成员变量和成员函数。
🙂3.友元:
📖在C++中,友元(friend)是一种机制,它允许非类成员函数和其他类中的成员函数访问当前类的私有和保护成员,即使这些成员未被公开定义。
友元可以是一个非成员函数、另一个类的成员函数或另一个类的整个实例。为了授权一个函数或类成为当前类的友元,需要在当前类的定义中使用关键字friend
进行声明。
例如:
class MyClass {
private:
int x;
friend void MyFriendFunc(MyClass& obj); // 声明友元函数
};
void MyFriendFunc(MyClass& obj) {
obj.x = 10; // 可以访问MyClass的私有变量x
}
在上面的例子中,MyFriendFunc
是一个非成员函数,但是通过在MyClass
的声明中声明它为友元函数,它可以访问和修改MyClass
的私有成员变量x
。
需要注意的是,友元机制可以破坏类的封装性和安全性,因此在使用友元时需要慎重考虑。只有当确实需要让一个非类成员函数或其他类的成员函数访问私有或保护成员时,才应该考虑使用友元机制。
🙂4.虚析构与纯虚析构:
📖C++中的虚析构和纯虚析构是用来处理继承和多态时可能出现的内存泄漏和定义不完整的问题的。
首先,虚析构函数是在基类中声明的虚函数,其作用是在派生类对象被删除时,正确地释放其所有的资源。它允许在删除指向派生类的指针时调用派生类的析构函数,从而确保所有的资源都被正确地释放,防止内存泄漏。
例如:
class Base {
public:
virtual ~Base() {
// 虚析构函数
}
};
class Derived : public Base {
public:
~Derived() {
// 派生类析构函数
}
};
在上面的例子中,Base
类定义了虚析构函数,这意味着当使用基类指针删除一个派生类对象时,将会调用派生类析构函数,并且释放所有的资源。
接下来,纯虚析构函数是在基类中声明的纯虚函数,它必须在派生类中进行实现,否则派生类将无法被实例化。纯虚析构函数用于定义一个抽象的基类,它提供了一个通用的接口,但没有实际的实现。
例如:
class Shape {
public:
virtual ~Shape() = 0; // 声明纯虚析构函数
};
Shape::~Shape() {
// 空实现
}
在上面的例子中,Shape
类定义了纯虚析构函数。由于它没有实际实现,需要在每个派生类中实现它。由于派生类必须实现纯虚析构函数,因此它们将能够正确地销毁所有资源,从而避免内存泄漏问题。
🙂5.谓词与函数对象:
📖在 C++ 中,谓词是一个返回布尔值的函数或函数对象。通常,谓词用于算法中,被用来确定一个值是否符合某些条件。
谓词可以用来比较数值、比较字符串、检查容器中的元素是否满足某种条件等等。
C++ 函数对象 详解_我是一盘牛肉的博客-CSDN博客