1、C++中 extern "C"的作用?

(1)被 extern “C”修饰的变量和函数是按照C语言方式进行编译和链接的。

C编译器编译后和函数签名和C++编译后的不一致。

(2)被extern “C” 限定的函数或变量是extern类型的。

(3)是为了实现C++与C的混合编程。一旦被 extern “C” 修饰之后,它便以 C 的方式工作(编译阶段:以C的方式编译,链接阶段:寻找C方式编译生成的符号)。

2、构造和析构的顺序?

(1)构造函数执行的顺序是先构造父类,再构造子类,其中父类的构造顺序是从左到右。

(2)析构函数执行的顺序是跟构造函数正好相反的。先执行自身的析构函数,然后再依次从右到左执行父类的析构函数。

3、虚析构函数

(1)什么时候用

  • 带多态性质的基类应该声明一个virtual析构函数。如果class带有任何virtual函数,它就应该拥有一个virtual析构函数。
  • 类的设计目的如果不是作为基类使用,或不是为了具备多态,就不该声明virtual析构函数。

(2)作用

使得在删除指向子类对象的基类指针时可以调用子类的析构函数达到释放子类中堆内存的目的,而防止内存泄露的。

4、newmalloc的区别

(0)new/delete是运算符,malloc/free是库函数。

(1)申请的内存所在位置

  • new 操作符是从自由存储区(free store)上为对象动态分配内存空间。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。

  • malloc函数是从堆上动态分配内存。堆是操作系统中的术语。

(2)是否需要指定内存大小

  • new不需要,编译器会自行计算。
  • malloc需要。

(3)返回类型

  • new内存分配成功时,返回的是对象类型的指针。
  • malloc返回的是void *。

(4)分配失败

  • new分配失败会抛出bad_alloc异常。
  • malloc分配内存失败返回NULL。

(5)重载

  • new/delete操作符可以重载。
  • malloc/free不允许。

5、C++中指针与引用的区别

(1)性质不同。指针是一个变量,存储的是一个地址。引用是原变量的一个别名,和原来的变量实质是同一个东西。

(2)指针可以有多级,引用只能是一级。

1
2
int **p; // ok
int &&a; // invalid

(3)指针可以定义的时候不初始化,而引用必须要。

指针可以指向NULL,引用不可以。

(4)指针初始化后可以再改变,引用不可以。

6、判断一个单向链表是否有环

这是LeetCode中的一道题目

使用快慢指针法可以得到O(1)的空间复杂度。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
ListNode *detectCycle(ListNode *head) {
if (head == nullptr || head->next == nullptr) return nullptr;
ListNode *fast = head, *slow = head;
ListNode *g = nullptr; // 相遇点
while (fast->next != nullptr && fast->next->next != nullptr) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) {
g = fast;
break;
}
}
if (g == nullptr) return nullptr;
// 找到相遇点后,将快指针移动到头,再以相同速度移动,相遇的点即是环入口
fast = head;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}

7、反转一个单向链表

这也是LeetCode中的一道题目

非递归方式:

1
2
3
4
5
6
7
8
9
10
11
12
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *pre = head, *cur = head->next;
pre->next = nullptr;
while (cur != nullptr) {
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}

使用递归:

1
2
3
4
5
6
7
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr) return head;
ListNode *last = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return last;
}

8、构造函数和析构函数中调用虚函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Base {
public:
Base() { func(); }
~Base() { func(); }
virtual void func() { cout << "in Base" << endl; }
};
class A : public Base {
public:
A() { func(); }
~A() { func(); }
virtual void func() { cout << "in A" << endl; }
};
int main()
{
{
A a; a.func();
}
return 0;
}

上面程序的输出是:

1
2
3
4
5
in Base
in A
in A
in A
in Base

在C++中,如果一个基类的指针或引用指向了一个派生类对象,那么在调用这个指针或引用的虚函数时,会调用派生类的版本。但是,在构造函数和析构函数中调用虚函数时,不会有这种行为,因为这个时候对象还没有完全构造好或已经开始析构,所以调用的是当前类的版本。