如果基类指针向派生类对象,则删除此指针时,我们希望调用该指针指向的派生类析构函数,而派生类的析构函数又自动调用基类的析构函数,这样整个派生类的对象完全被释放。但是,如果析构函数不被声明成虚函数,则编译器采用的绑定方式是静态绑定,在删除基类指针时,只会调用基类析构函数,而不调用派生类析构函数,这样就会导致基类指针指向的派生类对象析构不完全。
从语法上讲,class和struct做类型定义时只有两点区别:
(一)默认继承权限。如果不明确指定,来自class的继承按照private继承处理,来自struct的继承按照public继承处理;
(二)成员的默认访问权限。class的成员默认是private权限,struct默认是public权限。 除了这两点,class和struct基本就是一个东西。语法上没有任何其它区别。
关键字static的作用是什么:
在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。
在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数,它是一个本地的全局变量。
在模块内,一个被声明为静态的函数只可被这一模块的它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。
简述C,C++程序编译的内存分配情况:
C,C++中内存分配方式可以分为三种:
从静态存储区域分配:内存在程序编译时就已经分配好,这块内存在程序的整个运行期间都存在。速度快,不容易出错,因有系统自行管理。它主要存放静态数据、全局数据和常量。会默认初始化,其他两个不会自动初始化。
在栈上分配:在执行函数时,函数内局部变量的存储单元都在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
从堆上分配:即运态内存分配。程序在运行时候用malloc或new申请任意大小的内存,程序员自己负责在何进用free 和delete释放内存。
new 和 malloc的区别:
malloc/free是C的标准库函数,new/delete是C++的运算符。它们都可用于申请动态内存和释放内存。
当使用一些非内置数据类型的对象时,maloc/free不能自动执行构造函数和析构函数。这是因为malloc/free是库函数,不在编译器控制下。new分配内存时有设定一个标志位,使得delete删除时可以直接删除到标志位,而不需要像free指定删除空间大小。
指针、函数、数组:
这个指针的类型是什么?指针指的类型是什么?该指针指向了哪里?(重点注意)
sizeof(指针) = 4(32位平台) / 8(64位平台)
int *p; p++ //它把指针ptr 的值加上了sizeof(int)就是+4,指向的地址由原来的增加了4个字节(得看指针指向的类型来加相应的字节)
int *p[3] //P 是一个由返回整型数据的指针所组成的数组 ([]的优先级> *) (*p)++数组值+1
int (*p)[3] //P 是一个指向由整型数据组成的数组的指针
int (*p)(int) //P 是一个指向有一个整型参数且返回类型为整型的函数的指针
int *(*p(int))[3] int *(*p(int))[3]; //从P 开始,先与()结合,说明P 是一个函数,然后进入()里面,与int 结合,说明函数有一个整型变量参数,然后再与外面的*结合,说明函数返回的是一个指针,,然后到最外面一层,先与[]结合,说明返回的指针指向的是一个数组,然后再与*结合,说明数组里的元素是指针,然后再与int 结合,说明指针指向的内容是整型数据.所以P 是一个参数为一个整数据且返回一个指向由整型指针变量组成的数组的指针变量的函数
array[3] == *(array+3)
在不同的表达式中数组名array扮演不同的角色。在表达式sizeof(array)中,数组名array 代表数组本身,故这时sizeof 函数测出的是整个数组的大小。
在表达式*array 中,array 扮演的是指针,因此这个表达式的结果就是数组第0 号单元的值。sizeof(*array)测出的是数组单元的大小。
表达式array+n中,array 扮演的是指针,故array+n 的结果是一个指针,它的类型是TYPE *,它指向的类型是TYPE,它指向数组第n号单元。故sizeof(array+n)测出的是指针类型的大小。在32 位程序中结果是4
迭代器失效问题:
对于节点式容器(map,list,forward_list,set)元素的删除,插入操作会导致指向该元素的迭代器,指针,引用失效,但是其他元素迭代器,指针,引用不受影响
对于顺序式容器(vector,string)元素的插入肯定会导致指向该元素以及后面的元素迭代器,指针,引用失效,有可能会导致所有迭代器,指针,引用失效。取决于vector插入时预留的额外存储空间是否够用,需不需要重新分配新的存储空间,把原来存储在旧空间的元素全部复制到新的存储空间然后再插入新的元素,最后撤销旧的存储空间。但删除元素时,尾后迭代器总是失效,被删元素之前的迭代器,引用和指针仍有效
deque 插入到除首尾位置之外的任何位置都会导致迭代器,指针,和引用失效,如果是在首尾位置添加元素,迭代器会失效,但指向存在的元素的引用和指针不会失效。
在首尾之外的任何位置删除元素,那么指向被删除元素外的其他元素的迭代器,引用和指针都会失效,如果删除的是尾元素,尾后迭代器也会失效,但其他迭代器,引用和指针不受到影响,如果是删除首元素。这些也不会受到影响。
拷贝、深拷贝和浅拷贝:
拷贝:
如果l2是l1的拷贝对象,若l1内部的任意数据类型的元素变化,则l2内部的元素也会跟着改变,因为可变类型,值变id不变.
浅拷贝:
如果l2是l1的浅拷贝对象,若l1内的不可变元素发生了改变,l2不变;如果l1内的可变元素(如list)发生了改变,则l2也会跟着改变.
深拷贝:
如果l2是l1的深拷贝对象,若l1内的不可变类型元素发生改变,l2不会变;如果l1内的可变类型元素发生了变化,l2也不会改变,即l2永远不会因为l1的变化而变化.
都是针对可变类型数据而言
深拷贝和浅拷贝有什么区别?
浅拷贝在创建新实例类型时会用到,并保留在新实例中复制的值。浅拷贝用于复制引用指针,就像复制值一样。这些引用指向原始对象,并且在类的任何成员中所做的更改也将影响它的原始副本。浅拷贝允许更快地执行程序,它取决于所使用的数据的大小。
深拷贝用于存储已复制的值。深层复制不会将引用指针复制到对象。它引用一个对象,并存储一些其他对象指向的新对象。原始副本中所做的更改不会影响使用该对象的任何其他副本。由于为每个被调用的对象创建了某些副本,因此深层复制会使程序的执行速度变慢。
指针与引用的区别:
1、指针:一个变量,存储的内容为一个地址;引用:给一个已有对象起的别名
2、指针是一个实体,需要分配内存空间;引用知识变量别名,不需要分配内存空间
3、可以有多级指针,不能有多级引用
4、自增运算结果不一样
5、指针是间接访问,引用是直接访问
6、指针可以不用初始化,引用一定要先初始化
野指针、悬空指针、智能指针
用户态切换到内核态的 3 种方式:系统调用、异常、中断
const与define的区别:
1、编译器处理方式:const:编译时确定其zhi;define:预处理时进行替换
2、类型检查:const:有数据类型,编译时进行数据检查;define:无类型,也不做类型检查
3、内存空间:const:在静态存储区储存,仅此一份;define:在代码段区,每做一次替换就会进行一次拷贝
4、define可以用来防止重复定义,const不行
const关键字有哪些作用:
修饰变量,说明该变量不可以被改变;
修饰指针,分为指向常量的指针和指针常量;指定指针本身为const也可以指定指针所指的数据为const
常量引用,经常用于形参类型,即避免了拷贝,又避免了函数对值的修改;修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
修饰成员函数,说明该成员函数内不能修改成员变量(修饰时是在函数参数列表的后面跟一个const关键字)。指定其返回值为const类型,以使得其返回值不为“左值”
构造函数和析构函数:
构造函数 |
析构函数 |
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。 构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。构造函数可用于为某些成员变量设置初始值,也可以不带参数。 |
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。 析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。 |
拷贝构造函数 |
|
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。 |
|
class Line { public: Line(double len); // 这是构造函数 ~Line(); // 这是析构函数声明 } #构造函数设置初始值 Line::Line( double len) { cout << "Object is being created, length = " << len << endl; length = len; } |
#构造函数使用初始化列表来初始化 Line::Line( double len): length(len) { cout << "Object is being created, length = " << len << endl; } 等同于左边的初始值设置
Line::~Line(void) { cout << "Object is being deleted" << endl; } |
overload是重载,一般是在同一个类中实现若干重载的方法,这些方法的名称相同而参数形式不同。但是不能靠返回类型来判断。
位于同一个类中
函数的名字必须相同
形参列表不同
若一个重载版本的函数面前有virtual修饰,则表示他是虚函数,但他也是属于重载的一个版本
不同的构造函数(无参构造、有参构造、拷贝构造)是重载的应用
重载运算符的本质也是函数的重载
override是重写(覆盖)了一个方法,以实现不同的功能。一般用于子类在继承父类时,重写(覆盖)父类中的方法。函数特征相同,但是具体实现不同。
被重写的函数不能是static的,必须是virtual的
重写函数必须有相同的类型,名称和参数列表
重写函数的访问修饰符可以不同。尽管virtual是private的,派生类中重写改写为public、protect也是可以的
派生类对基类的成员函数重新定义,即派生类定义了某个函数,该函数的名字与基类中函数名字一样。
重定义也叫做隐藏,子类重定义父类中有相同名称的非虚函数(参数可以不同)。如果一个类,存在和父类相同的函数,那么这个类将会覆盖其父类的方法,除非你在调用的时候,强制转换为父类类型,否则试图对子类和父类做类似重载的调用时不能成功的。
不在同一个作用域(分别位于基类、派生类)
函数的名字必须相同
对函数的返回值、形参列表无要求
若派生类定义该函数与基类的成员函数完全一样(返回值、形参列表均相同),且基类的该函数为virtual,则属于派生类重写基类的虚函数
若重新定义了基类中的一个重载函数,则在派生类中,基类中该名字函数(即其他所有重载版本)都会被自动隐藏,包括同名的虚函数
在上段代码中 不使用virtual函数修饰,那么即为重定义。
①引入日志;函数执行时间统计;执行函数前预备处理;执行函数后的清理功能;权限校验等场景;缓存
写一个装饰器:
在函数内再写一个函数,执行父函数时会自动执行子函数,无法单独执行子函数
子函数可以被传递(子函数名后不加括号),加括号就会执行
def use_logging(func):
def wrapper():
logging.warn("%s is running" % func.__name__)
return func()
return wrapper
@use_logging
def foo(): #foo( )需要参数则在wrapper里定义参数,多个参数用*args,关键字参数用*kwrags
print("i am foo")
foo()
from functools import wraps
def logged(func):
@wraps(func)
def with_logging(*args, **kwargs):
print func.__name__ # 输出 'f'
print func.__doc__ # 输出 'does some math'
return func(*args, **kwargs)
return with_logging
@logged
def f(x):
"""does some math"""
return x + x * x
from functools import wraps
class logit(object):
def __init__(self, logfile='out.log'):
self.logfile = logfile
def __call__(self, func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string) # 打开logfile并写入
with open(self.logfile, 'a') as opened_file: # 现在将日志打到指定的文件 opened_file.write(log_string + '\n') # 现在,发送一个通知
self.notify()
return func(*args, **kwargs)
return wrapped_function
def notify(self): # logit只打日志,不做别的
pass
全局解释器锁:
Python代码的执行由Python 虚拟机(也叫解释器主循环,CPython版本)来控制,Python 在设计之初就考虑到要在解释器的主循环中,同时只有一个线程在执行,即在任意时刻,只有一个线程在解释器中运行。对Python 虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。
在多线程环境中,Python 虚拟机按以下方式执行:
1. 设置GIL
2. 切换到一个线程去运行
3. 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
4. 把线程设置为睡眠状态
5. 解锁GIL
6. 再次重复以上所有步骤
在调用外部代码(如C/C++扩展函数)的时候,GIL 将会被锁定,直到这个函数结束为止(由于在这期间没有Python 的字节码被运行,所以不会做线程切换)
函数编程:
filter 函数的功能相当于过滤器 filter(lambda x: x > 5, a)
map函数是对一个序列的每个项依次执行函数 map(lambda x:x*2,[1,2,3])
reduce函数是对一个序列的每个项迭代调用函数 reduce(lambda x,y:x*y,range(1,4))
__new__和__init__的区别:
1. __new__是一个静态方法,而__init__是一个实例方法.
2. __new__方法会返回一个创建的实例,而__init__什么都不返回.
3. 只有在__new__返回一个cls的实例时后面的__init__才能被调用.
4. 当创建一个新实例时调用__new__,初始化一个实例时用__init__.
python 中 yield 的用法:yield简单说来就是一个生成器,这样函数它记住上次返 回时在函数体中的位置。对生成器第 二次(或n 次)调用跳转至该函 次)调用跳转至该函 数。