迭代是Python最强大的功能之一,是访问集合元素的一种方式。
迭代器是一个可以记住遍历的位置的对象。
迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只能往前不会后退。
迭代器有两个基本的方法:iter() 和 next()。
在 Python 中,使用了 yield 的函数被称为生成器(generator)。
跟普通函数不同的是,生成器是一个返回迭代器的函数,只能用于迭代操作,更简单点理解生成器就是一个迭代器。
在调用生成器运行的过程中,每次遇到 yield 时函数会暂停并保存当前所有的运行信息,返回 yield 的值, 并在下一次执行 next() 方法时从当前位置继续运行。
调用一个生成器函数,返回的是一个迭代器对象。
所谓浅拷贝,就是对引用的拷贝,所谓深拷贝,就是对对象的资源的拷贝。
首先,我们对赋值操作要有以下认识:
浅拷贝知识将容器内的元素的地址复制了一份。这可以通过修改后,b中字符串没改变,但是list元素随着a相应改变得到验证。
深拷贝后,a和b的地址以及a和b中的元素地址均不同,这是完全拷贝的一个副本,修改a后,发现b没有发生任何改变,因为b是一个完全的副本,元素地址与a均不同,a修改不影响b。
参考链接
进程是程序的一次执行,线程是进程中的执行单元。
一、全局解释器锁(GIL)
什么是GIL
每个CPU在同一时间只能执行一个线程,那么其他的线程就必须等待该线程的全局解释器,使用权消失后才能使用全局解释器,即使多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。GIL的设计简化了CPython的实现,使的对象模型包括关键的内建类型,如:字典等,都是隐含的,可以并发访问的,锁住全局解释器使得比较容易的实现对多线程的支持,但也损失了多处理器主机的并行计算能力。
全局解释器锁的好处
1)避免了大量的加锁解锁的好处
2)使数据更加安全,解决多线程间的数据完整性和状态同步
全局解释器的缺点
多核处理器退化成单核处理器,只能并发不能并行。
GIL的作用:
多线程情况下必须存在资源的竞争,GIL是为了保证在解释器级别的线程唯一使用共享资源(cpu)。
二、同步锁
同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
三、
Python的闭包与装饰器
一篇文章搞懂Python装饰器所有用法
面试官问我Decorator,我上去就是两个耳刮子
装饰器模板
def decorator(func):
def wrapper_decorator(*args, **kwargs):
#调用前操作
ret_val = func(*args, **kwargs)
#调用后操作
return ret_val
return wrapper_decorator
按照这个模板:
修改装饰器的名字,把decorator替换为具体的名字。
在注释“调用前操作”的地方写自己想写的代码
在注释“调用后操作”的地方写自己想写的代码。
单例模式,是指一个类只能创建一个实例,是最常见的设计模式之一。
比如网站程序有一个类,统计网站的访问人数,这个类只能有一个实例。如果每次访问都创建一个新的实例,那人数就永远是1了。
在python中可以用装饰器实现单例模式。
类装饰器实际上装饰的是类的初始化方法。只有初始化的时候会装饰一次。
用property装饰器创建虚拟的半径属性
用setter装饰器给半径属性添加赋值操作
用classmethod,实现类方法
用static,实现静态方法
import time
from slow import slow
def timer(func):
def wrapper():
start_time = time.perf_counter()
func()
end_time = time.perf_counter()
used_time = end_time - start_time
print(f'{func.__name__} used {used_time}')
return wrapper
@slow
@timer
def run():
print('跑步有利于身体健康,来一圈')
run()
链接
dict,list是可变对象。允许值发生变化,如果对变量进行append,+=操作后, 只是改变了变量值,不会新建一个对象,变量引用的对象的地址不会变化。
str,int,tuple,float是 不可变对象。不允许值发生变化,若改变了变量的值,相当于新建了一个对象,对于相同值的对象,内存中只有一个对象。
https://www.bilibili.com/video/BV1F54114761?p=2
引用计数器为主,标记清除和分代回收为辅。
引用计数器
1.1 环状双向链表refchain
1.2 类型封装结构体
1.3 引用计数器 (为0,回收)
1.4 循环引用问题
标记清除
目的:为了解决引用计数器循环引用的不足
实现:在python底层再维护一个链表,链表中专门放那些可能存在循环引用的对象(list/tuple/dict/set)。在python内部某种情况下触发,回去扫描可能存在循环引用的链表中的每个元素,检查是否有循环引用,如果有,则让双方的引用计数器-1。如果是0则垃圾回收。
问题:1. 什么时候扫描,可能存在循环引用的链表扫描代价大,每次扫描耗时大。
分代回收
将可能存在循环引用的对象维护成3个链表:
3.1:0代:对象个数达到700个扫描一次。
3.2:1代:0代扫描10次,则1代扫描一次。
3.3:2代:1代扫描10次,则2代扫描一次。
缓存机制
4.1 池
4.2 free_list
map() 会根据提供的函数对指定序列做映射。
第一个参数 function 以参数序列中的每一个元素调用 function 函数,返回包含每次 function 函数返回值的新列表。
Python 3.x 返回迭代器。
>>> def square(x) : # 计算平方数
... return x ** 2
...
>>> map(square, [1,2,3,4,5]) # 计算列表各个元素的平方
<map object at 0x100d3d550> # 返回迭代器
>>> list(map(square, [1,2,3,4,5])) # 使用 list() 转换为列表
[1, 4, 9, 16, 25]
>>> list(map(lambda x: x ** 2, [1, 2, 3, 4, 5])) # 使用 lambda 匿名函数
[1, 4, 9, 16, 25]
>>>
reduce(func, seq[, init()])
reduce() 函数即为化简函数,它的执行过程为:每一次迭代,都将上一次的迭代结果(注:第一次为init元素,如果没有指定init则为seq的第一个元素)与下一个元素一同传入二元func函数中去执行。在reduce()函数中,init是可选的,如果指定,则作为第一次迭代的第一个元素使用,如果没有指定,就取seq中的第一个元素。
比如实现阶乘
#未指定init的情况
>>> n = 6
>>> print reduce(lambda x, y: x * y, range(1, n))
120
如果希望得到阶乘的结果再多增加几倍,可以启用init这个可选项。
>>> print reduce(lambda x, y: x * y, range(1, n),2)
240
这个时候,就会将init作为第一个元素,和seq1中的第一个元素1一起传入lambda函数中去执行,返回结果再作为下一次的第一个元素。
filter函数传入一个迭代对象作为参数并将这个迭代对象当中所有那些你不要的东西滤出去。
通常,filter传入一个函数和一个列表。将这个函数作用于列表当中的任意一个元素加入函数返回True,不做任何事情。加入返回False,将这个元素从列表中删除。
x = range(-5, 5)
all_less_than_zero = list(filter(lambda num: num < 0, x))
在命令式的编程范式当中,你通过告诉计算机一系列需要执行的任务,并在计算机执行以后以完成你的目的。当执行任务的时候,状态可能发生改变。
在一个函数式编程范式中,你并不告诉计算机要干什么,而是告诉它是干什么的。什么是一个数字的最大公因子,什么是从1到N的乘积,etc。
链接
单下划线:用来指定变量私有,只有类对象和子类对象能够访问,外部访问需要接口, 不能用import 导入
双下划线:私有成员,只有类对象自己能够访问,子类对象也无法访问。通过 对象名._类名__xxx来访问
foo: python内部的名字,用来区别其他用户自定义的命名,以防止冲突。
python知识点参考链接
1、STL的map底层是用红黑树实现的,查找时间复杂度是log(n);
2、STL的hash_map底层是用hash表存储的,查询时间复杂度是O(1);
3、STL的unordered_map和unordered_set使用的是hash表,unordered系列的关联式容器之所以效率比较高,是因为其底层使用了哈希结构。
STL之queue及其底层实现——list模拟
STL之priority_queue底层实现——优先级队列默认使用vector作为其底层存储数据的容器,在vector上又使用了堆算法将vector中元素构造成堆的结构
哈希的概念
红黑树的概念
1.指针和引用的定义和性质区别:
(1)指针:指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用跟原来
的变量实质上是同一个东西,只不过是原变量的一个别名而已。如:
int a=1;int *p=&a;
int a=1;int &b=a;
上面定义了一个整形变量和一个指针变量p,该指针变量指向a的存储单元,即p的值是a存储单元的地址。
而下面2句定义了一个整形变量a和这个整形a的引用b,事实上a和b是同一个东西,在内存占有同一个存储单元。
(2)引用不可以为空,当被创建的时候,必须初始化,而指针可以是空值,可以在任何时候被初始化。
(3)指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
参考
可以使用全局变量达到共享数据的目的,例如在一个程序文件中有多个函数,没一个函数都可以改变全局变量的值,但安全性得不到保证。如果想要在同类的多个对象之间实现数据共享,可以使用静态成员变量。static成员变量必须先初始化才能使用。static成员变量既可以通过对象来访问,也可以通过类来访问。static成员变量不随着对象的创建而分配内存,也不随着对象的销毁而释放内存。普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
普通成员函数可以访问所有成员变量,而静态成员函数只能访问静态成员变量。静态成员函数没有this指针。
静态方法是在类中使用staitc修饰的方法,在类定义的时候已经被装载和分配。而非静态方法是不加static关键字的方法,在类定义时没有占用内存,只有在类被实例化成对象时,对象调用该方法才被分配内存。
静态方法只能访问静态方法和静态成员。
非静态方法要被实例化才能被静态方法调用。
置于变量或者函数前: 表明该变量或者函数定义在别的文件中
mutable 只能用来修饰类的数据成员
在保护继承方式中,基类的公有成员和保护成员被派生类继承后变成派生类的保护成员,而基类的私有成员在派生类中不能访问。因为基类的公有成员和保护成员在派生类中都成了保护成员,所以派生类的新增成员可以直接访问基类的公有成员和保护成员,而派生类的对象不能访问它们,上一讲鸡啄米说过,类的对象也是处于类外的,不能访问类的保护成员。对基类的私有成员,派生类的新增成员函数和派生类对象都不能访问。
假设A类是基类,B类是从A类继承的派生类,A类中有保护成员,则对派生类B来说,A类中的保护成员和公有成员的访问权限是一样的。而对A类的对象的使用者来说,A类中的保护成员和私有成员都一样不能访问。可见类中的保护成员可以被派生类访问,但是不能被类的外部对象(包括该类的对象、一般函数、其他类等)访问。我们可以利用保护成员的这个特性,在软件开发中充分考虑数据隐藏和共享的结合,很好的实现代码的复用性和扩展性。
回调函数:一个通过函数指针调用的函数就叫做回调函数。
作用:对特定的事件或条件进行响应。
int (f) (int x); / 声明一个函数指针 */
f=func; /* 将func函数的首地址赋给指针f */
2. 指针函数即返回指针的函数,即本质是一个函数,函数的返回类型是某一类型的指针,如 int *f(x,y);
如果被拷贝内容是地址,也就是当前变量是个指针,指向另一片存储空间,那么深拷贝是连同地址和地址处的内容一并拷贝。
是针对指针的。
浅拷贝:只拷贝指针地址,意思是浅拷贝指针都指向同一个内存空间,当原指针地址所指空间被释放,那么浅拷贝的指针全部失效,形成悬挂指针。默认拷贝构造函数执行的是浅拷贝。
深拷贝:先申请一块跟拷贝数据一样大的内存空间,把数据复制过去。这样拷贝多次,就有多个不同的内存空间,互不干扰。
深拷贝和浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。
参考链接
标准教科书答案:const int * p:const右边接近于int这个类型声明,意思是有个指针p,指向的是一个int型的整数常量。即p可变,*p不可变。
int * const p表明指针变量p是const型,它的指向不可修改,但是指向的对象可以修改。
全局变量的声明之前,再冠以static就构成了静态的全局变量。两者的区别在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其他源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其他源文件中引起错误。
参考链接
对象作为函数参数时有三种情况
struct A{
A();
~A();
string name;
string sex;
}
struct B:public A{
B();
~B();
string addr;
string job;
}
bool getPerson(B b)
假如我们在调用getPerson函数的时候对B进行的是值传递,那么在函数内要对B调用一次copy构造函数,对其两个成员 addr,job也要初始化,又因为B继承自A,所以也要负责初始化A,以及A的两个成员name,sex,不知不觉进行了6次构造动作,然后在函数结束的时候,又会对这构造的6个对象分别进行了6次销毁动作!
我以前简单的以为值传递只比引用传递多了一个构造操作,今天读完effective c++第20条,才认识到多出来的是这么复杂的行为。
这里推荐使用第二种 第一种应用起来只能修改形参的值不能同时修改函数外的被调用对象的值
参考链接
TCP采用三次握手建立一个连接,采用四次挥手来关闭一个连接。
TCP是面向连接的、可靠的、基于字节流的传输层通信协议。
连接
服务对象
可靠性
拥塞控制、流量控制
首部开销
传输方式
分片不同
应用场景不同
由于TCP是面向连接,能保证数据的可靠性交付,因此经常用于:
由于UDP面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于
答:
(1)进程是程序的一次执行,线程是进程中的执行单元。计算机只能在同一个时间处理一个线程,多核可以避免,将任务分给不同的cpu核,避免多线程的劣势。
(2)进程间是独立的,这表现在内存空间、上下文环境上,线程运行在进程中;
(3)一般来讲,进程无法突破进程边界存取其他进程内的存储空间;而同一进程所产生的线程共享内存空间。(相当于车间的空间是工人们共享的,工人就是线程,车间就是进程)
(4)同一进程中的两段代码不能同时执行,除非引入多线程。
漫画进程与线程
线程之间的锁有:互斥锁、条件锁、自旋锁、读写锁。递归锁。锁的功能越强大,性能就越低。
当发生阻塞时,互斥锁可以让CPU去处理其他的任务;而自旋锁让CPU一直不断循环请求获取这个锁。通过两个含义的对比可以我们知道“自旋锁”是比较耗费CPU的
自旋锁适合于短时间的,轻量级的加锁机制。
参考链接
std::lock_guard在构造时只被锁定一次,并且在销毁时解锁。
参考链接
C++中的内存泄漏是怎么发生的?
如何避免C++中发生内存泄漏?
C++语言中的继承体系。
C++语言中的多态。
多态的实现主要分为静态多态和动态多态,静态多态主要是重载,在编译的时候就已经确定;动态多态是用虚函数机制实现的,在运行期间动态绑定。
在用父类指针调用函数时,实际调用的是指针指向的实际类型(子类)的成员函数。
当使用类的指针调用成员函数时,普通函数由指针类型决定,而虚函数由指针指向的实际类型决定。
https://blog.csdn.net/wanghao109/article/details/11483373
确认进程启动过程中,动态库加载的时间与用户代码响应时间。如果是动态库加载时间过长,考虑进程的动态库优化;如果是用户代码响应时间过长,考虑代码优化。(就出现了一次)
虚函数的实现:在有虚函数的类中,类的最开始部分是一个虚函数表的指针,这个指针指向一个虚函数表,表中放了虚函数的地址,实际的虚函数在代码段中。当子类继承了父类的时候,也会继承其虚函数表,当子类重写父类中虚函数时候,会将其继承到的虚函数表中的地址替换为重新写的函数地址。使用了虚函数,会增加访问内存开销,降低效率。
相关面试题
https://blog.csdn.net/weixin_36299192/article/details/88415452
https://juejin.cn/post/6844903991780835342
C++14 智能指针unique_ptr、shared_ptr、weak_ptr
shared_ptr和unique_ptr之间的区别在于:shared_ptr是引用计数的智能指针,而unique_ptr不是。这意味着,可以有多个shared_ptr实例指向同一块动态分配的内存,当最后一个shared_ptr离开作用域时,才会释放这块内存。另一方面,unique_ptr意味着所有权。单个unique_ptr离开作用域时,会立即释放底层内存。
智能指针
智能指针可用于动态资源管理,资源分配即初始化,定义一个类来封装资源的分配和释放,在构造函数中完成资源的分配和初始化,在析构函数完成资源的清理,可以保证资源的正确初始化和释放。
智能指针的原理:智能指针是一个类,这个类的构造函数中传入一个普通指针,析构函数中释放传入的指针。智能指针的类是栈上的对象,智能指针指向堆上开辟的空间,函数结束时,栈上的函数会自动被释放,智能指针指向的内存也会随之消失,防止内存泄露。
详情看另一篇博客(侯捷老师讲的太好了)
http://c.biancheng.net/view/6901.html
2. 哈希
https://blog.csdn.net/fjhugjkdsd/article/details/108091424
3. unordered_map与unordered_set的底层实现
所有无序容器的底层实现都是采用的哈希表存储结构,用“链地址法”解决数据存储位置发生冲突的哈希表。
4. map和set
20道必须掌握的C++面试题
问13:指针和引用的区别?
答:
指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元;而引用仅是个别名;
引用使用时无需解引用(*),指针需要解引用;
引用只能在定义时被初始化一次,之后不可变;指针可变;
引用没有 const,指针有 const;
引用不能为空,指针可以为空;
“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
指针和引用的自增(++)运算意义不一样;
指针可以有多级,但是引用只能是一级(int **p;合法 而 int &&a是不合法的)
9.从内存分配上看:程序为指针变量分配内存区域,而引用不需要分配内存区域。
60道30K+C++工程师面试必问面试题
c++面试题大全
c++面试题总结
c++面试题总结一
c++面试题总结二
c++经典面试题
c++经典面试50题