基础回顾
类和对象
封装
将属性和行为作为一个整体,表现为类,创建一个类的时候创建了很多变量和函数
class Person
{
public:
//设置姓名
void setName(string name)
{
p_name = name;
}
//获取姓名
string getName()
{
return p_name;
}
//获取年龄
int getAge()
{
return p_age;
}
//设置年龄
void setAge(int age)
{
p_age = age;
if (age < 0 || age >150)
{
p_age = 0;
cout << "什么鬼" << endl;
return;
}
}
//设置伙伴
void setLover(string lname)
{
lover = lname;
}
private:
//姓名 可读可写
string p_name;
//年龄 可读可写加个范围
int p_age;
//伙伴 只写
string lover;
};
int main(void)
{
Person p1;
p1.setName("张三");
cout << "姓名:" << p1.getName() << endl;
p1.setAge(18);
cout << "年龄:" << p1.getAge() << endl;
p1.setLover("赵四");
return 0;
}
构造函数与析构函数
用于对像的初始化清理:
构造函数:主要作用在于创建对象时为对象的成员属性赋值、构造函数由编译器自动调用无需手动调用
构造函数语法
类名(){}
- 构造函数没有返回值也不写void
- 函数名称与类名相同
- 构造函数可以有参数,因此可以发生重载
- 程序在调用对象的时候会自动调用构造,无须手动调用,而且只会调用一次
class Person { public: Person() { //不写的也会自动创建一个,只不过里面是空的 cout << "构造函数的调用" << endl; } };
构造函数要是类内没有写出,编译器会提供一个默认的无参空析构函数
含参构造函数调用的时候,需要在对象创建的时候传入参数
析构函数:主要是在对象销毁前系统的自动调用,执行一些清理工作
析构函数语法
~类名(){}
- 析构函数没有返回值也不写void
- 函数名称与类名相同,在名称前加上~
- 析构函数不可以有参数,因此不可以发生重载
- 程序在对象销毁前会自动调用析构,无须手动调用,而且只会调用一次
c++析构函数为虚函数
子类重写构造函数,当使用基类指针删除派生类对象的时候,会调用派生类的析构函数,确保派生类的资源释放class Derived : public Base { public: ~Derived() { // 派生类析构函数的实现 } }; ase* ptr = new Derived(); delete ptr; // 此处会调用Derived的析构函数
class Person
{
public:
Person()
{
cout << "构造函数的调用" << endl;
}
~Person()
{
cout << "析构函数的调用" << endl;
}
//构造和析构都是必须有的实现,如果我们自己不提供,编译器会提供一个空实现的构造和析构
};
this指针
谁调用就指向谁
友元
可客厅就是Public,你的卧室就是Private
客厅所有人都可以进去,但是你的卧室只有和你亲密的人可以进。
在程序中,有些私有属性也想让类外特殊的一些函数或者类进行访问,就需要用到友元技术。
友元的目的就是让一个函数或者类 访问另一个类中的私有元素。
- 全局函数做友元
- 类做友元
- 成员函数做友元
运算符重载
函数重载:同一个函数名但是参数不同,这样就省去了重新给函数命名
实现两个自定义数据类型相加的运算,本质是是创建一个成员函数,然后传入参数,获得返回值
继承
class Java : public BasePage 左边基础右边的
子类不会继承构造函数和析构函数
多态
静态多态
编译器在编译期间完成的,编译器会根据实参类型来推断该调用哪个函数,如果有对应的函数,就调用,没有则在编译时报错
不同的子类对象调用同一个函数接口实现函数不同功能,这个是基于虚函数表实现的
虚函数
基类定义虚函数,子类可以重写该函数
virtual void vir_func()
{
cout << "virtual function, this is base class" << endl;
}
纯虚函数
virtual 返回值类型函数名(参数表)= 0; 无法进行实例化,必须被子类重载才能使用
1、方法重写:子类可以重写父类的函数
2、向上类型转换:用一个父类指针指向子类对象的时候,假如调用的是虚函数,会自动暂时的将该指针转换为子类类型
通过基类的指针或引用调用虚函数时,根据实际对象的类型确定要执行的函数版本。这个因为每个对象都有一个虚函数表,子类的虚函数表与父类的不同在继承父类虚函数表基础上覆盖新的。在调用虚函数的时候会指向自己的虚函数,
#include <iostream>
class Shape {
public:
virtual void draw() {
std::cout << "Drawing a shape." << std::endl;
}
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing a circle." << std::endl;
}
};
class Square : public Shape {
public:
void draw() override {
std::cout << "Drawing a square." << std::endl;
}
};
int main() {
Shape* shape1 = new Circle();
Shape* shape2 = new Square();
shape1->draw(); // 输出:Drawing a circle.
shape2->draw(); // 输出:Drawing a square.
delete shape1;
delete shape2;
return 0;
}
c++基础
const
const在*左边,表示指针指向的数据是常量
const在*右边,表示指针是常量
sizeof与strlen
sizeof计算的是分配空间的实际字节数,会计算/0
strlen是空间中字符个数,是库函数,只能以\0结尾的字符串作为参数
inline
优点:
1、在内联函数被调用的地方进行代码展开,省去了函数调用的时间相比
2、相比宏函数,内联函数编译器会进行语法安全检查或数据类型转换
缺点:
代码膨胀,产生更多的开销如果修改内联函数,所有调用该函数的代码文件都要重新编译
空悬指针、野指针
空悬指针:指针指向的内存已经被delete,但是指针还存在
野指针; 指向不确定地址的指针变量。就是未初始化的指针称为野指针。null指针是指指向0地址,野指针是指向没有地址
避免野指针:
指针记得初始化
指针指向的内存空间释放后,指针应该指向null
指针操作超越了变量范围 解决办法:在变量的作用域结束前释放变量的地址空间让指针指向NULL
this指针
谁调用,this指向谁,在调用成员函数的时候,将对象的地址实参传给this
this指针指向当前对象,通过它可以访问当前对象的所有变量。this只能在类的内部使用,包括private。protected、public属性都能访问
this指针实际上是一个成员函数的形参,在调用成员函数的时候,将函数地址作为实参传递给this
his在成员函数的开始执行前创建,在成员的执行结束后清除
this指针会因编译器不同而有不同的放置位置,可能是栈、寄存器,甚至全局变量
引用和指针的区别
指针存放的是地址,引用是所引用的别名,与引用对象相同
当指针作为 形参进行传递时,将一个实参的一个拷贝传递给形参,两者指向的地址相同,但是不是同一变量。引用是对实参的别名可以改变实参
函数指针与指针函数
int *plusfunction(int a,int b); 指针函数,函数的返回值是一个指针
int (*f)(int a,int b); 函数指针,一个指向函数入口地址的指针
指针数组、数组指针
int *p1[5]; //指针数组 数组中存放的都是指针
int (*p2)[5] //数组指针 指针指向的是一个数组
智能指针
智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的两种智能指针的区别在于管理底层指针的方法不同,shared_ptr允许多个指针指向同一个对象,unique_ptr则“独占”所指向的对象。标准库还定义了一种名为weak_ptr的伴随类,它是一种弱引用,指向shared_ptr所管理的对象
智能指针本质就是一个类模板。它可以创建任意的类型的指针对象,当智能指针对象使用完后,对象就会自动调用析构函数去释放该指针指向的空间
shared_ptr
允许多个智能指针共享同一个动态分配的对象。它会在其所有者之间保持计数,并在最后一个所有者放弃对象所有权时自动释放内存
weak_ptr
weak_ptr
是用于解决shared_ptr
循环引用问题的一种智能指针。当一个对象被shared_ptr
管理,并且另一个对象也有一个指向它的shared_ptr
,会导致循环引用,从而导致内存泄漏。为了解决这个问题,我们可以使用weak_ptr
来打破这种循环引用。weak_ptr
允许访问由shared_ptr
管理的对象,但不会增加其引用计数。需要使用lock()
函数将weak_ptr
转换为shared_ptr
,以便在需要时安全地访问对象。
unique_ptr
是一种独占所有权的智能指针。它确保一个对象只有一个指向它的智能指针,并在其所有者放弃所有权时自动释放内存
面向对象
STL
六大组件
容器
容器 :
放数据---组织数据---本质:就是对常见的数据结构进行封装
① 序列式容器---线性数据结构
string:动态类型的顺序表----只能存储字符----字符串
vector :动态类型顺序表
array : 静态类型顺序表
list:带头结点双向循环链表
② 关联式容器
迭代器
//遍历迭代器
for (vector<Person*>::iterator it = v.begin(); it != v.end(); it++)
{
cout << "姓名:" << (*it)->m_Name << "年龄:" << (*it)->m_Age << endl;
}
vector
就是一个长度可变的数组,任何部分都可进行删除,插入(insert ,eserve),在尾部插入删除简单(push_back,pop_back)
vector的数据结构是一个连续线性空间,当空间不够的时候就会动态扩容,底层是一个数组
常用数据结构
链表
指针是一个地址,指针指向地址代表的空间
上一个节点想要连接到下一个节点,需要将下一个节点的地址存放到上一个节点中next域
//头插法 // 把地址给那个变量,就是让那个指针变量指向自己 // 注意左边是指针,右边是地址 //传入头节点、数据域 void headInsert(Node* list, int data) { Node* newnode = (Node*)malloc(sizeof(Node));//开辟一个空间返回一个地址,新的节点指针node指向这个空间 newnode->data = data;//将数据给新开辟的空间节点 newnode->next = list->next;//让头节点下一个地址赋给newnode的next指针,也就是newnode的next指针指向第二个节点 list-> next = newnode;//将头节点的next指针指向node节点 list->data++;//代表了头节点插入了一个元素 }
栈
单端操作(入栈与出栈在同一侧)
入栈 1,2,3,4,5
出栈 5,4,3,2,1
队列
(入队与出队在不同侧)
入队1,2,3,4
出队1,2,3,4
vector
就是一个长度可变的数组,任何部分都可进行删除,插入(insert ,eserve),在尾部插入删除简单(push_back,pop_back)
vector的数据结构是一个连续线性空间,当空间不够的时候就会动态扩容,底层是一个数组
应用场景:vector
小数据,任意位置插入
list
list底层是一个双向循环链表,以结点为单位存放数据,结点的地址在内存中不一定连续,每次插入或删除一个元素,就配置或释放一个元素空间
大数据,任意位置
deque
deque是一个双向开口的连续线性空间(双端队列),在头尾两端进行元素的插入和删除操作都有,可以扩容比vector优点多
理想的时间复杂度小数组,两端插入或者删除
queue 队列
队列是一个线性的数据结构,并且这个数据结构只允许在一端进行插入,另一端进行删除,
map(映射键值对)
底层是通过红黑树来实现的
应用:典型的例如按学生的名字来查询学生信息,即可将学生名字作为关键字,将学生信息作为元素值,保存在map 中。
set(集合)
底层使用红黑树实现
通常是满足/不满足某种要求的值集合,用set最为方便。
set中不会存在重复的元素,若是保存相同的元素,将直接视为无效
map、set、multiset、multimap
map与set
std::map
:存储键值对(key-value pairs),每个元素都有一个唯一的键(key)和一个关联的值(value)。std::set
:存储唯一的值(value),不允许重复元素
底层都是红黑树,会自动排序
map : 是一个关联容器,它存储键-值对,并根据键的排序顺序自动对其进行排序。每个键在
std::map
中是唯一的,因此不允许重复键。插入和查找操作的时间复杂度为 O(log n)。它适用于需要按键进行排序和查找的情况。
multiset
(多重集合):std::multiset
与std::set
类似,但它允许存储多个相同的值。它自动对元素进行排序,但在插入和查找操作的时间复杂度上与std::set
相同(O(log n))。std::multiset
适用于需要存储多个相同值的情况
set
(集合):std::set
是一个存储唯一值的关联容器。它自动对其元素进行排序,并且每个元素在std::set
中只能出现一次。插入和查找操作的时间复杂度为 O(log n)。std::set
适用于需要存储唯一值且不关心键-值对的情况
multiset
(多重集合):std::multiset
与std::set
类似,但它允许存储多个相同的值。它自动对元素进行排序,但在插入和查找操作的时间复杂度上与std::set
相同(O(log n))。std::multiset
适用于需要存储多个相同值的情况
unordered_map、unordered_set
底层都是哈希表,不会自动排序
map与hash_map或者unordered_map的区别
底层实现:map是基于红黑树实现的有序容器、unorderded_map是基于哈希表实现的无序容器
map中不允许存在相同的key,unorderd_map中允许
哈希表是小数据中,红黑树大数据
哈希冲突
- 链地址法
对于相同的哈希值,使用链表进行连接
2、再哈希法
提供多个哈希函数,如果第一个哈希函数计算出来的key的哈希值冲突了,则使用第二个哈希函数计算key的哈希值。
开放定址法
当关键字key的哈希地址p =H(key)出现冲突时,以p为基础,产生另一个哈希地址p1,若p1仍然冲突,再以p为基础,产生另一个哈希地址p2,…,直到找出一个不冲突的哈希地址pi ,将相应元素存入其中。
算法
迭代器
vector::iterator
itBegin 起始迭代器
itEnd 结束迭代器
v.begin() 容器开头
v.end() 容器结尾的下一个位置
我们的起始迭代器:指向容器第一个元素,末尾迭代器指向最后一个元素的下一个位置
仿函数
仿函数是一个类,里面有成员函数重载()符号,在我们调用实例对象的时候,会自动调用重载函数,这样就像是一个函数一样
最上面的一个实例对象,传入两个参数之后,就会自动调用成员函数重载()
重载函数接受一个参数就叫做一元谓词,接受两个参数就叫做二元谓词
内建函数
在库里面将一封装一些仿函数,我们可以在加入库的头文件之后,就可以像函数一样使用他
适配器
泛型编程
编写与类型无关的调用代码,是代码复用的一种手段。 模板是泛型编程的基础
模板
模板传入的参数先不设定,后面实例化的时候直接选择自己的参数实例化
内存管理
变量存储位置
堆区:程序员自己申请的变量,自己释放或者程序结束后操作系统释放
栈区:由编译器自动分配释放,存放函数的参数值,局部变量
全局/静态区:c语言中有分为初始化和未初始化的,c++中没有区分
代码区;存放函数二进制代码
文字常量区:常量字符串放在这里。程序结束时由系统释放
栈与堆
堆是从内存低地址向高地址方向增长,栈是从内存的高地址向低地址增长
因此,栈和堆在内存中的地址变化可以总结如下:
栈:从高地址向低地址增长(从上往下)。 堆:从低地址向高地址增长(从下往上)。
内存泄漏
由于程序未能释放掉不再使用的内存的情况。内存泄漏并发指物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该断内存的控制
1、在堆中创建对象分配内存,但未显式释放内存
2、在构造函数中动态分配内存,但未早析构函数中正确释内存
3、
避免内存泄漏
1、new完记得delete
2、在构造函数中动态申请内存,在析构中释放
3、使用智能指针
段错误
指程序访问了一段不可访问的内存地址
产生错误原因:
1、解引用空指针
2、访问不可访问的内存空间(内核空间),不存在的地址空间
3、试图写一个只读内存空间(代码段)
4、使用未初始化的指针(定义时没有初始化或者已经回收)
5、内存不够
避免断错误
1、定义指针后初始化
2、数组下标是否越界
3】在堆上分配空间是否猪狗
八股
1、什么函数不能定义为虚函数
1、构造函数
2、析构函数
虚函数的调用依赖于对象的类型,而在对象构造或析构的过程中,对象的类型可能会发生变化。因此,构造函数和析构函数不能是虚函数。
静态成员函数: 静态成员函数属于类本身,而不属于类的实例对象。虚函数的调用是基于对象的运行时类型,而静态成员函数与对象实例无关,所以不能将静态成员函数声明为虚函数。
2、静态链接库和动态链接库的区别?各自是怎么使用的?静和动都体现在哪些方面
静态链接库:
静态链接库是一种将代码和函数编译成目标文件,然后将这些目标文件打包成单个库文件的方式。这意味着库的代码被复制到每个使用它的程序中,从而使程序独立于外部库的存在
动态链接库是一种在运行时由操作系统加载的库,它可以被多个程序共享。不同于静态链接库,动态链接库的代码不会被复制到每个使用它的程序中
静与动静态链接库的代码被复制到程序中,而动态链接库的代码在运行时由操作系统加载。静态库在编译时链接,而动态库在运行时链接。
3、c++中的static的作用
c++修饰类成员,静态成员函数没有this指针,不属于类的实体,可以被类共享
4、深拷贝与浅拷贝
深拷贝,在堆区重新申请空间
浅拷贝简单的赋值操作,拷贝指针,两个指针指向一个内存空间
5、多继承组合问题
6、c++拷贝构造函数什么时候重写
7、父类析构函数必须是虚函数吗
如果父类的析构函数不是虚函数,则不会触发动态绑定(多态),结果就是只会调用父类的析构函数,而不会调用子类的析构函数,从而可能导致子类的内存泄漏(如果子类析构函数中存在free delete 等释放内存操作时)
为什么父类析构函数必须为虚函数_父类析构函数不是虚函数会怎么样-CSDN博客
c++11特性
1、智能指针
shared_ptr
我们可以对一个资源添加一个计数器,让所有管理该资源的智能共用这个计数器,倘若发生拷贝,计数器加一,倘若有析构发生, 计数器减一,当计数器等于0的时候,就把对象析构掉
weak_ptr
不增加引用计数,不参与管理,但是也像指针一样访问修改资源。
unique_ptr
只能有一个指针可以占用对象
2、左值、右值引用
左值和右值的主要区别是,左值可以被修改,而右值不能。不过,C++11 改变了这一区别。在一些特殊的情况下,我们可以使用右值的引用,并对右值进行修改。
3、Lambda 表达式
不需要定义函数名字,直接将函数作为参数传递
4、初始化列表
只能初始化一次
简单的知识
1、new
int* p = new int(10);//分配一个整型,值为10,p指向它
int* arry = new int[10];//分配一个人42个int的数组;p指向第一个int
delete p; //p必须指向一个动态分配的对象或为空
delete[] arry; //arry必须指向一个动态分配的数组或为空
2、引用
作用:给变量起别名
语法:数据类型 &别名 = 原名
int a = 0;
int &b = a;
//a和b操作的是同一块内存
- 引用必须初始化——告诉它它是谁的别名
- 引用在初始化之后,不可以改变
- 引用的本质是c++内部实现的一个指针常量,指针指向不能变
3、函数重载
作用:函数名可以相同,提高复用性
函数重载满足条件
- 同一个作用域下
- 函数名相同
- 函数参数类型不同或者个数不同或者顺序不同
4、c++函数提高
1、函数默认参数
在c++中函数形参列表中的形参是可以有默认值的。
如果某个位置已经有了默认参数,那么从这个位置往后都要有默认参数
声明和实现只能有一个有默认参数。
5、深拷贝与浅拷贝
6、static
1、编译阶段分配内存
2、普通变量可以访问静态,静态只能访问静态
3、静态共享,属于类
4、静态无this指针
5、静态函数的访问
通过对象
通过类名
8、内联函数
在编译的时候,将函数·原地展开,不需要进行跳转,减少了这个过程的一些资源,以空间换时间文章来源:https://www.toymoban.com/news/detail-481035.html
9、程序启动的过程
加载可执行程序:操作系统根据可执行的文件信息,分配进程空间,将代码段,数据段,BSS段等映射到进程的虚拟空间中
初始化:操作系统调用C++运行库的初始化代码,进行初始化,包括初始化全局变量,构造静态对象等
调用main()函数
根据程序设计和逻辑,在运行过程中,可能需要分配动态内存、创建新的线程、进行 I/O 操作等。
退出:当 main() 函数执行完毕,或者调用 exit() 函数结束程序运行,操作系统会回收进程空间和资源,完成程序的退出过程文章来源地址https://www.toymoban.com/news/detail-481035.html
到了这里,关于C++面经专题的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!