1.函数调用的压栈过程
入栈过程:函数参数从右向左入栈,函数运行状态入栈,函数返回地址入栈,代码区跳转(从当前代码区跳转到调用的函数入口)
出栈过程:保存返回值,弹出返回地址,返回值,弹出参数,跳转到原始地址
https://blog.csdn.net/u011555996/article/details/70211315
2.创建只有在堆/栈上生成的类
只能在堆上生成对象的类:
方案1:构造函数私有化
- 不能让这个类在栈上创建, 由于在栈上创建对象要直接调用构造函数, 如果我们把构造函数私有化, 就 无法在栈上创建对象了
- 那么我们又如何在堆上创建对象呢, 由于创建对象必定要调用构造函数, 在我们不定义其他构造函数时, 我们已经将两个默认构造函数已经私有, 在类外肯定是调用不到构造函数, 我们只有定义一个公有的静态成员函数 ,在其内部用new在堆区创建对象并返回其指针, (这里有很难以理解的一点, 在静态成员函数中用new 创建一个对象时, 也会调用构造函数, 我们知道, 静态成员函数不能调用成员函数, 那么new是如何调到构造函数的呢? 这得从静态成员函数为什么不能访问成员函数说起, 每一个对象都有一个隐含的this指针, 访问成员函数实际上时通过this指针调用的, 而在构造函数调用前还没有实例化出对象, 也就没有this指针, 所以构造函数不需要this指针调用, 静态成员函数也就可以调用构造函数了), 这点解释通了
#pragma once
class T1 {
T1(int val):b(val) {
}
T1(T1& x):b(x.b){
}
public:
int b;
static T1* newT1_p(int val = 0) {
return new T1(val);
}
static T1& newT1(int val = 0) {
return *new T1(val);
}
};
方案2:析构函数私有化
将析构函数私有化, 在栈上也就不能直接创建对象了, 因为编译器在编译时会进行检测, 那没有析构函数也是不行的, 我们还需要实现一个函数来调用私有的析构函数, (这个思路就比构造函数私有好理解多了)
class T2 {
~T2() {
delete this;
}
public:
int b;
T2(int val = 0) :b(val) {
}
void Destroy() {
this->~T2();
}
};
只能在栈上生成的类
由于使用new进行申请会在堆上生成新类,因此将operator new 重载为私有函数即可。
class T3 {
void* operator new(size_t val) {}
public:
int a;
T3(int val = 0) :a(val) {
}
};
#include<iostream>
using namespace std;
int main() {
T3 a;
cout << a.a << endl;
T3 b(10);
cout << b.a << endl;
system("pause");
return 0;
}
https://blog.csdn.net/qq_41071068/article/details/102696312
3.排序算法时间复杂度
4.面向对象的三大特性,五大准则
三大特性:
封装:将具体实现细节封装起来,留下外部接口供调用,保证安全性
继承:子类对父类属性和方法进行继承,减少了在内容扩展时的重构复杂度
多态:子类可对父类的属性和方法进行改写
五大原则:
单一职责原则:每一个类只有一个引起其变化的变量
开放闭合原则:类对扩展开放,对修改封闭,保证在进行功能变更时不改变原有代码结构
里氏替换原则:所有存在父类的位置均可以使用子类进行替代
依赖倒置原则:高层次模块不应以来低层次模块,两者都应该依赖于抽象。
接口隔离原则:接口应小而完备,不应存在过多无用的属性和方法
5.x=x+1,x+=1,x++效率
x=x+1:1:读取右x的地址->2.x+1.做加1操作->3.读取左x的地址。->4.将右值传给左边的x(编译器并不认为左右x的地址相同)
x+=1:1:读取x的地址->2.x+1.做加1操作->3.将右值传给的x
x++:1.读取x地址->2.x自增
6.static和extern的区别
static是对变量进行定义,同时对其进行初始化;extern只是对变量或函数进行声明而非定义,编译时不会检查extern声明的对象,但是链接过程中如果未找到则会报错。
7.strcpy的风险
1.如果要拷贝的字符串长度大于原始字符串长度,则会发生越界
2.拷贝数组的时候结尾未出现‘/0’,则会越界复制
3.当拷贝的字符串是自身的一部分的时候可能会出错
strcpy返回值是拷贝后指针,增加灵活性。
8.DLL HELL
还未看
https://blog.csdn.net/qwertyupoiuytr/article/details/53999586
9.当拷贝构造函数使用值传递时会有什么错误
使用值传递的时候会调用拷贝构造函数生成一个该类的副本,但是拷贝构造函数使用值传递,会套娃,所以拷贝构造函数必须使用引用传递。
10.virtual和static为什么不能同时作用于一个函数
1.static成员不属于任何类的对象,而是由类公用,因此加上virtual无意义
2.虚函数的调用关系:this -> vptr -> vtable ->virtual function。由于static函数没有this指针,因此无法进行虚函数访问。
11.vptr生成时间:
在基类构造函数之后,自身构造函数或初始化列表之前
构造函数调用虚函数:
派生类对象构造期间进入基类的构造函数时,对象类型变成了基类类型,而不是派生类类型。
同样,进入基类析构函数时,对象也是基类类型。
12.红黑树较AVL树的优点
黑树的查询性能略微逊色于AVL树,因为其比AVL树会稍微不平衡最多一层,也就是说红黑树的查询性能只比相同内容的AVL树最多多一次比较,但是,红黑树在插入和删除上优于AVL树,AVL树每次插入删除会进行大量的平衡度计算,而红黑树为了维持红黑性质所做的红黑变换和旋转的开销,相较于AVL树为了维持平衡的开销要小得多
搜索次数多:AVL;查找次数多:红黑树
13.空类大小为什么是1
保证每个实例都有一个独特的地址,不会产生重复,编译器会给空类添加隐藏字节。
14.迭代器失效的情况及处理方法
顺序式容器:在进行删除操作后,删除节点之后的迭代器会失效,在删除之前将迭代器与初始位置的相对偏移记录,后续再做修改
关联式容器/链表式容器:只会使删除节点的迭代器失效,删除前做移动即可
15.同步异步,阻塞非阻塞
同步:主动等消息
异步:等人发消息
阻塞:等返回消息,返回前挂起
非阻塞:获得返回消息前先做别的事情
并发:一段时间内多个线程同时运行,但是同一时刻只能有一个线程
并行:平行线,多个线程同时运行
16.ping的过程
首先通过主机IP/掩码/目的主机IP检查是否在同一网段内
17.C++内存泄漏的几种情况
1.类的构造和析构函数中没有正确调用匹配的new和delete运算符
2.没有正确清除嵌套的对象指针
3.在释放对象数组时在delete中没有使用方括号
4.指向对象的指针数组不等同于对象数组
5.缺少拷贝构造函数
6.缺少重载赋值运算符
7.返回局部对象的引用或指针(野指针)
8.没有将基类的析构函数定义为虚函数
18.int fun() 和 int fun(void)的区别?
这里考察的是c 中的默认类型机制。
在c中,int fun() 会解读为返回值为int(即使前面没有int,也是如此,但是在c++中如果没有返回类型将报错),输入类型和个数没有限制, 而int fun(void)则限制输入类型为一个void。
在c++下,这两种情况都会解读为返回int类型,输入void类型。
19. 在C中用const 能定义真正意义上的常量吗?C++中的const呢?
不能。c中的const仅仅是从编译层来限定,不允许对const 变量进行赋值操作,在运行期是无效的,所以并非是真正的常量(比如通过指针对const变量是可以修改值的),但是c++中是有区别的,c++在编译时会把const常量加入符号表,以后(仍然在编译期)遇到这个变量会从符号表中查找,所以在C++中是不可能修改到const变量的。
补充:
1). c中的局部const常量存储在栈空间,全局const常量存在只读存储区,所以全局const常量也是无法修改的,它是一个只读变量。
2). 这里需要说明的是,常量并非仅仅是不可修改,而是相对于变量,它的值在编译期已经决定,而不是在运行时决定。
3).c++中的const 和宏定义是有区别的,宏是在预编译期直接进行文本替换,而const发生在编译期,是可以进行类型检查和作用域检查的。
4).c语言中只有enum可以实现真正的常量。
5). c++中只有用字面量初始化的const常量会被加入符号表,而变量初始化的const常量依然只是只读变量。
6). c++中const成员为只读变量,可以通过指针修改const成员的值,另外const成员变量只能在初始化列表中进行初始化。
下面我们通过代码来看看区别。
同样一段代码,在c编译器下,打印结果为*pa = 4, 4
在c++编译下打印的结果为 *pa = 4, 8
int main(void)
{
const int a = 8;
int *pa = (int *)&a;
*pa = 4;
printf("*pa = %d, a = %d", *pa, a);
return 0;
}
20.模板函数和模板类的特例化
引入的原因:编写单一的模板,它能适应大众化,使每种类型都具有相同的功能,但对于某种特定类型,如果要实现其特有的功能,单一模板就无法做到,这时就需要模板特例化。
定义:是对单一模板提供的一个特殊实例,它将一个或多个模板参数绑定到特定的类型或值上。
函数模板特例化:必须为原函数模板的每个模板参数都提供实参,且使用关键字template后跟一个空尖括号对<>,表明将原模板的所有模板参数提供实参。
template<typename T> //函数模板
int compare(const T &v1,const T &v2)
{
if(v1 > v2) return -1;
if(v2 > v1) return 1;
return 0;
}
//模板特例化,满足针对字符串特定的比较,要提供所有实参,这里只有一个T
template<>
int compare(const char const &v1,const char const &v2)
{
return strcmp(p1,p2);
}
模板类特例化:原理类似函数模板,不过在类中,我们可以对模板进行特例化,也可以对类进行部分特例化。对类进行特例化时,仍然用template<>表示是一个特例化版本,例如:
template<typename T>class Foo
{
void Bar();
void Barst(T a)();
};
template<>
void Foo<int>::Bar()
{
//进行int类型的特例化处理
}
21.shared_ptr实现
#include <memory>
#include <iostream>
using namespace std;
template<typename T>
class smart{
private:
T* _ptr;
int* _count; //reference counting
public:
//构造函数
smart(T* ptr = nullptr):_ptr(ptr){
if (_ptr){
_count = new int(1);
}
else{
_count = new int(0);
}
}
//拷贝构造
smart(const smart& ptr){
if (this != &ptr){
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
}
}
//重载operator=
smart operator=(const smart& ptr){
if (this->_ptr == ptr._ptr){
return *this;
}
if (this->_ptr){
(*this->_count)--;
if (this->_count == 0){
delete this->_ptr;
delete this->_count;
}
}
this->_ptr = ptr._ptr;
this->_count = ptr._count;
(*this->_count)++;
return *this;
}
//operator*重载
T& operator*(){
if (this->_ptr){
return *(this->_ptr);
}
}
//operator->重载
T& operator->(){
if (this->_ptr){
return this->_ptr;
}
}
//析构函数
~smart(){
(*this->_count)--;
if (*this->_count == 0){
delete this->_ptr;
delete this->_count;
}
}
//return reference counting
int use_count(){
return *this->_count;
}
};
int main(){
smart<int> sm(new int(10));
cout << "operator*():" << *sm << endl;
cout << "reference counting:(sm)" << sm.use_count() << endl;
smart<int> sm2(sm);
cout <<"copy ctor reference counting:(sm)"<< sm.use_count() << endl;
smart<int> sm3;
sm3 = sm;
cout <<"copy operator= reference counting:(sm)"<< sm.use_count() << endl;
cout << &sm << endl;
return 0;
}
22.虚函数使用模板函数会发生什么
使用模板函数时,程序在运行时会动态对模板进行参数类型匹配;而存在虚函数的类在编译时需要生成虚函数表,此时需要获得固定的虚函数类型,存在模板虚函数时编译器需要对全部程序进行遍历来确定一共存在多少实例化的虚函数,效率非常低下。