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指向资源的弱引用,就是用来解决循环引用导致的资源无法释放的问题

posted @ 2021-07-11 17:44  XDU18清欢  阅读(101)  评论(0)    收藏  举报