听说是一本腾讯员工写的书,快到面试时候,拿来读一下看看,内容好像不太精,少了一丢丢所以然,但是包含了一些对面试有用的知识点,记录一下。
一、编程语言
-
c语言 是面向函数。
-
size_of 对函数来讲是得到 函数返回值类型 的size。
-
字节对齐的规则是按照结构体中长度最长的来对齐,比如说有double类型是8byte,那么20字节的结构体会占用24字节的内存。
-
宏定义一段语句的时候可以使用do…while(0); 这样的话整个代码块还算是一个语句。
-
extern ”C" 告诉编译器用C的方式来编译这一段。
-
空类的占用空间为1byte,有虚函数的类为一个指针的大小为8byte。
-
构造函数顺序:先调用基类的构造函数 -> 再调用成员的构造函数 -> 再调用派生类自己的构造函数。子成员对象的构造按照定义顺序,而不是初始化列表的顺序。析构反过来。
-
重写=覆盖, 覆盖了基类中的virtual函数,必须返回值与参数列表一致,通过不同的指针调用,则调用不同的函数,体现了多态。而重载是参数个数或类型不一致,并无多态的体现,根据参数选择函数运行。最后 重定义(也叫隐藏) 则是重新定义了这个函数,原来的函数被隐藏。
-
构造函数不能为虚函数:一是因为构造时必须要知道确切的类型,二是调用虚函数需要使用虚指针,而没有构造前又没有虚指针。而需要被继承的类最好设置虚析构函数。
-
string类的简易实现:
-
可以参考https://www.cnblogs.com/zhizhan/p/4876093.html
-
内层是字符指针的操作,有一些需要注意的点,以及一些字符串操作。
-
通用构造函数:传入字符串指针,需要判断指针是否空,然后new申请内存存放,如果为空,则需要将 ‘/0’ 放入。
String::String(const char *str)//通用构造函数 { if(!str){ length = 0; data = new char[1]; *data = '\0'; }else{ length = strlen(str); data = new char[length + 1]; strcpy(data, str); } }
-
拷贝构造函数:需要进行深拷贝,不只是复制指针,而是复制内容。string不会为空,则只需要使用成员函数size(),然后复制内容即可。
String::String(const String &str)//拷贝构造函数 { length = str.size(); data = new char[length + 1]; strcpy(data, str.c_str()); }
-
析构函数:delete[] 释放内存,改变长度。
String::~String()//析构函数 { delete []data; length = 0; }
-
拷贝操作符重载:首先要判断自赋值。然后将新的复制过来,长度不定所以需要重新申请内存。
String& String::operator=(const String &str) //重载= { if (this == &str) return *this; delete []data; length = str.length; data = new char[length + 1]; strcpy(data, str.c_str()); return *this; }
-
获取长度函数:内联加快速度。
inline size_t String::size() const//获取长度 { return length; }
-
重载输入运算符:先申请一块足够大的内存用来存放输入字符串,再进行新字符串的生成。这是一个比较简单朴素的实现,网上很多直接is>>str.data的方法是错误的,因为不能确定str.data的大小和即将输入的字符串的大小关系。
istream& operator>>(istream &is, String &str)//输入 { char tem[1000]; //简单的申请一块内存 is >> tem; str.length = strlen(tem); str.data = new char[str.length + 1]; strcpy(str.data, tem); return is; }
-
重载输出运算符,只需简单地输出字符串的内容即可。注意为了实现形如cout<<a<<b的连续输出,这里需要返回输出流。上面的输入也是类似。
ostream& operator<<(ostream &os, String &str)//输出 { os << str.data; return os; }
-
获取C字符串:
inline const char* String::c_str() const//获取C字符串 { return data; }
-
-
vector的简易实现:
- vector底层是一个c格式的数组,通过维护first(开始位置迭代器),last(存储元素的末尾di),end(分配内存的末尾)来工作。
- 书中实现的方式是通过theSize表示存储元素的末尾,theCapacity表示分配内存的末尾。
- 通过一个insert_before来实现push_back、push_front、带参数的构造函数。
- 通过一个成员函数allocator()来分配内存、deallocator()来销毁内存。在容量不够的时候,重新分配内存,然后销毁原来内存。
- 拷贝构造函数文中写的是 *this=other,但这样的话是浅拷贝,数组会被直接copy过来。而stl的vector应该是深拷贝,深拷贝则需要将数组中的元素都拷贝过来,而不是只是将指针指向同一个数组,这样会有问题。POD类型可以直接memncpy(),非POD可以for循环然后new。(POD(Plain Old Data,普通旧数据)是指int,float等兼容C的内置类型,支持memncpy)。
- vector拷贝构造函数——深拷贝也是一个知识点,参考这个博客:https://blog.csdn.net/double_happiness/article/details/71305435。
- map是键值类型的数据结构,采用红黑树,时间Ologn;unordered_map是:哈希表+bucket解决hash冲突(bucket为链表<8或者红黑树>=8),较好情况下是O(1)。不需要有序的情况下,用哈希的更好。
二、编译调试
-
静态链接库和动态链接库的特点,可以复习一下。
-
GCC 和 G++:
- G++会把 .c 和 .cpp 文件都当做C++文件。而GCC会区分。
- G++在编译阶段会调用gcc,因为gcc不能对C++程序进行链接,所以一般都直接用G++。
-
makefile的编写是个问题,没怎么用过,死记硬背也不好记。
-
strace命令可以让你看到一个程序后台运行的系统调用;还可以显示收到的信号;还可以用来统计系统调用的次数等。调试中,可以看到一些系统调用的返回的错误码的值,这样可以定位到程序出错。
-
GDB调试,不仅可以调试运行程序,还可以调试coredump文件。core dump(段错误)是程序发生某些错误时保存下来的一手资料,包含内存值、CPU寄存器的值、函数调用栈等。一般内存越界、多线程竞争问题,非法指针操作,堆栈溢出等情况会触发。
-
top是linux下的性能分析工具,类似任务管理器,显示资源占用情况。可以展示CPU、内存等的总体使用情况,以及各个进程的使用情况。
-
linux下C程序的内存空间布局:
-
堆heap和栈stack的区别:
-
申请方式不同
- 栈由系统自动分,声明在函数中一个局部变量 int 系统自动在栈中开辟空间。
- 堆则需要程序员自己申请,并指明大小。
-
申请响应不同:
- 栈是自动增长的,有空间则可以申请,否则报栈溢出。
- 而堆则是类似段表类型,空闲节点链表,一块一块的内存,分配一块够大的,然后占用一部分,剩下的又被分为一块,放入链表中。
-
申请速度:
- 栈较快,而堆较慢。
-
结构:
- 栈是一块固定大小的空间,有系统限制,一般较小;
- 而堆是链表结构,不连续,有内存碎片,容量较大,受虚拟内存限制 。
-
转载:https://blog.csdn.net/weixin_39596127/article/details/113808198