1.指针和引用的区别
指针是一个指向内存地址的变量,其本身是一个地址,地址保存的是变量的值,而且它本身可变,包括它指向的地址和地址上的存放的数据;引用即为一个变量的地址,也是变量的别名,和变量进行了绑定,并且引用必须初始化,且不可变。
2.const关键字
const的作用:被他修饰的值不可修改,是只读变量,必须在定义的时候就给他赋值
(1)常量指针:const 关键字位于 * 之前
指针一词在后,说明定义了一个指针指向常量,常量指针说明这个指针所指向的值是一个常量,不可变,不能通过这个指针改变所指对象的值,但是可以修改指针所指向的地址,指向其他对象
示例:
int temp = 10;
const int *a = &temp;
int const *a = &temp;
*a = 9; //错误,不能通过指针来修改对象的值
temp = 9; //正确
(2)指针常量:const 关键字位于 * 之后
常量一词在后,说明是一个常量类型的指针,指针本身即为地址,为常量类型则说明地址是常量,不可以修改,因此不能改变这个指针的指向,但是这个指针指向地址上的值可以改变
int temp = 10;
int temp1 = 12;
int* const p = & temp;
p = &temp1; //错误,地址为常量,不能更改p指向的地址
*p = 9; //正确
看一下整体代码:
#include <iostream>
int main()
{
//常量指针
int tmp = 10;
int tmp2 = 11;
const int *a = &tmp;
// *a = 9; //报错,指针所指向的对象不可变,不能通过指针更改对象值
tmp = 9; //正确,可以在原对象上修改他的值
a = &tmp2; //正确,可以修改指针指向
//指针常量
int tmp3 = 12;
int tmp4 = 13;
int *const p = &tmp3;
*p = 9; //正确,可以修改指针指向的值
//p=&tmp4; //错误,地址为常量不可变,不可以修改指向
return -1;
}
3.static关键字的作用
主要用于控制变量和函数的生命周期、作用域和访问权限
(1)静态变量:
- 在函数内部声明的static变量为静态变量
- 静态变量在程序的整个生命周期都存在,不会因为离开作用域而销毁
- 默认初始化为0
(2)静态函数:
- 在类内部声明的static函数为静态函数
- 静态函数属于类而不是类的实例,可以通过类名直接调用而无需创建对象
- 静态函数不能访问非静态成员函数和非静态成员变量
4.define和typedef的区别
define·
(1)只是简单的字符串替换,没有类型检查
(2)在编译的预处理阶段起作用
(3)不分配内存,给出的是立即数,有多少次使用就进行多少次替换
typedef
(1)有对应的数据类型,要进行判断
(2)是在编译、运行时候起作用
(3)在静态存储区分配空间
5.new和malloc的区别
(1)new分配内存失败时会抛出异常,但不会返回NULL,malloc分配内存失败时直接返回NULL
(2)使用new分配内存时无需指定大小,而malloc需要
(3)new/delete会调用对象的构造/析构函数以完成对象的构造/析构,而malloc/free不会
(4)new/delete是操作符,malloc/free是库函数
(5)new从自由存储区动态分配内存,malloc从堆上动态分配内存
6.堆和栈的区别
栈和堆都是⽤于存储程序数据的内存区域。栈是⼀种有限的内存区域,⽤于存储局部变量、函数调⽤信息等。堆是⼀种动态分配的内存区域,⽤于存储程序运⾏时动态分配的数据。
栈上的变量⽣命周期与其所在函数的执⾏周期相同,⽽堆上的变量⽣命周期由程序员显式控制,可以(使⽤ new或 malloc )和释放(使⽤ delete 或 free )。
栈上的内存分配和释放是⾃动的,速度较快。⽽堆上的内存分配和释放需要⼿动操作,速度较慢。
(1). 栈
栈⽤于存储函数的局部变量、函数参数和函数调⽤信息的区域。函数的调⽤和返回通过栈来管理。
(2). 堆
堆⽤于存储动态分配的内存的区域,由程序员⼿动分配和释放。使⽤ new 和 delete 或 malloc 和 free 来进⾏堆内存的分配和释放。
(3). 全局/静态区
全局区存储全局变量和静态变量。⽣命周期是整个程序运⾏期间。在程序启动时分配,程序结束时释放。
(4). 常量区
常量区也被称为只读区。存储常量数据,如字符串常量。
(5). 代码区
存储程序的代码。
7.什么是内存泄漏,智能指针,野指针
(1)内存泄漏(memory leak)是指由于疏忽或错误造成了程序未能释放掉不再使⽤的内存的情况。内存泄漏并⾮指内存在物理上的消失,⽽是应⽤程序分配某段内存后,由于设计错误,失去了对该段内存的控制,因⽽造成了内存的浪费。
(2)智能指针⽤于管理动态内存的对象,其主要⽬的是在避免内存泄漏和⽅便资源管理。
(3)野指针是指指向已被释放的或⽆效的内存地址的指针。使⽤野指针可能导致程序崩溃、数据损坏或其他不可预测的⾏为。
如何避免:在释放内存后将指针置为 nullptr;避免返回局部变量的指针;使⽤智能指针;注意函数参数的⽣命周期, 避免在函数内释放调⽤⽅传递的指针,或者通过引⽤传递指针
8.内存对齐是什么?为什么需要考虑内存对齐?
(1)内存对⻬是指数据在内存中的存储起始地址是某个值的倍数,在结构体中,编译器为结构体的每个成员按其⾃然边界(alignment)分配空间。为了使CPU能够对变量进⾏快速的访问,变量的起始地址应该具有某些特性,即所谓的“对⻬”,⽐如4字节的int型,其起始地址应该位于4字节的边界上,即起始地址能够被4整除,也即“对⻬”跟数据在内存中的位置有关。如果⼀个变量的内存地址正好位于它⻓度的整数倍,他就被称做⾃然对⻬。
(2)需要字节对⻬的根本原因在于CPU访问数据的效率问题。假设上⾯整型变量的地址不是⾃然对⻬,⽐如为0x00000002,则CPU如果取它的值的话需要访问两次内存,第⼀次取0x00000002-0x00000003的⼀个short,第⼆次取从0x00000004-0x00000005的⼀个short然后组合得到所要的数据,如果变量在0x00000003地址上的话则要访问三次内存,第⼀次为char,第⼆次为short,第三次为char,然后组合得到整型数据。⽽如果变量在⾃然对⻬位置上,则只要⼀次就可以取出数据。⼀些系统对对⻬要求⾮常严格,⽐如sparc系统,如果取未对⻬的数据会发⽣错误,⽽在x86上就不会出现错误,只是效率下降。
9.简述⼀下 C++ 的重载和重写,以及它们的区别
(1)overload是重载,这些⽅法的名称相同⽽参数形式不同
⼀个⽅法有不同的版本,存在于⼀个类中。重载是指在同⼀作⽤域内,使⽤相同的函数名但具有不同的参数列表或类型,使得同⼀个函数名可以有多个版本。
规则:
1. 不能通过访问权限、返回类型、抛出的异常进⾏重载
2. 不同的参数类型可以是不同的参数类型,不同的参数个数,不同的参数顺序(参数类型必须不⼀样)
3. ⽅法的异常类型和数⽬不会对重载造成影响
使⽤多态是为了避免在⽗类⾥⼤量重载引起代码臃肿且难于维护。
重写与重载的本质区别是,加⼊了override的修饰符的⽅法,此⽅法始终只有⼀个被你使⽤的⽅法
(2)override是重写(覆盖)了⼀个⽅法
重写是指派⽣类(⼦类)重新实现(覆盖)基类(⽗类)中的虚函数,以提供特定于派⽣类的实现。重写是⾯向对象编程中的多态性的⼀种体现,主要涉及基类和派⽣类之间的关系,⽤于实现运⾏时多态。以实现不同的功能,⼀般是⽤于⼦类在继承⽗类时,重写⽗类⽅法。
规则:
1. 重写⽅法的参数列表,返回值,所抛出的异常与被重写⽅法⼀致
2. 被重写的⽅法不能为private
3. 静态⽅法不能被重写为⾮静态的⽅法
4. 重写⽅法的访问修饰符⼀定要⼤于被重写⽅法的访问修饰符(public>protected>default>private)
10.C++构造函数有几种,分别什么作用
(1)默认构造函数:没有任何参数的构造函数
class MyClass {
public:
// 默认构造函数
MyClass() {
// 初始化操作
}
}
(2)有参构造函数:接受⼀个或多个参数,⽤于在创建对象时传递初始化值。可以定义多个带参数的构造函数,以⽀持不同的初始化⽅式。
class MyClass {
public:
// 带参数的构造函数
MyClass(int value) {
// 根据参数进⾏初始化操作
}
};
(3)拷贝构造函数:⽤于通过已存在的对象创建⼀个新对象,新对象是原对象的副本。参数通常是对同类型对象的引⽤。
class MyClass {
public:
// 拷⻉构造函数
MyClass(const MyClass &other) {
// 进⾏深拷⻉或浅拷⻉,根据实际情况
}
};
11.虚函数和纯虚函数的区别
(1) 虚函数
- 有实现: 虚函数有函数声明和实现,即在基类中可以提供默认实现。
- 可选实现: 派⽣类可以选择是否覆盖虚函数。如果派⽣类没有提供实现,将使⽤基类的默认实现。
- 允许实例化: 虚函数的类可以被实例化。即你可以创建⼀个虚函数的类的对象。
- 调⽤靠对象类型决定: 在运⾏时,根据对象的实际类型来决定调⽤哪个版本的虚函数。
- ⽤ virtual 关键字声明: 虚函数使⽤ virtual 关键字声明,但不包含 = 0 。
class Base {
public:
// 虚函数有实现
virtual void virtualFunction() {
// 具体实现
}
}
(2)纯虚函数
- 没有实现: 纯虚函数没有函数体,只有函数声明,即没有提供默认的实现。
- 强制覆盖: 派⽣类必须提供纯虚函数的具体实现,否则它们也会成为抽象类。
- 禁⽌实例化: 包含纯虚函数的类⽆法被实例化,只能⽤于派⽣其他类。
- ⽤ = 0 声明: 纯虚函数使⽤ = 0 在函数声明末尾进⾏声明。
- 为接⼝提供规范: 通过纯虚函数,抽象类提供⼀种接⼝规范,要求派⽣类提供相关实现。
class AbstractBase {
public:
// 纯虚函数,没有具体实现
virtual void pureVirtualFunction() = 0;
// 普通成员函数可以有具体实现
void commonFunction() {
// 具体实现
}
};
12.深拷贝与浅拷贝
主要区别在于如何处理对象内部的动态分配的资源。
(1) 深拷⻉
深拷⻉是对对象的完全独⽴复制,包括对象内部动态分配的资源。在深拷⻉中,不仅复制对象的值,还会复制对象所指向的堆上的数据。
主要特点:
- 复制对象及其所有成员变量的值。
- 动态分配的资源也会被复制,新对象拥有⾃⼰的⼀份资源副本。
深拷⻉通常涉及到⼿动分配内存,并在拷⻉构造函数或赋值操作符中进⾏资源的复制。
(2)浅拷⻉
浅拷⻉仅复制对象的值,⽽不涉及对象内部动态分配的资源。在浅拷⻉中,新对象和原对象共享相同的资源,⽽不是复制⼀份新的资源。
主要特点:
- 复制对象及其所有成员变量的值。
- 对象内部动态分配的资源不会被复制,新对象和原对象共享同⼀份资源。
浅拷⻉通常使⽤默认的拷⻉构造函数和赋值操作符,因为它们会逐成员地复制原对象的值。
13.什么是抽象类和虚函数
抽象类是不能被实例化的类,主要目的是提供一个接口,供派生类继承和实现。抽象类中可以包含普通的成员函数和构造函数,但必须包含至少一个纯虚函数,即在声明中使用virtual关键字并赋予函数一个=0的纯虚函数
class AbstractShape {
public:
// 纯虚函数,提供接⼝
virtual void draw() const = 0;
// 普通成员函数
void commonFunction() {
// 具体实现
}
};
纯虚函数是在抽象类中声明的虚函数,他没有具体的实现,只有函数的声明。通过在函数声明的末尾加上=0来表示这是一个纯虚函数。派生类必须实现抽象类中的纯虚函数,否则他们也会成为抽象类。
14.push_back 和 emplace_back 的区别
(1)push_back ⽤于在容器的尾部添加⼀个元素
container.push_back(value);
container 是⼀个⽀持 push_back 操作的容器,例如 std::vector 、 std::list 等,⽽ value 是要添加的元素的值。
(2)emplace_back ⽤于在容器的尾部直接构造⼀个元素
container.emplace_back(args);
其中 container 是⼀个⽀持 emplace_back 操作的容器,⽽ args 是传递给元素类型的构造函数的参数。与push_back 不同的是, emplace_back 不需要创建临时对象,⽽是直接在容器中构造新的元素。
区别:
- push_back 接受⼀个已存在的对象或⼀个可转换为容器元素类型的对象,并将其复制或移动到容器中。
- emplace_back 直接在容器中构造元素,不需要创建临时对象。
- emplace_back 通常比 push_back 更⾼效,因为它避免了创建和销毁临时对象的开销。
- emplace_back 的参数是传递给元素类型的构造函数的参数,⽽ push_back 直接接受⼀个元素
#include <vector>
#include <string>
int main() {
std::vector<int> numbers;
// 使⽤ push_back
numbers.push_back(42);
// 使⽤ emplace_back
numbers.emplace_back(3, 4, 5); // 通过构造函数参数直接构造元素
return 0;
}