指针变量所存的内容就是内存的地址编号。
&i:返回i变量的地址编号。*p:指针p所指向的地址的内容。
无效指针: 指针变量的值是NULL,或者未知的地址值,或者是当前应用程序不可访问的地址值,这样的指针就是无效指针,不能对他们做解指针操作,否则程序会出现运行时错误,导致程序意外终止。未经初始化的指针就是个无效指针,所以在定义指针变量的时候一定要进行初始化。如果实在是不知道指针的指向,则使用nullptr进行赋值。
野指针: 就是没有被初始化过的指针。指针变量未及时初始化 => 定义指针变量及时初始化,要么置空。
悬空指针: 是最初指向的内存已经被释放的指针。指针free或delete之后没有及时置空。因此使用时注意:释放操作后应立即置空,防止出现悬空指针。
void* 指针是一种特殊的指针类型,可用于存放任意对象的地址,但是丢失了类型信息。如果想要完整的提取指向的数据,程序员就必须对这个指针做出正确的类型转换,然后再解指针。编译器不允许直接对void*类型的指针做解指针操作(提示非法的间接寻址)。
在程序载入到内存后,函数的机器指令存放在一个特定的逻辑区域:代码区。既然是存放在内存中,那么函数也是有自己的指针的。函数名单独使用时就是函数指针。
函数指针的声明: int (*function)(int,int);
函数指针用处:
注意:不要把非静态局部变量的地址返回。局部变量是在栈中的,由系统创建和销毁,返回之后的地址有可能有效也有可能无效,这样会造成bug。可以返回全局变量、静态的局部变量、动态内存等的地址返回。
函数指针: 指向函数的指针;
指针函数: 返回指针的函数。
每个类编译后,是否创建一个类中函数表保存函数指针,以便用来调用函数?
普通的类函数(不论是成员函数,还是静态函数)都不会创建一个函数表来保存函数指针。只有虚函数才会被放到函数表中。但是,即使是虚函数,如果编译器能明确知道调用的是哪个函数,编译器就不会通过函数表中的指针来间接调用,而是会直接调用该函数。
测试1:
int* p[5]; //指针数组,数组大小为10,且每个元素都是int指针 int(*p)[5]; //数组指针,p指向一个int类型的数组 int* p(int); //函数声明,函数名是p,参数是int类型,返回值时int*类型 int(*p)(int); //函数指针,该指针指向的函数的参数和返回值都是int类型
测试2: 定义一个宏实现求结构体成员的偏移量(地址的偏移量),怎么做?
#define get_offset(struct_name, struct_member) (unsigned int)(&(((struct_name *)(0))->struct_member))
结构体元素的偏移量是针对首地址的,因此,第一步就是确定当前结构体的地址。(struct_name *)(0) 是常数0强制转换为struct_name类型的指针的类型转换符,这样该指针就指向了结构体的首地址(为0)。然后通过该指针指向不同的成员,并取得不同成员的地址进行显示转换,最终得到各个成员的地址偏移量。
访问这个结构体的某一个成员变量,这个部分是非法的,因为相当于是访问一个空指针,但是如果再在前面加上&取地址符号,就是合法的(访问非法,取地址是可以的)。
测试3: 一个子类继承了父类,如何获得子类对象的地址?
&((A*)0)->i;
当编译器要用要一个成员变量的时候,它会根据对象的首地址加上成员的偏移量得到成员变量的地址。当对象的首地址为0时,得到的成员变量地址就是它的偏移量。没有为A的对象分配内存,那怎么可以得到它的地址呢?这里确实没有分配内存,但是这个例子并没有要求有内存,也不对内存进行操作,所以不会引来崩溃。
this指针的本质是一个隐式插入类成员函数体中,用来指向需要操作的对象的指针常量。
C ++为成员函数提供了一个名字为this的指针,这个指针称为自引用指针,可以应用于所有类成员函数。每当创建一个对象时,系统就把this指针初始化为指向该对象,即this指针的值是当前被调用的成员函数所在的对象的起始地址。不同的对象调用同一个成员函数时,C++编译器将根据成员函数的this指针所指向的对象来确定应该引用哪一个对象的数据成员。
this指针的特性:
this指针的好处:
避免形参和数据成员重名,即用this指针来区分;在类的非静态成员函数中返回对象本身,可使用return *this;可以实现链式表达。
在成员函数里delete this会怎么样(对象在栈上,在堆上)?
可以delete this,但是一定要保证this对象是new出来的,不是在栈空间分配的,也不是在全局空间分配的,也不能是new[]分配的。而且,在delete之后不能访问该对象的数据成员和成员函数。
delete操作一般是先调用析构函数,再调用delete运算符。而且delete之后,该内存不会立刻被释放,只是做个标记,告诉操作系统这块内存可以被释放掉了。至于系统什么时候会释放是不知道的。所以delete this指针本身没问题,只是不能在delete this之后,访问对象的成员变量以及调用虚函数,因为成员变量和vptr是存放在该内存块的,如果以后再去访问,就是访问已经被销毁的内存。
如果在类的析构函数中调用delete this,会发生什么?
会导致堆栈溢出。原因很简单,delete的本质是“为将被释放的内存调用一个或多个析构函数,然后,释放内存” (来自effective c++)。显然,delete this会去调用本对象的析构函数,而析构函数中又调用delete this,形成无限递归,造成堆栈溢出,系统崩溃。
指针常量和常量指针,两者的区别是看const修饰的谁。
指针常量: 是指针本身是常量,换句话说,就是指针里面所存储的内容(内存地址)是常量,不能改变。但是,内存地址所对应的内容是可以通过指针改变的。以 * 为分界线,其左侧表示指针指向的类型,右侧表示指针本身的性质。
int a = 97; int b = 98; int* const p = &a; *p = 98; //正确 p = &b; //编译出错
常量指针: 是指向常量的指针,换句话说,就是指针指向的是常量,它指向的内容不能发生改变,不能通过指针来修改它指向的内容。但是,指针自身不是常量,它自身的值可以改变,从而指向另一个常量。const 的位置在指针声明运算符 * 的左侧。只要 const 位于 * 的左侧,无论它在类型名的左边或右边,都表示指向常量的指针。
int a = 97; int b = 98; const int* p = &a; *p = 98; //编译出错 p = &b; //正确
引用常量不存在,没有int& const p。而常量引用是存在的cosnt int &p。
什么时候需要使用常引用?
如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。引用型参数应该在能被定义为const的情况下,尽量定义为const 。常引用声明方式:const 类型标识符 &引用名=目标变量名;
用户自定义的类型最好用引用传参,这样可以避免不必要的构造函数和析构函数调用,但是对于内置类型,按值传参会比按引用传参更高效。
为什么要使用智能指针?
智能指针的作用是管理一个指针, 因为存在以下这种情况: 申请的空间在函数结束时忘记释放, 造成内存泄漏。 使用智能指针可以很大程度上的避免这个问题, 因为智能指针就是一个类,当超出了类的作用域是, 类会自动调用析构函数, 析构函数会自动释放资源。 所以智能指针的作用原理就是在函数结束时自动释放内存空间, 不需要手动释放内存空间。
四种智能指针:auto_ptr, shared_ptr, weak_ptr, unique_ptr,其中C++11 支持后三个,第一个已经被C++11弃用。
采用所有权模式。
auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.” )); auto_ptr<string> p2; p2 = p1; //auto_ptr 不会报错
p2 剥夺了 p1 的所有权,此时不会报错,但是当程序运行时访问 p1 将会报错。 所以 auto_ptr的缺点是: 存在潜在的内存崩溃问题!
unique_ptr 实现独占式拥有或严格拥有概念, 保证同一时间内只有一个智能指针可以指向该对象。 它对于避免资源泄露(例如“以 new 创建对象后因为发生异常而忘记调用 delete” )特别有用。unique_ptr 比 auto_ptr更安全。
unique_ptr<string> p1 (new string ("I reigned lonely as a cloud.” )); unique_ptr<string> p2; p2 = p1; //unique_ptr会报错
可以使用std::move()将一个 unique_ptr 赋给另一个。
shared_ptr 实现共享式拥有概念。 多个智能指针可以指向相同对象, 该对象和其相关资源会在“最后一个引用被销毁” 时候释放。 从名字 share 就可以看出了资源可以被多个指针共享,它使用计数机制来表明资源被几个指针共享。 可以通过成员函数 use_count()来查看资源的所有者个数。 除了可以通过 new 来构造, 还可以通过传入 auto_ptr, unique_ptr,weak_ptr 来构造。当我们调用 release()时, 当前指针会释放资源所有权, 计数减一。 当计数等于 0 时, 资源会被释放。
shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的), 在使用引用计数的机制上提供了可以共享所有权的智能指针。
#include<iostream> using namespace std; template <class T> class SmartPtr { private: T* ptr; //底层真实指针 int* use_count; //被多少指针引用,声明成指针是为了方便对其的递增或递减操作 public: SmartPtr(T* p); //SmartPtr<int>p (new int(2)); SmartPtr(const SmartPtr<T>& orig); //SmartPtr<int> q(p); SmartPtr<T>& operator=(const SmartPtr<T>& rhs); //q = p ~SmartPtr(); T operator*(); //为了能把智能指针当成普通指针操作,定义解引用操作 T* operator->(); //定义取成员操作 T* operator+(int i); //定义指针加一个常数 //int operator-(SmartPtr<T>& t1, SmartPtr<T>& t2); int getcount(); }; template <class T> int SmartPtr<T>::getcount() { return *use_count; } //template <class T> //int SmartPtr<T>::operator-(SmartPtr<T>& t1, SmartPtr<T>& t2) //{ // return t1.ptr - t2.ptr; //} template <class T> SmartPtr<T>::SmartPtr(T* p) { ptr = p; try { use_count = new int(1); } catch (...) { delete ptr; //申请失败时释放真实指针和引用技术的内存 ptr = nullptr; delete use_count; use_count = nullptr; cout << "Allocate memory for use_count fails.\n"; exit(1); } cout << "Constructor is called!\n"; } template <class T> SmartPtr<T>::SmartPtr(const SmartPtr<T>& orig) { //引用计数保存在一块内存,所有SmartPtr对象的引用计数都指向这里 use_count = orig.use_count; ptr = orig.ptr; ++(*use_count); //当前对象的引用计数加1 cout << "Copy constructor is called!\n"; } template <class T> SmartPtr<T>& SmartPtr<T>::operator=(const SmartPtr<T>& rhs) { //SmartPtr<int> p,q; p = q; //首先给q指向的对象的引用计数加1,然后给p原来指向的对象的引用计数减1 //如果减1后为0,先释放掉p原来指向的内存, //然后让q指向的对象的引用计数加1后赋值给p //这个赋值操作符在减少左操作数的使用计数之前使rhs的使用计数加1 //从而防止自身赋值”而导致的提早释放内存 ++(*rhs.use_count); if ((--(*use_count)) == 0) { delete ptr; ptr = nullptr; delete use_count; use_count = nullptr; cout << "Left side object is delete!\n"; } ptr = rhs.ptr; use_count = rhs.use_count; cout << "Assignment operator overloaded is called!\n"; return *this; } template <class T> SmartPtr<T>::~SmartPtr() { //SmartPtr的对象会在其生命周期结束的时候调用析构函数 //在析构函数中检测当前对昂的引用计数,为0就释放掉 //不为0就说明还有其他的SmartPtr引用当前对象 //等待其他对象声明周期结束时调用析构函数进行释放 if (--(*use_count) == 0) { delete ptr; ptr = nullptr; delete use_count; use_count = nullptr; cout << "Destructor is called!\n"; } } template <class T> T SmartPtr<T>::operator*() { return *ptr; } template <class T> T* SmartPtr<T>::operator->() { return ptr; } template <class T> T* SmartPtr<T>::operator+(int i) { T* tmp = ptr + i; return tmp; } int main() { SmartPtr<int> p1(new int(0)); p1 = p1; cout << *p1 << endl; cout << p1.getcount() << endl; SmartPtr<int> p2 = p1; SmartPtr<int> p3(p1); SmartPtr<int> p4(new int(1)); p4 = p1; return 0; }
weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助shared_ptr 工作, 它只可以从一个 shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。 weak_ptr 是用来解决 shared_ptr 相互引用时的死锁问题, 如果说两个shared_ptr 相互引用,那么这两个指针的引用计数永远不可能下降为 0,资源永远不会释放。 它是对对象的一种弱引用, 不会增加对象的引用计数, 和 shared_ptr 之间可以相互转化, shared_ptr 可以直接赋值给它, 它可以通过调用 lock 函数来获得 shared_ptr。
不能通过 weak_ptr 直接访问对象的方法, 比如 B 对象中有一个方法 print(), 我们不能这样访问, pa->pb_->print(); pb_是一个 weak_ptr, 应该先把它转化为shared_ptr, 如
shared_ptr<A> pa(new A()); shared_ptr<B> p = pa->pb_.lock(); p->print();