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
    评论
C、C++语言是IT行业的主流编程语言,也是很多程序员必备的软件基本功,是软件开发行业招聘考查的重点。本书以流行的面试题讲解为主要内容,介绍了C、C++语言基本概念,包括保留字、字符串、指针和引用、结构体、库函数等各个方面的基础知识,介绍了面向对象编程基本概念,包括如何实现继承、多态和封装等。还介绍了排序算法及数据结构的实现,包括链表、堆栈、队列和树。此外,本书开始用两章篇幅详细介绍了中英文面试的注意事项、常见问题及程序员的职业规划等软件工程师的常识。最后四章详细讲解了现在流行的智力测试题。 第一篇 求职 第1章 应聘求职 1.1 企业与人才 1.1.1 企业需要什么样的人才 1.1.2 如何成为企业需要的人才 1.2 做好面试的准备 1.2.1 面试衣着 1.2.2 简历 1.3 面试 1.3.1 面试注意事项 1.3.2 面试问题分析 问题一:“请自我介绍一下。” 问题二:“谈谈你的家庭情况。” 问题三:“你有什么业余爱好?” 问题四:“你最崇拜谁?” 问题五:“谈谈你的缺点。” 问题六:“谈一谈你的一次失败经历。” 问题七:“你为什么选择我们公司?” 问题八:“对这项工作,你可预见到哪些困难?” 问题九:“如果我们录用你,你将怎样开展工作?” 问题十:“与上级意见不一致,你将怎么办?” 问题十一:“我们为什么要录用你?” 问题十二:“你能为我们做什么?” 问题十三:“你是应届毕业生,缺乏经验,如何能胜任这项工作?” 问题十四:“你希望与什么样的上级共事?” 问题十五:“你在前一家公司的离职原因是什么?” 1.4 为明天做好计划 第2章 英文面试 2.1 英文电话面试 2.1.1 英文电话面试注意事项 2.1.2 英文电话面试常见   问题 问题一:When will you graduate? 问题二:How do you normally handle criticism? 问题三:Why should we hire you? 问题四:Please tell me something unreflected at your resume/about yourself/ your experience/your activities. 2.1.3 英文电话面试常用词汇 2.2 英文面试 2.2.1 英文简历 2.2.2 英文面试流程 2.2.3 英文面试注意事项 2.2.4 英文面试常见问题 问题一:What is your strongest trait? 问题二:How would your friends or colleagues describe you? 问题三:What personality traits do you admire? 问题四:What leadership qualities did you develop as an administrative personnel? 问题五:How do you normally handle criticism? 问题六:What do you find frustrating in a work situation? 问题七:How do you handle your failure? 问题八:What kinds of people do you like to work with? 2.2.5 英文面试常用词汇 2.3 计算机专业英语面试常用词汇 第二篇 C/C++面试题 第3章 C/C++程序基础 3.1 基本概念 面试题1:什么是C语言语句 面试题2:变量的声明和定义有什么区别 面试题3:下列字符中,哪些不是C语言关键字 面试题4:下列变量定义中,哪些是合法的 面试题5:如何以最简单的方式让电脑蜂鸣器发出声音 3.2 编程规范 面试题6:谈谈你对编程规范的理解或认识 面试题7:函数、变量等命名都有哪些规则 面试题8:写出bool、int、float、指针变量与“零值”比较的if语句 3.3 数据类型 面试题9:写出代码的输出结果 面试题10:C语言中不合法的整型常数 面试题11:short i = 0; i = i + 1L;这两句有错吗 面试题12:char x[] = {"abcd"}和 char y[] = {'a','b','c','d'} 有不同吗 面试题13:char型数据在内存中的存储形式 3.4 运算符 面试题14:请写出下列代码的输出内容 面试题15:运算符的优先级问题 面试题16:&&和&,||和|有什么区别 面试题17:什么是左值,什么是右值 面试题18:请写出程序的运行结果 面试题19:sizeof和strlen的区别 3.5 结构体 面试题20:结构体是什么样的数据类型 面试题21:结构体可以直接赋值吗 面试题22:组织WAV文件头,并解析WAV格式的各项信息 面试题23:计算学生不及格的人数打印他们的性别、姓名和成绩 面试题24:结构体内存对齐问题 3.6 C和C++的区别 面试题25:关键字static在C和C++中的区别 面试题26:C语言的结构体和C++的有什么区别 面试题27:C中的malloc和C++中的new有什么区别 面试题28:C++的引用和C语言的指针有什么区别 第4章 预处理、保留字 4.1 预处理 面试题1:简述#ifdef、#else、#endif和#ifndef的作用 面试题2:宏定义和函数 面试题3:用#define声明一个常数 面试题4:写一个“标准”宏MIN 面试题5:typedef和define有什么区别 面试题6:#define CHAR char*和typedef char* CHAR各有什么优劣 面试题7:谈谈你对typedef的认识 4.2 const(常量) 面试题8:关键字const是什么 面试题9:说明以下a声明的含义 面试题10:const、define定义常量的区别 4.3 static(静态)和extern 面试题11:static有什么作用 面试题12:extern有什么作用 面试题13:简述变量存储类型 4.4 volatile 面试题14:volatile有什么作用 面试题15:一个参数可以既是const又是volatile吗 面试题16:一个指针可以是volatile吗 第5章 引用和指针 5.1 引用 面试题1:什么是引用 面试题2:常引用有什么作用 面试题3:流操作符重载为什么返回引用 5.2 指针 面试题4:说明以下声明的含义 面试题5:简述指针常量与常量指针区别 面试题6:写出以下代码的输出结果 面试题7:找出代码的错误 5.3 指针和数组 面试题8:写出代码的输出结果 面试题9:请问这段程序有问题吗 面试题10:a和&a有什么区别 面试题11:请问代码有什么问题 面试题12:数组名和指针的区别 5.4 函数指针 面试题13:请解析(*(void (*)())0)()的含义 面试题14:指出程序的错误 5.5 “野指针” 面试题15:如何避免“野指针” 面试题16:程序是否正确 面试题17:指出程序的错误 5.6 动态内存 面试题18:简述C、C++程序编译的内存分配情况 面试题19:以下四段代码中哪段没有错误 第6章 字符串 6.1 数字字符串 面试题1:编码实现数字转化为字符串 面试题2:编码实现字符串转化为数字 6.2 字符串函数 面试题3:编写一个标准strcpy函数 面试题4:简述strcpy、sprintf与memcpy的区别 6.3 字符串与数组 面试题5:找出程序的错误之处 面试题6:判断程序会出现什么问题 第7章 嵌入式编程 面试题1:编码实现某一变量某位清或置 面试题2:用C编写一个死循环程序 面试题3:用变量a给出下面的定义 面试题4:设置地址为0x67a9的整型变量的值为0xaa66 面试题5:评论下面这个中断函数 面试题6:评价一个代码片段 第8章 面向对象 8.1 面向对象的基本概念 面试题1:谈谈你对面向对象的认识 面试题2:面向对象的三大特征 面试题3:面向过程和面向对象有什么区别 8.2 类的成员变量和成员函数 面试题4:简述类public、protected、private的作用 面试题5:写出代码的打印结果 面试题6:写出程序的打印结果 面试题7:C++的空类有哪些成员函数 8.3 构造函数和析构函数 面试题8:构造函数能否为虚函数 面试题9:简述子类与父类的析构、构造函数的调用顺序 面试题10:编写类String 的构造函数、析构函数和赋值函数 8.4 拷贝构造函数 面试题11:谈谈对拷贝构造函数和赋值运算符的认识 面试题12:写出当定义#define _INMAIN 0和不定义时代码打印结果 第9章 继承与多态 9.1 继承 面试题1:指出程序的错误 面试题2:用C++设计一个不能被继承的类 9.2 虚函数和纯虚函数 面试题3:下面说法中正确的是哪个 面试题4:写出程序的打印结果 面试题5:访问基类的私有虚函数 9.3 多态 面试题6:简述类成员函数的重写、重载和隐藏的区别 面试题7:简述多态实现的原理 第10章 数据结构 10.1 链表 面试题1:链表和数组有什么区别 面试题2:寻找单链表中间结点 面试题3:怎样把一个单链表反序 10.2 单循环链表 面试题4:根据需求建立一个单向循环链表 面试题5:检测一个较大的单向链表是否带环 10.3 双向链表 面试题6:按要求构造一个双向链表 面试题7:编程实现双链表插入新结点 面试题8:编程实现双链表删除指定结点 10.4 栈和队列 面试题9:简述队列和栈的异同 面试题10:建立一个链式栈 面试题11:建立一个链式队列 面试题12:能否用两个栈实现一个队列的功能 10.5 二叉树 面试题13:建立一个二叉树 面试题14:计算一棵二叉树的深度 面试题15:在二元树中找出和为某一值的所有路径 第11章 排序 11.1 插入排序 面试题1:编码实现直接插入排序 面试题2:编码实现希尔(Shell)排序 11.2 交换排序 面试题3:编码实现冒泡排序 面试题4:编码实现快速排序 11.3 选择排序 面试题5:编码实现直接选择排序 面试题6:编程实现堆排序 11.4 基数排序 面试题7:编程实现基数排序 第三篇 智力测试 第12章 基本方法 面试题1:斯密斯夫妇握手问题 面试题2:5个强盗分100颗宝石 面试题3:分牛 面试题4:谁在说谎 面试题5:是亏了还是赚了 面试题6:小虫分裂问题 面试题7:飞机绕地球环行问题 第13章 数学能力 面试题1:用一笔画出经过9个点的4条直线 面试题2:在9个点上画10条线 面试题3:100盏灯 面试题4:找出不同的球 面试题5:时针、分针和秒针重合问题 面试题6:可以喝多少瓶汽水 面试题7:怎样拿到第100号球 面试题8:烧绳计时 面试题9:分金条 面试题10:至少有多少人及格 面试题11:如何取3升水 面试题12:将16升水平均分给四个人 面试题13:如何将140克的盐分成50、90克各一份 面试题14:蜗牛几天能爬到井口 面试题15:100美元的差额到哪里去了 面试题16:点击鼠标比赛 面试题17:小猴最多能运回多少根香蕉 面试题18:算出小张买了几瓶啤酒、几瓶饮料 面试题19:牧场有多少匹马 面试题20:找出不同的苹果 面试题21:如何穿越沙漠 第14章 推理能力 面试题1:怎么少了100元 面试题2:村里有多少条病狗 面试题3:他们都在做什么 面试题4:躯体与灵魂 面试题5:小明一家能否安全过桥 面试题6:过河   问题 面试题7:这是张什么牌 面试题8:说谎岛上的两个部落 面试题9:谁是特尔斐城的预言家 面试题10:哪个政党获胜 面试题11:每个护士星期几休息 面试题12:每个人系的圆牌都是什么颜色的 面试题13:帽子问题 面试题14:谁是凶手 面试题15:他们的头发是什么颜色的 面试题16:谁是漂亮的青年 面试题17:哪个袋子里有金子 面试题18:他们星期几在说谎 面试题19:剩下的是什么牌 面试题20:老李的儿子们是做什么的 面试题21:史密斯家的门牌号 面试题22:尤克利地区的电话 面试题23:乡村庙会的15点游戏 面试题24:各家的孩子得了第几名 面试题25:经理应该带谁出差 面试题26:法官的判决 面试题27:张老师的生日是哪一天 面试题28:谁是M小姐的情人 面试题29:他们分别是哪国人 面试题30:他们分别是做什么的 面试题31:他们都会说什么语言 面试题32:怎么把马匹从甲村拉到乙村 面试题33:谁打碎了花瓶 面试题34:分机票 面试题35:石头有多重 面试题36:该释放谁 面试题37:谁打碎的玻璃 面试题38:谁是最优秀的医生 面试题39:今天星期几 面试题40:五个人进行汽车竞赛 面试题41:下一行是什么 面试题42:三筐水果各是什么 面试题43:最后剩下的是谁 第15章 反应能力 面试题1:下水道的井盖 面试题2:30秒答题 面试题3:一分钟答题 面试题4:镜子中的你 面试题5:埃及古币 面试题6:投硬币 面试题7:他在撒谎吗 面试题8:制造零件 面试题9:不喜欢正方形窗户的人 面试题10:孩子租房 面试题11:重男轻女的国度 面试题12:分遗产 面试题13:栽果树 面试题14:聪明的农民 面试题15:聪明的死刑犯 面试题16:幼儿园中奇怪的人 面试题17:奇怪的城镇 面试题18:聪明的商人 面试题19:渡船过河 面试题20:愚蠢的长工 面试题21:红球和白球 面试题22:小明坐在了哪里 面试题23:乌龟赛跑 面试题24:老师的爱恋 面试题25:爬楼梯 面试题26:马丁先生的约会 面试题27:巧入房间 面试题28:管子中的球 面试题29:女儿的错

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值