c/c++ 指针
指针 = 带类型的地址
右值指针不能进行++,--会导致编译错误!!!
各种阴间的声明
using arr = int (*) [10]; // 指向包含十个int的数组的指针,实际上是一个二级指针
int (*arr[10])(int,int); // 函数指针
引用
引用 = 非空的指针
特别是,引用访问内存的效果和指针一样,会导致多次内存访问
在循环上很影响性能
指针与数组
区别很多,说一个比较阴间的区别
对于指针来说,p[i] : 1.读取p的值 2.计算p + i 3.读取p + i
对于数组来说,p[i] : 1.计算p + i 2.读取p + i
也就是说,对于指针和数组的操作指令是不一样的
extern 数组/指针的时候,如果extern 声明和定义没有匹配,链接器可能不报错,但是程序运行会出问题,这种错误比较难查
如果对指针和数组的区别没有理解到位,很容易翻车
说明
对c/c++指针的一个总结
https://www.cnblogs.com/malecrab/p/5572119.html
https://blog.csdn.net/weixin_30376509/article/details/99139849
PART Ⅰ 指针的声明
0.void*指针禁止进行算数运算
你可能需要reinterpret_cast在类型系统上凿洞
1.一个元素的指针
int * p = new int(10);
2.数组的指针
int *p = new int [10]{1,2,3,4,5};
3.函数指针
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
void f()
{
cout << "f" << endl;
}
int main()
{
void (*p)() = f;
(*p)();
p();
return 0;
}
(*p)()是标准写法,p()是简单写法
非常的套路
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
int* f(int (&arr)[5],double)
{
cout << "f" << endl;
}
int main()
{
int* (*p)(int(&arr)[5],double) = f;
int arr[5];
(*p)(arr,double());
return 0;
}
4.成员函数指针
静态成员函数
这个等效于限定作用域的普通函数
查了一下,&可加可不加,正常来说不需要加&
ll (*p)(ll) = test::f;
因为c++定义,函数名就是函数的起始地址
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
struct test
{
static ll f(ll)
{
cout << "f" << endl;
return 0ll;
}
};
void wapper(ll (*p)(ll))
{
p(10);
}
int main()
{
ll (*p)(ll) = &test::f;
wapper(p);
wapper(&test::f);
return 0;
}
普通成员函数
成员函数指针的size是普通指针的两倍
也就是实现为8字节的具体函数指针 + 8字节的偏移量
8字节的具体函数指针指向一个带有this指针参数的函数
8字节的偏移量,标记this参数
因为多继承的时候,会有一个对象布局的情况,需要一个额外的偏移量确定this参数
没有多继承的时候,这个偏移量是 0
这里有比较奇怪的 .* 和 ->*,专门调用成员函数的运算符
还有优先级的问题,要(obj ->* p)()这样调用
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
struct test
{
ll f(ll a,ll b)
{
cout << "f" << "\n";
return a + b;
}
};
void wapper(ll (test::*p)(ll,ll))
{
test obj;
(obj.*p)(1,2);
}
int main()
{
auto obj = new test;
ll (test::*p)(ll,ll) = test::f;
(obj ->* p)(10,10);
wapper(p);
wapper(test::f);
return 0;
}
5.模板函数
静态成员函数、成员模板函数类似
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
template<typename T>
void f(T t)
{
cout << typeid(T).name() << endl;
}
int main()
{
void (*p)(ll) = f<ll>;
p(10);
return 0;
}
6.成员变量指针
取静态类成员函数指针的时候,要全局初始化全局变量
否则链接失败
注意普通指针实现为一个地址,而成员变量指针实现为一个偏移量
空成员变量指针的值为-1
这也是为什么成员变量指针需要特殊的操作的原因
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
struct test
{
static ll f;
ll g;
};
ll test::f = 100; // 要做全局初始化,否则,链接失败
int main()
{
ll *f = &test::f;
ll (test::*g) = &test::g;
test obj;
obj.*g = 10;
cout << *f << endl;
cout << obj.g << endl;
return 0;
}
7.虚函数指针
这个和正常的函数指针一样,虚函数指针在调用的时候,和普通调用触发虚函数的效果一样
总结
普通指针就是一个地址,不同的指针类型意味着对内存不同的操作方式,以及做算数运算时不同
为了实现指针的语意,不同的编译器有不同的实现方式
复杂指针的实现具体看编译器
PART Ⅱ 智能指针
1.unique
https://blog.csdn.net/KingOfMyHeart/article/details/107006742
值得注意的是auto_ptr没有正确的实现拷贝、移动语意,(丢进垃圾桶:P)
c++11没有make_unique,c++14才有
有一说一网上很多实现make_unique是错的...麻了...这个是标准库实现
#include <cstddef>
#include <memory>
#include <type_traits>
#include <utility>
namespace std {
template<class T> struct _Unique_if {
typedef unique_ptr<T> _Single_object;
};
template<class T> struct _Unique_if<T[]> {
typedef unique_ptr<T[]> _Unknown_bound;
};
template<class T, size_t N> struct _Unique_if<T[N]> {
typedef void _Known_bound;
};
template<class T, class... Args>
typename _Unique_if<T>::_Single_object
make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template<class T>
typename _Unique_if<T>::_Unknown_bound
make_unique(size_t n) {
typedef typename remove_extent<T>::type U;
return unique_ptr<T>(new U[n]());
}
template<class T, class... Args>
typename _Unique_if<T>::_Known_bound
make_unique(Args&&...) = delete;
}
滋滋数组操作
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
int main()
{
unique_ptr<int[]> p(new int[3]{1,3,3});
for (ll i = 0;i < 3;i++)
cout << p[i] << endl;
return 0;
}
各种删除器
#include <bits/stdc++.h>
using namespace std;
struct op
{
void operator() (int* p)
{
cout << "call my deledeter" << endl;
delete [] p;
}
};
int main()
{
std::unique_ptr<int,op> ptr0(new int[10]{1,2,3,4,5,6,7,8,9,10});
std::unique_ptr<int,function<void(int*)>> ptr1(new int[100],
[](int*p)->void{
cout<<"call my lambda deleter:int[]"<<endl;
delete []p;
}
);
std::unique_ptr<FILE,function<void(FILE*)>> ptr2(fopen("data.txt","w"),
[](FILE*p)->void{
cout<<"call my lambda deleter:FILE"<<endl;
fclose(p);
}
);
return 0;
}
总结
unique_ptr和make_unique支持创建数组
值得注意的是unique_ptr的删除器的类型是固定的,这与shared_ptr的删除器不同
unique_ptr的大小很奇怪的原因是因为空基类优化EBO
2.shared_ptr
使用shared_ptr需要注意的问题
1.不要用一个原始指针初始化多个shared_ptr,会造成二次销毁
2.不要在函数实参中创建shared_ptr。因为C++的函数参数的计算顺序在不同的编译器下是不同的。正确的做法是先创建好,然后再传入
3.禁止通过shared_from_this()返回this指针,这样做可能也会造成二次析构
4.避免循环引用。智能指针最大的一个陷阱是循环引用,循环引用会导致内存泄漏。解决办法是将循环引用中的一个shared_ptr换为weak_ptr
5.c++11::shared_ptr不支持数组
std::shared_ptr<int> p(new int[10](), std::default_delete<int[]>());
p.get()[0];
6.shared_ptr的删除器并不以模板参数的形式出现
这是与unique_ptr的一个重要区别,reset可以带删除器的参数
7.enable_shared_from_this
https://blog.csdn.net/weixin_44517656/article/details/114195265
这个是奇异模板递归,用来解决,要从类内返回一个shared_ptr,避免二次delete的问题
8.在多线程环境中使用共享指针的代价非常大,这是因为你需要避免关于引用计数的数据竞争
9.shared_ptr的size始终是16
一个资源指针 + 一个指向包含{计数,删除器}的指针
其中删除器采用了类型擦除的形式,传入一个可调用对象即可
https://www.cnblogs.com/XDU-mzb/p/15058530.html
https://www.zhihu.com/question/39235353/answer/80533306
https://www.cnblogs.com/muxue/archive/2009/12/10/1621451.html
enable_shared_from_this
https://blog.csdn.net/weixin_44517656/article/details/114195265
有几个坑,用的时候避开就行了
1.二次析构
enable_shared_from_this就是在解决这个问题
2.weak_this_没有初始化就调用shared_from_this
这个主要有两种情况
1.在构造函数值中调用shared_from_this
因为shared_ptr是由weak_this_赋值创建的,所以weak_this_必须被初始化,而整个类中只有_internal_accept_owner是初始化weak_this_的,该函数由shared_ptr构造时自动调用。所以目前逻辑就清楚了,只有shared_ptr构造被完整调用后,weak_this_才有值,然后shared_from_this才能被成功返回。但是注意,构造初始化完全的顺序刚好与这个顺序有点相反。例如:
shared_ptr<Test> p(new Test());
首先进入shared_ptr,但是会先去执行Test的构造,然后又因为enable_shared_from_this是Test的基类,所以最终先去执行完enable_shared_from_this的构造,再返回Test的构造执行完,最后返回shared_ptr的构造执行完。但是我们写代码时只需要记住必须只有shared_ptr被先执行,才能进入Test与enable_shared_from_this的构造。而不能越过shared_ptr的构造直接调用Test的构造和enable_shared_from_this的构造,这必然是错误的,因为没有shared_ptr的构造初始化weak_this_,shared_from_this返回的p肯定是非法的。
3.private 继承enable_share_from_this会出问题
2.在一个栈对象中使用shared_from_this;
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
struct test : public enable_shared_from_this<test>
{
shared_ptr<test> get_ptr()
{
return shared_from_this();
}
};
int main()
{
test a;
a.get_ptr();
return 0;
}
会throw std::bad_weak_ptr,因为weak_this_没有被shared_ptr初始化
3.weak_ptr
http://c.biancheng.net/view/7918.html
持有一个对shared_ptr指向资源的弱引用,就是用来解决循环引用导致的资源无法释放的问题
本文来自博客园,作者:XDU18清欢,转载请注明原文链接:https://www.cnblogs.com/XDU-mzb/p/14999097.html
浙公网安备 33010602011771号