c++ final关键字的作用是什么

作用1:禁止类被继承,在类的声明后面加上final,如class Person final,表示其他类无法继承该类。

作用2:禁止虚函数被子类重写virtual void func()final {};

class  Person final//final关键字用于禁止类的继承
{
public:
	Person() {};
	~Person() {};
	virtual void func()final {};
private:
};

sizeof与strlen的差异

sizeof是一个编译时确定的运算符,可以用于获取变量或类型在内存中占用的字节数。它在编译阶段就已确定,不依赖于运行时数据。

strlen是一个运行时确定的库函数,专门(只能)用于计算以空字符\0结尾的字符串的实际字符数。由于它需要遍历字符串,因此其结果仅在运行时才可知。

C与C++中static关键字的不同用途

在C语言中,static用于修饰局部静态变量(延长生命周期至程序结束)、外部静态变量(限制链接至其他文件)和静态函数(限制函数的作用域至定义它的文件内)。

在C++中,static除了上述功能外,还用于类中定义静态成员变量和静态成员函数。静态成员属于整个类,而非单个对象,常用于计数器或共享数据的存储。

C语言的delete关键字和delete[]

delete只会调用一次析构函数,delete[]会调用每个成员的析构函数。delete一般用于销毁对象,delete[]一般用于销毁数组。

C++new、delete、malloc、free关键字作用

malloc和free搭配使用,只是简单的申请动态内存和释放内存,不负责其他逻辑。

new和delete搭配使用,除了可以动态申请内存之外。还可以调用构造函数和析构函数。

子类析构时,需要调用父类的析构函数嘛

析构函数调用次序:先调用派生类析构函数,在调用基类析构函数。

构造函数调用次序:先调用基类构造函数,再调用派生类构造函数。

虚函数和纯虚函数

虚函数:定义在父类中的virtual关键字的函数,可以再子类中通过override关键字重写。基类有函数实现。

纯虚函数:父类中不提供具体实现,以=0结尾表示一个纯虚函数。强制所有子类必须重写该函数,同时基类变为抽象类,不能直接实例化。

什么是抽象类

如果一个类中包含纯虚函数,那么这个类被成为抽象类

引用和指针的区别

引用必须初始化,并且只能是一级引用。不能是多级引用。引用初始化之后不能被改变。

指针可以不用初始化,可以是多级指针。初始化只会可以再次被修改。

将引用作为函数参数有哪些特点

结构体和联合有什么区别

结构和联合都是由不同数据类型成员组成,区别是:同一时刻,联合只存放一个被选中的成员,所有成员公用一块地址。结构的所有成员,各有各的地址。

对联合成员的赋值,会覆盖其他成员的值。对结构体成员的赋值不影响其他成员的赋值。

C++虚析构函数的作用

防止内存泄漏,确保父类和派生类的析构函数都会被执行。作用是确保通过基类指针删除派生类对象时,能够正确调用派生类和基类的析构函数,防止资源泄漏。

C++类型转换有哪些

描述内存分配方式以及他们之间的区别

(1)、全局静态存储:编译阶段就已经分配好的内存,整个程序运行期间都存在,例如全局变量、static静态变量。

(2)、从栈上创建,执行函数时候,函数参数和内部变量分配的内存都在栈上,函数执行结束后会被释放掉。栈内存分配内置于处理器的指令集。

(3)、从堆上分配内存,也叫动态内存分配。程序员通过malloc或者new动态申请内存。同时需要自己手动释放内存。

C++中的空类有哪些成员函数

1、缺省构造函数:自动生成,用于创建类的新实例对象。

2、缺省拷贝构造函数:自动生成,在对象之间进行浅拷贝。 类中有指针等需要深拷贝的资源时,必须自定义拷贝构造函数,否则会导致资源重复释放等问题。

3、缺省析构函数:在对象生命周期结束时候,自动调用。

4、缺省赋值运算符:用于对象之间的赋值操作。

5、缺省取地址运算符:用于获取对象的地址。

6、缺省取值运算符const:常量版本的取地址运算符,保证对象不会被修改。

设计一个类,不能被继承

1、使用final关键字,编译器会禁止任何子类继承该类。推荐使用该方案

2、将构造函数或者析构函数设置为私有或者受保护,并不提供友元类。该方案主要是用于特殊设计(单例)

虚函数表是什么意思

类成员函数的重写、重载和隐藏是什么意思

重写override:发生在基类和派生类之间,基类必须用virtual关键字修饰,派生类重写的时候,参数必须一致。

class Base
{
public:
	virtual	void func(int a) {};
};
class Derived : public Base
{
public:
	void func(int a) override {};
};

重载overload:发生在同一个类中,参数列表不同或者参数类型不同。和virtual关键字没有任何关系。

class Person
{
public:
	void func(int a) {};
	void func(int a, int b) {};
	void func(double a) {};
};

隐藏:发生在派生类和基类之间,派生类定义了和父类同名的函数,父类的同名函数就会被隐藏。如何让父类函数不被隐藏:使用using Base::func;

class Base {
public:
    void func(int a) { std::cout << "Base::func(int)"; }
    void func(double a) { std::cout << "Base::func(double)"; }
};
class Derived : public Base {
public: 
    //using Base::func;
    void func(int a) { std::cout << "Derived::func(int)"; } // 隐藏了Base的func(double)
};
int main()
{	
    Derived d;
    d.func(0.21); // 输出Derived::func(int) 编译器尝试将0.21转换为int
	//std::cout << "Hello World!\n";
}

C++多态实现的原理

多态的实现依赖虚函数表vtable和虚函数指针vptr。当类中存在虚函数时候,编译器会为类生成vtable,并且在构造函数中将vptr指向相应的vtable。这样通过this指针就可以访问正确的vtable。实现动态绑定和多态。

vtable:每个类一张,存放虚函数地址。 vptr:每个对象一个,指向所属类的vtable。

链表和数组的区别

存储形式:数组是连续内存空间,链表是非连续内存空间,节点之间通过指针连接。

数据查找:数组支持快速查找,速度快。链表则需要顺序检索,效率较低。

数据插入或者删除:数组需要大量数据移动,效率很低。链表只需要修改节点的前指针和后指针就可以,效率很高。

越界问题:数组操作的时候容易越界,链表不容易越界。风险较小。

typedef和define的区别

define是预处理阶段的文本替换,不做任何检查。它指的是宏定义。

typedef是为类型起一个新的类名,由编译器处理类型安全。

const和constexpr的区别

const:声明一个常量,不可修改。在编译阶段或者运行阶段确定,只能初始化一次。

constexpr:声明一个编译期常量,要求初始化表达式的时候就确定常量的值。

int func() {
	return 1;
}
const int aa = func();
constexpr int bb = 1;

const关键字的多方面用途

1、修饰变量:只能被初始化一次,初始化之后,值不可以被修改。

const int aa =1;

2、修饰指针

const int* p:指向的内容不可变,指针本身可变。
int* const p:指针本身不可变,指向内容可变。
const int* const p:指针和内容都不可变。

3、修饰函数参数:防止在函数内部修改参数,只读传递。

void print(const int value);

4、修饰成员函数:表示该成员函数不会修改类的成员变量,只能调用其他的const函数。

int getAge() const { return age; }

5、修饰返回值:返回值不可以作为左值赋值。

6、修饰类成员变量:C++11开始支持,这个变量必须在初始化列表中初始化。

指针常量和常量指针

指针常量:指针本身的值不可改变,一般指针被初始化之后,不能再指向其他的值。

const int* p;
int const* p; // 等价写法
int a = 10;
int b = 20;
const int* p = &a; // *p 不可修改
//*p = 30; // 错误,不能通过p修改a
p = &b;   // 正确,p可以指向别的地址

常量指针:指针所指向的数据不可改变,但是指针本身的值可以改变,改变其地址。

int a = 10;
int b = 20;
int* const p = &a; // p不可修改
*p = 30; // 正确,可以修改a的值
//p = &b; // 错误,p不能指向别的地址