C++面试指南3

c++ 面试指南_Σίσυφος1900的博客-CSDN博客接着上一篇

c++ 面试指南2_Σίσυφος1900的博客-CSDN博客

Leetcode——C++突击面试_Stephen-CSDN博客

频繁对vector调用push_back()对性能的影响和原因?-帅地玩编程

37、explicit关键字的作用?

 一个参数的 构造函数(或者除了第一个参数外其余参数都有默认值的多参构造函数), 承担了两个角色。 1 是个 构造器 ,2 是个默认且隐含的类型转换操作符。
所以, 有时候在我们写下如 AAA = XXX, 这样的代码, 且恰好XXX的类型正好是AAA单参数构造器的参数类型, 这时候 编译器就自动调用这个构造器, 创建一个AAA的对象。
这样看起来好象很酷, 很方便。 但在某些情况下(见下面权威的例子), 却违背了我们(程序员)的本意。 这时候就要在这个构造器前面加上explicit修饰, 指定这个构造器只能被明确的调用/使用, 不能作为类型转换操作符被隐含的使用。

class Test1
{
public:
    Test1(int n)
    {
        num=n;
    }//普通构造函数
private:
    int num;
};
class Test2
{
public:
    explicit Test2(int n)
    {
        num=n;
    }//explicit(显式)构造函数
private:
    int num;
};
int main()
{
    Test1 t1=12;//隐式调用其构造函数,成功
    Test2 t2=12;//编译错误,不能隐式调用其构造函数
    Test2 t2(12);//显式调用成功
    return 0;
}


Test1的 构造函数带一个int型的参数,代码23行会隐式转换成调用Test1的这个构造函数。而Test2的构造函数被声明为explicit(显式),这表示不能通过隐式转换来调用这个构造函数,因此代码24行会出现编译错误。
普通构造函数能够被 隐式调用。而explicit构造函数只能被显式调用。

38、volatile有什么作用,是否具有原子性,对编译器有什么影响?一个参数可以既是const又是volatile吗

  • 状态寄存器一类的并行设备硬件寄存器。
  • 一个中断服务子程序会访问到的非自动变量。
  • 多线程间被几个任务共享的变量。

「注意」:虽然volatile在嵌入式方面应用比较多,但是在PC软件的多线程中,volatile修饰的临界变量也是非常实用的。

volatile 的作用:当对象的值可能在程序的控制或检测之外被改变时,应该将该对象声明为 violatile,告知编译器不应对这样的对象进行优化。

volatile不具有原子性。

volatile 对编译器的影响:使用该关键字后,编译器不会对相应的对象进行优化,即不会将变量从内存缓存到寄存器中,防止多个线程有可能使用内存中的变量,有可能使用寄存器中的变量,从而导致程序错误。

什么情况下一定要用 volatile, 能否和 const 一起使用?

使用 volatile 关键字的场景:

  • 当多个线程都会用到某一变量,并且该变量的值有可能发生改变时,需要用 volatile 关键字对该变量进行修饰;
  • 中断服务程序中访问的变量或并行设备的硬件寄存器的变量,最好用 volatile 关键字修饰。

volatile 关键字和 const 关键字可以同时使用,某种类型可以既是 volatile 又是 const ,同时具有二者的属性。

能否和 const 一起使用?

可以,用const和volatile同时修饰变量,表示这个变量在程序内部是只读的,不能改变的,只在程序外部条件变化下改变,并且编译器不会优化这个变量。每次使用这个变量时,都要小心地去内存读取这个变量的值,而不是去寄存器读取它的备份。

注意:在此一定要注意const的意思,const只是不允许程序中的代码改变某一变量,其在编译期发挥作用,它并没有实际地禁止某段内存的读写特性。

39、简述队列和栈的异同

队列和栈都是线性存储结构,但是两者的插入和删除数据的操作不同,队列是“先进先出”,栈是 “后进先出”。

「注意」:区别栈区和堆区。堆区的存取是“顺序随意”,而栈区是“后进先出”。栈由编译器自动分 配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆一般由程序员 分配释放, 若程序员不释放,程序结束时可能由OS 回收。分配方式类似于链表。它与本题中的堆和栈是两回事。堆栈只是一种数据结构,而堆区和栈区是程序的不同内存存储区域。

40、请解析(*(void (*)( ) )0)( )的含义

  • void (*0)( ) :是一个返回值为void,参数为空的函数指针0。
  • (void (*)( ))0:把0转变成一个返回值为void,参数为空的函数指针。
  • (void ()( ))0:在上句的基础上加*表示整个是一个返回值为void,无参数,并且起始地址为0的函数的名字。
  • ((void ()( ))0)( ):这就是上句的函数名所对应的函数的调用。

41、编码实现字符串转化为数字

// 将数字转还成字符串力扣

class Solution {
public:
    int translateNum(int num) 
    {
      // 将这个数字转换成string  类型
      string  s=to_string(num);
      // 数字的长度
      int  n=s.size();
      //
      vector<int> f(n+1);
      f[0]=1;// 
      for(int  i=1;i<=n;i++)
      {
        f[i]=f[i-1]; // 单独翻译

       if(i>1)
       {
         int  t=(s[i-2]-'0')*10+s[i-1]-'0';
         if(t>=10 && t<=25)// 组合的范围是10——25
         {
            f[i]+=f[i-2];
         }
       }
      }
      return f[n];
    }
};

42、自己实现一个String类

#if 1 // 自定义个一个string类
#include<stdlib.h>
#include<stdio.h>
#include<iostream>
using namespace std;
class mystring 
{
public :
	// 构造:默认(传参)、
	mystring(const char* str = nullptr);
	//拷贝构造
	mystring(const mystring& other);
	//移动构造
	mystring(mystring&& other);


	// 赋值:拷贝赋值、
	mystring& operator=(const mystring& other);
	//移动赋值
	mystring& operator=(mystring&& other);
	// 析构
	~mystring();
	int size(const char* str);
private:
	char* m_str;
};


int mystring::size(const char* str)
{
	int size = 0;
	if (str == nullptr)
	{
		size = 0;
	}
	else
	{
		size = strlen(str);
	}
	return size;
}
mystring::mystring(const  char* str)
{
	if (str == nullptr)
	{
		m_str = new char[1];
		*m_str = '\0';
	}
	else
	{
		int length = size(str);
		m_str = new char[length+1];
		strcpy_s(m_str, length + 1, str);
	}
};
//拷贝构造
mystring::mystring(const mystring& other) 
{
	int length = size(other.m_str);
	m_str = new char[length+1];
	strcpy_s(m_str, length + 1, other.m_str);
	printf("拷贝构造\n");
}
//移动构造
mystring::mystring(mystring&& other) 
{
	m_str = other.m_str;
	other.m_str = nullptr;
	printf("移动构造\n");
}
// 析构
mystring::~mystring()
{
	if (m_str!=nullptr)
	{
		delete[] m_str;
	}
}

// 赋值:拷贝赋值、
mystring& mystring::operator=(const mystring& other)
{
	if (this!=nullptr) 
	{
		if (!m_str) delete[] m_str;
		int length=size(other.m_str);
	     m_str = new  char[length +1];
		 strcpy_s(m_str, length+1,other.m_str);
		 printf("赋值:拷贝赋值\n");
	}
	return *this;
}
//移动赋值
mystring& mystring::operator=(mystring&& other)
{
	if (this != &other) {
		delete[] m_str;
		m_str = other.m_str;
		other.m_str = nullptr;
	}
	printf("赋值:移动复制\n");

	return *this;
}
int  main() 
{
	 // test 
	mystring  sss;
	mystring  sss2;
	cout << "size:" << sss.size("") << endl;
	cout << "size:" << sss2.size("chose one") << endl;
	// 测试:默认构造
	mystring s1;

	// 测试:传参构造
	mystring s2("hello world");

	// 测试:拷贝构造
	mystring s3(s1);

	// 测试:移动构造
	mystring s4(std::move(s3));

	// 测试:拷贝赋值
	mystring s5;
	s5 = s4;

	// 测试:移动赋值
	mystring s6;
	s6 = std::move(s5);

	// 测试:自动析构

	
	return 0;
}
#endif 

43、用两个栈实现一个队列的功能

力扣

力扣

class MyQueue 
{
public:
    /** Initialize your data structure here. */
    MyQueue() {

    }
    
    /** Push element x to the back of queue. */
    void push(int x) 
    {
      pushStack.push(x);
    }
    
    /** Removes the element from in front of queue and returns that element. */
    int pop()  // 先进先出
    {
      // 如果出栈为空的,那么需要吧入栈的数据全部导入到出栈中
      if(popStack.empty())
      {
         while(!pushStack.empty())
         {
            popStack.push(pushStack.top());
            pushStack.pop();
         }
      }

      //虽然上面已经判断看出栈是不是为空,但是假如出栈的里面也是没有数据的可能出栈也是为空的;
      if(popStack.empty())
      {
          return -1;
      }
       int  x=popStack.top();
       popStack.pop();
      return x;
    }
    
    /** Get the front element. */
    int peek() 
    {
      //返回的是第一个元素
      if(popStack.empty())
      {
         while(!pushStack.empty())
         {
            popStack.push(pushStack.top());
            pushStack.pop();
         }
      }
     return popStack.top();
    }
    
    /** Returns whether the queue is empty. */
    bool empty()
    {
       return pushStack.empty() && popStack.empty();
    }
private:

stack<int> pushStack;  // 入栈
stack<int> popStack;   // 出栈
};

44、vector 的底层原理

Vector优点:可使用下标随机访问,尾插尾删效率高。

缺点:前面部分的插入删除效率低,扩容有消耗,可能存在一定的空间浪费。

底层是由一块连续的内存空间组成,由三个指针实现的分别是头指针(表示目前使用空间的头),尾指针(表示目前使用空间的尾)和可用空间尾指针实现

List优点:按需申请内存,不需要扩容,不会造成内存空间浪费。在任意位置的插入删除下效率高。

缺点:不支持下标随机访问

底层是由双向链表实现的

vector的底层原理

vector底层是一个动态数组,包含三个迭代器,start和finish之间是已经被使用的空间范围,end_of_storage是整块连续空间包括备用空间的尾部。

当空间不够装下数据(vec.push_back(val))时,会自动申请另一片更大的空间(1.5倍或者2倍),然后把原来的数据拷贝到新的内存空间,接着释放原来的那片空间【vector内存增长机制】。

当释放或者删除(vec.clear())里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。

因此,对vector的任何操作一旦引起了空间的重新配置,指向原vector的所有迭代器会都失效了。

当空间不够装下数据(vec.push_back(val))时,会自动申请另一片更大的空间(1.5倍或者2倍),然后把原来的数据拷贝到新的内存空间,接着释放原来的那片空间【vector内存增长机制】。

当释放或者删除(vec.clear())里面的数据时,其存储空间不释放,仅仅是清空了里面的数据。

因此,对vector的任何操作一旦引起了空间的重新配置,指向原vector的所有迭代器会都失效了

Vector如何释放空间?

由于vector的内存占用空间只增不减,比如你首先分配了10,000个字节,然后erase掉后面9,999个,留下一个有效元素,但是内存占用仍为10,000个。所有内存空间是在vector析构时候才能被系统回收。empty()用来检测容器是否为空的,clear()可以清空所有元素。但是即使clear(),vector所占用的内存空间依然如故,无法保证内存的回收。

如果需要空间动态缩小,可以考虑使用deque。如果vector,可以用swap()来帮助你释放内存。
vector(Vec).swap(Vec); //将Vec的内存清除;
vector().swap(Vec); //清空Vec的内存;

频繁对vector调用push_back()对性能的影响和原因?

在一个vector的尾部之外的任何位置添加元素,都需要重新移动元素。而且,向一个vector添加元素可能引起整个对象存储空间的重新分配。重新分配一个对象的存储空间需要分配新的内存,并将元素从旧的空间移到新的空间。

vector迭代器失效的情况

      vector动态增加大小时,并不是在原空间后增加新的空间,而是以原大小的两倍在另外配置一片较大的新空间,然后将内容拷贝过来,并释放原来的空间。由于操作改变了空间,所以迭代器失效。

vector中erase方法与algorithn中的remove`方法区别

    erase()删除指定元素,元素个数减1,即size--
    remove()同时删除所有指定值的元素,其它元素前移补位,但元素总个数不变(size不变)
#if  1
#include <algorithm>
using  namespace std;
template<typename T>
void printvector(vector<T>& x)
{
	for(auto e: x)cout << e << " ";
	cout << endl;

}
int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(8);
	v.push_back(5);
	v.push_back(7);
	v.push_back(8);
	v.push_back(6);

	cout << "删除前:" << v.size() << endl;
	v.erase(v.begin());
	printvector(v);
	cout<<"erase  :" << v.size()<<endl;


	cout<< "======================================" << endl;


	vector<int>::iterator pos;
	pos = remove(v.begin(), v.end(), 2);
	printvector(v);
	cout << "v  :" << v.size() << endl;

	return 0;
}
#endif 

45、unordered_map、unordered_set 底层原理

      unordered_map的底层是一个防冗余的哈希表(采用除留余数法)。哈希表最大的优点,就是把数据的存储和查找消耗的时间大大降低,时间复杂度为O(1);而代价仅仅是消耗比较多的内存

使用一个下标范围比较大的数组来存储元素。可以设计一个函数(哈希函数(一般使用除留取余法),也叫做散列函数),使得每个元素的key都与一个函数值(即数组下标,hash值)相对应,于是用这个数组单元来存储这个元素;也可以简单的理解为,按照key为每一个元素“分类”,然后将这个元素存储在相应“类”所对应的地方,称为桶。

但是,不能够保证每个元素的key与函数值是一一对应的,因此极有可能出现对于不同的元素,却计算出了相同的函数值,这样就产生了“冲突”,换句话说,就是把不同的元素分在了相同的“类”之中。 一般可采用拉链法解决冲突:


46、list 底层原理

list的底层原理

list的底层是一个双向链表,以结点为单位存放数据,结点的地址在内存中不一定连续,每次插入或删除一个元素,就配置或释放一个元素空间。

list不支持随机存取,适合需要大量的插入和删除,而不关心随即存取的应用场景。

47、deque的底层原理?priority_queue的底层原理

deque的底层原理

vector是单向开口的连续线性空间,deque则是一种双向开口的连续线性空间。所谓双向开口,意思是可以在头尾两端分别做元素的插入和删除操作。vector当然也可以在头尾端进行操作(从技术观点),但是其从头部操作效率奇差,无法被接受。

deque是一个双向开口的连续线性空间(双端队列),在头尾两端进行元素的插入跟删除操作都有理想的时间复杂度。

48、map 、set、multiset、multimap的底层原理

map 、set、multiset、multimap的底层实现都是红黑树,epoll模型的底层数据结构也是红黑树,linux系统中CFS进程调度算法,也用到红黑树。

map中[ ]与find的区别

 将key作为下标去执行查找,并返回相应的值;如果不存在这个key,就将一个具有该key和value的某人值插入这个map。

map的find函数:用k执行查找,找到了返回该位置的迭代器;如果不存在这个k,就返回尾迭代器。

map 、set、multiset、multimap的特点

  1. set 和multiset 会进行排序,只是multimap 的元素可以重复,但是set中元素不允许重复
  2. map和multimap 是<k,v>,是根据k 来排序,只是multimap的k可以重复,但是map中k不允许重复

红黑树的特性:

  1. 每个结点或是红色或是黑色;
  2. 根结点是黑色;

  3. 每个叶结点是黑的;

  4. 如果一个结点是红的,则它的两个儿子均是黑色;

  5. 每个结点到其子孙结点的所有路径上包含相同数目的黑色结点。

为何map和set的插入删除效率比其他序列容器高,为何map和set每次Insert之后,以前保存的iterator不会失效

因为存储的是结点,不需要内存拷贝和内存移动。

因为插入操作只是结点指针换来换去,结点内存没有改变。而iterator就像指向结点的指针,内存没变,指向内存的指针也不会变。

49、hashtable的底层实现

STL中的hashtable使用的是开链法解决hash冲突问题

hashtable中的bucket所维护的list既不是list也不是slist,而是其自己定义的由hashtable_node数据结构组成的linked-list,而bucket聚合体本身使用vector进行存储。hashtable的迭代器只提供前进操作,不提供后退操作

在hashtable设计bucket的数量上,其内置了28个质数[53, 97, 193,…,429496729],在创建hashtable时,会根据存入的元素个数选择大于等于元素个数的质数作为hashtable的容量(vector的长度),其中每个bucket所维护的linked-list长度也等于hashtable的容量。如果插入hashtable的元素个数超过了bucket的容量,就要进行重建table操作,即找出下一个质数,创建新的buckets vector,重新计算元素在新hashtable的位置。

50、set的底层实现为什么不用哈希表而使用红黑树?

      set中元素是经过排序的,红黑树也是有序的而哈希是无序的

       如果只是单纯的查找元素的话,那么肯定要选哈希表了,因为哈希表在的最好查找时间复杂度为O(1),并且如果用到set中那么查找时间复杂度的一直是O(1),因为set中是不允许有元素重复的。而红黑树的查找时间复杂度为O(lgn)

51、hash_map与map的区别?什么时候用hash_map,什么时候用map?

总体来说,hash_map 查找速度会比 map 快,而且查找速度基本和数据数据量大小无关,属于常数级别;而 map 的查找速度是 log(n) 级别。

并不一定常数就比 log(n) 小,hash 还有 hash 函数的耗时,明白了吧,如果你考虑效率,特别是在元素达到一定数量级时,考虑考虑 hash_map。但若你对内存使用特别严格,希望程序尽可能少消耗内存,那么一定要小心,hash_map 可能会让你陷入尴尬,特别是当你的 hash_map 对象特别多时,你就更无法控制了。而且 hash_map 的构造速度较慢。

现在知道如何选择了吗?权衡三个因素: 查找速度, 数据量, 内存使用 。

53、 STL线程不安全的情况

54、内联函数、构造函数、静态成员函数可以是虚函数吗?
不可以

内联函数:在编译时展开,必须有实体;

静态成员函数:是属于这个类的,也必须要有实体

构造函数:如果构造函数也是虚函数,那么它也是存在于虚函数表中,需要通过vptr指针进行查表可得,但构造函数本身都不存在,创建不了实例,class的一些成员函数是不能被访问的(除静态成员函数外)

55、为什么构造函数不能为虚函数?

     虚函数是采用一种虚调用方法,虚调用是一种可以在只有部分信息情况下工作的机制。但是创建一个对象,则需要知道对象的准确类型,因此构造函数不能为虚函数。

56、

56、宏和内联(inline)函数的比较?

1)首先宏是C中引入的一种预处理功能;
2)内联(inline)函数是C++中引用的一个新的关键字;C++中推荐使用内联函数来替代宏代码片段;
3)内联函数将函数体直接扩展到调用内联函数的地方,这样减少了参数压栈,跳转,返回等过程;
4)  由于内联发生在编译阶段,所以内联相较宏,是有参数检查和返回值检查的,因此使用起来更为安全;
5)  需要注意的是, inline会向编译期提出内联请求,但是是否内联由编译期决定(当然可以通过设置编译器,强制使用内联);
6)  由于内联是一种优化方式,在某些情况下,即使没有显示的声明内联,比如定义在class内部的方法,编译器也可能将其作为内联函数。
7)  内联函数不能过于复杂,最初C++限定不能有任何形式的循环,不能有过多的条件判断,不能对函数进行取地址操作等,但是现在的编译器几乎没有什么限制,基本都可以实现内联。

57、返回函数中静态变量的地址会发生什么?

函数 fun 中定义了静态局部变量 var,使得离开该函数的作用域后,该变量不会销毁,返回到主函数中,该变量依然存在,从而使程序得到正确的运行结果。但是,该静态局部变量直到程序运行结束后才销毁,浪费内存空间。

#if 1

int* fun(int tmp) 
{
	static int var = 10;
	var *= tmp;
	return &var;
}

int main() {
	//int* b = fun(5);
	cout <<*fun(5) << endl;
	return 0;
}

/*
运行结果:
50
*/
#endif

58、sizeof(1==1) 在 C 和 C++ 中分别是什么结果?

C 语言代码:

#include<stdio.h>

void main(){
    printf("%d\n", sizeof(1==1));
}

/*
运行结果:
4
*/

C++ 代码:

#include <iostream>
using namespace std;

int main() {
    cout << sizeof(1==1) << endl;
    return 0;
}

/*
1
*/

59、写出int 、bool、 float 、指针变量与 “零值”比较的if 语句 

首先给个提示:题目中要求的是零值比较,而非与0进行比较,在C++里“零值”的范围可就大了,可以是0, 0.0 , FALSE或者“空指针”。

下面是答案。

//int与零值比较 
if ( n == 0 )
if ( n != 0 )

//bool与零值比较 
if   (flag) //   表示flag为真 
if   (!flag) //   表示flag为假 

//float与零值比较 
const float EPSINON = 0.00001;
if ((x >= - EPSINON) && (x <= EPSINON) //其中EPSINON是允许的误差(即精度)。
//指针变量与零值比较 
if (p == NULL)
if (p != NULL)
C++
详细解释

int:int 是整型,可以直接和 0 比较。

bool:根据布尔类型的语义,零值为假(记为FALSE),任何非零值都是真(记为TRUE)。TRUE 的值究竟是什么并没有统一的标准。

例如Visual C++ 将TRUE 定义为1,而Visual Basic 则将TRUE 定义为 -1。所以我们不可以将布尔变量直接与TRUE、FALSE 或者1、0 进行比较

float:千万要留意,无论是float 还是double 类型的变量,都有精度限制,都不可以用==”或!=与任何数字比较,应该设法转化成>=或<=`形式。
其中EPSINON 是允许的误差(即精度)

指针:指针变量的零值就是NULL

60、priority_queue的底层原理

       priority_queue(优先队列)是queue头文件中包含的,优先队列与队列的差别在于优先队列不是按照入队的顺序出队,而是按照队列中元素的优先权顺序出队(默认为大者优先,也可以通过指定算子来指定自己的优先顺序)默认是一个大根堆。

priority_queue的基本操作均与queue相同。

使用:


#if 1  // priority_queue 的使用
#include<queue>  
class T
{
public:
	string  x, y;
	int z;
	T(string  a,string b, int c) :x(a), y(b), z(c)
	{
	}
};
//重写运算符<
bool operator<(const T& t1, const T& t2)
{
	return t1.z < t2.z;
}
int main(void)
{
	priority_queue<T>q;
	q.push(T("梅西", "阿根廷", 6000));
	q.push(T("素雅", "乌拉圭", 4000));
	q.push(T("小内内", "巴西", 8000));
	q.push(T("小白", "西班牙", 3000));
	while (!q.empty())
	{
		T t = q.top();
		q.pop();
		cout << t.x << " " << t.y << " " << t.z << "  万英镑" << endl;
	}
	system("Pause");
	return 1;
	/*
	* 小内内 巴西 8000  万英镑
梅西 阿根廷 6000  万英镑
素雅 乌拉圭 4000  万英镑
小白 西班牙 3000  万英镑
请按任意键继续. . .
	*/
}
#endif 

priority_queue 的实现原理是vector+heap 

priority_queue底层的堆存储结构

简单的理解堆,它在是完全二叉树的基础上,要求树中所有的父节点和子节点之间,都要满足既定的排序规则:

  • 如果排序规则为从大到小排序,则表示堆的完全二叉树中,每个父节点的值都要不小于子节点的值,这种堆通常称为大顶堆;
  • 如果排序规则为从小到大排序,则表示堆的完全二叉树中,每个父节点的值都要不大于子节点的值,这种堆通常称为小顶堆;
  • {10,20,15,30,40,25,35,50,45}

应用:力扣
 

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) 
    {
        // priority_queue<int,vector<int>,greater<int>> q;
        // for(int i =0; i< k; i++)
        // {
        //     q.push(nums[i]);
        // }
        // for(int i = k;i < nums.size();i++){
        //     int t = q.top();
        //     if(nums[i] > t){
        //         q.pop();
        //         q.push(nums[i]);
        //     }
        // }
        // return q.top();

    priority_queue<int>pq;
    int size=nums.size();
    if(k>size)
    {
     return -1;
    }
     for (auto e :nums) 
     {
        pq.push(e);
    }

      while (--k) 
      {
        pq.pop();
      }
     // cout<< pq.top()<<endl;
      return pq.top();
    }
};

61、单例模式

// 第一  将构造私有化
// 第二  增加静态私有的当前类的指针变量
// 第三  提供公开的静态接口
// 
// 可以分为懒汉式和饿汉式

懒汉式-------多线程的时候是线程不安全的

// 1  懒汉式-------多线程的时候是线程不安全的

class Singleton_Lazy {
private :
	Singleton_Lazy()
	{
		cout << "Singleton_Lazy" << endl;
	}
	static  Singleton_Lazy* pSingleton_Lazy;
public:
	static Singleton_Lazy* getInstance() {
		if (pSingleton_Lazy==NULL) {
			pSingleton_Lazy = new Singleton_Lazy();
		}
		return pSingleton_Lazy;
	}
};
Singleton_Lazy* Singleton_Lazy::pSingleton_Lazy =NULL;
//========================================================================================
// 1  懒汉式-------多线程的时候是线程不安全的  线程互斥对象mutex来进行加强。
mutex mu;//线程互斥对象
class Singleton_Lazy_MutiThread
{
private:
	Singleton_Lazy_MutiThread()
	{
		cout << "我是懒汉式,在别人需要我的时候,我才现身。" << endl;
	}
	static Singleton_Lazy_MutiThread* singleton;
public:
	static Singleton_Lazy_MutiThread* getInstance()
	{

		if (NULL == singleton)
		{

			mu.lock();//关闭锁
			if (NULL == singleton)
			{
				singleton = new Singleton_Lazy_MutiThread;
			}
			mu.unlock();//打开锁
		}
		return singleton;
	}
};
Singleton_Lazy_MutiThread* Singleton_Lazy_MutiThread::singleton = NULL;

n_Lazy =NULL;

那如何才能变成线程安全的呢??枷锁


2  恶汉式-------多线程的时候是线程安全的

// 2  恶汉式-------多线程的时候是线程安全的
class Singleton_Hungry {
private:
	Singleton_Hungry()
	{
		cout << "Singleton_Hungry" << endl;
	}
	static  Singleton_Hungry* pSingleton_hungry;
public:
	static Singleton_Hungry* getInstance() {

			return  pSingleton_hungry;
	}
};
Singleton_Hungry* Singleton_Hungry::pSingleton_hungry = new Singleton_Hungry();



void test04() {
	
	cout << "main " << endl;
	Singleton_Lazy *p1=Singleton_Lazy::getInstance();
	Singleton_Lazy* p2 = Singleton_Lazy::getInstance();

	if (p1==p2) 
	{
		cout << "Singleton_Lazy  单例模式" << endl;
	}

	Singleton_Hungry* p3 = Singleton_Hungry::getInstance();
	Singleton_Hungry* p4 = Singleton_Hungry::getInstance();

	if (p3 == p4)
	{
		cout << "Singleton_Hungry 单例模式" << endl;
	}
}
int main() {
	test04();
	return 0;
}

 62、什么是工厂模式?如何实现?应用场景

工厂模式:包括简单工厂模式、抽象工厂模式、工厂方法模式

  • 简单工厂模式:主要用于创建对象。用一个工厂来根据输入的条件产生不同的类,然后根据不同类的虚函数得到不同的结果。
    #include <iostream>
    #include <vector>
    using namespace std;
    
    // Here is the product class
    class Operation
    {
    public:
        int var1, var2;
        virtual double GetResult()
        {
            double res = 0;
            return res;
        }
    };
    
    class Add_Operation : public Operation
    {
    public:
        virtual double GetResult()
        {
            return var1 + var2;
        }
    };
    
    class Sub_Operation : public Operation
    {
    public:
        virtual double GetResult()
        {
            return var1 - var2;
        }
    };
    
    class Mul_Operation : public Operation
    {
    public:
        virtual double GetResult()
        {
            return var1 * var2;
        }
    };
    
    class Div_Operation : public Operation
    {
    public:
        virtual double GetResult()
        {
            return var1 / var2;
        }
    };
    
    // Here is the Factory class
    class Factory
    {
    public:
        static Operation* CreateProduct(char op)
        {
            switch (op)
            {
            case '+':
                return new Add_Operation();
    
            case '-':
                return new Sub_Operation();
    
            case '*':
                return new Mul_Operation();
    
            case '/':
                return new Div_Operation();
    
            default:
                return new Add_Operation();
            }
        }
    };
    
    int main()
    {
        int a, b;
        cin >> a >> b;
        Operation* p = Factory::CreateProduct('+');
        p->var1 = a;
        p->var2 = b;
        cout << p->GetResult() << endl;
    
        p = Factory::CreateProduct('*');
        p->var1 = a;
        p->var2 = b;
        cout << p->GetResult() << endl;
    
        return 0;
    }
    

  • 工厂方法模式:修正了简单工厂模式中不遵守开放封闭原则。把选择判断移到了客户端去实现,如果想添加新功能就不用修改原来的类,直接修改客户端即可。
    #include<iostream>
    #include<vector>
    #include<string>
    using namespace std;
    
    class  AbstructFruits {
    public :
    	virtual  void  AllkindsofFruits() = 0;
    };
    
    
    class  Apple :public AbstructFruits
    {
    public:
    	virtual  void  AllkindsofFruits() {
    		cout << "Apple " << endl;
    
    	}
    };
    
    
    class  Pear :public AbstructFruits
    {
    public:
    	virtual  void  AllkindsofFruits() {
    		cout << "Pear" << endl;
    
    	}
    };
    
    
    class  AbstructFruitsFactory
    {
    public :
    	virtual  AbstructFruits* CreateAllkindsofFruits() = 0;
    };
    class  AppleFactory :public AbstructFruitsFactory
    {
    public:
    	virtual  AbstructFruits* CreateAllkindsofFruits() {
    			return new Apple;
    
    	}
    };
    
    
    class  PearFactory :public AbstructFruitsFactory
    {
    public:
    	virtual  AbstructFruits* CreateAllkindsofFruits() {
    		
    		return new Pear;
    	
    	}
    };
    
    void  test01() {
    
    	AbstructFruitsFactory* factory = NULL;
    
    	AbstructFruits* fruit = NULL;
    
    	// 创建一个苹果工厂
    	factory = new  AppleFactory();
    	fruit = factory->CreateAllkindsofFruits();
    	fruit->AllkindsofFruits();
    	delete  factory;
    	delete fruit;
    
    	// 创建一个鸭梨工厂
    	factory = new  PearFactory();
    	fruit = factory->CreateAllkindsofFruits();
    	fruit->AllkindsofFruits();
    	delete  factory;
    	delete fruit;
    
    
    }
    
    int main() {
    
    
    	test01();
    
    
    	return  0;
    }

  • 抽象工厂模式:定义了一个创建一系列相关或相互依赖的接口,而无需指定他们的具体类。
    #include<iostream>
    #include<vector>
    #include<string>
    using namespace std;
    
    class  AbstructApple {
    public:
    	virtual void ShowName() = 0;
    };
    
    class  ChineseApple :public AbstructApple
    {
    public:
    	virtual void ShowName() {
    		cout << "chinese  apple " << endl;
    	}
    };
    
    class  AmericanApple :public AbstructApple
    {
    public:
    	virtual void ShowName() {
    		cout << "American  apple " << endl;
    	}
    };
    
    class  JapanApple :public AbstructApple
    {
    public:
    	virtual void ShowName() {
    		cout << " Japan  apple " << endl;
    	}
    };
    
    
    
    
    
    class  AbstructPear {
    public:
    	virtual void ShowName() = 0;
    };
    
    class  ChinesePear :public AbstructPear
    {
    public:
    	virtual void ShowName() {
    		cout << "chinese  Pear " << endl;
    	}
    };
    
    class  AmericanPear :public AbstructPear
    {
    public:
    	virtual void ShowName() {
    		cout << "American  Pear " << endl;
    	}
    };
    
    class  JapanPear :public AbstructPear
    {
    public:
    	virtual void ShowName() {
    		cout << " Japan  Pear " << endl;
    	}
    };
    
    
    
    
    class  AbstructBalann {
    public:
    	virtual void ShowName() = 0;
    };
    
    class  ChineseBalann :public AbstructBalann
    {
    public:
    	virtual void ShowName() {
    		cout << "chinese  Balann " << endl;
    	}
    };
    
    class  AmericanBalann :public AbstructBalann
    {
    public:
    	virtual void ShowName() {
    		cout << "American  Balann " << endl;
    	}
    };
    
    class  JapanBalann :public AbstructBalann
    {
    public:
    	virtual void ShowName() {
    		cout << " Japan  Balann " << endl;
    	}
    };
    
    
    
    
    
    class AbstructFactory {
    public :
    	virtual  AbstructApple* CreateAllCountryApple() = 0;
    	virtual  AbstructPear* CreateAllCountryPear() = 0;
    	virtual  AbstructBalann* CreateAllCountryBalann() = 0;
    };
    
    
    
    
    class ChinaFactory :public AbstructFactory {
    public:
    	virtual  AbstructApple* CreateAllCountryApple() {
    		return new ChineseApple;
    	}
    	virtual  AbstructPear* CreateAllCountryPear() {
    		return new ChinesePear();
    	}
    	virtual  AbstructBalann* CreateAllCountryBalann() {
    		return new  ChineseBalann;
    	}
    };
    
    
    
    class JapanFactory :public AbstructFactory {
    public:
    	virtual  AbstructApple* CreateAllCountryApple() {
    		return new JapanApple;
    	}
    	virtual  AbstructPear* CreateAllCountryPear() {
    		return new JapanPear();
    	}
    	virtual  AbstructBalann* CreateAllCountryBalann() {
    		return new  JapanBalann;
    	}
    };
    
    
    
    class AmericanFactory :public AbstructFactory {
    public:
    	virtual  AbstructApple* CreateAllCountryApple() {
    		return new AmericanApple;
    	}
    	virtual  AbstructPear* CreateAllCountryPear() {
    		return  new AmericanPear();
    	}
    	virtual  AbstructBalann* CreateAllCountryBalann() {
    		return new  AmericanBalann;
    	}
    };
    
    
    
    void  test02()
    {
    	AbstructFactory* factory = NULL;
    	AbstructFactory* factory2 = NULL;
    	AbstructFactory* factory3 = NULL;
    	AbstructPear* pear = NULL;
    	AbstructApple* apple = NULL;
    	AbstructBalann  * b = NULL;
    
    	factory = new ChinaFactory();
    	apple =factory->CreateAllCountryApple();
    	apple->ShowName();
    
    
    
    	factory2 = new AmericanFactory();
    	b= factory2->CreateAllCountryBalann();
    	b->ShowName();
    
    
    
    
    	factory3 = new JapanFactory();
    	pear = factory3->CreateAllCountryPear();
    	pear->ShowName();
    }
    
    //int main()
    //{
    //
    //	test02();
    //	return 0;
    //
    //}

    C++ 内存管理

从高地址到低地址,一个程序由 内核空间、栈区、堆区、BSS段、数据段(data)、代码区组成。
常说的C++ 内存分区:栈、堆、全局/静态存储区、常量存储区、代码区。

可执行程序在运行时会多出两个区域:

栈:存放函数的局部变量、函数参数、返回地址等,由编译器自动分配和释放。栈从高地址向低地址增长。是一块连续的空间。栈一般分配几M大小的内存。

堆:动态申请的内存空间,就是由 malloc 分配的内存块,由程序员控制它的分配和释放,如果程序执行结束还没有释放,操作系统会自动回收。堆从低地址向高地址增长。一般可以分配几个G大小的内存。

在堆栈之间有一个 共享区(文件映射区)。

全局区/静态存储区(.BSS 段和 .data 段):存放全局变量和静态变量,程序运行结束操作系统自动释放,在 C 语言中,程序中未初始化的全局变量和静态变量存放在.BSS 段中,已初始化的全局变量和静态变量存放在 .data 段中,C++ 中不再区分了。
常量存储区(.data 段):存放的是常量,不允许修改,程序运行结束自动释放。

代码区(.text 段):存放程序执行代码的一块内存区域。只读,不允许修改,但可以执行。编译后的二进制文件存放在这里。代码段的头部还会包含一些只读的常量,如字符串常量字面值(注意:const变量虽然属于常量,但是本质还是变量,不存储于代码段)
                        
https://blog.csdn.net/XiaoFengsen/article/details/125937918

 

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值