C++面试题总结-自用+分享-后续更新

#初版2024.3.12
##面试内容:
1、C++11(支持智能指针)C++14、C++17
2、算法与数据结构
3、QT开发
4、python脚本编写
5、SVN或git
6、计算机图形学OpenCV、OPenGL、VTK、OSG
7、分布式软件架构设计、灾备设计、高性能设计、弹性可扩展设计
8、数据库SQL
9、linux平台高并发、多线程、GDB调试、profiling
10、协议:TCP(传输层)/HTTP/HTTPS(应用层)
11、MFC界面 GDI编程
##基础知识篇
1、
注意:应特别注意在 int、指针型变量和“零值”比较的时候,把“零值”放在左边,这样当把“==” 误写成“=”时,编译器可以报错,否则这种逻辑错误不容易发现,并且可能导致很严重的后果。

#include<complex>
//float 型数据与零比较: 
if ( ( flag >= -NORM ) && ( flag <= NORM ) ) 
{ 
 	A; 
} 
//int 型数据与零比较:
int flag;
if(0==flag)
{
	A;
}
//指针型
int * flag;
if(NULL==flag)
{
	A;
}
//bool 型数据与零比较:
if(flag)
{
	A;
}

2、sizeof和strlen的区别
sizeof:操作符;在编译的时候就计算出结果;参数可以是数据类型和变量;参数为数组时候不退化
strlen :库函数;在运行时候才能计算出结果;参数以\0作为结尾;参数为数组时候退化为指针
实现拷贝功能的三种方法:
strcpy ,memcpy ,sprintf

3、malloc和new的区别
new:new 和delete是操作符,在C中可用;调用构造函数和析构函数;返回某种类型的指针
malloc:malloc和free是函数,可以覆盖,C\C++中都可用;仅分配内存和回收内存;返回void类型指针

3、23种设计模式,六大原则(详细下篇具体记笔记)
降低耦合度,减少依赖
开放封闭原则:扩展开放,修改关闭
里氏替换原则:派生类可以替换掉基类,且软件功能不受影响
依赖倒置原则:依赖于抽象而不依赖于具体
接口隔离原则:使用多个接口比使用单个接口要好,降低类之间的耦合度
迪米特法则,最少知道原则:一个实体应该尽少地与实体之间发生作用,使得系统功能模块相对独立
合成复用原则:尽量使用合成/聚合的方式,而不使用继承

创建型模式、结构型模式、行为型模式、J2EE模式
工厂模式(创建型模式)当你需要什么,只需要传入一个正确的参数,就可以获得你所需要的对象,而无需知道其创建细节

4、智能指针
为什么引入智能指针?
防止程序员new之后忘记delete,造成内存泄漏;或者在另外一个指针还在使用同一块内存时,一个指针提前释放此内存,造成引用非法内存的指针。
为了更安全地使用指针,引入了智能指针。
智能指针负责自动释放指定的对象
shared_ptr :允许多个指针指向同一个对象
unique_ptr :独占 不能进行复制,只能进行移动操作
weak_ptr:弱引用,指向shared_ptr所管理的对象 不会对所指堆内存的引用计数+1
头文件:
智能指针代码示例:

5、静态库与动态库
本质区别:是否被编译到程序内部
静态库:劣势:文件大、函数库更新需要重新编译 优势:但是编译成的可执行文件可以独立运行
动态库:在程序中只有一个指向的位置,执行程序需要用到函数库机制时,程序才会读取程序库来调用。
劣势:可执行文件无法单独运行 优势:产品升级方便,直接升级对应动态库即可,不必重新编译整个可执行文件

从产品化的角度,发布的算法库或功能库尽量使动态库,这样方便更新和升级,不必重新编译整个可执行文件,只需新版本动态库替换掉旧动态库即可。
从函数库集成的角度,若要将发布的所有子库(不止一个)集成为一个动态库向外提供接口,那么就需要将所有子库编译为静态库,这样所有子库就可以全部编译进目标动态库中,由最终的一个集成库向外提供功能。

6.Boost库
boost库详解
自己动手编译一个boost库
<1>准备boost源码,目前我用到的源码版本为1.58.0 (源码链接,可参考官网或以下链接 https://sourceforge.net/projects/boost/files/boost/)
<2>解压源码:gzip -d boost_1_58_0.tar.gz
<3>解压源码 :tar -xvf boost_1_58_0.tar
<4>文件夹赋予777权限:chmod -R 777 * 【注:必须要赋予可执行权限,否则会编译失败。】
<5>生成b2, bjam文件:./bootstrap.sh --prefix=/home/admin/boost --with-libraries=all --with-toolset=gcc
–prefix-> 安装路径
–with-libraries -> 编译库
–with-toolset -> 编译工具
<6>、编译,安装:./b2 install
7.cmake
cmake学习指南
8.内存分配的方式:静态存储区、栈区、堆区
静态存储区:例如全局变量、static变量,在程序编译时就已经分配,整个运行期间都会存在;运行速度快,不易出错,系统自动管理回收
栈区:函数内部局部变量,函数执行结束时存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限
堆区:动态内存分配。程序在运行时候用malloc或new申请任意大小的内存,程序员任何时候决定free或者delete。如果不回收则会出现内存泄露的问题,且频繁分配和回收内存不同大小的堆空间容易出现堆内存碎片

9、数组作为参数传递时,退化为一个指针
10、C语言的移位问题
算数右移与逻辑右移
逻辑右移就是不考虑符号位,右移一位,左边补零即可。
算术右移需要考虑符号位,右移一位,若符号位为1,就在左边补1,;否则,就补0。
所以算术右移也可以进行有符号位的除法,右移,n位就等于除2的n次方。
例如,8位二进制数11001101分别右移一位。
逻辑右移就是[0]1100110
算术右移就是[1]1100110
<注意> 不同的编译器,有些是逻辑右移,有些是算数右移。为了保证统一性,定义为unsigned+类型,(采用无符号的数)保证移位运算结果的统一性
<注意>做移位运算的时候要注意:当移位超过类型能表示的最大位数时候,会清零,因此要格外小心。移位数要大于0,小于位数(比如一个char类型的位数是8位,与char类型做与运算或者或运算的时候最多移位小于8)
移位运算:const unsigned char BACK=(1<<6)//表示1向左移动六位0010 0000
为了保证超过最大位数不清零,我们可以使用bitset,输出为二进制,很直观,对初学者十分友好
11、C语言类型转换上的问题
sizeof返回值是一个unsigned类型。在C/C++中尽量避免unsigned值做比较,容易出现补码错误的问题;
整型值/整型值=一个整型值,即使将结果赋值给double,依然是一个整型.0,要想计算出正确的浮点型的值,需要拿至少一个浮点型的值.0做运算。
C++中四种类型转换:static_cast、const_cast、dynamic_cast、reinterpret_cast
static_cast:静态变量类型的强制转换
const_cast:常量转换为非常量的强制转换
dynamic_cast:类中父类和子类之间的强制转换
reinterpret_cast:指针内部任意类型的强制转换

12、C语言整数溢出问题
C语言中的整数不等于数学上的整数
C语言整数溢出可以引入boost库来解决

13、string
使用strcat的时候一定要小心:strcat(s1,s2),s1的长度要能容纳连接字符的总长度
用strcat_s替代strcat,更安全的方法
C语言字符串的典型缺陷:使用strlen(),计算的是字符串的长度(计算到\0之前);使用sizeof,计算的是数组容纳的空间(包括\0)
string.length() string字符串内部内容的实际长度 string.size()和string.length()是同义词,并返回完全相同的值。
string.capacity():返回已分配存储的大小。此容量不一定等于字符串长度。 它可以相等或更大
使用redis

13、C语言typedef和define的区别详解
typedef 是用来定义一种类型的新别名的,它不同于宏(#define),不是简单的字符串替换
#define 只是简单的字符串替换(原地扩展)在预处理时完成

二分查找法的时间复杂度为O(log2n),也叫折半查找法

14、指针
间接访问其它变量地址

15、cmake,CMakeLists.txt,Makefile的区别
手写 CMakeLists.txt 文件,CMake 根据 CMakeLists.txt 文件,生成 Makefile 文件,然后编译工程
[makefile学习总结]
(1)CFLAGS 是一个常用的 Makefile 变量,用于指定 C 语言编译器的参数。

# 设置编译器参数
CFLAGS = -Wall -O2
- `-Wall` 表示开启所有警告信息,这样可以让编译器输出更多有用的警告信息,帮助你发现潜在的问题。

- `-O2` 表示开启优化级别 2,这会让编译器进行更多的优化,生成性能更好的代码。
引入头文件:
# 定义头文件的搜索路径
CFLAGS = -I/path/to/include

-I选项来指定头文件的搜索路径:

```makefile
# 定义头文件的搜索路径
CFLAGS = -I/path/to/include

(2)LDFLAGS 是一个用于指定链接器选项的环境变量
常用参数:

  • -lpthread::POSIX线程库参数

  • -lrt: 这个参数指示链接器去链接 librt 库,librt 是 POSIX 实时扩展库,提供了一些与时间、定时器、互斥锁等相关的函数。常见的函数包括 clock_gettimetimer_create 等。如果你的程序中使用了这些函数,那么就需要链接 librt 库。

  • -ldl: 这个参数指示链接器去链接 libdl 库,libdl 是动态链接库的缩写,提供了动态加载共享库的函数。如果你的程序需要在运行时动态加载共享库,就需要链接 libdl 库。

  • -Wl,-rpath: 这个参数是传递给链接器的选项,其中 -Wl 表示传递参数给链接器,-rpath 用于指定运行时库的搜索路径。这个选项可以用来指定程序在运行时查找共享库的路径。比如,-Wl,-rpath,/path/to/library 会将 /path/to/library 加入到程序的运行时库搜索路径中。
    (3)COMPILE
    配置编译器:g++、gcc
    gcc 用于编译 C 语言代码,而 g++ 用于编译 C++ 语言代码。
    ifeq
    ifeq 是 GNU Make 中的一个条件判断语句,用于判断两个字符串是否相等。它通常用于 Makefile 中,用于根据条件执行不同的命令。ifeq 语句的基本语法如下:
    ifeq 是 GNU Make 中的一个条件判断语句

ifeq (arg1, arg2)
    # 如果 arg1 等于 arg2,则执行这里的命令
else
    # 如果 arg1 不等于 arg2,则执行这里的命令
endif

(4)(.PHONY))用于声明某些目标是“伪目标”,即不表示实际的文件名,而表示一个动作或者一组动作

#在这个Makefile中,声明了四个伪目标(.PHONY),它们分别是all、clean、dep和test。这表示这些目标不对应实际的文件,而是表示执行特定的动作或操作。
.PHONY: all clean dep test

注意:make clean: rm -rf .o 中*和.o 不可以有空格!!!!
16、windows的入口函数:initinstance()
17、定义一个指针 int p 与cout<<(p)
这两个
的意义不同,第一个是int加上
是定义一个指针的类型,输出的
p是取p地址中的内容(即p间接引用的值)

18、const 关键字的作用
1)欲阻止一个变量被改变,可使用const,在定义该const变量时,需先初始化,以后就没有机会改变他了;
2)对指针而言,可以指定指针本身为const,也可以指定指针所指的数据为const,或二者同时指定为const;
3)在一个函数声明中,const可以修饰形参表明他是一个输入参数,在函数内部不可以改变其值;
4)对于类的成员函数,有时候必须指定其为const类型,表明其是一个常函数,不能修改类的成员变量;
5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为“左值”。
static关键字的作用
static修饰局部变量,修饰局部变量,局部变量的生命周期变长,函数执行结束不会立即释放内存
static修饰全局变量,则该变量作用域变小,只能在当前文件使用,其它文件禁止用
static修饰函数,函数作用域变小,只能在当前文件使用
类的静态成员函数属于类,而不属于类的对象

关于const修饰的部分:看左侧最近的部分,如果左侧没有则看右侧

19、什么是死锁
死锁就是线程1占有资源A去访问资源B,线程2占有资源B去访问资源A,这样就造成俩个线程谁也访问不到需要的资源
20、造成死锁的4个必要条件?
(1)互斥,同一时间统一资源只能由一个线程访问
(2)不可剥夺,当一个线程占有某资源时,只有该线程主动放弃该资源,外力无法解除
(3)请求和保持,线程1在占有某资源A的时候还可以请求资源B(吃着碗里看着锅里)
(4)回环,线程1占有资源A去请求资源B,线程2占有资源B去请求资源C,线程3占有资源C去请求资 源A
21、如何避免死锁?
共有资源尽可能简短
线程死锁等待超时则自动放弃请求并自动释放自己所占有的资源
顺序加锁
22、C/C++内存可以划分为几个部分:
堆(例如malloc动态分配内存)、栈(局部变量)、全局(全局变量和静态变量)、常量、程序代码区
23、strchr与strtok
#include<string.h>
char * strtok(char s[],const char * delim) https://blog.csdn.net/weibo1230123/article/details/80177898
strchr() 用于查找字符串中的一个字符,并返回该字符在字符串中第一次出现的位置。
char *strchr(const char *str, int c) str – 要查找的字符串。 c – 要查找的字符。
24、string不能直接输出cout,需要用c_str()转换,接着才能正常输出
25、虚函数与纯虚函数
虚函数在类内定义声明,且必须在类内有实现
纯虚函数需要在类内有声明,并且不给实现; 在类内有声明,且在类外有实现。
虚函数和纯虚函数,只有在声明时候需要加关键字Virtual,实现时候不需要!!!
【注意】空类占一个字节,含有虚函数的类占四个字节(64位系统占8个字节)
26、基类的析构函数必须为【虚析构】
若基类的析构函数不为虚函数,则会导致基类的内存被释放,派生类的内存没有被释放,有内存泄露的风险
使用mmap虚拟内存映射替代read\write,减少频繁的I/O 操作,提高性能
23、【句柄】
本质上是指针,抽象的标识符,提供了安全高效的方式来访问操作系统的资源而无需直接使用资源本身。
文件句柄、窗口句柄、线程句柄
特征:需要手动释放句柄
同一个对象可能有多个句柄
操作系统为每个进程分配到一个句柄空间,不同进程的句柄值可能重复
句柄可以进行访问权限控制
句柄泄露可能导致资源耗尽
24、static关键字的作用
静态局部变量:这将使变量在程序生命周期内只被初始化一次,即使函数被多次调用
静态全局变量:使用 static 关键字在函数外部声明全局变量,将使变量只能在当前文件中访问
静态成员变量:在类中声明的静态成员变量将被所有类对象共享,并且可以在不创建类对象的情况下使用
静态成员函数:在类中声明的静态成员函数可以在不创建类对象的情况下使用,并且不能访问非静态成员变量
25、声明和变量的区别
是否分配存储空间来区分
一个变量可以有多个声明,但只能有一个地方定义
加入extern修饰的变量的声明,说明此变量将在文件以外或在文件后面部分定义
26、写一个标准的宏
#define min(a,b)((a)<=(b)?(a):(b))
使用++时候注意宏的副作用,防止被自增两次 eg:((++*p)<=(x)?(++*p):(x)
27、a和&a的区别
代码示例:

#include<stdio.h> 
void main( void ) 
{ 
 	int a[5]={1,2,3,4,5}; 
 	int *ptr=(int *)(&a+1); 
 	printf("%d,%d",*(a+1),*(ptr-1));  	
 	return; 
} 

int *ptr = (int *)(&a + 1); - 这行代码比较复杂。
首先,&a取得数组a的地址。因为a是一个数组,所以它的地址是整个数组的起始地址。
接着我们对这个地址进行类型转换(int *),将其转换为指向整数的指针。
然后我们加上1,【这里的加法操作实际上是将指针移动了一个整个数组的大小】。最后,将得到的结果赋给ptr。

将原式的 int *ptr=(int *)(&a+1); 改为 int *ptr=(int *)(a+1);时输出结果将是什么呢?【答案:2,1】
当将原始代码中的 int *ptr=(int *)(&a+1); 改为 int *ptr=(int *)(a+1); 时,指针ptr将指向数组a中的第二个元素。
因此,原来的printf语句 printf("%d,%d",*(a+1),*(ptr-1)) 将会输出数组a的第二个和第一个元素的值。
换句话说, *(a+1) 将输出数组a的第二个元素的值,而 *(ptr-1) 将输出数组a的第一个元素的值。

#3.16二次更新
28、介绍一下POSIX
POSIX(可移植操作系统接口)
linux 下使用 POSIX 线程库
先来了解下Windows API 中,ResetEvent 函数用于将事件重置为无信号状态,_beginthread创建线程,_endthread()终止线程
在 Linux 下,可以使用条件变量和互斥锁来实现类似的功能
pthread_create(&thread, NULL, threadFunction, NULL);//创建线程
pthread_cond_signal(pthread_cond_t *cond); //表示信号通知
pthread_cond_wait(&condition, &mutex); //表示等待信号通知
代码示例:

#include<iostream>
#include<stdio.h>
#include<pthread.h>
#include <unistd.h>
pthread_mutex_t mymutex=PTHREAD_MUTEX_INITIALIZER;
void * thread_Function(void *arg);
int main(void)
{
	int ThreadNo;
	pthread_t hThread;
	if(pthread_create(&hThread,NULL,thread_Function,NULL)==0)
	{
		printf("线程启动成功");
	}
	for(int i=0;i<20;i++)
	{
		pthread_mutex_lock(&mymutex);
		ThreadNo++;
		pthread_mutex_unlock(&mymutex);
	}
	if(pthread_join(hThread,NULL)==0)
	{
		printf("线程停止成功");
	}
	printf("启动线程数为%d",ThreadNo);
	return 0;
}
void * thread_Function(void *arg)
{
	int i=0;
	pthread_mutex_lock(&mymutex);
	for(int i=0;i<20;i++)
		printf("thread_Function[%d]\t",i);
	pthread_mutex_unlock(&mymutex);
}

29、STL
STL的组成:算法、容器、迭代器
算法:排序、复制等常用算法以及一些特定算法
容器:关联式容器(map、set等)和序列式容器(List、Vector等)
迭代器:不暴露容器内部结构的前提下对容器进行遍历
3.18日更新(以下+NO15.)
30、bool 和 BOOL
类型不同:bool是C++定义的布尔类型,BOOL是微软定义的int类型(就是int的别名);
取值不同:bool的取值只有0(false)、1(true)两个,而BOOL的取值是int范围;
长度不同:bool是单字节(8位二进制)类型,BOOL与int长度相同。
31、友元函数、友元类
特殊的访问控制机制,允许一个类或者函数访问另一个类的私有成员
友元函数可以看成是类扩展接口的组成部分
友元函数是在类外定义的,没有this指针
友元函数的函数原型位于在类声明中,但并不是类的成员函数,在声明函数原型时,需要使用 friend 关键字。
友元函数的函数定义不需要 friend 关键字和 :: 运算符,和普通非成员函数是一样的
友元函数可以直接访问类的私有成员
通常在运算符重载中使用友元函数的情况是:重载的运算符的第一个参数不是该类的对象

4.8日更新(以下+NO6.)
32、引用:给变量起别名
类型& 引用变量名(对象名)= 引用实体;
编译器不会为引用变量开辟内存空间,和引用的变量共用一块内存。注意:引用类型必须和引用实体是同种类型的。

#include <iostream>
void Test()
{
  int a=10;
  int &b=a;
  printf("%p \n",&a);
  printf("%p\n",&b);
}
void main()
{
Test();
}

运行结果
运行结果
使用场景:

//引用做参数
void Swap(int& x, int& y)
{
	int temp = x;
	x = y;
	y = temp;
}
//引用做返回值
int &Count()
{
static int a=0;
a++;
return a;
}

传值、传引用效率比较
以值作为参数或者返回类值型,在传值和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。

#include<time.h>
#include<iostream>
using namespace std;
struct A { int a[10000]; };
void Test1(A a) {}
void Test2(A &a){}
void Test()
{
	A a;
	//值作为函数参数
	size_t beginTime1 = clock();  //size_t 无符号型整数 unsigned int
	for (size_t i=0;i<100000;i++)
	{
		Test1(a);
	}
	size_t EndTime1 = clock();

	//引用作为函数参数
	size_t beginTime2 = clock();
	for (size_t i = 0; i < 100000; i++)
	{
		Test2(a);
	}
	size_t EndTime2 = clock();
	cout << "Test1(A).Time=" << EndTime1 - beginTime1 << endl;
	cout<< "Test1(&A).Time=" << EndTime2 - beginTime2 << endl;

}
void main()
{
	Test();
	system("PAUSE");
}

运行结果比对
在这里插入图片描述

值和引用的作为返回值类型的性能比较

using namespace std;
#include<time.h>
struct A { int a[10000]; };
A a;
A Test1 (){ return a; }
A &Test2(){ return a; }
void Test()
{
	
	//值作为函数参数
	size_t beginTime1 = clock();  //size_t 无符号型整数 unsigned int
	for (size_t i=0;i<100000;i++)
	{
		Test1();
	}
	size_t EndTime1 = clock();

	//引用作为函数参数
	size_t beginTime2 = clock();
	for (size_t i = 0; i < 100000; i++)
	{
		Test2();
	}
	size_t EndTime2 = clock();
	cout << "Test1(A).Time=" << EndTime1 - beginTime1 << endl;
	cout<< "Test1(&A).Time=" << EndTime2 - beginTime2 << endl;

}
void main()
{
	Test();
	system("PAUSE");
}

运行结果
运行结果

引用和指针的不同点:

1. 引用在定义时必须初始化,指针没有要求
2. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
3. 没有NULL引用,但有NULL指针
4. 在 sizeof 中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数 (32位平台下占4个字节)
5. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
6. 有多级指针,但是没有多级引用
7. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
8. 引用比指针使用起来相对更安全

4.22日更新
4.22日更新
33、32位和64位操作系统下不同的占用字节
DateType Win32 Win64
int 4 4
long 4 8
short int 2 2
long int 4 4
long long 8 8
signed/unsigned int 4 4
signed/unsigned short int 2 2
signed/unsigned long int 4 4
char 1 1
float 4 4
double 8 8
signed float 4 4
signed double 8 8
long double 8 8
指针 4 8
函数 除了void类型外,其它的函数占用字节等于函数的返回值类型所占用的字节,与函数内部无关
结构体 内部个数据类型占用之和(注意边界对应)
联合体 取其中占用字节数最大的数据类型所占用的字节数

  • 36
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值