==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍
目录:
一、基础知识
1.C/C++
3.数据结构与算法
4.计算机网络
5.操作系统
6.数据库
二、项目经历
1.纯属个人YY
一、基础知识
1.C/C++
(1).struct大小的确定
由于内存对齐的原则,在32位机器上,内存是4字节对齐,也就是说,不够4个字节的按 4字节来算。同理,在32位机器上,内存是8字节对齐。
例子:
struct test
{
int a;
char b;
int c;
}TEST;
其大小为4+4+4 = 12b
struct test
{
int a;
char b;
char d;
char e;
char f;
int c;
}TEST;
其大小为4+(1+1+1+1)+4 = 12b
struct test
{
int a;
char b;
int c;
char d;
}TEST;
其大小为4+4+4+4 = 16b
struct test
{
char a;
}TEST;
其大小为1b
struct test
{
char a;
char b;
}TEST;
其大小为2b,说明如果前面的字节+后面的字节不超过内存对齐所需要的字节,是不会用0填充的。实际多少个字节就是多少个字节。
(2).strlen、strcpy的实现
int _strlen(const char* str)
{
if (!str || *str == 0) return 0;
int len = 0;
while (*str++)++len;
return len;
}
char* _strcpy(char* d, const char* s)
{
if (!s || *s == 0) return d ? &(*d = 0) : NULL; //防止d未初始化乱码
char* td = d;
if (d) while (*d++ = *s++); //防止d为NULL
return td;
}
(3).memcpy的实现以及如何防止内存重叠造成的错误。
我没用过未定义的memcpy,我在VC、VS2013、VS2015测试的时候,memcpy已经对内存重叠进行检测了。所以,这里就写一个未检测内存重叠的和检测内存重叠的memcpy。
void* Memcpy(void* des, const void* src, size_t count)
{
if (!src || count <= 0) return des;
char* td = (char*)des;
const char* ts = (const char*) src;
while (count--) *td++ = *ts++;
return des;
}
例子如下:
char* p = new char[6];
memcpy(p, "12345", 10);
printf("%s\n", p);
Memcpy(p + 1, p, 4);
printf("%s\n", p);
void* Memmove(void* des,const void* src,size_t count)
{
if (!src || count <= 0) return des;
char* td = (char*)des;
const char* ts = (char*)src;
//如果源地址 + count 小于目的地址,说明,内存无重叠,进行正向拷贝
if (ts + count < td)
{
while (count--) *td++ = *ts++;
}
//否则有内存重叠,进行逆向拷贝
else
{
char* ttd = td + count - 1;
const char* tts = ts + count - 1;
while (count--) *ttd-- = *tts--;
}
return des;
}
例子如下:
char* p = new char[6];
memcpy(p, "12345", 10);
printf("%s\n", p);
Memmove(p + 1, p, 4);
printf("%s\n", p);
附上一张内存重叠示意图:
(4).全局变量、局部变量、静态变量的作用域以及存放的内存区域。
参考这篇文章http://www.cnblogs.com/bakari/archive/2012/08/05/2623637.html
(5).static关键字的作用
a.在函数体内部定义变量,该变量从程序开始到结束只会分配一次内存,当再次进入该函数的时候,其值不变,仍为上次退出时的值
例子:
int f()
{
static int i = 0;
return ++i;
}
int main()
{
cout << f() << f() << f() << endl;
return 0;
}
结果是321,为什么是321这也是一个点,下面会有解释。
b.模块内的static定义的变量和函数不能被外部引用,唯一的办法是通过一个间接变量或者函数来引用才行。
例子:
A.cpp
static char C = 'A';
char c = C;
static int F()
{
return 5;
}
int ff()
{
return F();
}
B.cpp
int main()
{
extern int ff();
extern char c;
cout << ff() << endl;
cout << c << endl;
return 0;
}
c.类中定义的static变量属于整个类,即类成员变量,与对象无关,只会在运行的时候创建一次。
d.类中定义的static函数属于整个类,及类成员函数,与对象无关,所以不能在静态函数里面使用this指针,这个是对象独有的。
例子:
class TEST
{
public:
static int m_i;
static int f() { return m_i; } //静态成员函数只能引用静态成员变量
};
int TEST::m_i = 6;
int main()
{
cout << TEST::f() << endl;
return 0;
}
(6).const关键字的作用
a.定义普通变量的时候,只能初始化一次,以后不可再修改其值。
b.定义指针变量时,再类型前,则值不能改,再类型后,则其地址不能改,若两个都有,则两者都不能改。
例子:
int a = 2, b = 3;
const int* p1 = &a;
*p1 = b; //值不可以修改
p1 = &b; //地址可以修改
int* const p2 = &a;
*p2 = b; //值可以修改
p2 = &b; //地址不可以修改
c.在函数形参声明中,使用const,可以防止传进来的参数被修改。
d.在类中使用const定义的函数,在函数内部不能修改成员变量的值,但是可以修改传进来的形参值,但是一般不这么用。
(7).sizeof是运算符而不是函数,计算变量或者结构体大小的时候可以不加括号,但是计算类型一定要加,所以计算什么都加就对了。
(8).数组地址问题
例子:
int a[] = { 1,2,3,4,5 };
cout << "a[0]: " << &a[0] << endl;
cout << "a[4]: " << endl;
cout << "(a[0] + 1): " <<&a[0] + 1 << endl;
cout << "(a + 1): " << a + 1 << endl;
cout << "&a + 1: " << &a + 1 << endl;
可以看出来,a和&a的值一样,概念不一样,a表示数组的首地址,而&a表示的是整个对象的首地址,所以当它+1的时候,就会跳出数组的边界。
(9).如何将浮点数分解为整数和小数部分
a.强转为int即为整数部分,然后再减去整数部分即可。
double Modf(double x, int* y)
{
*y = (int)x;
return x - *y;
}
b.直接使用库函数,double modf(double x,double* y);
(10).其它一样,只改变一样,不能使函数重载的是
返回类型。
(11).C的结构体和C++的结构体的区别
a.C结构体内部不允许有函数存在,C++允许
b.内部成员变量权限不同,C的只能是public,C++的可以有三种。
c.C结构体不可以继承,C++可以继承结构体或者类
(12).浅拷贝和深拷贝的原理
其实这两个概念很简单,浅拷贝就是两个对象共享一块内存,其缺点就是当析构一个对象的时候,另一个对象也不存在了,如果再使用它就会发生错误。深拷贝就是完完全全的复制出一个对象,两者在内存上无任何关系。
(13).不能重载的5个运算符
.(成员访问运算符)
->(成员指针运算符)
::(作用域解析运算符)
?(条件运算符)
sizeof运算符
(14)常见的不能声明为虚函数的有哪些?
普通函数(非成员函数)
静态成员函数
内联成员函数
构造函数
友元函数。
(15)C++的静态多态和动态多态
所谓静态多态就是运行前确定类型或者函数调用(也就是编译后确定),其采用的是函数重载和泛型(例如,模板。
所谓动态多态就是运行后确定类型或者函数调用,其采用虚函数实现。
(16)C++虚函数的原理
虚函数由虚表管理,这张表存放着所有虚函数的地址,而类的实例对象维护着一个虚表指针,指向这个表。运行的时候,根据new的对象里面的虚表指针,确定调用的函数。如下例子所示:
class A
{
public:
virtual void f1() { cout << "A...f1()" << endl; }
};
class B :public A
{
public:
void f1() { cout << "B...f1()" << endl; }
};
int main()
{
A* a = new A; //因为不是抽象类,所以可以实例化
B* b = new B; //直接用子类自己实例化
A* pB = new B; //用基类指针指向子类
return 0;
}
这就是为什么能在运行时确定调用哪个函数的原因了,当new B返回B的对象后,里面会有一张B类的虚表,然后根据B的虚表调用B的函数。
(17)C++虚函数占用类的大小
因为只需要维护一个指向虚表的指针,所以大小为4或8个字节
静态成员和函数不计入sizeof中。
(18)new与malloc的区别
参考http://www.cnblogs.com/QG-whz/p/5140930.html这篇文章。
(19)C++中有哪几种数据存储区?
栈、堆、自由存储区、全局/静态存储区、常量存储区
(20)什么是栈溢出?哪些情况下比较容易出现栈溢出?
栈溢出泛指系统维护的栈溢出,因数据压不下去了,导致溢出,此时程序会崩溃。
一般递归深度过大、创建普通数组过大(就是局部变量占用的空间大于栈了就会溢出,new的是堆,不算)。
(21)“#include”后面跟引号与尖括号的区别?
引号编译器会搜索当前工作目录下的头文件,尖括号会搜索安装目录下的头文件。
(22)gcc和g++的区别
参考这篇文章https://my.oschina.net/alphajay/blog/3989
(23)类成员函数的重载、覆盖和重写区别
重载和普通的函数重载一样。
覆盖则基类的函数要加virtual (这就是多态的实现)
如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏,子类就重写了这个函数
如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏,子类就重写了这个函数
(24)构造函数为什么不能是虚函数
虚函数存在于虚表中,而虚表要靠虚表指针维护,而只有实例化后的对象才有虚表指针,而实例化对象就必须调用构造函数初始化对象,所以冲突了,结果就是构造函数不能是虚函数。
(25)printf("%d,%d\n",i++,i++),若i=0,则结果输出什么。
这里有一个点就是,printf和cout输出的时候都是从右至左读取值,所以结果应该是1,0。