文章目录
- {}列表初始化
- 1. 初始化内置类型变量
- 2. 初始化数组
- 3. 初始化标准容器
- 4. 初始化自定义类型
- 5. 构造函数初始化列表
- 6. 初始化列表(initializer_list)
- 7. 返回值初始化
- 8. 静态成员变量和全局变量的就地初始化
- 9. 防止类型收窄
- 总结
- decltype
- 右值引用
- 完美转发--由右值引用引发的问题
- delete default
- 逗号表达式
- 特性
- 示例
- 注意事项
- 可变参数模板
- 展开参数包
- emplace_back
- 仿函数
- function包装器/适配器/类模板
- bind函数模板/包装器/适配器
{}列表初始化
1. 初始化内置类型变量
- 直接初始化:
int a{10};
。明确表达初始化过程,避免与赋值混淆。 - 空列表初始化:如果
{}
内为空,则内置类型变量会被初始化为该类型的零值,如int b{};
会将b
初始化为0
。
2. 初始化数组
- C++98风格:在C++98中,数组初始化就需要使用
{}
,如int arr1[] = {1, 2, 3};
。 - C++11风格:C++11引入了更简洁的数组初始化方式,允许省略
=
,如int arr2[]{1, 2, 3};
。
3. 初始化标准容器
-
STL容器:如
vector
、map
等,可以使用{}
进行初始化,vector<int> v{1, 2, 3}; map<int, float> m{ {1, 1.0f}, {2, 2.0f} };
4. 初始化自定义类型
- 无模板类:对于自定义的无模板类,可以在构造函数中使用
{}
初始化成员变量,也可以在对象创建时直接使用{}
进行初始化,如struct A { A(int a = 0, int b = 0) : _a(a), _b(b) {} }; A a{1, 2};
。 - 模板类:模板类同样支持使用
{}
进行初始化,如template<class T> struct B { B(T c = 0, T d = 0) : _c(c), _d(d) {} }; B<int> b{3, 4};
。
5. 构造函数初始化列表
- 在类的构造函数中,可以使用
{}
来初始化成员变量,这有助于确保成员变量在构造函数体执行之前就被正确初始化。
6. 初始化列表(initializer_list)
- C++11引入了
initializer_list
,允许构造函数接收一个初始化列表作为参数,从而可以更方便地初始化容器或自定义类型。 - 示例:
class C { C(initializer_list<int> lt) : _arr(lt) {} private: vector<int> _arr; }; C c = {1, 2, 3, 4};
7. 返回值初始化
- 在函数返回时,也可以使用
{}
来初始化返回值,这有助于确保返回值的正确性和类型安全。
8. 静态成员变量和全局变量的就地初始化
- 在C++11中,除了构造函数初始化列表外,还允许使用
=
或{}
对静态成员变量和全局变量进行就地初始化。
9. 防止类型收窄
- 列表初始化可以防止类型收窄,即当尝试将一种类型的值初始化为另一种类型(且这种转换可能会导致精度损失或范围超出)时,编译器会报错。
总结
int a{10}; //明确表达初始化过程,避免与赋值混淆
int b{}; //空列表初始化 内置类型变量会被初始化为该类型的零值int arr1[] = {1, 2, 3};
int arr2[]{1, 2, 3};int* pa = new int[4]{ 1,2,3,4 };vector<int> v{1, 2, 3};
map<int, float> m{ {1, 1.0f}, {2, 2.0f} };struct A
{ A(int a = 0, int b = 0) : _a(a), _b(b) {}
};
A a{1, 2};template<class T>
struct B
{ B(T c = 0, T d = 0) : _c(c), _d(d) {}
};
B<int> b{3, 4};class C
{ C(initializer_list<int> lt) : _arr(lt) {}
private: vector<int> _arr;
};
C c = {1, 2, 3, 4};std::string createString()
{ return {"Hello, World!"}; // 使用 {} 初始化返回值
}static int staticVar = 42; // 使用 = 初始化
static std::string staticStringVar{"Static String"}; // 使用 {} 初始化 int globalVar = 100; // 使用 = 初始化
std::string globalStringVar{"Global String"}; // 使用 {} 初始化 char c1 = 256; // 可能不会报错,但值会被截断
// char c2{256}; // 这会编译错误,因为256超出了char的表示范围 // 尝试将浮点数转换为整数,同样使用 {} 可以防止类型收窄
float f = 3.14;
int i1 = f; // 隐式转换,f的值被截断为整数
// int i2{f}; // 这会编译错误,因为浮点数到整数的转换可能不安全
int i3 = static_cast<int>(f); // 显式转换,明确意图 // 安全的转换 int main()
{auto i = { 10,20,30 };cout << typeid(i).name() << endl; // class std::initializer list<int>return 0;
}int main()
{vector<int> v1 = { 1,2,3,4,5 }; // initializer_list构造函数list<int> l1 = { 10, 20, 30 }; // initializer_list构造函数Date d1 = { 2024, 1, 9 }; // 参数匹配Date构造==》构造+拷贝=直接构造;// 参数不匹配,{ 2024, 1, 9, 0}被识别成 initializer_list类型,参数不匹配报错//vector& operator= (initializer_list<value_type> il);v1 = {10, 20, 30}; // 这个时候调的是赋值重载,而不是 initializer_list 的构造return 0;
}
decltype
将变量的类型声明为表达式指定的类型。decltype 可以推导对象的类型,这个类型是可以用来模板实参,或者再定义对象。
template<class T1, class T2>
void F(T1 t1, T2 t2)
{decltype(t1 * t2) ret;cout << typeid(ret).name() << endl;
}int main()
{const int x = 1;double y = 2.2;decltype(x * y) ret; // ret的类型是doubledecltype(&x) p; // p的类型是int*// 类型以字符串形式获取到cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;vector<decltype(ret)> v; // 使用 ret 的类型去实例化 vectorF(1, 'a');return 0;
}
右值引用
右值引用的出现
左值引用解决了:传引用传参 传引用返回;
左值引用无法解决:局部变量传引用返回。那么传值返回就要经历【构造+拷贝构造】
移动构造
移动构造本质是将参数右值的资源窃取过来,占为已有,就不用做深拷贝了,所以它叫做移动构造,就是窃取别人的资源来构造自己。
https://blog.csdn.net/LHRan_ran_/article/details/133823688?fromshare=blogdetail&sharetype=blogdetail&sharerId=133823688&sharerefer=PC&sharesource=LHRan_ran_&sharefrom=from_link
// 移动构造: 传过来的s是右值 比如 返回值或 ("hello" + "world")
//ape::string ret2 = (s1 + '!'); 右值拷贝 ret2.string( (s1 + '!') );
string(string&& s) noexcept:_str(nullptr)
{cout << " string(string&& s) -- 右值移动拷贝" << endl;swap(s); //直接换过来s._str = nullptr;
}ape::string int_to_string(int value)
{//return str; 构造一个右值tmp + 调用移动构造 使得s获得返回值return move(str);// 这里只是为了调用我们的移动构造输出调试信息 // 实际应用中 str是一个出了函数就销毁的变量 这是错误代码 仅为演示!// ape::string s = int_to_string(1234);
}
c++库里移动构造的行为
总结
左值引用通过引用直接操作 省去中间额外的拷贝
右值引用通过识别是左值还是右值 如果是右值 直接移动拷贝
为什么要有右值引用
为了提高效率 即 右值直接移动拷贝而非深拷贝 因为拷贝时拷贝对象获取到右值的资源后 右值就不再具有价值
因为想提升效率所以搞了右值 但是移动拷贝转移资源时需要”左值“属性 于是这样被设计:s作为”右值引用“ 但是s的属性是左值!
完美转发–由右值引用引发的问题
Fun(forward(t));将t的属性置为原有属性 当原有属性为左值 你还是左值 是右值 你还是右值 当需要把原有属性是右值但是在该函数需要当成左值来用时 不加forward<T>(t)
即可
void Fun(int& x)
{ cout << "左值引用" << endl;
}
void Fun(const int& x)
{ cout << "const 左值引用" << endl;
}void Fun(int&& x)
{ cout << "右值引用" << endl;
}
void Fun(const int&& x)
{ cout << "const 右值引用" << endl;
}//万能引用/引用折叠:既可以引用左值 又可以引用右值
template<typename T>
//这里是一个右值引用 当这个函数被调用 t是参数的右值引用 但是此时 t的属性变为了左值
void PerfectForward(T&& t)//模板中的 && 不代表右值引用,而是万能引用,其既能接收左值又能接收右值
{//Fun(forward<T>(t));Fun(t);
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(move(b)); // const 右值return 0;
}
应用场景:如果不使用完美转发就只能手动把forward改成move()
右值引用的移动语义出来以后,对深拷贝的类的影响比较大,自定义类的深拷贝传值返回影响也较大,因为移动构造和移动赋值出来以后减少了它们的深拷贝;一些容器的插入接口也新增了右值版本,也减少了深拷贝。但是右值引用对于浅拷贝的类是没有意义的,因为它们没有资源可以转移
默认移动构造/赋值
delete default
Person(Person&& p) = delete; // 不让生成实现
Person(const Person& p) = default; // 强制编译器生成
C++11可以让你更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用 default 关键字显示指定移动构造生成。
逗号表达式
逗号表达式的求值顺序是从左到右。整个逗号表达式的值是最后一个表达式的值。
特性
- 求值顺序:逗号表达式中的表达式按照从左到右的顺序被求值。
- 结果:逗号表达式的结果是最后一个表达式的值,而不是一个列表或者集合。
- 副作用:逗号表达式常用于利用其副作用,比如对变量进行多次赋值或调用具有副作用的函数。
示例
#include <iostream> int main()
{ int a = 0, b = 0; // 逗号表达式 (a = 5, b = a + 1, a + b); // a 被赋值为 5,b 被赋值为 6,表达式的结果是 a + b = 11,但赋值给变量时仅使用最后一个表达式的值 // 注意:逗号表达式通常用于语句中,而不是直接赋值给变量 // 下面的赋值仅会接收逗号表达式的最后一个表达式的值 int result = (a = 2, b = a + 1, a + b); // result 被赋值为 3(即 a + b 的结果) std::cout << "a = " << a << ", b = " << b << ", result = " << result << std::endl; return 0;
}
注意事项
逗号操作符与逗号分隔符(在函数参数列表、初始化列表等中使用的)在语法上是不同的。逗号操作符用于创建逗号表达式,而逗号分隔符用于分隔列表中的元素。
可变参数模板
template <class ...Args>
void ShowList(Args... args)
{cout << sizeof...(args) << endl;
}
int main()
{ShowList(1, 2, 3, 4, 5);ShowList(1, "abcde", 3.33);return 0;
}
展开参数包
编译时的递归推演
// 递归终止函数
void _ShowList()
{cout << endl;
}// 展开函数
template <class T, class ...Args>
void _ShowList(const T& val, Args... args)
{cout << val << " ";_ShowList(args...);
}template <class ...Args>
void ShowList(Args... args)
{_ShowList(args...);
}int main()
{ShowList(1, 2, 3, 4, 5);ShowList(1, "abcde", 3.33);return 0;
}
逗号表达式
template <class T>
void PrintArg(T t)
{cout << t << " ";
}template <class ...Args>
void ShowList(Args... args)
{// int arr[] = { PrintArg(args)... };(PrintArg(args), ...);cout << endl;
}int main()
{ShowList(1, 2, 3, 4, 5);ShowList(1, "abcde", 3.33);return 0;
}
emplace_back
int main()
{list<string> lt;lt.push_back("abc"); // const char* 构造string + string移动构造nodelt.emplace_back("abc"); // const char*直接移动构造node(完美转发)// 同样的 多参数也是如此list<pair<string,int>> lt;lt.push_back(make_pair("abc",1));lt.emplace_back("abc",1);return 0;
}
修改list模拟实现类
template <class... Args>
list_node(Args&&... args)
: _data(args...)
, _next(nullptr)
, _prev(nullptr)
{}template <class... Args>
void emplace_back(Args&&... args)
{Node* newnode = new Node(args...);// 链接节点
}
仿函数
function的构造函数接收Functor()的临时对象将其封装为一个可调用对象
class TD
{
public:void operator()(){cout << "Thread3" << endl;}
};int main()
{function<void()> f = TD();f();TD td;f = td;f();// 线程函数为仿函数thread t1((TD())); // ( TD() ) 必须这样写 原因不明 不重要thread t2(td);t1.join();t2.join();cout << "Main thread!" << endl;return 0;
}
function包装器/适配器/类模板
https://blog.csdn.net/LHRan_ran_/article/details/133930224?fromshare=blogdetail&sharetype=blogdetail&sharerId=133930224&sharerefer=PC&sharesource=LHRan_ran_&sharefrom=from_link
// 类模板原型如下
template <class Ret, class... Args>
class function<Ret(Args...)>;
模板参数说明:
Ret: 被调用函数的返回类型
Args…:被调用函数的形参
通过给这些可调用对象加上一层包装器,使得函数模板只实例化一份
function的构造函数接收Functor()的临时对象将其封装为一个可调用对象
包装器的应用
class Solution
{typedef long long Long;
public:int evalRPN(vector<string>& tokens){stack<Long> st;map< string, function<Long(Long, Long)> > m ={{"+", [](Long a, Long b) { return a + b; }},{"-", [](Long a, Long b) { return a - b; }},{"*", [](Long a, Long b) { return a * b; }},{"/", [](Long a, Long b) { return a / b; }}};for (auto& e : tokens){if (m.count(e)){Long right = st.top();st.pop();Long left = st.top();st.pop();st.push(m[e](left, right));}elsest.push(stoll(e));}return st.top();}
};
bind函数模板/包装器/适配器
- 接收一个可调用对象(callable object),生成一个新的可调用对象来“适应”原对象的参数列表
调整参数顺序
int Sub(int a, int b)
{return a - b;
}int main()
{function<int(int, int)> f1 = Sub;cout << f1(10, 5) << endl;// 调整参数顺序function<int(int, int)> f2 = bind(Sub, placeholders::_2, placeholders::_1);cout << f2(10, 5) << endl;// 10传给b 5传给areturn 0;
}
调整参数个数
int main()
{function<int(int, int)> f1 = Sub;cout << f1(10, 5) << endl;// 调整参数个数,有些参数可以使用 bind 时固定function<int(int)> f3 = bind(Sub, 20, placeholders::_1);cout << f3(5) << endl;//20给a 5给breturn 0;
}
调整参数个数的应用
class Plus
{
public:static int plusi(int a, int b){return a + b;}double plusd(double a, double b){return a + b;}
};
int main()
{// 静态成员函数名会自动退化为指向该函数的指针 // 写成&Plus::plusi也对 没必要function<int(int, int)> f1 = Plus::plusi;cout << f1(1, 2) << endl;// 普通成员函数 必须加&function<double(Plus*, double, double)> f2 = &Plus::plusd;Plus ps;cout << f2(&ps, 1.1, 2.2) << endl;// 这里传 Plus 编译器会进行特殊处理,实际上是 Plus*function<double(Plus, double, double)> f3 = &Plus::plusd;cout << f3(Plus(), 1.11, 2.22) << endl;// 通过提前bind简化调用的编写并显得统一function<double(double, double)> f4 = bind(&Plus::plusd, Plus(), placeholders::_1, placeholders::_2);cout << f4(1.11, 2.22) << endl;return 0;
}
应用