提示:以下是本篇文章正文内容,下面案例可供参考
int a=8
等同于 int a{8}
int b{}
初始化为0
#include
int main(int argc, char const *argv[])
{
struct Demo
{
int a_;
int b_;
Demo(std::initializer_list<int> list)
{
std::cout << "Demo(std::initializer_list list)" << std::endl;
}
};
struct Demo1
{
int a_;
int b_;
Demo1(int a, int b)
{
a_ = a;
b_ = b;
std::cout << "Demo1(int a, int b)" << std::endl;
}
};
struct Demo2
{
int a_;
int b_;
Demo2(int a, int b)
{
a_ = a;
b_ = b;
std::cout << "Demo2(int a, int b)" << std::endl;
}
Demo2(std::initializer_list<int> list)
{
std::cout << "Demo2(std::initializer_list list)" << std::endl;
}
};
// 非聚合体首先考虑 initializer_list 构造函数
Demo d{1, 2}; //花括号里的数据的类型必须一致
Demo1 d1({1, 2}); //等同于d1(1,2) d1{1,2} 编译时未发现initializer_list形参 就把括号内的值一一匹配赋给 Demo2(int a, int b)中的a和b了
Demo2 d2{1, 2}; //编译器会自动生成一个匿名的initializer_list类型的对象 把这条语句转换为 Demo d2({1,2})
// Demo2 d2(1, 2);//Demo2(int a, int b) 园括号就是直接指明要调用括号内的就是参数
return 0;
/*输出 :
Demo(std::initializer_list list)
Demo1(int a, int b)
Demo2(std::initializer_list list)
需要注意的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。
并且,拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,其实只是引用而已,原始列表和副本共享元素。
和使用vector一样,我们也可以使用迭代器访问initializer_list里的元素
如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内:*/
}
与数组的关系
#include
using namespace std;
template<class T,size_t N>
T average(const T (&array)[N]) //因为初始化列表是常量 所以要用const 类型接收
{
T sum{};
for(size_t i{};i<N;++i)
sum+=array[i];
return sum/N ;
}
int main()
{
cout<<average({1.0,2.0,3.0,4.0,5.0})<<endl; //T=double N 为 5
return 0;
}
与new
int* a = new int { 123 };
double b = double { 12.12 };
int* arr = new int[3] { 1, 2, 3 };
推荐以后多使用{}
来初始化 更加 type-safe
auto
auto name = value;
int x = 0;
auto *p1 = &x; //p1 为 int *,auto 推导为 int
auto p2 = &x; //p2 为 int*,auto 推导为 int*
auto &r1 = x; //r1 为 int&,auto 推导为 int
auto r2 = r1; //r2 为 int,auto 推导为 int
//---------------------------------------------------------------
const int a = 0;
auto b = a; //a 为 const int, auto 被推导为 int(const 属性被抛弃)
auto &c = a; //a 为 const int, auto 被推导为 const int(const 属性被保留)c 为 const int&类型
//-------------------------------------------------------------------
int n = 10;
int && r2 = std::move(n);
int &r3 = n; //r3是一个int &类型
auto r4 = r3; //auto 推导为 int 抛弃了左值引用
auto r5 = r2; //auto 推导为 int 抛弃了右值引用
>.warning: use of 'auto' in parameter declaration only available with '-std=c++20' or '-fconcepts'
>. 我开玩笑的,其实能用 但 auto 和函数参数默认值不能用在同一个参数上(该函数参数默认性将失效)
decltype
在某些特殊情况下auto用起来非常不方便,甚至压根无法使用,所以 decltype 关键字也被引入到 C++11 中。
decltype基本使用语法:
decltype(exp) varname = value;
其中,varname 表示变量名,value 表示赋给变量的值,exp 表示一个表达式。
auto 要求变量必须初始化,而 decltype 不要求:
decltype(exp) varname;
exp 是一个普通的表达式,它可以是任意复杂的形式,但要保证 exp 的结果是有类型的,不能是 void;
如果 exp 是一个左值 或者被括号()
包围:decltype((exp))
,那么 返回类型就是 exp 的引用;
exp为函数调用表达式时需要带上括号和参数, 这仅仅是形式, 并不会真的去执行函数代码。
decltype(func(100))
返回的类型就和函数返回值的类型一致
不同于auto, decltype 会保留 cv 限定符 , 以及引用
和某些具体类型混合使用
int x = 1;
int &&u= 5;
decltype(u)& y=x; //引用折叠 y为 int& 类型
decltype(auto)
int a=5;
decltype(auto) b=(a); //b推导为 int& 类型
template
auto add(T t, U u) -> decltype(t + u)
{
return t + u;
}
>.效果同上 等于是上面的简写形式
template
decltype(auto) add(T t, U u)
{
return t + u;
}
template
auto add(T t, U u)
{
return t + u;
}
>.这意味着纯auto返回类型推断有时候会不必要的复制值 可以试试返回 const auto&
using 覆盖了 typedef 的全部功能
// 重定义unsigned int
typedef unsigned int uint_t;
using uint_t = unsigned int;
// 重定义std::map
typedef std::map<std::string, int> map_int_t;
using map_int_t = std::map<std::string, int>;
//定义函数类型
typedef void (*func_t)(int, int);
using func_t = void (*)(int, int);
和模板的结合使用
template <typename Val>
using str_map_t = map<string, Val>;
// ...
str_map_t<int> map1;
别名模板(alias template)
template <typename T>
using func_t = void (*)(T, T);
// 使用 func_t 模板
func_t<int> xx_2;
using 语法和 typedef 一样,并不会创造新的类型 只是原类型的别名
func_t 定义的 xx_2 并不是一个由类模板实例化后的类,而是 void(*)(int, int) 的别名。
从这里可以看出模板非常灵活好用
[捕获] (形参列表) mutable noexcept -> 后置返回类型 { 函数体 }
mutable noexcept
是可选的 只是表明如果需要用到应该写在哪儿[]{}
捕获 | 功能 |
---|---|
[] | 空方括号表示当前 lambda 匿名函数中不导入任何外部变量(全局变量除外); |
[=] | 只有一个 = 等号,表示以值传递的方式导入所有外部变量(全局变量除外); |
[&] | 只有一个 & 符号,表示以引用传递的方式导入所有外部变量; |
[val1,val2,...] | 表示以值传递的方式导入 val1、val2 等指定的外部变量,同时多个变量之间没有先后次序; |
[&val1,&val2,...] | 表示以引用传递的方式导入 val1、val2等指定的外部变量,多个变量之间没有前后次序; |
[val,&val2,...] | 以上 2 种方式还可以混合使用,变量之间没有前后次序。 |
[=,&val1,...] | 表示除 val1 以引用传递的方式导入外,其它外部变量都以值传递的方式导入。 |
[this] | 表示以值传递的方式导入当前的 this 指针,这样就可以无限制直接访问 this 的成员 |
>.(全局变量除外)
[=] mutable {…}
>.也说了是值传递,所以只能修改的是与外部变量同名的副本变量
>.注意:前面讲了auto和函数参数默认值不能用在同一个参数上
constexpr auto add = [] (auto a,auto b) {return a+b};
add
是一个值,那么它所属的类型到底是什么呢? 其实 lambda 表达式的结果是一个函数对象#include
#include
using namespace std;
int main()
{
//display 即为 lambda 匿名函数的函数名
auto display = [](int a,int b) -> void{cout << a << " " << b;};
//调用 lambda 函数
display(10,20);
int num[4] = {4, 2, 3, 1};
//对 a 数组中的元素进行排序
sort(num, num+4, [=](int x, int y) -> bool{ return x < y; } );
for(int n : num){
cout << n << " ";
}
return 0;
}
POD: Plain Old Data 简洁旧数据
POD 类型一般具有以下几种特征(包括 class、union 和 struct等)
C++11 允许联合体有静态成员,构造函数,析构函数,重载运算符…像类一样
如果联合体内有一个非 POD 的成员,那么这个联合体的默认构造函数将被编译器删除;其他的特殊成员函数,例如默认拷贝构造函数、拷贝赋值操作符以及析构函数等,也将被删除。
#include
using namespace std;
union U {
string s;
int n;
};
int main() {
U u; // 构造失败,因为 U 的构造函数被删除
return 0;
}
解决上面问题的一般需要用到 placement new
#include
using namespace std;
union U {
string s;
int n;
public:
U() { new(&s) string; }
~U() { s.~string(); }
};
int main() {
U u;
return 0;
}
构造时,采用 placement new 将 s 构造在其地址 &s 上,这里 placement new 的唯一作用只是调用了一下 string 类的构造函数。注意,在析构时还需要调用 string 类的析构函数。
placement new 是什么?
placement new 是 new 关键字的一种进阶用法,既可以在栈(stack)上生成对象,也可以在堆(heap)上生成对象。相对应地,我们把常见的 new 的用法称为 operator new,它只能在 heap 上生成对象。
- placement new 的语法格式如下:
new(address) ClassConstruct(…)
- address 表示已有内存的地址,该内存可以在栈上,也可以在堆上;ClassConstruct(…) 表示调用类的构造函数,如果构造函数没有参数,也可以省略括号。
- placement new 利用已经申请好的内存来生成对象,它不再为对象分配新的内存,而是将对象数据放在 address 指定的内存中。在本例中,placement new 使用的是 s 的内存空间。
for(T& 变量名: arr){循环体}
变量名表示是arr中每个元素的引用for(T 变量名: arr){循环体}
变量名表示是arr中每个元素的复制for (char ch : “HelloC++11-C++17”)
cout << ch;
前文 c++规定 介绍的引用称为 左值引用 (lvalue reference)
右值引用常用于 移动语义 和 完美转发
通常情况下,判断某个表达式是左值还是右值,最常用的有以下 2 种方法。
基本用法:
int && a = 10;
a是一个右值引用类型, 但它自己是一个左值,可对 a 取地址
右值引用不能用左值初始化
右值引用可以对右值进行修改
左值(lvalue): 左值 lvalue 是有标识符、可以取地址的表达式
纯右值(prvalue): 纯右值 prvalue 是没有标识符、不可以取地址的表达式
将亡值(xvalue): 表达式static_cast
的结果可以被右值引用绑定,且具备左值的运行时多态性质,对于这种既有左值的特征,同时又能初始化右值引用的情况, 在c++11中将其归为将亡值
或长这样:
int&& f(){
return 3;
}
int main()
{
f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.
return 0;
}
或长这样:
struct As
{
int i;
};
As&& f(){
return As();
}
int main()
{
f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.
return 0;
}
如果一个 prvalue 被绑定到一个引用上,它的生命周期则会延长到跟这个引用变量一样长。
>.这条生命期延长规则只对 prvalue 有效,而对 xvalue 无效。如果由于某种原因,prvalue 在绑定到引用以前已经变成了 xvalue,那生命期就不会延长。
T&& Doesn’t Always Mean “Rvalue Reference”
-------by Scott Meyers
int && var1 = someWidget; // here, “&&” means rvalue reference
//
auto&& var2 = var1; // here, “&&” does not mean rvalue reference
//
template
void f(std::vector&& param); // here, “&&” means rvalue reference
//
template
void f(T&& param); // here, “&&”does not mean rvalue reference
If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.
如果一个变量或者参数被声明为T&&
,其中T是被推导的类型,那这个变量或者参数就是一个universal reference。
template
void f(T&& param);
int a;
f(a); // 传入左值,那么上述的T&& 就是lvalue reference,(int &)也就是左值引用绑定到了左值
f(1); // 传入右值,那么上述的T&& 就是rvalue reference,(int &&)也就是右值引用绑定到了右值
T&&
的形式出现!即便是仅仅加一个const限定符都会使得“&&”不再被解释为universal reference:
template
void f(const T&& param); // “&&” means rvalue reference
int &&a=5;
auto&& b=a; // a是一个lvalue , auto&& 转化为 int & ; lvalue reference
auto&& c=std::move(a);// std::move(a)是一个rvalue , auto&& 转化为 int &&; rvalue reference
int x = 5;
decltype(auto) z = std::move(x); // z推导为 int&& , std::move(x)是一个rvalue reference 类型的 rvalue :
template
void f(T&& param);
int x;
f(10); // invoke f on rvalue
f(x); // invoke f on lvalue
void f(int&& param); // f instantiated from rvalue
void f(int& && param); // initial instantiation of f with lvalue
- 为了避免编译器对这个代码报错,C++11引入了一个叫做“引用折叠”(reference collapsing)的规则来处理某些像模板实例化这种情况下带来的"引用的引用"的问题。上面折叠为
void f(int& param);
T&&
中 T 最终变成了什么?#include
using namespace std;
template<typename T>
void f(T&& param){
decltype(auto) temp{0};
T T_type = temp;
}
int main() {
int x;
int &a=x;
f(10); // invoke f on rvalue//T_type 为 T 类型 , param 为 T&& 类型
f(x); // invoke f on lvalue //T_type 为 T& 类型 , param 折叠后为 T& 类型
f(a); // invoke f on lvalue//T_type 为 T& 类型 param 折叠后为 T& 类型
}
表面上总结:(前面说过模板参数 T 本身的类型推断结果和 auto 类似,不会保留原类型中的引用)
T&&
被初始化为 rvalue reference 那么T 最终为 TT&&
被初始化为 lvalue reference 那么T 最终为 T&#include
using namespace std;
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<<endl;
}
demo(const demo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
//添加移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
~demo(){
cout<<"class destruct!"<<endl;
}
private:
int *num;
};
demo get_demo(){
return demo();
}
int main(){
demo a = get_demo();
return 0;
}
-fno-elide-constructors
编译标志 执行结果为:
construct!
move construct!
class destruct!
move construct!
class destruct!
class destruct!
C++11只是提供了能构成移动语义的条件 &&, 具体要怎么实现你的移动语义是按你的项目来定的(是你自己的事)
#include
using namespace std;
class demo {
public:
demo(int num):num(num){}
int get_num()&&{
return this->num;
}
private:
int num;
};
int main() {
demo a(10);
//cout << a.get_num() << endl; // 错误
cout << move(a).get_num() << endl; // 正确
return 0;
}
#include
using namespace std;
//重载被调用函数,查看完美转发的效果
void otherdef(int & t) {
cout << "lvalue\n";
}
void otherdef(int && t) {
cout << "rvalue\n";
}
//实现完美转发的函数模板
template <typename T>
void function(T &&t) {
otherdef(t); //t永远是个左值
otherdef(std::forward<T>(t)); //本例的重点 从类型中保留值的rvalueness和lvaluess
cout<<"============================"<<endl;
}
int main()
{
function(5);
int x = 1;
function(x);
function(move(x));//move(x) 内部调用了 static_cast(x)
int && a = 5;
function(a);
return 0;
}
输出:
lvalue
rvalue
============================
lvalue
lvalue
============================
lvalue
rvalue
============================
lvalue
lvalue
============================
先看看 std::remove_reference 是如何工作的
#include
using namespace std;
template<typename _Tp>
struct my_remove_reference
{
typedef _Tp type;
my_remove_reference(){cout<<"_Tp"<<endl;}
};
// 特化版本
template<typename _Tp>
struct my_remove_reference<_Tp&>
{
typedef _Tp type;
my_remove_reference(){cout<<"_Tp&"<<endl;}
};
template<typename _Tp>
struct my_remove_reference<_Tp&&>
{
typedef _Tp type;
my_remove_reference(){cout<<"_Tp&&"<<endl;}
};
int main()
{
my_remove_reference<int&&>();
my_remove_reference<int>();
my_remove_reference<int&>();
return 0;
}
输出:
_Tp&&
_Tp
_Tp&
std::move
template<typename _Tp>
constexpr typename std::remove_reference<_Tp>::type&&
move(_Tp&& __t) noexcept
{ return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }
std::forward
传入左值时
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
传入右值时
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value,
"std::forward must not be used to convert an rvalue to an lvalue");
return static_cast<_Tp&&>(__t);
}
形参包含引用时, 需要显式指明 类型和值
传入实参后内部出现了很多万能引用和引用折叠, 总起来说forward做了什么?
逆推: 万能引用下 T 变成不含引用的情况, 那么传入的参数必定是个右值. 也就是说 forward 只在万能引用领域里才像那么回事
默认构造函数
class Box{
Box() = default;
};
禁止构造与赋值
有时候,你可能想要禁止编译器生成默认的copy构造函数或赋值运算符,可以通过 delete 关键字显式的进行说明:
class B{
public:
B(int){ };
B(double) = delete;
B& operator= (const B&) = delete;
B(const B&) = delete;
};
int main() {
B a(1);
B a1(3.12); //error
B a2(a); //error
a2 = a1; //error
}
不会禁止派生类的构造函数, 只是禁止它自己的
委托构造函数
class B{
public:
double length {1.0};
double width {1.0};
double height {1.0};
B(double a,double b,double c):length{a},width{b},height{c} {};
B(double side):B(side,side,side) {}; //委托构造函数
};
void f() noexcept { //…}
noexcept(false)
可以定义能够抛出异常的析构函数 但一般不会这么做#include
#include
using namespace std;
void dis_1(const int x){
//错误,x是运行时常量 但不属于一个常量表达式 所以叫只读属性的变量比较合适
array <int,x> myarr{1,2,3,4,5};
cout << myarr[1] << endl;
}
void dis_2(){
const int x = 5; //编译时常量 属于常量表达式
array <int,x> myarr{1,2,3,4,5};
cout << myarr[1] << endl;
}
int main()
{
dis_1(5);
dis_2();
}
- 这是因为,dis_1() 函数中的“const int x”只是想强调 x 是一个只读的变量,其本质仍为变量,无法用来初始化 array 容器;而 dis_2() 函数中的“const int x”,表明 x 是一个只读变量的同时,x 还是一个值为 5 的常量,所以可以用来初始化 array 容器。
- C++ 11标准中,为了解决 const 关键字的双重语义问题,保留了 const 表示“只读”的语义,而将“常量”的语义划分给了新添加的 constexpr 关键字。因此 C++11 标准中,建议将 const 和 constexpr 的功能区分开,即凡是表达“只读”语义的场景都使用 const,表达“常量”语义的场景都使用 constexpr。
- const 用于为修饰的变量添加“只读”属性;而 constexpr 关键字则用于指明其后是一个常量(或者常量表达式),编译器在编译程序时可以顺带将其结果计算出来,而无需等到程序运行阶段
- C++ 11 标准中,constexpr 可用于修饰普通变量、函数(包括模板函数)以及类的构造函数。
- 当常量表达式中包含浮点数时,考虑到程序编译和运行所在的系统环境可能不同,常量表达式在编译阶段和运行阶段计算出的结果精度很可能会受到影响,因此 C++11 标准规定,浮点常量表达式在编译阶段计算的精度要至少等于(或者高于)运行阶段计算出的精度。
- 注意,获得在编译阶段计算出结果的能力,并不代表 constexpr 修饰的表达式一定会在程序编译阶段被计算出结果,具体的计算时机还是编译器说了算。
- 使用细节请看 C++中的const, constexpr, consteval, constinit 汇总
if constexpr (布尔常量表达式) {
//…分支code
} else if constexpr (布尔常量表达式) {
//…分支code
} else {
//…分支code
}
基类名::产生名字遮蔽的函数
才能调用基类的, 默认是不构成重载的.using
就可以在派生类中重载该产生名字遮蔽的函数
#include
#include
using namespace std;
class A{
public:
void f(){cout<<"A f"<<endl;}
void f1(char){cout<<"A f1"<<endl;}
void f3(int){cout<<"A f3"<<endl;}
private:
};
class B:public A{
public:
using A::f;
using A::f1;
//原型同基类中的 f() 相同
void f(){ cout<<"B f"<<endl;}
//原型同基类中的 f1(char) 不同 构成重载
void f1(){
f();
cout<<"B f1"<<endl;
}
private:
};
int main()
{
A a;
B b;
b.f3(5); //不产生名字遮蔽的函数
b.f1('a'); //调用基类中的 f1(char)
cout<<"----------"<<endl;
b.f1(); //调用派生类中的 f1()
}
void f() override {…}
virtual void f() final {…}
enum egg {Small,Medium,Large,Jumbo}
enum t_shirt {Small,Medium,Large,Jumbo}
enum class egg {Small,Medium,Large,Jumbo}
enum class t_shirt {Small,Medium,Large,Jumbo}
egg choice = egg::Large
t_shirt Floyd = t_shirt::Large
学而不思则罔,思而不学则殆