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的特点
- set 和multiset 会进行排序,只是multimap 的元素可以重复,但是set中元素不允许重复
-
map和multimap 是<k,v>,是根据k 来排序,只是multimap的k可以重复,但是map中k不允许重复
红黑树的特性:
- 每个结点或是红色或是黑色;
-
根结点是黑色;
-
每个叶结点是黑的;
-
如果一个结点是红的,则它的两个儿子均是黑色;
-
每个结点到其子孙结点的所有路径上包含相同数目的黑色结点。
为何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