C++98中可以使用 {}
对数组和结构体进行初始化。
struct Triangle
{
int _a;
int _b;
int _c;
};
int main()
{
//用{}对数组和结构体进行初始化
int arr1[] = { 2, 4, 6, 8, 7 };
int arr2[10] = { 3, 4, 5 };
Triangle t = { 3, 4, 5 };
return 0;
}
列表初始化是C++11引入的初始化语法,通花括号 {}
对所有对象。
内置类型初始化:
//直接列表初始化,可以省略掉 =
int x = { 10 };
int y{ 5 };
double d{3.14}
自定义类型初始化: 对于自定义类型的初始化,本质上是类型转化,先构造临时对象,再用临时对象拷贝构造,编译器会直接优化直接构造。
struct Triangle
{
int _a;
int _b;
int _c;
};
Triangle t1 = { 3, 4, 5 };
Triangle t2{ 6, 8, 10 };
//引用临时对象需要加const
const Triangle& t3 = { 6, 6, 6 };
列表初始化特点:
std::initializer_list
是C++标准库中的一个类模板,用于表示一组以花括号 {}
括起来的初始值序列。
主要用于函数参数的接收,允许代码编写更灵活、简洁的代码。
特点:
std::initializer_list
是一个轻量级的不可修改的对象,用于以数组形式存储初始化值。.begin()
和 .end()
。限制:
std::initializer_list
的元素是不可修改的,只能读取。STL
容器都增加了一个 initializer_list
的构造,目的是让 vector
、 list
等容器支持多参数构造。
不仅如此,容器的赋值也支持 initializer_list
的版本。
vector (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type());
list (initializer_list<value_type> il, const allocator_type& alloc =
allocator_type());
map (initializer_list<value_type> il,const key_compare& comp =
key_compare(),const allocator_type& alloc = allocator_type());
语法示例:
//调用了initializer_list版本的构造
vector<int> v1({ 1,2,3,4 });
//列表初始化,构造临时对象,再拷贝构造,但编译器会直接优化成构造(=可以省略)
vector<int> v2 = { 1,2,3,4 };
const vector<int>& v3 = { 1,2,3,4 };
//pair的{}的初始化和map的initializer_list构造结合
map<string, string> dict ( { {"apples", "苹果"}, {"index", "下标"} } );
//initializer_list版本的赋值重载
v1 = { 4,5,6,7 };
std::initializer_list
和列表初始化的区别:
特性 | std::initializer_list | 列表初始化 |
---|---|---|
引入版本 | C++11 | C++11 |
目的 | 用于函数接收初始值列表 | 统一初始化语法,增强灵活性 |
使用场景 | 函数参数 | 任意对象的初始化 |
实现机制 | 内部通过临时数组存储 | 直接调用构造函数 |
修改性 | 不可修改 | 支持修改 |
可变参数模板是C++11引入的一种强大的模板功能,允许模板**接受可变数量的模板参数,它为开发泛型代码提供了很大的灵活性,特别是在处理不同数量和类型的参数时。
一个可变参数模板用 ...
表示可变参数,基本语法如下:
template<typename... Args>
void functionName(Args... args) {
// 函数体
}
Args...
是一个模板参数包,表示零个或者多个模板参数,其原理与模板类似,本质还是去实例化对应类型和不同参数个数的多个函数。args...
是一个函数参数包,表示零个或者多个模板参数,可以用sizeof...
运算符去计算参数包中参数的个数,也可以使用左值引用和右值引用,与普通模板一样。语法示例: 计算函数参数包的个数。
template<class ...Args>
void CountArgs(Args&&...args) //右值引用
{
cout << sizeof...(args) << endl;
}
int main()
{
CountArgs(); //0个
CountArgs(1); //1个
CountArgs(1, string("ABCD")); //2个
CountArgs(1, string("ABCD"), vector<int>(10)); //3个
return 0;
}
参数包需要展开才能使用,展开一个参数包就是将其分解成单个元素,需要在参数包的右边放置 ...
来完成触发。
有几种典型的方式去展开参数包:
1. 递归展开
void ShowList()
{
cout << endl;
cout << "End of recursion" << endl;
}
template<class T, class ...Args>
void ShowList(T x, Args...args)
{
cout << x << " ";
//递归调用
ShowList(args...);
}
template<class ...Args>
void PrintArgs(Args...args)
{
//展开函数参数包
ShowList(args...);
}
int main()
{
PrintArgs(1, 2, 5, 'A', "hello");
return 0;
}
2. 使用折叠表达式(C++17)
template<class ...Args>
void PrintArgs(Args...args)
{
(..., (cout << args << " ")); //左折叠
cout << endl;
}
int main() {
PrintArgs(1, 2.5, "Hello", 'A');
return 0;
}
在C++11后, C++中的标准容器(如 vector
、deque
、map
等)新增了 emplace
系列的接口来高效插入元素,这个系列的接口均为可变参数模板,可以通过原地构造的方式,直接在容器的内存构造元素(in-place construction),避免不必要的拷贝或移动操作。
1.emplace
系列接口的优势:
push_back
或 insert
相比,emplace
系列允许直接构造元素在容器的目标位置,避免了额外的拷贝或移动。2.emplace
系列接口实现直接构造元素在容器的目标位置:
emplace
系列接口是通过完美转发来实现直接构造的。
emplace
接口接受可变参数(Args&&... args)
,并使用 std::forward
将这些参数转发到目标类型的构造函数。emplace
能够根据传入参数的具体类型(左值或右值)正确调用匹配的构造函数。3.相较于传统方法,emplace
系列接口具体高效的地方:
push_back
或 insert
)与emplace
系列的效率是一样的
push_back
或 insert
)在插入的对象不存在时,需要调用目标对象的构造函数创建临时对象,然后拷贝/移动到容器中。
emplace
系列接口可以接收不存在对象的构造函数的参数**,直接在容器的内存中调用目标对象的构造函数,无需创建临时对象,避免了拷贝或移动操作。
具体例子:
//用于存放pair的数组
vector<pair<string, int>> v;
//传统方法
v.push_back(make_pair("hello", 1));
v.push_back({ "world", 2 });
//emplace系列
v.emplace_back("happy", 3);
代码讲解:
push_back
:无论是使用 make_pair
还是 {}
,本质上都是构造一个 pair
的临时对象,然乎拷贝/移动到容器中。emplace_back
:直接将构造临时对象 piar
的参数传入,在函数内部,通过参数包的层层传入,最终在插入的目标位置调用 pair
的构造函数构造出 pair
,从而避免了不必要的拷贝/移动操作。emplace
系列与 push_back
和 insert
的对比 :接口 | 特点 | 适用场景 |
---|---|---|
push_back | 先构造临时对象,再拷贝或移动到容器中 | 简单场景,已有临时对象 |
emplace_back | 直接在容器尾部构造对象,避免拷贝/移动 | 高效插入对象到尾部 |
insert | 先构造临时对象,再插入到指定位置 | 在指定位置插入现有对象 |
emplace | 直接在指定位置构造对象,避免拷贝/移动 | 在指定位置高效插入新对象 |
总结来说,
emplace
系列接口为现代C++提供了更高效、更灵活的容器操作方式,尤其适合复杂对象的构造与插入。
C++11之后,STL新增了 array
、forward_list
、unordered_map
、unordered_set
、tuple
等容器,它们扩展了标准库容器的功能和使用场景。
以下介绍几个容器:
1. std::array
int main() {
array<int, 4> arr = {1, 2, 3, 4};
for (int n : arr) {
cout << n << " ";
}
return 0;
}
2.std::forward_list
int main() {
forward_list<int> flist = {1, 2, 3};
flist.push_front(0); // 添加到头部
for (int n : flist) {
cout << n << " ";
}
return 0;
}
3. std::tuple
int main() {
tuple<int, string, double> t(1, "Hello", 3.14);
cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t);
return 0;
}
这些新增容器为开发者提供了更灵活、高效的工具来处理各种数据结构,同时也更贴合现代 C++ 的设计理念(如
RAII
、泛型编程
等)。
C++11引入了右值引用(&&
),从而实现了移动语义。移动构造函数和移动赋值函数可以实现资源的转移,而非拷贝。
1.移动构造(移动赋值)函数生成条件
当没有实现移动过构造函数并且没有实现析构函数、拷贝构造函数和赋值重载函数,编译器就会生成一个默认移动构造函数或者移动赋值函数。
2.解析生成的移动构造(移动赋值)函数
内置类型成员
进行浅拷贝
。自定义类型的成员
,需要看这个成员有没有实现移动构造函数(移动赋值函数), 示例:
class MyClass
{
public:
MyClass(vector<int> v = vector<int>())
:data(v)
{}
//移动构造
MyClass(MyClass&& mycl) noexcept
:data(move(mycl.data))
{
cout << "Move Constructor Called" << endl;
}
//移动赋值
MyClass& operator=(MyClass&& mycl) noexcept
{
if (this != &mycl)
{
data = move(mycl.data);
cout << "Move Assignment Operator Called" << endl;
}
return *this;
}
void show()
{
for (auto e : data)
{
cout << e << " ";
}
cout << endl;
}
private:
vector<int> data;
};
int main()
{
//转移匿名对象的资源
MyClass mycl1 = move(MyClass({ 1,2,3,4 }));
mycl1.show();
//转移mycl2的资源到mycl3
MyClass mycl2({ 1,2,3,4 });
MyClass mycl3;
mycl3 = move(mycl2);
mycl2.show();
mycl3.show();
}
3.拷贝与移动的区别
特性 | 拷贝构造/赋值 | 移动构造/赋值 |
---|---|---|
操作方式 | 复制资源(通常深拷贝) | 转移资源的所有权 |
参数类型 | const T&(左值引用) | T&&(右值引用) |
性能 | 较慢(需要额外资源分配) | 较快(资源直接转移) |
&&
) 和 std::move
来实现高效的资源管理,是现代 C++ 中优化性能的重要工具。default
和 delete
1.默认函数(=default
)
允许显式声明默认构造函数、拷贝构造函数、移动构造函数和赋值重载函数等默认成员函数,这样编译器就会强制生成默认成员函数。
class MyClass {
public:
MyClass() = default; // 使用默认构造函数
MyClass(const MyClass&) = default; // 使用默认拷贝构造函数
};
2.删除函数(=delete
)
可以显式禁用默认成员函数,这样编译器就不会生成。
class MyClass {
public:
MyClass() = default;
MyClass(const MyClass&) = delete; // 禁止拷贝构造
};
1.final
用于禁止类被继承或者重写。
class Base {
public:
virtual void func() final {} // 禁止进一步重写
};
class Derived : public Base {
// void func() override {} // 编译错误
};
2.override
用于检测函数是否完成重写,没有完成则报错。
class Base {
public:
virtual void func() {}
};
class Derived : public Base {
public:
void func() override { // 确保是重写基类函数
//...
}
};
Lambda表达式
是C++11中的一项重要特性,他提供了一种更为简洁和灵活的方式来定义匿名函数或内联函数。这些函数可以在需要函数对象的地方作为参数传递,通常用于算法或函数式编程风格。
[capture](parameters) -> return_type { function_body }
capture
):捕获外部变量,可以按值(=
)或者按引用(&
)捕获。parameters
):函数参数,可以为空。-> return_type
):指定返回值类型,一般省略。{ function_body }
):Lambda 表达式的主体,可以使用参数或者捕获而来的变量进行实际操作。lambda表达式
本质上是一个匿名函数对象,在使用层面上没有类型
可言,一般使用 auto
或者 模板参数定义的对象
去接受 lambda 对象
示例1:打印 hello world
int main()
{
auto hello = []()->void {
cout << "hello world" << endl;
};
hello();
return 0;
}
lambda表达式
无参数且无返回值,执行打印 hello world
的操作。示例2:add
函数
int main()
{
auto add = [](int x, int y) {
return x + y;
};
cout << add(2, 3) << endl;
}
lambda表达式
有参数且有返回值,执行将两数相加的操作。捕获列表的作用就是将外部的参数捕获,使得函数体可以使用外部的参数,捕获的方式一般有以下几种:
[=]
):将外部变量的值复制到 lambda
中,类似于函数的传值传参,修改 lambda 中的变量不会影响外部变量。[&]
):将外部变量的引用传递给 lambda,lambda 中修改的变量将反映到外部变量。[=, &x]
或 [&. x]
):按值捕获所有外部变量的值,但按引用捕获 x
或 按引用捕获所有外部变量的值,但按值捕获 x
。int x = 10, y = 20, z = 30;
//按值捕获x,y,但是捕获而来的变量默认是加了const修饰的,如果想要修改需要加mutable
//因为是按值捕获是一种拷贝,在lambda里修改了捕获而来的变量也不会传递到外部
auto sum1 = [x, y]() mutable { x = 10; return x + y; };
//按引用捕获x,y,对x,y的修改可以传递到外部
auto sum2 = [&x, &y]() { return x + y; };
//按值捕获所有外部变量x、y、z
auto sum3 = [=]() { return x + y + z; };
//按引用捕获所有外部变量x、y、z
auto sum4 = [&]() { return x + y + z; };
//按值捕获所有外部变量,但按引用捕获z
auto sum5 = [=, &z]() { return x + y + z; };
//按引用捕获所有外部变量,但按值捕获z
auto sum6 = [&, z]() { return x + y + z; };
Lambda表达式
在 C++ 中本质上是由编译器生成的类对象(类似于仿函数),这个类实现了 operator()
(函数调用运算符),因此它行为类似于函数对象。
工作原理:
Lambda表达式
都对应一个编译器自动生成的类,其类名按照一定编译规则生成,保证不同的 lambda表达式
生成的类名不同Lambda
的函数体会转化为 operator()
方法的实现。Lambda表达式
在使用时,会生成这个类的一个对象。operator()
,执行 Lambda 的函数体。
Lambda表达式
在现代 C++ 中是一个强大的工具,能够提高代码的灵活性和简洁性。它的应用场景广泛,从简单的算法自定义操作到复杂的回调和递归逻辑。理解其背后的原理(编译器生成匿名类)可以更好地掌握其用法和性能特性。
function
在C++中,function
是一个通用的函数包装器,它能够储存、复制和调用任何可调用目标,包括普通函数、Lambda表达式、函数对象以及成员函数。function
是C++11引入的一部分,位于 <functional>
头文件。
1.function
的定义
function
是一个类模板,语法如下:
template <class Ret, class... Args>
class function<Ret(Args...)>;
2.function
的功能
function
可以保存普通函数、Lambda 表达式、函数对象(仿函数)或指向成员函数的指针。若不含任何可调用对象,则为空,调用空的 function
会抛出 std::bad_function_call
异常function
都提供统一的接口调用。3.使用示例
int add(int x, int y)
{
return x + y;
}
struct divide
{
double operator()(double x, double y)
{
return x / y;
}
};
class MyClass
{
public:
int subtract(int x, int y) const
{
return x - y;
}
static void Print()
{
cout << "hello world" << endl;
}
};
int main()
{
//包装普通函数
function<int(int, int)> addfunc = add;
cout << addfunc(2, 3) << endl; //输出5
//包装lambda表达式
function<int(int, int)> mumultiplyfunc = [](int x, int y) {
return x * y;
};
cout << mumultiplyfunc(3, 4) << endl; //输出12
//包装仿函数
function<double(double, double)> dividefunc = divide();
cout << dividefunc(6, 3) << endl; //输出2
//包装成员函数,成员函数要指定类域并且加&才能取地址
//还要注意隐式this指针的问题,传地址或者对象都可以
function<int(MyClass*, int, int)> subtractfunc = &MyClass::subtract;
MyClass mycl;
cout << subtractfunc(&mycl, 7, 4) << endl; //输出3
//包装静态成员函数,静态成员函数没有this指针
function<void()> Printfunc = &MyClass::Print;
Printfunc(); //打印helloworld
return 0;
}
function
在一些场景中,能够大幅简化代码,使得代码的可读性更高。就比如解决下面这道算法题:
逆波兰表达式求值 - 力扣(LeetCode)
比较传统的写法:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(auto e : tokens)
{
if(e != "+" && e != "-" && e != "*" && e != "/") st.push(stoi(e));
else if(!st.empty())
{
int x = st.top();
st.pop();
int y = st.top();
st.pop();
switch(e[0])
{
case '+':
st.push(x + y); break;
case '-':
st.push(y - x); break;
case '*':
st.push(x * y); break;
case '/':
st.push(y / x); break;
}
}
}
int ret = st.top();
st.pop();
return ret;
}
};
使用 function
的写法:
class Solution {
public:
int evalRPN(vector<string>& tokens) {
//function作为map的映射可调⽤对象的类型
map<string, function<int(int, int)>> FuncMap = {
{"+", [](int x, int y){ return x + y; }},
{"-", [](int x, int y){ return x - y; }},
{"*", [](int x, int y){ return x * y; }},
{"/", [](int x, int y){ return x / y; }},
};
stack<int> st;
for(auto& str : tokens)
{
if(FuncMap.count(str))
{
int right = st.top();
st.pop();
int left = st.top();
st.pop();
int ret = FuncMap[str](left, right);
st.push(ret);
}
else
{
st.push(stoi(str));
}
}
return st.top();
}
};
function
是通过函数包装器:支持普通函数、Lambda、仿函数和成员函数。 适合需要动态函数存储的场景:例如回调或事件系统。 性能权衡:灵活性带来一定的性能开销,在高性能场景下可优先考虑模板或直接调用。
bind
std::bind
是C++标准库 functional
中的一个工具(函数模板),作用是将函数和参数绑定,生成一个新的可调用对象(函数对象),这个对象可以像普通函数一样调用。
语法
#include <functional>
auto new_func = std::bind(func, arg1, arg2, ...);
func
:被绑定的目标函数,可以是普通函数、成员函数或者函数对象。arg1, arg2, ...
:绑定的参数。未绑定的参数用占位符 placeholders::_1
,placeholders::_2
表示。占位符
placeholders::_1
,placeholders::_2
等占位符表示未绑定的参数,调用时需要提供对应的值。
placeholders::_1
表示第一个未绑定的参数。placeholders::_2
表示第二个未绑定的参数。使用示例
void print(int a, int b)
{
cout << "a: " << a << ",b:" << b << endl;
}
class MyClass {
public:
void display(int value) {
cout << "Value: " << value << endl;
}
};
struct Functor
{
void operator()(int x, int y)
{
cout << "x - y = " << x - y << endl;
}
};
int main()
{
//绑定普通函数
auto bound_func = bind(print, 10, placeholders::_1);
bound_func(20);
//绑定lambda表达式
auto add = bind([](int x, int y) { return x + y; }, 10, placeholders::_1);
cout << "add:"<< add(30) << endl;
//绑定成员函数
auto b_display = bind(&MyClass::display, MyClass(), placeholders::_1);
b_display(20);
//绑定仿函数
auto b_subtract = bind(Functor(), 100, placeholders::_1);
b_subtract(50);
return 0;
}
总结
bind
是一个强大的工具,适合绑定函数和参数,生成新的函数对象。bind
仍然适用,但大多数场景更推荐使用 Lambda 表达式。拜拜,下期再见😏
摸鱼ing😴✨🎞
扫码关注腾讯云开发者
领取腾讯云代金券
Copyright © 2013 - 2025 Tencent Cloud. All Rights Reserved. 腾讯云 版权所有
深圳市腾讯计算机系统有限公司 ICP备案/许可证号:粤B2-20090059 深公网安备号 44030502008569
腾讯云计算(北京)有限责任公司 京ICP证150476号 | 京ICP备11018762号 | 京公网安备号11010802020287
Copyright © 2013 - 2025 Tencent Cloud.
All Rights Reserved. 腾讯云 版权所有