https://docs.microsoft.com/zh-cn/cpp/cpp/
智能指针
右值引用和通用引用
左值,右值 (lvalue, rvalue)
左值:在内存中有地址的值
右值:临时变量,不一定储存在内存中(可能在寄存器中,也可能是代码中的常量)
赋值符号的左边必须是左值
函数的返回值是右值
左值引用
在汇编层面,引用和指针没有任何区别,和指针一样需要一个变量指向目标地址,只是写代码时不需要手动解引用。
在语法层面,引用声明时就要初始化,之后不可更改,任何对引用的操作(包括sizeof,++运算)都认为是对目标变量的操作。从这个角度看,引用是操作受限了的指针,起到变量别名的作用。
非常量引用不能引用常量int &var = 10;
会报错
常量引用,用const修饰引用const class1 &ra = a;
这里说是常量引用,意思是不能通过这个引用改变被引用变量的值,只能读不能写。声明时等号右边可以是常量,也可以不是。
常量引用直接由右值初始化const int& x = 1;
能编译通过
这里x引用了一个const int
型,在汇编层面先把常量1放入栈中(栈中存放了一个右值),再用一个指针指向这个右值的地址。
右值引用
右值引用是本质是一个指向一个匿名变量的指针。
右值引用延长了右值的生命周期。如果右值原本在寄存器中,定义右值引用时需要先把它放到内存中。
左/右 常量/非常量 引用对比
auto x:range // 拷贝一份元素,修改x对range无影响,不可用于vector<bool>
auto& x : range // non-const左值引用,修改x会影响range,不可用于vector<bool>
auto&& x : range // 可以是左值引用也可以是右值引用,修改x会影响range,可用于vector<bool>
const auto& x:range // 不会拷贝,只是取出来,也不会修改,大多数情况下的首选
const auto x:range // 拷贝出来但是不会修改取出来的元素,基本没有使用场景
const auto&& x:range// 只绑定右值,而且无法修改元素,没有使用场景
移动构造函数
右值引用机制的最大意义是实现移动构造函数。
接受右值引用的构造函数称为移动构造函数。
移动构造时不需要拷贝,只需要把新对象的指针指向原来的地址,原来的对象变成空值(但地址不变,可能编译器做了些特殊处理)。
std::move
头文件#include
将输入值转换为右值。
相当于剪切操作,下面示例中使用之后bar变量变成空值。
std::move在右值引用的基础上被创造出来,有了右值引用才能将右值赋给新的对象,省去左值赋值copy再delete的操作。
// move example
#include <utility> // std::move
#include <iostream> // std::cout
#include <vector> // std::vector
#include <string> // std::string
int main () {
std::string foo = "foo-string";
std::string bar = "bar-string";
std::vector<std::string> myvector;
myvector.push_back (foo); // copies
myvector.push_back (std::move(bar)); // moves
std::cout << "myvector contains:";
for (std::string& x:myvector) std::cout << ' ' << x;
std::cout << '\n';
return 0;
}
std::forward
move()会无条件的将一个参数转换成右值,而forward()则会保留参数的左右值类型。
使用std::forward可以实现右值的完美转发。函数嵌套的情况下,外层函数接受右值参数时,通过std::forward将这个值传递给内层函数,内层函数接受到的仍然是右值。否则由于外层函数已经给了接受的变量一个标识符,它已经成为左值。
通用引用 (universal references)
std::forward几乎总是可以应用到通用引用上,并且在这本书即将出版之际,一些C++社区的成员已经开始将这种通用引用称之为转发引用(forwarding references)。
std::ref与reference_wrapper
参考:https://blog.csdn.net/u014645632/article/details/78966340
https://murphypei.github.io/blog/2019/04/cpp-std-ref
让按值传参的模板可以接受一个引用作为参数。(与std::bind配合)
需要注意的是,ref()是利用模板参数推导实现的,如果你创建一个按值传参的非模板函数而想传递一个引用,ref()是做不到的。下面例子中的funtest必须是模板函数。
#include<iostream>
#include<functional>
using namespace std;
template<typename T>
void functest(T a){
++a;
}
int main(){
int a=1;
int& b=a;
functest(a);
cout << a << endl; //1
functest(b);
cout << a << endl; //1
functest(ref(a));
cout << a << endl; //2
}
std::ref返回的是一个reference_wrapper对象,不能直接调用原对象包含的的方法,这时候就需要使用reference wrapper对象的get()方法,返回真正的引用。
auto r = ref(o);
//等价于
referencce_wrapper<dectype(o)> r(o);
类型推导
decltype
https://www.cnblogs.com/QG-whz/p/4952980.html
int i = 4;
decltype(i) a; //推导结果为int。a的类型为int。
泛型编程中结合auto,用于追踪函数的返回值类型
这也是decltype最大的用途了。
template <typename _Tx, typename _Ty>
auto multiply(_Tx x, _Ty y)->decltype(_Tx*_Ty)
{
return x*y;
}
对于型别为T的左值表达式,除非该式只有一个变量名(加了括号就不止是一个变量名),否则decltype总是得出T&:
// 等效于int f1(),调用f1()时返回一个右值
decltype(auto) f1(){
int x = 0;
return x;
}
// 返回一个左值引用
decltype(auto) f2(){
int x = 0;
return (x); // 在C++中,(x)表示的是一个左值,因此此处返回的值为int&
}
f1() = 1; // 不合法
f2() = 1; // 合法
auto
参考:https://zhuanlan.zhihu.com/p/25148592
auto x:range // 拷贝一份元素,修改x对range无影响,不可用于vector<bool>
auto& x : range // non-const左值引用,修改x会影响range,不可用于vector<bool>
auto&& x : range // 可以是左值引用也可以是右值引用,修改x会影响range,可用于vector<bool>
const auto& x:range // 不会拷贝,只是取出来,也不会修改,大多数情况下的首选
const auto x:range // 拷贝出来但是不会修改取出来的元素,基本没有使用场景
const auto&& x:range// 只绑定右值,而且无法修改元素,没有使用场景
注意:auto x : range不能用于含有std::unique_ptr等只有move语意的容器。因为auto是在进行拷贝,而std::unique_ptr没有,若你尝试这样去做,会有编译错误。
注意:有const/引用/指针的时候,不要隐式地把它们藏在auto里,而是显式用
const auto* euc = GetCurrentEndUserCredential();
坑
#include <iostream>
#include <vector>
#include <typeinfo>
using namespace std;
int main(){
vector<bool> v;
v.push_ back(true);
auto var = v[0];
cout << typeid(var).name() << endl;
}
vector是一个奇葩的存在,它的[]返回的不是bool,是一个表示单独bool引用的proxy class(为了节约空间,vector
当你使用for(auto x : vector)时,你得到的x是一个__bit_reference proxy class,而这个class在进行赋值操作符=时,是返回的引用,于是当你对x操作时,你会改变vector本身的元素。这样的情况,你最好直接使用for(bool x : vector).
auto和delctype和std::declval的区别
参考:https://www.zhihu.com/question/447107869/answer/1757741983
decltype与auto与std::declval一样,都是编译时推导类型
auto 与 delctype
- auto总是去除引用和顶层修饰符
- 当你需要某个表达式的返回值类型而又不想实际执行它时用decltype
大部分场景下都可以使用 auto 让编译器自动推导类型。但在某些场景下只能使用 decltype
auto会去掉引用,导致下面的用法是错误的,因为返回值是一个右值
template<typename Container, typename Index> auto access(Container &c, Index i){ return c[i]; } access(c, 2) = 1;
decltype(auto)自动推导一个完全相同的类型,使用它可以将返回值变为左值 ```cpp int a = 1; const int &b = a; decltype(auto) c = b; // 使用decltype后,c的类型与b完全相同
template
vector
2. **当你需要某个表达式的返回值类型而又不想实际执行它时用decltype**
```cpp
// auto 与 delctype
int a = 8, b = 3;
auto c = a + b; //运行时需要实际执行a+b,哪怕编译时就能推导出类型
decltype(a + b) d; //编译期类型推导
不可以用auto c; 直接声明变量,必须同时初始化。
- 大部分场景下都可以使用 auto 让编译器自动推导类型。但在某些场景下只能使用 decltype,例如,我们希望实现一个模板函数,它接收两个 std::vector 作为参数,返回一个 std::vector 包含输入的两个 vector 中相应位置元素相加的结果:
这里,由于 T 是作为 std::vector 的模板参数,而 C++ 编译器对推导类模板参数的支持较弱,因此必须手动显式地指定模板参数的类型。template <typename L, typename R> auto add(const std::vector<L>& lhs, const std::vector<R>& rhs) { using T = decltype(lhs[0] + rhs[0]); std::vector<T> result; for (size_t i = 0; i < lhs.size() && i < rhs.size(); ++i) { result.push_back(lhs[i] + rhs[i]); } return result; }
decltype 与 std::declval
decltype必须接受一个实例化的对象(虽然是在编译期进行类型推导),std::declval可以辅助decltype不需要实例化对象就能进行类型推导。
decltype()不会实际运行括号内的表达式,但这个表达式需要符合语法,通过编译器和链接器的检查。 ```cpp struct A { A() = delete; int foo(); };
int main() { decltype(A().foo()) foo = 1; // 无法通过编译,因为无法构造一个A类的实例 decltype(std::declval().foo()) foo = 1; // OK,注意std::declval后面还有个() }
template
<a name="KjEqw"></a>
# RTTI(Run Time Type Identification)
<a name="fGN2u"></a>
### typeid
```cpp
//输出变量类型,有的编译器输出首字母,有的输出全称
#include <typeinfo>
int x = 10;
cout << endl << typeid(x).name();
getchar();
typeid返回一个type_info对象,type_info有name()方法返回名称。
dynamic_cast强制转换运算符
该转换符用于将一个指向派生类的基类指针或引用转换为派生类的指针或引用,注意dynamic_cast转换符只能用于含有虚函数的类。
如果转换失败,如果是指针则反回一个0值,如果是转换的是引用,则抛出一个bad_cast异常。
//B是基类,D是派生类
B *pb;
D *pd, md;
pb=&md;
pd=dynamic<D*>(pb);
using关键字
指定别名(类似于typedef)
命名空间
#include <iostream>
using namespace std;
//或者using std::cout;只引入部分函数防止冲突
// 第一个命名空间
namespace first_space{
void func(){
cout << "Inside first_space" << endl;
}
}
// 第二个命名空间
namespace second_space{
void func(){
cout << "Inside second_space" << endl;
}
}
int main ()
{
// 调用第一个命名空间中的函数
first_space::func();
// 调用第二个命名空间中的函数
second_space::func();
return 0;
}
在子类中引用基类的成员
class T5Base {
public:
T5Base() :value(55) {}
virtual ~T5Base() {}
void test1() { cout << "T5Base test1..." << endl; }
protected:
int value;
};
class T5Derived : private T5Base {
public:
//using T5Base::test1;
//using T5Base::value;
void test2() { cout << "value is " << value << endl; }
};
————————————————
版权声明:本文为CSDN博主「私房菜」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/shift_wwx/article/details/78742459
基类的变量和函数在继承后都是private,外界无法访问,而using关键字可以让它们被访问到
大括号初始化(initializing-classes-and-structs-without-constructors-cpp)
C++11新特性
结构体
// Has a constructor
struct TempData2
{
TempData2(double minimum, double maximum, double cur, int id, time_t t) :
stationId{id}, timeSet{t}, current{cur}, maxTemp{maximum}, minTemp{minimum} {}
int stationId;
time_t timeSet;
double current;
double maxTemp;
double minTemp;
};
类
class class_a {
public:
class_a() {}
class_a(string str) : m_string{ str } {}
class_a(string str, double dbl) : m_string{ str }, m_double{ dbl } {}
double m_double;
string m_string;
};
其他C++11
static_assert
constexpr
defaul生成构造函数,delete禁用函数
注意,如果父类的构造函数被禁用了,或者父类的构造函数为private,default无法生效,但不会报错。
实践中不要继承构造函数为非可调用的公有函数的类。
struct Point{
Point(){} // 只存在一个默认构造函数时,不需要在大括号中手动初始化成员变量
int x;
int y;
};
struct Point{
// Point(){} 不能这么写,除非在大括号中手动初始化成员变量
Point() = default; // 不需要手动初始化成员变量
Point(int _x, int _y) : x(_x), y(_y) {}
int x = 0;
int y = 0;
};
// 为了能够让程序员显式的禁用某个函数,C++11 标准引入了一个新特性:"=delete"函数。程序员只需在函数声明后上“=delete;”,就可将该函数禁用。
class X3
{
public:
X3();
X3(const X3&) = delete; // 声明拷贝构造函数为 deleted 函数
X3& operator = (const X3 &) = delete; // 声明拷贝赋值操作符为 deleted 函数
};
explicit
拒绝隐式转换,只有显示转换时才调用转换函数。
指定构造函数或转换函数 (C++11起)为显式, 即它不能用于隐式转换和复制初始化.
explicit 指定符可以与常量表达式一同使用. 函数若且唯若该常量表达式求值为 true 才为显式. (C++20起)
noexcept
给编译器更多的优化空间。
析构函数自动添加noexcept。
推荐在移动构造函数上加noexcept。
如果没有把握,就不要加。