C/C++面试知识点总结(一)

==> 学习汇总(持续更新)
==> 从零搭建后端基础设施系列(一)-- 背景介绍


目录:

一、基础知识

    1.C/C++

    2.STL

    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。

_acme_ CSDN认证博客专家 JAVA
座右铭:学而不思则罔,思而不学则殆。
排比句:喜欢探索未知,喜欢解决问题,喜欢旅游摄影,拒绝996ICU。
鸡汤句:机会只会留给有准备的人。
可私信交流疑难问题和工作上遇到的一些实际问题。
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页