提示:以下是本篇文章正文内容,下面案例可供参考
int a=8
等同于 int a{8}
int b{}
初始化为0
#include <iostream>
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<int> 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<int> list)" << std::endl;
}
};
// 非聚合体首先考虑 initializer_list 构造函数
Demo d{1, 2}; //花括号里的数据的类型必须一致
Demo1 d1({1, 2}); //等同于d1(1,2) d1{1,2} 编译时未发现initializer_list<int>形参 就把括号内的值一一匹配赋给 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<int> list)
Demo1(int a, int b)
Demo2(std::initializer_list<int> list)
需要注意的是,initializer_list对象中的元素永远是常量值,我们无法改变initializer_list对象中元素的值。
并且,拷贝或赋值一个initializer_list对象不会拷贝列表中的元素,其实只是引用而已,原始列表和副本共享元素。
和使用vector一样,我们也可以使用迭代器访问initializer_list里的元素
如果想向initializer_list形参中传递一个值的序列,则必须把序列放在一对花括号内:*/
}
与数组的关系
#include <iostream>
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 <typename T, typename U>
auto add(T t, U u) -> decltype(t + u)
{
return t + u;
}
>.效果同上 等于是上面的简写形式
template <typename T, typename U>
decltype(auto) add(T t, U u)
{
return t + u;
}
template <typename T, typename U>
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) 的别名。
从这里可以看出模板非常灵活好用
在c++17之前,类模板的参数必须显式指定,而c++17中类模板和函数模板一样,可以从初始化构造函数参数中推断模板参数类型
在c++17之后, 如果类模板所有参数能全部推断出来(包含有默认模板参数的情况) (形参不含引用的情况)则生成对象时不需要指定尖括号<>
std::vector v(8,2)
//自动推断为vector<int>
类型
在c++17中 允许auto或decltype(auto)用于模板的非类型参数推断
template<auto N>
class A {
…
};
A<42> s1; // OK: type of N in A is int
A<‘a’> s2; // OK: type of N in A is char
不能从函数形参的默认值处(包括构造函数) 推断
#include <iostream>
using namespace std;
template <typename T, typename U>
void func(T val1 = 0, U val2 = 0)
{
//...
}
int main()
{
func(5,6); //ok
func(); //编译报错
return 0;
}
模板参数的推断规则本质上和 auto 一样, 模板类型参数T
中是不保留原类型中的引用的,除非你显式指明:func<int&,int&&>
#include <iostream>
using namespace std;
template <class T=short,class Z=double,class U=char,auto N=5>
Z func(Z val)
{
T t{};
Z z{};
U u{};
decltype(N) n {}; //用decltype()推导右值N的类型
cout<<N<<endl;
return val;
}
int main()
{
//c++17之后允许传递给非类型参数 非全局作用域内此被static修饰的 局部变量的地址
static int temp{};
func(97); // T=short, Z=int, U=char, decltype(N)= int
func<char>(97); // T=char, Z=int, U=char, decltype(N)= int
func<double,long>(97); // T=double, Z=long, U=char, decltype(N)= int
func<double,long,short,&temp>(97); // T=double, Z=long, U=short, decltype(N)= int*
return 0;
//为模板参数指定默认值时可以是在参数列表的开头, 中间 或 结尾
}
>.指针变量必须用constexpr修饰才能传入 要么就用&取变量的地址直接传入 用auto修饰的非类型参数可以自动推断为该变量的指针类型
#include <iostream>
using namespace std;
//类模板
template <class Z>
class x
{
//....
};
//函数模板
template <class Z>
void func(Z z){
//...
}
//普通函数
void func1(char ch){ //
//...
}
//测试非类型参数用 函数模板
template <class Z, decltype(auto) N>
Z test(Z val)
{
decltype(N) n = N;
return val;
}
//传指针变量时必须用constexpr修饰为常量 表示后面出现pfunc时 可以自动转换为指向的函数的地址常量
constexpr auto pfunc =func<double>;//必须显示指明,不然不会有这个版本的函数,模板只是个"图纸"
auto& pfunc1 =func1; //普通函数的引用 传引用类型时可不用constexpr修饰
constexpr int temp = 5; //基本类型 使其成为编译时常量即可传入模板的非类型参数
auto & temp1 = temp; //基本类型的引用
constexpr auto pTemp = &temp; //基本类型的指针 必须用constexpr声明为常量指针 才能传入
int main()
{
static x<short> obj; //或者在全局范围声明就不用加static
static x<short>& obj1 = obj;
decltype(auto) testType = pfunc1;
//各个版本对非类型参数的要求略有不同 c++98要求必须将temp声明在全局,
//即使用static修饰也无济于事 直到c++17 (至少gcc下如此)
test<int,pTemp>(97); //pTemp为 const int*const 类型 N 为 const int* 类型
test<int,temp>(97); //temp为 const int 类型 N 为 int 类型 (把temp当做编译时常量所以没有const)
test<int,&obj>(97); //&obj为 obj 对象地址 N 为 x<short>* 类型
test<int,obj1>(97); //obj1为 x<short>& 类型 N 为 x<short>& 类型
test<int,func1>(97); //func1为 普通函数 N 为 void(*)(char) 类型
test<int,func<int>>(97); //func为 函数模板实例化后的函数 N 为 void(*)(int) 类型
test<int,pfunc1>(97); //pfunc1为 普通函数的引用 N 为 void(&)(char) 类型
test<int,pfunc>(97); //pfunc为 函数模板实列化后的地址常量 指针 N 为 void(*)(double) 类型
return 0;
//函数名会自动转换为地址常量 可以不用 & 取函数的地址
}
如果该模板体不需要用上模板参数则可以不写名字
template<typename,typename> //可以是一个/可以是多个
struct test
{
test(){cout<<"i do not need to use this/these type"<<endl;}
};
通常是用在基模板上
template<typename,typename>
struct test
{
test(){cout<<"i do not need to use this/these type"<<endl;}
};
// 特化版本
template<typename _Tp,class A>
struct test<_Tp,A&> //只要有一个参数不一样就能形成特化
{
typedef _Tp type;
test(){cout<<"Specialization"<<endl;}
};
无名非类型模板参数
template<auto,auto>
struct test
{
test(){cout<<"i do not need to use this/these non-type parameters"<<endl;}
};
// 特化版本
template<char a,int b>
struct test<a,b>
{
test(){cout<<"Specialization"<<endl;}
};
template<typename... T>
void vair_fun(T...args) {
//函数体
}
…
就表明T
是一个可变模板参数,它可以接收多种数据类型,又称模板参数包。args
参数的类型用T…
表示,表示 args 参数可以接收任意个参数,又称函数参数包。vair_fun();
可以这样调用,这表明 参数包可以是空包 相当于没有这个参数包#include <iostream>
using namespace std;
//模板函数递归的出口
void vir_fun() {
}
template <typename T, typename... args>
void vir_fun(T argc, args... argv)
{
cout << argc << endl;
//开始递归,将第一个参数外的 argv 参数包重新传递给 vir_fun
vir_fun(argv...);
}
int main()
{
vir_fun(1, "mytest.variadic_templates", 2.34);
return 0;
}
#include <iostream>
using namespace std;
template <typename T>
void dispaly(T t) {
cout << t << endl;
}
template <typename... args>
void vir_fun(args... argv)
{
//逗号表达式+初始化列表
int arr[] = { (dispaly(argv),0)... };
//{ (display(argv),0)... }会依次展开为
//{ (display(1),0), (display("mytest.variadic_templates"),0), (display(2.34),0) }
//逗号表达式总是返回最后一个值,所以arr数组存储的都是0值
//arr 数组纯粹是为了将参数包展开,没有发挥其它作用
}
int main()
{
vir_fun(1, "mytest.variadic_templates", 2.34);
return 0;
}
#include <iostream>
using namespace std;
template <typename T>
void dispaly(T t) {
cout << t << endl;
}
template<typename... Tail>
class demo
{
public:
demo(Tail... vtail) {
int arr[] = { (dispaly(vtail),0)... };
}
};
int main() {
demo t(1, 2.34, "mytest.variadic_templates");
return 0;
}
#include <iostream>
//形式类模板demo 只起一个形式作用 中转作用 可以直接写成声明形式
template<typename... Values> class demo;//类型必须要用参数包申明符... 不然特化1 会 error!
//特化1 模板特化/专门化, 因为模板参数包允许空包 继承式递归的出口
template<> class demo<> {};
//特化2 模板特化(含继承操作) 以继承的方式解包
template<typename Head, typename... Tail>
class demo<Head, Tail...>
: private demo<Tail...> //继承形式基类
{
public:
//demo<Tail...>(vtail...) 初始化形式基类 , 实际会跳转选择初始化 特化2
demo(Head v, Tail... vtail) : m_head(v), demo<Tail...>(vtail...) {
dis_head();
}
void dis_head() { std::cout << m_head << std::endl; }
protected:
Head m_head;
};
int main() {
//必须在<>内指明类型 别问 问就是 error
demo<int, float, std::string> t(1, 2.34, "mytest.variadic_templates");
return 0;
}
/*输出:
mytest.variadic_templates
2.34
1
*/
[捕获] (形参列表) 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 <iostream>
#include <algorithm>
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 <string>
using namespace std;
union U {
string s;
int n;
};
int main() {
U u; // 构造失败,因为 U 的构造函数被删除
return 0;
}
解决上面问题的一般需要用到 placement new
#include <string>
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<int&&> (value)
的结果可以被右值引用绑定,且具备左值的运行时多态性质,对于这种既有左值的特征,同时又能初始化右值引用的情况, 在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<typename T>
void f(std::vector&& param); // here, “&&” means rvalue reference
//
template<typename T>
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<typename T>
void f(T&& param);
int a;
f(a); // 传入左值,那么上述的T&& 就是lvalue reference,(int &)也就是左值引用绑定到了左值
f(1); // 传入右值,那么上述的T&& 就是rvalue reference,(int &&)也就是右值引用绑定到了右值
T&&
的形式出现!即便是仅仅加一个const限定符都会使得“&&”不再被解释为universal reference:
template<typename T>
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<typename T>
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 <iostream>
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 <iostream>
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!
#include <iostream>
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<int&&>(x)
int && a = 5;
function(a);
return 0;
}
输出:
lvalue
rvalue
============================
lvalue
lvalue
============================
lvalue
rvalue
============================
lvalue
lvalue
============================
先看看 std::remove_reference 是如何工作的
#include <iostream>
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做了什么?
默认构造函数
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 <iostream>
#include <array>
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
}
学而不思则罔,思而不学则殆