本篇博客对C++相关基础知识做个简单的回顾和整理,方便日后查阅。主要参考这个网页。
一、C++基础知识
- 1、C++是一种静态类型、编译式、通用、大小写敏感、不规则的编程语言,支持过程化编程、面向对象编程和泛型编程。
- 2、C++被认为是一种中级语言,它综合了高级语言和低级语言的特点。
- 3、面向对象开发四大特性:封装(Encapsulation)、继承(Inheritance)、多态(Polymorphism)、抽象(Abstraction)。
- 封装(Encapsulation)是将数据和方法组合在一起,对外部隐藏实现细节,只公开对外提供的接口。这样可以提高安全性、可靠性和灵活性。
- 继承(Inheritance)是从已有类中派生出新类,新类具有已有类的属性和方法,并且可以扩展或修改这些属性和方法。这样可以提高代码的复用性和可扩展性。
- 多态(Polymorphism)是指同一种操作作用于不同的对象,可以有不同的解释和实现。它可以通过接口或继承实现,可以提高代码的灵活性和可读性。
- 抽象(Abstraction)是从具体的实例中提取共同的特征,形成抽象类或接口,以便于代码的复用和扩展。抽象类和接口可以让程序员专注于高层次的设计和业务逻辑,而不必关注底层的实现细节。
- 4、标准C++由三个重要部分组成:
- 核心语言:提供了所有构件块,包括变量、数据类型和常量等
- C++标准库:提供了大量的函数,用于操作文件、字符串等
- 标准模版库(STL):提供了大量的方法,用于操作数据结构等
二、C++数学相关函数
在C++标准库中有cmath
文件,包含了很多数学有关的内置函数。使用前需要#include <cmath>
。常用函数和数字常量简单列举如下:
double cos(double)
: 返回double类型弧度角度对应的余弦值。double sin(double)
: 返回double类型弧度角度对应的正弦值。double tan(double)
: 返回double类型弧度角度对应的正切值。double log(double)
: 返回double类型参数对应的自然对数。double pow(double, double)
: 返回double类型的x(第一个参数)的y(第二个参数)次方值。double sqrt(double)
: 返回参数的平方根值。int abs(int)
: 返回整数参数的绝对值。double fabs(double)
: 返回double型参数的绝对值。double floor(double)
: 返回小于double型参数的最大整数。
此外,从C++20开始,引入了预先定义好的数学常数,引入#include <numbers>
即可使用,部分如下:
- 自然底数e:
std::numbers::e
。 - π:
std::numbers::pi
。
三、C++中的结构体
3.1 结构体定义
struct
关键词用于定义结构体(structure),与类(class)类似,结构体允许你定义成员变量和成员函数。定义格式如下:
struct type_name {
member_type1 member_name1;
member_type2 member_name2;
};
与class
类似,可以在 struct
中使用public
、private
和protected
来定义成员访问权限。在struct
中,默认所有成员都是 public
,而class
中默认都是private
。因此,可以将 struct
当作一种简化形式的 class
,适合用于没有太多复杂功能的简单数据封装。
相比于类,结构体具有如下优点:
- 简单数据封装:适合封装多种类型的简单数据,通常用于数据的存储。
- 轻量级:相比 class,结构体语法更简洁,适合小型数据对象。
- 面向对象支持:支持构造函数、成员函数和访问权限控制,可以实现面向对象的设计。
结构体对象的创建和类相同,也非常简单,结构体类型名+对象名即可,如下:
#include <iostream>
using namespace std;
struct myTime {
int day;
int hour;
int minute;
int second;
};
int main() {
myTime time1;
return 0;
}
3.2 访问结构体成员
使用成员访问运算符.
即可访问成员(可以修改),和访问类成员和函数相同。如下:
#include <iostream>
using namespace std;
struct myTime {
int day;
int hour;
int minute;
int second;
};
int main() {
myTime time1;
cout << time1.day << endl;
time1.day = 100;
cout << time1.day << endl;
return 0;
}
四、C++中的Vector
vector
是一种序列容器,它允许在运行时动态地插入和删除元素(所以又被称为动态数组)。vector
是基于数组的数据结构,但它可以自动管理内存。与 C++数组相比,vector
具有更多的灵活性和功能。vector
是C++标准模板库(STL)的一部分,提供了灵活的接口和高效的操作。需要引入<vector>
头文件。
4.1 基本特性
- 动态大小:
vector
的大小可以根据需要自动增长和缩小。 - 连续存储:
vector
中的元素在内存中是连续存储的,这使得访问元素非常快速。 - 可迭代:
vector
可以被迭代,你可以使用循环(如for循环)来访问它的元素。 - 任意元素类型:
vector
可以存储任何类型的元素,包括内置类型、对象、指针等。
4.2 适合场景
- 当需要一个可以动态增长和缩小的数组时。
- 当需要频繁地在序列的末尾添加或移除元素时。
- 当需要一个可以高效随机访问元素的容器时。
4.3 Vector操作
简单列举部分常用操作函数:
.push_back()
: 将元素添加到vector
末尾.size()
: 获取vector
的元素数量(大小).erase()
: 删除vector
中指定位置的元素.clear()
: 清空vector
中的所有元素
五、C++中的数据结构
前面简单介绍了数组、vector等常用的数据结构,这里对C++中的数据结构做进一步的总结和对比。
5.1 数组(Array)
数组是最基础的数据结构,用于存储一组相同类型的数据。其使用不需要额外包含头文件。
特点:
- 固定大小,一旦声明,大小不能改变。
- 直接访问元素,时间复杂度为 O(1)。
- 适合处理大小已知、元素类型相同的集合。
优点:
- 访问速度快,内存紧凑。
缺点:
- 大小固定,无法动态扩展,不适合处理大小不确定的数据集。
代码展示如下:
#include <iostream>
using namespace std;
int main() {
double myArray[3] = {1, 2, 3};
return 0;
}
5.2 结构体(Struct)
结构体允许将不同类型的数据组合在一起,形成一种自定义的数据类型。其使用不需要额外包含头文件。
特点:
- 可以包含不同类型的成员变量。
- 提供了对数据的基本封装,但功能有限。
代码展示如下:
#include <iostream>
using namespace std;
struct myNode {
int time;
double value;
};
int main() {
myNode node1;
node1.time = 0;
node1.value = 0;
return 0;
}
5.3 类(Class)
类是C++中用于面向对象编程的核心结构,允许定义成员变量和成员函数。与struct
类似,但功能更强大,支持继承、封装、多态等特性。其使用不需要额外包含头文件。
特点:
- 可以包含成员变量、成员函数、构造函数、析构函数。
- 支持面向对象特性,如封装、继承、多态。
代码示例如下:
#include <iostream>
using namespace std;
class Person {
public:
string name;
int age;
Person(string name, int age) {
this->name = name;
this->age = age;
}
};
int main() {
Person john = Person("John", 10);
return 0;
}
5.4 链表(Linked List)
链表是一种动态数据结构,由一系列节点组成,每个节点包含数据和指向下一个节点的指针。使用无需额外包含头文件,但需要自己定义节点结构体,然后组成链表。
特点:
- 动态调整大小,不需要提前定义容量。
- 插入和删除操作效率高,时间复杂度为O(1)(在链表头部或尾部操作)。
- 线性查找,时间复杂度为 O(n)。
优点:
- 动态大小,适合频繁插入和删除的场景。
缺点:
- 随机访问效率低,不如数组直接访问快。
代码示例:
#include <iostream>
using namespace std;
struct Node {
int data;
Node *next;
};
int main() {
// 新建head指针用于遍历
Node *head = NULL;
// 依次添加节点
Node node1;
node1.data = 1;
node1.next = NULL;
Node node2;
node2.data = 2;
node2.next = NULL;
Node node3;
node3.data = 3;
node3.next = NULL;
// 将各节点连接起来
head = &node1;
node1.next = &node2;
node2.next = &node3;
// 利用head指针遍历链表
while (head != NULL) {
cout << head->data << endl;
head = head->next;
}
return 0;
}
5.5 栈(Stack)
栈是一种后进先出(LIFO, Last In First Out)的数据结构,常用于递归、深度优先搜索等场景。使用需要包含<stack>
头文件。
特点:
- 只允许在栈顶进行插入和删除操作。
- 时间复杂度为 O(1)。
优点:
- 操作简单,效率高。
缺点:
- 只能在栈顶操作,访问其他元素需要弹出栈顶元素(无法随机访问)。
代码示例如下:
#include <iostream>
#include <stack>
using namespace std;
int main() {
stack<int> s;
s.push(2);
s.push(3);
s.push(4);
for (int i = 0; i < 3; ++i) {
cout << s.top() << endl;
s.pop();
}
return 0;
}
5.6 队列(Queue)
队列是一种先进先出(FIFO, First In First Out)的数据结构,常用于广度优先搜索、任务调度等场景。使用需要包含<queue>
头文件。
特点:
- 插入操作在队尾进行,删除操作在队头进行。
- 时间复杂度为 O(1)。
优点:
- 适合按顺序处理数据的场景,如任务调度。
缺点:
- 无法随机访问元素。
代码示例:
#include <iostream>
#include <queue>
using namespace std;
int main() {
queue<int> q;
q.push(1);
q.push(2);
q.push(3);
return 0;
}
5.7 双端队列(Deque)
双端队列允许在两端进行插入和删除操作,是栈和队列的结合体。使用需要包含<deque>
头文件。
特点:
- 允许在两端进行插入和删除。
- 时间复杂度为 O(1)。
优点:
- 灵活的双向操作。
缺点:
- 空间占用较大,适合需要在两端频繁操作的场景。
代码示例:
#include <iostream>
#include <deque>
using namespace std;
int main() {
deque<int> q;
q.push_back(1);
q.push_back(2);
q.push_front(4);
return 0;
}
5.8 哈希表(Hash Table)
哈希表是一种通过键值对存储数据的数据结构(就是之前熟悉的Key-Value结构),支持快速查找、插入和删除操作。C++中的 unordered_map
是哈希表的实现。使用需要包含<unordered_map>
头文件。
特点:
- 使用哈希函数快速定位元素,时间复杂度为 O(1)。
- 不保证元素的顺序。
代码示例:
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
unordered_map<string, int> map;
map["test"] = 1;
cout << map["test"] << endl;
return 0;
}
5.9 映射(Map)
map
是一种有序的键值对容器,底层实现是红黑树。与unordered_map
不同,它保证键的顺序,查找、插入和删除的时间复杂度为O(log n)。使用需要包含<map>
头文件。
特点:
- 保证元素按键的顺序排列。
- 使用二叉搜索树实现。
优点:
- 元素有序,适合需要按顺序处理数据的场景。
缺点:
- 操作效率比
unordered_map
略低。
代码示例:
#include <iostream>
#include <map>
using namespace std;
int main() {
map<string, int> map;
map["test"] = 1;
cout << map["test"] << endl;
return 0;
}
5.10 集合(Set)
一种用于存储唯一元素的有序集合,底层同样使用红黑树实现。它保证元素不重复且有序。使用需要包含<set>
头文件。
特点:
- 保证元素的唯一性。
- 元素自动按升序排列。
- 时间复杂度为 O(log n)。
优点:
- 自动排序和唯一性保证。
缺点:
- 插入和删除的效率不如无序集合。
代码示例:
#include <iostream>
#include <set>
using namespace std;
int main() {
set<int> s;
s.insert(6);
s.insert(7);
cout << *s.begin() << endl;
return 0;
}
5.11 动态数组(Vector)
vector
是C++标准库提供的动态数组实现,可以动态扩展容量,支持随机访问。使用需要包含<vector>
头文件。
特点:
- 动态调整大小。
- 支持随机访问,时间复杂度为O(1)。
- 当容量不足时,动态扩展,时间复杂度为摊销O(1)。
优点:
- 支持随机访问,动态扩展。
缺点:
- 插入和删除中间元素的效率较低。
代码示例:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
for (int i = 0; i < v.size(); i++) {
cout << v[i] << endl;
}
return 0;
}
六、C++类相关知识
- 1、指定新建的类继承了一个已有的类的成员,这个已有的类称为基类,新建的类称为派生类。继承代表了is a关系。
- 2、当一个类派生自基类,该基类可以被继承为public、protected或private几种类型。几乎不使用protected或private继承,通常使用public继承。
- 3、继承的基本语法:
class Dog : public Animal{}
。 - 4、重载是指,在同一个作用域内,可以声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者顺序)必须不同。
- 5、多态(Polymorphism)按字面的意思就是多种形态。当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。C++多态允许使用基类指针或引用来调用子类的重写方法,从而使得同一接口可以表现不同的行为。
- 6、虚函数(Virtual Functions)
- 在基类中声明一个函数为虚函数,使用关键字virtual。
- 派生类可以重写(override)这个虚函数。
- 调用虚函数时,会根据对象的实际类型来决定调用哪个版本的函数。
- 给用户提供默认行为,允许子类重写。
- 7、动态绑定(Dynamic Binding)
- 也称为晚期绑定(Late Binding),在运行时确定函数调用的具体实现。
- 需要使用指向基类的指针或引用来调用虚函数,编译器在运行时根据对象的实际类型来决定调用哪个函数。
- 8、纯虚函数(Pure Virtual Functions)
- 一个包含纯虚函数的类被称为抽象类(Abstract Class),它不能被直接实例化。
- 纯虚函数没有函数体,声明时使用 = 0。
- 它强制派生类提供具体的实现。
- 定义接口,强制子类实现具体行为。
- 9、多态的优势:
- 代码复用:通过基类指针或引用,可以操作不同类型的派生类对象,实现代码的复用。
- 扩展性:新增派生类时,不需要修改依赖于基类的代码,只需要确保新类正确重写了虚函数。
- 解耦:多态允许程序设计更加模块化,降低类之间的耦合度。 10、数据抽象是指,只向外界提供关键信息,并隐藏其后台的实现细节,即只表现必要的信息而不呈现细节。数据抽象是一种依赖于接口和实现分离的编程(设计)技术。 11、数据封装(Data Encapsulation)是面向对象编程(OOP)的一个基本概念,它通过将数据和操作数据的函数封装在一个类中来实现。这种封装确保了数据的私有性和完整性,实现数据隐藏,防止了外部代码对其直接访问和修改。 12、数据封装是一种把数据和操作数据的函数捆绑在一起的机制,数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
一个继承与多态的示例如下:
#include <iostream>
using namespace std;
// 基类 Animal
class Animal {
public:
// 虚函数 sound,为不同的动物发声提供接口
virtual void sound() const {
cout << "Animal makes a sound" << endl;
}
// 虚析构函数确保子类对象被正确析构
virtual ~Animal() {
cout << "Animal destroyed" << endl;
}
};
// 派生类 Dog,继承自 Animal
class Dog : public Animal {
public:
// 重写 sound 方法
void sound() const override {
cout << "Dog barks" << endl;
}
~Dog() {
cout << "Dog destroyed" << endl;
}
};
// 派生类 Cat,继承自 Animal
class Cat : public Animal {
public:
// 重写 sound 方法
void sound() const override {
cout << "Cat meows" << endl;
}
~Cat() {
cout << "Cat destroyed" << endl;
}
};
// 测试多态
int main() {
Animal *animalPtr; // 基类指针
// 创建 Dog 对象,并指向 Animal 指针
animalPtr = new Dog();
animalPtr->sound(); // 调用 Dog 的 sound 方法
delete animalPtr; // 释放内存,调用 Dog 和 Animal 的析构函数
// 创建 Cat 对象,并指向 Animal 指针
animalPtr = new Cat();
animalPtr->sound(); // 调用 Cat 的 sound 方法
delete animalPtr; // 释放内存,调用 Cat 和 Animal 的析构函数
return 0;
}
七、参考资料
- [1] https://www.runoob.com/cplusplus/cpp-intro.html
本文作者原创,未经许可不得转载,谢谢配合