c plus plus interview review
文章目录
这是我看了《C笔试面试宝典》一书的笔记
new, delete, malloc, free的关系
new和delete是C++的 运算符 ,new调用构造函数,delete调用析构函数。
malloc和delete是C运行库的函数。
delete和delete[]的区别
delete只会调用一次析构函数,delete[]则会调用每个成员的析构函数。
写程序一测:
#include <iostream>
#include <string>
class Computer {
private:
std::string name;
public:
Computer();
~Computer();
};
Computer::Computer() { }
Computer::~Computer() { }
int main(int argc, char *argv[])
{
Computer *cs = new Computer[5];
Computer *c = new Computer;
int *integers = new int[5];
int *i = new int;
delete[] i;
delete[] integers;
delete[] cs;
delete[] c;
return 0;
}
发现编译可以通过,但是运行会Segment Fault。gdb一下,发现在delete[] c时出错了。
原因是,对于基本数据类型,delete[]能代替delete,对于自定义的类型,则要严格区分, new[]完之后用delete[]释放,new完之后用delete释放。
继承和组合的优缺点
引用自 这里 。
引用
不能建立数组的引用。引用没有定义新的变量,不占用内存空间。
关联,聚合和组合
关联是两个类的一般性关联,如老师和学生。
聚合是has-a关系,聚合类不需要对被聚合类负责,用空的菱形表示,实现如下:
class A {
};
class B {
A *a;
};
组合是contains-a关系,组合类和被组合类有相同的生命周期,组合类要对被组合类负责, 用实的菱形表示,实现如下:
class A {
};
class B {
A a;
};
初始化列表
当类中含有const和引用成员变量时,基类构造函数只能使用初始化列表来初始化, 但是
const int& a;
这种就可以用赋值的方法。
类型安全
c++不是类型安全的,因为不同类型的指针之间可以强制转换。
空类
当一个类没有任何成员时,大小是1byte,这个字节是用来区分这个类的不同对象的。
逻辑地址 to 物理地址
给出的逻辑地址格式是这样的, 段地址:段内偏移地址,那么真实的地址(物理地址) 是: 段地址 * 10H + 段内偏移地址。当然,这只适合于Intel 8086。
4种类型转换
-
const_cast,把const的变量变成非const的,用法:
新变量 = const_cast<类型>(变量);
-
static_cast, 用于基本类型的转换,不能用于无关类型(不是基类与子类)之间的 指针的转换
-
dynamic_cast, 运行时会有安全检查,用于基类与子类之间的转换,常用于多态
-
reinterpret_cast,重新解释类型,没有转换,常用于函数指针的转换。
数组作参数
当数组作为参数传递时,它会退化成同类型的指针。
override和隐藏
override,基类中必须要有virtual。
如果基类函数名没有virtual,子类函数与父类函数签名一样,则称隐藏。
不管基类函数名有没有virtual,子类函数名一样,签名不一样,则也称隐藏。
求两个数中的最大的那个数
不能用判断(if, :?, switch)。答案是用abs函数:
((a + b) + abs(a - b)) / 2
而我觉得这种方法不好,因为用到了库函数,库函数里面还可能也要判断,其实是换汤不换药。
我问了同学,他想到了下面的方法,我觉得很好:
#include <stdio.h>
int max(int a, int b);
int main(int argc, char *argv[])
{
int a = 9999, b = 23;
printf("%d\n", max(a, b));
return 0;
}
int max(int a, int b)
{
int c = a - b;
int flag = (unsigned)c >> (sizeof(int) * 8 - 1);
return (1 - flag) * a + flag * b;
}
根据负数与正数的符号位的不一样,而得出那个数。
打印源文件的文件名和当前行号
在C/C++中,可以用__FILE__和__LINE__,由编译器来识别。
main函数执行完之后还能执行代码?
居然是可以的! <stdlib.h>
中有一个奇葩的库函数叫on_exit,在linux下的man page中, 定义如下:
int on_exit(void (*function)(int , void *), void *arg);
传进去一个函数指针,和一个参数,这个函数必须是2个参数,分别是int和void*类型的, 可以调用多个,以LIFO形式执行, on_exit在任何地方调用都只会在main函数结束之后才会执行。 测试一下:
#include <stdio.h>
#include <stdlib.h>
void one(int status, void *arg);
void two(int status, void *arg);
int main(int argc, char *argv[])
{
printf("top\n");
on_exit(two, NULL);
on_exit(one, NULL);
printf("It may be the last one.\n");
return 0;
}
void one(int status, void *arg)
{
printf("one\n");
}
void two(int status, void *arg)
{
printf("two\n");
}
书上说的是 _onexit
,根据我找的资料,这函数应该只有在windows的VC中才有。
判断是由C编译器编译还是由C++编译器编译
使用一个宏 __cplusplus
:
#ifdef __cplusplus
...
#else
...
求n个数中第k大的数
我智商低,只能想到普通的办法,就是选择排序的外面循环K次。不过不能因为这样而找不到工作啊, 学习了大牛的算法:
吸取快排中的思想,随机取一个数,把比它小的数放到左边,比它大的数放到右边, 如果运气非常好,它的下标i刚好是n - k - 1,则它就是第k大的数,如果i小于 n
- k - 1,则第k大的数在左边,否则在右边,再分为子问题进行求解。
写了很久终于写出来了,要是在面试的时候,估计写不出来:
#include <stdio.h>
#include <stdlib.h>
#include <stdlib.h>
int get_maxk(int *arr, int n, int k);
void swap(int *arr, int i, int j);
int main(int argc, char *argv[])
{
int a[10];
int i;
srand(time(NULL));
for (i = 0; i < 10; i++) {
a[i] = rand();
}
for (i = 1; i <= 10; i++) {
printf("%d\n", get_maxk(a, 10, i));
}
return 0;
}
int get_maxk(int *arr, int n, int k)
{
int pivot, last_left, i;
if (k > n) {
fprintf(stderr, "k can't be larger than n\n");
exit(1);
} else if (k == n) {
return arr[0];
}
pivot = 0;
last_left = pivot;
for (i = 1; i < n; i++) {
if (arr[i] < arr[pivot]) {
last_left++;
swap(arr, last_left, i);
}
}
swap(arr, pivot, last_left);
if ((n - last_left) == k) {
return arr[k];
} else if ((n - last_left) > k) {
return get_maxk(arr + last_left + 1, n - last_left - 1, k);
} else {
return get_maxk(arr, last_left, k - (n - last_left));
}
}
void swap(int *arr, int i, int j)
{
int temp;
if (i == j) {
return;
}
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
判断单链接有环
想到这个方法的人就是神!
用两个指针,一个每次走一步,另外一个每次走两步,如果有环必定重合, 否则走两步的那个指针将到达终点。
传值还是传地址
看下面的代码:
void func(char *a)
{
a = (char *)malloc(10);
}
int main(void)
{
char *a = NULL;
func(a);
}
执行完主函数后,a还是NULL!a不是传进去申请了空间吗?我之前也是这样认为的,
后来发现, 其实func函数的那个参数是以传值的方式传进去的,而不是传地址!
如果要传地址的话, 应该是 char **a,func(&a)
这样才行!
extern “C”
这个是用于C/C++混合编程的,当引用C语言代码时在函数前面加上。
内联函数
编译器在编译内联函数时会对参数类型进行检查。
堆栈溢出的原因
分配了内存没有释放 递归层次太深
唯一不能声明为虚函数的函数
构造函数!PS:析构函数可以声明为虚函数。
#error
当预处理执行到#error时,会停止编译,并给出自定义的错误信息
指针&数组
指向数组的指针:
int (*a)[10];
指向函数(返回值为int,1个int参数)的指针:
int (*a)(int);
volatile
修饰的变量,表明可能会被意想不到地改变,因此编译器不会从寄存器的备份中读取(因为 内存中的值可能已经被改变了),而要每次小心地从内存中读取。