1.C++ override关键字
如果派生类在虚函数声明时使用了override描述符,那么该函数必须重载其基类中的同名函数,否则代码将无法通过编译。
如果派生类里面是像重载虚函数 就加上关键字override 这样编译器可以辅助检查是不是正确重载,如果没加这个关键字 也没什么严重的error 只是少了编译器检查的安全性
2.内核安全
3.mallow和new的区别
①属性
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
②参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
③返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
④分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
⑤自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
⑥重载
C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
⑦内存区域
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
malloc 的底层实现是系统调用函数 brk(),其主要移动指针 _enddata(此时的 _enddata 指的是 Linux 地址空间中堆段的末尾地址,不是数据段的末尾地址)
malloc不允许重载的原因: 总结来说,malloc 不允许重载的原因是 C 语言不支持函数重载,而 malloc 本身的定义是标准化的、不可更改的。如果需要自定义的内存分配行为,可以通过封装函数实现。
4.vector扩容
我们知道动态数组是可以动态地插入删除数据,但是动态数组也是预设了数组大小,在大小不够的时候(size = capacity),会申请一块更大的新内存,进行数组扩容。
起始容量为0,GCC 是 2 倍扩容,即之后插入按照 1 2 4 8 16 二倍扩容,VS13 是 1.5 倍扩容。扩容后是一片新的内存,需要把旧内存空间中的所有元素都拷贝进新内存空间中去,之后再在新内存空间中的原数据的后面继续进行插入构造新元素,并且同时释放旧内存空间,并且,由于 vector 空间的重新配置,导致旧 vector 的所有迭代器都失效了。
5.多进程共享动态链接库
进程共享动态链接库(Dynamic Link Library,简称 DLL 或 .so 文件)是现代操作系统中常见的内存管理机制,允许多个进程同时使用同一个库中的代码或资源,而无需为每个进程创建独立的副本。这种共享机制能够节省内存,提高系统性能。
6.析构函数为什么是虚函数
在C++中,析构函数被声明为虚函数的原因主要是为了确保基类指针或引用 指向派生类对象时,能够正确调用派生类的析构函数。具体来说,如果一个类有虚函数,它通常也应该将析构函数声明为虚函数。
如果基类的析构函数不是虚函数,当你通过基类的指针或引用删除派生类对象时,只会调用基类的析构函数而不会调用派生类的析构函数。这会导致派生类中动态分配的资源没有被正确释放,进而引发内存泄漏等问题。
7.vector和array的区别
都能用下标来访问元素。
都是顺序容器,采用的是顺序的存储空间。
创建方式上不同:
vector无需指定大小,只需指定类型,e.g. vector<int> a。
array需要同时指定类型和大小,e.g. array<int, 3> a。
内存使用上不同:
vector需要占据比array更多的内存,因为其内存空间大小是动态可变的。
效率上不同:
vector效率偏低,因为当向vector中添加新元素的时候,内存空间不够,需要重新申请更大的空间,由于vector是连续内存空间的,因此其申请更多空间的时候,可能整个位置发生改变,需要将原来空间里的数据拷贝过去。
下标类型不同:
在用下标访问元素时,vector 使用 vector::size_type 作为下标的类型,而数组下标的正确类型则是 size_t;
swap操作不同:
vector是将引用进行交换,效率高,其迭代器指向原来的容器(原来的容器中的元素指向的却是另一个容器的值),但是end的引用并没有发生交换,因此在输出的时候注意别用end作为迭代终止条件。
array是进行值的交换,效率低,且迭代器仍指向原来的容器。
8.静态链接和动态链接的区别?
静态链接库是.lib格式的文件,一般在工程的设置界面加入工程中,程序编译时会把lib文件的代码直接链接进目标程序中,因此会增加代码大小,静态链接的可执行文件会大一些,程序运行的时候不再需要其它的库文件,不能手动移除lib代码。
动态链接库是一个包含可由多个程序同时使用的代码和数据的库,它是包含函数和数据的模块,格式.dll。程序运行时动态加载这些模块,运行时可以随意加载和移除,节省内存空间。
动态链接库和静态链接库的相同点是它们都实现了代码的共享,不同点是静态链接库lib文件中的代码被包含exe文件中,该lib中不能再包含其他动态链接或者静态链接的库了。而动态链接库dll可以被调用的exe动态地“引用”和“卸载”,一个dll中可以包含其他动态链接库或者静态链接库。
9.左值和右值
左值(lvalue)是表达式结束后依然存在的持久对象,可以出现在赋值表达式的左边或右边,右值(rvalue)是表达式结束后就不再存在的临时对象,不能出现在赋值表达式的左边。左值可以取地址,右值不能取地址。
10.NULL和nullptr的区别?
特性 |
NULL |
nullptr |
定义 |
宏,通常定义为 0 |
关键字,类型为 std::nullptr_t |
类型 |
整数常量 |
空指针类型 |
类型安全 |
否,会隐式转换为整数 |
是,只能用于指针 |
使用场景 |
C 和旧版 C++ 中的空指针 |
C++11 及以后的空指针 |
函数重载问题 |
可能导致歧义 |
不会导致歧义 |
在现代C++编程中,建议始终使用 nullptr 而不是 NULL,因为 nullptr 提供了更强的类型安全性,避免了由 NULL 引发的类型不匹配问题,尤其在模板和函数重载等场景下。
总结:NULL 是一个整数常量,类型不安全,而 nullptr 是C++11引入的专门表示空指针的关键字,类型安全,建议在C++代码中优先使用 nullptr。
nullptr 是 C++11 引入的关键字,用来表示空指针,它表示指针没有指向任何有效内存地址。
11. 原子变量会引起死锁吗?
原子变量通过硬件提供的原子操作实现无锁同步,不会引发死锁。
原子变量是一种特殊的数据类型,用于执行原子操作。原子操作是不可分割的操作,可以确保在多线程环境中线程安全地执行。C++中的std::atomic提供了对原子操作的支持。一个独立不可分割的操作。多线程编程需要保证线程安全,而线程安全一个很重要的特性就是原子性,即在同一时刻只有一个线程对原子进行操作,保证数据访问的互斥性。
原子操作:
原子操作是计算机科学中的概念,指的是在执行期间不能被中断的一组操作。在多线程环境中,确保原子操作的执行是不可分割的,要么完全执行,要么完全不执行。这种特性使得在并发编程中更容易管理共享资源,避免竞态条件和数据不一致。
12.手撕代码:多线程循环打印123
#include <stdio.h>
#include <pthread.h>
#define LOOP_COUNT 5 // 定义循环次数
pthread_mutex_t mutex;
pthread_cond_t cond;
int current_num = 1;
void* print_num(void* arg) {
int num = *(int*)arg;
for (int i = 0; i < LOOP_COUNT; i++) {
pthread_mutex_lock(&mutex); // 加锁
while (current_num != num) {
pthread_cond_wait(&cond, &mutex); // 等待条件变量
}
printf("%d ", num); // 输出当前数字
// 更新下一个要输出的数字
current_num = current_num % 3 + 1;
pthread_cond_broadcast(&cond); // 广播唤醒所有等待的线程
pthread_mutex_unlock(&mutex); // 解锁
}
return NULL;
}
int main() {
pthread_t tid1, tid2, tid3;
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁
pthread_cond_init(&cond, NULL); // 初始化条件变量
int num1 = 1, num2 = 2, num3 = 3;
// 创建三个线程
pthread_create(&tid1, NULL, print_num, &num1);
pthread_create(&tid2, NULL, print_num, &num2);
pthread_create(&tid3, NULL, print_num, &num3);
// 等待线程结束
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_join(tid3, NULL);
pthread_mutex_destroy(&mutex); // 销毁互斥锁
pthread_cond_destroy(&cond); // 销毁条件变量
return 0;
}
13.交叉链表
#include <stdio.h>
#include <stdlib.h>
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode *getsamenode(struct ListNode *headA, struct ListNode *headB) {
if (headA == NULL || headB == NULL) return NULL;
struct ListNode *PA = headA, *PB = headB;
// 两个指针不断移动,直到它们相等或都为NULL
while (PA != PB) {
PA = (PA == NULL) ? headB : PA->next;
PB = (PB == NULL) ? headA : PB->next;
}
return PA; // 返回相交的结点,或NULL表示不相交
}
struct ListNode *create(int val) {
struct ListNode *newnode = (struct ListNode *)malloc(sizeof(struct ListNode));
newnode->val = val;
newnode->next = NULL;
return newnode;
}
int main() {
// 创建两个链表
struct ListNode *headA = create(1);
headA->next = create(2);
headA->next->next = create(3);
headA->next->next->next = create(4);
headA->next->next->next->next = create(5);
struct ListNode *headB = create(9);
headB->next = create(8);
// 模拟链表相交,从headB的结点8开始与headA的结点3相交
headB->next->next = headA->next->next; // 让两个链表在节点3相交
// 打印两个链表
struct ListNode *current = headA;
while (current != NULL) {
printf("ListA: %d\\n", current->val);
current = current->next;
}
struct ListNode *current2 = headB;
while (current2 != NULL) {
printf("ListB: %d\\n", current2->val);
current2 = current2->next;
}
// 查找相交的节点
struct ListNode *interaction = getsamenode(headA, headB);
if (interaction != NULL)
printf("Intersecting Node: %d\\n", interaction->val);
else
printf("No intersection\\n");
return 0;
}
评论前必须登录!
注册