剑指offer学习笔记:2.2 编程语言

每天学一点

2.2 编程语言



第一种类型:对c++概念的理解程度

典型问题:对c++关键字的理解

1.在c++中,有哪四种与类型转换相关的关键字,这几个关键字有什么特点,应该在什么场合下使用

参考链接 https://www.jianshu.com/p/e6a1ed13f14f https://blog.csdn.net/Bob__yuan/article/details/88044361

答:static_cast,reinterpret_cast,dynamic_cast和const_cast。****const_dynamic和reinterpret_cast用于给const变量赋值和非相关类型之间转换,尽量不要用。static_cast常用于基本数据类型之间的转换。static_cast和dynamic_cast都可以用于类之间指针和引用的转换。但用dynamic_cast进行转换要求基类必须是多态的(有虚函数),static_cast没有这个限制,但是由于其没有类型检查,在进行基类指针向派生类指针转换时,存在风险。四种转换类型中,除dynamic_cast是在运行时进行类型转换,其余都是编译时进行。

可以参考https://blog.csdn.net/Bob__yuan/article/details/88044361最后一段代码加深理解

使用方法:type B = static_cast(type) (A)

a. static_cast 用于非多态类型的转换(静态转换,类中有虚函数,这个类就是多态的)。四种方法中最常见的方法。只能用于相关类型之间的转换,例如int和char,不能用于不相关类型之间的转换,例如int和int*,因为他俩一个表示数据,一个表示地址。此关键字没有运行时类型检查来保证转换的安全性

注意点:1)不能在没派生关系的两个类类型之间进行转换 2)不能去除掉原有类型的类型修饰符,例如const,volatile,__unaligned 3)转换对象时由于没有动态类型检查,所以由基类对象转换成派生类对象的时候存在安全隐患

主要用法:1)用于类层析结构中基类和派生类之间指针和引用的转换,进行上行转换,将派生类指针转换为基类指针是安全的,进行下行转换,将基类指针或引用转换为派生类,由于没有动态类型检查,是不安全的 2)用于基本数据类型的转换(最常用),例如int转换为char,安全性也由开发人员来保证 3)将空指针转换为目标类型的空指针 4)将任何类型的表达式转化为void类型

int i = 10; double d2 = static_cast(i);

b. reinterpret_cast reinterpret的含义是重新解释,可将一种类型转换成另一种不相关类型,需要谨慎使用

主要用法:1)改变指针或引用类型 2)将指针转化为足够长度的整数 3)将整型转化为指针或引用类型

int i = 10; int* p2 = reinterpret_cast(i);

还可以进行函数转换

void Fun(int s)  { cout << s;}

typedef void(*FUNC)();

FUNC pf = reinterpret_cast(Fun);   pf(); 

这段代码最终会输出一个随机值。因为用户在外部未传参,但是该函数在调用时会创建形参,该形参未初始化,自然是随机值

c. const_cast 删除变量的const属性,方便再次赋值 。该转换在编译时完成,用于解除const,volatile修饰符,只能转换指针或者引用。没什么实际用途,容易踩坑,尽量不用。

const int i = 10; int *p = const_cast(&i);

d. dynamic_cast 以上三种类型转换都是编译时完成,dynamic_cast是运行时完成,并进行类型检查。用法dynamic_cast (expression)

注意点:1)运行时完成,且有类型检查 2)不能用于内置基本数据类型之间的强制转换 3)type-id必须是指针或者引用,转换成功的话返回指向类的指针或者引用,失败返回nullptr 4)在类间进行上行转换(子类指针转化为父类指针),效果同static_cast,进行下行转换(父类指针转换为子类指针,由于有类型检查功能,更加安全)5)使用dynamic_cast进行转换,基类一定要有虚函数。这是由于运行是类型检查需要的类型信息是存在虚函数表中的,只有定义了虚函数才有虚函数表(即要求基类是多态的)

2.sizeof问题

面试题:定义一个空的类型,里面没有任何成员变量和成员函数,对该类型进行sizeof,结果是多少

答:是1(或是由编译器决定)

原因:空类型的实例不包含任何信息,本来sizeof应该是0。但是由于我们在声明该类型的实例时,他必须在内存中占有一定的空间,否则无法使用。因此编译器会给其分配一定的内存,具体多少,由编译器决定。

面试题:如果在该类型中添加一个构造或者析构函数,再对该类型进行sizeof,结果是多少

答:不变

原因:调用构造函数和析构函数只需要知道函数的地址即可,而函数地址只有类型有关,与类的实例无关,编译器不会在实例中添加任何额外信息

面试题:如果将析构函数定义为虚函数呢

答:一个指针的大小。32位编译器上市4,64位编译器上是8

原因:c++编译器一旦发现一个类中有虚函数,就会对该类型生成虚函数表,并在该类型的实例中添加一个指向虚函数表的指针。

【C++拾遗】 C++虚函数 参考链接 https://blog.csdn.net/xiejingfa/article/details/50454819

在c++中存在静态联编和动态联编的区别。静态联编是指编译器在编译阶段就实现了联编或者绑定。比如函数重载,c++编译器在编译阶段根据给函数传递的参数个数和参数类型就判断具体使用哪一个函数,实现绑定,这种绑定方式就叫做静态联编。而动态联编,指在程序运行时完成的联编。

c++通过虚函数来支持动态联编,并实现多态机制。在c++中,多态就是利用基类指针指向子类实例,然后通过基类指针调用子类(虚)函数,实现“一个接口,多种形态”的效果。c++中用基类指针和虚函数实现动态多态,虚函数在基类中声明一次即可(virtual),在派生类中,virtual关键字可以省略。

假设类base2继承类base1,base1中有虚函数func1和非虚函数func2,定义

base2 b2; base1* b1 = &b2;b1->func1();(base2中的func1,因为这是个虚函数,具体调用哪个版本取决于指针所指向的对象类型) b1->func2();(bass1中func2,对于非虚函数,具体调用哪个版本由指针类型本身决定,与指针指向对象无关)

注意:类中非虚函数可以在继承来中被重新定义,但是绝大多数情况下不应该这么做



第二种类型:分析代码运行结果

面试题:

**class A {**

**private: int value; **

**public: **

**A(int n) { value = n; } **

**A(A other) {value = other.value;}**

**};**

**int _tmain() { A a = 10; A b = a; b.print();}**

问该程序是否能正常执行

答:不能,编译出错

原因:在上述代码中,A(A other)这个复制构造函数是一个传值函数。在调用传值函数时,需要调用复制构造函数将实参复制给形参。这样会造成复制构造函数无休止的递归,最终造成栈溢出。因此,c++中不允许复制构造函数为传值函数,会编译出错。应将其修改为常量引用A(const A& other)



第三种类型:要求写代码定义一个类型或者实现类型中的成员函数

典型问题:围绕构造函数,析构函数和运算符重载

记录基本书籍

《effective c++》 c++经常出现的问题及其解决技巧

《Inside c++ object model》了解c++对象的内部,包括前面sizeof问题

面试题1:赋值运算符函数

如下为类型CMyString的声明,请为改类型添加赋值运算符函数

class CMyString {

public:

CMyString(char* pData = NULL);

CMyString(const CMyString& str); 

~CMyString(void);

private: char* m_pData;

};

参考链接: https://www.cnblogs.com/CJT-blog/p/10458442.html
[https://github.com/zhedahht/CodingInterviewChinese2/blob/master/01_AssignmentOperator/AssignmentOperator.cpp]
剑指offer源码
(https://github.com/zhedahht/CodingInterviewChinese2/blob/master/01_AssignmentOperator/AssignmentOperator.cpp)

答:

#ifndef UNTITLED_CMYSTRING_H
#define UNTITLED_CMYSTRING_H
# include 
// 剑指offer第一题,写一个赋值函数
// 注意点 1.返回值要是实例引用,为了可以连续赋值 2.传递参数为常量引用,防止在函数内被更改
// 3.判断传入变量不为当前对象本身,避免释放掉自己本身 4.注意释放空间
class CMyString {
public:
   CMyString(const char* pData = NULL);
   CMyString(const CMyString& str);
  ~CMyString(void)
  {
      delete [] m_pData;
 }
 CMyString& operator=(const CMyString &str );
  void print()
   {
      cout << m_pData;
   }
private:
   char* m_pData;
};
CMyString::CMyString(const char* pData) // 构造函数
{
  if (pData == NULL)
   {
      m_pData = NULL;
       return;
  }
   m_pData = new char[strlen(pData) + 1];
   strcpy(m_pData, pData);
}

CMyString::CMyString(const CMyString& str)   // 复制构造函数,注意这里要是引用,不然会是浅拷贝
{
   if (str.m_pData != NULL)
   {
       m_pData = new char[strlen(str.m_pData) + 1];
       strcpy(m_pData, str.m_pData);
   } else
 {
     m_pData = NULL;
   }
}

CMyString& CMyString::operator=(const CMyString &str ) {
   if (this == &str || str.m_pData == NULL) // 这里一定要判断传入参数与实例是否为同一个,否则后面释放内存,会把传入参数也释放了
   {
       return *this;
   }
   /*  这个方法是比较常见的写法,但是有点问题,如果new不成功,返回的是一个m_pdata为空的实例
    *  后续调用容易出问题
    *  这里最好升级成如果new不成功,则返回原来的实例,不进行delete的
   if (m_pData != NULL)
   {
       delete [] m_pData;
   }
   m_pData = new char[strlen(str.m_pData) + 1];
   strcpy(m_pData, str.m_pData);
   return *this;
    */
   // 定义局部实例strTemp,接着将其m_pData与当前实例做交换。由于局部实例在函数结束后会释放,自动调用析构函数,因此当前实例m_pData内存就会被释放
   // 如果new空间不够,则赋值构造函数跑出诸如alloc之类的异常,原来的实例状态尚未被修改
   CMyString strTemp(str);
   char *pTemp = strTemp.m_pData;
   strTemp.m_pData = m_pData;
   m_pData = pTemp;
   return *this;
}

#endif //UNTITLED_CMYSTRING_H

// main.cpp中测试用例
   // test operator = 测试用例
  // 1.正常赋值
   cout << "test1 begin" << endl;
   char* test = "hello world";
   CMyString str1(test);
   CMyString str2;
   str2 = str1;
  cout << "str2: ";
   str2.print();
  cout << endl;
   cout << "test1 end" << endl;
   // 2.赋值给自己
  cout << "test2" << endl;
   str1 = str1;
   str1.print();
   cout << endl;
   cout << "test2 end" << endl;
   // 3.连续赋值
   cout << "test3" << endl;
   CMyString str3;
   CMyString str4;
   str4 = str3 = str1;
   str4.print();
   cout << endl;
   cout << "test3 end" << endl;

注意考察点 1)是否把返回值的类型声明为该类型的引用,并在函数结束前返回实例本身引用(即*this)。若返回值为void,将不能连续赋值,不符合运算符规则 2)是否将传入参数的类型声明为常量引用。传入参数用引用代替实例,节约一次形参到实参调用复制构造函数的过程,const关键字,可以防止赋值函数内部更改传入实例状态 3)是否释放实例自身已有内存 4)是否判断传入参数与当前实例是否为同一实例,防止delete掉需要赋值的内容

答:1)经典解法

CMyString&(注意点1) CMyString::operator = (const CMyString &str(注意点2))

{

步骤1:if (this == &str)   return *this; (注意点4)   //同一实例,不需要进行赋值操作

步骤2:delete []m_pData;   m_pData = NULL; (注意点3)

步骤3:m_pData = new char[strlen(str.m_pData) + 1];

步骤4:strcp(m_pData, str.m_pData);

步骤5:return *this;

}

2)升级,考虑异常安全性

注意升级点:考虑如果运行步骤3的时候由于运行内存不足,导致new空间失败。由于原始m_pData空间已在步骤2中被清空,m_pData将是一个空指针,容易导致程序崩溃。因此,升级程序,考虑在new失败情况下,不更改当前实例的方法。 一种方法:先new再delete,若是new不成功,则保持原实例不变 另一种方法:创建一个临时实例,再交换临时实例和原来的实例。在构造函数中用new分配内存,在析构函数中释放内存。

CMyString&(注意点1) CMyString::operator = (const CMyString &str(注意点2))

{

if (this != &str) (注意点4)

{   CMyString strTemp(str);  char* pTemp = strTemp.m_pData;  strTemp.m_pData = m_pData; m_pData = pTemp;(运用临时变量,当new不成功时,可以返回原始实例) }

return *this;

}

【c++拾遗】memcpy和strcpy
参考链接:https://www.cnblogs.com/stoneJin/archive/2011/09/16/2179248.html

1)strcpy只能复制字符串,memcpy可以复制任何内容,例如字符数组,结构体等
2)复制方法不同,strcpy不需要指定长度,他遇到被复制字符串的结束符"\0"才会结束,因此容易溢出。memcpy由第三个参数决定复制长度
3)用途不同。一般复制字符串用strcpy,其余用memcpy

典型问题:设计模式

面试题2:实现singleton(单例)模式(设计一个类,我们只能生成该类的一个实例)

//
// Created by Xue,Lin on 2020/6/9.
//
// 饿汉模式
#ifndef UNTITLED_SINGLETON_HUNGERY_H
#define UNTITLED_SINGLETON_HUNGERY_H
class SingletonHungry{
private:
   static SingletonHungry* single;
   SingletonHungry() { cout << "constructor called!" << endl; };
   SingletonHungry(SingletonHungry&) {};
   SingletonHungry& operator=(const SingletonHungry&) {};
   ~SingletonHungry() { cout << "destructor called!" << endl; }
public:
   static SingletonHungry* GetSingleton();
};
// 重点在这里,在类定义的时候就已经被实例化了
SingletonHungry *SingletonHungry::single = new SingletonHungry();
SingletonHungry *SingletonHungry::GetSingleton()
{
   return single;
}
#endif >//UNTITLED_SINGLETON_HUNGERY_H
// main文件中
   SingletonHungry* single1 = SingletonHungry::GetSingleton();
   SingletonHungry* single2 = >SingletonHungry::GetSingleton();
 // 运行结果,调用一次构造函数,不调用析构函数。注意main文件里什么都不写,其实构造函数也会被调用,就是这样
 // SingletonHungry* single1 = SingletonHungry::GetSingleton();
 // SingletonHungry* single2 = SingletonHungry::GetSingleton();
#ifndef UNTITLED_SINGLETON_LAZY_H
#define UNTITLED_SINGLETON_LAZY_H
// 懒汉模式
#include 
class SingleLazy{
private:
   static SingleLazy* single;
   SingleLazy() {cout << "constructor called!"; };
   SingleLazy(SingleLazy&);
   SingleLazy&operator=(const SingleLazy&);
   ~SingleLazy() {cout << "destructor called!"; };
   static std::mutex my_mutex;
public:
   static SingleLazy* GetSingleton()
   {
       if (single == NULL)
       {
           //这个锁的保护范围就是这个大括号中的内容
           std::lock_guard ml(my_mutex);
           if (single == NULL)
           {
               single = new SingleLazy();
           }
       }
       return single;
   }
};
std::mutex SingleLazy::my_mutex;
SingleLazy* SingleLazy::single = NULL;

#endif //UNTITLED_SINGLETON_LAZY_H

书中进阶思路:定义构造函数为private(只适用于单线程,多线程扔会创建多个) --- 在创建实例之前加同步锁(lock(syncobj){if(instance == null) instance = new singleton;} 每次想要获得实例,还需要进行同步锁,非常耗时) --- 再多加一层判断,先判断实例是否存在,不存在再加锁(if(instance == null) {lock(syncobj){if(instance == null) instance = new singleton;}},代码复杂,容易出错)--- 强烈推荐的算法1:利用静态构造函数初始化静态变量,利用静态函数只被调用一次的特性(只要调用类就会被创建,浪费内存,这个应该是对应下面饿汉模式,在调用频繁的时候更有优势,相当于空间换时间) --- 强烈推荐算法2:定义一个私有类型,只有在调用静态构造函数创建实例时,该类型才会被调用(这个对应下面的懒汉模式,在调用不频繁时更有优势,用时间换空间)。

书中代码用的是c#,参考网上资料研究c++版本

https://www.cnblogs.com/sunchaothu/p/10389842.html, https://www.jianshu.com/p/69eef7651667
https://www.cnblogs.com/xuelisheng/p/9744301.html

单例是设计模式的一种,其特点是只提供唯一一个类的实例,具有全局变量的特点,在任何位置都可以通过接口获得那个唯一的实例。

具体应用场景 1)设备管理器,系统中可能有多个设备,但是只有一个设备管理器,用于管理设备驱动 2)数据池,用于缓存数据的数据结构,在一处写,多处读取,或者在多处写,多处读取

基础要点 1)全局只有一个实例,static特性,同时禁止用户自己声明并定义实例(将构造函数定义为private) 2)线程安全 3)禁止赋值和拷贝 4)用户通过接口获取实例:使用static类成员函数

c++实现单例模式的几种方法:
饿汉模式:在单例类定义时就进行实例化。用空间来换取时间。本身就是线程安全的。
懒汉模式:顾名思义,就是不到万不得已不会去实例化类,也就是第一次用到类实例的时候才会去实例化。在访问量较小时使用,用时间来换取空间。

饿汉模式代码

class Singelton{
private:
  Singelton(){
  }
  static Singelton *single;
public:
  static Singelton *GetSingelton();
};
// 饿汉模式的关键:初始化(实例化)
Singelton *Singelton::single = new Singelton();
Singelton *Singelton::GetSingelton(){
return single;
}

简单懒汉模式代码,这个单例模式只适用于单线程,当多线程时,其余线程可能判断当前对象是null创建新对象,因此是线程不安全的(解决方法:加锁)。同时,只有new对象,没有delete对象,容易造成内存泄漏(解决方法:使用共享指针)

class Singleton{
private:
    Singleton(){};
    static Singelton *single;
public:
    static Singleton *GetSingleton();
};
Singleton *Singleton::single = nullpter; (与饿汉模式的区别点)
Singleton *Singleton::GetSingleton() {
    if (single == nullptr)
    {
        single = new Singleton();
    }
    return single;
}

双检锁加只能指针懒汉模式,解决线程不安全和内存泄漏问题。shared ptr在析构的时候,其对应的new出来的对象也会被delete掉,会调用析构函数。但是其实关于内存泄漏的问题存在争议,有说法,https://blog.csdn.net/stpeace/article/details/68953096?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
单例伴随着进程的生命周期, 常驻内存, 不需要程序员来释放(实际上, 人为释放是有风险的)。 如果进程终结, 对应的堆内存自动被回收, 不会泄露。

class Singleton{
private:
    Singleton(){};
    Singleton(Singleton&){};
    Singleton& operator=(const Singleton&){};
    // static Singelton *single;
    static Ptr m_instance_ptr; // 实例改为智能指针
    static std::mutex m_mutex; // 加锁
public:
    typedef std::shared_ptr Ptr;
    static Ptr GetSingleton();
};
Singleton::Ptr Singleton::m_instance_ptr = nullpter;
std::mutex Singleton::m_mutex;
Ptr Singleton::GetSingleton() {
    if (m_instance_ptr == nullptr){  // 如果判断为空,再加锁判断,不然加锁判断比较耗时
        std::lock_guard lk(m_mutex);
        if(m_instance_ptr == nullpter)
        {
            m_instance_ptr = std::shared_ptr(new Singleton);
        }
        return m_instance_ptr;
    }
}

这种模式仍存在不足1)使用智能指针要求用户也要用只能指针。同时用锁也有开销。更严重的是,在某些平台,双检锁会失效。具体解决不研究了,参考https://www.cnblogs.com/sunchaothu/p/10389842.html

【c++拾遗】c++内存区 参考链接 https://blog.csdn.net/aheadkeeper/article/details/105038740
在c++中,内存分为5个区,分别为堆,栈,自由存储区,全局/静态存储区和常量存储区
堆:由new分配的内存块,他们的释放编译器不管,由程序来控制,一般一个new对应一个delete。如果程序员没有释放掉,在程序结束,操作系统会自动回收。
栈:就是编译器在需要的时候分配,在不需要的时候自动清理的空间。里面变量包括局部变量,函数参数等。
自由存储区: 由malloc分配的内存块,与堆相似,不过是用free来进行释放
全局/静态存储区:全局变量和静态变量都被分配在同一块内存中
常量存储区:存放常量
int\* p = new int[5]; 中即用到了堆又用到了栈。程序会先确定在堆中分配内存的大小,调用operatenew来分配一块内存区。然后返回内存区首地址,为指针值,存在栈中。释放内存时要注意告诉编译器这是一块数组delete []p

【c++拾遗】 static关键字(修饰类,方法,变量,静态块)参考链接: https://www.cnblogs.com/XuChengNotes/p/10403523.html

c++中static用法分为面向过程的static用法(面向函数和变量)和面向对象的static用法(面向类)

面向过程的static用法 1)静态全局变量 2)静态局部变量 3)静态函数
静态变量都在全局数据分配区,包括静态全局变量和静态局部变量。把全局变量改为静态全局变量是改变其作用域,限制其使用范围只在本文件中。将局部变量改为全局变量是改变其存储方式,进而改变其生存期。
1)静态全局变量
在全局变量之前,加上static,这个变量就被定义为静态全局变量。具有以下特点 a.该变量在全局数据区分配内存 b.未经初始化的静态全局变量会被程序自动初始化成0 c.静态全局变量在声明他的整个文件是可见的,在文件外是不可见的
静态全局变量与普通全局变量:
共同点:都存在全局变量区,都是静态存储,就是编译的时候就分配好空间。
不同点:动态全局变量的作用域为整个源程序,当一个源程序有多个源文件,动态全局变量在各个文件中都有效。但是静态全局变量只在自己定义的文件中有效。静态函数也是为了限制其作用域,只在本文件中可见,其余文件中可以定义同名函数,不会发生冲突。
2)静态局部变量
通常情况下,在函数体内定义一个变量,每当程序运行到该语句就会给局部变量分配栈内存。但随着程序退出函数体,栈内存会被释放,局部变量也会因此失效。但是有时候,我们需要在两次调用之间对变量值进行保存,用静态局部变量就可以实现这个功能。
特点:a. 在全局数据区分配空间 b. 在程序首次执行到声明处时被初始化,之后不会再进行初始化 c.如果没有显式初始化,会被程序自动初始化成0 d.静态局部变量作用域为局部作用域。但是存储是在全局存储区,知道程序运行结束。
3)静态函数
声明只在本文件中可见,不会被其余文件使用。在其余文件中定义同名函数,不会发生冲突。

面向对象的static方法 1)静态数据成员 2)静态成员函数 3)静态类
1)静态数据成员
对于非静态,每个类都有自己的拷贝。为静态数据成员,无论这个类的对象被定义了多少个,静态数据成员在程序中只有一份拷贝,由所有成员共享。
静态数据成员初始化形式与其余数据成员不同,<数据类型><类名>::<静态数据成员名>=<值>。若静态数据成员为public属性,其访问格式<类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
2)静态成员函数
与静态数据成员一样,静态成员函数不是具体为某一个类的对象服务。普通成员函数一般隐藏一个this指针,指向对象本身,但是静态成员函数不与任何对象联系,因此其不具有this指针。因此,静态成员函数无法访问非静态数据成员和其余非静态成员函数。但是非静态成员函数可以访问静态成员函数和成员变量。
3)静态类
静态类只能包含静态成员类型,不能进行实例化,不能被继承,不能在外部进行new操作。相当于一个sealed abstract类。
注意事项:
1)静态成员函数只能访问静态数据成员和其余静态成员函数。静态成员函数不能被定义为虚函数
2)静态数据成员是静态存储的,因此必须进行初始化,且初始化的方式与普通数据成员不同
静态数据成员的初始化需要在类外进行,前面不加static。且不能在头文件中进行,避免头文件被多次引用,造成多次定义。

【c++拾遗】智能指针 参考链接 https://blog.csdn.net/k346k346/article/details/81478223

auto_ptr, unique_ptr, shared_ptr, weak_ptr。其中auto_ptr已被废弃,使用unique_ptr替代。c++中,new在动态内存管理中为对象分配空间,并返回一个指向该对象的指针,可以对对象进行初始化。delete接受一个动态对象指针,销毁该对象,并释放与之关联的内存。但是动态内存使用,有时候容易忘记释放,导致内存泄漏,有时候在还在使用的时候就释放了,导致引用非法内存指针。为了更容易管理动态内存,c++提供智能指针管理动态对象。智能指针可以自动释放其所指向的内存空间。

1)unique是c++ 11引入,替代不安全的auto。unique是定义在头文件中的智能指针。他持有对对象的独有权 --- 两个unique不能指向同一个对象。他无法复制到其余的unique,也无法通过传值传递到函数。
2)shared是一个标准的共享所有权智能指针,允许多个指针指向同一个对象,通用定义在头文件中。
3)weak被设计为和shared共同工作,用于观测shared的使用情况,可以用来判断管理的shared资源是否被释放,解决shared循环问题。

智能指针的选用指南:
1)如果程序要使用多个指向同一个对象的指针,用shared。具体情况包括
a. 将参数作为参数或函数返回值进行传递
b. 两个对象都包含指向第三个对象的指针
c. stl容器包含指针(不是很明白)
2)如果程序不需要多个指向同一个对象的指针,用unique。如果函数使用new分配内存,并返回该内存的指针,将其返回类型声明为unique是不错的选择,用该指针负责delete。

【c++拾遗】看到的两种写单例互斥的锁模式
参考链接:https://blog.csdn.net/xy_cpp/article/details/81910513

1)pthread_mutex_t
互斥锁,主要有pthread_mutex_init,pthread_mutex_destory,pthread_mutex_lock,pthread_mutex_unlock几个函数完成锁的初始换,锁的销毁,上锁和释放锁操作。
使用方法:
pthread_mutex_t mutex ;(定义)
pthread_mutex_init(&mutex,NULL);(初始化)
pthread_mutex_lock(&mutex); (上锁)
pthread_mutex_unlock(&mutex); (释放锁)
pthread_mutex_destroy(&mutex); (销毁锁)
2)lock_guard
mutex互斥量是一个类,这个类有一个lock()方法和一个unlock()方法。如果第一次运行了lock方法而没有运行unlock,第二次运行lock的时候会卡在这里,只有当运行了unlock方法后,第二次lock方法才会通过。运用这种锁的机制可以保障两端代码独立运行。lock和unlock必须成对出现,不能多写也不能少写。
std::mutex my_mutex_1;
my_mutex_1.lock();
要保护的代码块
my_mutex_1.unlock();

lock_quard()是一个类模板,代替lock和unlock。将需要保护的代码用一个大括号括起来,然后在大括号的第一句定义
std::mutex my_mutex_1;
{std::lock_guard my_lock_guard(my_mutex_1)
要保护的代码块}

你可能感兴趣的:(剑指offer学习笔记:2.2 编程语言)