第001讲 类型推导(auto/decltype)
auto关键字允许编译器根据初始化表达式的类型自动推导出变量的类型。这使得代码更加简洁和易于维护,尤其是在处理复杂类型时。
注意事项:
- auto不能用于没有初始化的变量。
- auto在某些情况下会推导出意想不到的类型,特别是在涉及引用和指针时。
decltype关键字用于获取表达式的类型而不计算表达式的值。它非常适合用于需要知道某个表达式类型的场合,尤其是模板编程中。
注意事项:
- decltype可以用于任意表达式,包括函数和复杂的类型表达式。
- 使用decltype时,如果表达式是一个左值,则结果将是引用类型;如果是右值,结果将是值类型。
语法:
auto 变量名称 = 值;
decltype(表达式) 变量名称[=值]
示例1:
#include <iostream>
#include <vector>int main() {auto x = 5; // x 是 intauto y = 3.14; // y 是 doubleauto z = "Hello"; // z 是 const char*std::vector<int> vec = {1, 2, 3, 4, 5};auto it = vec.begin(); // it 是 std::vector<int>::iteratorstd::cout << "x: " << x << ", y: " << y << ", z: " << z << std::endl;return 0;
}
示例2:
#include <iostream>int main() {int a = 5;decltype(a) b = 10; // b 是 int,类型与 a 相同decltype((a)) c = a; // c 是 int&,因为 (a) 是一个左值std::cout << "b: " << b << ", c: " << c << std::endl;return 0;
}
第002讲 序列for循环语句
C++语言当中可以遍历数组、容器、string以及由begin/end函数定义的序列。C++11引入了基于范围的for循环(range-based for loop),该语法特别适用于操作容器中的元素。
基本的序列for循环语法如下:
for (声明 : 容器) {// 处理元素
}
其中,声明通常是一个变量,用于代表当前遍历的元素,容器是你希望遍历的集合(如数组、向量等)
注意事项
- 使用const引用可以避免不必要的对象拷贝,提高性能。
- 如果要修改容器中的元素,应使用非const引用,如int& x。
- 如果容器中的元素是复杂对象,使用const引用可以提高效率,避免复制
。
示例1:
#include <iostream>int main() {int arr[] = {1, 2, 3, 4, 5};for (int x : arr) {std::cout << x << " ";}return 0;
}
示例2:
#include <iostream>
#include <vector>int main() {std::vector<std::string> fruits = {"apple", "banana", "cherry"};for (const std::string& fruit : fruits) {std::cout << fruit << std::endl;}return 0;
}
第003讲 lamdba表达式
C++ 11中的lambda表达式用于定义并创建匿名的函数对象。我们如何声明
lambda表达式完整格式语法如下:
[capture](parameters) -> return_type {// function body
}
其中:
- capture:捕获列表,用于捕获外部变量。它可以是空的,也可以包含变量名(按值或引用捕获)
- parameters:参数列表,类似于函数的参数。
- return_type:返回类型,可以省略,编译器会自动推导。
- function body:函数体,包含执行的代码。
示例1:
#include <iostream>
#include <vector>
#include <algorithm>int main() {std::vector<int> numbers = {1, 2, 3, 4, 5};// 使用lambda表达式打印每个元素。std::for_each 是 C++ 标准库中的一个算法,用于对指定范围内的每个元素执行给定的操作。std::for_each(numbers.begin(), numbers.end(), [](int n) {std::cout << n << " ";});std::cout << std::endl;// 使用lambda表达式计算平方并存储到新容器中。std::transform:这是一个算法,用于将输入范围内的每个元素应用一个给定的操作(在这里是一个 Lambda 表达式),并将结果输出到指定的地方std::vector<int> squares;std::transform(numbers.begin(), numbers.end(), std::back_inserter(squares), [](int n) {return n * n;});// 打印平方结果for (int s : squares) {std::cout << s << " ";}return 0;
}
捕获列表可以用来捕获外部变量。常见的捕获方式有:
- 按值捕获:在方括号中直接列出变量名。
- 按引用捕获:在方括号中列出变量名前加上&符号。
- 混合捕获:可以同时使用值捕获和引用捕获。
示例2:
int x = 10, y = 20;
auto add = [x, &y]() { return x + y; }; // x按值捕获,y按引用捕获
lambda表达式使用场景:
- 排序和过滤:与标准库算法(如std::sort、std::remove_if等)结合使用。
- 回调函数:在异步编程或事件处理中的回调定义。
- 简化代码:减少定义普通函数的需求,使代码更简洁。
lambda表达式注意事项:
- 捕获外部变量时的生命周期:确保捕获的变量在lambda表达式的使用中仍然有效。
- 泛型编程:可以结合std::function使用,使lambda表达式具有更广泛的应用。
- 可读性和调试:虽然lambda表达式提高了代码的灵活性和简洁性,但过度使用可能会影响可读性,尤其是复杂的lambda表达式。
第004讲 构造函数:委托构造和继承构造
委托构造函数可以使用当前的类的其他构造函数来协助当前构造函数的初始化操作。
普通构造函数和委托构造函数区别:
- 它们两都是一个成员初始值列表与一函数体;
- 委托构造函数的成员初始值列表只有一个唯一的参数,就是构造函数。当被委托构造函数当中函数体有代码,那么会先执行完函数体的代码,才加回来到委托构造函数。
示例:
#include <iostream>
#include <string>class Person {
public:std::string name;int age;// 主构造函数Person(const std::string& name, int age) : name(name), age(age) {std::cout << "Person constructed with name and age.\n";}// 委托构造函数Person(const std::string& name) : Person(name, 0) { // 委托给主构造函数std::cout << "Person constructed with name only.\n";}
};int main() {Person p1("Alice", 30); // 调用主构造函数Person p2("Bob"); // 调用委托构造函数return 0;
}
在上面的示例中,Person(const std::string& name) 是一个委托构造函数,它委托调用了 Person(const std::string& name, int age) 构造函数来进行对象的初始化。
继承构造函数:
在c++语言当中的继承关系,只有虚函数可以被继承,而构造函数不可以是虚函数,所以构造函数不能被继承,但是我们可以通过某种手段,达到继承效果。
示例:
#include <iostream>class Base {
public:Base(int x) {std::cout << "Base constructed with value: " << x << "\n";}
};class Derived : public Base {
public:Derived(int x) : Base(x) { // 调用基类的构造函数std::cout << "Derived constructed.\n";}
};int main() {Derived d(10); // 输出基类和派生类的构造信息return 0;
}
在上述例子中,Derived 类的构造函数中使用了 Base 类的构造函数 Base(x),这样在创建 Derived 对象时,首先会调用基类的构造函数,然后再执行派生类的构造函数。
总结:
- 委托构造:允许一个构造函数调用另一个构造函数,以增强代码重用性和简洁性。适合于同一类中的构造函数之间的调用。
- 继承构造:在派生类构造过程中显式调用基类的构造函数,以确保基类的属性被正确初始化。适合于类之间的继承关系。
第005讲 容器(array/forward_list)
1. std::array
概述
std::array是一个固定大小的数组容器,它在C++11中引入。与C风格数组相比,std::array提供了更好的类型安全性和一些内置的功能。
特点:
- 固定大小:std::array的大小在编译时确定,不能动态改变。
- 类型安全:提供更好的类型检查,避免了在使用时发生错误。
- 支持STL算法:可以与标准模板库的算法相结合使用。
- 栈内存:通常分配在栈上,具有较高的性能。
示例:
#include <array>
#include <iostream>int main() {// 创建一个包含5个整数的std::arraystd::array<int, 5> arr = {1, 2, 3, 4, 5};// 访问元素for (size_t i = 0; i < arr.size(); ++i) {std::cout << arr[i] << " ";}std::cout << std::endl;// 修改元素arr[0] = 10;// 使用范围for循环for (const auto& element : arr) {std::cout << element << " ";}std::cout << std::endl;return 0;
}
2.std::forward_list
概述
std::forward_list是一个单向链表容器,它在C++11中引入。与std::list相比,std::forward_list只支持单向遍历,因此它的内存开销更小。
特点:
- 单向链表:节点只指向下一个节点,无法反向遍历。
- 动态大小:可以在运行时动态增加或减少元素。
- 内存效率:由于每个节点只包含一个指针,相比双向链表(std::list),它的内存开销更小。
- 不支持随机访问:无法通过下标直接访问元素,必须通过迭代器遍历。
示例:
#include <forward_list>
#include <iostream>int main() {// 创建一个std::forward_list并初始化std::forward_list<int> fl = {1, 2, 3, 4, 5};// 添加元素fl.push_front(0); // 在前端添加0fl.push_front(-1); // 在前端添加-1// 遍历和打印元素for (const auto& element : fl) {std::cout << element << " ";}std::cout << std::endl;// 删除元素fl.remove(3); // 删除值为3的元素// 再次遍历和打印for (const auto& element : fl) {std::cout << element << " ";}std::cout << std::endl;return 0;
}
总结:
- std::array适用于需要固定大小的数组场景,提供了栈上高效的元素存储,且具备良好的类型安全性。
- std::forward_list适用于需要频繁插入和删除操作的场景,尤其是在头部插入的情况下,它的内存开销较小,但不支持反向遍历和随机访问。
第006讲 垃圾回收机制
一、标准C++没有垃圾回收机制原因:系统开销、耗内存、替代方法、没有共同基类。
二、C/C++中经典垃圾回收算法:引用计数算法、标记-清除算法、节点拷贝算法。
在C++中,垃圾回收机制并不像在一些其他编程语言(例如Java或Python)中那样内置。C++主要依赖于程序员手动管理内存的分配和释放。这种方式使得C++在性能上优化得更好,但同时也要求程序员必须小心处理内存,以避免内存泄漏和悬挂指针等问题。
C++中内存管理主要通过以下方式实现:
1.栈内存(Stack Memory):
- 自动管理:在函数调用结束后,栈上的局部变量会自动释放。
- 不需要显式的内存释放。
2.堆内存(Heap Memory):
- 手动管理:使用 new 关键词分配内存,使用 delete 释放内存。
- 开发者需要确保每个 new 都配对一个delete,否则会造成内存泄漏。
垃圾回收的模拟
虽然C++没有内置的垃圾回收机制,但可以通过智能指针来实现类似的功能。智能指针是C++11引入的特性,能在一定程度上自动管理堆内存。
智能指针的类型:
- std::unique_ptr: 独占式智能指针,确保同一时间只有一个指针指向某个对象。
- std::shared_ptr:共享式智能指针,允许多个指针共享同一个对象的所有权,使用引用计数来管理内存。
- std::weak_ptr: 弱引用智能指针,配合 std::shared_ptr 使用,防止循环引用。
示例:
以下是一个使用 std::shared_ptr 的简单示例,演示了如何管理动态分配的内存。
#include <iostream>
#include <memory> // 包含智能指针的头文件class Resource {
public:Resource() {std::cout << "Resource acquired!" << std::endl;}~Resource() {std::cout << "Resource released!" << std::endl;}
};int main() {{std::shared_ptr<Resource> res1(new Resource()); // 创建一个 shared_ptr{std::shared_ptr<Resource> res2 = res1; // 共享所有权std::cout << "Shared ownership between res1 and res2" << std::endl;} // res2 超出作用域,引用计数减少std::cout << "Back to res1" << std::endl;} // res1 超出作用域,引用计数为0,资源被释放return 0;
}
总结:
虽然C++不提供内置的垃圾回收机制,但通过智能指针等工具,可以有效地管理内存并减少内存泄漏的风险。程序员需要理解内存管理的基本原理,并适当地选择和使用智能指针,以提高代码的安全性和可维护性。
第007讲 正则表达式基础
C++中的正则表达式是通过regex库来实现的,它提供了一组用于处理和匹配文本的工具。正则表达式是一种用于描述字符串模式的工具,可以非常强大地用于字符串搜索、替换和验证。
正则表达式基础
1.基本概念:
- 字符: 字符本身。
- 元字符: 有特殊意义的字符,用于构建模式。
- 字符类: 用方括号[]定义的字符集合,如[abc]表示匹配a、b或c。
- 量词: 指定字符或子表达式的重复次数,如*(零次或多次)、+(一次或多次)、?(零次或一次)。
2.常用元字符:
- .: 匹配除换行符以外的任何单个字符。
- ^: 匹配字符串的开始。
- $: 匹配字符串的结束。
- : 转义字符,用于匹配特殊字符,如\d表示数字,\w表示字母数字字符,\s表示空白字符等。
3.常用的量词:
- *: 零次或多次
- +: 一次或多次。
- ?: 零次或一次。
- {n}: 精确匹配n次。
- {n,}: 至少匹配n次。
- {n,m}: 匹配n到m次。
C++中的正则表达式使用
1. 包含头文件
#include <iostream>
#include <regex>
#include <string>
2. 创建正则表达式对象
你可以通过std::regex类来创建一个正则表达式对象。
std::regex pattern("正则表达式");
3. 匹配字符串
可以使用std::regex_match、std::regex_search和std::regex_replace等函数进行匹配、搜索和替换。
- 匹配整个字符串:
std::string str = "测试字符串";
std::regex pattern("测试字符串");if (std::regex_match(str, pattern)) {std::cout << "匹配成功!" << std::endl;
} else {std::cout << "匹配失败!" << std::endl;
}
- 搜索子串:
std::string str = "这是一个测试字符串";
std::regex pattern("测试");if (std::regex_search(str, pattern)) {std::cout << "找到了匹配的子串!" << std::endl;
} else {std::cout << "没有找到匹配的子串!" << std::endl;
}
- 替换字符串:
std::string str = "这是一个测试字符串";
std::regex pattern("测试");
std::string result = std::regex_replace(str, pattern, "替代");std::cout << "替换后的字符串: " << result << std::endl;
第 008 讲 智能指针(shared_ptr/unique_ptr/weak_ptr)
智能指针需包含头文件memory
1. std::unique_ptr
std::unique_ptr是一个独占所有权的智能指针,意味着它所指向的对象只能被一个unique_ptr实例所拥有。当unique_ptr被销毁时,它所指向的对象也会被自动释放。
特点:
- 只能有一个unique_ptr指向某个对象。
- 不能被复制,但可以通过std::move进行移动。
- 轻量级,开销小。
示例:
#include <iostream>
#include <memory>using namespace std;int main()
{std::unique_ptr<int> p1(new int(24));cout << "*p1=" <<* p1 << endl << endl;std::unique_ptr<int> p2 = std::move(p1);cout << "*p2=" << *p2 << endl << endl;p2.reset(); // 显式释放内存p1.reset(); std::unique_ptr<int>p3(new int(250));p3.reset(new int(666)); // 绑定动态对象cout << "*p3=" << *p3 << endl << endl;p3 = nullptr; // 显式销毁指向对象,同时智能指针变为空,p3.reset()std::unique_ptr<int> p4(new int(999));int* p = p4.release(); // 只是释放控制权,不会释放内存cout << "*p=" << *p << endl << endl;cout << "*p4=" <<*p4 << endl;delete p; // 释放区资源数据return 0;
}
运行结果:
shared_ptr 智能指针
std::shared_ptr是一个共享所有权的智能指针,意味着多个shared_ptr可以指向同一个对象,并且使用引用计数来管理对象的生命周期。当最后一个指向该对象的shared_ptr被销毁时,对象才会被释放。
特点:
- 可以有多个shared_ptr指向同一个对象。
- 通过引用计数管理对象的生命周期。
- 线程安全的引用计数。
示例:
#include <iostream>#include <memory>using namespace std;int main(){shared_ptr<int> p1(new int(456));shared_ptr<int> p2 = p1;cout << "p2=" << p2.use_count() << endl << endl;cout << "*p1=" <<*p1<< endl;cout << "*p2=" << *p2 << endl;cout << "p2=" << p2.use_count() << endl << endl;p1.reset();cout << "p2=" << p2.use_count() << endl << endl;return 0;}
运行结果:
weak_ptr 智能指针:
std::weak_ptr是一个弱引用智能指针,它不控制对象的生命周期,也不增加引用计数。weak_ptr用于解决shared_ptr之间的循环引用问题。
特点:
- 不增加引用计数。
- 可以从shared_ptr构造,但不能直接使用。
- 通过lock()方法可以获取一个shared_ptr。
示例:
#include
#include
using namespace std;
int main()
{
shared_ptr p1(new int(300));
shared_ptr p2 = p1;
weak_ptr wp = p1;
cout << “count=” << wp.use_count() << endl << endl;
cout << “*p1=” << *p1 << endl;
cout << “*p2=” << *p2 << endl;
cout << endl;
return 0;
}
运行结果:
案例:
#include <iostream>
#include <memory>class MyClass {
public:MyClass() { std::cout << "MyClass constructor\n"; }~MyClass() { std::cout << "MyClass destructor\n"; }
};int main() {std::shared_ptr<MyClass> ptr1 = std::make_shared<MyClass>();std::weak_ptr<MyClass> weakPtr = ptr1; // weak_ptr不影响引用计数std::cout << "Use count: " << ptr1.use_count() << "\n"; // 1if (auto sharedPtr = weakPtr.lock()) { // 尝试获取shared_ptrstd::cout << "Object is still alive.\n";} else {std::cout << "Object has been destroyed.\n";}ptr1.reset(); // 释放ptr1,MyClass会被销毁if (auto sharedPtr = weakPtr.lock()) {std::cout << "Object is still alive.\n";} else {std::cout << "Object has been destroyed.\n"; // 这将被打印}return 0;
}